Merge remote-tracking branch 'xelaris/json-output-for-show-cmd'
commit
f5e026c6c5
|
@ -14,6 +14,7 @@ namespace Composer\Command;
|
||||||
|
|
||||||
use Composer\DependencyResolver\Pool;
|
use Composer\DependencyResolver\Pool;
|
||||||
use Composer\DependencyResolver\DefaultPolicy;
|
use Composer\DependencyResolver\DefaultPolicy;
|
||||||
|
use Composer\Json\JsonFile;
|
||||||
use Composer\Package\CompletePackageInterface;
|
use Composer\Package\CompletePackageInterface;
|
||||||
use Composer\Package\Version\VersionParser;
|
use Composer\Package\Version\VersionParser;
|
||||||
use Composer\Package\BasePackage;
|
use Composer\Package\BasePackage;
|
||||||
|
@ -74,6 +75,7 @@ class ShowCommand extends BaseCommand
|
||||||
new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates. Use with the --outdated option.'),
|
new InputOption('minor-only', 'm', InputOption::VALUE_NONE, 'Show only packages that have minor SemVer-compatible updates. Use with the --outdated option.'),
|
||||||
new InputOption('direct', 'D', InputOption::VALUE_NONE, 'Shows only packages that are directly required by the root package'),
|
new InputOption('direct', 'D', InputOption::VALUE_NONE, 'Shows only packages that are directly required by the root package'),
|
||||||
new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'),
|
new InputOption('strict', null, InputOption::VALUE_NONE, 'Return a non-zero exit code when there are outdated packages'),
|
||||||
|
new InputOption('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text'),
|
||||||
))
|
))
|
||||||
->setHelp(<<<EOT
|
->setHelp(<<<EOT
|
||||||
The show command displays detailed information about a package, or
|
The show command displays detailed information about a package, or
|
||||||
|
@ -114,6 +116,13 @@ EOT
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$format = $input->getOption('format');
|
||||||
|
if (!in_array($format, array('text', 'json'))) {
|
||||||
|
$io->writeError(sprintf('Unsupported format "%s". See help for supported formats.', $format));
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
// init repos
|
// init repos
|
||||||
$platformOverrides = array();
|
$platformOverrides = array();
|
||||||
if ($composer) {
|
if ($composer) {
|
||||||
|
@ -163,6 +172,9 @@ EOT
|
||||||
|
|
||||||
// show single package or single version
|
// show single package or single version
|
||||||
if (($packageFilter && false === strpos($packageFilter, '*')) || !empty($package)) {
|
if (($packageFilter && false === strpos($packageFilter, '*')) || !empty($package)) {
|
||||||
|
if ('json' === $format) {
|
||||||
|
$io->writeError('Format "json" is only supported for package listings, falling back to format "text"');
|
||||||
|
}
|
||||||
if (empty($package)) {
|
if (empty($package)) {
|
||||||
list($package, $versions) = $this->getPackage($installedRepo, $repos, $input->getArgument('package'), $input->getArgument('version'));
|
list($package, $versions) = $this->getPackage($installedRepo, $repos, $input->getArgument('package'), $input->getArgument('version'));
|
||||||
|
|
||||||
|
@ -205,6 +217,9 @@ EOT
|
||||||
|
|
||||||
// show tree view if requested
|
// show tree view if requested
|
||||||
if ($input->getOption('tree')) {
|
if ($input->getOption('tree')) {
|
||||||
|
if ('json' === $format) {
|
||||||
|
$io->writeError('Format "json" is only supported for package listings, falling back to format "text"');
|
||||||
|
}
|
||||||
$rootRequires = $this->getRootRequires();
|
$rootRequires = $this->getRootRequires();
|
||||||
foreach ($installedRepo->getPackages() as $package) {
|
foreach ($installedRepo->getPackages() as $package) {
|
||||||
if (in_array($package->getName(), $rootRequires, true)) {
|
if (in_array($package->getName(), $rootRequires, true)) {
|
||||||
|
@ -232,16 +247,31 @@ EOT
|
||||||
$packageListFilter = $this->getRootRequires();
|
$packageListFilter = $this->getRootRequires();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
list($width) = $this->getApplication()->getTerminalDimensions();
|
||||||
|
if (null === $width) {
|
||||||
|
// In case the width is not detected, we're probably running the command
|
||||||
|
// outside of a real terminal, use space without a limit
|
||||||
|
$width = PHP_INT_MAX;
|
||||||
|
}
|
||||||
|
if (Platform::isWindows()) {
|
||||||
|
$width--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($input->getOption('path') && null === $composer) {
|
||||||
|
$io->writeError('No composer.json found in the current directory, disabling "path" option');
|
||||||
|
$input->setOption('path', false);
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($repos as $repo) {
|
foreach ($repos as $repo) {
|
||||||
if ($repo === $platformRepo) {
|
if ($repo === $platformRepo) {
|
||||||
$type = '<info>platform</info>:';
|
$type = 'platform';
|
||||||
} elseif (
|
} elseif (
|
||||||
$repo === $installedRepo
|
$repo === $installedRepo
|
||||||
|| ($installedRepo instanceof CompositeRepository && in_array($repo, $installedRepo->getRepositories(), true))
|
|| ($installedRepo instanceof CompositeRepository && in_array($repo, $installedRepo->getRepositories(), true))
|
||||||
) {
|
) {
|
||||||
$type = '<info>installed</info>:';
|
$type = 'installed';
|
||||||
} else {
|
} else {
|
||||||
$type = '<comment>available</comment>:';
|
$type = 'available';
|
||||||
}
|
}
|
||||||
if ($repo instanceof ComposerRepository && $repo->hasProviders()) {
|
if ($repo instanceof ComposerRepository && $repo->hasProviders()) {
|
||||||
foreach ($repo->getProviderNames() as $name) {
|
foreach ($repo->getProviderNames() as $name) {
|
||||||
|
@ -270,11 +300,11 @@ EOT
|
||||||
$showMinorOnly = $input->getOption('minor-only');
|
$showMinorOnly = $input->getOption('minor-only');
|
||||||
$indent = $showAllTypes ? ' ' : '';
|
$indent = $showAllTypes ? ' ' : '';
|
||||||
$latestPackages = array();
|
$latestPackages = array();
|
||||||
foreach (array('<info>platform</info>:' => true, '<comment>available</comment>:' => false, '<info>installed</info>:' => true) as $type => $showVersion) {
|
$exitCode = 0;
|
||||||
|
$viewData = array();
|
||||||
|
$viewMetaData = array();
|
||||||
|
foreach (array('platform' => true, 'available' => false, 'installed' => true) as $type => $showVersion) {
|
||||||
if (isset($packages[$type])) {
|
if (isset($packages[$type])) {
|
||||||
if ($showAllTypes) {
|
|
||||||
$io->write($type);
|
|
||||||
}
|
|
||||||
ksort($packages[$type]);
|
ksort($packages[$type]);
|
||||||
|
|
||||||
$nameLength = $versionLength = $latestLength = 0;
|
$nameLength = $versionLength = $latestLength = 0;
|
||||||
|
@ -298,58 +328,111 @@ EOT
|
||||||
$nameLength = max($nameLength, strlen($package));
|
$nameLength = max($nameLength, strlen($package));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
list($width) = $this->getApplication()->getTerminalDimensions();
|
|
||||||
if (null === $width) {
|
|
||||||
// In case the width is not detected, we're probably running the command
|
|
||||||
// outside of a real terminal, use space without a limit
|
|
||||||
$width = PHP_INT_MAX;
|
|
||||||
}
|
|
||||||
if (Platform::isWindows()) {
|
|
||||||
$width--;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($input->getOption('path') && null === $composer) {
|
|
||||||
$io->writeError('No composer.json found in the current directory, disabling "path" option');
|
|
||||||
$input->setOption('path', false);
|
|
||||||
}
|
|
||||||
|
|
||||||
$writePath = !$input->getOption('name-only') && $input->getOption('path');
|
$writePath = !$input->getOption('name-only') && $input->getOption('path');
|
||||||
$writeVersion = !$input->getOption('name-only') && !$input->getOption('path') && $showVersion && ($nameLength + $versionLength + 3 <= $width);
|
$writeVersion = !$input->getOption('name-only') && !$input->getOption('path') && $showVersion;
|
||||||
$writeLatest = $writeVersion && $showLatest && ($nameLength + $versionLength + $latestLength + 3 <= $width);
|
$writeLatest = $writeVersion && $showLatest;
|
||||||
$writeDescription = !$input->getOption('name-only') && !$input->getOption('path') && ($nameLength + $versionLength + $latestLength + 24 <= $width);
|
$writeDescription = !$input->getOption('name-only') && !$input->getOption('path');
|
||||||
if ($writeLatest && !$io->isDecorated()) {
|
|
||||||
$latestLength += 2;
|
|
||||||
}
|
|
||||||
$hasOutdatedPackages = false;
|
$hasOutdatedPackages = false;
|
||||||
|
|
||||||
|
$viewData[$type] = array();
|
||||||
|
$viewMetaData[$type] = array(
|
||||||
|
'nameLength' => $nameLength,
|
||||||
|
'versionLength' => $versionLength,
|
||||||
|
'latestLength' => $latestLength,
|
||||||
|
);
|
||||||
foreach ($packages[$type] as $package) {
|
foreach ($packages[$type] as $package) {
|
||||||
|
$packageViewData = array();
|
||||||
if (is_object($package)) {
|
if (is_object($package)) {
|
||||||
$latestPackackage = null;
|
$latestPackage = null;
|
||||||
if ($showLatest && isset($latestPackages[$package->getPrettyName()])) {
|
if ($showLatest && isset($latestPackages[$package->getPrettyName()])) {
|
||||||
$latestPackackage = $latestPackages[$package->getPrettyName()];
|
$latestPackage = $latestPackages[$package->getPrettyName()];
|
||||||
}
|
}
|
||||||
if ($input->getOption('outdated') && $latestPackackage && $latestPackackage->getFullPrettyVersion() === $package->getFullPrettyVersion() && !$latestPackackage->isAbandoned()) {
|
if ($input->getOption('outdated') && $latestPackage && $latestPackage->getFullPrettyVersion() === $package->getFullPrettyVersion() && !$latestPackage->isAbandoned()) {
|
||||||
continue;
|
continue;
|
||||||
} elseif ($input->getOption('outdated')) {
|
} elseif ($input->getOption('outdated')) {
|
||||||
$hasOutdatedPackages = true;
|
$hasOutdatedPackages = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
$io->write($indent . str_pad($package->getPrettyName(), $nameLength, ' '), false);
|
$packageViewData['name'] = $package->getPrettyName();
|
||||||
|
|
||||||
if ($writeVersion) {
|
if ($writeVersion) {
|
||||||
$io->write(' ' . str_pad($package->getFullPrettyVersion(), $versionLength, ' '), false);
|
$packageViewData['version'] = $package->getFullPrettyVersion();
|
||||||
}
|
}
|
||||||
|
if ($writeLatest && $latestPackage) {
|
||||||
if ($writeLatest && $latestPackackage) {
|
$packageViewData['latest'] = $latestPackage->getFullPrettyVersion();
|
||||||
$latestVersion = $latestPackackage->getFullPrettyVersion();
|
$packageViewData['status'] = $this->getUpdateStatus($latestPackage, $package);
|
||||||
$style = $this->getVersionStyle($latestPackackage, $package);
|
|
||||||
if (!$io->isDecorated()) {
|
|
||||||
$latestVersion = str_replace(array('info', 'highlight', 'comment'), array('=', '!', '~'), $style) . ' ' . $latestVersion;
|
|
||||||
}
|
}
|
||||||
$io->write(' <'.$style.'>' . str_pad($latestVersion, $latestLength, ' ') . '</'.$style.'>', false);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($writeDescription) {
|
if ($writeDescription) {
|
||||||
$description = strtok($package->getDescription(), "\r\n");
|
$packageViewData['description'] = $package->getDescription();
|
||||||
|
}
|
||||||
|
if ($writePath) {
|
||||||
|
$packageViewData['path'] = strtok(realpath($composer->getInstallationManager()->getInstallPath($package)), "\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($latestPackage && $latestPackage->isAbandoned()) {
|
||||||
|
$replacement = (is_string($latestPackage->getReplacementPackage()))
|
||||||
|
? 'Use ' . $latestPackage->getReplacementPackage() . ' instead'
|
||||||
|
: 'No replacement was suggested';
|
||||||
|
$packageWarning = sprintf(
|
||||||
|
'Package %s is abandoned, you should avoid using it. %s.',
|
||||||
|
$package->getPrettyName(),
|
||||||
|
$replacement
|
||||||
|
);
|
||||||
|
$packageViewData['warning'] = $packageWarning;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$packageViewData['name'] = $package;
|
||||||
|
}
|
||||||
|
$viewData[$type][] = $packageViewData;
|
||||||
|
}
|
||||||
|
if ($input->getOption('strict') && $hasOutdatedPackages) {
|
||||||
|
$exitCode = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('json' === $format) {
|
||||||
|
$io->write(JsonFile::encode($viewData));
|
||||||
|
} else {
|
||||||
|
foreach ($viewData as $type => $packages) {
|
||||||
|
$nameLength = $viewMetaData[$type]['nameLength'];
|
||||||
|
$versionLength = $viewMetaData[$type]['versionLength'];
|
||||||
|
$latestLength = $viewMetaData[$type]['latestLength'];
|
||||||
|
|
||||||
|
$writeVersion = $nameLength + $versionLength + 3 <= $width;
|
||||||
|
$writeLatest = $nameLength + $versionLength + $latestLength + 3 <= $width;
|
||||||
|
$writeDescription = $nameLength + $versionLength + $latestLength + 24 <= $width;
|
||||||
|
|
||||||
|
if ($writeLatest && !$io->isDecorated()) {
|
||||||
|
$latestLength += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($showAllTypes) {
|
||||||
|
if ('available' === $type) {
|
||||||
|
$io->write('<comment>' . $type . '</comment>:');
|
||||||
|
} else {
|
||||||
|
$io->write('<info>' . $type . '</info>:');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($packages as $package) {
|
||||||
|
$io->write($indent . str_pad($package['name'], $nameLength, ' '), false);
|
||||||
|
if (isset($package['version']) && $writeVersion) {
|
||||||
|
$io->write(' ' . str_pad($package['version'], $versionLength, ' '), false);
|
||||||
|
}
|
||||||
|
if (isset($package['latest']) && $writeLatest) {
|
||||||
|
$latestVersion = $package['latest'];
|
||||||
|
$updateStatus = $package['status'];
|
||||||
|
$style = $this->updateStatusToVersionStyle($updateStatus);
|
||||||
|
if (!$io->isDecorated()) {
|
||||||
|
$latestVersion = str_replace(array('up-to-date', 'update-recommended', 'update-possible'), array('=', '!', '~'), $updateStatus) . ' ' . $latestVersion;
|
||||||
|
}
|
||||||
|
$io->write(' <' . $style . '>' . str_pad($latestVersion, $latestLength, ' ') . '</' . $style . '>', false);
|
||||||
|
}
|
||||||
|
if (isset($package['description']) && $writeDescription) {
|
||||||
|
$description = strtok($package['description'], "\r\n");
|
||||||
$remaining = $width - $nameLength - $versionLength - 4;
|
$remaining = $width - $nameLength - $versionLength - 4;
|
||||||
if ($writeLatest) {
|
if ($writeLatest) {
|
||||||
$remaining -= $latestLength;
|
$remaining -= $latestLength;
|
||||||
|
@ -359,40 +442,23 @@ EOT
|
||||||
}
|
}
|
||||||
$io->write(' ' . $description, false);
|
$io->write(' ' . $description, false);
|
||||||
}
|
}
|
||||||
|
if (isset($package['path'])) {
|
||||||
if ($writePath) {
|
$io->write(' ' . $package['path'], false);
|
||||||
$path = strtok(realpath($composer->getInstallationManager()->getInstallPath($package)), "\r\n");
|
|
||||||
$io->write(' ' . $path, false);
|
|
||||||
}
|
}
|
||||||
|
if (isset($package['warning'])) {
|
||||||
if ($latestPackackage && $latestPackackage->isAbandoned()) {
|
|
||||||
$replacement = (is_string($latestPackackage->getReplacementPackage()))
|
|
||||||
? 'Use ' . $latestPackackage->getReplacementPackage() . ' instead'
|
|
||||||
: 'No replacement was suggested';
|
|
||||||
|
|
||||||
$io->writeError('');
|
$io->writeError('');
|
||||||
$io->writeError(
|
$io->writeError('<warning>' . $package['warning'] . '</warning>', false);
|
||||||
sprintf(
|
|
||||||
"<warning>Package %s is abandoned, you should avoid using it. %s.</warning>",
|
|
||||||
$package->getPrettyName(),
|
|
||||||
$replacement
|
|
||||||
),
|
|
||||||
false
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$io->write($indent . $package, false);
|
|
||||||
}
|
}
|
||||||
$io->write('');
|
$io->write('');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($showAllTypes) {
|
if ($showAllTypes) {
|
||||||
$io->write('');
|
$io->write('');
|
||||||
}
|
}
|
||||||
if ($input->getOption('strict') && $hasOutdatedPackages) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $exitCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getRootRequires()
|
protected function getRootRequires()
|
||||||
|
@ -406,22 +472,7 @@ EOT
|
||||||
|
|
||||||
protected function getVersionStyle(PackageInterface $latestPackage, PackageInterface $package)
|
protected function getVersionStyle(PackageInterface $latestPackage, PackageInterface $package)
|
||||||
{
|
{
|
||||||
if ($latestPackage->getFullPrettyVersion() === $package->getFullPrettyVersion()) {
|
return $this->updateStatusToVersionStyle($this->getUpdateStatus($latestPackage, $package));
|
||||||
// print green as it's up to date
|
|
||||||
return 'info';
|
|
||||||
}
|
|
||||||
|
|
||||||
$constraint = $package->getVersion();
|
|
||||||
if (0 !== strpos($constraint, 'dev-')) {
|
|
||||||
$constraint = '^'.$constraint;
|
|
||||||
}
|
|
||||||
if ($latestPackage->getVersion() && Semver::satisfies($latestPackage->getVersion(), $constraint)) {
|
|
||||||
// print red as it needs an immediate semver-compliant upgrade
|
|
||||||
return 'highlight';
|
|
||||||
}
|
|
||||||
|
|
||||||
// print yellow as it needs an upgrade but has potential BC breaks so is not urgent
|
|
||||||
return 'comment';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -716,6 +767,32 @@ EOT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function updateStatusToVersionStyle($updateStatus)
|
||||||
|
{
|
||||||
|
// 'up-to-date' is printed green
|
||||||
|
// 'update-recommended' is printed red
|
||||||
|
// 'upgrade-possible' is printed yellow
|
||||||
|
return str_replace(array('up-to-date', 'update-recommended', 'update-possible'), array('info', 'highlight', 'comment'), $updateStatus);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getUpdateStatus(PackageInterface $latestPackage, PackageInterface $package) {
|
||||||
|
if ($latestPackage->getFullPrettyVersion() === $package->getFullPrettyVersion()) {
|
||||||
|
return 'up-to-date';
|
||||||
|
}
|
||||||
|
|
||||||
|
$constraint = $package->getVersion();
|
||||||
|
if (0 !== strpos($constraint, 'dev-')) {
|
||||||
|
$constraint = '^'.$constraint;
|
||||||
|
}
|
||||||
|
if ($latestPackage->getVersion() && Semver::satisfies($latestPackage->getVersion(), $constraint)) {
|
||||||
|
// it needs an immediate semver-compliant upgrade
|
||||||
|
return 'update-recommended';
|
||||||
|
}
|
||||||
|
|
||||||
|
// it needs an upgrade but has potential BC breaks so is not urgent
|
||||||
|
return 'update-possible';
|
||||||
|
}
|
||||||
|
|
||||||
private function writeTreeLine($line)
|
private function writeTreeLine($line)
|
||||||
{
|
{
|
||||||
$io = $this->getIO();
|
$io = $this->getIO();
|
||||||
|
|
Loading…
Reference in New Issue