stub2ref
authorMichael Wallner <mike@php.net>
Wed, 19 Jan 2022 17:42:45 +0000 (18:42 +0100)
committerMichael Wallner <mike@php.net>
Wed, 19 Jan 2022 17:42:45 +0000 (18:42 +0100)
16 files changed:
bin/stub2ref [new file with mode: 0755]
composer.json
mdref/Action.php
mdref/Entry.php
mdref/Generator.php [new file with mode: 0644]
mdref/Generator/Arg.php [new file with mode: 0644]
mdref/Generator/Cls.php [new file with mode: 0644]
mdref/Generator/Func.php [new file with mode: 0644]
mdref/Generator/Param.php [new file with mode: 0644]
mdref/Generator/Prop.php [new file with mode: 0644]
mdref/Generator/Scrap.php [new file with mode: 0644]
mdref/Generator/SeeAlso.php [new file with mode: 0644]
mdref/Generator/Template.php [new file with mode: 0644]
mdref/Inspector.php [new file with mode: 0644]
mdref/Tree.php
public/index.js

diff --git a/bin/stub2ref b/bin/stub2ref
new file mode 100755 (executable)
index 0000000..de1899b
--- /dev/null
@@ -0,0 +1,30 @@
+#!/usr/bin/env php
+<?php
+
+namespace mdref;
+
+require_once __DIR__."/../vendor/autoload.php";
+
+if ($argc < 3) {
+       fprintf(STDERR, "Usage: cd ref-<ns> && %s <ns> <stub.php>\n", $argv[0]);
+       exit(1);
+}
+
+$namespace = $argv[1];
+require_once $argv[2];
+
+if (!file_exists("$namespace.mdref")) {
+       fprintf(STDERR, "Missing $namespace.mdref; generated default.\n");
+       file_put_contents("$namespace.mdref", "./%s");
+}
+if (!file_exists("$namespace.md")) {
+       fprintf(STDERR, "Missing $namespace.md; hard linking README.md\n");
+       link(dirname($argv[2]) . "/README.md", "$namespace.md");
+}
+
+$inspector = new Inspector;
+$inspector->inspectNamespace($namespace);
+
+$generator = new Generator;
+$generator->generateFunctions($inspector->getFunctions());
+$generator->generateClasses($inspector->getClasses());
index 01de530ee30a826f24c04e03168fa295c32936c8..4a4999983bf2e0dd5777abb3c3e565b0076efd05 100644 (file)
@@ -9,8 +9,10 @@
         }
     ],
     "bin": [
+        "bin/ext2ref",
+        "bin/ref2html",
         "bin/ref2stub",
-        "bin/ref2html"
+        "bin/stub2ref"
     ],
     "autoload": {
         "psr-4": {
@@ -23,7 +25,8 @@
         "ext-filter": "*",
         "ext-pcre": "*",
         "ext-http": "^3.2 || ^4.2",
-        "league/commonmark": "^2.0"
+        "league/commonmark": "^2.0",
+        "phpdocumentor/reflection-docblock": "^5.3"
     },
     "suggests": {
         "ext-discount": "A Markdown renderer is needed, this is the preferred one."
index da3a4da871c5dd6c354af8990b8d66f879d9e1a0..3addfa0527fe4880259d57e3f4ab964ff9c04ae2 100644 (file)
@@ -180,6 +180,9 @@ class Action {
                include ROOT."/views/layout.phtml";
                $this->response->addHeader("Link", "<" . $this->baseUrl->path . "index.css>; rel=preload; as=style");
                $this->response->addHeader("Link", "<" . $this->baseUrl->path . "index.js>; rel=preload; as=script");
+               if (isset($exception) && $exception->getCode()) {
+                       $this->response->setResponseCode($exception->getCode());
+               }
                if (is_resource($this->output)) {
                        $this->response->send($this->output);
                } else {
@@ -194,10 +197,10 @@ class Action {
                try {
                        $pld = $this->createPayload();
 
-                       if (strlen($pld->ref)) {
+                       if (isset($pld->ref) && strlen($pld->ref)) {
                                $cnn = null;
                                if (($repo = $this->reference->getRepoForEntry($pld->ref, $cnn))) {
-                                       if (strlen($cnn)) {
+                                       if (isset($cnn) && strlen($cnn)) {
                                                /* redirect */
                                                $this->serveCanonical($cnn);
                                                return;
index 152ba47232c38f9a2e8a2d6c25c494d5b9ef0047..345f3c7b5bcc5bc9a2e1599c2cf6e5010c56d4df 100644 (file)
@@ -293,7 +293,7 @@ class Entry implements IteratorAggregate {
         * @return bool
         */
        function hasIterator(?string $glob = null, bool $loose = false) : bool {
-               if (strlen($glob)) {
+               if (isset($glob) && strlen($glob)) {
                        return glob($this->getNextDirname() . "/$glob") ||
                                ($loose && glob($this->getNextDirname() . "/*/$glob"));
                } elseif ($this->isRoot()) {
diff --git a/mdref/Generator.php b/mdref/Generator.php
new file mode 100644 (file)
index 0000000..84a323e
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+
+namespace mdref;
+
+use mdref\Generator\{Cls, Func};
+
+class Generator {
+       /**
+        * @param array<string, array<string, \ReflectionFunctionAbstract>> $functions
+        * @return void
+        */
+       public function generateFunctions(array $functions) : void {
+               foreach ($functions as $ns => $funcs) {
+                       $ns_path = strtr($ns, "\\", "/");
+                       foreach ($funcs as $fn => $rf) {
+                               $fn_file = "$ns_path/$fn.md";
+                               fprintf(STDERR, "Generating %s\n", $fn_file);
+                               is_dir($ns_path) || mkdir($ns_path, 0770, true);
+                               file_put_contents($fn_file, new Func($this, $rf));
+                       }
+               }
+       }
+
+       /**
+        * @param array<string, array<string, \ReflectionClass>> $classes
+        * @return void
+        */
+       public function generateClasses(array $classes) : void {
+               foreach ($classes as $ns => $cls) {
+                       $ns_path = strtr($ns, "\\", "/");
+                       foreach ($cls as $cn => $rc) {
+                               $cn_path = "$ns_path/$cn";
+                               $cn_file = "$cn_path.md";
+                               fprintf(STDERR, "Generating %s\n", $cn_file);
+                               is_dir($ns_path) || mkdir($ns_path, 0770, true);
+                               file_put_contents($cn_file, new Cls($this, $rc));
+                               $this->generateMethods($rc);
+                       }
+               }
+       }
+
+       private function generateMethods(\ReflectionClass $rc) : void {
+               $funcs = [];
+               foreach ($rc->getMethods(\ReflectionMethod::IS_PUBLIC) as $rm) {
+                       if ($rm->getDeclaringClass()->getName() === $rc->getName()) {
+                               foreach ($rc->getInterfaces() as $ri) {
+                                       if ($ri->hasMethod($rm->getName())) {
+                                               continue 2;
+                                       }
+                               }
+                               $funcs[$rc->getName()][$rm->getName()] = $rm;
+                       }
+               }
+               $this->generateFunctions($funcs);
+       }
+}
diff --git a/mdref/Generator/Arg.php b/mdref/Generator/Arg.php
new file mode 100644 (file)
index 0000000..df6fbbc
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+
+namespace mdref\Generator;
+
+use mdref\Generator;
+use phpDocumentor\Reflection\{DocBlock, DocBlock\Tags};
+
+class Arg extends Scrap {
+       public function __toString() : string {
+               return parent::toString(__FILE__, __COMPILER_HALT_OFFSET__, [
+                       "tag" => $this->getParamTag($this->ref->getName())
+               ]);
+       }
+
+}
+
+/** @var $gen Generator */
+/** @var $ref \ReflectionParameter */
+/** @var $doc ?DocBlock */
+/** @var $tag ?Tags\Param */
+
+__HALT_COMPILER();
+<?= $ref->hasType() ? $ref->getType() : str_replace("\\ref", "", $tag?->getType() ?? "mixed")
+?> <?php
+if ($ref->isVariadic()) : ?>
+       ?>...<?php
+endif;
+if ($ref->isPassedByReference()) :
+       ?>&<?php
+endif;
+?>$<?=$ref->getName()
+?><?php
+if ($ref->isDefaultValueAvailable()) :
+       ?> = <?php var_export($ref->getDefaultValue()) ?><?php
+endif;
diff --git a/mdref/Generator/Cls.php b/mdref/Generator/Cls.php
new file mode 100644 (file)
index 0000000..5292c6d
--- /dev/null
@@ -0,0 +1,85 @@
+<?php
+
+namespace mdref\Generator;
+
+use mdref\Generator;
+use phpDocumentor\Reflection\DocBlock;
+
+class Cls extends Scrap {
+       public function __toString() :string {
+               return parent::toString(__FILE__, __COMPILER_HALT_OFFSET__);
+       }
+}
+
+/** @var $gen Generator */
+/** @var $ref \ReflectionClass */
+/** @var $doc DocBlock */
+/** @var $patch callable as function(string, \Reflector) */
+
+__HALT_COMPILER();
+# <?php
+if ($ref instanceof \ReflectionEnum) :
+       ?>enum<?php
+else :
+       ?><?= implode(" ", \Reflection::getModifierNames($ref->getModifiers()));
+       ?> <?= $ref->isInterface() ? "interface" : "class"
+       ?><?php
+endif;
+
+?> <?= $ref->getName() ?><?php
+
+if (($parent = $ref->getParentClass())) :
+       ?> extends <?= $parent->getName() ?><?php
+endif;
+if (($implements = $ref->getInterfaceNames())) : sort($implements);
+       ?> implements <?= implode(", ", $implements); ?><?php
+endif;
+?>
+
+
+<?= $doc?->getSummary() ?>
+
+
+<?= $doc?->getDescription() ?>
+
+
+<?php $patch(SeeAlso::class, $ref) ?>
+
+
+
+## Constants:
+
+<?php
+if (!($consts = array_filter($ref->getReflectionConstants(), fn($rc) => $rc->getDeclaringClass()->getName() === $ref->getName()))) :
+       ?>None.<?php
+else:
+       /** @var \ReflectionClassConstant $rc */
+       foreach ($consts as $rc) :
+               ?> * <span class="constant"><?= $rc->getName();
+               ?></span> = <span><?php
+               if ($rc->getValue() instanceof \UnitEnum) :
+                       var_export($rc->getValue()->value);
+               else :
+                       var_export($rc->getValue());
+               endif;
+               ?><?= "</span>\n"
+               ?><?php
+       endforeach;
+endif;
+?>
+
+
+## Properties:
+
+<?php
+if (!($props = array_filter($ref->getProperties(), fn($rp) => $rp->getDeclaringClass()->getName() === $ref->getName()))) :
+       ?>None.<?php
+else:
+       foreach ($props as $rp) :
+               ?> * <?php
+               $patch(Prop::class, $rp);
+       endforeach;
+endif;
+?>
+
+<?php
diff --git a/mdref/Generator/Func.php b/mdref/Generator/Func.php
new file mode 100644 (file)
index 0000000..f31941a
--- /dev/null
@@ -0,0 +1,94 @@
+<?php
+
+namespace mdref\Generator;
+
+use mdref\Generator;
+use phpDocumentor\Reflection\{DocBlock, DocBlock\Tags, DocBlockFactory};
+
+class Func extends Scrap {
+       public function __toString() : string {
+               return parent::toString(__FILE__, __COMPILER_HALT_OFFSET__);
+       }
+}
+
+/** @var $gen Generator */
+/** @var $ref \ReflectionFunctionAbstract */
+/** @var $doc DocBlock */
+/** @var $patch callable as function(string, \Reflector) */
+
+__HALT_COMPILER();
+# <?php
+if ($ref instanceof \ReflectionMethod) :
+       if ($ref->isFinal()) :
+               ?>final <?php
+       endif;
+       if ($ref->isStatic()) :
+               ?>static <?php
+       endif;
+endif;
+?><?= $ref->hasReturnType() ? $ref->getReturnType() : "void"
+?> <?php
+if ($ref instanceof \ReflectionMethod) :
+       ?><?=$ref->getDeclaringClass()->getName()
+       ?>::<?php
+endif;
+?><?= $ref->getName() ?>
+(<?php
+       $optional = 0;
+       foreach ($ref->getParameters() as $i => $param) :
+               if ($param->isOptional()) : $optional++
+                       ?>[<?php
+               endif;
+               $patch(Arg::class, $param);
+               if ($i < $ref->getNumberOfParameters()-1):
+                       ?>, <?php
+               endif;
+       endforeach;
+       echo str_repeat("]", $optional);
+?>
+)
+
+<?= $doc?->getSummary() ?>
+
+
+<?= $doc?->getDescription()?->getBodyTemplate() ?>
+
+
+<?php $patch(SeeAlso::class, $ref) ?>
+
+
+
+## Params:
+
+<?php
+if (!($params = $ref->getParameters())) :
+       ?>None.<?php
+else :
+       foreach ($params as $i => $param) :
+               $patch(Param::class, $param);
+       endforeach;
+endif;
+
+if (($tags = $doc?->getTagsWithTypeByName("return")) || ($ref->hasReturnType() && $ref->hasReturnType() != "void")) :
+?>
+
+
+## Returns:
+
+<?php
+       if ($tags) :
+               foreach ($tags as $tag) :
+                       ?>* <?= $tag->getType()
+                       ?>, <?= $tag->getDescription()
+                       ?><?="\n"
+                       ?><?php
+               endforeach;
+       else :
+               ?>* <?= $ref->getReturnType()
+               ?><?php
+       endif;
+endif;
+?>
+
+
+<?php
diff --git a/mdref/Generator/Param.php b/mdref/Generator/Param.php
new file mode 100644 (file)
index 0000000..2c1f8b3
--- /dev/null
@@ -0,0 +1,48 @@
+<?php
+
+namespace mdref\Generator;
+
+use mdref\Generator;
+use phpDocumentor\Reflection\{DocBlock, DocBlock\Tags};
+
+
+class Param extends Scrap {
+       public function __toString() : string {
+               return parent::toString(__FILE__, __COMPILER_HALT_OFFSET__, [
+                       "tag" => $this->getParamTag($this->ref->getName())
+               ]);
+       }
+}
+
+/** @var $gen Generator */
+/** @var $ref \ReflectionParameter */
+/** @var $doc ?DocBlock */
+/** @var $tag ?Tags\Param */
+
+__HALT_COMPILER();
+* <?php
+if ($ref->isOptional()) :
+       ?>Optional <?php
+endif;
+?><?= $ref->hasType() ? $ref->getType() : str_replace("\\ref", "Reference",$tag?->getType() ?? "mixed")
+?> <?php
+if ($ref->isVariadic()) : ?>
+       ?>...<?php
+endif;
+if ($ref->isPassedByReference()) :
+       ?>&<?php
+endif;
+?>$<?= $ref->getName()
+?><?php
+if ($ref->isDefaultValueAvailable()) :
+       ?> = <?php var_export($ref->getDefaultValue()) ?><?php
+endif;
+
+if (($desc = $tag?->getDescription() ?? $doc?->getSummary())) :
+       ?><?= "  \n   "
+       ?><?= $desc
+       ?><?php
+endif;
+?>
+
+<?php
diff --git a/mdref/Generator/Prop.php b/mdref/Generator/Prop.php
new file mode 100644 (file)
index 0000000..69d07ce
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+
+namespace mdref\Generator;
+
+use mdref\Generator;
+use phpDocumentor\Reflection\{DocBlock, DocBlock\Tags};
+
+class Prop extends Scrap {
+       public function __toString() : string {
+               return parent::toString(__FILE__, __COMPILER_HALT_OFFSET__, [
+                       "tag" => $this->getVarTag($this->ref->getName())
+               ]);
+       }
+
+}
+
+/** @var $gen Generator */
+/** @var $ref \ReflectionParameter */
+/** @var $doc ?DocBlock */
+/** @var $tag ?Tags\Param */
+
+__HALT_COMPILER();
+<?= implode(" ", \Reflection::getModifierNames($ref->getModifiers()))
+?> <?= $ref->hasType() ? $ref->getType() : ($tag?->getType() ?? "mixed")
+?> $<?=$ref->getName() ?><?php
+if ($ref->hasDefaultValue()) :
+       ?> = <?php var_export($ref->getDefaultValue()) ?><?php
+endif;
+
+if (($desc = $doc?->getSummary())) :
+       ?><?= "  \n  $desc"
+       ?><?php
+endif;
+
+?><?= "\n"
+?><?php
+
diff --git a/mdref/Generator/Scrap.php b/mdref/Generator/Scrap.php
new file mode 100644 (file)
index 0000000..30b936b
--- /dev/null
@@ -0,0 +1,79 @@
+<?php
+
+namespace mdref\Generator;
+
+use mdref\Generator;
+use mdref\Generator\{Arg, Param};
+use phpDocumentor\Reflection\{DocBlock, DocBlockFactory, DocBlock\Tags};
+
+use Reflector;
+
+class Scrap {
+       public function __construct(
+               protected Generator $gen,
+               protected Reflector $ref,
+               protected ?DocBlock $doc = null,
+               bool $overrideDocFromRef = false,
+       ) {
+               if ($overrideDocFromRef || !$this->doc) {
+                       $this->createDocBlock();
+               }
+               if (!$this->doc) {
+                       fprintf(STDERR, ... match (get_class($ref)) {
+                               \ReflectionClass::class => ["Missing docs for class %s\n", $ref->name],
+                               \ReflectionMethod::class => ["Missing docs for method %s::%s()\n", $ref->class, $ref->name],
+                               \ReflectionProperty::class => ["Missing docs for property %s %s::\$%s\n", $ref->getType(), $ref->class, $ref->name],
+                               \ReflectionClassConstant::class => ["Missing docs for constant %s::%s\n", $ref->class, $ref->name],
+                               \ReflectionFunction::class => ["Missing docs for function %s()\n", $ref->name],
+                               \ReflectionParameter::class => ($ref->getDeclaringClass()
+                                       ? ["Missing docs for method arg %s::%s(%s $%s)\n", $ref->getDeclaringClass()->name]
+                                       : ["Missing docs for function arg %s(%s $%s)\n"])
+                                       + [3=>$ref->getDeclaringFunction()->name, $ref->getType(), $ref->name],
+                               default => ["Missing docs for ??? %s\n", $ref->name],
+                       });
+               }
+       }
+
+       protected function createDocBlock() {
+               if (method_exists($this->ref, "getDocComment")) {
+                       $docs = $this->ref->getDocComment();
+               } elseif (($this->ref instanceof \ReflectionParameter) && $this->ref->getDeclaringClass()?->hasProperty($this->ref->name)) {
+                       // ctor promoted properties
+                       $docs = $this->ref->getDeclaringClass()->getProperty($this->ref->name)->getDocComment();
+               }
+               if (isset($docs) && $docs !== false && strlen($docs)) {
+                       $this->doc = DocBlockFactory::createInstance()->create((string) $docs);
+               }
+       }
+
+       protected function toString(string $file, int $offset, array $imports = []) : string {
+               $tpl = (string) new Template($file, $offset);
+               $patch = function(string $scrap_class, Reflector $ref) : void {
+                       echo new $scrap_class($this->gen, $ref, $this->doc, true);
+               };
+               return (static function(Generator $gen, Reflector $ref, ?DocBlock $doc) use($patch) {
+                       $imports = func_get_arg(3); extract($imports); unset($imports);
+                       ob_start(null, 0x4000);
+                       include func_get_arg(4);
+                       return ob_get_clean();
+               })($this->gen, $this->ref, $this->doc, $imports, $tpl);
+       }
+
+       protected function getParamTag(string $var_name) : ?Tags\Param {
+               if ($this->doc) foreach ($this->doc->getTagsByName("param") as $param) {
+                       if ($param->getVariableName() === $var_name) {
+                               return $param;
+                       }
+               }
+               return null;
+       }
+
+       protected function getVarTag(string $var_name) : ?Tags\Var_ {
+               if ($this->doc) foreach ($this->doc->getTagsByName("var") as $prop) {
+                       if ($prop->getVariableName() === $var_name) {
+                               return $prop;
+                       }
+               }
+               return null;
+       }
+}
diff --git a/mdref/Generator/SeeAlso.php b/mdref/Generator/SeeAlso.php
new file mode 100644 (file)
index 0000000..69f7892
--- /dev/null
@@ -0,0 +1,39 @@
+<?php
+
+namespace mdref\Generator;
+use phpDocumentor\Reflection\DocBlock;
+
+
+class SeeAlso extends Scrap {
+       public function __toString() : string {
+               return parent::toString(__FILE__, __COMPILER_HALT_OFFSET__);
+       }
+}
+
+/** @var $doc DocBlock */
+/** @var $patch callable as function(string, \Reflector) */
+
+__HALT_COMPILER();
+<?php
+if (($sees = $doc?->getTagsByName("see"))) :
+       ?>See also <?php
+       foreach ($sees as $i => $see) :
+               /** @var $see DocBlock\Tags\See */
+               if (($desc = $see->getDescription())) :
+                       ?>[<?= $see->getDescription() ?>](<?= $see->getReference() ?>)<?php
+               else :
+                       ?><?= $see->getReference() ?><?php
+               endif;
+               if ($i < count($sees)) :
+                       if ($i === count($sees) - 1) :
+                               ?>.<?php
+                       else :
+                               ?>, <?php
+                       endif;
+                       if ($i === count($sees) - 2) :
+                               ?>and <?php
+                       endif;
+               endif;
+       endforeach;
+endif;
+
diff --git a/mdref/Generator/Template.php b/mdref/Generator/Template.php
new file mode 100644 (file)
index 0000000..0b0e638
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+
+namespace mdref\Generator;
+
+use http\Exception\RuntimeException;
+
+class Template {
+       public function __construct(
+               private string $file,
+               private int $offset
+       ) {}
+
+       public function source() {
+               return file_get_contents($this->file, false, null, $this->offset);
+       }
+
+       private function cache(string $cache_file) : string {
+               $cache_path = dirname($cache_file);
+               if (!is_dir($cache_path) && !mkdir($cache_path, 0700, true)) {
+                       throw new RuntimeException(error_get_last()["message"]);
+               }
+               if (!file_put_contents($cache_file, '<?php namespace mdref\\Generator; ?>' . $this->source())) {
+                       throw new RuntimeException(error_get_last()["message"]);
+               }
+               return $cache_file;
+       }
+
+       public function __toString() : string {
+               $cache_file = sys_get_temp_dir() . "/mdref/generate." . basename($this->file) . ".md.tmp";
+               $cache_stat = @stat($cache_file);
+               if ($cache_stat && $cache_stat["mtime"] >= filemtime($this->file)) {
+                       return $cache_file;
+               }
+
+               return $this->cache($cache_file);
+       }
+}
diff --git a/mdref/Inspector.php b/mdref/Inspector.php
new file mode 100644 (file)
index 0000000..162cc51
--- /dev/null
@@ -0,0 +1,100 @@
+<?php
+
+namespace mdref;
+
+class Inspector {
+       /**
+        * @var array
+        */
+       private $constants = [];
+       /**
+        * @var array
+        */
+       private $classes = [];
+       /**
+        * @var array
+        */
+       private $functions = [];
+
+       public function inspectExtension($ext) {
+               if (!($ext instanceof \ReflectionExtension)) {
+                       $ext = new \ReflectionExtension($ext);
+               }
+
+               $this->addClasses($ext->getClasses());
+               $this->addFunctions($ext->getFunctions());
+               $this->addConstants($ext->getConstants());
+       }
+
+       public function inspectNamespace(string $namespace) {
+               $grep = function(array $a) use($namespace) {
+                       return preg_grep("/^" . preg_quote($namespace) . "\\\\/", $a);
+               };
+
+               $this->addClasses($this->wrap(\ReflectionClass::class, $grep(get_declared_interfaces())));
+               $this->addClasses($this->wrap(\ReflectionClass::class, array_filter(
+                       $grep(get_declared_classes()),
+                       fn($cn) => !is_subclass_of($cn, \UnitEnum::class)
+               )));
+               $this->addClasses($this->wrap(\ReflectionEnum::class, array_filter(
+                       $grep(get_declared_classes()),
+                       fn($cn) => is_subclass_of($cn, \UnitEnum::class)
+               )));
+               $this->addFunctions($this->wrap(\ReflectionFunction::class, $grep(get_defined_functions()["internal"])));
+               $this->addFunctions($this->wrap(\ReflectionFunction::class, $grep(get_defined_functions()["user"])));
+               $this->addConstants($grep(get_defined_constants()));
+       }
+
+       private function wrap(string $klass, array $list) : array {
+               $res = [];
+               foreach ($list as $entry) {
+                       $res[] = new $klass($entry);
+               }
+               return $res;
+       }
+
+       private function addClasses(array $classes) {
+               foreach ($classes as $c) {
+                       /* @var $c \ReflectionClass */
+                       $ns_name = $c->inNamespace() ? $c->getNamespaceName() : "";
+                       $this->classes[$ns_name][$c->getShortName()] = $c;
+               }
+       }
+
+       private function addFunctions(array $functions) {
+               foreach ($functions as $f) {
+                       /* @var $f \ReflectionFunction */
+                       $ns_name = $f->inNamespace() ? $f->getNamespaceName() : "";
+                       $this->functions[$ns_name][$f->getShortName()] = $f;
+               }
+       }
+
+       private function addConstants(array $constants) {
+               foreach ($constants as $constant => $value) {
+                       $ns_name = ($ns_end = strrpos($constant, "\\")) ? substr($constant, 0, $ns_end++) : "";
+                       $cn = substr($constant, $ns_end);
+                       $this->constants[$ns_name][$cn] = $value;
+               }
+       }
+
+       /**
+        * @return array
+        */
+       public function getConstants() : array {
+               return $this->constants;
+       }
+
+       /**
+        * @return array
+        */
+       public function getClasses(): array {
+               return $this->classes;
+       }
+
+       /**
+        * @return array
+        */
+       public function getFunctions(): array {
+               return $this->functions;
+       }
+}
index fc12a1c4233713fcc2535d3a30b63ce261a216a4..68c5113b925b82b969f19fdaf114a21ec8fb03c0 100644 (file)
@@ -92,7 +92,7 @@ class Tree implements RecursiveIterator {
                        } elseif ($bb[0] === ":") {
                                return 1;
                        }
-
+/*
                        $ad = is_dir(dirname($a)."/$ab");
                        $bd = is_dir(dirname($b)."/$bb");
 
@@ -103,7 +103,7 @@ class Tree implements RecursiveIterator {
                        } elseif ($bd) {
                                return 1;
                        }
-
+*/
                        $au = preg_match("/^\p{Lu}/", $ab);
                        $bu = preg_match("/^\p{Lu}/", $bb);
 
@@ -170,7 +170,7 @@ class Tree implements RecursiveIterator {
         * Implements \RecursiveIterator
         * @return \mdref\Tree
         */
-       public function getChildren() : Iterator {
+       public function getChildren() : Tree {
                return $this->current()->getIterator();
        }
 }
index 45862d01bdb1c12cad8d98f404d0574d5f852b11..5124ffee52e50acec7bec355821a6a5d4075716b 100644 (file)
@@ -51,6 +51,7 @@ $(function() {
                        case "Traversable":
                        case "Iterator":
                        case "IteratorAggregate":
+                       case "RecursiveIterator":
                        case "ArrayAccess":
                        case "ArrayObject":
                        case "ArrayIterator":
@@ -73,6 +74,7 @@ $(function() {
                                        return "<br>&nbsp;<em>";
                                }
                        case "class":
+                       case "enum":
                        case "interface":
                        case "namespace":
                        case "public":