Browse Source

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

Jordi Boggiano 8 years ago
parent
commit
f5e026c6c5
1 changed files with 163 additions and 86 deletions
  1. 163 86
      src/Composer/Command/ShowCommand.php

+ 163 - 86
src/Composer/Command/ShowCommand.php

@@ -14,6 +14,7 @@ namespace Composer\Command;
 
 use Composer\DependencyResolver\Pool;
 use Composer\DependencyResolver\DefaultPolicy;
+use Composer\Json\JsonFile;
 use Composer\Package\CompletePackageInterface;
 use Composer\Package\Version\VersionParser;
 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('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('format', 'f', InputOption::VALUE_REQUIRED, 'Format of the output: text or json', 'text'),
             ))
             ->setHelp(<<<EOT
 The show command displays detailed information about a package, or
@@ -114,6 +116,13 @@ EOT
             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
         $platformOverrides = array();
         if ($composer) {
@@ -163,6 +172,9 @@ EOT
 
         // show single package or single version
         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)) {
                 list($package, $versions) = $this->getPackage($installedRepo, $repos, $input->getArgument('package'), $input->getArgument('version'));
 
@@ -205,6 +217,9 @@ EOT
 
         // show tree view if requested
         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();
             foreach ($installedRepo->getPackages() as $package) {
                 if (in_array($package->getName(), $rootRequires, true)) {
@@ -232,16 +247,31 @@ EOT
             $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) {
             if ($repo === $platformRepo) {
-                $type = '<info>platform</info>:';
+                $type = 'platform';
             } elseif (
                 $repo === $installedRepo
                 || ($installedRepo instanceof CompositeRepository && in_array($repo, $installedRepo->getRepositories(), true))
             ) {
-                $type = '<info>installed</info>:';
+                $type = 'installed';
             } else {
-                $type = '<comment>available</comment>:';
+                $type = 'available';
             }
             if ($repo instanceof ComposerRepository && $repo->hasProviders()) {
                 foreach ($repo->getProviderNames() as $name) {
@@ -270,11 +300,11 @@ EOT
         $showMinorOnly = $input->getOption('minor-only');
         $indent = $showAllTypes ? '  ' : '';
         $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 ($showAllTypes) {
-                    $io->write($type);
-                }
                 ksort($packages[$type]);
 
                 $nameLength = $versionLength = $latestLength = 0;
@@ -298,101 +328,137 @@ EOT
                         $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');
-                $writeVersion = !$input->getOption('name-only') && !$input->getOption('path') && $showVersion && ($nameLength + $versionLength + 3 <= $width);
-                $writeLatest = $writeVersion && $showLatest && ($nameLength + $versionLength + $latestLength + 3 <= $width);
-                $writeDescription = !$input->getOption('name-only') && !$input->getOption('path') && ($nameLength + $versionLength + $latestLength + 24 <= $width);
-                if ($writeLatest && !$io->isDecorated()) {
-                    $latestLength += 2;
-                }
+                $writeVersion = !$input->getOption('name-only') && !$input->getOption('path') && $showVersion;
+                $writeLatest = $writeVersion && $showLatest;
+                $writeDescription = !$input->getOption('name-only') && !$input->getOption('path');
+
                 $hasOutdatedPackages = false;
+
+                $viewData[$type] = array();
+                $viewMetaData[$type] = array(
+                    'nameLength' => $nameLength,
+                    'versionLength' => $versionLength,
+                    'latestLength' => $latestLength,
+                );
                 foreach ($packages[$type] as $package) {
+                    $packageViewData = array();
                     if (is_object($package)) {
-                        $latestPackackage = null;
+                        $latestPackage = null;
                         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;
                         } elseif ($input->getOption('outdated')) {
                             $hasOutdatedPackages = true;
                         }
 
-                        $io->write($indent . str_pad($package->getPrettyName(), $nameLength, ' '), false);
-
+                        $packageViewData['name'] = $package->getPrettyName();
                         if ($writeVersion) {
-                            $io->write(' ' . str_pad($package->getFullPrettyVersion(), $versionLength, ' '), false);
+                            $packageViewData['version'] = $package->getFullPrettyVersion();
                         }
-
-                        if ($writeLatest && $latestPackackage) {
-                            $latestVersion = $latestPackackage->getFullPrettyVersion();
-                            $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 ($writeLatest && $latestPackage) {
+                            $packageViewData['latest'] = $latestPackage->getFullPrettyVersion();
+                            $packageViewData['status'] = $this->getUpdateStatus($latestPackage, $package);
                         }
-
                         if ($writeDescription) {
-                            $description = strtok($package->getDescription(), "\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);
+                            $packageViewData['description'] = $package->getDescription();
                         }
-
                         if ($writePath) {
-                            $path = strtok(realpath($composer->getInstallationManager()->getInstallPath($package)), "\r\n");
-                            $io->write(' ' . $path, false);
+                            $packageViewData['path'] = strtok(realpath($composer->getInstallationManager()->getInstallPath($package)), "\r\n");
                         }
 
-                        if ($latestPackackage && $latestPackackage->isAbandoned()) {
-                            $replacement = (is_string($latestPackackage->getReplacementPackage()))
-                                ? 'Use ' . $latestPackackage->getReplacementPackage() . ' instead'
+                        if ($latestPackage && $latestPackage->isAbandoned()) {
+                            $replacement = (is_string($latestPackage->getReplacementPackage()))
+                                ? 'Use ' . $latestPackage->getReplacementPackage() . ' instead'
                                 : 'No replacement was suggested';
-
-                            $io->writeError('');
-                            $io->writeError(
-                                sprintf(
-                                    "<warning>Package %s is abandoned, you should avoid using it. %s.</warning>",
-                                    $package->getPrettyName(),
-                                    $replacement
-                                ),
-                                false
+                            $packageWarning = sprintf(
+                                'Package %s is abandoned, you should avoid using it. %s.',
+                                $package->getPrettyName(),
+                                $replacement
                             );
+                            $packageViewData['warning'] = $packageWarning;
                         }
                     } else {
-                        $io->write($indent . $package, false);
+                        $packageViewData['name'] = $package;
                     }
-                    $io->write('');
+                    $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;
+                        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 ($input->getOption('strict') && $hasOutdatedPackages) {
-                    return 1;
+
+                if ($showAllTypes) {
+                    $io->write('');
                 }
             }
         }
+
+        return $exitCode;
     }
 
     protected function getRootRequires()
@@ -406,22 +472,7 @@ EOT
 
     protected function getVersionStyle(PackageInterface $latestPackage, PackageInterface $package)
     {
-        if ($latestPackage->getFullPrettyVersion() === $package->getFullPrettyVersion()) {
-            // 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';
+        return $this->updateStatusToVersionStyle($this->getUpdateStatus($latestPackage, $package));
     }
 
     /**
@@ -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)
     {
         $io = $this->getIO();