Updated to Drupal 8.5. Core Media not yet in use.
[yaffs-website] / vendor / consolidation / robo / src / Task / Development / GenerateMarkdownDoc.php
diff --git a/vendor/consolidation/robo/src/Task/Development/GenerateMarkdownDoc.php b/vendor/consolidation/robo/src/Task/Development/GenerateMarkdownDoc.php
new file mode 100644 (file)
index 0000000..490aef3
--- /dev/null
@@ -0,0 +1,782 @@
+<?php
+namespace Robo\Task\Development;
+
+use Robo\Task\BaseTask;
+use Robo\Result;
+use Robo\Contract\BuilderAwareInterface;
+use Robo\Common\BuilderAwareTrait;
+
+/**
+ * Simple documentation generator from source files.
+ * Takes classes, properties and methods with their docblocks and writes down a markdown file.
+ *
+ * ``` php
+ * <?php
+ * $this->taskGenDoc('models.md')
+ *      ->docClass('Model\User') // take class Model\User
+ *      ->docClass('Model\Post') // take class Model\Post
+ *      ->filterMethods(function(\ReflectionMethod $r) {
+ *          return $r->isPublic() or $r->isProtected(); // process public and protected methods
+ *      })->processClass(function(\ReflectionClass $r, $text) {
+ *          return "Class ".$r->getName()."\n\n$text\n\n###Methods\n";
+ *      })->run();
+ * ```
+ *
+ * By default this task generates a documentation for each public method of a class, interface or trait.
+ * It combines method signature with a docblock. Both can be post-processed.
+ *
+ * ``` php
+ * <?php
+ * $this->taskGenDoc('models.md')
+ *      ->docClass('Model\User')
+ *      ->processClassSignature(false) // false can be passed to not include class signature
+ *      ->processClassDocBlock(function(\ReflectionClass $r, $text) {
+ *          return "[This is part of application model]\n" . $text;
+ *      })->processMethodSignature(function(\ReflectionMethod $r, $text) {
+ *          return "#### {$r->name}()";
+ *      })->processMethodDocBlock(function(\ReflectionMethod $r, $text) {
+ *          return strpos($r->name, 'save')===0 ? "[Saves to the database]\n" . $text : $text;
+ *      })->run();
+ * ```
+ */
+class GenerateMarkdownDoc extends BaseTask implements BuilderAwareInterface
+{
+    use BuilderAwareTrait;
+
+    /**
+     * @var string[]
+     */
+    protected $docClass = [];
+
+    /**
+     * @var callable
+     */
+    protected $filterMethods;
+
+    /**
+     * @var callable
+     */
+    protected $filterClasses;
+
+    /**
+     * @var callable
+     */
+    protected $filterProperties;
+
+    /**
+     * @var callable
+     */
+    protected $processClass;
+
+    /**
+     * @var callable|false
+     */
+    protected $processClassSignature;
+
+    /**
+     * @var callable|false
+     */
+    protected $processClassDocBlock;
+
+    /**
+     * @var callable|false
+     */
+    protected $processMethod;
+
+    /**
+     * @var callable|false
+     */
+    protected $processMethodSignature;
+
+    /**
+     * @var callable|false
+     */
+    protected $processMethodDocBlock;
+
+    /**
+     * @var callable|false
+     */
+    protected $processProperty;
+
+    /**
+     * @var callable|false
+     */
+    protected $processPropertySignature;
+
+    /**
+     * @var callable|false
+     */
+    protected $processPropertyDocBlock;
+
+    /**
+     * @var callable
+     */
+    protected $reorder;
+
+    /**
+     * @var callable
+     */
+    protected $reorderMethods;
+
+    /**
+     * @todo Unused property.
+     *
+     * @var callable
+     */
+    protected $reorderProperties;
+
+    /**
+     * @var string
+     */
+    protected $filename;
+
+    /**
+     * @var string
+     */
+    protected $prepend = "";
+
+    /**
+     * @var string
+     */
+    protected $append = "";
+
+    /**
+     * @var string
+     */
+    protected $text;
+
+    /**
+     * @var string[]
+     */
+    protected $textForClass = [];
+
+    /**
+     * @param string $filename
+     *
+     * @return static
+     */
+    public static function init($filename)
+    {
+        return new static($filename);
+    }
+
+    /**
+     * @param string $filename
+     */
+    public function __construct($filename)
+    {
+        $this->filename = $filename;
+    }
+
+    /**
+     * Put a class you want to be documented.
+     *
+     * @param string $item
+     *
+     * @return $this
+     */
+    public function docClass($item)
+    {
+        $this->docClass[] = $item;
+        return $this;
+    }
+
+    /**
+     * Using a callback function filter out methods that won't be documented.
+     *
+     * @param callable $filterMethods
+     *
+     * @return $this
+     */
+    public function filterMethods($filterMethods)
+    {
+        $this->filterMethods = $filterMethods;
+        return $this;
+    }
+
+    /**
+     * Using a callback function filter out classes that won't be documented.
+     *
+     * @param callable $filterClasses
+     *
+     * @return $this
+     */
+    public function filterClasses($filterClasses)
+    {
+        $this->filterClasses = $filterClasses;
+        return $this;
+    }
+
+    /**
+     * Using a callback function filter out properties that won't be documented.
+     *
+     * @param callable $filterProperties
+     *
+     * @return $this
+     */
+    public function filterProperties($filterProperties)
+    {
+        $this->filterProperties = $filterProperties;
+        return $this;
+    }
+
+    /**
+     * Post-process class documentation.
+     *
+     * @param callable $processClass
+     *
+     * @return $this
+     */
+    public function processClass($processClass)
+    {
+        $this->processClass = $processClass;
+        return $this;
+    }
+
+    /**
+     * Post-process class signature. Provide *false* to skip.
+     *
+     * @param callable|false $processClassSignature
+     *
+     * @return $this
+     */
+    public function processClassSignature($processClassSignature)
+    {
+        $this->processClassSignature = $processClassSignature;
+        return $this;
+    }
+
+    /**
+     * Post-process class docblock contents. Provide *false* to skip.
+     *
+     * @param callable|false $processClassDocBlock
+     *
+     * @return $this
+     */
+    public function processClassDocBlock($processClassDocBlock)
+    {
+        $this->processClassDocBlock = $processClassDocBlock;
+        return $this;
+    }
+
+    /**
+     * Post-process method documentation. Provide *false* to skip.
+     *
+     * @param callable|false $processMethod
+     *
+     * @return $this
+     */
+    public function processMethod($processMethod)
+    {
+        $this->processMethod = $processMethod;
+        return $this;
+    }
+
+    /**
+     * Post-process method signature. Provide *false* to skip.
+     *
+     * @param callable|false $processMethodSignature
+     *
+     * @return $this
+     */
+    public function processMethodSignature($processMethodSignature)
+    {
+        $this->processMethodSignature = $processMethodSignature;
+        return $this;
+    }
+
+    /**
+     * Post-process method docblock contents. Provide *false* to skip.
+     *
+     * @param callable|false $processMethodDocBlock
+     *
+     * @return $this
+     */
+    public function processMethodDocBlock($processMethodDocBlock)
+    {
+        $this->processMethodDocBlock = $processMethodDocBlock;
+        return $this;
+    }
+
+    /**
+     * Post-process property documentation. Provide *false* to skip.
+     *
+     * @param callable|false $processProperty
+     *
+     * @return $this
+     */
+    public function processProperty($processProperty)
+    {
+        $this->processProperty = $processProperty;
+        return $this;
+    }
+
+    /**
+     * Post-process property signature. Provide *false* to skip.
+     *
+     * @param callable|false $processPropertySignature
+     *
+     * @return $this
+     */
+    public function processPropertySignature($processPropertySignature)
+    {
+        $this->processPropertySignature = $processPropertySignature;
+        return $this;
+    }
+
+    /**
+     * Post-process property docblock contents. Provide *false* to skip.
+     *
+     * @param callable|false $processPropertyDocBlock
+     *
+     * @return $this
+     */
+    public function processPropertyDocBlock($processPropertyDocBlock)
+    {
+        $this->processPropertyDocBlock = $processPropertyDocBlock;
+        return $this;
+    }
+
+    /**
+     * Use a function to reorder classes.
+     *
+     * @param callable $reorder
+     *
+     * @return $this
+     */
+    public function reorder($reorder)
+    {
+        $this->reorder = $reorder;
+        return $this;
+    }
+
+    /**
+     * Use a function to reorder methods in class.
+     *
+     * @param callable $reorderMethods
+     *
+     * @return $this
+     */
+    public function reorderMethods($reorderMethods)
+    {
+        $this->reorderMethods = $reorderMethods;
+        return $this;
+    }
+
+    /**
+     * @param callable $reorderProperties
+     *
+     * @return $this
+     */
+    public function reorderProperties($reorderProperties)
+    {
+        $this->reorderProperties = $reorderProperties;
+        return $this;
+    }
+
+    /**
+     * @param string $filename
+     *
+     * @return $this
+     */
+    public function filename($filename)
+    {
+        $this->filename = $filename;
+        return $this;
+    }
+
+    /**
+     * Inserts text at the beginning of markdown file.
+     *
+     * @param string $prepend
+     *
+     * @return $this
+     */
+    public function prepend($prepend)
+    {
+        $this->prepend = $prepend;
+        return $this;
+    }
+
+    /**
+     * Inserts text at the end of markdown file.
+     *
+     * @param string $append
+     *
+     * @return $this
+     */
+    public function append($append)
+    {
+        $this->append = $append;
+        return $this;
+    }
+
+    /**
+     * @param string $text
+     *
+     * @return $this
+     */
+    public function text($text)
+    {
+        $this->text = $text;
+        return $this;
+    }
+
+    /**
+     * @param string $item
+     *
+     * @return $this
+     */
+    public function textForClass($item)
+    {
+        $this->textForClass[] = $item;
+        return $this;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function run()
+    {
+        foreach ($this->docClass as $class) {
+            $this->printTaskInfo("Processing {class}", ['class' => $class]);
+            $this->textForClass[$class] = $this->documentClass($class);
+        }
+
+        if (is_callable($this->reorder)) {
+            $this->printTaskInfo("Applying reorder function");
+            call_user_func_array($this->reorder, [$this->textForClass]);
+        }
+
+        $this->text = implode("\n", $this->textForClass);
+
+        /** @var \Robo\Result $result */
+        $result = $this->collectionBuilder()->taskWriteToFile($this->filename)
+            ->line($this->prepend)
+            ->text($this->text)
+            ->line($this->append)
+            ->run();
+
+        $this->printTaskSuccess('{filename} created. {class-count} classes documented', ['filename' => $this->filename, 'class-count' => count($this->docClass)]);
+
+        return new Result($this, $result->getExitCode(), $result->getMessage(), $this->textForClass);
+    }
+
+    /**
+     * @param string $class
+     *
+     * @return null|string
+     */
+    protected function documentClass($class)
+    {
+        if (!class_exists($class) && !trait_exists($class)) {
+            return "";
+        }
+        $refl = new \ReflectionClass($class);
+
+        if (is_callable($this->filterClasses)) {
+            $ret = call_user_func($this->filterClasses, $refl);
+            if (!$ret) {
+                return;
+            }
+        }
+        $doc = $this->documentClassSignature($refl);
+        $doc .= "\n" . $this->documentClassDocBlock($refl);
+        $doc .= "\n";
+
+        if (is_callable($this->processClass)) {
+            $doc = call_user_func($this->processClass, $refl, $doc);
+        }
+
+        $properties = [];
+        foreach ($refl->getProperties() as $reflProperty) {
+            $properties[] = $this->documentProperty($reflProperty);
+        }
+
+        $properties = array_filter($properties);
+        $doc .= implode("\n", $properties);
+
+        $methods = [];
+        foreach ($refl->getMethods() as $reflMethod) {
+            $methods[$reflMethod->name] = $this->documentMethod($reflMethod);
+        }
+        if (is_callable($this->reorderMethods)) {
+            call_user_func_array($this->reorderMethods, [&$methods]);
+        }
+
+        $methods = array_filter($methods);
+
+        $doc .= implode("\n", $methods)."\n";
+
+        return $doc;
+    }
+
+    /**
+     * @param \ReflectionClass $reflectionClass
+     *
+     * @return string
+     */
+    protected function documentClassSignature(\ReflectionClass $reflectionClass)
+    {
+        if ($this->processClassSignature === false) {
+            return "";
+        }
+
+        $signature = "## {$reflectionClass->name}\n\n";
+
+        if ($parent = $reflectionClass->getParentClass()) {
+            $signature .= "* *Extends* `{$parent->name}`";
+        }
+        $interfaces = $reflectionClass->getInterfaceNames();
+        if (count($interfaces)) {
+            $signature .= "\n* *Implements* `" . implode('`, `', $interfaces) . '`';
+        }
+        $traits = $reflectionClass->getTraitNames();
+        if (count($traits)) {
+            $signature .= "\n* *Uses* `" . implode('`, `', $traits) . '`';
+        }
+        if (is_callable($this->processClassSignature)) {
+            $signature = call_user_func($this->processClassSignature, $reflectionClass, $signature);
+        }
+
+        return $signature;
+    }
+
+    /**
+     * @param \ReflectionClass $reflectionClass
+     *
+     * @return string
+     */
+    protected function documentClassDocBlock(\ReflectionClass $reflectionClass)
+    {
+        if ($this->processClassDocBlock === false) {
+            return "";
+        }
+        $doc = self::indentDoc($reflectionClass->getDocComment());
+        if (is_callable($this->processClassDocBlock)) {
+            $doc = call_user_func($this->processClassDocBlock, $reflectionClass, $doc);
+        }
+        return $doc;
+    }
+
+    /**
+     * @param \ReflectionMethod $reflectedMethod
+     *
+     * @return string
+     */
+    protected function documentMethod(\ReflectionMethod $reflectedMethod)
+    {
+        if ($this->processMethod === false) {
+            return "";
+        }
+        if (is_callable($this->filterMethods)) {
+            $ret = call_user_func($this->filterMethods, $reflectedMethod);
+            if (!$ret) {
+                return "";
+            }
+        } else {
+            if (!$reflectedMethod->isPublic()) {
+                return "";
+            }
+        }
+
+        $signature = $this->documentMethodSignature($reflectedMethod);
+        $docblock = $this->documentMethodDocBlock($reflectedMethod);
+        $methodDoc = "$signature $docblock";
+        if (is_callable($this->processMethod)) {
+            $methodDoc = call_user_func($this->processMethod, $reflectedMethod, $methodDoc);
+        }
+        return $methodDoc;
+    }
+
+    /**
+     * @param \ReflectionProperty $reflectedProperty
+     *
+     * @return string
+     */
+    protected function documentProperty(\ReflectionProperty $reflectedProperty)
+    {
+        if ($this->processProperty === false) {
+            return "";
+        }
+        if (is_callable($this->filterProperties)) {
+            $ret = call_user_func($this->filterProperties, $reflectedProperty);
+            if (!$ret) {
+                return "";
+            }
+        } else {
+            if (!$reflectedProperty->isPublic()) {
+                return "";
+            }
+        }
+        $signature = $this->documentPropertySignature($reflectedProperty);
+        $docblock = $this->documentPropertyDocBlock($reflectedProperty);
+        $propertyDoc = $signature . $docblock;
+        if (is_callable($this->processProperty)) {
+            $propertyDoc = call_user_func($this->processProperty, $reflectedProperty, $propertyDoc);
+        }
+        return $propertyDoc;
+    }
+
+    /**
+     * @param \ReflectionProperty $reflectedProperty
+     *
+     * @return string
+     */
+    protected function documentPropertySignature(\ReflectionProperty $reflectedProperty)
+    {
+        if ($this->processPropertySignature === false) {
+            return "";
+        }
+        $modifiers = implode(' ', \Reflection::getModifierNames($reflectedProperty->getModifiers()));
+        $signature = "#### *$modifiers* {$reflectedProperty->name}";
+        if (is_callable($this->processPropertySignature)) {
+            $signature = call_user_func($this->processPropertySignature, $reflectedProperty, $signature);
+        }
+        return $signature;
+    }
+
+    /**
+     * @param \ReflectionProperty $reflectedProperty
+     *
+     * @return string
+     */
+    protected function documentPropertyDocBlock(\ReflectionProperty $reflectedProperty)
+    {
+        if ($this->processPropertyDocBlock === false) {
+            return "";
+        }
+        $propertyDoc = $reflectedProperty->getDocComment();
+        // take from parent
+        if (!$propertyDoc) {
+            $parent = $reflectedProperty->getDeclaringClass();
+            while ($parent = $parent->getParentClass()) {
+                if ($parent->hasProperty($reflectedProperty->name)) {
+                    $propertyDoc = $parent->getProperty($reflectedProperty->name)->getDocComment();
+                }
+            }
+        }
+        $propertyDoc = self::indentDoc($propertyDoc, 7);
+        $propertyDoc = preg_replace("~^@(.*?)([$\s])~", ' * `$1` $2', $propertyDoc); // format annotations
+        if (is_callable($this->processPropertyDocBlock)) {
+            $propertyDoc = call_user_func($this->processPropertyDocBlock, $reflectedProperty, $propertyDoc);
+        }
+        return ltrim($propertyDoc);
+    }
+
+    /**
+     * @param \ReflectionParameter $param
+     *
+     * @return string
+     */
+    protected function documentParam(\ReflectionParameter $param)
+    {
+        $text = "";
+        if ($param->isArray()) {
+            $text .= 'array ';
+        }
+        if ($param->isCallable()) {
+            $text .= 'callable ';
+        }
+        $text .= '$' . $param->name;
+        if ($param->isDefaultValueAvailable()) {
+            if ($param->allowsNull()) {
+                $text .= ' = null';
+            } else {
+                $text .= ' = ' . str_replace("\n", ' ', print_r($param->getDefaultValue(), true));
+            }
+        }
+
+        return $text;
+    }
+
+    /**
+     * @param string $doc
+     * @param int $indent
+     *
+     * @return string
+     */
+    public static function indentDoc($doc, $indent = 3)
+    {
+        if (!$doc) {
+            return $doc;
+        }
+        return implode(
+            "\n",
+            array_map(
+                function ($line) use ($indent) {
+                    return substr($line, $indent);
+                },
+                explode("\n", $doc)
+            )
+        );
+    }
+
+    /**
+     * @param \ReflectionMethod $reflectedMethod
+     *
+     * @return string
+     */
+    protected function documentMethodSignature(\ReflectionMethod $reflectedMethod)
+    {
+        if ($this->processMethodSignature === false) {
+            return "";
+        }
+        $modifiers = implode(' ', \Reflection::getModifierNames($reflectedMethod->getModifiers()));
+        $params = implode(
+            ', ',
+            array_map(
+                function ($p) {
+                    return $this->documentParam($p);
+                },
+                $reflectedMethod->getParameters()
+            )
+        );
+        $signature = "#### *$modifiers* {$reflectedMethod->name}($params)";
+        if (is_callable($this->processMethodSignature)) {
+            $signature = call_user_func($this->processMethodSignature, $reflectedMethod, $signature);
+        }
+        return $signature;
+    }
+
+    /**
+     * @param \ReflectionMethod $reflectedMethod
+     *
+     * @return string
+     */
+    protected function documentMethodDocBlock(\ReflectionMethod $reflectedMethod)
+    {
+        if ($this->processMethodDocBlock === false) {
+            return "";
+        }
+        $methodDoc = $reflectedMethod->getDocComment();
+        // take from parent
+        if (!$methodDoc) {
+            $parent = $reflectedMethod->getDeclaringClass();
+            while ($parent = $parent->getParentClass()) {
+                if ($parent->hasMethod($reflectedMethod->name)) {
+                    $methodDoc = $parent->getMethod($reflectedMethod->name)->getDocComment();
+                }
+            }
+        }
+        // take from interface
+        if (!$methodDoc) {
+            $interfaces = $reflectedMethod->getDeclaringClass()->getInterfaces();
+            foreach ($interfaces as $interface) {
+                $i = new \ReflectionClass($interface->name);
+                if ($i->hasMethod($reflectedMethod->name)) {
+                    $methodDoc = $i->getMethod($reflectedMethod->name)->getDocComment();
+                    break;
+                }
+            }
+        }
+
+        $methodDoc = self::indentDoc($methodDoc, 7);
+        $methodDoc = preg_replace("~^@(.*?) ([$\s])~m", ' * `$1` $2', $methodDoc); // format annotations
+        if (is_callable($this->processMethodDocBlock)) {
+            $methodDoc = call_user_func($this->processMethodDocBlock, $reflectedMethod, $methodDoc);
+        }
+
+        return $methodDoc;
+    }
+}