X-Git-Url: http://www.aleph1.co.uk/gitweb/?a=blobdiff_plain;f=vendor%2Fconsolidation%2Fannotated-command%2Fsrc%2FParser%2FInternal%2FBespokeDocBlockParser.php;fp=vendor%2Fconsolidation%2Fannotated-command%2Fsrc%2FParser%2FInternal%2FBespokeDocBlockParser.php;h=b53545c6216316a6bfd03adb952c7a24cf9a718a;hb=9917807b03b64faf00f6a1f29dcb6eafc454efa5;hp=0000000000000000000000000000000000000000;hpb=aea91e65e895364e460983b890e295aa5d5540a5;p=yaffs-website diff --git a/vendor/consolidation/annotated-command/src/Parser/Internal/BespokeDocBlockParser.php b/vendor/consolidation/annotated-command/src/Parser/Internal/BespokeDocBlockParser.php new file mode 100644 index 000000000..b53545c62 --- /dev/null +++ b/vendor/consolidation/annotated-command/src/Parser/Internal/BespokeDocBlockParser.php @@ -0,0 +1,322 @@ + 'processCommandTag', + 'name' => 'processCommandTag', + 'arg' => 'processArgumentTag', + 'param' => 'processArgumentTag', + 'return' => 'processReturnTag', + 'option' => 'processOptionTag', + 'default' => 'processDefaultTag', + 'aliases' => 'processAliases', + 'usage' => 'processUsageTag', + 'description' => 'processAlternateDescriptionTag', + 'desc' => 'processAlternateDescriptionTag', + ]; + + public function __construct(CommandInfo $commandInfo, \ReflectionMethod $reflection, $fqcnCache = null) + { + $this->commandInfo = $commandInfo; + $this->reflection = $reflection; + $this->fqcnCache = $fqcnCache ?: new FullyQualifiedClassCache(); + } + + /** + * Parse the docBlock comment for this command, and set the + * fields of this class with the data thereby obtained. + */ + public function parse() + { + $doc = $this->reflection->getDocComment(); + $this->parseDocBlock($doc); + } + + /** + * Save any tag that we do not explicitly recognize in the + * 'otherAnnotations' map. + */ + protected function processGenericTag($tag) + { + $this->commandInfo->addAnnotation($tag->getTag(), $tag->getContent()); + } + + /** + * Set the name of the command from a @command or @name annotation. + */ + protected function processCommandTag($tag) + { + if (!$tag->hasWordAndDescription($matches)) { + throw new \Exception('Could not determine command name from tag ' . (string)$tag); + } + $commandName = $matches['word']; + $this->commandInfo->setName($commandName); + // We also store the name in the 'other annotations' so that is is + // possible to determine if the method had a @command annotation. + $this->commandInfo->addAnnotation($tag->getTag(), $commandName); + } + + /** + * The @description and @desc annotations may be used in + * place of the synopsis (which we call 'description'). + * This is discouraged. + * + * @deprecated + */ + protected function processAlternateDescriptionTag($tag) + { + $this->commandInfo->setDescription($tag->getContent()); + } + + /** + * Store the data from a @arg annotation in our argument descriptions. + */ + protected function processArgumentTag($tag) + { + if (!$tag->hasVariable($matches)) { + throw new \Exception('Could not determine argument name from tag ' . (string)$tag); + } + if ($matches['variable'] == $this->optionParamName()) { + return; + } + $this->addOptionOrArgumentTag($tag, $this->commandInfo->arguments(), $matches['variable'], $matches['description']); + } + + /** + * Store the data from an @option annotation in our option descriptions. + */ + protected function processOptionTag($tag) + { + if (!$tag->hasVariable($matches)) { + throw new \Exception('Could not determine option name from tag ' . (string)$tag); + } + $this->addOptionOrArgumentTag($tag, $this->commandInfo->options(), $matches['variable'], $matches['description']); + } + + protected function addOptionOrArgumentTag($tag, DefaultsWithDescriptions $set, $name, $description) + { + $variableName = $this->commandInfo->findMatchingOption($name); + $description = static::removeLineBreaks($description); + $set->add($variableName, $description); + } + + /** + * Store the data from a @default annotation in our argument or option store, + * as appropriate. + */ + protected function processDefaultTag($tag) + { + if (!$tag->hasVariable($matches)) { + throw new \Exception('Could not determine parameter name for default value from tag ' . (string)$tag); + } + $variableName = $matches['variable']; + $defaultValue = $this->interpretDefaultValue($matches['description']); + if ($this->commandInfo->arguments()->exists($variableName)) { + $this->commandInfo->arguments()->setDefaultValue($variableName, $defaultValue); + return; + } + $variableName = $this->commandInfo->findMatchingOption($variableName); + if ($this->commandInfo->options()->exists($variableName)) { + $this->commandInfo->options()->setDefaultValue($variableName, $defaultValue); + } + } + + /** + * Store the data from a @usage annotation in our example usage list. + */ + protected function processUsageTag($tag) + { + $lines = explode("\n", $tag->getContent()); + $usage = trim(array_shift($lines)); + $description = static::removeLineBreaks(implode("\n", array_map(function ($line) { + return trim($line); + }, $lines))); + + $this->commandInfo->setExampleUsage($usage, $description); + } + + /** + * Process the comma-separated list of aliases + */ + protected function processAliases($tag) + { + $this->commandInfo->setAliases((string)$tag->getContent()); + } + + /** + * Store the data from a @return annotation in our argument descriptions. + */ + protected function processReturnTag($tag) + { + // The return type might be a variable -- '$this'. It will + // usually be a type, like RowsOfFields, or \Namespace\RowsOfFields. + if (!$tag->hasVariableAndDescription($matches)) { + throw new \Exception('Could not determine return type from tag ' . (string)$tag); + } + // Look at namespace and `use` statments to make returnType a fqdn + $returnType = $matches['variable']; + $returnType = $this->findFullyQualifiedClass($returnType); + $this->commandInfo->setReturnType($returnType); + } + + protected function findFullyQualifiedClass($className) + { + if (strpos($className, '\\') !== false) { + return $className; + } + + return $this->fqcnCache->qualify($this->reflection->getFileName(), $className); + } + + private function parseDocBlock($doc) + { + // Remove the leading /** and the trailing */ + $doc = preg_replace('#^\s*/\*+\s*#', '', $doc); + $doc = preg_replace('#\s*\*+/\s*#', '', $doc); + + // Nothing left? Exit. + if (empty($doc)) { + return; + } + + $tagFactory = new TagFactory(); + $lines = []; + + foreach (explode("\n", $doc) as $row) { + // Remove trailing whitespace and leading space + '*'s + $row = rtrim($row); + $row = preg_replace('#^[ \t]*\**#', '', $row); + + if (!$tagFactory->parseLine($row)) { + $lines[] = $row; + } + } + + $this->processDescriptionAndHelp($lines); + $this->processAllTags($tagFactory->getTags()); + } + + protected function processDescriptionAndHelp($lines) + { + // Trim all of the lines individually. + $lines = + array_map( + function ($line) { + return trim($line); + }, + $lines + ); + + // Everything up to the first blank line goes in the description. + $description = array_shift($lines); + while ($this->nextLineIsNotEmpty($lines)) { + $description .= ' ' . array_shift($lines); + } + + // Everything else goes in the help. + $help = trim(implode("\n", $lines)); + + $this->commandInfo->setDescription($description); + $this->commandInfo->setHelp($help); + } + + protected function nextLineIsNotEmpty($lines) + { + if (empty($lines)) { + return false; + } + + $nextLine = trim($lines[0]); + return !empty($nextLine); + } + + protected function processAllTags($tags) + { + // Iterate over all of the tags, and process them as necessary. + foreach ($tags as $tag) { + $processFn = [$this, 'processGenericTag']; + if (array_key_exists($tag->getTag(), $this->tagProcessors)) { + $processFn = [$this, $this->tagProcessors[$tag->getTag()]]; + } + $processFn($tag); + } + } + + protected function lastParameterName() + { + $params = $this->commandInfo->getParameters(); + $param = end($params); + if (!$param) { + return ''; + } + return $param->name; + } + + /** + * Return the name of the last parameter if it holds the options. + */ + public function optionParamName() + { + // Remember the name of the last parameter, if it holds the options. + // We will use this information to ignore @param annotations for the options. + if (!isset($this->optionParamName)) { + $this->optionParamName = ''; + $options = $this->commandInfo->options(); + if (!$options->isEmpty()) { + $this->optionParamName = $this->lastParameterName(); + } + } + + return $this->optionParamName; + } + + protected function interpretDefaultValue($defaultValue) + { + $defaults = [ + 'null' => null, + 'true' => true, + 'false' => false, + "''" => '', + '[]' => [], + ]; + foreach ($defaults as $defaultName => $defaultTypedValue) { + if ($defaultValue == $defaultName) { + return $defaultTypedValue; + } + } + return $defaultValue; + } + + /** + * Given a list that might be 'a b c' or 'a, b, c' or 'a,b,c', + * convert the data into the last of these forms. + */ + protected static function convertListToCommaSeparated($text) + { + return preg_replace('#[ \t\n\r,]+#', ',', $text); + } + + /** + * Take a multiline description and convert it into a single + * long unbroken line. + */ + protected static function removeLineBreaks($text) + { + return trim(preg_replace('#[ \t\n\r]+#', ' ', $text)); + } +}