1
0
Fork 0

Merge remote-tracking branch 'xelaris/json-output-for-show-cmd'

pull/6093/merge
Jordi Boggiano 2017-03-07 14:08:36 +01:00
commit f5e026c6c5
1 changed files with 165 additions and 88 deletions

View File

@ -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,101 +328,137 @@ 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();
$remaining = $width - $nameLength - $versionLength - 4;
if ($writeLatest) {
$remaining -= $latestLength;
}
if (strlen($description) > $remaining) {
$description = substr($description, 0, $remaining - 3) . '...';
}
$io->write(' ' . $description, false);
} }
if ($writePath) { if ($writePath) {
$path = strtok(realpath($composer->getInstallationManager()->getInstallPath($package)), "\r\n"); $packageViewData['path'] = strtok(realpath($composer->getInstallationManager()->getInstallPath($package)), "\r\n");
$io->write(' ' . $path, false);
} }
if ($latestPackackage && $latestPackackage->isAbandoned()) { if ($latestPackage && $latestPackage->isAbandoned()) {
$replacement = (is_string($latestPackackage->getReplacementPackage())) $replacement = (is_string($latestPackage->getReplacementPackage()))
? 'Use ' . $latestPackackage->getReplacementPackage() . ' instead' ? 'Use ' . $latestPackage->getReplacementPackage() . ' instead'
: 'No replacement was suggested'; : 'No replacement was suggested';
$packageWarning = sprintf(
$io->writeError(''); 'Package %s is abandoned, you should avoid using it. %s.',
$io->writeError( $package->getPrettyName(),
sprintf( $replacement
"<warning>Package %s is abandoned, you should avoid using it. %s.</warning>",
$package->getPrettyName(),
$replacement
),
false
); );
$packageViewData['warning'] = $packageWarning;
} }
} else { } else {
$io->write($indent . $package, false); $packageViewData['name'] = $package;
} }
$io->write(''); $viewData[$type][] = $packageViewData;
}
if ($showAllTypes) {
$io->write('');
} }
if ($input->getOption('strict') && $hasOutdatedPackages) { if ($input->getOption('strict') && $hasOutdatedPackages) {
return 1; $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;
if ($writeLatest) {
$remaining -= $latestLength;
}
if (strlen($description) > $remaining) {
$description = substr($description, 0, $remaining - 3) . '...';
}
$io->write(' ' . $description, false);
}
if (isset($package['path'])) {
$io->write(' ' . $package['path'], false);
}
if (isset($package['warning'])) {
$io->writeError('');
$io->writeError('<warning>' . $package['warning'] . '</warning>', false);
}
$io->write('');
}
if ($showAllTypes) {
$io->write('');
}
}
}
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();