From 56c88cf51f83e805654a93b71027dfb7cd1bd488 Mon Sep 17 00:00:00 2001 From: Markus Poerschke Date: Fri, 1 Feb 2019 07:59:14 +0100 Subject: [PATCH 1/7] Add options to exclude packages and dependencies from graph. Dev dependencies of dependencies are not shown anymore, because they are not relevant for the current package. * `--no-dev` will hide dev dependencies * `--no-php` will hide the constraints regarding the PHP version * `--no-ext` will hide PHP extensions * `--depth` will limit the depth of the generated graph * `--exclude-regex` allows to apply regular expressions to hide packages To apply these filters the packages must be rendered in the correct order. Before the packages were drawn "randomly" and were connected later on. Now packages are drawn from root package and only if they are a dependency of a package. This allow filtering for dependencies and packages. If a package (node) is missing, then the dependency (edge) is not shown. Vice versa the dependency (edge) is not shown when the package (node) is not available. --- .../GraphComposer/Command/AbstractCommand.php | 10 ++ src/Clue/GraphComposer/Command/Show.php | 50 +++++-- .../Dependency/ChainedDependencyRule.php | 29 ++++ .../Exclusion/Dependency/DependencyRule.php | 10 ++ .../Dependency/NegateDependencyRule.php | 23 ++++ .../Dependency/NoDevDependencyRule.php | 17 +++ .../Exclusion/Package/ChainedPackageRule.php | 29 ++++ .../Package/ExcludeByNamePackageRule.php | 23 ++++ .../Exclusion/Package/NegatePackageRule.php | 24 ++++ .../Exclusion/Package/NoPhpExtensionRule.php | 17 +++ .../Exclusion/Package/PackageRule.php | 10 ++ .../GraphComposer/Graph/GraphComposer.php | 130 ++++++++++++++---- 12 files changed, 335 insertions(+), 37 deletions(-) create mode 100644 src/Clue/GraphComposer/Command/AbstractCommand.php create mode 100644 src/Clue/GraphComposer/Exclusion/Dependency/ChainedDependencyRule.php create mode 100644 src/Clue/GraphComposer/Exclusion/Dependency/DependencyRule.php create mode 100644 src/Clue/GraphComposer/Exclusion/Dependency/NegateDependencyRule.php create mode 100644 src/Clue/GraphComposer/Exclusion/Dependency/NoDevDependencyRule.php create mode 100644 src/Clue/GraphComposer/Exclusion/Package/ChainedPackageRule.php create mode 100644 src/Clue/GraphComposer/Exclusion/Package/ExcludeByNamePackageRule.php create mode 100644 src/Clue/GraphComposer/Exclusion/Package/NegatePackageRule.php create mode 100644 src/Clue/GraphComposer/Exclusion/Package/NoPhpExtensionRule.php create mode 100644 src/Clue/GraphComposer/Exclusion/Package/PackageRule.php diff --git a/src/Clue/GraphComposer/Command/AbstractCommand.php b/src/Clue/GraphComposer/Command/AbstractCommand.php new file mode 100644 index 0000000..1d3a074 --- /dev/null +++ b/src/Clue/GraphComposer/Command/AbstractCommand.php @@ -0,0 +1,10 @@ +setName('show') - ->setDescription('Show dependency graph image for given project directory') - ->addArgument('dir', InputArgument::OPTIONAL, 'Path to project directory to scan', '.') - ->addOption('format', null, InputOption::VALUE_REQUIRED, 'Image format (svg, png, jpeg)', 'svg') - /*->addOption('dev', null, InputOption::VALUE_NONE, 'If set, Whether require-dev dependencies should be shown') */; + ->setDescription('Show dependency graph image for given project directory') + ->addArgument('dir', InputArgument::OPTIONAL, 'Path to project directory to scan', '.') + ->addOption('format', null, InputOption::VALUE_REQUIRED, 'Image format (svg, png, jpeg)', 'svg') + ->addOption('no-dev', null, InputOption::VALUE_NONE, 'Removes dev dependencies from the generated graph') + ->addOption('no-php', null, InputOption::VALUE_NONE, 'Hides dependency to PHP') + ->addOption('no-ext', null, InputOption::VALUE_NONE, 'Hide PHP extensions') + ->addOption('depth', null, InputOption::VALUE_REQUIRED, 'Set the maximum depth of dependency graph', PHP_INT_MAX) + ->addOption('exclude-regex', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Excludes packages using a regular expression like #^phpunit/.*/$#') + ; } protected function execute(InputInterface $input, OutputInterface $output) { - $graph = new GraphComposer($input->getArgument('dir')); + $dependencyRule = new ChainedDependencyRule(); + if ($input->getOption('no-dev')) { + $dependencyRule->add(new NoDevDependencyRule()); + } + + $packageRule = new ChainedPackageRule(); + if ($input->getOption('no-php')) { + $packageRule->add(new ExcludeByNamePackageRule('#^php$#')); + } + + if ($input->getOption('no-ext')) { + $packageRule->add(new NoPhpExtensionRule()); + } + + foreach ($input->getOption('exclude-regex') as $regex) { + $packageRule->add(new ExcludeByNamePackageRule($regex)); + } + + $graph = new GraphComposer( + $input->getArgument('dir'), + null, + $packageRule, + $dependencyRule, + (int) $input->getOption('depth') + ); $graph->setFormat($input->getOption('format')); $graph->displayGraph(); } diff --git a/src/Clue/GraphComposer/Exclusion/Dependency/ChainedDependencyRule.php b/src/Clue/GraphComposer/Exclusion/Dependency/ChainedDependencyRule.php new file mode 100644 index 0000000..3136e58 --- /dev/null +++ b/src/Clue/GraphComposer/Exclusion/Dependency/ChainedDependencyRule.php @@ -0,0 +1,29 @@ +rules[] = $rule; + } + + public function isExcluded(DependencyEdge $dependency) + { + foreach ($this->rules as $rule) { + if ($rule->isExcluded($dependency)) { + return true; + } + } + + return false; + } +} diff --git a/src/Clue/GraphComposer/Exclusion/Dependency/DependencyRule.php b/src/Clue/GraphComposer/Exclusion/Dependency/DependencyRule.php new file mode 100644 index 0000000..832d40d --- /dev/null +++ b/src/Clue/GraphComposer/Exclusion/Dependency/DependencyRule.php @@ -0,0 +1,10 @@ +rule = $rule; + } + + public function isExcluded(DependencyEdge $dependency) + { + return !$this->rule->isExcluded($dependency); + } +} diff --git a/src/Clue/GraphComposer/Exclusion/Dependency/NoDevDependencyRule.php b/src/Clue/GraphComposer/Exclusion/Dependency/NoDevDependencyRule.php new file mode 100644 index 0000000..4d31fa0 --- /dev/null +++ b/src/Clue/GraphComposer/Exclusion/Dependency/NoDevDependencyRule.php @@ -0,0 +1,17 @@ +isDevDependency()) { + return true; + } + + return false; + } +} diff --git a/src/Clue/GraphComposer/Exclusion/Package/ChainedPackageRule.php b/src/Clue/GraphComposer/Exclusion/Package/ChainedPackageRule.php new file mode 100644 index 0000000..e61d0e6 --- /dev/null +++ b/src/Clue/GraphComposer/Exclusion/Package/ChainedPackageRule.php @@ -0,0 +1,29 @@ +rules[] = $rule; + } + + public function isExcluded(PackageNode $packageNode) + { + foreach ($this->rules as $rule) { + if ($rule->isExcluded($packageNode)) { + return true; + } + } + + return false; + } +} diff --git a/src/Clue/GraphComposer/Exclusion/Package/ExcludeByNamePackageRule.php b/src/Clue/GraphComposer/Exclusion/Package/ExcludeByNamePackageRule.php new file mode 100644 index 0000000..fd8b498 --- /dev/null +++ b/src/Clue/GraphComposer/Exclusion/Package/ExcludeByNamePackageRule.php @@ -0,0 +1,23 @@ +pattern = $pattern; + } + + public function isExcluded(PackageNode $package) + { + return preg_match($this->pattern, $package->getName()); + } +} diff --git a/src/Clue/GraphComposer/Exclusion/Package/NegatePackageRule.php b/src/Clue/GraphComposer/Exclusion/Package/NegatePackageRule.php new file mode 100644 index 0000000..7f8b3e4 --- /dev/null +++ b/src/Clue/GraphComposer/Exclusion/Package/NegatePackageRule.php @@ -0,0 +1,24 @@ +rule = $rule; + } + + public function isExcluded(PackageNode $packageNode) + { + return !$this->rule->isExcluded($packageNode); + } +} diff --git a/src/Clue/GraphComposer/Exclusion/Package/NoPhpExtensionRule.php b/src/Clue/GraphComposer/Exclusion/Package/NoPhpExtensionRule.php new file mode 100644 index 0000000..69b3af2 --- /dev/null +++ b/src/Clue/GraphComposer/Exclusion/Package/NoPhpExtensionRule.php @@ -0,0 +1,17 @@ +isPhpExtension()) { + return true; + } + + return false; + } +} diff --git a/src/Clue/GraphComposer/Exclusion/Package/PackageRule.php b/src/Clue/GraphComposer/Exclusion/Package/PackageRule.php new file mode 100644 index 0000000..be80771 --- /dev/null +++ b/src/Clue/GraphComposer/Exclusion/Package/PackageRule.php @@ -0,0 +1,10 @@ + '#eeeeee', 'style' => 'filled, rounded', 'shape' => 'box', - 'fontcolor' => '#314B5F' + 'fontcolor' => '#314B5F', ); private $layoutVertexRoot = array( - 'style' => 'filled, rounded, bold' + 'style' => 'filled, rounded, bold', ); private $layoutEdge = array( 'fontcolor' => '#767676', 'fontsize' => 10, - 'color' => '#1A2833' + 'color' => '#1A2833', ); private $layoutEdgeDev = array( - 'style' => 'dashed' + 'style' => 'dashed', + 'fontcolor' => '#767676', + 'fontsize' => 10, + 'color' => '#1A2833', ); private $dependencyGraph; @@ -38,19 +46,51 @@ class GraphComposer private $graphviz; /** + * The maximum depth of dependency to display. * - * @param string $dir - * @param GraphViz|null $graphviz + * @var int + */ + private $maxDepth; + + /** + * @var PackageRule */ - public function __construct($dir, GraphViz $graphviz = null) + private $packageExclusionRule; + + /** + * @var DependencyRule + */ + private $dependencyExclusionRule; + + public function __construct( + $dir, + GraphViz $graphviz = null, + PackageRule $packageExclusionRule = null, + DependencyRule $dependencyExclusionRule = null, + $maxDepth = PHP_INT_MAX) { if ($graphviz === null) { $graphviz = new GraphViz(); $graphviz->setFormat('svg'); } + + if ($packageExclusionRule === null) { + $packageExclusionRule = new ChainedPackageRule(); + //$packageExclusionRule->add(new NoPhpExtensionRule()); + //$packageExclusionRule->add(new ExcludeByNamePackageRule('#^php$#')); + } + + if ($dependencyExclusionRule === null) { + $dependencyExclusionRule = new ChainedDependencyRule(); + // $dependencyExclusionRule->add(new NoDevDependencyRule()); + } + $analyzer = new \JMS\Composer\DependencyAnalyzer(); $this->dependencyGraph = $analyzer->analyze($dir); $this->graphviz = $graphviz; + $this->packageExclusionRule = $packageExclusionRule; + $this->dependencyExclusionRule = $dependencyExclusionRule; + $this->maxDepth = $maxDepth; } /** @@ -62,36 +102,68 @@ public function createGraph() { $graph = new Graph(); - foreach ($this->dependencyGraph->getPackages() as $package) { - $name = $package->getName(); - $start = $graph->createVertex($name, true); + $drawnPackages = array(); + $rootPackage = $this->dependencyGraph->getRootPackage(); + $this->drawPackageNode($graph, $rootPackage, $drawnPackages, $this->layoutVertexRoot); - $label = $name; - if ($package->getVersion() !== null) { - $label .= ': ' . $package->getVersion(); - } + return $graph; + } + + private function drawPackageNode( + Graph $graph, + PackageNode $packageNode, + array &$drawnPackages, + array $layoutVertex = null, + $depth = 0) + { + if (count($drawnPackages) && $this->packageExclusionRule->isExcluded($packageNode)) { + return null; + } + + $name = $packageNode->getName(); + if (isset($drawnPackages[$name])) { + return $drawnPackages[$name]; + } - $this->setLayout($start, array('label' => $label) + $this->layoutVertex); + if ($depth > $this->maxDepth) { + return null; + } + + if ($layoutVertex === null) { + $layoutVertex = $this->layoutVertex; + } - foreach ($package->getOutEdges() as $requires) { - $targetName = $requires->getDestPackage()->getName(); - $target = $graph->createVertex($targetName, true); + $label = $name; + if ($packageNode->getVersion()) { + $label .= ': ' .$packageNode->getVersion(); + } + $vertex = $graph->createVertex($name, true); + $this->setLayout($vertex, array('label' => $label) + $layoutVertex); + $drawnPackages[$name] = $vertex; + + foreach ($packageNode->getOutEdges() as $dependency) + { + if ($this->dependencyExclusionRule->isExcluded($dependency)) { + continue; + } - $label = $requires->getVersionConstraint(); + // never show dev dependencies of dependencies: + // they are not relevant for the current application and are ignored by composer + if ($depth > 0 && $dependency->isDevDependency()) { + continue; + } - $edge = $start->createEdgeTo($target); - $this->setLayout($edge, array('label' => $label) + $this->layoutEdge); + $targetVertex = $this->drawPackageNode($graph, $dependency->getDestPackage(), $drawnPackages, null, $depth + 1); - if ($requires->isDevDependency()) { - $this->setLayout($edge, $this->layoutEdgeDev); - } + if ($targetVertex && $depth < $this->maxDepth) { + $label = $dependency->getVersionConstraint(); + $edge = $vertex->createEdgeTo($targetVertex); + $layoutEdge = $dependency->isDevDependency() ? $this->layoutEdgeDev : $this->layoutEdge; + $this->setLayout($edge, array('label' => $label) + $layoutEdge); } } - $root = $graph->getVertex($this->dependencyGraph->getRootPackage()->getName()); - $this->setLayout($root, $this->layoutVertexRoot); - - return $graph; + return $vertex; } private function setLayout(AttributeAware $entity, array $layout) From 13bcd1d06efd1a69c4139c08dadf1fe38e12a318 Mon Sep 17 00:00:00 2001 From: Markus Poerschke Date: Fri, 1 Feb 2019 14:43:42 +0100 Subject: [PATCH 2/7] Merge duplicated code in commands --- .../GraphComposer/Command/AbstractCommand.php | 57 ++++++++++++++++++- src/Clue/GraphComposer/Command/Export.php | 23 ++------ src/Clue/GraphComposer/Command/Show.php | 52 ++--------------- 3 files changed, 68 insertions(+), 64 deletions(-) diff --git a/src/Clue/GraphComposer/Command/AbstractCommand.php b/src/Clue/GraphComposer/Command/AbstractCommand.php index 1d3a074..60905ad 100644 --- a/src/Clue/GraphComposer/Command/AbstractCommand.php +++ b/src/Clue/GraphComposer/Command/AbstractCommand.php @@ -2,9 +2,64 @@ namespace Clue\GraphComposer\Command; +use Clue\GraphComposer\Exclusion\Dependency\ChainedDependencyRule; +use Clue\GraphComposer\Exclusion\Dependency\NoDevDependencyRule; +use Clue\GraphComposer\Exclusion\Package\ChainedPackageRule; +use Clue\GraphComposer\Exclusion\Package\ExcludeByNamePackageRule; +use Clue\GraphComposer\Exclusion\Package\NoPhpExtensionRule; +use Clue\GraphComposer\Graph\GraphComposer; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\InputArgument; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; class AbstractCommand extends Command { + protected function configure() + { + $this + ->addArgument('dir', InputArgument::OPTIONAL, 'Path to project directory to scan', '.') + ->addOption('format', null, InputOption::VALUE_REQUIRED, 'Image format (svg, png, jpeg)', 'svg') + ->addOption('no-dev', null, InputOption::VALUE_NONE, 'Removes dev dependencies from the generated graph') + ->addOption('no-php', null, InputOption::VALUE_NONE, 'Hides dependency to PHP') + ->addOption('no-ext', null, InputOption::VALUE_NONE, 'Hide PHP extensions') + ->addOption('depth', null, InputOption::VALUE_REQUIRED, 'Set the maximum depth of dependency graph', PHP_INT_MAX) + ->addOption('exclude-regex', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Excludes packages using a regular expression like #^phpunit/.*/$#'); + } -} \ No newline at end of file + /** + * @param InputInterface $input + * @return GraphComposer + */ + protected function createGraph(InputInterface $input) + { + $dependencyRule = new ChainedDependencyRule(); + if ($input->getOption('no-dev')) { + $dependencyRule->add(new NoDevDependencyRule()); + } + + $packageRule = new ChainedPackageRule(); + if ($input->getOption('no-php')) { + $packageRule->add(new ExcludeByNamePackageRule('#^php$#')); + } + + if ($input->getOption('no-ext')) { + $packageRule->add(new NoPhpExtensionRule()); + } + + foreach ($input->getOption('exclude-regex') as $regex) { + $packageRule->add(new ExcludeByNamePackageRule($regex)); + } + + $graph = new GraphComposer( + $input->getArgument('dir'), + null, + $packageRule, + $dependencyRule, + (int)$input->getOption('depth') + ); + $graph->setFormat($input->getOption('format')); + + return $graph; + } +} diff --git a/src/Clue/GraphComposer/Command/Export.php b/src/Clue/GraphComposer/Command/Export.php index 925bbda..0fabfa1 100644 --- a/src/Clue/GraphComposer/Command/Export.php +++ b/src/Clue/GraphComposer/Command/Export.php @@ -2,31 +2,25 @@ namespace Clue\GraphComposer\Command; -use Symfony\Component\Console\Input\InputOption; +use Clue\GraphComposer\Graph\GraphComposer; use Symfony\Component\Console\Input\InputArgument; -use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -use Clue\GraphComposer\Graph\GraphComposer; -class Export extends Command +class Export extends AbstractCommand { protected function configure() { + parent::configure(); + $this->setName('export') ->setDescription('Export dependency graph image for given project directory') - ->addArgument('dir', InputArgument::OPTIONAL, 'Path to project directory to scan', '.') - ->addArgument('output', InputArgument::OPTIONAL, 'Path to output image file') - - // add output format option. default value MUST NOT be given, because default is to overwrite with output extension - ->addOption('format', null, InputOption::VALUE_REQUIRED, 'Image format (svg, png, jpeg)'/*, 'svg'*/) - - /*->addOption('dev', null, InputOption::VALUE_NONE, 'If set, Whether require-dev dependencies should be shown') */; + ->addArgument('output', InputArgument::OPTIONAL, 'Path to output image file'); } protected function execute(InputInterface $input, OutputInterface $output) { - $graph = new GraphComposer($input->getArgument('dir')); + $graph = $this->createGraph($input); $target = $input->getArgument('output'); if ($target !== null) { @@ -42,11 +36,6 @@ protected function execute(InputInterface $input, OutputInterface $output) } } - $format = $input->getOption('format'); - if ($format !== null) { - $graph->setFormat($format); - } - $path = $graph->getImagePath(); if ($target !== null) { diff --git a/src/Clue/GraphComposer/Command/Show.php b/src/Clue/GraphComposer/Command/Show.php index 4a3922f..37aa24b 100644 --- a/src/Clue/GraphComposer/Command/Show.php +++ b/src/Clue/GraphComposer/Command/Show.php @@ -2,62 +2,22 @@ namespace Clue\GraphComposer\Command; -use Clue\GraphComposer\Exclusion\Dependency\ChainedDependencyRule; -use Clue\GraphComposer\Exclusion\Dependency\NoDevDependencyRule; -use Clue\GraphComposer\Exclusion\Package\ChainedPackageRule; -use Clue\GraphComposer\Exclusion\Package\ExcludeByNamePackageRule; -use Clue\GraphComposer\Exclusion\Package\NoPhpExtensionRule; -use Clue\GraphComposer\Graph\GraphComposer; -use Symfony\Component\Console\Command\Command; -use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -class Show extends Command +class Show extends AbstractCommand { protected function configure() { - $this->setName('show') - ->setDescription('Show dependency graph image for given project directory') - ->addArgument('dir', InputArgument::OPTIONAL, 'Path to project directory to scan', '.') - ->addOption('format', null, InputOption::VALUE_REQUIRED, 'Image format (svg, png, jpeg)', 'svg') - ->addOption('no-dev', null, InputOption::VALUE_NONE, 'Removes dev dependencies from the generated graph') - ->addOption('no-php', null, InputOption::VALUE_NONE, 'Hides dependency to PHP') - ->addOption('no-ext', null, InputOption::VALUE_NONE, 'Hide PHP extensions') - ->addOption('depth', null, InputOption::VALUE_REQUIRED, 'Set the maximum depth of dependency graph', PHP_INT_MAX) - ->addOption('exclude-regex', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Excludes packages using a regular expression like #^phpunit/.*/$#') - ; + parent::configure(); + $this + ->setName('show') + ->setDescription('Show dependency graph image for given project directory'); } protected function execute(InputInterface $input, OutputInterface $output) { - $dependencyRule = new ChainedDependencyRule(); - if ($input->getOption('no-dev')) { - $dependencyRule->add(new NoDevDependencyRule()); - } - - $packageRule = new ChainedPackageRule(); - if ($input->getOption('no-php')) { - $packageRule->add(new ExcludeByNamePackageRule('#^php$#')); - } - - if ($input->getOption('no-ext')) { - $packageRule->add(new NoPhpExtensionRule()); - } - - foreach ($input->getOption('exclude-regex') as $regex) { - $packageRule->add(new ExcludeByNamePackageRule($regex)); - } - - $graph = new GraphComposer( - $input->getArgument('dir'), - null, - $packageRule, - $dependencyRule, - (int) $input->getOption('depth') - ); - $graph->setFormat($input->getOption('format')); + $graph = $this->createGraph($input); $graph->displayGraph(); } } From 446871b9a6dabe189a7d87259060bb2fb4d22546 Mon Sep 17 00:00:00 2001 From: Markus Poerschke Date: Fri, 1 Feb 2019 15:00:36 +0100 Subject: [PATCH 3/7] Update code style --- .../GraphComposer/Graph/GraphComposer.php | 57 +++++++++++-------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/src/Clue/GraphComposer/Graph/GraphComposer.php b/src/Clue/GraphComposer/Graph/GraphComposer.php index 288cde8..8266e1a 100644 --- a/src/Clue/GraphComposer/Graph/GraphComposer.php +++ b/src/Clue/GraphComposer/Graph/GraphComposer.php @@ -67,8 +67,8 @@ public function __construct( GraphViz $graphviz = null, PackageRule $packageExclusionRule = null, DependencyRule $dependencyExclusionRule = null, - $maxDepth = PHP_INT_MAX) - { + $maxDepth = PHP_INT_MAX + ) { if ($graphviz === null) { $graphviz = new GraphViz(); $graphviz->setFormat('svg'); @@ -94,8 +94,6 @@ public function __construct( } /** - * - * @param string $dir * @return \Fhaculty\Graph\Graph */ public function createGraph() @@ -109,18 +107,38 @@ public function createGraph() return $graph; } + public function displayGraph() + { + $graph = $this->createGraph(); + + $this->graphviz->display($graph); + } + + public function getImagePath() + { + $graph = $this->createGraph(); + + return $this->graphviz->createImageFile($graph); + } + private function drawPackageNode( Graph $graph, PackageNode $packageNode, array &$drawnPackages, array $layoutVertex = null, - $depth = 0) - { - if (count($drawnPackages) && $this->packageExclusionRule->isExcluded($packageNode)) { + $depth = 0 + ) { + // the root package may not excluded + // beginning with $depth = 1 the packages are filtered using the exclude rule + if ($depth > 0 && $this->packageExclusionRule->isExcluded($packageNode)) { return null; } $name = $packageNode->getName(); + // ensure that packages are only drawn once + // if two packages in the tree require a package twice + // then this dependency does not need to be drawn twice + // and the vertex is returned directly (so an edge can be added) if (isset($drawnPackages[$name])) { return $drawnPackages[$name]; } @@ -133,16 +151,16 @@ private function drawPackageNode( $layoutVertex = $this->layoutVertex; } + $vertex = $drawnPackages[$name] = $graph->createVertex($name, true); + $label = $name; if ($packageNode->getVersion()) { $label .= ': ' .$packageNode->getVersion(); } - $vertex = $graph->createVertex($name, true); $this->setLayout($vertex, array('label' => $label) + $layoutVertex); - $drawnPackages[$name] = $vertex; - foreach ($packageNode->getOutEdges() as $dependency) - { + // this foreach will loop over the dependencies of the current package + foreach ($packageNode->getOutEdges() as $dependency) { if ($this->dependencyExclusionRule->isExcluded($dependency)) { continue; } @@ -155,6 +173,9 @@ private function drawPackageNode( $targetVertex = $this->drawPackageNode($graph, $dependency->getDestPackage(), $drawnPackages, null, $depth + 1); + // drawPackageNode will return null if the package should not be shown + // also the dependencies of a package will be only drawn if max depth is not reached + // this ensures that packages in a deeper level will not have any dependency if ($targetVertex && $depth < $this->maxDepth) { $label = $dependency->getVersionConstraint(); $edge = $vertex->createEdgeTo($targetVertex); @@ -174,20 +195,6 @@ private function setLayout(AttributeAware $entity, array $layout) return $entity; } - public function displayGraph() - { - $graph = $this->createGraph(); - - $this->graphviz->display($graph); - } - - public function getImagePath() - { - $graph = $this->createGraph(); - - return $this->graphviz->createImageFile($graph); - } - public function setFormat($format) { $this->graphviz->setFormat($format); From 0fb5cdfc9c5105c1bd188c50bcaf2674bd8decb9 Mon Sep 17 00:00:00 2001 From: Markus Poerschke Date: Fri, 1 Feb 2019 15:29:31 +0100 Subject: [PATCH 4/7] Add filter mode "only" and allow to filter for package types --- .../GraphComposer/Command/AbstractCommand.php | 30 ++++++++++++++++++- .../Package/ExcludeTypePackageRule.php | 29 ++++++++++++++++++ .../Exclusion/Package/NegatePackageRule.php | 3 +- 3 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 src/Clue/GraphComposer/Exclusion/Package/ExcludeTypePackageRule.php diff --git a/src/Clue/GraphComposer/Command/AbstractCommand.php b/src/Clue/GraphComposer/Command/AbstractCommand.php index 60905ad..fb7c6b3 100644 --- a/src/Clue/GraphComposer/Command/AbstractCommand.php +++ b/src/Clue/GraphComposer/Command/AbstractCommand.php @@ -6,6 +6,8 @@ use Clue\GraphComposer\Exclusion\Dependency\NoDevDependencyRule; use Clue\GraphComposer\Exclusion\Package\ChainedPackageRule; use Clue\GraphComposer\Exclusion\Package\ExcludeByNamePackageRule; +use Clue\GraphComposer\Exclusion\Package\ExcludeTypePackageRule; +use Clue\GraphComposer\Exclusion\Package\NegatePackageRule; use Clue\GraphComposer\Exclusion\Package\NoPhpExtensionRule; use Clue\GraphComposer\Graph\GraphComposer; use Symfony\Component\Console\Command\Command; @@ -24,7 +26,11 @@ protected function configure() ->addOption('no-php', null, InputOption::VALUE_NONE, 'Hides dependency to PHP') ->addOption('no-ext', null, InputOption::VALUE_NONE, 'Hide PHP extensions') ->addOption('depth', null, InputOption::VALUE_REQUIRED, 'Set the maximum depth of dependency graph', PHP_INT_MAX) - ->addOption('exclude-regex', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Excludes packages using a regular expression like #^phpunit/.*/$#'); + ->addOption('exclude-regex', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Excludes packages using a regular expression like #^phpunit/.*/$#') + ->addOption('only-regex', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Includes only packages using a regular expression like #^phpunit/.*/$#') + ->addOption('exclude-type', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Excludes packages of given type.') + ->addOption('only-type', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'Includes only packages of given type.') + ; } /** @@ -51,6 +57,28 @@ protected function createGraph(InputInterface $input) $packageRule->add(new ExcludeByNamePackageRule($regex)); } + $onlyRegex = $input->getOption('only-regex'); + if (count($onlyRegex)) { + $onlyRegexRule = new ChainedPackageRule(); + foreach ($onlyRegex as $regex) { + $onlyRegexRule->add(new ExcludeByNamePackageRule($regex)); + } + $packageRule->add(new NegatePackageRule($onlyRegexRule)); + } + + foreach ($input->getOption('exclude-type') as $type) { + $packageRule->add(new ExcludeTypePackageRule($type)); + } + + $onlyTypes = $input->getOption('only-type'); + if (count($onlyTypes)) { + $onlyTypesRule = new ChainedPackageRule(); + foreach ($onlyTypes as $type) { + $onlyTypesRule->add(new ExcludeTypePackageRule($type)); + } + $packageRule->add(new NegatePackageRule($onlyTypesRule)); + } + $graph = new GraphComposer( $input->getArgument('dir'), null, diff --git a/src/Clue/GraphComposer/Exclusion/Package/ExcludeTypePackageRule.php b/src/Clue/GraphComposer/Exclusion/Package/ExcludeTypePackageRule.php new file mode 100644 index 0000000..76debcd --- /dev/null +++ b/src/Clue/GraphComposer/Exclusion/Package/ExcludeTypePackageRule.php @@ -0,0 +1,29 @@ +type = $type; + } + + public function isExcluded(PackageNode $package) + { + $data = $package->getData(); + + if (!isset($data['type'])) { + return false; + } + + return $data['type'] === $this->type; + } +} diff --git a/src/Clue/GraphComposer/Exclusion/Package/NegatePackageRule.php b/src/Clue/GraphComposer/Exclusion/Package/NegatePackageRule.php index 7f8b3e4..4f92edb 100644 --- a/src/Clue/GraphComposer/Exclusion/Package/NegatePackageRule.php +++ b/src/Clue/GraphComposer/Exclusion/Package/NegatePackageRule.php @@ -1,8 +1,7 @@ Date: Fri, 1 Feb 2019 15:45:25 +0100 Subject: [PATCH 5/7] Add new and available options --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 1eb3ed1..3c00f6e 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,20 @@ $ php graph-composer.phar export ~/path/to/your/project * You may optionally pass an `--format=[svg/svgz/png/jpeg/...]` option to set the image type (defaults to `svg`). +### Options + +|Option |Description |Example | +|----------------|----------------------------------------------------------------|-----------------------------------------------------------| +|--format |Image format (`svg`, `png` or `jpeg`), default `svg` |`graph-composer show --format png` | +|--no-dev |Hides dev dependencies |`graph-composer show --no-dev` | +|--no-php |Hides dependency to PHP version |`graph-composer show --no-php` | +|--no-ext |Hides dependency to PHP extensions |`graph-composer show --no-ext` | +|--depth |Stops rendering the graph at the given depth |`graph-composer show --depth 3` | +|--exclude-regex |Hides packages that are matching the regular expression |`graph-composer show --exclude-regex "#^phpunit/#"` | +|--only-regex |Hides packages that are **not** matching the regular expression |`graph-composer show --only-regex "#^mycompany/#"` | +|--exclude-type |Hides packages that are of the given type |`graph-composer show --exclude-type "typo3-cms-framework"` | +|--only-type |Hides packages that are **not** of the given type |`graph-composer show --only-type "typo3-cms-framework"` | + ## Install You can grab a copy of clue/graph-composer in either of the following ways. From 18351b664f03cc4cd18853a26a3b841d095c197b Mon Sep 17 00:00:00 2001 From: Markus Poerschke Date: Fri, 1 Feb 2019 15:49:48 +0100 Subject: [PATCH 6/7] Update CHANGELOG --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c52a88c..ef7d7d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +## ?.?.? (unreleased) + +* Dev dependencies are only rendered for the root package + +* Added new options to filter packages and dependencies: + * `--no-dev` will hide dev dependencies + * `--no-php` will hide the constraints regarding the PHP version + * `--no-ext` will hide PHP extensions + * `--depth` will limit the depth of the generated graph + * `--exclude-regex` allows to apply regular expressions to hide packages + * `--only-regex` show only packages with their name matching the expression + * `--exclude-type` exclude pacakges with given type + * `--only-type` only show packages of given type + ## 1.0.0 (2015-11-17) * First stable release, now following SemVer. From 64d3c85f76a3a1e951556275270ee1d9d4a57343 Mon Sep 17 00:00:00 2001 From: Markus Poerschke Date: Fri, 1 Feb 2019 19:15:35 +0100 Subject: [PATCH 7/7] Remove unused code --- src/Clue/GraphComposer/Graph/GraphComposer.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Clue/GraphComposer/Graph/GraphComposer.php b/src/Clue/GraphComposer/Graph/GraphComposer.php index 8266e1a..cfc08ea 100644 --- a/src/Clue/GraphComposer/Graph/GraphComposer.php +++ b/src/Clue/GraphComposer/Graph/GraphComposer.php @@ -76,13 +76,10 @@ public function __construct( if ($packageExclusionRule === null) { $packageExclusionRule = new ChainedPackageRule(); - //$packageExclusionRule->add(new NoPhpExtensionRule()); - //$packageExclusionRule->add(new ExcludeByNamePackageRule('#^php$#')); } if ($dependencyExclusionRule === null) { $dependencyExclusionRule = new ChainedDependencyRule(); - // $dependencyExclusionRule->add(new NoDevDependencyRule()); } $analyzer = new \JMS\Composer\DependencyAnalyzer();