ÿØÿàJFIFÿþ ÿÛC       ÿÛC ÿÀÿÄÿÄ"#QrÿÄÿÄ&1!A"2qQaáÿÚ ?Øy,æ/3JæÝ¹È߲؋5êXw²±ÉyˆR”¾I0ó2—PI¾IÌÚiMö¯–þrìN&"KgX:Šíµ•nTJnLK„…@!‰-ý ùúmë;ºgµŒ&ó±hw’¯Õ@”Ü— 9ñ-ë.²1<yà‚¹ïQÐU„ہ?.’¦èûbß±©Ö«Âw*VŒ) `$‰bØÔŸ’ëXÖ-ËTÜíGÚ3ð«g Ÿ§¯—Jx„–’U/ÂÅv_s(Hÿ@TñJÑãõçn­‚!ÈgfbÓc­:él[ðQe 9ÀPLbÃãCµm[5¿ç'ªjglå‡Ûí_§Úõl-;"PkÞÞÁQâ¼_Ñ^¢SŸx?"¸¦ùY騐ÒOÈ q’`~~ÚtËU¹CڒêV  I1Áß_ÿÙ#!/usr/bin/env php '/src/AbstractGenerator.php', 'sebastianbergmann\\phpunit\\skeletongenerator\\classgenerator' => '/src/ClassGenerator.php', 'sebastianbergmann\\phpunit\\skeletongenerator\\cli\\application' => '/src/CLI/Application.php', 'sebastianbergmann\\phpunit\\skeletongenerator\\cli\\basecommand' => '/src/CLI/BaseCommand.php', 'sebastianbergmann\\phpunit\\skeletongenerator\\cli\\generateclasscommand' => '/src/CLI/GenerateClassCommand.php', 'sebastianbergmann\\phpunit\\skeletongenerator\\cli\\generatetestcommand' => '/src/CLI/GenerateTestCommand.php', 'sebastianbergmann\\phpunit\\skeletongenerator\\testgenerator' => '/src/TestGenerator.php', 'sebastianbergmann\\version' => '/sebastian-version/Version.php', 'symfony\\component\\console\\application' => '/symfony/console/Symfony/Component/Console/Application.php', 'symfony\\component\\console\\command\\command' => '/symfony/console/Symfony/Component/Console/Command/Command.php', 'symfony\\component\\console\\command\\helpcommand' => '/symfony/console/Symfony/Component/Console/Command/HelpCommand.php', 'symfony\\component\\console\\command\\listcommand' => '/symfony/console/Symfony/Component/Console/Command/ListCommand.php', 'symfony\\component\\console\\consoleevents' => '/symfony/console/Symfony/Component/Console/ConsoleEvents.php', 'symfony\\component\\console\\descriptor\\applicationdescription' => '/symfony/console/Symfony/Component/Console/Descriptor/ApplicationDescription.php', 'symfony\\component\\console\\descriptor\\descriptor' => '/symfony/console/Symfony/Component/Console/Descriptor/Descriptor.php', 'symfony\\component\\console\\descriptor\\descriptorinterface' => '/symfony/console/Symfony/Component/Console/Descriptor/DescriptorInterface.php', 'symfony\\component\\console\\descriptor\\jsondescriptor' => '/symfony/console/Symfony/Component/Console/Descriptor/JsonDescriptor.php', 'symfony\\component\\console\\descriptor\\markdowndescriptor' => '/symfony/console/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php', 'symfony\\component\\console\\descriptor\\textdescriptor' => '/symfony/console/Symfony/Component/Console/Descriptor/TextDescriptor.php', 'symfony\\component\\console\\descriptor\\xmldescriptor' => '/symfony/console/Symfony/Component/Console/Descriptor/XmlDescriptor.php', 'symfony\\component\\console\\event\\consolecommandevent' => '/symfony/console/Symfony/Component/Console/Event/ConsoleCommandEvent.php', 'symfony\\component\\console\\event\\consoleevent' => '/symfony/console/Symfony/Component/Console/Event/ConsoleEvent.php', 'symfony\\component\\console\\event\\consoleexceptionevent' => '/symfony/console/Symfony/Component/Console/Event/ConsoleExceptionEvent.php', 'symfony\\component\\console\\event\\consoleterminateevent' => '/symfony/console/Symfony/Component/Console/Event/ConsoleTerminateEvent.php', 'symfony\\component\\console\\formatter\\outputformatter' => '/symfony/console/Symfony/Component/Console/Formatter/OutputFormatter.php', 'symfony\\component\\console\\formatter\\outputformatterinterface' => '/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterInterface.php', 'symfony\\component\\console\\formatter\\outputformatterstyle' => '/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyle.php', 'symfony\\component\\console\\formatter\\outputformatterstyleinterface' => '/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyleInterface.php', 'symfony\\component\\console\\formatter\\outputformatterstylestack' => '/symfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyleStack.php', 'symfony\\component\\console\\helper\\descriptorhelper' => '/symfony/console/Symfony/Component/Console/Helper/DescriptorHelper.php', 'symfony\\component\\console\\helper\\dialoghelper' => '/symfony/console/Symfony/Component/Console/Helper/DialogHelper.php', 'symfony\\component\\console\\helper\\formatterhelper' => '/symfony/console/Symfony/Component/Console/Helper/FormatterHelper.php', 'symfony\\component\\console\\helper\\helper' => '/symfony/console/Symfony/Component/Console/Helper/Helper.php', 'symfony\\component\\console\\helper\\helperinterface' => '/symfony/console/Symfony/Component/Console/Helper/HelperInterface.php', 'symfony\\component\\console\\helper\\helperset' => '/symfony/console/Symfony/Component/Console/Helper/HelperSet.php', 'symfony\\component\\console\\helper\\inputawarehelper' => '/symfony/console/Symfony/Component/Console/Helper/InputAwareHelper.php', 'symfony\\component\\console\\helper\\progresshelper' => '/symfony/console/Symfony/Component/Console/Helper/ProgressHelper.php', 'symfony\\component\\console\\helper\\tablehelper' => '/symfony/console/Symfony/Component/Console/Helper/TableHelper.php', 'symfony\\component\\console\\input\\argvinput' => '/symfony/console/Symfony/Component/Console/Input/ArgvInput.php', 'symfony\\component\\console\\input\\arrayinput' => '/symfony/console/Symfony/Component/Console/Input/ArrayInput.php', 'symfony\\component\\console\\input\\input' => '/symfony/console/Symfony/Component/Console/Input/Input.php', 'symfony\\component\\console\\input\\inputargument' => '/symfony/console/Symfony/Component/Console/Input/InputArgument.php', 'symfony\\component\\console\\input\\inputawareinterface' => '/symfony/console/Symfony/Component/Console/Input/InputAwareInterface.php', 'symfony\\component\\console\\input\\inputdefinition' => '/symfony/console/Symfony/Component/Console/Input/InputDefinition.php', 'symfony\\component\\console\\input\\inputinterface' => '/symfony/console/Symfony/Component/Console/Input/InputInterface.php', 'symfony\\component\\console\\input\\inputoption' => '/symfony/console/Symfony/Component/Console/Input/InputOption.php', 'symfony\\component\\console\\input\\stringinput' => '/symfony/console/Symfony/Component/Console/Input/StringInput.php', 'symfony\\component\\console\\output\\bufferedoutput' => '/symfony/console/Symfony/Component/Console/Output/BufferedOutput.php', 'symfony\\component\\console\\output\\consoleoutput' => '/symfony/console/Symfony/Component/Console/Output/ConsoleOutput.php', 'symfony\\component\\console\\output\\consoleoutputinterface' => '/symfony/console/Symfony/Component/Console/Output/ConsoleOutputInterface.php', 'symfony\\component\\console\\output\\nulloutput' => '/symfony/console/Symfony/Component/Console/Output/NullOutput.php', 'symfony\\component\\console\\output\\output' => '/symfony/console/Symfony/Component/Console/Output/Output.php', 'symfony\\component\\console\\output\\outputinterface' => '/symfony/console/Symfony/Component/Console/Output/OutputInterface.php', 'symfony\\component\\console\\output\\streamoutput' => '/symfony/console/Symfony/Component/Console/Output/StreamOutput.php', 'symfony\\component\\console\\shell' => '/symfony/console/Symfony/Component/Console/Shell.php', 'symfony\\component\\console\\tester\\applicationtester' => '/symfony/console/Symfony/Component/Console/Tester/ApplicationTester.php', 'symfony\\component\\console\\tester\\commandtester' => '/symfony/console/Symfony/Component/Console/Tester/CommandTester.php', 'symfony\\component\\yaml\\dumper' => '/symfony/yaml/Symfony/Component/Yaml/Dumper.php', 'symfony\\component\\yaml\\escaper' => '/symfony/yaml/Symfony/Component/Yaml/Escaper.php', 'symfony\\component\\yaml\\exception\\dumpexception' => '/symfony/yaml/Symfony/Component/Yaml/Exception/DumpException.php', 'symfony\\component\\yaml\\exception\\exceptioninterface' => '/symfony/yaml/Symfony/Component/Yaml/Exception/ExceptionInterface.php', 'symfony\\component\\yaml\\exception\\parseexception' => '/symfony/yaml/Symfony/Component/Yaml/Exception/ParseException.php', 'symfony\\component\\yaml\\exception\\runtimeexception' => '/symfony/yaml/Symfony/Component/Yaml/Exception/RuntimeException.php', 'symfony\\component\\yaml\\inline' => '/symfony/yaml/Symfony/Component/Yaml/Inline.php', 'symfony\\component\\yaml\\parser' => '/symfony/yaml/Symfony/Component/Yaml/Parser.php', 'symfony\\component\\yaml\\unescaper' => '/symfony/yaml/Symfony/Component/Yaml/Unescaper.php', 'symfony\\component\\yaml\\yaml' => '/symfony/yaml/Symfony/Component/Yaml/Yaml.php', 'text_template' => '/php-text-template/Template.php' ); } $class = strtolower($class); if (isset($classes[$class])) { require 'phar://phpunit-skelgen-2.0.1.phar' . $classes[$class]; } } ); Phar::mapPhar('phpunit-skelgen-2.0.1.phar'); if (isset($_SERVER['argv'][1]) && $_SERVER['argv'][1] == '--manifest') { print file_get_contents(__SKELGEN_PHAR_ROOT__ . '/manifest.txt'); exit; } $application = new SebastianBergmann\PHPUnit\SkeletonGenerator\CLI\Application; $application->run(); __HALT_COMPILER(); ?> 9Ophpunit-skelgen-2.0.1.pharsebastian-version/Version.php $sS Y php-text-template/Template.php$sS;$,symfony/yaml/Symfony/Component/Yaml/Yaml.php $sS ~.symfony/yaml/Symfony/Component/Yaml/Inline.php?$sS?۱.symfony/yaml/Symfony/Component/Yaml/Parser.php[$sS[J/symfony/yaml/Symfony/Component/Yaml/Escaper.phpm $sSm m2.symfony/yaml/Symfony/Component/Yaml/Dumper.php $sS H1symfony/yaml/Symfony/Component/Yaml/Unescaper.php$sS6H @symfony/yaml/Symfony/Component/Yaml/Exception/ParseException.php $sS Bsymfony/yaml/Symfony/Component/Yaml/Exception/RuntimeException.php$sS|-?symfony/yaml/Symfony/Component/Yaml/Exception/DumpException.php$sSؙ՚Dsymfony/yaml/Symfony/Component/Yaml/Exception/ExceptionInterface.php$sS+l;symfony/console/Symfony/Component/Console/Helper/Helper.php$sS7@Esymfony/console/Symfony/Component/Console/Helper/InputAwareHelper.php$sS˶Dsymfony/console/Symfony/Component/Console/Helper/FormatterHelper.php$sS'|ڶAsymfony/console/Symfony/Component/Console/Helper/DialogHelper.php>@$sS>@~Dsymfony/console/Symfony/Component/Console/Helper/HelperInterface.php$sSz׶Esymfony/console/Symfony/Component/Console/Helper/DescriptorHelper.php $sS "U1>symfony/console/Symfony/Component/Console/Helper/HelperSet.php $sS C< Csymfony/console/Symfony/Component/Console/Helper/ProgressHelper.phpc.$sSc.^3@symfony/console/Symfony/Component/Console/Helper/TableHelper.php2$sS2j9symfony/console/Symfony/Component/Console/Application.php$sS)c?symfony/console/Symfony/Component/Console/Output/NullOutput.php$sSF$_Dsymfony/console/Symfony/Component/Console/Output/OutputInterface.phpM $sSM 8Ksymfony/console/Symfony/Component/Console/Output/ConsoleOutputInterface.phpK$sSK0;symfony/console/Symfony/Component/Console/Output/Output.php;$sS;0Asymfony/console/Symfony/Component/Console/Output/StreamOutput.phpM $sSM $Csymfony/console/Symfony/Component/Console/Output/BufferedOutput.phph$sSht|X4Bsymfony/console/Symfony/Component/Console/Output/ConsoleOutput.phpz $sSz lͶGsymfony/console/Symfony/Component/Console/Formatter/OutputFormatter.php$sS~:Usymfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyleInterface.php$sS5Qsymfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyleStack.php $sS v Lsymfony/console/Symfony/Component/Console/Formatter/OutputFormatterStyle.php&$sS&onjPsymfony/console/Symfony/Component/Console/Formatter/OutputFormatterInterface.php$sSKFsymfony/console/Symfony/Component/Console/Tester/ApplicationTester.phpf $sSf ֏SBsymfony/console/Symfony/Component/Console/Tester/CommandTester.php$sS[ ߶;symfony/console/Symfony/Component/Console/ConsoleEvents.php$sS\0E?symfony/console/Symfony/Component/Console/Input/InputOption.phpS$sSSL]9symfony/console/Symfony/Component/Console/Input/Input.php$sS:޾Csymfony/console/Symfony/Component/Console/Input/InputDefinition.php(0$sS(0CGsymfony/console/Symfony/Component/Console/Input/InputAwareInterface.php^$sS^9Kh=symfony/console/Symfony/Component/Console/Input/ArgvInput.php)$sS)2l4?symfony/console/Symfony/Component/Console/Input/StringInput.php $sS ΂=Bsymfony/console/Symfony/Component/Console/Input/InputInterface.php3$sS3)A>symfony/console/Symfony/Component/Console/Input/ArrayInput.php%$sS%A-'Asymfony/console/Symfony/Component/Console/Input/InputArgument.php $sS U!9Gsymfony/console/Symfony/Component/Console/Event/ConsoleCommandEvent.php$sS||-&Isymfony/console/Symfony/Component/Console/Event/ConsoleExceptionEvent.phpA$sSAe@Isymfony/console/Symfony/Component/Console/Event/ConsoleTerminateEvent.php"$sS"-ܲ@symfony/console/Symfony/Component/Console/Event/ConsoleEvent.php$sS Asymfony/console/Symfony/Component/Console/Command/HelpCommand.php_ $sS_ m=symfony/console/Symfony/Component/Console/Command/Command.php4@$sS4@Lf:Asymfony/console/Symfony/Component/Console/Command/ListCommand.php $sS  xGsymfony/console/Symfony/Component/Console/Descriptor/TextDescriptor.php$sSLsymfony/console/Symfony/Component/Console/Descriptor/DescriptorInterface.php$sSJZ0<Fsymfony/console/Symfony/Component/Console/Descriptor/XmlDescriptor.php%$sS%ﶲͶCsymfony/console/Symfony/Component/Console/Descriptor/Descriptor.php $sS .iQKsymfony/console/Symfony/Component/Console/Descriptor/MarkdownDescriptor.php.$sS.lPOsymfony/console/Symfony/Component/Console/Descriptor/ApplicationDescription.php $sS sXGsymfony/console/Symfony/Component/Console/Descriptor/JsonDescriptor.phpu$sSuD3symfony/console/Symfony/Component/Console/Shell.php$sSPsrc/TestGenerator.php2$sS2q]src/AbstractGenerator.php$sSeWsrc/CLI/Application.php$sSQsrc/CLI/GenerateTestCommand.php$sS0mIsrc/CLI/BaseCommand.php$sSB src/CLI/GenerateClassCommand.phpx$sSxsrc/ClassGenerator.phpF$sSFsrc/template/Method.tpl.dist$sSagT*src/template/IncompleteTestMethod.tpl.distF$sSF3@q5/src/template/TestMethodExceptionStatic.tpl.dist$sS8)src/template/TestMethodException.tpl.dist$sSU*src/template/TestMethodBoolStatic.tpl.dist$sSP¶ src/template/TestMethod.tpl.dist"$sS"J^$src/template/TestMethodBool.tpl.dist $sS Tsrc/template/Class.tpl.distk$sSkF4)src/template/TestClass.tpl.dist$sS.&src/template/TestMethodStatic.tpl.dist $sS Bv*s manifest.txt|$sS|dC$. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * * Neither the name of Sebastian Bergmann nor the names of his * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @package Version * @author Sebastian Bergmann * @copyright 2013-2014 Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://github.com/sebastianbergmann/version * @since File available since Release 1.0.0 */ namespace SebastianBergmann; /** * @package Version * @author Sebastian Bergmann * @copyright 2013-2014 Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://github.com/sebastianbergmann/version * @since Class available since Release 1.0.0 */ class Version { private $path; private $release; private $version; /** * @param string $release * @param string $path */ public function __construct($release, $path) { $this->release = $release; $this->path = $path; } /** * @return string */ public function getVersion() { if ($this->version === null) { if (count(explode('.', $this->release)) == 3) { $this->version = $this->release; } else { $this->version = $this->release . '-dev'; } $git = $this->getGitInformation($this->path); if ($git) { if (count(explode('.', $this->release)) == 3) { $this->version = $git; } else { $git = explode('-', $git); $this->version = $this->release . '-' . $git[2]; } } } return $this->version; } /** * @param string $path * @return boolean|string */ private function getGitInformation($path) { if (!is_dir($path . DIRECTORY_SEPARATOR . '.git')) { return false; } $dir = getcwd(); chdir($path); $result = @exec('git describe --tags 2>&1', $output, $returnCode); chdir($dir); if ($returnCode !== 0) { return false; } return $result; } } . * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * * Neither the name of Sebastian Bergmann nor the names of his * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @category Text * @package Template * @author Sebastian Bergmann * @copyright 2009-2014 Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://github.com/sebastianbergmann/php-text-template * @since File available since Release 1.0.0 */ /** * A simple template engine. * * @category Text * @package Template * @author Sebastian Bergmann * @copyright 2009-2014 Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @version Release: @package_version@ * @link http://github.com/sebastianbergmann/php-text-template * @since Class available since Release 1.0.0 */ class Text_Template { /** * @var string */ protected $template = ''; /** * @var string */ protected $openDelimiter = '{'; /** * @var string */ protected $closeDelimiter = '}'; /** * @var array */ protected $values = array(); /** * Constructor. * * @param string $file * @throws InvalidArgumentException */ public function __construct($file = '', $openDelimiter = '{', $closeDelimiter = '}') { $this->setFile($file); $this->openDelimiter = $openDelimiter; $this->closeDelimiter = $closeDelimiter; } /** * Sets the template file. * * @param string $file * @throws InvalidArgumentException */ public function setFile($file) { $distFile = $file . '.dist'; if (file_exists($file)) { $this->template = file_get_contents($file); } else if (file_exists($distFile)) { $this->template = file_get_contents($distFile); } else { throw new InvalidArgumentException( 'Template file could not be loaded.' ); } } /** * Sets one or more template variables. * * @param array $values * @param boolean $merge */ public function setVar(array $values, $merge = TRUE) { if (!$merge || empty($this->values)) { $this->values = $values; } else { $this->values = array_merge($this->values, $values); } } /** * Renders the template and returns the result. * * @return string */ public function render() { $keys = array(); foreach ($this->values as $key => $value) { $keys[] = $this->openDelimiter . $key . $this->closeDelimiter; } return str_replace($keys, $this->values, $this->template); } /** * Renders the template and writes the result to a file. * * @param string $target */ public function renderTo($target) { $fp = @fopen($target, 'wt'); if ($fp) { fwrite($fp, $this->render()); fclose($fp); } else { $error = error_get_last(); throw new RuntimeException( sprintf( 'Could not write to %s: %s', $target, substr( $error['message'], strpos($error['message'], ':') + 2 ) ) ); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Yaml; use Symfony\Component\Yaml\Exception\ParseException; /** * Yaml offers convenience methods to load and dump YAML. * * @author Fabien Potencier * * @api */ class Yaml { /** * Parses YAML into a PHP array. * * The parse method, when supplied with a YAML stream (string or file), * will do its best to convert YAML in a file into a PHP array. * * Usage: * * $array = Yaml::parse('config.yml'); * print_r($array); * * * As this method accepts both plain strings and file names as an input, * you must validate the input before calling this method. Passing a file * as an input is a deprecated feature and will be removed in 3.0. * * @param string $input Path to a YAML file or a string containing YAML * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types false otherwise * @param bool $objectSupport True if object support is enabled, false otherwise * * @return array The YAML converted to a PHP array * * @throws ParseException If the YAML is not valid * * @api */ public static function parse($input, $exceptionOnInvalidType = false, $objectSupport = false) { // if input is a file, process it $file = ''; if (strpos($input, "\n") === false && is_file($input)) { if (false === is_readable($input)) { throw new ParseException(sprintf('Unable to parse "%s" as the file is not readable.', $input)); } $file = $input; $input = file_get_contents($file); } $yaml = new Parser(); try { return $yaml->parse($input, $exceptionOnInvalidType, $objectSupport); } catch (ParseException $e) { if ($file) { $e->setParsedFile($file); } throw $e; } } /** * Dumps a PHP array to a YAML string. * * The dump method, when supplied with an array, will do its best * to convert the array into friendly YAML. * * @param array $array PHP array * @param int $inline The level where you switch to inline YAML * @param int $indent The amount of spaces to use for indentation of nested nodes. * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise * @param bool $objectSupport true if object support is enabled, false otherwise * * @return string A YAML string representing the original PHP array * * @api */ public static function dump($array, $inline = 2, $indent = 4, $exceptionOnInvalidType = false, $objectSupport = false) { $yaml = new Dumper(); $yaml->setIndentation($indent); return $yaml->dump($array, $inline, 0, $exceptionOnInvalidType, $objectSupport); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Yaml; use Symfony\Component\Yaml\Exception\ParseException; use Symfony\Component\Yaml\Exception\DumpException; /** * Inline implements a YAML parser/dumper for the YAML inline syntax. * * @author Fabien Potencier */ class Inline { const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*(?:\\\\.[^"\\\\]*)*)"|\'([^\']*(?:\'\'[^\']*)*)\')'; private static $exceptionOnInvalidType = false; private static $objectSupport = false; /** * Converts a YAML string to a PHP array. * * @param string $value A YAML string * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise * @param bool $objectSupport true if object support is enabled, false otherwise * * @return array A PHP array representing the YAML string * * @throws ParseException */ public static function parse($value, $exceptionOnInvalidType = false, $objectSupport = false) { self::$exceptionOnInvalidType = $exceptionOnInvalidType; self::$objectSupport = $objectSupport; $value = trim($value); if (0 == strlen($value)) { return ''; } if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) { $mbEncoding = mb_internal_encoding(); mb_internal_encoding('ASCII'); } $i = 0; switch ($value[0]) { case '[': $result = self::parseSequence($value, $i); ++$i; break; case '{': $result = self::parseMapping($value, $i); ++$i; break; default: $result = self::parseScalar($value, null, array('"', "'"), $i); } // some comments are allowed at the end if (preg_replace('/\s+#.*$/A', '', substr($value, $i))) { throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i))); } if (isset($mbEncoding)) { mb_internal_encoding($mbEncoding); } return $result; } /** * Dumps a given PHP variable to a YAML string. * * @param mixed $value The PHP variable to convert * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise * @param bool $objectSupport true if object support is enabled, false otherwise * * @return string The YAML string representing the PHP array * * @throws DumpException When trying to dump PHP resource */ public static function dump($value, $exceptionOnInvalidType = false, $objectSupport = false) { switch (true) { case is_resource($value): if ($exceptionOnInvalidType) { throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value))); } return 'null'; case is_object($value): if ($objectSupport) { return '!!php/object:'.serialize($value); } if ($exceptionOnInvalidType) { throw new DumpException('Object support when dumping a YAML file has been disabled.'); } return 'null'; case is_array($value): return self::dumpArray($value, $exceptionOnInvalidType, $objectSupport); case null === $value: return 'null'; case true === $value: return 'true'; case false === $value: return 'false'; case ctype_digit($value): return is_string($value) ? "'$value'" : (int) $value; case is_numeric($value): $locale = setlocale(LC_NUMERIC, 0); if (false !== $locale) { setlocale(LC_NUMERIC, 'C'); } $repr = is_string($value) ? "'$value'" : (is_infinite($value) ? str_ireplace('INF', '.Inf', strval($value)) : strval($value)); if (false !== $locale) { setlocale(LC_NUMERIC, $locale); } return $repr; case Escaper::requiresDoubleQuoting($value): return Escaper::escapeWithDoubleQuotes($value); case Escaper::requiresSingleQuoting($value): return Escaper::escapeWithSingleQuotes($value); case '' == $value: return "''"; case preg_match(self::getTimestampRegex(), $value): case in_array(strtolower($value), array('null', '~', 'true', 'false')): return "'$value'"; default: return $value; } } /** * Dumps a PHP array to a YAML string. * * @param array $value The PHP array to dump * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise * @param bool $objectSupport true if object support is enabled, false otherwise * * @return string The YAML string representing the PHP array */ private static function dumpArray($value, $exceptionOnInvalidType, $objectSupport) { // array $keys = array_keys($value); if ((1 == count($keys) && '0' == $keys[0]) || (count($keys) > 1 && array_reduce($keys, function ($v, $w) { return (int) $v + $w; }, 0) == count($keys) * (count($keys) - 1) / 2) ) { $output = array(); foreach ($value as $val) { $output[] = self::dump($val, $exceptionOnInvalidType, $objectSupport); } return sprintf('[%s]', implode(', ', $output)); } // mapping $output = array(); foreach ($value as $key => $val) { $output[] = sprintf('%s: %s', self::dump($key, $exceptionOnInvalidType, $objectSupport), self::dump($val, $exceptionOnInvalidType, $objectSupport)); } return sprintf('{ %s }', implode(', ', $output)); } /** * Parses a scalar to a YAML string. * * @param scalar $scalar * @param string $delimiters * @param array $stringDelimiters * @param int &$i * @param bool $evaluate * * @return string A YAML string * * @throws ParseException When malformed inline YAML string is parsed */ public static function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true) { if (in_array($scalar[$i], $stringDelimiters)) { // quoted scalar $output = self::parseQuotedScalar($scalar, $i); if (null !== $delimiters) { $tmp = ltrim(substr($scalar, $i), ' '); if (!in_array($tmp[0], $delimiters)) { throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i))); } } } else { // "normal" string if (!$delimiters) { $output = substr($scalar, $i); $i += strlen($output); // remove comments if (false !== $strpos = strpos($output, ' #')) { $output = rtrim(substr($output, 0, $strpos)); } } elseif (preg_match('/^(.+?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) { $output = $match[1]; $i += strlen($output); } else { throw new ParseException(sprintf('Malformed inline YAML string (%s).', $scalar)); } if ($evaluate) { $output = self::evaluateScalar($output); } } return $output; } /** * Parses a quoted scalar to YAML. * * @param string $scalar * @param int &$i * * @return string A YAML string * * @throws ParseException When malformed inline YAML string is parsed */ private static function parseQuotedScalar($scalar, &$i) { if (!preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) { throw new ParseException(sprintf('Malformed inline YAML string (%s).', substr($scalar, $i))); } $output = substr($match[0], 1, strlen($match[0]) - 2); $unescaper = new Unescaper(); if ('"' == $scalar[$i]) { $output = $unescaper->unescapeDoubleQuotedString($output); } else { $output = $unescaper->unescapeSingleQuotedString($output); } $i += strlen($match[0]); return $output; } /** * Parses a sequence to a YAML string. * * @param string $sequence * @param int &$i * * @return string A YAML string * * @throws ParseException When malformed inline YAML string is parsed */ private static function parseSequence($sequence, &$i = 0) { $output = array(); $len = strlen($sequence); $i += 1; // [foo, bar, ...] while ($i < $len) { switch ($sequence[$i]) { case '[': // nested sequence $output[] = self::parseSequence($sequence, $i); break; case '{': // nested mapping $output[] = self::parseMapping($sequence, $i); break; case ']': return $output; case ',': case ' ': break; default: $isQuoted = in_array($sequence[$i], array('"', "'")); $value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i); if (!$isQuoted && false !== strpos($value, ': ')) { // embedded mapping? try { $value = self::parseMapping('{'.$value.'}'); } catch (\InvalidArgumentException $e) { // no, it's not } } $output[] = $value; --$i; } ++$i; } throw new ParseException(sprintf('Malformed inline YAML string %s', $sequence)); } /** * Parses a mapping to a YAML string. * * @param string $mapping * @param int &$i * * @return string A YAML string * * @throws ParseException When malformed inline YAML string is parsed */ private static function parseMapping($mapping, &$i = 0) { $output = array(); $len = strlen($mapping); $i += 1; // {foo: bar, bar:foo, ...} while ($i < $len) { switch ($mapping[$i]) { case ' ': case ',': ++$i; continue 2; case '}': return $output; } // key $key = self::parseScalar($mapping, array(':', ' '), array('"', "'"), $i, false); // value $done = false; while ($i < $len) { switch ($mapping[$i]) { case '[': // nested sequence $output[$key] = self::parseSequence($mapping, $i); $done = true; break; case '{': // nested mapping $output[$key] = self::parseMapping($mapping, $i); $done = true; break; case ':': case ' ': break; default: $output[$key] = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i); $done = true; --$i; } ++$i; if ($done) { continue 2; } } } throw new ParseException(sprintf('Malformed inline YAML string %s', $mapping)); } /** * Evaluates scalars and replaces magic values. * * @param string $scalar * * @return string A YAML string */ private static function evaluateScalar($scalar) { $scalar = trim($scalar); $scalarLower = strtolower($scalar); switch (true) { case 'null' === $scalarLower: case '' === $scalar: case '~' === $scalar: return; case 'true' === $scalarLower: return true; case 'false' === $scalarLower: return false; // Optimise for returning strings. case $scalar[0] === '+' || $scalar[0] === '-' || $scalar[0] === '.' || $scalar[0] === '!' || is_numeric($scalar[0]): switch (true) { case 0 === strpos($scalar, '!str'): return (string) substr($scalar, 5); case 0 === strpos($scalar, '! '): return intval(self::parseScalar(substr($scalar, 2))); case 0 === strpos($scalar, '!!php/object:'): if (self::$objectSupport) { return unserialize(substr($scalar, 13)); } if (self::$exceptionOnInvalidType) { throw new ParseException('Object support when parsing a YAML file has been disabled.'); } return; case ctype_digit($scalar): $raw = $scalar; $cast = intval($scalar); return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw); case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)): $raw = $scalar; $cast = intval($scalar); return '0' == $scalar[1] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw); case is_numeric($scalar): return '0x' == $scalar[0].$scalar[1] ? hexdec($scalar) : floatval($scalar); case '.inf' === $scalarLower: case '.nan' === $scalarLower: return -log(0); case '-.inf' === $scalarLower: return log(0); case preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar): return floatval(str_replace(',', '', $scalar)); case preg_match(self::getTimestampRegex(), $scalar): return strtotime($scalar); } default: return (string) $scalar; } } /** * Gets a regex that matches a YAML date. * * @return string The regular expression * * @see http://www.yaml.org/spec/1.2/spec.html#id2761573 */ private static function getTimestampRegex() { return <<[0-9][0-9][0-9][0-9]) -(?P[0-9][0-9]?) -(?P[0-9][0-9]?) (?:(?:[Tt]|[ \t]+) (?P[0-9][0-9]?) :(?P[0-9][0-9]) :(?P[0-9][0-9]) (?:\.(?P[0-9]*))? (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?) (?::(?P[0-9][0-9]))?))?)? $~x EOF; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Yaml; use Symfony\Component\Yaml\Exception\ParseException; /** * Parser parses YAML strings to convert them to PHP arrays. * * @author Fabien Potencier */ class Parser { const FOLDED_SCALAR_PATTERN = '(?P\||>)(?P\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P +#.*)?'; private $offset = 0; private $lines = array(); private $currentLineNb = -1; private $currentLine = ''; private $refs = array(); /** * Constructor * * @param int $offset The offset of YAML document (used for line numbers in error messages) */ public function __construct($offset = 0) { $this->offset = $offset; } /** * Parses a YAML string to a PHP value. * * @param string $value A YAML string * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise * @param bool $objectSupport true if object support is enabled, false otherwise * * @return mixed A PHP value * * @throws ParseException If the YAML is not valid */ public function parse($value, $exceptionOnInvalidType = false, $objectSupport = false) { $this->currentLineNb = -1; $this->currentLine = ''; $this->lines = explode("\n", $this->cleanup($value)); if (function_exists('mb_detect_encoding') && false === mb_detect_encoding($value, 'UTF-8', true)) { throw new ParseException('The YAML value does not appear to be valid UTF-8.'); } if (function_exists('mb_internal_encoding') && ((int) ini_get('mbstring.func_overload')) & 2) { $mbEncoding = mb_internal_encoding(); mb_internal_encoding('UTF-8'); } $data = array(); $context = null; while ($this->moveToNextLine()) { if ($this->isCurrentLineEmpty()) { continue; } // tab? if ("\t" === $this->currentLine[0]) { throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine); } $isRef = $isInPlace = $isProcessed = false; if (preg_match('#^\-((?P\s+)(?P.+?))?\s*$#u', $this->currentLine, $values)) { if ($context && 'mapping' == $context) { throw new ParseException('You cannot define a sequence item when in a mapping'); } $context = 'sequence'; if (isset($values['value']) && preg_match('#^&(?P[^ ]+) *(?P.*)#u', $values['value'], $matches)) { $isRef = $matches['ref']; $values['value'] = $matches['value']; } // array if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) { $c = $this->getRealCurrentLineNb() + 1; $parser = new Parser($c); $parser->refs =& $this->refs; $data[] = $parser->parse($this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport); } else { if (isset($values['leadspaces']) && ' ' == $values['leadspaces'] && preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P.+?))?\s*$#u', $values['value'], $matches) ) { // this is a compact notation element, add to next block and parse $c = $this->getRealCurrentLineNb(); $parser = new Parser($c); $parser->refs =& $this->refs; $block = $values['value']; if ($this->isNextLineIndented()) { $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + 2); } $data[] = $parser->parse($block, $exceptionOnInvalidType, $objectSupport); } else { $data[] = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport); } } } elseif (preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\[\{].*?) *\:(\s+(?P.+?))?\s*$#u', $this->currentLine, $values) && false === strpos($values['key'],' #')) { if ($context && 'sequence' == $context) { throw new ParseException('You cannot define a mapping item when in a sequence'); } $context = 'mapping'; // force correct settings Inline::parse(null, $exceptionOnInvalidType, $objectSupport); try { $key = Inline::parseScalar($values['key']); } catch (ParseException $e) { $e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setSnippet($this->currentLine); throw $e; } if ('<<' === $key) { if (isset($values['value']) && 0 === strpos($values['value'], '*')) { $isInPlace = substr($values['value'], 1); if (!array_key_exists($isInPlace, $this->refs)) { throw new ParseException(sprintf('Reference "%s" does not exist.', $isInPlace), $this->getRealCurrentLineNb() + 1, $this->currentLine); } } else { if (isset($values['value']) && $values['value'] !== '') { $value = $values['value']; } else { $value = $this->getNextEmbedBlock(); } $c = $this->getRealCurrentLineNb() + 1; $parser = new Parser($c); $parser->refs =& $this->refs; $parsed = $parser->parse($value, $exceptionOnInvalidType, $objectSupport); $merged = array(); if (!is_array($parsed)) { throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine); } elseif (isset($parsed[0])) { // Numeric array, merge individual elements foreach (array_reverse($parsed) as $parsedItem) { if (!is_array($parsedItem)) { throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem); } $merged = array_merge($parsedItem, $merged); } } else { // Associative array, merge $merged = array_merge($merged, $parsed); } $isProcessed = $merged; } } elseif (isset($values['value']) && preg_match('#^&(?P[^ ]+) *(?P.*)#u', $values['value'], $matches)) { $isRef = $matches['ref']; $values['value'] = $matches['value']; } if ($isProcessed) { // Merge keys $data = $isProcessed; // hash } elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) { // if next line is less indented or equal, then it means that the current value is null if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) { $data[$key] = null; } else { $c = $this->getRealCurrentLineNb() + 1; $parser = new Parser($c); $parser->refs =& $this->refs; $data[$key] = $parser->parse($this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport); } } else { if ($isInPlace) { $data = $this->refs[$isInPlace]; } else { $data[$key] = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport); } } } else { // 1-liner optionally followed by newline $lineCount = count($this->lines); if (1 === $lineCount || (2 === $lineCount && empty($this->lines[1]))) { try { $value = Inline::parse($this->lines[0], $exceptionOnInvalidType, $objectSupport); } catch (ParseException $e) { $e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setSnippet($this->currentLine); throw $e; } if (is_array($value)) { $first = reset($value); if (is_string($first) && 0 === strpos($first, '*')) { $data = array(); foreach ($value as $alias) { $data[] = $this->refs[substr($alias, 1)]; } $value = $data; } } if (isset($mbEncoding)) { mb_internal_encoding($mbEncoding); } return $value; } switch (preg_last_error()) { case PREG_INTERNAL_ERROR: $error = 'Internal PCRE error.'; break; case PREG_BACKTRACK_LIMIT_ERROR: $error = 'pcre.backtrack_limit reached.'; break; case PREG_RECURSION_LIMIT_ERROR: $error = 'pcre.recursion_limit reached.'; break; case PREG_BAD_UTF8_ERROR: $error = 'Malformed UTF-8 data.'; break; case PREG_BAD_UTF8_OFFSET_ERROR: $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.'; break; default: $error = 'Unable to parse.'; } throw new ParseException($error, $this->getRealCurrentLineNb() + 1, $this->currentLine); } if ($isRef) { $this->refs[$isRef] = end($data); } } if (isset($mbEncoding)) { mb_internal_encoding($mbEncoding); } return empty($data) ? null : $data; } /** * Returns the current line number (takes the offset into account). * * @return int The current line number */ private function getRealCurrentLineNb() { return $this->currentLineNb + $this->offset; } /** * Returns the current line indentation. * * @return int The current line indentation */ private function getCurrentLineIndentation() { return strlen($this->currentLine) - strlen(ltrim($this->currentLine, ' ')); } /** * Returns the next embed block of YAML. * * @param int $indentation The indent level at which the block is to be read, or null for default * * @return string A YAML string * * @throws ParseException When indentation problem are detected */ private function getNextEmbedBlock($indentation = null) { $this->moveToNextLine(); if (null === $indentation) { $newIndent = $this->getCurrentLineIndentation(); $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem($this->currentLine); if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) { throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine); } } else { $newIndent = $indentation; } $data = array(substr($this->currentLine, $newIndent)); $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem($this->currentLine); // Comments must not be removed inside a string block (ie. after a line ending with "|") $removeCommentsPattern = '~'.self::FOLDED_SCALAR_PATTERN.'$~'; $removeComments = !preg_match($removeCommentsPattern, $this->currentLine); while ($this->moveToNextLine()) { $indent = $this->getCurrentLineIndentation(); if ($indent === $newIndent) { $removeComments = !preg_match($removeCommentsPattern, $this->currentLine); } if ($isItUnindentedCollection && !$this->isStringUnIndentedCollectionItem($this->currentLine)) { $this->moveToPreviousLine(); break; } if ($this->isCurrentLineBlank()) { $data[] = substr($this->currentLine, $newIndent); continue; } if ($removeComments && $this->isCurrentLineComment()) { continue; } if ($indent >= $newIndent) { $data[] = substr($this->currentLine, $newIndent); } elseif (0 == $indent) { $this->moveToPreviousLine(); break; } else { throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine); } } return implode("\n", $data); } /** * Moves the parser to the next line. * * @return bool */ private function moveToNextLine() { if ($this->currentLineNb >= count($this->lines) - 1) { return false; } $this->currentLine = $this->lines[++$this->currentLineNb]; return true; } /** * Moves the parser to the previous line. */ private function moveToPreviousLine() { $this->currentLine = $this->lines[--$this->currentLineNb]; } /** * Parses a YAML value. * * @param string $value A YAML value * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types false otherwise * @param bool $objectSupport True if object support is enabled, false otherwise * * @return mixed A PHP value * * @throws ParseException When reference does not exist */ private function parseValue($value, $exceptionOnInvalidType, $objectSupport) { if (0 === strpos($value, '*')) { if (false !== $pos = strpos($value, '#')) { $value = substr($value, 1, $pos - 2); } else { $value = substr($value, 1); } if (!array_key_exists($value, $this->refs)) { throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLine); } return $this->refs[$value]; } if (preg_match('/^'.self::FOLDED_SCALAR_PATTERN.'$/', $value, $matches)) { $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : ''; return $this->parseFoldedScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), intval(abs($modifiers))); } try { return Inline::parse($value, $exceptionOnInvalidType, $objectSupport); } catch (ParseException $e) { $e->setParsedLine($this->getRealCurrentLineNb() + 1); $e->setSnippet($this->currentLine); throw $e; } } /** * Parses a folded scalar. * * @param string $separator The separator that was used to begin this folded scalar (| or >) * @param string $indicator The indicator that was used to begin this folded scalar (+ or -) * @param int $indentation The indentation that was used to begin this folded scalar * * @return string The text value */ private function parseFoldedScalar($separator, $indicator = '', $indentation = 0) { $notEOF = $this->moveToNextLine(); if (!$notEOF) { return ''; } $isCurrentLineBlank = $this->isCurrentLineBlank(); $text = ''; // leading blank lines are consumed before determining indentation while ($notEOF && $isCurrentLineBlank) { // newline only if not EOF if ($notEOF = $this->moveToNextLine()) { $text .= "\n"; $isCurrentLineBlank = $this->isCurrentLineBlank(); } } // determine indentation if not specified if (0 === $indentation) { if (preg_match('/^ +/', $this->currentLine, $matches)) { $indentation = strlen($matches[0]); } } if ($indentation > 0) { $pattern = sprintf('/^ {%d}(.*)$/', $indentation); while ( $notEOF && ( $isCurrentLineBlank || preg_match($pattern, $this->currentLine, $matches) ) ) { if ($isCurrentLineBlank) { $text .= substr($this->currentLine, $indentation); } else { $text .= $matches[1]; } // newline only if not EOF if ($notEOF = $this->moveToNextLine()) { $text .= "\n"; $isCurrentLineBlank = $this->isCurrentLineBlank(); } } } elseif ($notEOF) { $text .= "\n"; } if ($notEOF) { $this->moveToPreviousLine(); } // replace all non-trailing single newlines with spaces in folded blocks if ('>' === $separator) { preg_match('/(\n*)$/', $text, $matches); $text = preg_replace('/(?getCurrentLineIndentation(); $EOF = !$this->moveToNextLine(); while (!$EOF && $this->isCurrentLineEmpty()) { $EOF = !$this->moveToNextLine(); } if ($EOF) { return false; } $ret = false; if ($this->getCurrentLineIndentation() > $currentIndentation) { $ret = true; } $this->moveToPreviousLine(); return $ret; } /** * Returns true if the current line is blank or if it is a comment line. * * @return bool Returns true if the current line is empty or if it is a comment line, false otherwise */ private function isCurrentLineEmpty() { return $this->isCurrentLineBlank() || $this->isCurrentLineComment(); } /** * Returns true if the current line is blank. * * @return bool Returns true if the current line is blank, false otherwise */ private function isCurrentLineBlank() { return '' == trim($this->currentLine, ' '); } /** * Returns true if the current line is a comment line. * * @return bool Returns true if the current line is a comment line, false otherwise */ private function isCurrentLineComment() { //checking explicitly the first char of the trim is faster than loops or strpos $ltrimmedLine = ltrim($this->currentLine, ' '); return $ltrimmedLine[0] === '#'; } /** * Cleanups a YAML string to be parsed. * * @param string $value The input YAML string * * @return string A cleaned up YAML string */ private function cleanup($value) { $value = str_replace(array("\r\n", "\r"), "\n", $value); // strip YAML header $count = 0; $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#su', '', $value, -1, $count); $this->offset += $count; // remove leading comments $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count); if ($count == 1) { // items have been removed, update the offset $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); $value = $trimmedValue; } // remove start of the document marker (---) $trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count); if ($count == 1) { // items have been removed, update the offset $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n"); $value = $trimmedValue; // remove end of the document marker (...) $value = preg_replace('#\.\.\.\s*$#s', '', $value); } return $value; } /** * Returns true if the next line starts unindented collection * * @return bool Returns true if the next line starts unindented collection, false otherwise */ private function isNextLineUnIndentedCollection() { $currentIndentation = $this->getCurrentLineIndentation(); $notEOF = $this->moveToNextLine(); while ($notEOF && $this->isCurrentLineEmpty()) { $notEOF = $this->moveToNextLine(); } if (false === $notEOF) { return false; } $ret = false; if ( $this->getCurrentLineIndentation() == $currentIndentation && $this->isStringUnIndentedCollectionItem($this->currentLine) ) { $ret = true; } $this->moveToPreviousLine(); return $ret; } /** * Returns true if the string is un-indented collection item * * @return bool Returns true if the string is un-indented collection item, false otherwise */ private function isStringUnIndentedCollectionItem() { return (0 === strpos($this->currentLine, '- ')); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Yaml; /** * Escaper encapsulates escaping rules for single and double-quoted * YAML strings. * * @author Matthew Lewinski */ class Escaper { // Characters that would cause a dumped string to require double quoting. const REGEX_CHARACTER_TO_ESCAPE = "[\\x00-\\x1f]|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9"; // Mapping arrays for escaping a double quoted string. The backslash is // first to ensure proper escaping because str_replace operates iteratively // on the input arrays. This ordering of the characters avoids the use of strtr, // which performs more slowly. private static $escapees = array('\\\\', '\\"', '"', "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", "\x08", "\x09", "\x0a", "\x0b", "\x0c", "\x0d", "\x0e", "\x0f", "\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17", "\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f", "\xc2\x85", "\xc2\xa0", "\xe2\x80\xa8", "\xe2\x80\xa9"); private static $escaped = array('\\"', '\\\\', '\\"', "\\0", "\\x01", "\\x02", "\\x03", "\\x04", "\\x05", "\\x06", "\\a", "\\b", "\\t", "\\n", "\\v", "\\f", "\\r", "\\x0e", "\\x0f", "\\x10", "\\x11", "\\x12", "\\x13", "\\x14", "\\x15", "\\x16", "\\x17", "\\x18", "\\x19", "\\x1a", "\\e", "\\x1c", "\\x1d", "\\x1e", "\\x1f", "\\N", "\\_", "\\L", "\\P"); /** * Determines if a PHP value would require double quoting in YAML. * * @param string $value A PHP value * * @return bool True if the value would require double quotes. */ public static function requiresDoubleQuoting($value) { return preg_match('/'.self::REGEX_CHARACTER_TO_ESCAPE.'/u', $value); } /** * Escapes and surrounds a PHP value with double quotes. * * @param string $value A PHP value * * @return string The quoted, escaped string */ public static function escapeWithDoubleQuotes($value) { return sprintf('"%s"', str_replace(self::$escapees, self::$escaped, $value)); } /** * Determines if a PHP value would require single quoting in YAML. * * @param string $value A PHP value * * @return bool True if the value would require single quotes. */ public static function requiresSingleQuoting($value) { return preg_match('/[ \s \' " \: \{ \} \[ \] , & \* \# \?] | \A[ \- ? | < > = ! % @ ` ]/x', $value); } /** * Escapes and surrounds a PHP value with single quotes. * * @param string $value A PHP value * * @return string The quoted, escaped string */ public static function escapeWithSingleQuotes($value) { return sprintf("'%s'", str_replace('\'', '\'\'', $value)); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Yaml; /** * Dumper dumps PHP variables to YAML strings. * * @author Fabien Potencier */ class Dumper { /** * The amount of spaces to use for indentation of nested nodes. * * @var int */ protected $indentation = 4; /** * Sets the indentation. * * @param int $num The amount of spaces to use for indentation of nested nodes. */ public function setIndentation($num) { $this->indentation = (int) $num; } /** * Dumps a PHP value to YAML. * * @param mixed $input The PHP value * @param int $inline The level where you switch to inline YAML * @param int $indent The level of indentation (used internally) * @param bool $exceptionOnInvalidType true if an exception must be thrown on invalid types (a PHP resource or object), false otherwise * @param bool $objectSupport true if object support is enabled, false otherwise * * @return string The YAML representation of the PHP value */ public function dump($input, $inline = 0, $indent = 0, $exceptionOnInvalidType = false, $objectSupport = false) { $output = ''; $prefix = $indent ? str_repeat(' ', $indent) : ''; if ($inline <= 0 || !is_array($input) || empty($input)) { $output .= $prefix.Inline::dump($input, $exceptionOnInvalidType, $objectSupport); } else { $isAHash = array_keys($input) !== range(0, count($input) - 1); foreach ($input as $key => $value) { $willBeInlined = $inline - 1 <= 0 || !is_array($value) || empty($value); $output .= sprintf('%s%s%s%s', $prefix, $isAHash ? Inline::dump($key, $exceptionOnInvalidType, $objectSupport).':' : '-', $willBeInlined ? ' ' : "\n", $this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + $this->indentation, $exceptionOnInvalidType, $objectSupport) ).($willBeInlined ? "\n" : ''); } } return $output; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Yaml; /** * Unescaper encapsulates unescaping rules for single and double-quoted * YAML strings. * * @author Matthew Lewinski */ class Unescaper { // Parser and Inline assume UTF-8 encoding, so escaped Unicode characters // must be converted to that encoding. const ENCODING = 'UTF-8'; // Regex fragment that matches an escaped character in a double quoted // string. const REGEX_ESCAPED_CHARACTER = "\\\\([0abt\tnvfre \\\"\\/\\\\N_LP]|x[0-9a-fA-F]{2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})"; /** * Unescapes a single quoted string. * * @param string $value A single quoted string. * * @return string The unescaped string. */ public function unescapeSingleQuotedString($value) { return str_replace('\'\'', '\'', $value); } /** * Unescapes a double quoted string. * * @param string $value A double quoted string. * * @return string The unescaped string. */ public function unescapeDoubleQuotedString($value) { $self = $this; $callback = function ($match) use ($self) { return $self->unescapeCharacter($match[0]); }; // evaluate the string return preg_replace_callback('/'.self::REGEX_ESCAPED_CHARACTER.'/u', $callback, $value); } /** * Unescapes a character that was found in a double-quoted string * * @param string $value An escaped character * * @return string The unescaped character */ public function unescapeCharacter($value) { switch ($value{1}) { case '0': return "\x0"; case 'a': return "\x7"; case 'b': return "\x8"; case 't': return "\t"; case "\t": return "\t"; case 'n': return "\n"; case 'v': return "\xb"; case 'f': return "\xc"; case 'r': return "\xd"; case 'e': return "\x1b"; case ' ': return ' '; case '"': return '"'; case '/': return '/'; case '\\': return '\\'; case 'N': // U+0085 NEXT LINE return $this->convertEncoding("\x00\x85", self::ENCODING, 'UCS-2BE'); case '_': // U+00A0 NO-BREAK SPACE return $this->convertEncoding("\x00\xA0", self::ENCODING, 'UCS-2BE'); case 'L': // U+2028 LINE SEPARATOR return $this->convertEncoding("\x20\x28", self::ENCODING, 'UCS-2BE'); case 'P': // U+2029 PARAGRAPH SEPARATOR return $this->convertEncoding("\x20\x29", self::ENCODING, 'UCS-2BE'); case 'x': $char = pack('n', hexdec(substr($value, 2, 2))); return $this->convertEncoding($char, self::ENCODING, 'UCS-2BE'); case 'u': $char = pack('n', hexdec(substr($value, 2, 4))); return $this->convertEncoding($char, self::ENCODING, 'UCS-2BE'); case 'U': $char = pack('N', hexdec(substr($value, 2, 8))); return $this->convertEncoding($char, self::ENCODING, 'UCS-4BE'); } } /** * Convert a string from one encoding to another. * * @param string $value The string to convert * @param string $to The input encoding * @param string $from The output encoding * * @return string The string with the new encoding * * @throws \RuntimeException if no suitable encoding function is found (iconv or mbstring) */ private function convertEncoding($value, $to, $from) { if (function_exists('mb_convert_encoding')) { return mb_convert_encoding($value, $to, $from); } elseif (function_exists('iconv')) { return iconv($from, $to, $value); } throw new \RuntimeException('No suitable convert encoding function (install the iconv or mbstring extension).'); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Yaml\Exception; if (!defined('JSON_UNESCAPED_UNICODE')) { define('JSON_UNESCAPED_SLASHES', 64); define('JSON_UNESCAPED_UNICODE', 256); } /** * Exception class thrown when an error occurs during parsing. * * @author Fabien Potencier * * @api */ class ParseException extends RuntimeException { private $parsedFile; private $parsedLine; private $snippet; private $rawMessage; /** * Constructor. * * @param string $message The error message * @param int $parsedLine The line where the error occurred * @param int $snippet The snippet of code near the problem * @param string $parsedFile The file name where the error occurred * @param \Exception $previous The previous exception */ public function __construct($message, $parsedLine = -1, $snippet = null, $parsedFile = null, \Exception $previous = null) { $this->parsedFile = $parsedFile; $this->parsedLine = $parsedLine; $this->snippet = $snippet; $this->rawMessage = $message; $this->updateRepr(); parent::__construct($this->message, 0, $previous); } /** * Gets the snippet of code near the error. * * @return string The snippet of code */ public function getSnippet() { return $this->snippet; } /** * Sets the snippet of code near the error. * * @param string $snippet The code snippet */ public function setSnippet($snippet) { $this->snippet = $snippet; $this->updateRepr(); } /** * Gets the filename where the error occurred. * * This method returns null if a string is parsed. * * @return string The filename */ public function getParsedFile() { return $this->parsedFile; } /** * Sets the filename where the error occurred. * * @param string $parsedFile The filename */ public function setParsedFile($parsedFile) { $this->parsedFile = $parsedFile; $this->updateRepr(); } /** * Gets the line where the error occurred. * * @return int The file line */ public function getParsedLine() { return $this->parsedLine; } /** * Sets the line where the error occurred. * * @param int $parsedLine The file line */ public function setParsedLine($parsedLine) { $this->parsedLine = $parsedLine; $this->updateRepr(); } private function updateRepr() { $this->message = $this->rawMessage; $dot = false; if ('.' === substr($this->message, -1)) { $this->message = substr($this->message, 0, -1); $dot = true; } if (null !== $this->parsedFile) { $this->message .= sprintf(' in %s', json_encode($this->parsedFile, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); } if ($this->parsedLine >= 0) { $this->message .= sprintf(' at line %d', $this->parsedLine); } if ($this->snippet) { $this->message .= sprintf(' (near "%s")', $this->snippet); } if ($dot) { $this->message .= '.'; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Yaml\Exception; /** * Exception class thrown when an error occurs during parsing. * * @author Romain Neutron * * @api */ class RuntimeException extends \RuntimeException implements ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Yaml\Exception; /** * Exception class thrown when an error occurs during dumping. * * @author Fabien Potencier * * @api */ class DumpException extends RuntimeException { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Yaml\Exception; /** * Exception interface for all exceptions thrown by the component. * * @author Fabien Potencier * * @api */ interface ExceptionInterface { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; /** * Helper is the base class for all helper classes. * * @author Fabien Potencier */ abstract class Helper implements HelperInterface { protected $helperSet = null; /** * Sets the helper set associated with this helper. * * @param HelperSet $helperSet A HelperSet instance */ public function setHelperSet(HelperSet $helperSet = null) { $this->helperSet = $helperSet; } /** * Gets the helper set associated with this helper. * * @return HelperSet A HelperSet instance */ public function getHelperSet() { return $this->helperSet; } /** * Returns the length of a string, using mb_strlen if it is available. * * @param string $string The string to check its length * * @return int The length of the string */ protected function strlen($string) { if (!function_exists('mb_strlen')) { return strlen($string); } if (false === $encoding = mb_detect_encoding($string)) { return strlen($string); } return mb_strlen($string, $encoding); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputAwareInterface; /** * An implementation of InputAwareInterface for Helpers. * * @author Wouter J */ abstract class InputAwareHelper extends Helper implements InputAwareInterface { protected $input; /** * {@inheritdoc} */ public function setInput(InputInterface $input) { $this->input = $input; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Formatter\OutputFormatter; /** * The Formatter class provides helpers to format messages. * * @author Fabien Potencier */ class FormatterHelper extends Helper { /** * Formats a message within a section. * * @param string $section The section name * @param string $message The message * @param string $style The style to apply to the section * * @return string The format section */ public function formatSection($section, $message, $style = 'info') { return sprintf('<%s>[%s] %s', $style, $section, $style, $message); } /** * Formats a message as a block of text. * * @param string|array $messages The message to write in the block * @param string $style The style to apply to the whole block * @param bool $large Whether to return a large block * * @return string The formatter message */ public function formatBlock($messages, $style, $large = false) { $messages = (array) $messages; $len = 0; $lines = array(); foreach ($messages as $message) { $message = OutputFormatter::escape($message); $lines[] = sprintf($large ? ' %s ' : ' %s ', $message); $len = max($this->strlen($message) + ($large ? 4 : 2), $len); } $messages = $large ? array(str_repeat(' ', $len)) : array(); foreach ($lines as $line) { $messages[] = $line.str_repeat(' ', $len - $this->strlen($line)); } if ($large) { $messages[] = str_repeat(' ', $len); } foreach ($messages as &$message) { $message = sprintf('<%s>%s', $style, $message, $style); } return implode("\n", $messages); } /** * {@inheritdoc} */ public function getName() { return 'formatter'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Formatter\OutputFormatterStyle; /** * The Dialog class provides helpers to interact with the user. * * @author Fabien Potencier */ class DialogHelper extends InputAwareHelper { private $inputStream; private static $shell; private static $stty; /** * Asks the user to select a value. * * @param OutputInterface $output An Output instance * @param string|array $question The question to ask * @param array $choices List of choices to pick from * @param bool|string $default The default answer if the user enters nothing * @param bool|int $attempts Max number of times to ask before giving up (false by default, which means infinite) * @param string $errorMessage Message which will be shown if invalid value from choice list would be picked * @param bool $multiselect Select more than one value separated by comma * * @return int|string|array The selected value or values (the key of the choices array) * * @throws \InvalidArgumentException */ public function select(OutputInterface $output, $question, $choices, $default = null, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false) { $width = max(array_map('strlen', array_keys($choices))); $messages = (array) $question; foreach ($choices as $key => $value) { $messages[] = sprintf(" [%-${width}s] %s", $key, $value); } $output->writeln($messages); $result = $this->askAndValidate($output, '> ', function ($picked) use ($choices, $errorMessage, $multiselect) { // Collapse all spaces. $selectedChoices = str_replace(" ", "", $picked); if ($multiselect) { // Check for a separated comma values if (!preg_match('/^[a-zA-Z0-9_-]+(?:,[a-zA-Z0-9_-]+)*$/', $selectedChoices, $matches)) { throw new \InvalidArgumentException(sprintf($errorMessage, $picked)); } $selectedChoices = explode(",", $selectedChoices); } else { $selectedChoices = array($picked); } $multiselectChoices = array(); foreach ($selectedChoices as $value) { if (empty($choices[$value])) { throw new \InvalidArgumentException(sprintf($errorMessage, $value)); } array_push($multiselectChoices, $value); } if ($multiselect) { return $multiselectChoices; } return $picked; }, $attempts, $default); return $result; } /** * Asks a question to the user. * * @param OutputInterface $output An Output instance * @param string|array $question The question to ask * @param string $default The default answer if none is given by the user * @param array $autocomplete List of values to autocomplete * * @return string The user answer * * @throws \RuntimeException If there is no data to read in the input stream */ public function ask(OutputInterface $output, $question, $default = null, array $autocomplete = null) { if ($this->input && !$this->input->isInteractive()) { return $default; } $output->write($question); $inputStream = $this->inputStream ?: STDIN; if (null === $autocomplete || !$this->hasSttyAvailable()) { $ret = fgets($inputStream, 4096); if (false === $ret) { throw new \RuntimeException('Aborted'); } $ret = trim($ret); } else { $ret = ''; $i = 0; $ofs = -1; $matches = $autocomplete; $numMatches = count($matches); $sttyMode = shell_exec('stty -g'); // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead) shell_exec('stty -icanon -echo'); // Add highlighted text style $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white')); // Read a keypress while (!feof($inputStream)) { $c = fread($inputStream, 1); // Backspace Character if ("\177" === $c) { if (0 === $numMatches && 0 !== $i) { $i--; // Move cursor backwards $output->write("\033[1D"); } if ($i === 0) { $ofs = -1; $matches = $autocomplete; $numMatches = count($matches); } else { $numMatches = 0; } // Pop the last character off the end of our string $ret = substr($ret, 0, $i); } elseif ("\033" === $c) { // Did we read an escape sequence? $c .= fread($inputStream, 2); // A = Up Arrow. B = Down Arrow if ('A' === $c[2] || 'B' === $c[2]) { if ('A' === $c[2] && -1 === $ofs) { $ofs = 0; } if (0 === $numMatches) { continue; } $ofs += ('A' === $c[2]) ? -1 : 1; $ofs = ($numMatches + $ofs) % $numMatches; } } elseif (ord($c) < 32) { if ("\t" === $c || "\n" === $c) { if ($numMatches > 0 && -1 !== $ofs) { $ret = $matches[$ofs]; // Echo out remaining chars for current match $output->write(substr($ret, $i)); $i = strlen($ret); } if ("\n" === $c) { $output->write($c); break; } $numMatches = 0; } continue; } else { $output->write($c); $ret .= $c; $i++; $numMatches = 0; $ofs = 0; foreach ($autocomplete as $value) { // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle) if (0 === strpos($value, $ret) && $i !== strlen($value)) { $matches[$numMatches++] = $value; } } } // Erase characters from cursor to end of line $output->write("\033[K"); if ($numMatches > 0 && -1 !== $ofs) { // Save cursor position $output->write("\0337"); // Write highlighted text $output->write(''.substr($matches[$ofs], $i).''); // Restore cursor position $output->write("\0338"); } } // Reset stty so it behaves normally again shell_exec(sprintf('stty %s', $sttyMode)); } return strlen($ret) > 0 ? $ret : $default; } /** * Asks a confirmation to the user. * * The question will be asked until the user answers by nothing, yes, or no. * * @param OutputInterface $output An Output instance * @param string|array $question The question to ask * @param bool $default The default answer if the user enters nothing * * @return bool true if the user has confirmed, false otherwise */ public function askConfirmation(OutputInterface $output, $question, $default = true) { $answer = 'z'; while ($answer && !in_array(strtolower($answer[0]), array('y', 'n'))) { $answer = $this->ask($output, $question); } if (false === $default) { return $answer && 'y' == strtolower($answer[0]); } return !$answer || 'y' == strtolower($answer[0]); } /** * Asks a question to the user, the response is hidden * * @param OutputInterface $output An Output instance * @param string|array $question The question * @param bool $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not * * @return string The answer * * @throws \RuntimeException In case the fallback is deactivated and the response can not be hidden */ public function askHiddenResponse(OutputInterface $output, $question, $fallback = true) { if (defined('PHP_WINDOWS_VERSION_BUILD')) { $exe = __DIR__.'/../Resources/bin/hiddeninput.exe'; // handle code running from a phar if ('phar:' === substr(__FILE__, 0, 5)) { $tmpExe = sys_get_temp_dir().'/hiddeninput.exe'; copy($exe, $tmpExe); $exe = $tmpExe; } $output->write($question); $value = rtrim(shell_exec($exe)); $output->writeln(''); if (isset($tmpExe)) { unlink($tmpExe); } return $value; } if ($this->hasSttyAvailable()) { $output->write($question); $sttyMode = shell_exec('stty -g'); shell_exec('stty -echo'); $value = fgets($this->inputStream ?: STDIN, 4096); shell_exec(sprintf('stty %s', $sttyMode)); if (false === $value) { throw new \RuntimeException('Aborted'); } $value = trim($value); $output->writeln(''); return $value; } if (false !== $shell = $this->getShell()) { $output->write($question); $readCmd = $shell === 'csh' ? 'set mypassword = $<' : 'read -r mypassword'; $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd); $value = rtrim(shell_exec($command)); $output->writeln(''); return $value; } if ($fallback) { return $this->ask($output, $question); } throw new \RuntimeException('Unable to hide the response'); } /** * Asks for a value and validates the response. * * The validator receives the data to validate. It must return the * validated data when the data is valid and throw an exception * otherwise. * * @param OutputInterface $output An Output instance * @param string|array $question The question to ask * @param callable $validator A PHP callback * @param int $attempts Max number of times to ask before giving up (false by default, which means infinite) * @param string $default The default answer if none is given by the user * @param array $autocomplete List of values to autocomplete * * @return mixed * * @throws \Exception When any of the validators return an error */ public function askAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $default = null, array $autocomplete = null) { $that = $this; $interviewer = function () use ($output, $question, $default, $autocomplete, $that) { return $that->ask($output, $question, $default, $autocomplete); }; return $this->validateAttempts($interviewer, $output, $validator, $attempts); } /** * Asks for a value, hide and validates the response. * * The validator receives the data to validate. It must return the * validated data when the data is valid and throw an exception * otherwise. * * @param OutputInterface $output An Output instance * @param string|array $question The question to ask * @param callable $validator A PHP callback * @param int $attempts Max number of times to ask before giving up (false by default, which means infinite) * @param bool $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not * * @return string The response * * @throws \Exception When any of the validators return an error * @throws \RuntimeException In case the fallback is deactivated and the response can not be hidden * */ public function askHiddenResponseAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $fallback = true) { $that = $this; $interviewer = function () use ($output, $question, $fallback, $that) { return $that->askHiddenResponse($output, $question, $fallback); }; return $this->validateAttempts($interviewer, $output, $validator, $attempts); } /** * Sets the input stream to read from when interacting with the user. * * This is mainly useful for testing purpose. * * @param resource $stream The input stream */ public function setInputStream($stream) { $this->inputStream = $stream; } /** * Returns the helper's input stream * * @return string */ public function getInputStream() { return $this->inputStream; } /** * {@inheritdoc} */ public function getName() { return 'dialog'; } /** * Return a valid Unix shell * * @return string|bool The valid shell name, false in case no valid shell is found */ private function getShell() { if (null !== self::$shell) { return self::$shell; } self::$shell = false; if (file_exists('/usr/bin/env')) { // handle other OSs with bash/zsh/ksh/csh if available to hide the answer $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null"; foreach (array('bash', 'zsh', 'ksh', 'csh') as $sh) { if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) { self::$shell = $sh; break; } } } return self::$shell; } private function hasSttyAvailable() { if (null !== self::$stty) { return self::$stty; } exec('stty 2>&1', $output, $exitcode); return self::$stty = $exitcode === 0; } /** * Validate an attempt * * @param callable $interviewer A callable that will ask for a question and return the result * @param OutputInterface $output An Output instance * @param callable $validator A PHP callback * @param int $attempts Max number of times to ask before giving up ; false will ask infinitely * * @return string The validated response * * @throws \Exception In case the max number of attempts has been reached and no valid response has been given */ private function validateAttempts($interviewer, OutputInterface $output, $validator, $attempts) { $error = null; while (false === $attempts || $attempts--) { if (null !== $error) { $output->writeln($this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error')); } try { return call_user_func($validator, $interviewer()); } catch (\Exception $error) { } } throw $error; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; /** * HelperInterface is the interface all helpers must implement. * * @author Fabien Potencier * * @api */ interface HelperInterface { /** * Sets the helper set associated with this helper. * * @param HelperSet $helperSet A HelperSet instance * * @api */ public function setHelperSet(HelperSet $helperSet = null); /** * Gets the helper set associated with this helper. * * @return HelperSet A HelperSet instance * * @api */ public function getHelperSet(); /** * Returns the canonical name of this helper. * * @return string The canonical name * * @api */ public function getName(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Descriptor\DescriptorInterface; use Symfony\Component\Console\Descriptor\JsonDescriptor; use Symfony\Component\Console\Descriptor\MarkdownDescriptor; use Symfony\Component\Console\Descriptor\TextDescriptor; use Symfony\Component\Console\Descriptor\XmlDescriptor; use Symfony\Component\Console\Output\OutputInterface; /** * This class adds helper method to describe objects in various formats. * * @author Jean-François Simon */ class DescriptorHelper extends Helper { /** * @var DescriptorInterface[] */ private $descriptors = array(); /** * Constructor. */ public function __construct() { $this ->register('txt', new TextDescriptor()) ->register('xml', new XmlDescriptor()) ->register('json', new JsonDescriptor()) ->register('md', new MarkdownDescriptor()) ; } /** * Describes an object if supported. * * Available options are: * * format: string, the output format name * * raw_text: boolean, sets output type as raw * * @param OutputInterface $output * @param object $object * @param array $options * * @throws \InvalidArgumentException */ public function describe(OutputInterface $output, $object, array $options = array()) { $options = array_merge(array( 'raw_text' => false, 'format' => 'txt', ), $options); if (!isset($this->descriptors[$options['format']])) { throw new \InvalidArgumentException(sprintf('Unsupported format "%s".', $options['format'])); } $descriptor = $this->descriptors[$options['format']]; $descriptor->describe($output, $object, $options); } /** * Registers a descriptor. * * @param string $format * @param DescriptorInterface $descriptor * * @return DescriptorHelper */ public function register($format, DescriptorInterface $descriptor) { $this->descriptors[$format] = $descriptor; return $this; } /** * {@inheritdoc} */ public function getName() { return 'descriptor'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Command\Command; /** * HelperSet represents a set of helpers to be used with a command. * * @author Fabien Potencier */ class HelperSet implements \IteratorAggregate { private $helpers = array(); private $command; /** * Constructor. * * @param Helper[] $helpers An array of helper. */ public function __construct(array $helpers = array()) { foreach ($helpers as $alias => $helper) { $this->set($helper, is_int($alias) ? null : $alias); } } /** * Sets a helper. * * @param HelperInterface $helper The helper instance * @param string $alias An alias */ public function set(HelperInterface $helper, $alias = null) { $this->helpers[$helper->getName()] = $helper; if (null !== $alias) { $this->helpers[$alias] = $helper; } $helper->setHelperSet($this); } /** * Returns true if the helper if defined. * * @param string $name The helper name * * @return bool true if the helper is defined, false otherwise */ public function has($name) { return isset($this->helpers[$name]); } /** * Gets a helper value. * * @param string $name The helper name * * @return HelperInterface The helper instance * * @throws \InvalidArgumentException if the helper is not defined */ public function get($name) { if (!$this->has($name)) { throw new \InvalidArgumentException(sprintf('The helper "%s" is not defined.', $name)); } return $this->helpers[$name]; } /** * Sets the command associated with this helper set. * * @param Command $command A Command instance */ public function setCommand(Command $command = null) { $this->command = $command; } /** * Gets the command associated with this helper set. * * @return Command A Command instance */ public function getCommand() { return $this->command; } public function getIterator() { return new \ArrayIterator($this->helpers); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Output\OutputInterface; /** * The Progress class provides helpers to display progress output. * * @author Chris Jones * @author Fabien Potencier */ class ProgressHelper extends Helper { const FORMAT_QUIET = ' %percent%%'; const FORMAT_NORMAL = ' %current%/%max% [%bar%] %percent%%'; const FORMAT_VERBOSE = ' %current%/%max% [%bar%] %percent%% Elapsed: %elapsed%'; const FORMAT_QUIET_NOMAX = ' %current%'; const FORMAT_NORMAL_NOMAX = ' %current% [%bar%]'; const FORMAT_VERBOSE_NOMAX = ' %current% [%bar%] Elapsed: %elapsed%'; // options private $barWidth = 28; private $barChar = '='; private $emptyBarChar = '-'; private $progressChar = '>'; private $format = null; private $redrawFreq = 1; private $lastMessagesLength; private $barCharOriginal; /** * @var OutputInterface */ private $output; /** * Current step * * @var int */ private $current; /** * Maximum number of steps * * @var int */ private $max; /** * Start time of the progress bar * * @var int */ private $startTime; /** * List of formatting variables * * @var array */ private $defaultFormatVars = array( 'current', 'max', 'bar', 'percent', 'elapsed', ); /** * Available formatting variables * * @var array */ private $formatVars; /** * Stored format part widths (used for padding) * * @var array */ private $widths = array( 'current' => 4, 'max' => 4, 'percent' => 3, 'elapsed' => 6, ); /** * Various time formats * * @var array */ private $timeFormats = array( array(0, '???'), array(2, '1 sec'), array(59, 'secs', 1), array(60, '1 min'), array(3600, 'mins', 60), array(5400, '1 hr'), array(86400, 'hrs', 3600), array(129600, '1 day'), array(604800, 'days', 86400), ); /** * Sets the progress bar width. * * @param int $size The progress bar size */ public function setBarWidth($size) { $this->barWidth = (int) $size; } /** * Sets the bar character. * * @param string $char A character */ public function setBarCharacter($char) { $this->barChar = $char; } /** * Sets the empty bar character. * * @param string $char A character */ public function setEmptyBarCharacter($char) { $this->emptyBarChar = $char; } /** * Sets the progress bar character. * * @param string $char A character */ public function setProgressCharacter($char) { $this->progressChar = $char; } /** * Sets the progress bar format. * * @param string $format The format */ public function setFormat($format) { $this->format = $format; } /** * Sets the redraw frequency. * * @param int $freq The frequency in steps */ public function setRedrawFrequency($freq) { $this->redrawFreq = (int) $freq; } /** * Starts the progress output. * * @param OutputInterface $output An Output instance * @param int|null $max Maximum steps */ public function start(OutputInterface $output, $max = null) { $this->startTime = time(); $this->current = 0; $this->max = (int) $max; $this->output = $output; $this->lastMessagesLength = 0; $this->barCharOriginal = ''; if (null === $this->format) { switch ($output->getVerbosity()) { case OutputInterface::VERBOSITY_QUIET: $this->format = self::FORMAT_QUIET_NOMAX; if ($this->max > 0) { $this->format = self::FORMAT_QUIET; } break; case OutputInterface::VERBOSITY_VERBOSE: case OutputInterface::VERBOSITY_VERY_VERBOSE: case OutputInterface::VERBOSITY_DEBUG: $this->format = self::FORMAT_VERBOSE_NOMAX; if ($this->max > 0) { $this->format = self::FORMAT_VERBOSE; } break; default: $this->format = self::FORMAT_NORMAL_NOMAX; if ($this->max > 0) { $this->format = self::FORMAT_NORMAL; } break; } } $this->initialize(); } /** * Advances the progress output X steps. * * @param int $step Number of steps to advance * @param bool $redraw Whether to redraw or not * * @throws \LogicException */ public function advance($step = 1, $redraw = false) { $this->setCurrent($this->current + $step, $redraw); } /** * Sets the current progress. * * @param int $current The current progress * @param bool $redraw Whether to redraw or not * * @throws \LogicException */ public function setCurrent($current, $redraw = false) { if (null === $this->startTime) { throw new \LogicException('You must start the progress bar before calling setCurrent().'); } $current = (int) $current; if ($current < $this->current) { throw new \LogicException('You can\'t regress the progress bar'); } if (0 === $this->current) { $redraw = true; } $prevPeriod = intval($this->current / $this->redrawFreq); $this->current = $current; $currPeriod = intval($this->current / $this->redrawFreq); if ($redraw || $prevPeriod !== $currPeriod || $this->max === $this->current) { $this->display(); } } /** * Outputs the current progress string. * * @param bool $finish Forces the end result * * @throws \LogicException */ public function display($finish = false) { if (null === $this->startTime) { throw new \LogicException('You must start the progress bar before calling display().'); } $message = $this->format; foreach ($this->generate($finish) as $name => $value) { $message = str_replace("%{$name}%", $value, $message); } $this->overwrite($this->output, $message); } /** * Removes the progress bar from the current line. * * This is useful if you wish to write some output * while a progress bar is running. * Call display() to show the progress bar again. */ public function clear() { $this->overwrite($this->output, ''); } /** * Finishes the progress output. */ public function finish() { if (null === $this->startTime) { throw new \LogicException('You must start the progress bar before calling finish().'); } if (null !== $this->startTime) { if (!$this->max) { $this->barChar = $this->barCharOriginal; $this->display(true); } $this->startTime = null; $this->output->writeln(''); $this->output = null; } } /** * Initializes the progress helper. */ private function initialize() { $this->formatVars = array(); foreach ($this->defaultFormatVars as $var) { if (false !== strpos($this->format, "%{$var}%")) { $this->formatVars[$var] = true; } } if ($this->max > 0) { $this->widths['max'] = $this->strlen($this->max); $this->widths['current'] = $this->widths['max']; } else { $this->barCharOriginal = $this->barChar; $this->barChar = $this->emptyBarChar; } } /** * Generates the array map of format variables to values. * * @param bool $finish Forces the end result * * @return array Array of format vars and values */ private function generate($finish = false) { $vars = array(); $percent = 0; if ($this->max > 0) { $percent = (float) $this->current / $this->max; } if (isset($this->formatVars['bar'])) { $completeBars = 0; if ($this->max > 0) { $completeBars = floor($percent * $this->barWidth); } else { if (!$finish) { $completeBars = floor($this->current % $this->barWidth); } else { $completeBars = $this->barWidth; } } $emptyBars = $this->barWidth - $completeBars - $this->strlen($this->progressChar); $bar = str_repeat($this->barChar, $completeBars); if ($completeBars < $this->barWidth) { $bar .= $this->progressChar; $bar .= str_repeat($this->emptyBarChar, $emptyBars); } $vars['bar'] = $bar; } if (isset($this->formatVars['elapsed'])) { $elapsed = time() - $this->startTime; $vars['elapsed'] = str_pad($this->humaneTime($elapsed), $this->widths['elapsed'], ' ', STR_PAD_LEFT); } if (isset($this->formatVars['current'])) { $vars['current'] = str_pad($this->current, $this->widths['current'], ' ', STR_PAD_LEFT); } if (isset($this->formatVars['max'])) { $vars['max'] = $this->max; } if (isset($this->formatVars['percent'])) { $vars['percent'] = str_pad(floor($percent * 100), $this->widths['percent'], ' ', STR_PAD_LEFT); } return $vars; } /** * Converts seconds into human-readable format. * * @param int $secs Number of seconds * * @return string Time in readable format */ private function humaneTime($secs) { $text = ''; foreach ($this->timeFormats as $format) { if ($secs < $format[0]) { if (count($format) == 2) { $text = $format[1]; break; } else { $text = ceil($secs / $format[2]).' '.$format[1]; break; } } } return $text; } /** * Overwrites a previous message to the output. * * @param OutputInterface $output An Output instance * @param string $message The message */ private function overwrite(OutputInterface $output, $message) { $length = $this->strlen($message); // append whitespace to match the last line's length if (null !== $this->lastMessagesLength && $this->lastMessagesLength > $length) { $message = str_pad($message, $this->lastMessagesLength, "\x20", STR_PAD_RIGHT); } // carriage return $output->write("\x0D"); $output->write($message); $this->lastMessagesLength = $this->strlen($message); } /** * {@inheritdoc} */ public function getName() { return 'progress'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Helper; use Symfony\Component\Console\Output\OutputInterface; use InvalidArgumentException; /** * Provides helpers to display table output. * * @author Саша Стаменковић */ class TableHelper extends Helper { const LAYOUT_DEFAULT = 0; const LAYOUT_BORDERLESS = 1; const LAYOUT_COMPACT = 2; /** * Table headers. * * @var array */ private $headers = array(); /** * Table rows. * * @var array */ private $rows = array(); // Rendering options private $paddingChar; private $horizontalBorderChar; private $verticalBorderChar; private $crossingChar; private $cellHeaderFormat; private $cellRowFormat; private $cellRowContentFormat; private $borderFormat; private $padType; /** * Column widths cache. * * @var array */ private $columnWidths = array(); /** * Number of columns cache. * * @var array */ private $numberOfColumns; /** * @var OutputInterface */ private $output; public function __construct() { $this->setLayout(self::LAYOUT_DEFAULT); } /** * Sets table layout type. * * @param int $layout self::LAYOUT_* * * @return TableHelper */ public function setLayout($layout) { switch ($layout) { case self::LAYOUT_BORDERLESS: $this ->setPaddingChar(' ') ->setHorizontalBorderChar('=') ->setVerticalBorderChar(' ') ->setCrossingChar(' ') ->setCellHeaderFormat('%s') ->setCellRowFormat('%s') ->setCellRowContentFormat(' %s ') ->setBorderFormat('%s') ->setPadType(STR_PAD_RIGHT) ; break; case self::LAYOUT_COMPACT: $this ->setPaddingChar(' ') ->setHorizontalBorderChar('') ->setVerticalBorderChar(' ') ->setCrossingChar('') ->setCellHeaderFormat('%s') ->setCellRowFormat('%s') ->setCellRowContentFormat('%s') ->setBorderFormat('%s') ->setPadType(STR_PAD_RIGHT) ; break; case self::LAYOUT_DEFAULT: $this ->setPaddingChar(' ') ->setHorizontalBorderChar('-') ->setVerticalBorderChar('|') ->setCrossingChar('+') ->setCellHeaderFormat('%s') ->setCellRowFormat('%s') ->setCellRowContentFormat(' %s ') ->setBorderFormat('%s') ->setPadType(STR_PAD_RIGHT) ; break; default: throw new InvalidArgumentException(sprintf('Invalid table layout "%s".', $layout)); break; }; return $this; } public function setHeaders(array $headers) { $this->headers = array_values($headers); return $this; } public function setRows(array $rows) { $this->rows = array(); return $this->addRows($rows); } public function addRows(array $rows) { foreach ($rows as $row) { $this->addRow($row); } return $this; } public function addRow(array $row) { $this->rows[] = array_values($row); $keys = array_keys($this->rows); $rowKey = array_pop($keys); foreach ($row as $key => $cellValue) { if (!strstr($cellValue, "\n")) { continue; } $lines = explode("\n", $cellValue); $this->rows[$rowKey][$key] = $lines[0]; unset($lines[0]); foreach ($lines as $lineKey => $line) { $nextRowKey = $rowKey + $lineKey + 1; if (isset($this->rows[$nextRowKey])) { $this->rows[$nextRowKey][$key] = $line; } else { $this->rows[$nextRowKey] = array($key => $line); } } } return $this; } public function setRow($column, array $row) { $this->rows[$column] = $row; return $this; } /** * Sets padding character, used for cell padding. * * @param string $paddingChar * * @return TableHelper */ public function setPaddingChar($paddingChar) { if (!$paddingChar) { throw new \LogicException('The padding char must not be empty'); } $this->paddingChar = $paddingChar; return $this; } /** * Sets horizontal border character. * * @param string $horizontalBorderChar * * @return TableHelper */ public function setHorizontalBorderChar($horizontalBorderChar) { $this->horizontalBorderChar = $horizontalBorderChar; return $this; } /** * Sets vertical border character. * * @param string $verticalBorderChar * * @return TableHelper */ public function setVerticalBorderChar($verticalBorderChar) { $this->verticalBorderChar = $verticalBorderChar; return $this; } /** * Sets crossing character. * * @param string $crossingChar * * @return TableHelper */ public function setCrossingChar($crossingChar) { $this->crossingChar = $crossingChar; return $this; } /** * Sets header cell format. * * @param string $cellHeaderFormat * * @return TableHelper */ public function setCellHeaderFormat($cellHeaderFormat) { $this->cellHeaderFormat = $cellHeaderFormat; return $this; } /** * Sets row cell format. * * @param string $cellRowFormat * * @return TableHelper */ public function setCellRowFormat($cellRowFormat) { $this->cellRowFormat = $cellRowFormat; return $this; } /** * Sets row cell content format. * * @param string $cellRowContentFormat * * @return TableHelper */ public function setCellRowContentFormat($cellRowContentFormat) { $this->cellRowContentFormat = $cellRowContentFormat; return $this; } /** * Sets table border format. * * @param string $borderFormat * * @return TableHelper */ public function setBorderFormat($borderFormat) { $this->borderFormat = $borderFormat; return $this; } /** * Sets cell padding type. * * @param int $padType STR_PAD_* * * @return TableHelper */ public function setPadType($padType) { $this->padType = $padType; return $this; } /** * Renders table to output. * * Example: * +---------------+-----------------------+------------------+ * | ISBN | Title | Author | * +---------------+-----------------------+------------------+ * | 99921-58-10-7 | Divine Comedy | Dante Alighieri | * | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | * | 960-425-059-0 | The Lord of the Rings | J. R. R. Tolkien | * +---------------+-----------------------+------------------+ * * @param OutputInterface $output */ public function render(OutputInterface $output) { $this->output = $output; $this->renderRowSeparator(); $this->renderRow($this->headers, $this->cellHeaderFormat); if (!empty($this->headers)) { $this->renderRowSeparator(); } foreach ($this->rows as $row) { $this->renderRow($row, $this->cellRowFormat); } if (!empty($this->rows)) { $this->renderRowSeparator(); } $this->cleanup(); } /** * Renders horizontal header separator. * * Example: +-----+-----------+-------+ */ private function renderRowSeparator() { if (0 === $count = $this->getNumberOfColumns()) { return; } if (!$this->horizontalBorderChar && !$this->crossingChar) { return; } $markup = $this->crossingChar; for ($column = 0; $column < $count; $column++) { $markup .= str_repeat($this->horizontalBorderChar, $this->getColumnWidth($column)).$this->crossingChar; } $this->output->writeln(sprintf($this->borderFormat, $markup)); } /** * Renders vertical column separator. */ private function renderColumnSeparator() { $this->output->write(sprintf($this->borderFormat, $this->verticalBorderChar)); } /** * Renders table row. * * Example: | 9971-5-0210-0 | A Tale of Two Cities | Charles Dickens | * * @param array $row * @param string $cellFormat */ private function renderRow(array $row, $cellFormat) { if (empty($row)) { return; } $this->renderColumnSeparator(); for ($column = 0, $count = $this->getNumberOfColumns(); $column < $count; $column++) { $this->renderCell($row, $column, $cellFormat); $this->renderColumnSeparator(); } $this->output->writeln(''); } /** * Renders table cell with padding. * * @param array $row * @param int $column * @param string $cellFormat */ private function renderCell(array $row, $column, $cellFormat) { $cell = isset($row[$column]) ? $row[$column] : ''; $width = $this->getColumnWidth($column); // str_pad won't work properly with multi-byte strings, we need to fix the padding if (function_exists('mb_strlen') && false !== $encoding = mb_detect_encoding($cell)) { $width += strlen($cell) - mb_strlen($cell, $encoding); } $width += $this->strlen($cell) - $this->computeLengthWithoutDecoration($cell); $content = sprintf($this->cellRowContentFormat, $cell); $this->output->write(sprintf($cellFormat, str_pad($content, $width, $this->paddingChar, $this->padType))); } /** * Gets number of columns for this table. * * @return int */ private function getNumberOfColumns() { if (null !== $this->numberOfColumns) { return $this->numberOfColumns; } $columns = array(0); $columns[] = count($this->headers); foreach ($this->rows as $row) { $columns[] = count($row); } return $this->numberOfColumns = max($columns); } /** * Gets column width. * * @param int $column * * @return int */ private function getColumnWidth($column) { if (isset($this->columnWidths[$column])) { return $this->columnWidths[$column]; } $lengths = array(0); $lengths[] = $this->getCellWidth($this->headers, $column); foreach ($this->rows as $row) { $lengths[] = $this->getCellWidth($row, $column); } return $this->columnWidths[$column] = max($lengths) + strlen($this->cellRowContentFormat) - 2; } /** * Gets cell width. * * @param array $row * @param int $column * * @return int */ private function getCellWidth(array $row, $column) { return isset($row[$column]) ? $this->computeLengthWithoutDecoration($row[$column]) : 0; } /** * Called after rendering to cleanup cache data. */ private function cleanup() { $this->columnWidths = array(); $this->numberOfColumns = null; } private function computeLengthWithoutDecoration($string) { $formatter = $this->output->getFormatter(); $isDecorated = $formatter->isDecorated(); $formatter->setDecorated(false); $string = $formatter->format($string); $formatter->setDecorated($isDecorated); return $this->strlen($string); } /** * {@inheritdoc} */ public function getName() { return 'table'; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console; use Symfony\Component\Console\Descriptor\TextDescriptor; use Symfony\Component\Console\Descriptor\XmlDescriptor; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputAwareInterface; use Symfony\Component\Console\Output\BufferedOutput; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\HelpCommand; use Symfony\Component\Console\Command\ListCommand; use Symfony\Component\Console\Helper\HelperSet; use Symfony\Component\Console\Helper\FormatterHelper; use Symfony\Component\Console\Helper\DialogHelper; use Symfony\Component\Console\Helper\ProgressHelper; use Symfony\Component\Console\Helper\TableHelper; use Symfony\Component\Console\Event\ConsoleCommandEvent; use Symfony\Component\Console\Event\ConsoleExceptionEvent; use Symfony\Component\Console\Event\ConsoleTerminateEvent; use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** * An Application is the container for a collection of commands. * * It is the main entry point of a Console application. * * This class is optimized for a standard CLI environment. * * Usage: * * $app = new Application('myapp', '1.0 (stable)'); * $app->add(new SimpleCommand()); * $app->run(); * * @author Fabien Potencier * * @api */ class Application { private $commands = array(); private $wantHelps = false; private $runningCommand; private $name; private $version; private $catchExceptions = true; private $autoExit = true; private $definition; private $helperSet; private $dispatcher; private $terminalDimensions; /** * Constructor. * * @param string $name The name of the application * @param string $version The version of the application * * @api */ public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN') { $this->name = $name; $this->version = $version; $this->helperSet = $this->getDefaultHelperSet(); $this->definition = $this->getDefaultInputDefinition(); foreach ($this->getDefaultCommands() as $command) { $this->add($command); } } public function setDispatcher(EventDispatcherInterface $dispatcher) { $this->dispatcher = $dispatcher; } /** * Runs the current application. * * @param InputInterface $input An Input instance * @param OutputInterface $output An Output instance * * @return int 0 if everything went fine, or an error code * * @throws \Exception When doRun returns Exception * * @api */ public function run(InputInterface $input = null, OutputInterface $output = null) { if (null === $input) { $input = new ArgvInput(); } if (null === $output) { $output = new ConsoleOutput(); } $this->configureIO($input, $output); try { $exitCode = $this->doRun($input, $output); } catch (\Exception $e) { if (!$this->catchExceptions) { throw $e; } if ($output instanceof ConsoleOutputInterface) { $this->renderException($e, $output->getErrorOutput()); } else { $this->renderException($e, $output); } $exitCode = $e->getCode(); if (is_numeric($exitCode)) { $exitCode = (int) $exitCode; if (0 === $exitCode) { $exitCode = 1; } } else { $exitCode = 1; } } if ($this->autoExit) { if ($exitCode > 255) { $exitCode = 255; } // @codeCoverageIgnoreStart exit($exitCode); // @codeCoverageIgnoreEnd } return $exitCode; } /** * Runs the current application. * * @param InputInterface $input An Input instance * @param OutputInterface $output An Output instance * * @return int 0 if everything went fine, or an error code */ public function doRun(InputInterface $input, OutputInterface $output) { if (true === $input->hasParameterOption(array('--version', '-V'))) { $output->writeln($this->getLongVersion()); return 0; } $name = $this->getCommandName($input); if (true === $input->hasParameterOption(array('--help', '-h'))) { if (!$name) { $name = 'help'; $input = new ArrayInput(array('command' => 'help')); } else { $this->wantHelps = true; } } if (!$name) { $name = 'list'; $input = new ArrayInput(array('command' => 'list')); } // the command name MUST be the first element of the input $command = $this->find($name); $this->runningCommand = $command; $exitCode = $this->doRunCommand($command, $input, $output); $this->runningCommand = null; return $exitCode; } /** * Set a helper set to be used with the command. * * @param HelperSet $helperSet The helper set * * @api */ public function setHelperSet(HelperSet $helperSet) { $this->helperSet = $helperSet; } /** * Get the helper set associated with the command. * * @return HelperSet The HelperSet instance associated with this command * * @api */ public function getHelperSet() { return $this->helperSet; } /** * Set an input definition set to be used with this application * * @param InputDefinition $definition The input definition * * @api */ public function setDefinition(InputDefinition $definition) { $this->definition = $definition; } /** * Gets the InputDefinition related to this Application. * * @return InputDefinition The InputDefinition instance */ public function getDefinition() { return $this->definition; } /** * Gets the help message. * * @return string A help message. */ public function getHelp() { $messages = array( $this->getLongVersion(), '', 'Usage:', ' [options] command [arguments]', '', 'Options:', ); foreach ($this->getDefinition()->getOptions() as $option) { $messages[] = sprintf(' %-29s %s %s', '--'.$option->getName().'', $option->getShortcut() ? '-'.$option->getShortcut().'' : ' ', $option->getDescription() ); } return implode(PHP_EOL, $messages); } /** * Sets whether to catch exceptions or not during commands execution. * * @param bool $boolean Whether to catch exceptions or not during commands execution * * @api */ public function setCatchExceptions($boolean) { $this->catchExceptions = (bool) $boolean; } /** * Sets whether to automatically exit after a command execution or not. * * @param bool $boolean Whether to automatically exit after a command execution or not * * @api */ public function setAutoExit($boolean) { $this->autoExit = (bool) $boolean; } /** * Gets the name of the application. * * @return string The application name * * @api */ public function getName() { return $this->name; } /** * Sets the application name. * * @param string $name The application name * * @api */ public function setName($name) { $this->name = $name; } /** * Gets the application version. * * @return string The application version * * @api */ public function getVersion() { return $this->version; } /** * Sets the application version. * * @param string $version The application version * * @api */ public function setVersion($version) { $this->version = $version; } /** * Returns the long version of the application. * * @return string The long application version * * @api */ public function getLongVersion() { if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion()) { return sprintf('%s version %s', $this->getName(), $this->getVersion()); } return 'Console Tool'; } /** * Registers a new command. * * @param string $name The command name * * @return Command The newly created command * * @api */ public function register($name) { return $this->add(new Command($name)); } /** * Adds an array of command objects. * * @param Command[] $commands An array of commands * * @api */ public function addCommands(array $commands) { foreach ($commands as $command) { $this->add($command); } } /** * Adds a command object. * * If a command with the same name already exists, it will be overridden. * * @param Command $command A Command object * * @return Command The registered command * * @api */ public function add(Command $command) { $command->setApplication($this); if (!$command->isEnabled()) { $command->setApplication(null); return; } if (null === $command->getDefinition()) { throw new \LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command))); } $this->commands[$command->getName()] = $command; foreach ($command->getAliases() as $alias) { $this->commands[$alias] = $command; } return $command; } /** * Returns a registered command by name or alias. * * @param string $name The command name or alias * * @return Command A Command object * * @throws \InvalidArgumentException When command name given does not exist * * @api */ public function get($name) { if (!isset($this->commands[$name])) { throw new \InvalidArgumentException(sprintf('The command "%s" does not exist.', $name)); } $command = $this->commands[$name]; if ($this->wantHelps) { $this->wantHelps = false; $helpCommand = $this->get('help'); $helpCommand->setCommand($command); return $helpCommand; } return $command; } /** * Returns true if the command exists, false otherwise. * * @param string $name The command name or alias * * @return bool true if the command exists, false otherwise * * @api */ public function has($name) { return isset($this->commands[$name]); } /** * Returns an array of all unique namespaces used by currently registered commands. * * It does not returns the global namespace which always exists. * * @return array An array of namespaces */ public function getNamespaces() { $namespaces = array(); foreach ($this->commands as $command) { $namespaces[] = $this->extractNamespace($command->getName()); foreach ($command->getAliases() as $alias) { $namespaces[] = $this->extractNamespace($alias); } } return array_values(array_unique(array_filter($namespaces))); } /** * Finds a registered namespace by a name or an abbreviation. * * @param string $namespace A namespace or abbreviation to search for * * @return string A registered namespace * * @throws \InvalidArgumentException When namespace is incorrect or ambiguous */ public function findNamespace($namespace) { $allNamespaces = $this->getNamespaces(); $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $namespace); $namespaces = preg_grep('{^'.$expr.'}', $allNamespaces); if (empty($namespaces)) { $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace); if ($alternatives = $this->findAlternatives($namespace, $allNamespaces, array())) { if (1 == count($alternatives)) { $message .= "\n\nDid you mean this?\n "; } else { $message .= "\n\nDid you mean one of these?\n "; } $message .= implode("\n ", $alternatives); } throw new \InvalidArgumentException($message); } $exact = in_array($namespace, $namespaces, true); if (count($namespaces) > 1 && !$exact) { throw new \InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions(array_values($namespaces)))); } return $exact ? $namespace : reset($namespaces); } /** * Finds a command by name or alias. * * Contrary to get, this command tries to find the best * match if you give it an abbreviation of a name or alias. * * @param string $name A command name or a command alias * * @return Command A Command instance * * @throws \InvalidArgumentException When command name is incorrect or ambiguous * * @api */ public function find($name) { $allCommands = array_keys($this->commands); $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { return preg_quote($matches[1]).'[^:]*'; }, $name); $commands = preg_grep('{^'.$expr.'}', $allCommands); if (empty($commands) || count(preg_grep('{^'.$expr.'$}', $commands)) < 1) { if (false !== $pos = strrpos($name, ':')) { // check if a namespace exists and contains commands $this->findNamespace(substr($name, 0, $pos)); } $message = sprintf('Command "%s" is not defined.', $name); if ($alternatives = $this->findAlternatives($name, $allCommands, array())) { if (1 == count($alternatives)) { $message .= "\n\nDid you mean this?\n "; } else { $message .= "\n\nDid you mean one of these?\n "; } $message .= implode("\n ", $alternatives); } throw new \InvalidArgumentException($message); } // filter out aliases for commands which are already on the list if (count($commands) > 1) { $commandList = $this->commands; $commands = array_filter($commands, function ($nameOrAlias) use ($commandList, $commands) { $commandName = $commandList[$nameOrAlias]->getName(); return $commandName === $nameOrAlias || !in_array($commandName, $commands); }); } $exact = in_array($name, $commands, true); if (count($commands) > 1 && !$exact) { $suggestions = $this->getAbbreviationSuggestions(array_values($commands)); throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions)); } return $this->get($exact ? $name : reset($commands)); } /** * Gets the commands (registered in the given namespace if provided). * * The array keys are the full names and the values the command instances. * * @param string $namespace A namespace name * * @return Command[] An array of Command instances * * @api */ public function all($namespace = null) { if (null === $namespace) { return $this->commands; } $commands = array(); foreach ($this->commands as $name => $command) { if ($namespace === $this->extractNamespace($name, substr_count($namespace, ':') + 1)) { $commands[$name] = $command; } } return $commands; } /** * Returns an array of possible abbreviations given a set of names. * * @param array $names An array of names * * @return array An array of abbreviations */ public static function getAbbreviations($names) { $abbrevs = array(); foreach ($names as $name) { for ($len = strlen($name); $len > 0; --$len) { $abbrev = substr($name, 0, $len); $abbrevs[$abbrev][] = $name; } } return $abbrevs; } /** * Returns a text representation of the Application. * * @param string $namespace An optional namespace name * @param bool $raw Whether to return raw command list * * @return string A string representing the Application * * @deprecated Deprecated since version 2.3, to be removed in 3.0. */ public function asText($namespace = null, $raw = false) { $descriptor = new TextDescriptor(); $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, !$raw); $descriptor->describe($output, $this, array('namespace' => $namespace, 'raw_output' => true)); return $output->fetch(); } /** * Returns an XML representation of the Application. * * @param string $namespace An optional namespace name * @param bool $asDom Whether to return a DOM or an XML string * * @return string|\DOMDocument An XML string representing the Application * * @deprecated Deprecated since version 2.3, to be removed in 3.0. */ public function asXml($namespace = null, $asDom = false) { $descriptor = new XmlDescriptor(); if ($asDom) { return $descriptor->getApplicationDocument($this, $namespace); } $output = new BufferedOutput(); $descriptor->describe($output, $this, array('namespace' => $namespace)); return $output->fetch(); } /** * Renders a caught exception. * * @param \Exception $e An exception instance * @param OutputInterface $output An OutputInterface instance */ public function renderException($e, $output) { $strlen = function ($string) { if (!function_exists('mb_strlen')) { return strlen($string); } if (false === $encoding = mb_detect_encoding($string)) { return strlen($string); } return mb_strlen($string, $encoding); }; do { $title = sprintf(' [%s] ', get_class($e)); $len = $strlen($title); $width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : PHP_INT_MAX; // HHVM only accepts 32 bits integer in str_split, even when PHP_INT_MAX is a 64 bit integer: https://github.com/facebook/hhvm/issues/1327 if (defined('HHVM_VERSION') && $width > 1 << 31) { $width = 1 << 31; } $formatter = $output->getFormatter(); $lines = array(); foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) { foreach (str_split($line, $width - 4) as $line) { // pre-format lines to get the right string length $lineLength = $strlen(preg_replace('/\[[^m]*m/', '', $formatter->format($line))) + 4; $lines[] = array($line, $lineLength); $len = max($lineLength, $len); } } $messages = array('', ''); $messages[] = $emptyLine = $formatter->format(sprintf('%s', str_repeat(' ', $len))); $messages[] = $formatter->format(sprintf('%s%s', $title, str_repeat(' ', max(0, $len - $strlen($title))))); foreach ($lines as $line) { $messages[] = $formatter->format(sprintf(' %s %s', $line[0], str_repeat(' ', $len - $line[1]))); } $messages[] = $emptyLine; $messages[] = ''; $messages[] = ''; $output->writeln($messages, OutputInterface::OUTPUT_RAW); if (OutputInterface::VERBOSITY_VERBOSE <= $output->getVerbosity()) { $output->writeln('Exception trace:'); // exception related properties $trace = $e->getTrace(); array_unshift($trace, array( 'function' => '', 'file' => $e->getFile() != null ? $e->getFile() : 'n/a', 'line' => $e->getLine() != null ? $e->getLine() : 'n/a', 'args' => array(), )); for ($i = 0, $count = count($trace); $i < $count; $i++) { $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : ''; $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : ''; $function = $trace[$i]['function']; $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a'; $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a'; $output->writeln(sprintf(' %s%s%s() at %s:%s', $class, $type, $function, $file, $line)); } $output->writeln(""); $output->writeln(""); } } while ($e = $e->getPrevious()); if (null !== $this->runningCommand) { $output->writeln(sprintf('%s', sprintf($this->runningCommand->getSynopsis(), $this->getName()))); $output->writeln(""); $output->writeln(""); } } /** * Tries to figure out the terminal width in which this application runs * * @return int|null */ protected function getTerminalWidth() { $dimensions = $this->getTerminalDimensions(); return $dimensions[0]; } /** * Tries to figure out the terminal height in which this application runs * * @return int|null */ protected function getTerminalHeight() { $dimensions = $this->getTerminalDimensions(); return $dimensions[1]; } /** * Tries to figure out the terminal dimensions based on the current environment * * @return array Array containing width and height */ public function getTerminalDimensions() { if ($this->terminalDimensions) { return $this->terminalDimensions; } if (defined('PHP_WINDOWS_VERSION_BUILD')) { // extract [w, H] from "wxh (WxH)" if (preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim(getenv('ANSICON')), $matches)) { return array((int) $matches[1], (int) $matches[2]); } // extract [w, h] from "wxh" if (preg_match('/^(\d+)x(\d+)$/', $this->getConsoleMode(), $matches)) { return array((int) $matches[1], (int) $matches[2]); } } if ($sttyString = $this->getSttyColumns()) { // extract [w, h] from "rows h; columns w;" if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) { return array((int) $matches[2], (int) $matches[1]); } // extract [w, h] from "; h rows; w columns" if (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) { return array((int) $matches[2], (int) $matches[1]); } } return array(null, null); } /** * Sets terminal dimensions. * * Can be useful to force terminal dimensions for functional tests. * * @param int $width The width * @param int $height The height * * @return Application The current application */ public function setTerminalDimensions($width, $height) { $this->terminalDimensions = array($width, $height); return $this; } /** * Configures the input and output instances based on the user arguments and options. * * @param InputInterface $input An InputInterface instance * @param OutputInterface $output An OutputInterface instance */ protected function configureIO(InputInterface $input, OutputInterface $output) { if (true === $input->hasParameterOption(array('--ansi'))) { $output->setDecorated(true); } elseif (true === $input->hasParameterOption(array('--no-ansi'))) { $output->setDecorated(false); } if (true === $input->hasParameterOption(array('--no-interaction', '-n'))) { $input->setInteractive(false); } elseif (function_exists('posix_isatty') && $this->getHelperSet()->has('dialog')) { $inputStream = $this->getHelperSet()->get('dialog')->getInputStream(); if (!@posix_isatty($inputStream)) { $input->setInteractive(false); } } if (true === $input->hasParameterOption(array('--quiet', '-q'))) { $output->setVerbosity(OutputInterface::VERBOSITY_QUIET); } else { if ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || $input->getParameterOption('--verbose') === 3) { $output->setVerbosity(OutputInterface::VERBOSITY_DEBUG); } elseif ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || $input->getParameterOption('--verbose') === 2) { $output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE); } elseif ($input->hasParameterOption('-v') || $input->hasParameterOption('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getParameterOption('--verbose')) { $output->setVerbosity(OutputInterface::VERBOSITY_VERBOSE); } } } /** * Runs the current command. * * If an event dispatcher has been attached to the application, * events are also dispatched during the life-cycle of the command. * * @param Command $command A Command instance * @param InputInterface $input An Input instance * @param OutputInterface $output An Output instance * * @return int 0 if everything went fine, or an error code */ protected function doRunCommand(Command $command, InputInterface $input, OutputInterface $output) { foreach ($command->getHelperSet() as $helper) { if ($helper instanceof InputAwareInterface) { $helper->setInput($input); } } if (null === $this->dispatcher) { return $command->run($input, $output); } $event = new ConsoleCommandEvent($command, $input, $output); $this->dispatcher->dispatch(ConsoleEvents::COMMAND, $event); try { $exitCode = $command->run($input, $output); } catch (\Exception $e) { $event = new ConsoleTerminateEvent($command, $input, $output, $e->getCode()); $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event); $event = new ConsoleExceptionEvent($command, $input, $output, $e, $event->getExitCode()); $this->dispatcher->dispatch(ConsoleEvents::EXCEPTION, $event); throw $event->getException(); } $event = new ConsoleTerminateEvent($command, $input, $output, $exitCode); $this->dispatcher->dispatch(ConsoleEvents::TERMINATE, $event); return $event->getExitCode(); } /** * Gets the name of the command based on input. * * @param InputInterface $input The input interface * * @return string The command name */ protected function getCommandName(InputInterface $input) { return $input->getFirstArgument(); } /** * Gets the default input definition. * * @return InputDefinition An InputDefinition instance */ protected function getDefaultInputDefinition() { return new InputDefinition(array( new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message.'), new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message.'), new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'), new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this application version.'), new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output.'), new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output.'), new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question.'), )); } /** * Gets the default commands that should always be available. * * @return Command[] An array of default Command instances */ protected function getDefaultCommands() { return array(new HelpCommand(), new ListCommand()); } /** * Gets the default helper set with the helpers that should always be available. * * @return HelperSet A HelperSet instance */ protected function getDefaultHelperSet() { return new HelperSet(array( new FormatterHelper(), new DialogHelper(), new ProgressHelper(), new TableHelper(), )); } /** * Runs and parses stty -a if it's available, suppressing any error output * * @return string */ private function getSttyColumns() { if (!function_exists('proc_open')) { return; } $descriptorspec = array(1 => array('pipe', 'w'), 2 => array('pipe', 'w')); $process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, array('suppress_errors' => true)); if (is_resource($process)) { $info = stream_get_contents($pipes[1]); fclose($pipes[1]); fclose($pipes[2]); proc_close($process); return $info; } } /** * Runs and parses mode CON if it's available, suppressing any error output * * @return string x or null if it could not be parsed */ private function getConsoleMode() { if (!function_exists('proc_open')) { return; } $descriptorspec = array(1 => array('pipe', 'w'), 2 => array('pipe', 'w')); $process = proc_open('mode CON', $descriptorspec, $pipes, null, null, array('suppress_errors' => true)); if (is_resource($process)) { $info = stream_get_contents($pipes[1]); fclose($pipes[1]); fclose($pipes[2]); proc_close($process); if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) { return $matches[2].'x'.$matches[1]; } } } /** * Returns abbreviated suggestions in string format. * * @param array $abbrevs Abbreviated suggestions to convert * * @return string A formatted string of abbreviated suggestions */ private function getAbbreviationSuggestions($abbrevs) { return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : ''); } /** * Returns the namespace part of the command name. * * This method is not part of public API and should not be used directly. * * @param string $name The full name of the command * @param string $limit The maximum number of parts of the namespace * * @return string The namespace of the command */ public function extractNamespace($name, $limit = null) { $parts = explode(':', $name); array_pop($parts); return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit)); } /** * Finds alternative of $name among $collection, * if nothing is found in $collection, try in $abbrevs * * @param string $name The string * @param array|\Traversable $collection The collection * * @return array A sorted array of similar string */ private function findAlternatives($name, $collection) { $threshold = 1e3; $alternatives = array(); $collectionParts = array(); foreach ($collection as $item) { $collectionParts[$item] = explode(':', $item); } foreach (explode(':', $name) as $i => $subname) { foreach ($collectionParts as $collectionName => $parts) { $exists = isset($alternatives[$collectionName]); if (!isset($parts[$i]) && $exists) { $alternatives[$collectionName] += $threshold; continue; } elseif (!isset($parts[$i])) { continue; } $lev = levenshtein($subname, $parts[$i]); if ($lev <= strlen($subname) / 3 || false !== strpos($parts[$i], $subname)) { $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev; } elseif ($exists) { $alternatives[$collectionName] += $threshold; } } } foreach ($collection as $item) { $lev = levenshtein($name, $item); if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) { $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev; } } $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { return $lev < 2*$threshold; }); asort($alternatives); return array_keys($alternatives); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Output; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Formatter\OutputFormatterInterface; /** * NullOutput suppresses all output. * * $output = new NullOutput(); * * @author Fabien Potencier * @author Tobias Schultze * * @api */ class NullOutput implements OutputInterface { /** * {@inheritdoc} */ public function setFormatter(OutputFormatterInterface $formatter) { // do nothing } /** * {@inheritdoc} */ public function getFormatter() { // to comply with the interface we must return a OutputFormatterInterface return new OutputFormatter(); } /** * {@inheritdoc} */ public function setDecorated($decorated) { // do nothing } /** * {@inheritdoc} */ public function isDecorated() { return false; } /** * {@inheritdoc} */ public function setVerbosity($level) { // do nothing } /** * {@inheritdoc} */ public function getVerbosity() { return self::VERBOSITY_QUIET; } /** * {@inheritdoc} */ public function writeln($messages, $type = self::OUTPUT_NORMAL) { // do nothing } /** * {@inheritdoc} */ public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) { // do nothing } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Output; use Symfony\Component\Console\Formatter\OutputFormatterInterface; /** * OutputInterface is the interface implemented by all Output classes. * * @author Fabien Potencier * * @api */ interface OutputInterface { const VERBOSITY_QUIET = 0; const VERBOSITY_NORMAL = 1; const VERBOSITY_VERBOSE = 2; const VERBOSITY_VERY_VERBOSE = 3; const VERBOSITY_DEBUG = 4; const OUTPUT_NORMAL = 0; const OUTPUT_RAW = 1; const OUTPUT_PLAIN = 2; /** * Writes a message to the output. * * @param string|array $messages The message as an array of lines or a single string * @param bool $newline Whether to add a newline * @param int $type The type of output (one of the OUTPUT constants) * * @throws \InvalidArgumentException When unknown output type is given * * @api */ public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL); /** * Writes a message to the output and adds a newline at the end. * * @param string|array $messages The message as an array of lines of a single string * @param int $type The type of output (one of the OUTPUT constants) * * @throws \InvalidArgumentException When unknown output type is given * * @api */ public function writeln($messages, $type = self::OUTPUT_NORMAL); /** * Sets the verbosity of the output. * * @param int $level The level of verbosity (one of the VERBOSITY constants) * * @api */ public function setVerbosity($level); /** * Gets the current verbosity of the output. * * @return int The current level of verbosity (one of the VERBOSITY constants) * * @api */ public function getVerbosity(); /** * Sets the decorated flag. * * @param bool $decorated Whether to decorate the messages * * @api */ public function setDecorated($decorated); /** * Gets the decorated flag. * * @return bool true if the output will decorate messages, false otherwise * * @api */ public function isDecorated(); /** * Sets output formatter. * * @param OutputFormatterInterface $formatter * * @api */ public function setFormatter(OutputFormatterInterface $formatter); /** * Returns current output formatter instance. * * @return OutputFormatterInterface * * @api */ public function getFormatter(); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Output; /** * ConsoleOutputInterface is the interface implemented by ConsoleOutput class. * This adds information about stderr output stream. * * @author Dariusz Górecki */ interface ConsoleOutputInterface extends OutputInterface { /** * Gets the OutputInterface for errors. * * @return OutputInterface */ public function getErrorOutput(); /** * Sets the OutputInterface used for errors. * * @param OutputInterface $error */ public function setErrorOutput(OutputInterface $error); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Output; use Symfony\Component\Console\Formatter\OutputFormatterInterface; use Symfony\Component\Console\Formatter\OutputFormatter; /** * Base class for output classes. * * There are five levels of verbosity: * * * normal: no option passed (normal output) * * verbose: -v (more output) * * very verbose: -vv (highly extended output) * * debug: -vvv (all debug output) * * quiet: -q (no output) * * @author Fabien Potencier * * @api */ abstract class Output implements OutputInterface { private $verbosity; private $formatter; /** * Constructor. * * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) * @param bool $decorated Whether to decorate messages * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) * * @api */ public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = false, OutputFormatterInterface $formatter = null) { $this->verbosity = null === $verbosity ? self::VERBOSITY_NORMAL : $verbosity; $this->formatter = $formatter ?: new OutputFormatter(); $this->formatter->setDecorated($decorated); } /** * {@inheritdoc} */ public function setFormatter(OutputFormatterInterface $formatter) { $this->formatter = $formatter; } /** * {@inheritdoc} */ public function getFormatter() { return $this->formatter; } /** * {@inheritdoc} */ public function setDecorated($decorated) { $this->formatter->setDecorated($decorated); } /** * {@inheritdoc} */ public function isDecorated() { return $this->formatter->isDecorated(); } /** * {@inheritdoc} */ public function setVerbosity($level) { $this->verbosity = (int) $level; } /** * {@inheritdoc} */ public function getVerbosity() { return $this->verbosity; } public function isQuiet() { return self::VERBOSITY_QUIET === $this->verbosity; } public function isVerbose() { return self::VERBOSITY_VERBOSE <= $this->verbosity; } public function isVeryVerbose() { return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; } public function isDebug() { return self::VERBOSITY_DEBUG <= $this->verbosity; } /** * {@inheritdoc} */ public function writeln($messages, $type = self::OUTPUT_NORMAL) { $this->write($messages, true, $type); } /** * {@inheritdoc} */ public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) { if (self::VERBOSITY_QUIET === $this->verbosity) { return; } $messages = (array) $messages; foreach ($messages as $message) { switch ($type) { case OutputInterface::OUTPUT_NORMAL: $message = $this->formatter->format($message); break; case OutputInterface::OUTPUT_RAW: break; case OutputInterface::OUTPUT_PLAIN: $message = strip_tags($this->formatter->format($message)); break; default: throw new \InvalidArgumentException(sprintf('Unknown output type given (%s)', $type)); } $this->doWrite($message, $newline); } } /** * Writes a message to the output. * * @param string $message A message to write to the output * @param bool $newline Whether to add a newline or not */ abstract protected function doWrite($message, $newline); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Output; use Symfony\Component\Console\Formatter\OutputFormatterInterface; /** * StreamOutput writes the output to a given stream. * * Usage: * * $output = new StreamOutput(fopen('php://stdout', 'w')); * * As `StreamOutput` can use any stream, you can also use a file: * * $output = new StreamOutput(fopen('/path/to/output.log', 'a', false)); * * @author Fabien Potencier * * @api */ class StreamOutput extends Output { private $stream; /** * Constructor. * * @param mixed $stream A stream resource * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) * * @throws \InvalidArgumentException When first argument is not a real stream * * @api */ public function __construct($stream, $verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null) { if (!is_resource($stream) || 'stream' !== get_resource_type($stream)) { throw new \InvalidArgumentException('The StreamOutput class needs a stream as its first argument.'); } $this->stream = $stream; if (null === $decorated) { $decorated = $this->hasColorSupport(); } parent::__construct($verbosity, $decorated, $formatter); } /** * Gets the stream attached to this StreamOutput instance. * * @return resource A stream resource */ public function getStream() { return $this->stream; } /** * {@inheritdoc} */ protected function doWrite($message, $newline) { if (false === @fwrite($this->stream, $message.($newline ? PHP_EOL : ''))) { // @codeCoverageIgnoreStart // should never happen throw new \RuntimeException('Unable to write output.'); // @codeCoverageIgnoreEnd } fflush($this->stream); } /** * Returns true if the stream supports colorization. * * Colorization is disabled if not supported by the stream: * * - Windows without Ansicon and ConEmu * - non tty consoles * * @return bool true if the stream supports colorization, false otherwise */ protected function hasColorSupport() { // @codeCoverageIgnoreStart if (DIRECTORY_SEPARATOR == '\\') { return false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI'); } return function_exists('posix_isatty') && @posix_isatty($this->stream); // @codeCoverageIgnoreEnd } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Output; /** * @author Jean-François Simon */ class BufferedOutput extends Output { /** * @var string */ private $buffer = ''; /** * Empties buffer and returns its content. * * @return string */ public function fetch() { $content = $this->buffer; $this->buffer = ''; return $content; } /** * {@inheritdoc} */ protected function doWrite($message, $newline) { $this->buffer .= $message; if ($newline) { $this->buffer .= "\n"; } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Output; use Symfony\Component\Console\Formatter\OutputFormatterInterface; /** * ConsoleOutput is the default class for all CLI output. It uses STDOUT. * * This class is a convenient wrapper around `StreamOutput`. * * $output = new ConsoleOutput(); * * This is equivalent to: * * $output = new StreamOutput(fopen('php://stdout', 'w')); * * @author Fabien Potencier * * @api */ class ConsoleOutput extends StreamOutput implements ConsoleOutputInterface { private $stderr; /** * Constructor. * * @param int $verbosity The verbosity level (one of the VERBOSITY constants in OutputInterface) * @param bool|null $decorated Whether to decorate messages (null for auto-guessing) * @param OutputFormatterInterface|null $formatter Output formatter instance (null to use default OutputFormatter) * * @api */ public function __construct($verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null) { $outputStream = 'php://stdout'; if (!$this->hasStdoutSupport()) { $outputStream = 'php://output'; } parent::__construct(fopen($outputStream, 'w'), $verbosity, $decorated, $formatter); $this->stderr = new StreamOutput(fopen('php://stderr', 'w'), $verbosity, $decorated, $formatter); } /** * {@inheritdoc} */ public function setDecorated($decorated) { parent::setDecorated($decorated); $this->stderr->setDecorated($decorated); } /** * {@inheritdoc} */ public function setFormatter(OutputFormatterInterface $formatter) { parent::setFormatter($formatter); $this->stderr->setFormatter($formatter); } /** * {@inheritdoc} */ public function setVerbosity($level) { parent::setVerbosity($level); $this->stderr->setVerbosity($level); } /** * {@inheritdoc} */ public function getErrorOutput() { return $this->stderr; } /** * {@inheritdoc} */ public function setErrorOutput(OutputInterface $error) { $this->stderr = $error; } /** * Returns true if current environment supports writing console output to * STDOUT. * * IBM iSeries (OS400) exhibits character-encoding issues when writing to * STDOUT and doesn't properly convert ASCII to EBCDIC, resulting in garbage * output. * * @return bool */ protected function hasStdoutSupport() { return ('OS400' != php_uname('s')); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Formatter; /** * Formatter class for console output. * * @author Konstantin Kudryashov * * @api */ class OutputFormatter implements OutputFormatterInterface { private $decorated; private $styles = array(); private $styleStack; /** * Escapes "<" special char in given text. * * @param string $text Text to escape * * @return string Escaped text */ public static function escape($text) { return preg_replace('/([^\\\\]?) FormatterStyle" instances * * @api */ public function __construct($decorated = false, array $styles = array()) { $this->decorated = (bool) $decorated; $this->setStyle('error', new OutputFormatterStyle('white', 'red')); $this->setStyle('info', new OutputFormatterStyle('green')); $this->setStyle('comment', new OutputFormatterStyle('yellow')); $this->setStyle('question', new OutputFormatterStyle('black', 'cyan')); foreach ($styles as $name => $style) { $this->setStyle($name, $style); } $this->styleStack = new OutputFormatterStyleStack(); } /** * Sets the decorated flag. * * @param bool $decorated Whether to decorate the messages or not * * @api */ public function setDecorated($decorated) { $this->decorated = (bool) $decorated; } /** * Gets the decorated flag. * * @return bool true if the output will decorate messages, false otherwise * * @api */ public function isDecorated() { return $this->decorated; } /** * Sets a new style. * * @param string $name The style name * @param OutputFormatterStyleInterface $style The style instance * * @api */ public function setStyle($name, OutputFormatterStyleInterface $style) { $this->styles[strtolower($name)] = $style; } /** * Checks if output formatter has style with specified name. * * @param string $name * * @return bool * * @api */ public function hasStyle($name) { return isset($this->styles[strtolower($name)]); } /** * Gets style options from style with specified name. * * @param string $name * * @return OutputFormatterStyleInterface * * @throws \InvalidArgumentException When style isn't defined * * @api */ public function getStyle($name) { if (!$this->hasStyle($name)) { throw new \InvalidArgumentException(sprintf('Undefined style: %s', $name)); } return $this->styles[strtolower($name)]; } /** * Formats a message according to the given styles. * * @param string $message The message to style * * @return string The styled message * * @api */ public function format($message) { $offset = 0; $output = ''; $tagRegex = '[a-z][a-z0-9_=;-]*'; preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#isx", $message, $matches, PREG_OFFSET_CAPTURE); foreach ($matches[0] as $i => $match) { $pos = $match[1]; $text = $match[0]; // add the text up to the next tag $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset)); $offset = $pos + strlen($text); // opening tag? if ($open = '/' != $text[1]) { $tag = $matches[1][$i][0]; } else { $tag = isset($matches[3][$i][0]) ? $matches[3][$i][0] : ''; } if (!$open && !$tag) { // $this->styleStack->pop(); } elseif ($pos && '\\' == $message[$pos - 1]) { // escaped tag $output .= $this->applyCurrentStyle($text); } elseif (false === $style = $this->createStyleFromString(strtolower($tag))) { $output .= $this->applyCurrentStyle($text); } elseif ($open) { $this->styleStack->push($style); } else { $this->styleStack->pop($style); } } $output .= $this->applyCurrentStyle(substr($message, $offset)); return str_replace('\\<', '<', $output); } /** * @return OutputFormatterStyleStack */ public function getStyleStack() { return $this->styleStack; } /** * Tries to create new style instance from string. * * @param string $string * * @return OutputFormatterStyle|bool false if string is not format string */ private function createStyleFromString($string) { if (isset($this->styles[$string])) { return $this->styles[$string]; } if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', strtolower($string), $matches, PREG_SET_ORDER)) { return false; } $style = new OutputFormatterStyle(); foreach ($matches as $match) { array_shift($match); if ('fg' == $match[0]) { $style->setForeground($match[1]); } elseif ('bg' == $match[0]) { $style->setBackground($match[1]); } else { $style->setOption($match[1]); } } return $style; } /** * Applies current style from stack to text, if must be applied. * * @param string $text Input text * * @return string Styled text */ private function applyCurrentStyle($text) { return $this->isDecorated() && strlen($text) > 0 ? $this->styleStack->getCurrent()->apply($text) : $text; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Formatter; /** * Formatter style interface for defining styles. * * @author Konstantin Kudryashov * * @api */ interface OutputFormatterStyleInterface { /** * Sets style foreground color. * * @param string $color The color name * * @api */ public function setForeground($color = null); /** * Sets style background color. * * @param string $color The color name * * @api */ public function setBackground($color = null); /** * Sets some specific style option. * * @param string $option The option name * * @api */ public function setOption($option); /** * Unsets some specific style option. * * @param string $option The option name */ public function unsetOption($option); /** * Sets multiple style options at once. * * @param array $options */ public function setOptions(array $options); /** * Applies the style to a given text. * * @param string $text The text to style * * @return string */ public function apply($text); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Formatter; /** * @author Jean-François Simon */ class OutputFormatterStyleStack { /** * @var OutputFormatterStyleInterface[] */ private $styles; /** * @var OutputFormatterStyleInterface */ private $emptyStyle; /** * Constructor. * * @param OutputFormatterStyleInterface|null $emptyStyle */ public function __construct(OutputFormatterStyleInterface $emptyStyle = null) { $this->emptyStyle = $emptyStyle ?: new OutputFormatterStyle(); $this->reset(); } /** * Resets stack (ie. empty internal arrays). */ public function reset() { $this->styles = array(); } /** * Pushes a style in the stack. * * @param OutputFormatterStyleInterface $style */ public function push(OutputFormatterStyleInterface $style) { $this->styles[] = $style; } /** * Pops a style from the stack. * * @param OutputFormatterStyleInterface|null $style * * @return OutputFormatterStyleInterface * * @throws \InvalidArgumentException When style tags incorrectly nested */ public function pop(OutputFormatterStyleInterface $style = null) { if (empty($this->styles)) { return $this->emptyStyle; } if (null === $style) { return array_pop($this->styles); } foreach (array_reverse($this->styles, true) as $index => $stackedStyle) { if ($style->apply('') === $stackedStyle->apply('')) { $this->styles = array_slice($this->styles, 0, $index); return $stackedStyle; } } throw new \InvalidArgumentException('Incorrectly nested style tag found.'); } /** * Computes current style with stacks top codes. * * @return OutputFormatterStyle */ public function getCurrent() { if (empty($this->styles)) { return $this->emptyStyle; } return $this->styles[count($this->styles)-1]; } /** * @param OutputFormatterStyleInterface $emptyStyle * * @return OutputFormatterStyleStack */ public function setEmptyStyle(OutputFormatterStyleInterface $emptyStyle) { $this->emptyStyle = $emptyStyle; return $this; } /** * @return OutputFormatterStyleInterface */ public function getEmptyStyle() { return $this->emptyStyle; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Formatter; /** * Formatter style class for defining styles. * * @author Konstantin Kudryashov * * @api */ class OutputFormatterStyle implements OutputFormatterStyleInterface { private static $availableForegroundColors = array( 'black' => 30, 'red' => 31, 'green' => 32, 'yellow' => 33, 'blue' => 34, 'magenta' => 35, 'cyan' => 36, 'white' => 37 ); private static $availableBackgroundColors = array( 'black' => 40, 'red' => 41, 'green' => 42, 'yellow' => 43, 'blue' => 44, 'magenta' => 45, 'cyan' => 46, 'white' => 47 ); private static $availableOptions = array( 'bold' => 1, 'underscore' => 4, 'blink' => 5, 'reverse' => 7, 'conceal' => 8 ); private $foreground; private $background; private $options = array(); /** * Initializes output formatter style. * * @param string|null $foreground The style foreground color name * @param string|null $background The style background color name * @param array $options The style options * * @api */ public function __construct($foreground = null, $background = null, array $options = array()) { if (null !== $foreground) { $this->setForeground($foreground); } if (null !== $background) { $this->setBackground($background); } if (count($options)) { $this->setOptions($options); } } /** * Sets style foreground color. * * @param string|null $color The color name * * @throws \InvalidArgumentException When the color name isn't defined * * @api */ public function setForeground($color = null) { if (null === $color) { $this->foreground = null; return; } if (!isset(static::$availableForegroundColors[$color])) { throw new \InvalidArgumentException(sprintf( 'Invalid foreground color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableForegroundColors)) )); } $this->foreground = static::$availableForegroundColors[$color]; } /** * Sets style background color. * * @param string|null $color The color name * * @throws \InvalidArgumentException When the color name isn't defined * * @api */ public function setBackground($color = null) { if (null === $color) { $this->background = null; return; } if (!isset(static::$availableBackgroundColors[$color])) { throw new \InvalidArgumentException(sprintf( 'Invalid background color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableBackgroundColors)) )); } $this->background = static::$availableBackgroundColors[$color]; } /** * Sets some specific style option. * * @param string $option The option name * * @throws \InvalidArgumentException When the option name isn't defined * * @api */ public function setOption($option) { if (!isset(static::$availableOptions[$option])) { throw new \InvalidArgumentException(sprintf( 'Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions)) )); } if (false === array_search(static::$availableOptions[$option], $this->options)) { $this->options[] = static::$availableOptions[$option]; } } /** * Unsets some specific style option. * * @param string $option The option name * * @throws \InvalidArgumentException When the option name isn't defined * */ public function unsetOption($option) { if (!isset(static::$availableOptions[$option])) { throw new \InvalidArgumentException(sprintf( 'Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions)) )); } $pos = array_search(static::$availableOptions[$option], $this->options); if (false !== $pos) { unset($this->options[$pos]); } } /** * Sets multiple style options at once. * * @param array $options */ public function setOptions(array $options) { $this->options = array(); foreach ($options as $option) { $this->setOption($option); } } /** * Applies the style to a given text. * * @param string $text The text to style * * @return string */ public function apply($text) { $codes = array(); if (null !== $this->foreground) { $codes[] = $this->foreground; } if (null !== $this->background) { $codes[] = $this->background; } if (count($this->options)) { $codes = array_merge($codes, $this->options); } if (0 === count($codes)) { return $text; } return sprintf("\033[%sm%s\033[0m", implode(';', $codes), $text); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Formatter; /** * Formatter interface for console output. * * @author Konstantin Kudryashov * * @api */ interface OutputFormatterInterface { /** * Sets the decorated flag. * * @param bool $decorated Whether to decorate the messages or not * * @api */ public function setDecorated($decorated); /** * Gets the decorated flag. * * @return bool true if the output will decorate messages, false otherwise * * @api */ public function isDecorated(); /** * Sets a new style. * * @param string $name The style name * @param OutputFormatterStyleInterface $style The style instance * * @api */ public function setStyle($name, OutputFormatterStyleInterface $style); /** * Checks if output formatter has style with specified name. * * @param string $name * * @return bool * * @api */ public function hasStyle($name); /** * Gets style options from style with specified name. * * @param string $name * * @return OutputFormatterStyleInterface * * @api */ public function getStyle($name); /** * Formats a message according to the given styles. * * @param string $message The message to style * * @return string The styled message * * @api */ public function format($message); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Tester; use Symfony\Component\Console\Application; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\StreamOutput; /** * Eases the testing of console applications. * * When testing an application, don't forget to disable the auto exit flag: * * $application = new Application(); * $application->setAutoExit(false); * * @author Fabien Potencier */ class ApplicationTester { private $application; private $input; private $output; private $statusCode; /** * Constructor. * * @param Application $application An Application instance to test. */ public function __construct(Application $application) { $this->application = $application; } /** * Executes the application. * * Available options: * * * interactive: Sets the input interactive flag * * decorated: Sets the output decorated flag * * verbosity: Sets the output verbosity flag * * @param array $input An array of arguments and options * @param array $options An array of options * * @return int The command exit code */ public function run(array $input, $options = array()) { $this->input = new ArrayInput($input); if (isset($options['interactive'])) { $this->input->setInteractive($options['interactive']); } $this->output = new StreamOutput(fopen('php://memory', 'w', false)); if (isset($options['decorated'])) { $this->output->setDecorated($options['decorated']); } if (isset($options['verbosity'])) { $this->output->setVerbosity($options['verbosity']); } return $this->statusCode = $this->application->run($this->input, $this->output); } /** * Gets the display returned by the last execution of the application. * * @param bool $normalize Whether to normalize end of lines to \n or not * * @return string The display */ public function getDisplay($normalize = false) { rewind($this->output->getStream()); $display = stream_get_contents($this->output->getStream()); if ($normalize) { $display = str_replace(PHP_EOL, "\n", $display); } return $display; } /** * Gets the input instance used by the last execution of the application. * * @return InputInterface The current input instance */ public function getInput() { return $this->input; } /** * Gets the output instance used by the last execution of the application. * * @return OutputInterface The current output instance */ public function getOutput() { return $this->output; } /** * Gets the status code returned by the last execution of the application. * * @return int The status code */ public function getStatusCode() { return $this->statusCode; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Tester; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\StreamOutput; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * Eases the testing of console commands. * * @author Fabien Potencier */ class CommandTester { private $command; private $input; private $output; private $statusCode; /** * Constructor. * * @param Command $command A Command instance to test. */ public function __construct(Command $command) { $this->command = $command; } /** * Executes the command. * * Available options: * * * interactive: Sets the input interactive flag * * decorated: Sets the output decorated flag * * verbosity: Sets the output verbosity flag * * @param array $input An array of arguments and options * @param array $options An array of options * * @return int The command exit code */ public function execute(array $input, array $options = array()) { // set the command name automatically if the application requires // this argument and no command name was passed if (!isset($input['command']) && (null !== $application = $this->command->getApplication()) && $application->getDefinition()->hasArgument('command') ) { $input['command'] = $this->command->getName(); } $this->input = new ArrayInput($input); if (isset($options['interactive'])) { $this->input->setInteractive($options['interactive']); } $this->output = new StreamOutput(fopen('php://memory', 'w', false)); if (isset($options['decorated'])) { $this->output->setDecorated($options['decorated']); } if (isset($options['verbosity'])) { $this->output->setVerbosity($options['verbosity']); } return $this->statusCode = $this->command->run($this->input, $this->output); } /** * Gets the display returned by the last execution of the command. * * @param bool $normalize Whether to normalize end of lines to \n or not * * @return string The display */ public function getDisplay($normalize = false) { rewind($this->output->getStream()); $display = stream_get_contents($this->output->getStream()); if ($normalize) { $display = str_replace(PHP_EOL, "\n", $display); } return $display; } /** * Gets the input instance used by the last execution of the command. * * @return InputInterface The current input instance */ public function getInput() { return $this->input; } /** * Gets the output instance used by the last execution of the command. * * @return OutputInterface The current output instance */ public function getOutput() { return $this->output; } /** * Gets the status code returned by the last execution of the application. * * @return int The status code */ public function getStatusCode() { return $this->statusCode; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console; /** * Contains all events dispatched by an Application. * * @author Francesco Levorato */ final class ConsoleEvents { /** * The COMMAND event allows you to attach listeners before any command is * executed by the console. It also allows you to modify the command, input and output * before they are handled to the command. * * The event listener method receives a Symfony\Component\Console\Event\ConsoleCommandEvent * instance. * * @var string */ const COMMAND = 'console.command'; /** * The TERMINATE event allows you to attach listeners after a command is * executed by the console. * * The event listener method receives a Symfony\Component\Console\Event\ConsoleTerminateEvent * instance. * * @var string */ const TERMINATE = 'console.terminate'; /** * The EXCEPTION event occurs when an uncaught exception appears. * * This event allows you to deal with the exception or * to modify the thrown exception. The event listener method receives * a Symfony\Component\Console\Event\ConsoleExceptionEvent * instance. * * @var string */ const EXCEPTION = 'console.exception'; } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; /** * Represents a command line option. * * @author Fabien Potencier * * @api */ class InputOption { const VALUE_NONE = 1; const VALUE_REQUIRED = 2; const VALUE_OPTIONAL = 4; const VALUE_IS_ARRAY = 8; private $name; private $shortcut; private $mode; private $default; private $description; /** * Constructor. * * @param string $name The option name * @param string|array $shortcut The shortcuts, can be null, a string of shortcuts delimited by | or an array of shortcuts * @param int $mode The option mode: One of the VALUE_* constants * @param string $description A description text * @param mixed $default The default value (must be null for self::VALUE_REQUIRED or self::VALUE_NONE) * * @throws \InvalidArgumentException If option mode is invalid or incompatible * * @api */ public function __construct($name, $shortcut = null, $mode = null, $description = '', $default = null) { if (0 === strpos($name, '--')) { $name = substr($name, 2); } if (empty($name)) { throw new \InvalidArgumentException('An option name cannot be empty.'); } if (empty($shortcut)) { $shortcut = null; } if (null !== $shortcut) { if (is_array($shortcut)) { $shortcut = implode('|', $shortcut); } $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-')); $shortcuts = array_filter($shortcuts); $shortcut = implode('|', $shortcuts); if (empty($shortcut)) { throw new \InvalidArgumentException('An option shortcut cannot be empty.'); } } if (null === $mode) { $mode = self::VALUE_NONE; } elseif (!is_int($mode) || $mode > 15 || $mode < 1) { throw new \InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); } $this->name = $name; $this->shortcut = $shortcut; $this->mode = $mode; $this->description = $description; if ($this->isArray() && !$this->acceptValue()) { throw new \InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.'); } $this->setDefault($default); } /** * Returns the option shortcut. * * @return string The shortcut */ public function getShortcut() { return $this->shortcut; } /** * Returns the option name. * * @return string The name */ public function getName() { return $this->name; } /** * Returns true if the option accepts a value. * * @return bool true if value mode is not self::VALUE_NONE, false otherwise */ public function acceptValue() { return $this->isValueRequired() || $this->isValueOptional(); } /** * Returns true if the option requires a value. * * @return bool true if value mode is self::VALUE_REQUIRED, false otherwise */ public function isValueRequired() { return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode); } /** * Returns true if the option takes an optional value. * * @return bool true if value mode is self::VALUE_OPTIONAL, false otherwise */ public function isValueOptional() { return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode); } /** * Returns true if the option can take multiple values. * * @return bool true if mode is self::VALUE_IS_ARRAY, false otherwise */ public function isArray() { return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); } /** * Sets the default value. * * @param mixed $default The default value * * @throws \LogicException When incorrect default value is given */ public function setDefault($default = null) { if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) { throw new \LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.'); } if ($this->isArray()) { if (null === $default) { $default = array(); } elseif (!is_array($default)) { throw new \LogicException('A default value for an array option must be an array.'); } } $this->default = $this->acceptValue() ? $default : false; } /** * Returns the default value. * * @return mixed The default value */ public function getDefault() { return $this->default; } /** * Returns the description text. * * @return string The description text */ public function getDescription() { return $this->description; } /** * Checks whether the given option equals this one * * @param InputOption $option option to compare * @return bool */ public function equals(InputOption $option) { return $option->getName() === $this->getName() && $option->getShortcut() === $this->getShortcut() && $option->getDefault() === $this->getDefault() && $option->isArray() === $this->isArray() && $option->isValueRequired() === $this->isValueRequired() && $option->isValueOptional() === $this->isValueOptional() ; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; /** * Input is the base class for all concrete Input classes. * * Three concrete classes are provided by default: * * * `ArgvInput`: The input comes from the CLI arguments (argv) * * `StringInput`: The input is provided as a string * * `ArrayInput`: The input is provided as an array * * @author Fabien Potencier */ abstract class Input implements InputInterface { /** * @var InputDefinition */ protected $definition; protected $options = array(); protected $arguments = array(); protected $interactive = true; /** * Constructor. * * @param InputDefinition $definition A InputDefinition instance */ public function __construct(InputDefinition $definition = null) { if (null === $definition) { $this->definition = new InputDefinition(); } else { $this->bind($definition); $this->validate(); } } /** * Binds the current Input instance with the given arguments and options. * * @param InputDefinition $definition A InputDefinition instance */ public function bind(InputDefinition $definition) { $this->arguments = array(); $this->options = array(); $this->definition = $definition; $this->parse(); } /** * Processes command line arguments. */ abstract protected function parse(); /** * Validates the input. * * @throws \RuntimeException When not enough arguments are given */ public function validate() { if (count($this->arguments) < $this->definition->getArgumentRequiredCount()) { throw new \RuntimeException('Not enough arguments.'); } } /** * Checks if the input is interactive. * * @return bool Returns true if the input is interactive */ public function isInteractive() { return $this->interactive; } /** * Sets the input interactivity. * * @param bool $interactive If the input should be interactive */ public function setInteractive($interactive) { $this->interactive = (bool) $interactive; } /** * Returns the argument values. * * @return array An array of argument values */ public function getArguments() { return array_merge($this->definition->getArgumentDefaults(), $this->arguments); } /** * Returns the argument value for a given argument name. * * @param string $name The argument name * * @return mixed The argument value * * @throws \InvalidArgumentException When argument given doesn't exist */ public function getArgument($name) { if (!$this->definition->hasArgument($name)) { throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); } return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name)->getDefault(); } /** * Sets an argument value by name. * * @param string $name The argument name * @param string $value The argument value * * @throws \InvalidArgumentException When argument given doesn't exist */ public function setArgument($name, $value) { if (!$this->definition->hasArgument($name)) { throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); } $this->arguments[$name] = $value; } /** * Returns true if an InputArgument object exists by name or position. * * @param string|int $name The InputArgument name or position * * @return bool true if the InputArgument object exists, false otherwise */ public function hasArgument($name) { return $this->definition->hasArgument($name); } /** * Returns the options values. * * @return array An array of option values */ public function getOptions() { return array_merge($this->definition->getOptionDefaults(), $this->options); } /** * Returns the option value for a given option name. * * @param string $name The option name * * @return mixed The option value * * @throws \InvalidArgumentException When option given doesn't exist */ public function getOption($name) { if (!$this->definition->hasOption($name)) { throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); } return isset($this->options[$name]) ? $this->options[$name] : $this->definition->getOption($name)->getDefault(); } /** * Sets an option value by name. * * @param string $name The option name * @param string|bool $value The option value * * @throws \InvalidArgumentException When option given doesn't exist */ public function setOption($name, $value) { if (!$this->definition->hasOption($name)) { throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); } $this->options[$name] = $value; } /** * Returns true if an InputOption object exists by name. * * @param string $name The InputOption name * * @return bool true if the InputOption object exists, false otherwise */ public function hasOption($name) { return $this->definition->hasOption($name); } /** * Escapes a token through escapeshellarg if it contains unsafe chars * * @param string $token * * @return string */ public function escapeToken($token) { return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; if (!defined('JSON_UNESCAPED_UNICODE')) { define('JSON_UNESCAPED_SLASHES', 64); define('JSON_UNESCAPED_UNICODE', 256); } use Symfony\Component\Console\Descriptor\TextDescriptor; use Symfony\Component\Console\Descriptor\XmlDescriptor; use Symfony\Component\Console\Output\BufferedOutput; /** * A InputDefinition represents a set of valid command line arguments and options. * * Usage: * * $definition = new InputDefinition(array( * new InputArgument('name', InputArgument::REQUIRED), * new InputOption('foo', 'f', InputOption::VALUE_REQUIRED), * )); * * @author Fabien Potencier * * @api */ class InputDefinition { private $arguments; private $requiredCount; private $hasAnArrayArgument = false; private $hasOptional; private $options; private $shortcuts; /** * Constructor. * * @param array $definition An array of InputArgument and InputOption instance * * @api */ public function __construct(array $definition = array()) { $this->setDefinition($definition); } /** * Sets the definition of the input. * * @param array $definition The definition array * * @api */ public function setDefinition(array $definition) { $arguments = array(); $options = array(); foreach ($definition as $item) { if ($item instanceof InputOption) { $options[] = $item; } else { $arguments[] = $item; } } $this->setArguments($arguments); $this->setOptions($options); } /** * Sets the InputArgument objects. * * @param InputArgument[] $arguments An array of InputArgument objects * * @api */ public function setArguments($arguments = array()) { $this->arguments = array(); $this->requiredCount = 0; $this->hasOptional = false; $this->hasAnArrayArgument = false; $this->addArguments($arguments); } /** * Adds an array of InputArgument objects. * * @param InputArgument[] $arguments An array of InputArgument objects * * @api */ public function addArguments($arguments = array()) { if (null !== $arguments) { foreach ($arguments as $argument) { $this->addArgument($argument); } } } /** * Adds an InputArgument object. * * @param InputArgument $argument An InputArgument object * * @throws \LogicException When incorrect argument is given * * @api */ public function addArgument(InputArgument $argument) { if (isset($this->arguments[$argument->getName()])) { throw new \LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName())); } if ($this->hasAnArrayArgument) { throw new \LogicException('Cannot add an argument after an array argument.'); } if ($argument->isRequired() && $this->hasOptional) { throw new \LogicException('Cannot add a required argument after an optional one.'); } if ($argument->isArray()) { $this->hasAnArrayArgument = true; } if ($argument->isRequired()) { ++$this->requiredCount; } else { $this->hasOptional = true; } $this->arguments[$argument->getName()] = $argument; } /** * Returns an InputArgument by name or by position. * * @param string|int $name The InputArgument name or position * * @return InputArgument An InputArgument object * * @throws \InvalidArgumentException When argument given doesn't exist * * @api */ public function getArgument($name) { if (!$this->hasArgument($name)) { throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); } $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; return $arguments[$name]; } /** * Returns true if an InputArgument object exists by name or position. * * @param string|int $name The InputArgument name or position * * @return bool true if the InputArgument object exists, false otherwise * * @api */ public function hasArgument($name) { $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; return isset($arguments[$name]); } /** * Gets the array of InputArgument objects. * * @return InputArgument[] An array of InputArgument objects * * @api */ public function getArguments() { return $this->arguments; } /** * Returns the number of InputArguments. * * @return int The number of InputArguments */ public function getArgumentCount() { return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments); } /** * Returns the number of required InputArguments. * * @return int The number of required InputArguments */ public function getArgumentRequiredCount() { return $this->requiredCount; } /** * Gets the default values. * * @return array An array of default values */ public function getArgumentDefaults() { $values = array(); foreach ($this->arguments as $argument) { $values[$argument->getName()] = $argument->getDefault(); } return $values; } /** * Sets the InputOption objects. * * @param InputOption[] $options An array of InputOption objects * * @api */ public function setOptions($options = array()) { $this->options = array(); $this->shortcuts = array(); $this->addOptions($options); } /** * Adds an array of InputOption objects. * * @param InputOption[] $options An array of InputOption objects * * @api */ public function addOptions($options = array()) { foreach ($options as $option) { $this->addOption($option); } } /** * Adds an InputOption object. * * @param InputOption $option An InputOption object * * @throws \LogicException When option given already exist * * @api */ public function addOption(InputOption $option) { if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) { throw new \LogicException(sprintf('An option named "%s" already exists.', $option->getName())); } if ($option->getShortcut()) { foreach (explode('|', $option->getShortcut()) as $shortcut) { if (isset($this->shortcuts[$shortcut]) && !$option->equals($this->options[$this->shortcuts[$shortcut]])) { throw new \LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut)); } } } $this->options[$option->getName()] = $option; if ($option->getShortcut()) { foreach (explode('|', $option->getShortcut()) as $shortcut) { $this->shortcuts[$shortcut] = $option->getName(); } } } /** * Returns an InputOption by name. * * @param string $name The InputOption name * * @return InputOption A InputOption object * * @throws \InvalidArgumentException When option given doesn't exist * * @api */ public function getOption($name) { if (!$this->hasOption($name)) { throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); } return $this->options[$name]; } /** * Returns true if an InputOption object exists by name. * * @param string $name The InputOption name * * @return bool true if the InputOption object exists, false otherwise * * @api */ public function hasOption($name) { return isset($this->options[$name]); } /** * Gets the array of InputOption objects. * * @return InputOption[] An array of InputOption objects * * @api */ public function getOptions() { return $this->options; } /** * Returns true if an InputOption object exists by shortcut. * * @param string $name The InputOption shortcut * * @return bool true if the InputOption object exists, false otherwise */ public function hasShortcut($name) { return isset($this->shortcuts[$name]); } /** * Gets an InputOption by shortcut. * * @param string $shortcut the Shortcut name * * @return InputOption An InputOption object */ public function getOptionForShortcut($shortcut) { return $this->getOption($this->shortcutToName($shortcut)); } /** * Gets an array of default values. * * @return array An array of all default values */ public function getOptionDefaults() { $values = array(); foreach ($this->options as $option) { $values[$option->getName()] = $option->getDefault(); } return $values; } /** * Returns the InputOption name given a shortcut. * * @param string $shortcut The shortcut * * @return string The InputOption name * * @throws \InvalidArgumentException When option given does not exist */ private function shortcutToName($shortcut) { if (!isset($this->shortcuts[$shortcut])) { throw new \InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); } return $this->shortcuts[$shortcut]; } /** * Gets the synopsis. * * @return string The synopsis */ public function getSynopsis() { $elements = array(); foreach ($this->getOptions() as $option) { $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : ''; $elements[] = sprintf('['.($option->isValueRequired() ? '%s--%s="..."' : ($option->isValueOptional() ? '%s--%s[="..."]' : '%s--%s')).']', $shortcut, $option->getName()); } foreach ($this->getArguments() as $argument) { $elements[] = sprintf($argument->isRequired() ? '%s' : '[%s]', $argument->getName().($argument->isArray() ? '1' : '')); if ($argument->isArray()) { $elements[] = sprintf('... [%sN]', $argument->getName()); } } return implode(' ', $elements); } /** * Returns a textual representation of the InputDefinition. * * @return string A string representing the InputDefinition * * @deprecated Deprecated since version 2.3, to be removed in 3.0. */ public function asText() { $descriptor = new TextDescriptor(); $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true); $descriptor->describe($output, $this, array('raw_output' => true)); return $output->fetch(); } /** * Returns an XML representation of the InputDefinition. * * @param bool $asDom Whether to return a DOM or an XML string * * @return string|\DOMDocument An XML string representing the InputDefinition * * @deprecated Deprecated since version 2.3, to be removed in 3.0. */ public function asXml($asDom = false) { $descriptor = new XmlDescriptor(); if ($asDom) { return $descriptor->getInputDefinitionDocument($this); } $output = new BufferedOutput(); $descriptor->describe($output, $this); return $output->fetch(); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; /** * InputAwareInterface should be implemented by classes that depends on the * Console Input. * * @author Wouter J */ interface InputAwareInterface { /** * Sets the Console Input. * * @param InputInterface */ public function setInput(InputInterface $input); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; /** * ArgvInput represents an input coming from the CLI arguments. * * Usage: * * $input = new ArgvInput(); * * By default, the `$_SERVER['argv']` array is used for the input values. * * This can be overridden by explicitly passing the input values in the constructor: * * $input = new ArgvInput($_SERVER['argv']); * * If you pass it yourself, don't forget that the first element of the array * is the name of the running application. * * When passing an argument to the constructor, be sure that it respects * the same rules as the argv one. It's almost always better to use the * `StringInput` when you want to provide your own input. * * @author Fabien Potencier * * @see http://www.gnu.org/software/libc/manual/html_node/Argument-Syntax.html * @see http://www.opengroup.org/onlinepubs/009695399/basedefs/xbd_chap12.html#tag_12_02 * * @api */ class ArgvInput extends Input { private $tokens; private $parsed; /** * Constructor. * * @param array $argv An array of parameters from the CLI (in the argv format) * @param InputDefinition $definition A InputDefinition instance * * @api */ public function __construct(array $argv = null, InputDefinition $definition = null) { if (null === $argv) { $argv = $_SERVER['argv']; } // strip the application name array_shift($argv); $this->tokens = $argv; parent::__construct($definition); } protected function setTokens(array $tokens) { $this->tokens = $tokens; } /** * Processes command line arguments. */ protected function parse() { $parseOptions = true; $this->parsed = $this->tokens; while (null !== $token = array_shift($this->parsed)) { if ($parseOptions && '' == $token) { $this->parseArgument($token); } elseif ($parseOptions && '--' == $token) { $parseOptions = false; } elseif ($parseOptions && 0 === strpos($token, '--')) { $this->parseLongOption($token); } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) { $this->parseShortOption($token); } else { $this->parseArgument($token); } } } /** * Parses a short option. * * @param string $token The current token. */ private function parseShortOption($token) { $name = substr($token, 1); if (strlen($name) > 1) { if ($this->definition->hasShortcut($name[0]) && $this->definition->getOptionForShortcut($name[0])->acceptValue()) { // an option with a value (with no space) $this->addShortOption($name[0], substr($name, 1)); } else { $this->parseShortOptionSet($name); } } else { $this->addShortOption($name, null); } } /** * Parses a short option set. * * @param string $name The current token * * @throws \RuntimeException When option given doesn't exist */ private function parseShortOptionSet($name) { $len = strlen($name); for ($i = 0; $i < $len; $i++) { if (!$this->definition->hasShortcut($name[$i])) { throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $name[$i])); } $option = $this->definition->getOptionForShortcut($name[$i]); if ($option->acceptValue()) { $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1)); break; } else { $this->addLongOption($option->getName(), null); } } } /** * Parses a long option. * * @param string $token The current token */ private function parseLongOption($token) { $name = substr($token, 2); if (false !== $pos = strpos($name, '=')) { $this->addLongOption(substr($name, 0, $pos), substr($name, $pos + 1)); } else { $this->addLongOption($name, null); } } /** * Parses an argument. * * @param string $token The current token * * @throws \RuntimeException When too many arguments are given */ private function parseArgument($token) { $c = count($this->arguments); // if input is expecting another argument, add it if ($this->definition->hasArgument($c)) { $arg = $this->definition->getArgument($c); $this->arguments[$arg->getName()] = $arg->isArray()? array($token) : $token; // if last argument isArray(), append token to last argument } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) { $arg = $this->definition->getArgument($c - 1); $this->arguments[$arg->getName()][] = $token; // unexpected argument } else { throw new \RuntimeException('Too many arguments.'); } } /** * Adds a short option value. * * @param string $shortcut The short option key * @param mixed $value The value for the option * * @throws \RuntimeException When option given doesn't exist */ private function addShortOption($shortcut, $value) { if (!$this->definition->hasShortcut($shortcut)) { throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut)); } $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); } /** * Adds a long option value. * * @param string $name The long option key * @param mixed $value The value for the option * * @throws \RuntimeException When option given doesn't exist */ private function addLongOption($name, $value) { if (!$this->definition->hasOption($name)) { throw new \RuntimeException(sprintf('The "--%s" option does not exist.', $name)); } $option = $this->definition->getOption($name); // Convert false values (from a previous call to substr()) to null if (false === $value) { $value = null; } if (null !== $value && !$option->acceptValue()) { throw new \RuntimeException(sprintf('The "--%s" option does not accept a value.', $name, $value)); } if (null === $value && $option->acceptValue() && count($this->parsed)) { // if option accepts an optional or mandatory argument // let's see if there is one provided $next = array_shift($this->parsed); if (isset($next[0]) && '-' !== $next[0]) { $value = $next; } elseif (empty($next)) { $value = ''; } else { array_unshift($this->parsed, $next); } } if (null === $value) { if ($option->isValueRequired()) { throw new \RuntimeException(sprintf('The "--%s" option requires a value.', $name)); } if (!$option->isArray()) { $value = $option->isValueOptional() ? $option->getDefault() : true; } } if ($option->isArray()) { $this->options[$name][] = $value; } else { $this->options[$name] = $value; } } /** * Returns the first argument from the raw parameters (not parsed). * * @return string The value of the first argument or null otherwise */ public function getFirstArgument() { foreach ($this->tokens as $token) { if ($token && '-' === $token[0]) { continue; } return $token; } } /** * Returns true if the raw parameters (not parsed) contain a value. * * This method is to be used to introspect the input parameters * before they have been validated. It must be used carefully. * * @param string|array $values The value(s) to look for in the raw parameters (can be an array) * * @return bool true if the value is contained in the raw parameters */ public function hasParameterOption($values) { $values = (array) $values; foreach ($this->tokens as $token) { foreach ($values as $value) { if ($token === $value || 0 === strpos($token, $value.'=')) { return true; } } } return false; } /** * Returns the value of a raw option (not parsed). * * This method is to be used to introspect the input parameters * before they have been validated. It must be used carefully. * * @param string|array $values The value(s) to look for in the raw parameters (can be an array) * @param mixed $default The default value to return if no result is found * * @return mixed The option value */ public function getParameterOption($values, $default = false) { $values = (array) $values; $tokens = $this->tokens; while ($token = array_shift($tokens)) { foreach ($values as $value) { if ($token === $value || 0 === strpos($token, $value.'=')) { if (false !== $pos = strpos($token, '=')) { return substr($token, $pos + 1); } return array_shift($tokens); } } } return $default; } /** * Returns a stringified representation of the args passed to the command * * @return string */ public function __toString() { $self = $this; $tokens = array_map(function ($token) use ($self) { if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { return $match[1] . $self->escapeToken($match[2]); } if ($token && $token[0] !== '-') { return $self->escapeToken($token); } return $token; }, $this->tokens); return implode(' ', $tokens); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; /** * StringInput represents an input provided as a string. * * Usage: * * $input = new StringInput('foo --bar="foobar"'); * * @author Fabien Potencier * * @api */ class StringInput extends ArgvInput { const REGEX_STRING = '([^\s]+?)(?:\s|(?setTokens($this->tokenize($input)); if (null !== $definition) { $this->bind($definition); } } /** * Tokenizes a string. * * @param string $input The input to tokenize * * @return array An array of tokens * * @throws \InvalidArgumentException When unable to parse input (should never happen) */ private function tokenize($input) { $tokens = array(); $length = strlen($input); $cursor = 0; while ($cursor < $length) { if (preg_match('/\s+/A', $input, $match, null, $cursor)) { } elseif (preg_match('/([^="\'\s]+?)(=?)('.self::REGEX_QUOTED_STRING.'+)/A', $input, $match, null, $cursor)) { $tokens[] = $match[1].$match[2].stripcslashes(str_replace(array('"\'', '\'"', '\'\'', '""'), '', substr($match[3], 1, strlen($match[3]) - 2))); } elseif (preg_match('/'.self::REGEX_QUOTED_STRING.'/A', $input, $match, null, $cursor)) { $tokens[] = stripcslashes(substr($match[0], 1, strlen($match[0]) - 2)); } elseif (preg_match('/'.self::REGEX_STRING.'/A', $input, $match, null, $cursor)) { $tokens[] = stripcslashes($match[1]); } else { // should never happen // @codeCoverageIgnoreStart throw new \InvalidArgumentException(sprintf('Unable to parse input near "... %s ..."', substr($input, $cursor, 10))); // @codeCoverageIgnoreEnd } $cursor += strlen($match[0]); } return $tokens; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; /** * InputInterface is the interface implemented by all input classes. * * @author Fabien Potencier */ interface InputInterface { /** * Returns the first argument from the raw parameters (not parsed). * * @return string The value of the first argument or null otherwise */ public function getFirstArgument(); /** * Returns true if the raw parameters (not parsed) contain a value. * * This method is to be used to introspect the input parameters * before they have been validated. It must be used carefully. * * @param string|array $values The values to look for in the raw parameters (can be an array) * * @return bool true if the value is contained in the raw parameters */ public function hasParameterOption($values); /** * Returns the value of a raw option (not parsed). * * This method is to be used to introspect the input parameters * before they have been validated. It must be used carefully. * * @param string|array $values The value(s) to look for in the raw parameters (can be an array) * @param mixed $default The default value to return if no result is found * * @return mixed The option value */ public function getParameterOption($values, $default = false); /** * Binds the current Input instance with the given arguments and options. * * @param InputDefinition $definition A InputDefinition instance */ public function bind(InputDefinition $definition); /** * Validates if arguments given are correct. * * Throws an exception when not enough arguments are given. * * @throws \RuntimeException */ public function validate(); /** * Returns all the given arguments merged with the default values. * * @return array */ public function getArguments(); /** * Gets argument by name. * * @param string $name The name of the argument * * @return mixed */ public function getArgument($name); /** * Sets an argument value by name. * * @param string $name The argument name * @param string $value The argument value * * @throws \InvalidArgumentException When argument given doesn't exist */ public function setArgument($name, $value); /** * Returns true if an InputArgument object exists by name or position. * * @param string|int $name The InputArgument name or position * * @return bool true if the InputArgument object exists, false otherwise */ public function hasArgument($name); /** * Returns all the given options merged with the default values. * * @return array */ public function getOptions(); /** * Gets an option by name. * * @param string $name The name of the option * * @return mixed */ public function getOption($name); /** * Sets an option value by name. * * @param string $name The option name * @param string|bool $value The option value * * @throws \InvalidArgumentException When option given doesn't exist */ public function setOption($name, $value); /** * Returns true if an InputOption object exists by name. * * @param string $name The InputOption name * * @return bool true if the InputOption object exists, false otherwise */ public function hasOption($name); /** * Is this input means interactive? * * @return bool */ public function isInteractive(); /** * Sets the input interactivity. * * @param bool $interactive If the input should be interactive */ public function setInteractive($interactive); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; /** * ArrayInput represents an input provided as an array. * * Usage: * * $input = new ArrayInput(array('name' => 'foo', '--bar' => 'foobar')); * * @author Fabien Potencier * * @api */ class ArrayInput extends Input { private $parameters; /** * Constructor. * * @param array $parameters An array of parameters * @param InputDefinition $definition A InputDefinition instance * * @api */ public function __construct(array $parameters, InputDefinition $definition = null) { $this->parameters = $parameters; parent::__construct($definition); } /** * Returns the first argument from the raw parameters (not parsed). * * @return string The value of the first argument or null otherwise */ public function getFirstArgument() { foreach ($this->parameters as $key => $value) { if ($key && '-' === $key[0]) { continue; } return $value; } } /** * Returns true if the raw parameters (not parsed) contain a value. * * This method is to be used to introspect the input parameters * before they have been validated. It must be used carefully. * * @param string|array $values The values to look for in the raw parameters (can be an array) * * @return bool true if the value is contained in the raw parameters */ public function hasParameterOption($values) { $values = (array) $values; foreach ($this->parameters as $k => $v) { if (!is_int($k)) { $v = $k; } if (in_array($v, $values)) { return true; } } return false; } /** * Returns the value of a raw option (not parsed). * * This method is to be used to introspect the input parameters * before they have been validated. It must be used carefully. * * @param string|array $values The value(s) to look for in the raw parameters (can be an array) * @param mixed $default The default value to return if no result is found * * @return mixed The option value */ public function getParameterOption($values, $default = false) { $values = (array) $values; foreach ($this->parameters as $k => $v) { if (is_int($k) && in_array($v, $values)) { return true; } elseif (in_array($k, $values)) { return $v; } } return $default; } /** * Returns a stringified representation of the args passed to the command * * @return string */ public function __toString() { $params = array(); foreach ($this->parameters as $param => $val) { if ($param && '-' === $param[0]) { $params[] = $param . ('' != $val ? '='.$this->escapeToken($val) : ''); } else { $params[] = $this->escapeToken($val); } } return implode(' ', $params); } /** * Processes command line arguments. */ protected function parse() { foreach ($this->parameters as $key => $value) { if (0 === strpos($key, '--')) { $this->addLongOption(substr($key, 2), $value); } elseif ('-' === $key[0]) { $this->addShortOption(substr($key, 1), $value); } else { $this->addArgument($key, $value); } } } /** * Adds a short option value. * * @param string $shortcut The short option key * @param mixed $value The value for the option * * @throws \InvalidArgumentException When option given doesn't exist */ private function addShortOption($shortcut, $value) { if (!$this->definition->hasShortcut($shortcut)) { throw new \InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); } $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); } /** * Adds a long option value. * * @param string $name The long option key * @param mixed $value The value for the option * * @throws \InvalidArgumentException When option given doesn't exist * @throws \InvalidArgumentException When a required value is missing */ private function addLongOption($name, $value) { if (!$this->definition->hasOption($name)) { throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); } $option = $this->definition->getOption($name); if (null === $value) { if ($option->isValueRequired()) { throw new \InvalidArgumentException(sprintf('The "--%s" option requires a value.', $name)); } $value = $option->isValueOptional() ? $option->getDefault() : true; } $this->options[$name] = $value; } /** * Adds an argument value. * * @param string $name The argument name * @param mixed $value The value for the argument * * @throws \InvalidArgumentException When argument given doesn't exist */ private function addArgument($name, $value) { if (!$this->definition->hasArgument($name)) { throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); } $this->arguments[$name] = $value; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Input; /** * Represents a command line argument. * * @author Fabien Potencier * * @api */ class InputArgument { const REQUIRED = 1; const OPTIONAL = 2; const IS_ARRAY = 4; private $name; private $mode; private $default; private $description; /** * Constructor. * * @param string $name The argument name * @param int $mode The argument mode: self::REQUIRED or self::OPTIONAL * @param string $description A description text * @param mixed $default The default value (for self::OPTIONAL mode only) * * @throws \InvalidArgumentException When argument mode is not valid * * @api */ public function __construct($name, $mode = null, $description = '', $default = null) { if (null === $mode) { $mode = self::OPTIONAL; } elseif (!is_int($mode) || $mode > 7 || $mode < 1) { throw new \InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); } $this->name = $name; $this->mode = $mode; $this->description = $description; $this->setDefault($default); } /** * Returns the argument name. * * @return string The argument name */ public function getName() { return $this->name; } /** * Returns true if the argument is required. * * @return bool true if parameter mode is self::REQUIRED, false otherwise */ public function isRequired() { return self::REQUIRED === (self::REQUIRED & $this->mode); } /** * Returns true if the argument can take multiple values. * * @return bool true if mode is self::IS_ARRAY, false otherwise */ public function isArray() { return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); } /** * Sets the default value. * * @param mixed $default The default value * * @throws \LogicException When incorrect default value is given */ public function setDefault($default = null) { if (self::REQUIRED === $this->mode && null !== $default) { throw new \LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.'); } if ($this->isArray()) { if (null === $default) { $default = array(); } elseif (!is_array($default)) { throw new \LogicException('A default value for an array argument must be an array.'); } } $this->default = $default; } /** * Returns the default value. * * @return mixed The default value */ public function getDefault() { return $this->default; } /** * Returns the description text. * * @return string The description text */ public function getDescription() { return $this->description; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Event; /** * Allows to do things before the command is executed. * * @author Fabien Potencier */ class ConsoleCommandEvent extends ConsoleEvent { } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Event; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * Allows to handle exception thrown in a command. * * @author Fabien Potencier */ class ConsoleExceptionEvent extends ConsoleEvent { private $exception; private $exitCode; public function __construct(Command $command, InputInterface $input, OutputInterface $output, \Exception $exception, $exitCode) { parent::__construct($command, $input, $output); $this->setException($exception); $this->exitCode = (int) $exitCode; } /** * Returns the thrown exception. * * @return \Exception The thrown exception */ public function getException() { return $this->exception; } /** * Replaces the thrown exception. * * This exception will be thrown if no response is set in the event. * * @param \Exception $exception The thrown exception */ public function setException(\Exception $exception) { $this->exception = $exception; } /** * Gets the exit code. * * @return int The command exit code */ public function getExitCode() { return $this->exitCode; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Event; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * Allows to manipulate the exit code of a command after its execution. * * @author Francesco Levorato */ class ConsoleTerminateEvent extends ConsoleEvent { /** * The exit code of the command. * * @var int */ private $exitCode; public function __construct(Command $command, InputInterface $input, OutputInterface $output, $exitCode) { parent::__construct($command, $input, $output); $this->setExitCode($exitCode); } /** * Sets the exit code. * * @param int $exitCode The command exit code */ public function setExitCode($exitCode) { $this->exitCode = (int) $exitCode; } /** * Gets the exit code. * * @return int The command exit code */ public function getExitCode() { return $this->exitCode; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Event; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\EventDispatcher\Event; /** * Allows to inspect input and output of a command. * * @author Francesco Levorato */ class ConsoleEvent extends Event { protected $command; private $input; private $output; public function __construct(Command $command, InputInterface $input, OutputInterface $output) { $this->command = $command; $this->input = $input; $this->output = $output; } /** * Gets the command that is executed. * * @return Command A Command instance */ public function getCommand() { return $this->command; } /** * Gets the input instance. * * @return InputInterface An InputInterface instance */ public function getInput() { return $this->input; } /** * Gets the output instance. * * @return OutputInterface An OutputInterface instance */ public function getOutput() { return $this->output; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Command; use Symfony\Component\Console\Helper\DescriptorHelper; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; /** * HelpCommand displays the help for a given command. * * @author Fabien Potencier */ class HelpCommand extends Command { private $command; /** * {@inheritdoc} */ protected function configure() { $this->ignoreValidationErrors(); $this ->setName('help') ->setDefinition(array( new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'), new InputOption('xml', null, InputOption::VALUE_NONE, 'To output help as XML'), new InputOption('format', null, InputOption::VALUE_REQUIRED, 'To output help in other formats', 'txt'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'), )) ->setDescription('Displays help for a command') ->setHelp(<<%command.name% command displays help for a given command: php %command.full_name% list You can also output the help in other formats by using the --format option: php %command.full_name% --format=xml list To display the list of available commands, please use the list command. EOF ) ; } /** * Sets the command * * @param Command $command The command to set */ public function setCommand(Command $command) { $this->command = $command; } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { if (null === $this->command) { $this->command = $this->getApplication()->find($input->getArgument('command_name')); } if ($input->getOption('xml')) { $input->setOption('format', 'xml'); } $helper = new DescriptorHelper(); $helper->describe($output, $this->command, array( 'format' => $input->getOption('format'), 'raw' => $input->getOption('raw'), )); $this->command = null; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Command; use Symfony\Component\Console\Descriptor\TextDescriptor; use Symfony\Component\Console\Descriptor\XmlDescriptor; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\BufferedOutput; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Application; use Symfony\Component\Console\Helper\HelperSet; /** * Base class for all commands. * * @author Fabien Potencier * * @api */ class Command { private $application; private $name; private $aliases = array(); private $definition; private $help; private $description; private $ignoreValidationErrors = false; private $applicationDefinitionMerged = false; private $applicationDefinitionMergedWithArgs = false; private $code; private $synopsis; private $helperSet; /** * Constructor. * * @param string|null $name The name of the command; passing null means it must be set in configure() * * @throws \LogicException When the command name is empty * * @api */ public function __construct($name = null) { $this->definition = new InputDefinition(); if (null !== $name) { $this->setName($name); } $this->configure(); if (!$this->name) { throw new \LogicException('The command name cannot be empty.'); } } /** * Ignores validation errors. * * This is mainly useful for the help command. */ public function ignoreValidationErrors() { $this->ignoreValidationErrors = true; } /** * Sets the application instance for this command. * * @param Application $application An Application instance * * @api */ public function setApplication(Application $application = null) { $this->application = $application; if ($application) { $this->setHelperSet($application->getHelperSet()); } else { $this->helperSet = null; } } /** * Sets the helper set. * * @param HelperSet $helperSet A HelperSet instance */ public function setHelperSet(HelperSet $helperSet) { $this->helperSet = $helperSet; } /** * Gets the helper set. * * @return HelperSet A HelperSet instance */ public function getHelperSet() { return $this->helperSet; } /** * Gets the application instance for this command. * * @return Application An Application instance * * @api */ public function getApplication() { return $this->application; } /** * Checks whether the command is enabled or not in the current environment * * Override this to check for x or y and return false if the command can not * run properly under the current conditions. * * @return bool */ public function isEnabled() { return true; } /** * Configures the current command. */ protected function configure() { } /** * Executes the current command. * * This method is not abstract because you can use this class * as a concrete class. In this case, instead of defining the * execute() method, you set the code to execute by passing * a Closure to the setCode() method. * * @param InputInterface $input An InputInterface instance * @param OutputInterface $output An OutputInterface instance * * @return null|int null or 0 if everything went fine, or an error code * * @throws \LogicException When this abstract method is not implemented * @see setCode() */ protected function execute(InputInterface $input, OutputInterface $output) { throw new \LogicException('You must override the execute() method in the concrete command class.'); } /** * Interacts with the user. * * @param InputInterface $input An InputInterface instance * @param OutputInterface $output An OutputInterface instance */ protected function interact(InputInterface $input, OutputInterface $output) { } /** * Initializes the command just after the input has been validated. * * This is mainly useful when a lot of commands extends one main command * where some things need to be initialized based on the input arguments and options. * * @param InputInterface $input An InputInterface instance * @param OutputInterface $output An OutputInterface instance */ protected function initialize(InputInterface $input, OutputInterface $output) { } /** * Runs the command. * * The code to execute is either defined directly with the * setCode() method or by overriding the execute() method * in a sub-class. * * @param InputInterface $input An InputInterface instance * @param OutputInterface $output An OutputInterface instance * * @return int The command exit code * * @throws \Exception * * @see setCode() * @see execute() * * @api */ public function run(InputInterface $input, OutputInterface $output) { // force the creation of the synopsis before the merge with the app definition $this->getSynopsis(); // add the application arguments and options $this->mergeApplicationDefinition(); // bind the input against the command specific arguments/options try { $input->bind($this->definition); } catch (\Exception $e) { if (!$this->ignoreValidationErrors) { throw $e; } } $this->initialize($input, $output); if ($input->isInteractive()) { $this->interact($input, $output); } $input->validate(); if ($this->code) { $statusCode = call_user_func($this->code, $input, $output); } else { $statusCode = $this->execute($input, $output); } return is_numeric($statusCode) ? (int) $statusCode : 0; } /** * Sets the code to execute when running this command. * * If this method is used, it overrides the code defined * in the execute() method. * * @param callable $code A callable(InputInterface $input, OutputInterface $output) * * @return Command The current instance * * @throws \InvalidArgumentException * * @see execute() * * @api */ public function setCode($code) { if (!is_callable($code)) { throw new \InvalidArgumentException('Invalid callable provided to Command::setCode.'); } $this->code = $code; return $this; } /** * Merges the application definition with the command definition. * * This method is not part of public API and should not be used directly. * * @param bool $mergeArgs Whether to merge or not the Application definition arguments to Command definition arguments */ public function mergeApplicationDefinition($mergeArgs = true) { if (null === $this->application || (true === $this->applicationDefinitionMerged && ($this->applicationDefinitionMergedWithArgs || !$mergeArgs))) { return; } if ($mergeArgs) { $currentArguments = $this->definition->getArguments(); $this->definition->setArguments($this->application->getDefinition()->getArguments()); $this->definition->addArguments($currentArguments); } $this->definition->addOptions($this->application->getDefinition()->getOptions()); $this->applicationDefinitionMerged = true; if ($mergeArgs) { $this->applicationDefinitionMergedWithArgs = true; } } /** * Sets an array of argument and option instances. * * @param array|InputDefinition $definition An array of argument and option instances or a definition instance * * @return Command The current instance * * @api */ public function setDefinition($definition) { if ($definition instanceof InputDefinition) { $this->definition = $definition; } else { $this->definition->setDefinition($definition); } $this->applicationDefinitionMerged = false; return $this; } /** * Gets the InputDefinition attached to this Command. * * @return InputDefinition An InputDefinition instance * * @api */ public function getDefinition() { return $this->definition; } /** * Gets the InputDefinition to be used to create XML and Text representations of this Command. * * Can be overridden to provide the original command representation when it would otherwise * be changed by merging with the application InputDefinition. * * This method is not part of public API and should not be used directly. * * @return InputDefinition An InputDefinition instance */ public function getNativeDefinition() { return $this->getDefinition(); } /** * Adds an argument. * * @param string $name The argument name * @param int $mode The argument mode: InputArgument::REQUIRED or InputArgument::OPTIONAL * @param string $description A description text * @param mixed $default The default value (for InputArgument::OPTIONAL mode only) * * @return Command The current instance * * @api */ public function addArgument($name, $mode = null, $description = '', $default = null) { $this->definition->addArgument(new InputArgument($name, $mode, $description, $default)); return $this; } /** * Adds an option. * * @param string $name The option name * @param string $shortcut The shortcut (can be null) * @param int $mode The option mode: One of the InputOption::VALUE_* constants * @param string $description A description text * @param mixed $default The default value (must be null for InputOption::VALUE_REQUIRED or InputOption::VALUE_NONE) * * @return Command The current instance * * @api */ public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null) { $this->definition->addOption(new InputOption($name, $shortcut, $mode, $description, $default)); return $this; } /** * Sets the name of the command. * * This method can set both the namespace and the name if * you separate them by a colon (:) * * $command->setName('foo:bar'); * * @param string $name The command name * * @return Command The current instance * * @throws \InvalidArgumentException When the name is invalid * * @api */ public function setName($name) { $this->validateName($name); $this->name = $name; return $this; } /** * Returns the command name. * * @return string The command name * * @api */ public function getName() { return $this->name; } /** * Sets the description for the command. * * @param string $description The description for the command * * @return Command The current instance * * @api */ public function setDescription($description) { $this->description = $description; return $this; } /** * Returns the description for the command. * * @return string The description for the command * * @api */ public function getDescription() { return $this->description; } /** * Sets the help for the command. * * @param string $help The help for the command * * @return Command The current instance * * @api */ public function setHelp($help) { $this->help = $help; return $this; } /** * Returns the help for the command. * * @return string The help for the command * * @api */ public function getHelp() { return $this->help; } /** * Returns the processed help for the command replacing the %command.name% and * %command.full_name% patterns with the real values dynamically. * * @return string The processed help for the command */ public function getProcessedHelp() { $name = $this->name; $placeholders = array( '%command.name%', '%command.full_name%' ); $replacements = array( $name, $_SERVER['PHP_SELF'].' '.$name ); return str_replace($placeholders, $replacements, $this->getHelp()); } /** * Sets the aliases for the command. * * @param array $aliases An array of aliases for the command * * @return Command The current instance * * @throws \InvalidArgumentException When an alias is invalid * * @api */ public function setAliases($aliases) { foreach ($aliases as $alias) { $this->validateName($alias); } $this->aliases = $aliases; return $this; } /** * Returns the aliases for the command. * * @return array An array of aliases for the command * * @api */ public function getAliases() { return $this->aliases; } /** * Returns the synopsis for the command. * * @return string The synopsis */ public function getSynopsis() { if (null === $this->synopsis) { $this->synopsis = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis())); } return $this->synopsis; } /** * Gets a helper instance by name. * * @param string $name The helper name * * @return mixed The helper value * * @throws \InvalidArgumentException if the helper is not defined * * @api */ public function getHelper($name) { return $this->helperSet->get($name); } /** * Returns a text representation of the command. * * @return string A string representing the command * * @deprecated Deprecated since version 2.3, to be removed in 3.0. */ public function asText() { $descriptor = new TextDescriptor(); $output = new BufferedOutput(BufferedOutput::VERBOSITY_NORMAL, true); $descriptor->describe($output, $this, array('raw_output' => true)); return $output->fetch(); } /** * Returns an XML representation of the command. * * @param bool $asDom Whether to return a DOM or an XML string * * @return string|\DOMDocument An XML string representing the command * * @deprecated Deprecated since version 2.3, to be removed in 3.0. */ public function asXml($asDom = false) { $descriptor = new XmlDescriptor(); if ($asDom) { return $descriptor->getCommandDocument($this); } $output = new BufferedOutput(); $descriptor->describe($output, $this); return $output->fetch(); } /** * Validates a command name. * * It must be non-empty and parts can optionally be separated by ":". * * @param string $name * * @throws \InvalidArgumentException When the name is invalid */ private function validateName($name) { if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { throw new \InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Command; use Symfony\Component\Console\Helper\DescriptorHelper; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\InputDefinition; /** * ListCommand displays the list of all available commands for the application. * * @author Fabien Potencier */ class ListCommand extends Command { /** * {@inheritdoc} */ protected function configure() { $this ->setName('list') ->setDefinition($this->createDefinition()) ->setDescription('Lists commands') ->setHelp(<<%command.name% command lists all commands: php %command.full_name% You can also display the commands for a specific namespace: php %command.full_name% test You can also output the information in other formats by using the --format option: php %command.full_name% --format=xml It's also possible to get raw list of commands (useful for embedding command runner): php %command.full_name% --raw EOF ) ; } /** * {@inheritdoc} */ public function getNativeDefinition() { return $this->createDefinition(); } /** * {@inheritdoc} */ protected function execute(InputInterface $input, OutputInterface $output) { if ($input->getOption('xml')) { $input->setOption('format', 'xml'); } $helper = new DescriptorHelper(); $helper->describe($output, $this->getApplication(), array( 'format' => $input->getOption('format'), 'raw_text' => $input->getOption('raw'), 'namespace' => $input->getArgument('namespace'), )); } /** * {@inheritdoc} */ private function createDefinition() { return new InputDefinition(array( new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'), new InputOption('xml', null, InputOption::VALUE_NONE, 'To output list as XML'), new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'), new InputOption('format', null, InputOption::VALUE_REQUIRED, 'To output list in other formats', 'txt'), )); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Descriptor; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; /** * Text descriptor. * * @author Jean-François Simon */ class TextDescriptor extends Descriptor { /** * {@inheritdoc} */ protected function describeInputArgument(InputArgument $argument, array $options = array()) { if (null !== $argument->getDefault() && (!is_array($argument->getDefault()) || count($argument->getDefault()))) { $default = sprintf(' (default: %s)', $this->formatDefaultValue($argument->getDefault())); } else { $default = ''; } $nameWidth = isset($options['name_width']) ? $options['name_width'] : strlen($argument->getName()); $this->writeText(sprintf(" %-${nameWidth}s %s%s", $argument->getName(), str_replace("\n", "\n".str_repeat(' ', $nameWidth + 2), $argument->getDescription()), $default ), $options); } /** * {@inheritdoc} */ protected function describeInputOption(InputOption $option, array $options = array()) { if ($option->acceptValue() && null !== $option->getDefault() && (!is_array($option->getDefault()) || count($option->getDefault()))) { $default = sprintf(' (default: %s)', $this->formatDefaultValue($option->getDefault())); } else { $default = ''; } $nameWidth = isset($options['name_width']) ? $options['name_width'] : strlen($option->getName()); $nameWithShortcutWidth = $nameWidth - strlen($option->getName()) - 2; $this->writeText(sprintf(" %s %-${nameWithShortcutWidth}s%s%s%s", '--'.$option->getName(), $option->getShortcut() ? sprintf('(-%s) ', $option->getShortcut()) : '', str_replace("\n", "\n".str_repeat(' ', $nameWidth + 2), $option->getDescription()), $default, $option->isArray() ? ' (multiple values allowed)' : '' ), $options); } /** * {@inheritdoc} */ protected function describeInputDefinition(InputDefinition $definition, array $options = array()) { $nameWidth = 0; foreach ($definition->getOptions() as $option) { $nameLength = strlen($option->getName()) + 2; if ($option->getShortcut()) { $nameLength += strlen($option->getShortcut()) + 3; } $nameWidth = max($nameWidth, $nameLength); } foreach ($definition->getArguments() as $argument) { $nameWidth = max($nameWidth, strlen($argument->getName())); } ++$nameWidth; if ($definition->getArguments()) { $this->writeText('Arguments:', $options); $this->writeText("\n"); foreach ($definition->getArguments() as $argument) { $this->describeInputArgument($argument, array_merge($options, array('name_width' => $nameWidth))); $this->writeText("\n"); } } if ($definition->getArguments() && $definition->getOptions()) { $this->writeText("\n"); } if ($definition->getOptions()) { $this->writeText('Options:', $options); $this->writeText("\n"); foreach ($definition->getOptions() as $option) { $this->describeInputOption($option, array_merge($options, array('name_width' => $nameWidth))); $this->writeText("\n"); } } } /** * {@inheritdoc} */ protected function describeCommand(Command $command, array $options = array()) { $command->getSynopsis(); $command->mergeApplicationDefinition(false); $this->writeText('Usage:', $options); $this->writeText("\n"); $this->writeText(' '.$command->getSynopsis(), $options); $this->writeText("\n"); if (count($command->getAliases()) > 0) { $this->writeText("\n"); $this->writeText('Aliases: '.implode(', ', $command->getAliases()).'', $options); } if ($definition = $command->getNativeDefinition()) { $this->writeText("\n"); $this->describeInputDefinition($definition, $options); } $this->writeText("\n"); if ($help = $command->getProcessedHelp()) { $this->writeText('Help:', $options); $this->writeText("\n"); $this->writeText(' '.str_replace("\n", "\n ", $help), $options); $this->writeText("\n"); } } /** * {@inheritdoc} */ protected function describeApplication(Application $application, array $options = array()) { $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; $description = new ApplicationDescription($application, $describedNamespace); if (isset($options['raw_text']) && $options['raw_text']) { $width = $this->getColumnWidth($description->getCommands()); foreach ($description->getCommands() as $command) { $this->writeText(sprintf("%-${width}s %s", $command->getName(), $command->getDescription()), $options); $this->writeText("\n"); } } else { $width = $this->getColumnWidth($description->getCommands()); $this->writeText($application->getHelp(), $options); $this->writeText("\n\n"); if ($describedNamespace) { $this->writeText(sprintf("Available commands for the \"%s\" namespace:", $describedNamespace), $options); } else { $this->writeText('Available commands:', $options); } // add commands by namespace foreach ($description->getNamespaces() as $namespace) { if (!$describedNamespace && ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { $this->writeText("\n"); $this->writeText(''.$namespace['id'].'', $options); } foreach ($namespace['commands'] as $name) { $this->writeText("\n"); $this->writeText(sprintf(" %-${width}s %s", $name, $description->getCommand($name)->getDescription()), $options); } } $this->writeText("\n"); } } /** * {@inheritdoc} */ private function writeText($content, array $options = array()) { $this->write( isset($options['raw_text']) && $options['raw_text'] ? strip_tags($content) : $content, isset($options['raw_output']) ? !$options['raw_output'] : true ); } /** * Formats input option/argument default value. * * @param mixed $default * * @return string */ private function formatDefaultValue($default) { if (version_compare(PHP_VERSION, '5.4', '<')) { return str_replace('\/', '/', json_encode($default)); } return json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); } /** * @param Command[] $commands * * @return int */ private function getColumnWidth(array $commands) { $width = 0; foreach ($commands as $command) { $width = strlen($command->getName()) > $width ? strlen($command->getName()) : $width; } return $width + 2; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Descriptor; use Symfony\Component\Console\Output\OutputInterface; /** * Descriptor interface. * * @author Jean-François Simon */ interface DescriptorInterface { /** * Describes an InputArgument instance. * * @param OutputInterface $output * @param object $object * @param array $options */ public function describe(OutputInterface $output, $object, array $options = array()); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Descriptor; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; /** * XML descriptor. * * @author Jean-François Simon */ class XmlDescriptor extends Descriptor { /** * @param InputDefinition $definition * * @return \DOMDocument */ public function getInputDefinitionDocument(InputDefinition $definition) { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($definitionXML = $dom->createElement('definition')); $definitionXML->appendChild($argumentsXML = $dom->createElement('arguments')); foreach ($definition->getArguments() as $argument) { $this->appendDocument($argumentsXML, $this->getInputArgumentDocument($argument)); } $definitionXML->appendChild($optionsXML = $dom->createElement('options')); foreach ($definition->getOptions() as $option) { $this->appendDocument($optionsXML, $this->getInputOptionDocument($option)); } return $dom; } /** * @param Command $command * * @return \DOMDocument */ public function getCommandDocument(Command $command) { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($commandXML = $dom->createElement('command')); $command->getSynopsis(); $command->mergeApplicationDefinition(false); $commandXML->setAttribute('id', $command->getName()); $commandXML->setAttribute('name', $command->getName()); $commandXML->appendChild($usageXML = $dom->createElement('usage')); $usageXML->appendChild($dom->createTextNode(sprintf($command->getSynopsis(), ''))); $commandXML->appendChild($descriptionXML = $dom->createElement('description')); $descriptionXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getDescription()))); $commandXML->appendChild($helpXML = $dom->createElement('help')); $helpXML->appendChild($dom->createTextNode(str_replace("\n", "\n ", $command->getProcessedHelp()))); $commandXML->appendChild($aliasesXML = $dom->createElement('aliases')); foreach ($command->getAliases() as $alias) { $aliasesXML->appendChild($aliasXML = $dom->createElement('alias')); $aliasXML->appendChild($dom->createTextNode($alias)); } $definitionXML = $this->getInputDefinitionDocument($command->getNativeDefinition()); $this->appendDocument($commandXML, $definitionXML->getElementsByTagName('definition')->item(0)); return $dom; } /** * @param Application $application * @param string|null $namespace * * @return \DOMDocument */ public function getApplicationDocument(Application $application, $namespace = null) { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($rootXml = $dom->createElement('symfony')); if ($application->getName() !== 'UNKNOWN') { $rootXml->setAttribute('name', $application->getName()); if ($application->getVersion() !== 'UNKNOWN') { $rootXml->setAttribute('version', $application->getVersion()); } } $rootXml->appendChild($commandsXML = $dom->createElement('commands')); $description = new ApplicationDescription($application, $namespace); if ($namespace) { $commandsXML->setAttribute('namespace', $namespace); } foreach ($description->getCommands() as $command) { $this->appendDocument($commandsXML, $this->getCommandDocument($command)); } if (!$namespace) { $rootXml->appendChild($namespacesXML = $dom->createElement('namespaces')); foreach ($description->getNamespaces() as $namespaceDescription) { $namespacesXML->appendChild($namespaceArrayXML = $dom->createElement('namespace')); $namespaceArrayXML->setAttribute('id', $namespaceDescription['id']); foreach ($namespaceDescription['commands'] as $name) { $namespaceArrayXML->appendChild($commandXML = $dom->createElement('command')); $commandXML->appendChild($dom->createTextNode($name)); } } } return $dom; } /** * {@inheritdoc} */ protected function describeInputArgument(InputArgument $argument, array $options = array()) { $this->writeDocument($this->getInputArgumentDocument($argument)); } /** * {@inheritdoc} */ protected function describeInputOption(InputOption $option, array $options = array()) { $this->writeDocument($this->getInputOptionDocument($option)); } /** * {@inheritdoc} */ protected function describeInputDefinition(InputDefinition $definition, array $options = array()) { $this->writeDocument($this->getInputDefinitionDocument($definition)); } /** * {@inheritdoc} */ protected function describeCommand(Command $command, array $options = array()) { $this->writeDocument($this->getCommandDocument($command)); } /** * {@inheritdoc} */ protected function describeApplication(Application $application, array $options = array()) { $this->writeDocument($this->getApplicationDocument($application, isset($options['namespace']) ? $options['namespace'] : null)); } /** * Appends document children to parent node. * * @param \DOMNode $parentNode * @param \DOMNode $importedParent */ private function appendDocument(\DOMNode $parentNode, \DOMNode $importedParent) { foreach ($importedParent->childNodes as $childNode) { $parentNode->appendChild($parentNode->ownerDocument->importNode($childNode, true)); } } /** * Writes DOM document. * * @param \DOMDocument $dom * * @return \DOMDocument|string */ private function writeDocument(\DOMDocument $dom) { $dom->formatOutput = true; $this->write($dom->saveXML()); } /** * @param InputArgument $argument * * @return \DOMDocument */ private function getInputArgumentDocument(InputArgument $argument) { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($objectXML = $dom->createElement('argument')); $objectXML->setAttribute('name', $argument->getName()); $objectXML->setAttribute('is_required', $argument->isRequired() ? 1 : 0); $objectXML->setAttribute('is_array', $argument->isArray() ? 1 : 0); $objectXML->appendChild($descriptionXML = $dom->createElement('description')); $descriptionXML->appendChild($dom->createTextNode($argument->getDescription())); $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); $defaults = is_array($argument->getDefault()) ? $argument->getDefault() : (is_bool($argument->getDefault()) ? array(var_export($argument->getDefault(), true)) : ($argument->getDefault() ? array($argument->getDefault()) : array())); foreach ($defaults as $default) { $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); $defaultXML->appendChild($dom->createTextNode($default)); } return $dom; } /** * @param InputOption $option * * @return \DOMDocument */ private function getInputOptionDocument(InputOption $option) { $dom = new \DOMDocument('1.0', 'UTF-8'); $dom->appendChild($objectXML = $dom->createElement('option')); $objectXML->setAttribute('name', '--'.$option->getName()); $pos = strpos($option->getShortcut(), '|'); if (false !== $pos) { $objectXML->setAttribute('shortcut', '-'.substr($option->getShortcut(), 0, $pos)); $objectXML->setAttribute('shortcuts', '-'.implode('|-', explode('|', $option->getShortcut()))); } else { $objectXML->setAttribute('shortcut', $option->getShortcut() ? '-'.$option->getShortcut() : ''); } $objectXML->setAttribute('accept_value', $option->acceptValue() ? 1 : 0); $objectXML->setAttribute('is_value_required', $option->isValueRequired() ? 1 : 0); $objectXML->setAttribute('is_multiple', $option->isArray() ? 1 : 0); $objectXML->appendChild($descriptionXML = $dom->createElement('description')); $descriptionXML->appendChild($dom->createTextNode($option->getDescription())); if ($option->acceptValue()) { $defaults = is_array($option->getDefault()) ? $option->getDefault() : (is_bool($option->getDefault()) ? array(var_export($option->getDefault(), true)) : ($option->getDefault() ? array($option->getDefault()) : array())); $objectXML->appendChild($defaultsXML = $dom->createElement('defaults')); if (!empty($defaults)) { foreach ($defaults as $default) { $defaultsXML->appendChild($defaultXML = $dom->createElement('default')); $defaultXML->appendChild($dom->createTextNode($default)); } } } return $dom; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Descriptor; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; /** * @author Jean-François Simon */ abstract class Descriptor implements DescriptorInterface { /** * @var OutputInterface */ private $output; /** * {@inheritdoc} */ public function describe(OutputInterface $output, $object, array $options = array()) { $this->output = $output; switch (true) { case $object instanceof InputArgument: $this->describeInputArgument($object, $options); break; case $object instanceof InputOption: $this->describeInputOption($object, $options); break; case $object instanceof InputDefinition: $this->describeInputDefinition($object, $options); break; case $object instanceof Command: $this->describeCommand($object, $options); break; case $object instanceof Application: $this->describeApplication($object, $options); break; default: throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object))); } } /** * Writes content to output. * * @param string $content * @param bool $decorated */ protected function write($content, $decorated = false) { $this->output->write($content, false, $decorated ? OutputInterface::OUTPUT_NORMAL : OutputInterface::OUTPUT_RAW); } /** * Describes an InputArgument instance. * * @param InputArgument $argument * @param array $options * * @return string|mixed */ abstract protected function describeInputArgument(InputArgument $argument, array $options = array()); /** * Describes an InputOption instance. * * @param InputOption $option * @param array $options * * @return string|mixed */ abstract protected function describeInputOption(InputOption $option, array $options = array()); /** * Describes an InputDefinition instance. * * @param InputDefinition $definition * @param array $options * * @return string|mixed */ abstract protected function describeInputDefinition(InputDefinition $definition, array $options = array()); /** * Describes a Command instance. * * @param Command $command * @param array $options * * @return string|mixed */ abstract protected function describeCommand(Command $command, array $options = array()); /** * Describes an Application instance. * * @param Application $application * @param array $options * * @return string|mixed */ abstract protected function describeApplication(Application $application, array $options = array()); } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Descriptor; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; /** * Markdown descriptor. * * @author Jean-François Simon */ class MarkdownDescriptor extends Descriptor { /** * {@inheritdoc} */ protected function describeInputArgument(InputArgument $argument, array $options = array()) { $this->write( '**'.$argument->getName().':**'."\n\n" .'* Name: '.($argument->getName() ?: '')."\n" .'* Is required: '.($argument->isRequired() ? 'yes' : 'no')."\n" .'* Is array: '.($argument->isArray() ? 'yes' : 'no')."\n" .'* Description: '.($argument->getDescription() ?: '')."\n" .'* Default: `'.str_replace("\n", '', var_export($argument->getDefault(), true)).'`' ); } /** * {@inheritdoc} */ protected function describeInputOption(InputOption $option, array $options = array()) { $this->write( '**'.$option->getName().':**'."\n\n" .'* Name: `--'.$option->getName().'`'."\n" .'* Shortcut: '.($option->getShortcut() ? '`-'.implode('|-', explode('|', $option->getShortcut())).'`' : '')."\n" .'* Accept value: '.($option->acceptValue() ? 'yes' : 'no')."\n" .'* Is value required: '.($option->isValueRequired() ? 'yes' : 'no')."\n" .'* Is multiple: '.($option->isArray() ? 'yes' : 'no')."\n" .'* Description: '.($option->getDescription() ?: '')."\n" .'* Default: `'.str_replace("\n", '', var_export($option->getDefault(), true)).'`' ); } /** * {@inheritdoc} */ protected function describeInputDefinition(InputDefinition $definition, array $options = array()) { if ($showArguments = count($definition->getArguments()) > 0) { $this->write('### Arguments:'); foreach ($definition->getArguments() as $argument) { $this->write("\n\n"); $this->write($this->describeInputArgument($argument)); } } if (count($definition->getOptions()) > 0) { if ($showArguments) { $this->write("\n\n"); } $this->write('### Options:'); foreach ($definition->getOptions() as $option) { $this->write("\n\n"); $this->write($this->describeInputOption($option)); } } } /** * {@inheritdoc} */ protected function describeCommand(Command $command, array $options = array()) { $command->getSynopsis(); $command->mergeApplicationDefinition(false); $this->write( $command->getName()."\n" .str_repeat('-', strlen($command->getName()))."\n\n" .'* Description: '.($command->getDescription() ?: '')."\n" .'* Usage: `'.$command->getSynopsis().'`'."\n" .'* Aliases: '.(count($command->getAliases()) ? '`'.implode('`, `', $command->getAliases()).'`' : '') ); if ($help = $command->getProcessedHelp()) { $this->write("\n\n"); $this->write($help); } if ($definition = $command->getNativeDefinition()) { $this->write("\n\n"); $this->describeInputDefinition($command->getNativeDefinition()); } } /** * {@inheritdoc} */ protected function describeApplication(Application $application, array $options = array()) { $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; $description = new ApplicationDescription($application, $describedNamespace); $this->write($application->getName()."\n".str_repeat('=', strlen($application->getName()))); foreach ($description->getNamespaces() as $namespace) { if (ApplicationDescription::GLOBAL_NAMESPACE !== $namespace['id']) { $this->write("\n\n"); $this->write('**'.$namespace['id'].':**'); } $this->write("\n\n"); $this->write(implode("\n", array_map(function ($commandName) { return '* '.$commandName; } , $namespace['commands']))); } foreach ($description->getCommands() as $command) { $this->write("\n\n"); $this->write($this->describeCommand($command)); } } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Descriptor; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; /** * @author Jean-François Simon */ class ApplicationDescription { const GLOBAL_NAMESPACE = '_global'; /** * @var Application */ private $application; /** * @var null|string */ private $namespace; /** * @var array */ private $namespaces; /** * @var Command[] */ private $commands; /** * @var Command[] */ private $aliases; /** * Constructor. * * @param Application $application * @param string|null $namespace */ public function __construct(Application $application, $namespace = null) { $this->application = $application; $this->namespace = $namespace; } /** * @return array */ public function getNamespaces() { if (null === $this->namespaces) { $this->inspectApplication(); } return $this->namespaces; } /** * @return Command[] */ public function getCommands() { if (null === $this->commands) { $this->inspectApplication(); } return $this->commands; } /** * @param string $name * * @return Command * * @throws \InvalidArgumentException */ public function getCommand($name) { if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) { throw new \InvalidArgumentException(sprintf('Command %s does not exist.', $name)); } return isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name]; } private function inspectApplication() { $this->commands = array(); $this->namespaces = array(); $all = $this->application->all($this->namespace ? $this->application->findNamespace($this->namespace) : null); foreach ($this->sortCommands($all) as $namespace => $commands) { $names = array(); /** @var Command $command */ foreach ($commands as $name => $command) { if (!$command->getName()) { continue; } if ($command->getName() === $name) { $this->commands[$name] = $command; } else { $this->aliases[$name] = $command; } $names[] = $name; } $this->namespaces[$namespace] = array('id' => $namespace, 'commands' => $names); } } /** * @param array $commands * * @return array */ private function sortCommands(array $commands) { $namespacedCommands = array(); foreach ($commands as $name => $command) { $key = $this->application->extractNamespace($name, 1); if (!$key) { $key = '_global'; } $namespacedCommands[$key][$name] = $command; } ksort($namespacedCommands); foreach ($namespacedCommands as &$commands) { ksort($commands); } return $namespacedCommands; } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console\Descriptor; use Symfony\Component\Console\Application; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputDefinition; use Symfony\Component\Console\Input\InputOption; /** * JSON descriptor. * * @author Jean-François Simon */ class JsonDescriptor extends Descriptor { /** * {@inheritdoc} */ protected function describeInputArgument(InputArgument $argument, array $options = array()) { $this->writeData($this->getInputArgumentData($argument), $options); } /** * {@inheritdoc} */ protected function describeInputOption(InputOption $option, array $options = array()) { $this->writeData($this->getInputOptionData($option), $options); } /** * {@inheritdoc} */ protected function describeInputDefinition(InputDefinition $definition, array $options = array()) { $this->writeData($this->getInputDefinitionData($definition), $options); } /** * {@inheritdoc} */ protected function describeCommand(Command $command, array $options = array()) { $this->writeData($this->getCommandData($command), $options); } /** * {@inheritdoc} */ protected function describeApplication(Application $application, array $options = array()) { $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; $description = new ApplicationDescription($application, $describedNamespace); $commands = array(); foreach ($description->getCommands() as $command) { $commands[] = $this->getCommandData($command); } $data = $describedNamespace ? array('commands' => $commands, 'namespace' => $describedNamespace) : array('commands' => $commands, 'namespaces' => array_values($description->getNamespaces())); $this->writeData($data, $options); } /** * Writes data as json. * * @param array $data * @param array $options * * @return array|string */ private function writeData(array $data, array $options) { $this->write(json_encode($data, isset($options['json_encoding']) ? $options['json_encoding'] : 0)); } /** * @param InputArgument $argument * * @return array */ private function getInputArgumentData(InputArgument $argument) { return array( 'name' => $argument->getName(), 'is_required' => $argument->isRequired(), 'is_array' => $argument->isArray(), 'description' => $argument->getDescription(), 'default' => $argument->getDefault(), ); } /** * @param InputOption $option * * @return array */ private function getInputOptionData(InputOption $option) { return array( 'name' => '--'.$option->getName(), 'shortcut' => $option->getShortcut() ? '-'.implode('|-', explode('|', $option->getShortcut())) : '', 'accept_value' => $option->acceptValue(), 'is_value_required' => $option->isValueRequired(), 'is_multiple' => $option->isArray(), 'description' => $option->getDescription(), 'default' => $option->getDefault(), ); } /** * @param InputDefinition $definition * * @return array */ private function getInputDefinitionData(InputDefinition $definition) { $inputArguments = array(); foreach ($definition->getArguments() as $name => $argument) { $inputArguments[$name] = $this->getInputArgumentData($argument); } $inputOptions = array(); foreach ($definition->getOptions() as $name => $option) { $inputOptions[$name] = $this->getInputOptionData($option); } return array('arguments' => $inputArguments, 'options' => $inputOptions); } /** * @param Command $command * * @return array */ private function getCommandData(Command $command) { $command->getSynopsis(); $command->mergeApplicationDefinition(false); return array( 'name' => $command->getName(), 'usage' => $command->getSynopsis(), 'description' => $command->getDescription(), 'help' => $command->getProcessedHelp(), 'aliases' => $command->getAliases(), 'definition' => $this->getInputDefinitionData($command->getNativeDefinition()), ); } } * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Symfony\Component\Console; use Symfony\Component\Console\Input\StringInput; use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\Process\ProcessBuilder; use Symfony\Component\Process\PhpExecutableFinder; /** * A Shell wraps an Application to add shell capabilities to it. * * Support for history and completion only works with a PHP compiled * with readline support (either --with-readline or --with-libedit) * * @author Fabien Potencier * @author Martin Hasoň */ class Shell { private $application; private $history; private $output; private $hasReadline; private $processIsolation = false; /** * Constructor. * * If there is no readline support for the current PHP executable * a \RuntimeException exception is thrown. * * @param Application $application An application instance */ public function __construct(Application $application) { $this->hasReadline = function_exists('readline'); $this->application = $application; $this->history = getenv('HOME').'/.history_'.$application->getName(); $this->output = new ConsoleOutput(); } /** * Runs the shell. */ public function run() { $this->application->setAutoExit(false); $this->application->setCatchExceptions(true); if ($this->hasReadline) { readline_read_history($this->history); readline_completion_function(array($this, 'autocompleter')); } $this->output->writeln($this->getHeader()); $php = null; if ($this->processIsolation) { $finder = new PhpExecutableFinder(); $php = $finder->find(); $this->output->writeln(<<Running with process isolation, you should consider this: * each command is executed as separate process, * commands don't support interactivity, all params must be passed explicitly, * commands output is not colorized. EOF ); } while (true) { $command = $this->readline(); if (false === $command) { $this->output->writeln("\n"); break; } if ($this->hasReadline) { readline_add_history($command); readline_write_history($this->history); } if ($this->processIsolation) { $pb = new ProcessBuilder(); $process = $pb ->add($php) ->add($_SERVER['argv'][0]) ->add($command) ->inheritEnvironmentVariables(true) ->getProcess() ; $output = $this->output; $process->run(function ($type, $data) use ($output) { $output->writeln($data); }); $ret = $process->getExitCode(); } else { $ret = $this->application->run(new StringInput($command), $this->output); } if (0 !== $ret) { $this->output->writeln(sprintf('The command terminated with an error status (%s)', $ret)); } } } /** * Returns the shell header. * * @return string The header string */ protected function getHeader() { return <<{$this->application->getName()} shell ({$this->application->getVersion()}). At the prompt, type help for some help, or list to get a list of available commands. To exit the shell, type ^D. EOF; } /** * Renders a prompt. * * @return string The prompt */ protected function getPrompt() { // using the formatter here is required when using readline return $this->output->getFormatter()->format($this->application->getName().' > '); } protected function getOutput() { return $this->output; } protected function getApplication() { return $this->application; } /** * Tries to return autocompletion for the current entered text. * * @param string $text The last segment of the entered text * * @return bool|array A list of guessed strings or true */ private function autocompleter($text) { $info = readline_info(); $text = substr($info['line_buffer'], 0, $info['end']); if ($info['point'] !== $info['end']) { return true; } // task name? if (false === strpos($text, ' ') || !$text) { return array_keys($this->application->all()); } // options and arguments? try { $command = $this->application->find(substr($text, 0, strpos($text, ' '))); } catch (\Exception $e) { return true; } $list = array('--help'); foreach ($command->getDefinition()->getOptions() as $option) { $list[] = '--'.$option->getName(); } return $list; } /** * Reads a single line from standard input. * * @return string The single line from standard input */ private function readline() { if ($this->hasReadline) { $line = readline($this->getPrompt()); } else { $this->output->write($this->getPrompt()); $line = fgets(STDIN, 1024); $line = (!$line && strlen($line) == 0) ? false : rtrim($line); } return $line; } public function getProcessIsolation() { return $this->processIsolation; } public function setProcessIsolation($processIsolation) { $this->processIsolation = (bool) $processIsolation; if ($this->processIsolation && !class_exists('Symfony\\Component\\Process\\Process')) { throw new \RuntimeException('Unable to isolate processes as the Symfony Process Component is not installed.'); } } } . * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * * Neither the name of Sebastian Bergmann nor the names of his * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @author Sebastian Bergmann * @copyright 2012-2014 Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://www.phpunit.de/ * @since File available since Release 1.0.0 */ namespace SebastianBergmann\PHPUnit\SkeletonGenerator; /** * Generator for test class skeletons from classes. * * @author Sebastian Bergmann * @copyright 2012-2014 Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://www.phpunit.de/ * @since Class available since Release 1.0.0 */ class TestGenerator extends AbstractGenerator { /** * @var array */ protected $methodNameCounter = array(); /** * Constructor. * * @param string $inClassName * @param string $inSourceFile * @param string $outClassName * @param string $outSourceFile * @throws \RuntimeException */ public function __construct($inClassName, $inSourceFile = '', $outClassName = '', $outSourceFile = '') { if (class_exists($inClassName)) { $reflector = new \ReflectionClass($inClassName); $inSourceFile = $reflector->getFileName(); if ($inSourceFile === false) { $inSourceFile = ''; } unset($reflector); } else { if (empty($inSourceFile)) { $possibleFilenames = array( $inClassName . '.php', str_replace( array('_', '\\'), DIRECTORY_SEPARATOR, $inClassName ) . '.php' ); foreach ($possibleFilenames as $possibleFilename) { if (is_file($possibleFilename)) { $inSourceFile = $possibleFilename; } } } if (empty($inSourceFile)) { throw new \RuntimeException( sprintf( 'Neither "%s" nor "%s" could be opened.', $possibleFilenames[0], $possibleFilenames[1] ) ); } if (!is_file($inSourceFile)) { throw new \RuntimeException( sprintf( '"%s" could not be opened.', $inSourceFile ) ); } $inSourceFile = realpath($inSourceFile); include_once $inSourceFile; if (!class_exists($inClassName)) { throw new \RuntimeException( sprintf( 'Could not find class "%s" in "%s".', $inClassName, $inSourceFile ) ); } } if (empty($outClassName)) { $outClassName = $inClassName . 'Test'; } if (empty($outSourceFile)) { $outSourceFile = dirname($inSourceFile) . DIRECTORY_SEPARATOR . $outClassName . '.php'; } parent::__construct( $inClassName, $inSourceFile, $outClassName, $outSourceFile ); } /** * @return string */ public function generate() { $class = new \ReflectionClass( $this->inClassName['fullyQualifiedClassName'] ); $methods = ''; $incompleteMethods = ''; foreach ($class->getMethods() as $method) { if (!$method->isConstructor() && !$method->isAbstract() && $method->isPublic() && $method->getDeclaringClass()->getName() == $this->inClassName['fullyQualifiedClassName']) { $assertAnnotationFound = false; if (preg_match_all('/@assert(.*)$/Um', $method->getDocComment(), $annotations)) { foreach ($annotations[1] as $annotation) { if (preg_match('/\((.*)\)\s+([^\s]*)\s+(.*)/', $annotation, $matches)) { switch ($matches[2]) { case '==': $assertion = 'Equals'; break; case '!=': $assertion = 'NotEquals'; break; case '===': $assertion = 'Same'; break; case '!==': $assertion = 'NotSame'; break; case '>': $assertion = 'GreaterThan'; break; case '>=': $assertion = 'GreaterThanOrEqual'; break; case '<': $assertion = 'LessThan'; break; case '<=': $assertion = 'LessThanOrEqual'; break; case 'throws': $assertion = 'exception'; break; default: throw new \RuntimeException( sprintf( 'Token "%s" could not be parsed in @assert annotation.', $matches[2] ) ); } if ($assertion == 'exception') { $template = 'TestMethodException'; } elseif ($assertion == 'Equals' && strtolower($matches[3]) == 'true') { $assertion = 'True'; $template = 'TestMethodBool'; } elseif ($assertion == 'NotEquals' && strtolower($matches[3]) == 'true') { $assertion = 'False'; $template = 'TestMethodBool'; } elseif ($assertion == 'Equals' && strtolower($matches[3]) == 'false') { $assertion = 'False'; $template = 'TestMethodBool'; } elseif ($assertion == 'NotEquals' && strtolower($matches[3]) == 'false') { $assertion = 'True'; $template = 'TestMethodBool'; } else { $template = 'TestMethod'; } if ($method->isStatic()) { $template .= 'Static'; } $methodTemplate = new \Text_Template( sprintf( '%s%stemplate%s%s.tpl', __DIR__, DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR, $template ) ); $origMethodName = $method->getName(); $methodName = ucfirst($origMethodName); if (isset($this->methodNameCounter[$methodName])) { $this->methodNameCounter[$methodName]++; } else { $this->methodNameCounter[$methodName] = 1; } if ($this->methodNameCounter[$methodName] > 1) { $methodName .= $this->methodNameCounter[$methodName]; } $methodTemplate->setVar( array( 'annotation' => trim($annotation), 'arguments' => $matches[1], 'assertion' => isset($assertion) ? $assertion : '', 'expected' => $matches[3], 'origMethodName' => $origMethodName, 'className' => $this->inClassName['fullyQualifiedClassName'], 'methodName' => $methodName ) ); $methods .= $methodTemplate->render(); $assertAnnotationFound = true; } } } if (!$assertAnnotationFound) { $methodTemplate = new \Text_Template( sprintf( '%s%stemplate%sIncompleteTestMethod.tpl', __DIR__, DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR ) ); $methodTemplate->setVar( array( 'className' => $this->inClassName['fullyQualifiedClassName'], 'methodName' => ucfirst($method->getName()), 'origMethodName' => $method->getName() ) ); $incompleteMethods .= $methodTemplate->render(); } } } $classTemplate = new \Text_Template( sprintf( '%s%stemplate%sTestClass.tpl', __DIR__, DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR ) ); if ($this->outClassName['namespace'] != '') { $namespace = "\nnamespace " . $this->outClassName['namespace'] . ";\n"; } else { $namespace = ''; } $classTemplate->setVar( array( 'namespace' => $namespace, 'namespaceSeparator' => !empty($namespace) ? '\\' : '', 'className' => $this->inClassName['className'], 'testClassName' => $this->outClassName['className'], 'methods' => $methods . $incompleteMethods, 'date' => date('Y-m-d'), 'time' => date('H:i:s') ) ); return $classTemplate->render(); } } . * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * * Neither the name of Sebastian Bergmann nor the names of his * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @author Sebastian Bergmann * @copyright 2012-2014 Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://www.phpunit.de/ * @since File available since Release 1.0.0 */ namespace SebastianBergmann\PHPUnit\SkeletonGenerator; /** * Generator for skeletons. * * @author Sebastian Bergmann * @copyright 2012-2014 Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://www.phpunit.de/ * @since Class available since Release 1.0.0 */ abstract class AbstractGenerator { /** * @var array */ protected $inClassName; /** * @var string */ protected $inSourceFile; /** * @var array */ protected $outClassName; /** * @var string */ protected $outSourceFile; /** * Constructor. * * @param string $inClassName * @param string $inSourceFile * @param string $outClassName * @param string $outSourceFile */ public function __construct($inClassName, $inSourceFile = '', $outClassName = '', $outSourceFile = '') { $this->inClassName = $this->parseFullyQualifiedClassName( $inClassName ); $this->outClassName = $this->parseFullyQualifiedClassName( $outClassName ); $this->inSourceFile = str_replace( $this->inClassName['fullyQualifiedClassName'], $this->inClassName['className'], $inSourceFile ); $this->outSourceFile = str_replace( $this->outClassName['fullyQualifiedClassName'], $this->outClassName['className'], $outSourceFile ); } /** * @return string */ public function getOutClassName() { return $this->outClassName['fullyQualifiedClassName']; } /** * @return string */ public function getOutSourceFile() { return $this->outSourceFile; } /** * Generates the code and writes it to a source file. * * @param string $file */ public function write($file = '') { if ($file == '') { $file = $this->outSourceFile; } file_put_contents($file, $this->generate()); } /** * @param string $className * @return array */ protected function parseFullyQualifiedClassName($className) { $result = array( 'namespace' => '', 'className' => $className, 'fullyQualifiedClassName' => $className ); if (strpos($className, '\\') !== false) { $tmp = explode('\\', $className); $result['className'] = $tmp[count($tmp)-1]; $result['namespace'] = $this->arrayToName($tmp); } return $result; } /** * @param array $parts * @return string */ protected function arrayToName(array $parts) { $result = ''; if (count($parts) > 1) { array_pop($parts); $result = join('\\', $parts); } return $result; } /** * @return string */ abstract public function generate(); } . * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * * Neither the name of Sebastian Bergmann nor the names of his * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @author Sebastian Bergmann * @copyright 2012-2014 Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @since File available since Release 2.0.0 */ namespace SebastianBergmann\PHPUnit\SkeletonGenerator\CLI; use SebastianBergmann\Version; use Symfony\Component\Console\Application as AbstractApplication; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Input\ArrayInput; /** * TextUI frontend for PHPUnit Skeleton Generator. * * @author Sebastian Bergmann * @copyright 2009-2014 Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://github.com/sebastianbergmann/phpunit-skeleton-generator/tree * @since Class available since Release 2.0.0 */ class Application extends AbstractApplication { public function __construct() { $version = new Version('2.0.1', dirname(dirname(__DIR__))); parent::__construct('phpunit-skelgen', $version->getVersion()); $this->add(new GenerateClassCommand); $this->add(new GenerateTestCommand); } /** * Runs the current application. * * @param InputInterface $input An Input instance * @param OutputInterface $output An Output instance * * @return integer 0 if everything went fine, or an error code */ public function doRun(InputInterface $input, OutputInterface $output) { if (!$input->hasParameterOption('--quiet')) { $output->write( sprintf( "phpunit-skelgen %s by Sebastian Bergmann.\n\n", $this->getVersion() ) ); } if ($input->hasParameterOption('--version') || $input->hasParameterOption('-V')) { exit; } parent::doRun($input, $output); } } . * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * * Neither the name of Sebastian Bergmann nor the names of his * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @author Sebastian Bergmann * @copyright 2012-2014 Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @since File available since Release 2.0.0 */ namespace SebastianBergmann\PHPUnit\SkeletonGenerator\CLI; use SebastianBergmann\PHPUnit\SkeletonGenerator\AbstractGenerator; use SebastianBergmann\PHPUnit\SkeletonGenerator\TestGenerator; 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; /** * @author Sebastian Bergmann * @copyright 2012-2014 Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://github.com/sebastianbergmann/phpunit-skeleton-generator/tree * @since Class available since Release 2.0.0 */ class GenerateTestCommand extends BaseCommand { /** * Configures the current command. */ protected function configure() { $this->setName('generate-test') ->setDescription('Generate a test class based on a class') ->addArgument( 'class', InputArgument::REQUIRED, 'The name of the class to generate a test class for' ) ->addArgument( 'class-source', InputArgument::OPTIONAL, 'The source file that declared the class to generate a test class for' ) ->addArgument( 'test-class', InputArgument::OPTIONAL, 'The name of the test class that is to be generated' ) ->addArgument( 'test-source', InputArgument::OPTIONAL, 'The file to which the generated test code is to be written' ); parent::configure(); } /** * @param InputInterface $input An InputInterface instance * @return AbstractGenerator */ protected function getGenerator(InputInterface $input) { return new TestGenerator( (string)$input->getArgument('class'), (string)$input->getArgument('class-source'), (string)$input->getArgument('test-class'), (string)$input->getArgument('test-source') ); } } . * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * * Neither the name of Sebastian Bergmann nor the names of his * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @author Sebastian Bergmann * @copyright 2012-2014 Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @since File available since Release 2.0.0 */ namespace SebastianBergmann\PHPUnit\SkeletonGenerator\CLI; use SebastianBergmann\PHPUnit\SkeletonGenerator\AbstractGenerator; 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; /** * @author Sebastian Bergmann * @copyright 2012-2014 Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://github.com/sebastianbergmann/phpunit-skeleton-generator/tree * @since Class available since Release 2.0.0 */ abstract class BaseCommand extends Command { /** * Configures the current command. */ protected function configure() { $this->addOption( 'bootstrap', null, InputOption::VALUE_REQUIRED, 'A "bootstrap" PHP file that is run at startup' ); } /** * Executes the current command. * * @param InputInterface $input An InputInterface instance * @param OutputInterface $output An OutputInterface instance * * @return null|integer null or 0 if everything went fine, or an error code */ protected function execute(InputInterface $input, OutputInterface $output) { if ($input->getOption('bootstrap') && file_exists($input->getOption('bootstrap'))) { include $input->getOption('bootstrap'); } $generator = $this->getGenerator($input); $generator->write(); $output->writeln( sprintf( 'Wrote skeleton for "%s" to "%s".', $generator->getOutClassName(), $generator->getOutSourceFile() ) ); } /** * @param InputInterface $input An InputInterface instance * @return AbstractGenerator */ abstract protected function getGenerator(InputInterface $input); } . * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * * Neither the name of Sebastian Bergmann nor the names of his * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @author Sebastian Bergmann * @copyright 2012-2014 Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @since File available since Release 2.0.0 */ namespace SebastianBergmann\PHPUnit\SkeletonGenerator\CLI; use SebastianBergmann\PHPUnit\SkeletonGenerator\AbstractGenerator; use SebastianBergmann\PHPUnit\SkeletonGenerator\ClassGenerator; 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; /** * @author Sebastian Bergmann * @copyright 2012-2014 Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://github.com/sebastianbergmann/phpunit-skeleton-generator/tree * @since Class available since Release 2.0.0 */ class GenerateClassCommand extends BaseCommand { /** * Configures the current command. */ protected function configure() { $this->setName('generate-class') ->setDescription('Generate a class based on a test class') ->addArgument( 'test-class', InputArgument::REQUIRED, 'The name of the test class that is to be used as a template' ) ->addArgument( 'test-source', InputArgument::OPTIONAL, 'The source file that contains the test class' ) ->addArgument( 'class', InputArgument::OPTIONAL, 'The name of the class to be generated' ) ->addArgument( 'class-source', InputArgument::OPTIONAL, 'The file to which the generated code is to be written' ); parent::configure(); } /** * @param InputInterface $input An InputInterface instance * @return AbstractGenerator */ protected function getGenerator(InputInterface $input) { return new ClassGenerator( (string)$input->getArgument('test-class'), (string)$input->getArgument('test-source'), (string)$input->getArgument('class'), (string)$input->getArgument('class-source') ); } } . * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * * Neither the name of Sebastian Bergmann nor the names of his * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * * @author Sebastian Bergmann * @copyright 2012-2014 Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://www.phpunit.de/ * @since File available since Release 1.0.0 */ namespace SebastianBergmann\PHPUnit\SkeletonGenerator; /** * Generator for class skeletons from test classes. * * @author Sebastian Bergmann * @copyright 2012-2014 Sebastian Bergmann * @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License * @link http://www.phpunit.de/ * @since Class available since Release 1.0.0 */ class ClassGenerator extends AbstractGenerator { /** * Constructor. * * @param string $inClassName * @param string $inSourceFile * @param string $outClassName * @param string $outSourceFile * @throws \RuntimeException */ public function __construct($inClassName, $inSourceFile = '', $outClassName = '', $outSourceFile = '') { if (empty($inSourceFile)) { $inSourceFile = $inClassName . '.php'; } if (!is_file($inSourceFile)) { throw new \RuntimeException( sprintf( '"%s" could not be opened.', $inSourceFile ) ); } if (empty($outClassName)) { $outClassName = substr($inClassName, 0, strlen($inClassName) - 4); } if (empty($outSourceFile)) { $outSourceFile = dirname($inSourceFile) . DIRECTORY_SEPARATOR . $outClassName . '.php'; } parent::__construct( $inClassName, $inSourceFile, $outClassName, $outSourceFile ); } /** * @return string */ public function generate() { $methods = ''; foreach ($this->findTestedMethods() as $method) { $methodTemplate = new \Text_Template( sprintf( '%s%stemplate%sMethod.tpl', __DIR__, DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR ) ); $methodTemplate->setVar( array('methodName' => $method) ); $methods .= $methodTemplate->render(); } $classTemplate = new \Text_Template( sprintf( '%s%stemplate%sClass.tpl', __DIR__, DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR ) ); $classTemplate->setVar( array( 'className' => $this->outClassName['fullyQualifiedClassName'], 'methods' => $methods, 'date' => date('Y-m-d'), 'time' => date('H:i:s') ) ); return $classTemplate->render(); } /** * Returns the methods of the class under test * that are called from the test methods. * * @return array */ protected function findTestedMethods() { $setUpVariables = array(); $testedMethods = array(); $classes = $this->getClassesInFile($this->inSourceFile); $testMethods = $classes[$this->inClassName['fullyQualifiedClassName']]['methods']; unset($classes); foreach ($testMethods as $name => $testMethod) { if (strtolower($name) == 'setup') { $setUpVariables = $this->findVariablesThatReferenceClass( $testMethod['tokens'] ); break; } } foreach ($testMethods as $name => $testMethod) { $argVariables = array(); if (strtolower($name) == 'setup') { continue; } $start = strpos($testMethod['signature'], '(') + 1; $end = strlen($testMethod['signature']) - $start - 1; $args = substr($testMethod['signature'], $start, $end); foreach (explode(',', $args) as $arg) { $arg = explode(' ', trim($arg)); if (count($arg) == 2) { $type = $arg[0]; $var = $arg[1]; } else { $type = null; $var = $arg[0]; } if ($type == $this->outClassName['fullyQualifiedClassName']) { $argVariables[] = $var; } } $variables = array_unique( array_merge( $setUpVariables, $argVariables, $this->findVariablesThatReferenceClass($testMethod['tokens']) ) ); foreach ($testMethod['tokens'] as $i => $token) { if (is_array($token) && $token[0] == T_DOUBLE_COLON && is_array($testMethod['tokens'][$i-1]) && $testMethod['tokens'][$i-1][0] == T_STRING && $testMethod['tokens'][$i-1][1] == $this->outClassName['fullyQualifiedClassName'] && is_array($testMethod['tokens'][$i+1]) && $testMethod['tokens'][$i+1][0] == T_STRING && $testMethod['tokens'][$i+2] == '(') { // Class::method() $testedMethods[] = $testMethod['tokens'][$i+1][1]; } elseif (is_array($token) && $token[0] == T_OBJECT_OPERATOR && in_array($this->findVariableName($testMethod['tokens'], $i), $variables) && is_array($testMethod['tokens'][$i+2]) && $testMethod['tokens'][$i+2][0] == T_OBJECT_OPERATOR && is_array($testMethod['tokens'][$i+3]) && $testMethod['tokens'][$i+3][0] == T_STRING && $testMethod['tokens'][$i+4] == '(') { // $this->object->method() $testedMethods[] = $testMethod['tokens'][$i+3][1]; } elseif (is_array($token) && $token[0] == T_OBJECT_OPERATOR && in_array($this->findVariableName($testMethod['tokens'], $i), $variables) && is_array($testMethod['tokens'][$i+1]) && $testMethod['tokens'][$i+1][0] == T_STRING && $testMethod['tokens'][$i+2] == '(') { // $object->method() $testedMethods[] = $testMethod['tokens'][$i+1][1]; } } } $testedMethods = array_unique($testedMethods); sort($testedMethods); return $testedMethods; } /** * Returns the variables used in test methods * that reference the class under test. * * @param array $tokens * @return array */ protected function findVariablesThatReferenceClass(array $tokens) { $inNew = false; $variables = array(); foreach ($tokens as $i => $token) { if (is_string($token)) { if (trim($token) == ';') { $inNew = false; } continue; } list ($token, $value) = $token; switch ($token) { case T_NEW: $inNew = true; break; case T_STRING: if ($inNew) { if ($value == $this->outClassName['fullyQualifiedClassName']) { $variables[] = $this->findVariableName($tokens, $i); } } $inNew = false; break; } } return $variables; } /** * Finds the variable name of the object for the method call * that is currently being processed. * * @param array $tokens * @param integer $start * @return mixed */ protected function findVariableName(array $tokens, $start) { for ($i = $start - 1; $i >= 0; $i--) { if (is_array($tokens[$i]) && $tokens[$i][0] == T_VARIABLE) { $variable = $tokens[$i][1]; if (is_array($tokens[$i+1]) && $tokens[$i+1][0] == T_OBJECT_OPERATOR && $tokens[$i+2] != '(' && $tokens[$i+3] != '(') { $variable .= '->' . $tokens[$i+2][1]; } return $variable; } } return false; } /** * @param string $filename * @return array */ protected function getClassesInFile($filename) { $result = array(); $tokens = token_get_all( file_get_contents($filename) ); $numTokens = count($tokens); $blocks = array(); $line = 1; $currentBlock = false; $currentNamespace = false; $currentClass = false; $currentFunction = false; $currentFunctionStartLine = false; $currentFunctionTokens = array(); $currentDocComment = false; $currentSignature = false; $currentSignatureStartToken = false; for ($i = 0; $i < $numTokens; $i++) { if ($currentFunction !== false) { $currentFunctionTokens[] = $tokens[$i]; } if (is_string($tokens[$i])) { if ($tokens[$i] == '{') { if ($currentBlock == T_CLASS) { $block = $currentClass; } elseif ($currentBlock == T_FUNCTION) { $currentSignature = ''; for ($j = $currentSignatureStartToken; $j < $i; $j++) { if (is_string($tokens[$j])) { $currentSignature .= $tokens[$j]; } else { $currentSignature .= $tokens[$j][1]; } } $currentSignature = trim($currentSignature); $block = $currentFunction; $currentSignatureStartToken = false; } else { $block = false; } array_push($blocks, $block); $currentBlock = false; } elseif ($tokens[$i] == '}') { $block = array_pop($blocks); if ($block !== false && $block !== null) { if ($block == $currentFunction) { if ($currentDocComment !== false) { $docComment = $currentDocComment; $currentDocComment = false; } else { $docComment = ''; } $tmp = array( 'docComment' => $docComment, 'signature' => $currentSignature, 'startLine' => $currentFunctionStartLine, 'endLine' => $line, 'tokens' => $currentFunctionTokens ); if ($currentClass !== false) { $result[$currentClass]['methods'][$currentFunction] = $tmp; } $currentFunction = false; $currentFunctionStartLine = false; $currentFunctionTokens = array(); $currentSignature = false; } elseif ($block == $currentClass) { $result[$currentClass]['endLine'] = $line; $currentClass = false; $currentClassStartLine = false; } } } continue; } switch ($tokens[$i][0]) { case T_HALT_COMPILER: return; break; case T_NAMESPACE: $currentNamespace = $tokens[$i+2][1]; for ($j = $i+3; $j < $numTokens; $j += 2) { if ($tokens[$j][0] == T_NS_SEPARATOR) { $currentNamespace .= '\\' . $tokens[$j+1][1]; } else { break; } } break; case T_CURLY_OPEN: $currentBlock = T_CURLY_OPEN; array_push($blocks, $currentBlock); break; case T_DOLLAR_OPEN_CURLY_BRACES: $currentBlock = T_DOLLAR_OPEN_CURLY_BRACES; array_push($blocks, $currentBlock); break; case T_CLASS: $currentBlock = T_CLASS; if ($currentNamespace === false) { $currentClass = $tokens[$i+2][1]; } else { $currentClass = $currentNamespace . '\\' . $tokens[$i+2][1]; } if ($currentDocComment !== false) { $docComment = $currentDocComment; $currentDocComment = false; } else { $docComment = ''; } $result[$currentClass] = array( 'methods' => array(), 'docComment' => $docComment, 'startLine' => $line ); break; case T_FUNCTION: if (!((is_array($tokens[$i+2]) && $tokens[$i+2][0] == T_STRING) || (is_string($tokens[$i+2]) && $tokens[$i+2] == '&' && is_array($tokens[$i+3]) && $tokens[$i+3][0] == T_STRING))) { continue; } $currentBlock = T_FUNCTION; $currentFunctionStartLine = $line; $done = false; $currentSignatureStartToken = $i - 1; do { switch ($tokens[$currentSignatureStartToken][0]) { case T_ABSTRACT: case T_FINAL: case T_PRIVATE: case T_PUBLIC: case T_PROTECTED: case T_STATIC: case T_WHITESPACE: $currentSignatureStartToken--; break; default: $currentSignatureStartToken++; $done = true; } } while (!$done); if (isset($tokens[$i+2][1])) { $functionName = $tokens[$i+2][1]; } elseif (isset($tokens[$i+3][1])) { $functionName = $tokens[$i+3][1]; } if ($currentNamespace === false) { $currentFunction = $functionName; } else { $currentFunction = $currentNamespace . '\\' . $functionName; } break; case T_DOC_COMMENT: $currentDocComment = $tokens[$i][1]; break; } $line += substr_count($tokens[$i][1], "\n"); } return $result; } } /** * @todo Implement {methodName}(). */ public function {methodName}() { // Remove the following line when you implement this method. throw new RuntimeException('Not yet implemented.'); } /** * @covers {className}::{origMethodName} * @todo Implement test{methodName}(). */ public function test{methodName}() { // Remove the following lines when you implement this test. $this->markTestIncomplete( 'This test has not been implemented yet.' ); } /** * Generated from @assert {annotation}. * * @covers {className}::{origMethodName} * @expectedException {expected} */ public function test{methodName}() { {className}::{origMethodName}({arguments}); } /** * Generated from @assert {annotation}. * * @covers {className}::{origMethodName} * @expectedException {expected} */ public function test{methodName}() { $this->object->{origMethodName}({arguments}); } /** * Generated from @assert {annotation}. * * @covers {className}::{origMethodName} */ public function test{methodName}() { $this->assert{assertion}( {className}::{origMethodName}({arguments}) ); } /** * Generated from @assert {annotation}. * * @covers {className}::{origMethodName} */ public function test{methodName}() { $this->assert{assertion}( {expected}, $this->object->{origMethodName}({arguments}) ); } /** * Generated from @assert {annotation}. * * @covers {className}::{origMethodName} */ public function test{methodName}() { $this->assert{assertion}( $this->object->{origMethodName}({arguments}) ); } object = new {className}; } /** * Tears down the fixture, for example, closes a network connection. * This method is called after a test is executed. */ protected function tearDown() { } {methods}} /** * Generated from @assert {annotation}. * * @covers {className}::{origMethodName} */ public function test{methodName}() { $this->assert{assertion}( {expected}, {className}::{origMethodName}({arguments}) ); } phpunit/phpunit-skeleton-generator: 2.0.1 phpunit/php-text-template: 1.2.0 sebastian/version: 1.0.3 symfony/console: v2.4.4 'pU+i XU{lGBMB