From: Gunnar Wrobel Date: Wed, 13 Oct 2010 19:02:52 +0000 (+0200) Subject: Add a dependency list module. X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=10468cddca5650ef28120fd50610f8df4095ed64;p=horde.git Add a dependency list module. --- diff --git a/components/TODO b/components/TODO index af335f0e3..01bc3494d 100644 --- a/components/TODO +++ b/components/TODO @@ -8,8 +8,6 @@ - Add module for generating component documentation. - - Produce an HTML dependency graph. - - Add module for an initial empty PEAR template - Add a commit module that automatically adds a changelog entry in diff --git a/components/lib/Components/Dependencies/Injector.php b/components/lib/Components/Dependencies/Injector.php index 69580593f..8825cec09 100644 --- a/components/lib/Components/Dependencies/Injector.php +++ b/components/lib/Components/Dependencies/Injector.php @@ -83,6 +83,16 @@ implements Components_Dependencies } /** + * Returns the dependency list handler for a package. + * + * @return Components_Runner_Dependencies The dependency handler. + */ + public function getRunnerDependencies() + { + return $this->getInstance('Components_Runner_Dependencies'); + } + + /** * Returns the installer for a package. * * @return Components_Runner_Installer The installer. diff --git a/components/lib/Components/Helper/ListRun.php b/components/lib/Components/Helper/ListRun.php new file mode 100644 index 000000000..2ccf4524e --- /dev/null +++ b/components/lib/Components/Helper/ListRun.php @@ -0,0 +1,199 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Components + */ + +/** + * Components_Helper_ListRun:: provides a utility that produces a dependency + * list and records what has already been listed. + * + * Copyright 2010 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Horde + * @package Components + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Components + */ +class Components_Helper_ListRun +{ + /** + * The output handler. + * + * @param Component_Output + */ + private $_output; + + /** + * The list of dependencies already displayed. + * + * @var array + */ + private $_displayed_dependencies = array(); + + /** + * The list of elements in case we are producing condensed output. + * + * @var array + */ + private $_quiet_list = array(); + + /** + * Constructor. + * + * @param Component_Output $output The output handler. + */ + public function __construct(Components_Output $output) + { + $this->_output = $output; + if ($this->_output->isVerbose()) { + $output->bold('List contains optional dependencies!'); + } else { + $output->bold('List only contains required dependencies!'); + } + $output->blue('Dependencies on PEAR itself are not displayed.'); + $output->bold(''); + } + + /** + * List a Horde component as dependency. + * + * @param Components_Pear_Package $package The package that should be listed. + * @param int $level The current list level. + * @param string $parent Name of the parent element. + * @param boolean $reqired Is this a required element? + * + * @return boolean True in case listing should continue. + */ + public function listHordePackage( + Components_Pear_Package $package, + $level, + $parent, + $required + ) { + if (!$this->_output->isVerbose() && !$required) { + return false; + } + $key = $package->getName() . '@pear.horde.org'; + if (!$this->_output->isQuiet()) { + if (in_array($key, array_keys($this->_displayed_dependencies))) { + if (empty($this->_displayed_dependencies[$key])) { + $add = ' (RECURSION) ***STOP***'; + } else { + $add = ' (ALREADY LISTED WITH ' + . $this->_displayed_dependencies[$key] . ') ***STOP***'; + } + } else { + $add = ''; + } + $this->_output->green( + Horde_String::pad( + $this->_listLevel($level) . '|_' + . $package->getName(), 40 + ) + . Horde_String::pad(' [pear.horde.org]', 20) + . $add + ); + if (in_array($key, array_keys($this->_displayed_dependencies))) { + return false; + } else { + $this->_displayed_dependencies[$key] = $parent; + return true; + } + } else { + $this->_quiet_list[$key] = array( + 'channel' => 'pear.horde.org', + 'name' => $package->getName(), + 'color' => 'green' + ); + return true; + } + } + + /** + * List an external package as dependency. + * + * @param array $dependency The dependency that should be listed. + * @param int $level The current list level. + * + * @return NULL + */ + public function listExternalPackage(array $dependency, $level) + { + // Showing PEAR does not make much sense. + if ($dependency['name'] == 'PEAR' + && $dependency['channel'] == 'pear.php.net') { + return; + } + + $key = $dependency['name'] . '@' . $dependency['channel']; + if (!$this->_output->isQuiet()) { + $this->_output->yellow( + Horde_String::pad( + $this->_listLevel($level) . '|_' + . $dependency['name'], 40 + ) + . Horde_String::pad(' [' . $dependency['channel'] . ']', 20) + . ' (EXTERNAL) ***STOP***' + ); + } else { + $this->_quiet_list[$key] = array( + 'channel' => $dependency['channel'], + 'name' => $dependency['name'], + 'color' => 'yellow' + ); + } + } + + /** + * Wrap up the listing. This will produce a condensed list of packages in + * case quiet Output was requested. + * + * @return NULL + */ + public function finish() + { + if (empty($this->_quiet_list)) { + return; + } + $channels = array(); + $names = array(); + $colors = array(); + foreach ($this->_quiet_list as $key => $element) { + $channels[] = $element['channel']; + $names[] = $element['name']; + $colors[] = $element['color']; + } + array_multisort($channels, $names, $colors); + foreach ($names as $key => $name) { + $this->_output->$colors[$key]( + Horde_String::pad($name, 20) . + Horde_String::pad('[' . $channels[$key] . ']', 20) + ); + } + } + + /** + * Produces an amount of whitespace depending on the specified level. + * + * @param int $level The level of indentation. + * + * @return string Whitespace. + */ + private function _listLevel($level) + { + return str_repeat(' ', $level); + } +} \ No newline at end of file diff --git a/components/lib/Components/Helper/Tree.php b/components/lib/Components/Helper/Tree.php index ac9d510f7..32c42a0c4 100644 --- a/components/lib/Components/Helper/Tree.php +++ b/components/lib/Components/Helper/Tree.php @@ -73,10 +73,9 @@ class Components_Helper_Tree /** * Install the tree of packages into the specified environment. * - * @param string $package_file Path to the package file - * representing the element - * at the root of the - * dependency tree. + * @param string $package_file Path to the package file representing the element + * at the root of the dependency tree. + * * @return NULL */ public function installTreeInEnvironment($package_file) { @@ -87,6 +86,26 @@ class Components_Helper_Tree } /** + * List the dependency tree for the specified root package element. + * + * @param string $package_file Path to the package file representing + * the element at the root of the + * dependency tree. + * @param Component_Output $output The output handler. + * + * @return NULL + */ + public function listDependencyTree( + $package_file, + Components_Output $output + ) { + $run = new Components_Helper_ListRun($output); + $this->_getHordeChildElement($package_file) + ->listDependencies($run, 0); + $run->finish(); + } + + /** * Get the children for an element. * * @param array $dependencies The dependencies of a package to be @@ -104,7 +123,10 @@ class Components_Helper_Tree . 'framework' . DIRECTORY_SEPARATOR . $dependency['name'] . DIRECTORY_SEPARATOR . 'package.xml'; } - $children[] = $this->_getHordeChildElement($package_file); + $children[] = $this->_getHordeChildElement( + $package_file, + isset($dependency['optional']) && $dependency['optional'] == 'no' + ); } return $children; } @@ -112,19 +134,19 @@ class Components_Helper_Tree /** * Return a Horde child element. * - * @param string $package_file Path to the package - * file representing the - * element at the root - * of the dependency tree. + * @param string $package_file Path to the package file representing the + * element at the root of the dependency tree. + * @param boolean $required Is this a required element? * @return NULL */ - private function _getHordeChildElement($package_file) + private function _getHordeChildElement($package_file, $required = true) { return new Components_Helper_Tree_Element( $this->_factory->createPackageForEnvironment( $package_file, $this->_environment ), $package_file, + $required, $this ); } diff --git a/components/lib/Components/Helper/Tree/Element.php b/components/lib/Components/Helper/Tree/Element.php index 989a494af..5594f75fb 100644 --- a/components/lib/Components/Helper/Tree/Element.php +++ b/components/lib/Components/Helper/Tree/Element.php @@ -44,6 +44,13 @@ class Components_Helper_Tree_Element private $_package_file; /** + * Is this a required element? + * + * @var boolean + */ + private $_required; + + /** * The parent tree for this child. * * @var Components_Helper_Tree @@ -55,15 +62,18 @@ class Components_Helper_Tree_Element * * @param Components_Pear_Package $package The root of the dependency tree. * @param string $package_file The path to the package file. + * @param boolean $required Is this a required element? * @param Components_Helper_Tree $tree The parent tree for this child. */ public function __construct( Components_Pear_Package $package, $package_file, + $required, Components_Helper_Tree $tree ) { $this->_package = $package; $this->_package_file = $package_file; + $this->_required = $required; $this->_tree = $tree; } @@ -91,4 +101,33 @@ class Components_Helper_Tree_Element } $run->installHordePackageOnce($this->_package_file); } + + /** + * List the dependency tree for this package. + * + * @param Components_Helper_ListRun $run The current listing run. + * @param int $level The current list level. + * @param string $parent The name of the parent element. + * + * @return NULL + */ + public function listDependencies( + Components_Helper_ListRun $run, + $level, + $parent = '' + ) { + if ($run->listHordePackage($this->_package, $level, $parent, $this->_required)) { + foreach ($this->_package->listAllExternalDependencies() as $dependency) { + $run->listExternalPackage($dependency, $level + 1); + } + foreach ( + $this->_tree->getChildren( + $this->_package->listAllHordeDependencies() + ) as $child + ) { + $child->listDependencies($run, $level + 1, $this->_package->getName()); + } + } + } + } \ No newline at end of file diff --git a/components/lib/Components/Module/Dependencies.php b/components/lib/Components/Module/Dependencies.php new file mode 100644 index 000000000..449d83a6d --- /dev/null +++ b/components/lib/Components/Module/Dependencies.php @@ -0,0 +1,85 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Components + */ + +/** + * Components_Module_Dependencies:: generates a dependency listing for the + * specified package. + * + * Copyright 2010 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Horde + * @package Components + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Components + */ +class Components_Module_Dependencies +extends Components_Module_Base +{ + /** + * Return the title for the option group representing this module. + * + * @return string The group title. + */ + public function getOptionGroupTitle() + { + return 'Package Dependencies'; + } + + /** + * Return the description for the option group representing this module. + * + * @return string The group description. + */ + public function getOptionGroupDescription() + { + return 'This module generates a list of dependencies for the specified package'; + } + + /** + * Return the options for this module. + * + * @return array The group options. + */ + public function getOptionGroupOptions() + { + return array( + new Horde_Argv_Option( + '-L', + '--list-deps', + array( + 'action' => 'store_true', + 'help' => 'generate a dependency listing' + ) + ), + ); + } + + /** + * Determine if this module should act. Run all required actions if it has + * been instructed to do so. + * + * @return NULL + */ + public function handle(Components_Config $config) + { + $options = $config->getOptions(); + if (!empty($options['list_deps'])) { + $this->_dependencies->getRunnerDependencies()->run(); + } + } +} diff --git a/components/lib/Components/Output.php b/components/lib/Components/Output.php index e3b81f2de..ecd44f103 100644 --- a/components/lib/Components/Output.php +++ b/components/lib/Components/Output.php @@ -72,6 +72,42 @@ class Components_Output $this->_nocolor = !empty($options['nocolor']); } + public function bold($text) + { + if ($this->_nocolor) { + $this->_cli->writeln($text); + } else { + $this->_cli->writeln($this->_cli->bold($text)); + } + } + + public function blue($text) + { + if ($this->_nocolor) { + $this->_cli->writeln($text); + } else { + $this->_cli->writeln($this->_cli->blue($text)); + } + } + + public function green($text) + { + if ($this->_nocolor) { + $this->_cli->writeln($text); + } else { + $this->_cli->writeln($this->_cli->green($text)); + } + } + + public function yellow($text) + { + if ($this->_nocolor) { + $this->_cli->writeln($text); + } else { + $this->_cli->writeln($this->_cli->yellow($text)); + } + } + public function ok($text) { if ($this->_quiet) { @@ -146,4 +182,14 @@ class Components_Output return $type; } } + + public function isVerbose() + { + return $this->_verbose; + } + + public function isQuiet() + { + return $this->_quiet; + } } \ No newline at end of file diff --git a/components/lib/Components/Pear/Factory.php b/components/lib/Components/Pear/Factory.php index 918d0ca75..a84c8157b 100644 --- a/components/lib/Components/Pear/Factory.php +++ b/components/lib/Components/Pear/Factory.php @@ -134,6 +134,24 @@ class Components_Pear_Factory } /** + * Create a tree helper for a specific PEAR environment.. + * + * @param string $config_file The path to the configuration file. + * @param string $root_path The basic root path for Horde packages. + * @param array $options The application options + * + * @return Components_Helper_Tree The tree helper. + */ + public function createSimpleTreeHelper( $root_path) + { + return new Components_Helper_Tree( + $this, + $this->_dependencies->createInstance('Components_Pear_InstallLocation'), + $root_path + ); + } + + /** * Return the PEAR Package representation. * * @param string $package_xml_path Path to the package.xml file. diff --git a/components/lib/Components/Pear/Package.php b/components/lib/Components/Pear/Package.php index 89126c8dd..573fb1d82 100644 --- a/components/lib/Components/Pear/Package.php +++ b/components/lib/Components/Pear/Package.php @@ -179,6 +179,16 @@ class Components_Pear_Package } /** + * Return the name for this package. + * + * @return string The package name. + */ + public function getName() + { + return $this->_getPackageFile()->getName(); + } + + /** * Return the description for this package. * * @return string The package description. diff --git a/components/lib/Components/Runner/Dependencies.php b/components/lib/Components/Runner/Dependencies.php new file mode 100644 index 000000000..f5375eb25 --- /dev/null +++ b/components/lib/Components/Runner/Dependencies.php @@ -0,0 +1,81 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Components + */ + +/** + * Components_Runner_Dependencies:: lists a tree of dependencies. + * + * Copyright 2010 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Horde + * @package Components + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Components + */ +class Components_Runner_Dependencies +{ + /** + * The configuration for the current job. + * + * @var Components_Config + */ + private $_config; + + /** + * The factory for PEAR dependencies. + * + * @var Components_Pear_Factory + */ + private $_factory; + + /** + * The output handler. + * + * @param Component_Output + */ + private $_output; + + /** + * Constructor. + * + * @param Components_Config $config The configuration for the current + * job. + * @param Components_Pear_Factory $factory The factory for PEAR + * dependencies. + * @param Component_Output $output The output handler. + */ + public function __construct( + Components_Config $config, + Components_Pear_Factory $factory, + Components_Output $output + ) { + $this->_config = $config; + $this->_factory = $factory; + $this->_output = $output; + } + + public function run() + { + $options = $this->_config->getOptions(); + $arguments = $this->_config->getArguments(); + $this->_factory + ->createSimpleTreeHelper(dirname(realpath($arguments[0]))) + ->listDependencyTree( + realpath($arguments[0]) . DIRECTORY_SEPARATOR . 'package.xml', + $this->_output + ); + } +} diff --git a/components/lib/Components/Runner/Installer.php b/components/lib/Components/Runner/Installer.php index 065bb69e8..ce3205fcc 100644 --- a/components/lib/Components/Runner/Installer.php +++ b/components/lib/Components/Runner/Installer.php @@ -29,8 +29,6 @@ */ class Components_Runner_Installer { - private $_run; - /** * The configuration for the current job. * diff --git a/components/package.xml b/components/package.xml index 0ae90edf8..d14185ea6 100644 --- a/components/package.xml +++ b/components/package.xml @@ -201,6 +201,10 @@ pear.horde.org + Util + pear.horde.org + + Injector pear.horde.org diff --git a/components/test/Components/Integration/Components/Module/DependenciesTest.php b/components/test/Components/Integration/Components/Module/DependenciesTest.php new file mode 100644 index 000000000..3652d80fb --- /dev/null +++ b/components/test/Components/Integration/Components/Module/DependenciesTest.php @@ -0,0 +1,80 @@ + + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Components + */ + +/** + * Prepare the test setup. + */ +require_once dirname(__FILE__) . '/../../../Autoload.php'; + +/** + * Test the Dependencies module. + * + * Copyright 2010 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @category Horde + * @package Components + * @subpackage UnitTests + * @author Gunnar Wrobel + * @license http://www.fsf.org/copyleft/lgpl.html LGPL + * @link http://pear.horde.org/index.php?package=Components + */ +class Components_Integration_Components_Module_DependenciesTest +extends Components_StoryTestCase +{ + /** + * @scenario + */ + public function theDependenciesModuleAddsTheLOptionInTheHelpOutput() + { + $this->given('the default Components setup') + ->when('calling the package with the help option') + ->then('the help will contain the option', '-L,\s*--list-deps'); + } + + /** + * @scenario + */ + public function theTheLOptionListThePackageDependenciesAsTree() + { + $this->given('the default Components setup') + ->when('calling the package with the list dependencies option and a path to a Horde framework component') + ->then('the non-Horde dependencies of the component will be listed') + ->and('the Horde dependencies of the component will be listed'); + } + + /** + * @scenario + */ + public function theTheVerboseLOptionListThePackageDependenciesAsTree() + { + $this->given('the default Components setup') + ->when('calling the package with the verbose list dependencies option and a path to a Horde framework component') + ->then('the non-Horde dependencies of the component will be listed') + ->and('the Horde dependencies of the component will be listed'); + } + + /** + * @scenario + */ + public function theTheQuietLOptionListThePackageDependenciesAsTree() + { + $this->given('the default Components setup') + ->when('calling the package with the quiet list dependencies option and a path to a Horde framework component') + ->then('the non-Horde dependencies of the component will be listed') + ->and('the Horde dependencies of the component will be listed'); + } +} \ No newline at end of file diff --git a/components/test/Components/Integration/Components/Module/InstallerTest.php b/components/test/Components/Integration/Components/Module/InstallerTest.php index 12040d98e..f9600bd54 100644 --- a/components/test/Components/Integration/Components/Module/InstallerTest.php +++ b/components/test/Components/Integration/Components/Module/InstallerTest.php @@ -42,7 +42,7 @@ extends Components_StoryTestCase { $this->given('the default Components setup') ->when('calling the package with the help option') - ->then('the help will contain the "i" option.'); + ->then('the help will contain the option', '-i\s*INSTALL,\s*--install=INSTALL'); } /** diff --git a/components/test/Components/StoryTestCase.php b/components/test/Components/StoryTestCase.php index 6a1bf25ad..4f9fa0c5b 100644 --- a/components/test/Components/StoryTestCase.php +++ b/components/test/Components/StoryTestCase.php @@ -182,6 +182,32 @@ extends PHPUnit_Extensions_Story_TestCase ); $world['output'] = $this->_callUnstrictComponents(); break; + case 'calling the package with the list dependencies option and a path to a Horde framework component': + $_SERVER['argv'] = array( + 'horde-components', + '--list-deps', + dirname(__FILE__) . '/fixture/framework/Install' + ); + $world['output'] = $this->_callUnstrictComponents(); + break; + case 'calling the package with the verbose list dependencies option and a path to a Horde framework component': + $_SERVER['argv'] = array( + 'horde-components', + '--verbose', + '--list-deps', + dirname(__FILE__) . '/fixture/framework/Install' + ); + $world['output'] = $this->_callUnstrictComponents(); + break; + case 'calling the package with the quiet list dependencies option and a path to a Horde framework component': + $_SERVER['argv'] = array( + 'horde-components', + '--quiet', + '--list-deps', + dirname(__FILE__) . '/fixture/framework/Install' + ); + $world['output'] = $this->_callUnstrictComponents(); + break; default: return $this->notImplemented($action); } @@ -223,12 +249,6 @@ extends PHPUnit_Extensions_Story_TestCase $world['output'] ); break; - case 'the help will contain the "i" option.': - $this->assertRegExp( - '/-i\s*INSTALL,\s*--install=INSTALL/', - $world['output'] - ); - break; case 'the help will contain the option': $this->assertRegExp( '/' . $arguments[0] . '/', @@ -374,6 +394,18 @@ extends PHPUnit_Extensions_Story_TestCase $world['output'] ); break; + case 'the non-Horde dependencies of the component will be listed': + $this->assertContains( + 'Console_Getopt', + $world['output'] + ); + break; + case 'the Horde dependencies of the component will be listed': + $this->assertContains( + 'Dependency', + $world['output'] + ); + break; default: return $this->notImplemented($action); }