Sfoglia il codice sorgente

Rewrote DependsCommand

Niels Keurentjes 9 anni fa
parent
commit
e38fe67333
2 ha cambiato i file con 119 aggiunte e 54 eliminazioni
  1. 111 54
      src/Composer/Command/DependsCommand.php
  2. 8 0
      src/Composer/Package/Link.php

+ 111 - 54
src/Composer/Command/DependsCommand.php

@@ -15,12 +15,15 @@ namespace Composer\Command;
 use Composer\DependencyResolver\Pool;
 use Composer\Package\Link;
 use Composer\Package\PackageInterface;
+use Composer\Package\RootPackage;
 use Composer\Repository\ArrayRepository;
 use Composer\Repository\CompositeRepository;
 use Composer\Repository\PlatformRepository;
 use Composer\Plugin\CommandEvent;
 use Composer\Plugin\PluginEvents;
+use Composer\Semver\Constraint\ConstraintInterface;
 use Composer\Semver\VersionParser;
+use Symfony\Component\Console\Helper\Table;
 use Symfony\Component\Console\Input\InputInterface;
 use Symfony\Component\Console\Input\InputArgument;
 use Symfony\Component\Console\Input\InputOption;
@@ -32,22 +35,19 @@ use Symfony\Component\Console\Output\OutputInterface;
  */
 class DependsCommand extends Command
 {
-    protected $linkTypes = array(
-        'require' => array('requires', 'requires'),
-        'require-dev' => array('devRequires', 'requires (dev)'),
-    );
+    /** @var CompositeRepository  */
+    private $repository;
 
     protected function configure()
     {
         $this
             ->setName('depends')
+            ->setAliases(array('why'))
             ->setDescription('Shows which packages depend on the given package')
             ->setDefinition(array(
                 new InputArgument('package', InputArgument::REQUIRED, 'Package to inspect'),
-                new InputOption('link-type', '', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Link types to show (require, require-dev)', array_keys($this->linkTypes)),
                 new InputOption('match-constraint', 'm', InputOption::VALUE_REQUIRED, 'Filters the dependencies shown using this constraint', '*'),
                 new InputOption('invert-match-constraint', 'i', InputOption::VALUE_NONE, 'Turns --match-constraint around into a blacklist instead of whitelist'),
-                new InputOption('with-replaces', '', InputOption::VALUE_NONE, 'Search for replaced packages as well'),
             ))
             ->setHelp(<<<EOT
 Displays detailed information about where a package is referenced.
@@ -61,80 +61,137 @@ EOT
 
     protected function execute(InputInterface $input, OutputInterface $output)
     {
+        // Emit command event on startup
         $composer = $this->getComposer();
-
         $commandEvent = new CommandEvent(PluginEvents::COMMAND, 'depends', $input, $output);
         $composer->getEventDispatcher()->dispatch($commandEvent->getName(), $commandEvent);
 
+        // Prepare repositories and set up a pool
         $platformOverrides = $composer->getConfig()->get('platform') ?: array();
-        $repo = new CompositeRepository(array(
+        $this->repository = new CompositeRepository(array(
             new ArrayRepository(array($composer->getPackage())),
             $composer->getRepositoryManager()->getLocalRepository(),
             new PlatformRepository(array(), $platformOverrides),
         ));
-        $needle = $input->getArgument('package');
-
         $pool = new Pool();
-        $pool->addRepository($repo);
+        $pool->addRepository($this->repository);
 
+        // Find packages that are or provide the requested package first
+        $needle = $input->getArgument('package');
         $packages = $pool->whatProvides($needle);
         if (empty($packages)) {
-            throw new \InvalidArgumentException('Could not find package "'.$needle.'" in your project.');
+            throw new \InvalidArgumentException(sprintf('Could not find package "%s" in your project', $needle));
         }
 
-        $linkTypes = $this->linkTypes;
+        // Parse options that are only relevant for the initial needle(s)
+        if ('*' !== ($textConstraint = $input->getOption('match-constraint'))) {
+            $versionParser = new VersionParser();
+            $constraint = $versionParser->parseConstraints($textConstraint);
+        } else {
+            $constraint = null;
+        }
+        $matchInvert = $input->getOption('invert-match-constraint');
+        $recursive = true;
+        $tree = true;
+
+        // Resolve dependencies
+        $results = $this->getDependers($needle, $constraint, $matchInvert, $recursive);
+        if (empty($results)) {
+            $extra = isset($constraint) ? sprintf(' in versions %smatching %s', $matchInvert ? 'not ' : '', $textConstraint) : '';
+            $this->getIO()->writeError(sprintf('<info>There is no installed package depending on "%s"%s</info>',
+                        $needle, $extra));
+        } elseif ($tree) {
+            $root = $packages[0];
+            $this->getIO()->write(sprintf('<info>%s</info> %s %s', $root->getPrettyName(), $root->getPrettyVersion(), $root->getDescription()));
+            $this->printTree($output, $results);
+        } else {
+            $this->printTable($output, $results);
+        }
+    }
 
-        $types = array_map(function ($type) use ($linkTypes) {
-            $type = rtrim($type, 's');
-            if (!isset($linkTypes[$type])) {
-                throw new \InvalidArgumentException('Unexpected link type: '.$type.', valid types: '.implode(', ', array_keys($linkTypes)));
+    private function printTable(OutputInterface $output, $results)
+    {
+        $table = array();
+        $doubles = array();
+        do {
+            $queue = array();
+            $rows = array();
+            foreach($results as $result) {
+                /**
+                 * @var PackageInterface $package
+                 * @var Link $link
+                 */
+                list($package, $link, $children) = $result;
+                $unique = (string)$link;
+                if (isset($doubles[$unique])) {
+                    continue;
+                }
+                $doubles[$unique] = true;
+                $realVersion = (strpos($package->getPrettyVersion(), 'No version set') === 0) ? '-' : $package->getPrettyVersion();
+                $rows[] = array($package->getPrettyName(), $realVersion, $link->getDescription(), sprintf('%s (%s)', $link->getTarget(), $link->getPrettyConstraint()));
+                $queue = array_merge($queue, $children);
             }
+            $results = $queue;
+            $table = array_merge($rows, $table);
+        } while(!empty($results));
 
-            return $type;
-        }, $input->getOption('link-type'));
+        // Render table
+        $renderer = new Table($output);
+        $renderer->setStyle('compact')->setRows($table)->render();
+    }
 
-        $versionParser = new VersionParser();
-        $constraint = $versionParser->parseConstraints($input->getOption('match-constraint'));
-        $matchInvert = $input->getOption('invert-match-constraint');
+    public function printTree(OutputInterface $output, $results, $prefix = '')
+    {
+        $count = count($results);
+        $idx = 0;
+        foreach($results as $key => $result) {
+            /**
+             * @var PackageInterface $package
+             * @var Link $link
+             */
+            list($package, $link, $children) = $result;
+            $isLast = (++$idx == $count);
+            $output->write(sprintf("%s%s %s %s %s (%s)\n", $prefix, $isLast ? '`-' : '|-', $link->getSource(), $link->getDescription(), $link->getTarget(), $link->getPrettyConstraint()));
+            $this->printTree($output, $children, $prefix . ($isLast ? '   ' : '|  '));
+        }
+    }
 
-        $needles = array($needle);
-        if (true === $input->getOption('with-replaces')) {
-            foreach ($packages as $package) {
-                $needles = array_merge($needles, array_map(function (Link $link) {
-                    return $link->getTarget();
-                }, $package->getReplaces()));
+    /**
+     * @param string $needle The package to inspect.
+     * @param ConstraintInterface|null $constraint Optional constraint to filter by.
+     * @param bool $invert Whether to invert matches on the previous constraint.
+     * @param bool $recurse Whether to recursively expand the requirement tree.
+     * @return array An array with dependers as key, and as values an array containing the source package and the link respectively
+     */
+    private function getDependers($needle, $constraint = null, $invert = false, $recurse = true)
+    {
+        $needles = is_array($needle) ? $needle : array($needle);
+        $results = array();
+
+        /**
+         * Loop over all currently installed packages.
+         * @var PackageInterface $package
+         */
+        foreach ($this->repository->getPackages() as $package) {
+            // Retrieve all requirements, but dev only for the root package
+            $links = $package->getRequires();
+            $links += $package->getReplaces();
+            if ($package instanceof RootPackage) {
+                $links += $package->getDevRequires();
             }
-        }
 
-        $messages = array();
-        $outputPackages = array();
-        $io = $this->getIO();
-        /** @var PackageInterface $package */
-        foreach ($repo->getPackages() as $package) {
-            foreach ($types as $type) {
-                /** @var Link $link */
-                foreach ($package->{'get'.$linkTypes[$type][0]}() as $link) {
-                    foreach ($needles as $needle) {
-                        if ($link->getTarget() === $needle && ($link->getConstraint()->matches($constraint) ? !$matchInvert : $matchInvert)) {
-                            if (!isset($outputPackages[$package->getName()])) {
-                                $messages[] = '<info>'.$package->getPrettyName() . '</info> ' . $linkTypes[$type][1] . ' ' . $needle .' (<info>' . $link->getPrettyConstraint() . '</info>)';
-                                $outputPackages[$package->getName()] = true;
-                            }
+            // Cross-reference all discovered links to the needles
+            foreach ($links as $link) {
+                foreach ($needles as $needle) {
+                    if ($link->getTarget() === $needle) {
+                        if (is_null($constraint) || (($link->getConstraint()->matches($constraint) === !$invert))) {
+                            $results[$link->getSource()] = array($package, $link, $recurse ? $this->getDependers($link->getSource(), null, false, true) : array());
                         }
                     }
                 }
             }
         }
-
-        if ($messages) {
-            sort($messages);
-            $io->write($messages);
-        } else {
-            $matchText = '';
-            if ($input->getOption('match-constraint') !== '*') {
-                $matchText = ' in versions '.($matchInvert ? 'not ' : '').'matching ' . $input->getOption('match-constraint');
-            }
-            $io->writeError('<info>There is no installed package depending on "'.$needle.'"'.$matchText.'.</info>');
-        }
+        ksort($results);
+        return $results;
     }
 }

+ 8 - 0
src/Composer/Package/Link.php

@@ -64,6 +64,14 @@ class Link
         $this->prettyConstraint = $prettyConstraint;
     }
 
+    /**
+     * @return string
+     */
+    public function getDescription()
+    {
+        return $this->description;
+    }
+
     /**
      * @return string
      */