+++ /dev/null
-<?php
-
-/*
- * This file is part of Psy Shell.
- *
- * (c) 2012-2017 Justin Hileman
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Psy\Util;
-
-/**
- * A docblock representation.
- *
- * Based on PHP-DocBlock-Parser by Paul Scott:
- *
- * {@link http://www.github.com/icio/PHP-DocBlock-Parser}
- *
- * @author Paul Scott <paul@duedil.com>
- * @author Justin Hileman <justin@justinhileman.info>
- */
-class Docblock
-{
- /**
- * Tags in the docblock that have a whitespace-delimited number of parameters
- * (such as `@param type var desc` and `@return type desc`) and the names of
- * those parameters.
- *
- * @var array
- */
- public static $vectors = array(
- 'throws' => array('type', 'desc'),
- 'param' => array('type', 'var', 'desc'),
- 'return' => array('type', 'desc'),
- );
-
- protected $reflector;
-
- /**
- * The description of the symbol.
- *
- * @var string
- */
- public $desc;
-
- /**
- * The tags defined in the docblock.
- *
- * The array has keys which are the tag names (excluding the @) and values
- * that are arrays, each of which is an entry for the tag.
- *
- * In the case where the tag name is defined in {@see DocBlock::$vectors} the
- * value within the tag-value array is an array in itself with keys as
- * described by {@see DocBlock::$vectors}.
- *
- * @var array
- */
- public $tags;
-
- /**
- * The entire DocBlock comment that was parsed.
- *
- * @var string
- */
- public $comment;
-
- /**
- * Docblock constructor.
- *
- * @param \Reflector $reflector
- */
- public function __construct(\Reflector $reflector)
- {
- $this->reflector = $reflector;
- $this->setComment($reflector->getDocComment());
- }
-
- /**
- * Set and parse the docblock comment.
- *
- * @param string $comment The docblock
- */
- protected function setComment($comment)
- {
- $this->desc = '';
- $this->tags = array();
- $this->comment = $comment;
-
- $this->parseComment($comment);
- }
-
- /**
- * Find the length of the docblock prefix.
- *
- * @param array $lines
- *
- * @return int Prefix length
- */
- protected static function prefixLength(array $lines)
- {
- // find only lines with interesting things
- $lines = array_filter($lines, function ($line) {
- return substr($line, strspn($line, "* \t\n\r\0\x0B"));
- });
-
- // if we sort the lines, we only have to compare two items
- sort($lines);
-
- $first = reset($lines);
- $last = end($lines);
-
- // find the longest common substring
- $count = min(strlen($first), strlen($last));
- for ($i = 0; $i < $count; $i++) {
- if ($first[$i] !== $last[$i]) {
- return $i;
- }
- }
-
- return $count;
- }
-
- /**
- * Parse the comment into the component parts and set the state of the object.
- *
- * @param string $comment The docblock
- */
- protected function parseComment($comment)
- {
- // Strip the opening and closing tags of the docblock
- $comment = substr($comment, 3, -2);
-
- // Split into arrays of lines
- $comment = array_filter(preg_split('/\r?\n\r?/', $comment));
-
- // Trim asterisks and whitespace from the beginning and whitespace from the end of lines
- $prefixLength = self::prefixLength($comment);
- $comment = array_map(function ($line) use ($prefixLength) {
- return rtrim(substr($line, $prefixLength));
- }, $comment);
-
- // Group the lines together by @tags
- $blocks = array();
- $b = -1;
- foreach ($comment as $line) {
- if (self::isTagged($line)) {
- $b++;
- $blocks[] = array();
- } elseif ($b === -1) {
- $b = 0;
- $blocks[] = array();
- }
- $blocks[$b][] = $line;
- }
-
- // Parse the blocks
- foreach ($blocks as $block => $body) {
- $body = trim(implode("\n", $body));
-
- if ($block === 0 && !self::isTagged($body)) {
- // This is the description block
- $this->desc = $body;
- } else {
- // This block is tagged
- $tag = substr(self::strTag($body), 1);
- $body = ltrim(substr($body, strlen($tag) + 2));
-
- if (isset(self::$vectors[$tag])) {
- // The tagged block is a vector
- $count = count(self::$vectors[$tag]);
- if ($body) {
- $parts = preg_split('/\s+/', $body, $count);
- } else {
- $parts = array();
- }
-
- // Default the trailing values
- $parts = array_pad($parts, $count, null);
-
- // Store as a mapped array
- $this->tags[$tag][] = array_combine(self::$vectors[$tag], $parts);
- } else {
- // The tagged block is only text
- $this->tags[$tag][] = $body;
- }
- }
- }
- }
-
- /**
- * Whether or not a docblock contains a given @tag.
- *
- * @param string $tag The name of the @tag to check for
- *
- * @return bool
- */
- public function hasTag($tag)
- {
- return is_array($this->tags) && array_key_exists($tag, $this->tags);
- }
-
- /**
- * The value of a tag.
- *
- * @param string $tag
- *
- * @return array
- */
- public function tag($tag)
- {
- return $this->hasTag($tag) ? $this->tags[$tag] : null;
- }
-
- /**
- * Whether or not a string begins with a @tag.
- *
- * @param string $str
- *
- * @return bool
- */
- public static function isTagged($str)
- {
- return isset($str[1]) && $str[0] === '@' && ctype_alpha($str[1]);
- }
-
- /**
- * The tag at the beginning of a string.
- *
- * @param string $str
- *
- * @return string|null
- */
- public static function strTag($str)
- {
- if (preg_match('/^@[a-z0-9_]+/', $str, $matches)) {
- return $matches[0];
- }
- }
-}