SamiTwigExtension.php 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865
  1. <?php namespace Markdown;
  2. use \Sami\Reflection\Reflection as Reflection;
  3. use \Sami\Reflection\ClassReflection as ClassReflection;
  4. use \Sami\Reflection\MethodReflection as MethodReflection;
  5. use \Sami\Reflection\ParameterReflection as ParameterReflection;
  6. use \Sami\Reflection\HintReflection as HintReflection;
  7. use \Sami\Reflection\InterfaceReflection as InterfaceReflection;
  8. /**
  9. * An extension than builds a single ReadMe markdown
  10. * file out of your code.
  11. *
  12. * By now, most is done "behind the scene" and the template
  13. * is very minimal. This means that it's not much you can do to
  14. * change any of the layout etc, without editing the actual code/class,
  15. * but you may want to change template structure by calling
  16. * the methods "render_classes", "render_class" etc, but be aware
  17. * that these methods does not remove whitespaces and takes care of
  18. * any format issues your template will get.
  19. *
  20. *
  21. * This is in lack of a a single-file-documentation, for a project,
  22. * and since the markdown language is very strict on format -
  23. * (indentation, signs etc.) - using the twig template was a hassle,
  24. * whitout ending with several "twig-files" that was unreadable (no
  25. * indentation etc).
  26. *
  27. * Get [Sami](https://github.com/FriendsOfPHP/Sami/) and fork this
  28. * repo
  29. *
  30. * ```bash
  31. * git clone giaever@git.giaever.org:joachimmg/sami-markdown.git
  32. * ```
  33. *
  34. * Include the `SamiTwigExtension.php` into your Sami configuration-file
  35. * and add it to twig. See `example.conf.php` or
  36. *
  37. * * set template: `"template" => "markdown"`
  38. * * add extension:
  39. *
  40. * ```php
  41. * $sami["twig"]->addExtension(new Markdown\SamiTwigExtension());
  42. * ```
  43. *
  44. * @see example.conf.php
  45. * @author Joachim M. Giaever (joachim[]giaever.org)
  46. * @version 0.1
  47. */
  48. class SamiTwigExtension extends \Twig_Extension {
  49. /**
  50. * Setting pretty-print to true will print
  51. * tables in the read me into a pretty printet format,
  52. * which makes it easier to read.
  53. *
  54. * @static
  55. * @access public
  56. */
  57. public static $pretty_print = false;
  58. /**
  59. * @see $pretty_print
  60. */
  61. public function __construct(bool $pretty_print = false) {
  62. self::$pretty_print = $pretty_print;
  63. }
  64. /**
  65. * Set the functions available for the template engine.
  66. * @return
  67. */
  68. public function getFunctions() {
  69. return array(
  70. new \Twig_Function("toc", array($this, "toc")),
  71. new \Twig_Function("render", array($this, "render"))
  72. );
  73. }
  74. /**
  75. * Checks if var is a Sami-reflection type.
  76. *
  77. * Makes it easier to determine if the variable is
  78. * a string (namespace) or a Reflection-type.
  79. *
  80. * @param $var
  81. * @return bool
  82. */
  83. private static function isReflection($var) {
  84. return $var instanceof Reflection || $var instanceof HintReflection;
  85. }
  86. /**
  87. * Join an array into a string.
  88. *
  89. * @param array $arr The array to join
  90. * @param string $pad Padding letter (typically a asterisk etc)
  91. * @param string $sep Separator, before the padding
  92. * @param string $esep Ending separator
  93. * @param string $epad Ending pad
  94. * @return null On empty array
  95. * @return string
  96. */
  97. private static function join(array $arr, string $pad = "", string $sep = "\n", string $esep = "", string $epad = "") {
  98. if (empty($arr))
  99. return null;
  100. return $sep . $pad . join($sep . $pad, $arr) . $esep . $epad;
  101. }
  102. /**
  103. * Returns x numbers of tabulations.
  104. *
  105. * @param int $depth
  106. * @return string
  107. */
  108. private static function tab(int $depth) {
  109. return str_repeat("\t", $depth);
  110. }
  111. /**
  112. * Returns the short description of a reflection.
  113. *
  114. * @todo Implement $max
  115. * @param \Sami\Reflection\Reflection $refl Reflection to return description of
  116. * @param bool $oneliner Removes every newline and tabulation.
  117. * @param int $max Maximum of letters
  118. * @return string|null Null on empty
  119. */
  120. public static function short_description(Reflection $refl, bool $oneliner = true, int $max = -1) {
  121. return !empty($desc = $refl->getShortDesc()) ? (
  122. $oneliner ? str_replace(array("\n", "\r", "\t"), "", $desc) : $desc
  123. ) : null;
  124. }
  125. /**
  126. * Returns a long description (both short and long) of a reflection.
  127. *
  128. * @param \Sami\Reflection\Reflection $refl Reflection to return description of
  129. * @param bool $oneliner Removes every newline and tabulation
  130. * @return string|null Null on empty
  131. */
  132. public static function long_description(Reflection $refl, bool $oneliner = false) {
  133. return !empty($desc = self::short_description($refl, false) . (!empty($refl->getLongDesc()) ? "\n\n" . $refl->getLongDesc() : null)) ? (
  134. $oneliner ? str_replace(array("\n", "\r", "\t"), "", $desc) : $desc
  135. ) : null;
  136. }
  137. /**
  138. * Returnes a deprecated label if class, method etc is.
  139. *
  140. * If `$notice` is false, it will include the deprecated
  141. * note - if given in the documentation.
  142. *
  143. * @param \Sami\Reflection\Reflection Reflection
  144. * @param bool $notice Just as notice
  145. * @return string|null Null on none
  146. */
  147. public static function deprecated(Reflection $refl, $notice = true) {
  148. if (empty($refl->getDeprecated()))
  149. return null;
  150. if ($notice)
  151. return "`@deprecated`";
  152. return sprintf(
  153. "`@deprecated`: %s",
  154. join(" ", $refl->getDeprecated()[0])
  155. );
  156. }
  157. public static function todo(Reflection $refl) {
  158. return (empty($todo = $refl->getTodo())) ? $todo : null;
  159. }
  160. /**
  161. * Returnes a markdown link.
  162. *
  163. * To match the markdown template classes is linked to by
  164. * `#classname-namespace`, and methods `#method-namespace\classname`
  165. * and namespaces is linked to by `#namespace`, `$namespace` must be set
  166. * to true when linking to it.
  167. *
  168. * @param string $ltxt The link text
  169. * @param string $lurl The link destination
  170. * @param bool $namespace True when linking to a namespace
  171. * @param string $desc Link title (like the html title/hover-tag)
  172. * @return string
  173. **/
  174. public static function href(string $ltxt, string $lurl, bool $namespace = false, string $desc = null) {
  175. $desc = $desc ? sprintf(' "%s"', $desc) : null;
  176. // Not linking a namespace directly
  177. if (!$namespace) {
  178. // Not within this package
  179. if (strpos($lurl, "\\") === false)
  180. return sprintf("[%s](https://www.google.no/search?q=%s%s)", $ltxt, rawurlencode($lurl), $desc);
  181. else // Withing package; set `name`-`namespace` order
  182. $lurl = sprintf(
  183. "%s %s",
  184. substr($lurl, strrpos($lurl, "\\")),
  185. substr($lurl, 0, strrpos($lurl, "\\", 1))
  186. );
  187. }
  188. return sprintf(
  189. "[%s](#%s%s)",
  190. $ltxt,
  191. strtolower(
  192. str_replace(
  193. array("\\", " "), // Replace "\" = "" and " " = "-"
  194. array("", "-"),
  195. $lurl
  196. )
  197. ),
  198. $desc
  199. );
  200. }
  201. /**
  202. * Tabel of contents
  203. *
  204. * Generates a table of contentes out of the whole
  205. * project tree.
  206. *
  207. * @param array $tree The tree array passed from twig
  208. * @param int depth Initially this should be 0
  209. * @return string
  210. **/
  211. public static function toc(array $tree, int $depth = 0) {
  212. // Appending to given string
  213. $append = (function(string &$into, int $depth, int $idx, array $elem) {
  214. // Create a link to this entry
  215. $element = self::href($elem[0], $elem[1], is_string($elem[1]), $elem[1]);
  216. // Get deprecated notice and short description
  217. if (self::isReflection($elem[1])) {
  218. if (($dep = self::deprecated($elem[1])))
  219. $element .= " " . $dep;
  220. if (($desc = self::short_description($elem[1])))
  221. $element .= " " . $desc;
  222. }
  223. $into .= sprintf("%s%d. %s\n", self::tab($depth), $idx + 1, $element);
  224. });
  225. $str = "";
  226. foreach ($tree as $key => $elem) {
  227. $append($str, $depth, $key, $elem);
  228. if (isset($elem[2]) && !empty($elem[2])) {
  229. usort($elem[2], function($a, $b) {
  230. return strcmp("" . $a[1], "" . $b[1]);
  231. });
  232. foreach($elem[2] as $key2 => $elem2) {
  233. $append($str, ($depth+1), $key2, $elem2);
  234. if (isset($elem2[2]) && !empty($elem2[2]))
  235. $str .= self::toc($elem2[2], ($depth+2));
  236. }
  237. }
  238. }
  239. return $str;
  240. }
  241. /**
  242. * Get hints of a param.
  243. *
  244. * This could be `string`, `bool` etc or several if an parameter
  245. * can be `mixed`. If it's not stated in the functions signature,
  246. * the hint will automatically be `mixed`.
  247. *
  248. * If the hint is a part of this package (root namespace), and
  249. * `link` is set to `true` it will return an internal link to the type,
  250. * but if link is set to true and the type is
  251. *
  252. * * [...](http://php.net/manual/en/functions.arguments.php#functions.variable-arg-list),
  253. * * [iterable](http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration.types) or
  254. * * something else
  255. *
  256. * it will either return a link to this type or add a Google search query-link.
  257. *
  258. * @param \Sami\Reflection\ParameterReflection $param The parameter
  259. * @param bool $link Set to true to link
  260. * @return string
  261. */
  262. public static function param_hint(ParameterReflection $param, bool $link = false) {
  263. $hints = array();
  264. // Loop through hints
  265. foreach ($param->getHint() as $hint)
  266. $hints[] = (function() use ($hint, $link) {
  267. // If hint is a class (e.g \Sami\Reflection\ClassReflection)
  268. if ($hint->isClass()) {
  269. /**
  270. * Sami doesnt know "..." (variable arg list) and "iterable".
  271. * It belives it's a class/method or something within the scope
  272. * of a namespace.
  273. */
  274. if ($link && strrpos($hint, "...") == (strlen($hint) - 3))
  275. return "[...](http://php.net/manual/en/functions.arguments.php#functions.variable-arg-list)";
  276. else if (strrpos($hint, "...") == (strlen($hint) - 3))
  277. return "...";
  278. if ($link && strrpos($hint, "iterable") == (strlen($hint) - 8))
  279. return "[iterable](http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration.types)";
  280. else if (strrpos($hint, "iterable") == (strlen($hint) - 8))
  281. return "iterable";
  282. // Return name+link (if true) or just name of reference
  283. if ($link && ($pos = strrpos($hint, "\\")) !== false && !empty($ns = $hint->getName()->getNamespace()))
  284. return self::href(substr($hint, $pos + 1), $hint->getName()->getNamespace());
  285. else if (($pos = strrpos($hint, "\\")) !== false)
  286. return substr($hint, $pos + 1);
  287. }
  288. return $hint;
  289. })();
  290. return !empty($hints) ? join(" ", $hints) . "" : "mixed";
  291. }
  292. /**
  293. * Get default for parameter.
  294. *
  295. * @param \Sami\Reflection\ParameterReflection $param
  296. * @return string|null Null on empty
  297. */
  298. public static function param_default(ParameterReflection $param) {
  299. return !empty($param->getDefault()) ? $param->getDefault() : null;
  300. }
  301. /**
  302. * Get the methods hints.
  303. *
  304. * This hints is typically what a method returns, e.g `string`, `bool` etc.
  305. *
  306. * Method works similar as @see param_hint
  307. *
  308. * @param \Sami\Reflection\MethodReflection $method
  309. * @param bool $link = false
  310. * @return string
  311. */
  312. public static function method_hint(MethodReflection $method, bool $link = false) {
  313. $hints = array();
  314. // Loop through hintss
  315. foreach ($method->getHint() as $hint)
  316. $hints[] = (function() use ($hint, $link) {
  317. // If class; get name+link if true or just reference name.
  318. if ($hint->isClass()) {
  319. $name = substr($hint->getName()->getName(), strrpos($hint->getName()->getName(), "\\") + 1);
  320. return $link ? self::href($name, $hint->getName()->getName()) : $name;
  321. }
  322. return $hint;
  323. })();
  324. // Join with v (mathically OR) to not break tables.
  325. return join(" ***v*** ", $hints);
  326. }
  327. /**
  328. * Get access to method.
  329. *
  330. * Returns if a method is abstract, final, protected etc. Access
  331. * to a method can be a mix and this method will include every.
  332. *
  333. * @param MethodReflection $method
  334. * @return string
  335. */
  336. public static function method_access(MethodReflection $method) {
  337. $sign = array();
  338. if ($method->isAbstract())
  339. $sign[] = "abstract";
  340. if ($method->isFinal())
  341. $sign[] = "final";
  342. if ($method->isProtected())
  343. $sign[] = "protected";
  344. if ($method->isPrivate())
  345. $sign[] = "private";
  346. if ($method->isPublic())
  347. $sign[] = "public";
  348. if ($method->isStatic())
  349. $sign[] = "static";
  350. return join(" ", $sign);
  351. }
  352. /**
  353. * Get signature of a method.
  354. *
  355. * Returns the function name, parameters and access. It
  356. * also includes default parameter values if `$incname` is
  357. * set to true.
  358. *
  359. * The format will be
  360. * ```php
  361. * access function name(paramterers [= "value"]);
  362. * ```
  363. *
  364. * @param \Sami\Reflection\MethodReflection $method Method reflection
  365. * @param bool $incname Adds default parameter values on true
  366. * @return string
  367. */
  368. public static function method_signature(MethodReflection $method, bool $incname = true) {
  369. $sign = array();
  370. // Loop through params
  371. foreach ($method->getParameters() as $param)
  372. $sign [] = sprintf("%s%s",
  373. self::param_hint($param),
  374. (function() use ($param, $incname) {
  375. if (!$incname)
  376. return null;
  377. $var = sprintf(" $%s", $param->getName());
  378. if (!empty($param->getDefault()))
  379. $var = sprintf("%s = %s", $var, $param->getDefault());
  380. return $var;
  381. })($param)
  382. );
  383. return sprintf("%s(%s);", $method->getName(), join(", ", $sign));
  384. }
  385. /**
  386. * Return a link to method in source code.
  387. *
  388. * @todo Investigate! Doesnt work as expected. sourcePath is always `null`.
  389. *
  390. * @param \Sami\Reflection MethodReflection $method Method reflection
  391. * @return string
  392. */
  393. public static function method_source_url(MethodReflection $method) {
  394. if (empty($method->getClass()->getSourcePath()))
  395. return null;
  396. return "\n> [File: ok.php#L](" . $method->getClass()->getSourcePath() . ")";
  397. }
  398. /**
  399. * Pretty print table.
  400. *
  401. * Makes a better readable format of a table when
  402. * reading the source-code of the markdown directly.
  403. *
  404. * Note that each array-entry can contain several rows, but
  405. * rows must the be separated with `\n`.
  406. *
  407. * @param array &$arr Array containg each row in a table
  408. */
  409. private static function pp_tbl(array &$arr) {
  410. if (empty($arr) || !self::$pretty_print)
  411. return;
  412. // Count rows
  413. $cols = substr_count(explode("\n", $arr[0])[0], "|");
  414. // And fill an array
  415. $cmax = array_fill(0, $cols, 0);
  416. // Loop through each entry in array
  417. foreach($arr as $key => $line) {
  418. $h = 0; // Last column separator position
  419. $col = 0;
  420. // And each character in entry
  421. for($i = 0; $i < strlen($line); $i++) {
  422. // To work with entries with several rows in an entry
  423. if ($line[$i] == "\n") {
  424. $h = $i;
  425. continue;
  426. }
  427. // Hit column separator
  428. if ($line[$i] == "|") {
  429. // Find longes column
  430. if (($i-$h) > $cmax[$col % $cols])
  431. $cmax[$col % $cols] = ($i - $h - 1);
  432. $h = $i;
  433. $col++;
  434. }
  435. }
  436. }
  437. // Do the same as above
  438. foreach($arr as $key => $line) {
  439. $h = 0; // Last column separator position
  440. $col = 0;
  441. // Clear array entry
  442. $arr[$key] = "";
  443. for($i = 0; $i < strlen($line); $i++) {
  444. if ($line[$i] == "\n") {
  445. $arr[$key] .= "|";
  446. $h = $i;
  447. continue;
  448. }
  449. if ($line[$i] == "|") {
  450. // Get the conten from $h to $i (content of column)
  451. $lead = substr($line, $h, $i - $h);
  452. // Check if it must be padded with spaces
  453. if (($i - $h) < $cmax[$col % $cols])
  454. $lead .= str_repeat(" ", $cmax[$col % $cols] - ($i - $h) + 1);
  455. // Restore array entry
  456. $arr[$key] .= $lead . (($i + 1) == strlen($line) ? "|" : "");
  457. $h = $i;
  458. $col++;
  459. }
  460. }
  461. }
  462. }
  463. /**
  464. * Render methods.
  465. *
  466. * Returns a summary and detailed description of every
  467. * method in the method array.
  468. *
  469. * @param array $methods
  470. * @return string|null Null on empty
  471. */
  472. public static function render_methods(array $methods) {
  473. // No methods here... return
  474. if (empty($methods))
  475. return null;
  476. // Table layout | left padding | left p | left p | left p |
  477. $tbl = array("|Name|Return|Access|Description|\n|:---|:---|:---|:---|");
  478. // Create the summary
  479. foreach ($methods as $method)
  480. $tbl[] = sprintf("|%s|%s|%s| %s|",
  481. self::href($method->getName(), $method->getClass()->getName() . "\\" . $method->getName()),
  482. self::method_hint($method, true),
  483. self::method_access($method),
  484. self::short_description($method)
  485. );
  486. // Fix layout of table
  487. self::pp_tbl($tbl);
  488. $details = array();
  489. // Create descriptions
  490. foreach ($methods as $method) {
  491. $details[] = sprintf("\n##### %s `%s`\n```php\n%s function %s\n```%s%s%s%s%s%s\n---\n",
  492. $method->getName(), $method->getClass()->getName(),
  493. self::method_access($method),
  494. self::method_signature($method),
  495. // Method description, padded left with > (block/indent)
  496. (function() use ($method) {
  497. if (empty(self::long_description($method)))
  498. return null;
  499. return "\n " . self::long_description($method) . "\n";
  500. })(),
  501. // TODO: Debug this. No output?
  502. self::method_source_url($method),
  503. // Method parameters in table with description
  504. (function() use ($method) {
  505. // No parameters, skip
  506. if (empty($method->getParameters()))
  507. return null;
  508. // Layout table
  509. $params = array("| Type | Variable | Description |", "|---|---|---|");
  510. foreach ($method->getParameters() as $param)
  511. $params[] = sprintf("|%s|$%s|%s|",
  512. self::param_hint($param, true),
  513. $param->getName(),
  514. !empty(self::long_description($param)) ? self::long_description($param) : '*None*'
  515. );
  516. self::pp_tbl($params);
  517. array_unshift($params, "Parameters\n");
  518. // Return padded..
  519. return "\n" . self::join($params);
  520. })(),
  521. // Method returns
  522. (!empty($method->getHint()) ? "\n\nReturns: " . self::method_hint($method, true) . "\n" : null),
  523. // Method throws
  524. (function () use ($method) {
  525. // Nothing... skip
  526. if (empty($method->getExceptions()))
  527. return null;
  528. // Return exceptions padded whith > and linked to...
  529. return "\n> Throws: " . (function() use ($method) {
  530. $links = array();
  531. foreach($method->getExceptions() as $Exception)
  532. $links[] = self::href($Exception[0]->getShortName(), $Exception[0]->getName(), false, "Exception: " . $Exception[0]->getName());
  533. return "\n> * " . join("\n> * ", $links) . "\n";
  534. })();
  535. })(),
  536. (function() use ($method) {
  537. if (empty(self::todo($method)))
  538. return null;
  539. return "\n> Todo: " . self::join(self::todo($method), "> * ") . "\n";
  540. })()
  541. );
  542. }
  543. if (!empty($details))
  544. array_unshift($details, "\n#### Method details");
  545. return sprintf(
  546. "\n#### Methods\n%s%s",
  547. self::join($tbl),
  548. self::join($details)
  549. );
  550. }
  551. /**
  552. * Render class
  553. *
  554. * Returns information about a class including it's methods.
  555. *
  556. * @param \Sami\Reflection\ClassReflection $class Class reflection
  557. * @return string
  558. */
  559. public static function render_class(ClassReflection $class) {
  560. return sprintf(
  561. "\n### %s `%s`\n%s%s%s",
  562. $class->getShortName(),
  563. $class->getNamespace(),
  564. // Get class info
  565. (function() use ($class) {
  566. $notes = array();
  567. if (!empty($depr = self::deprecated($class, false)))
  568. $notes[] = $depr;
  569. if ($class->isAbstract())
  570. $notes[] = "Class is abstact";
  571. if ($class->isFinal())
  572. $notes[] = "Class is final";
  573. if (!empty($class->getParent()))
  574. $notes[] = sprintf("Class extends %s", self::href($class->getParent()->getName(), $class->getParent()->getName()));
  575. if (!empty($ifaces = $class->getInterfaces(true))) {
  576. $interfaces = array();
  577. foreach($ifaces as $interface)
  578. $interfaces[] = self::href($interface->getName(), $interface->getName());
  579. if (count($interfaces) == 1)
  580. $notes[] = "Class implements" . $interfaces[0];
  581. else
  582. $notes[] = "Class implements" . self::join($interfaces, "* ", "\n\t");
  583. }
  584. if (!empty($tr = $class->getTraits())) {
  585. $traits = array();
  586. foreach($tr as $trait)
  587. $traits[] = self::href($trait->getName(), $trait->getName());
  588. if (count($traits) == 1)
  589. $notes[] = "Class uses " . $traits[0];
  590. else
  591. $notes[] = "Class uses" . self::join($traits, "* ", "\n\t");
  592. }
  593. if (empty($notes))
  594. return null;
  595. return "\n" . self::join($notes, "* ", "\n") . "\n";
  596. })(),
  597. // Get full description
  598. (function() use ($class) {
  599. if (empty(self::long_description($class)))
  600. return null;
  601. return "\n" . self::long_description($class) . "\n";
  602. })(),
  603. self::render_methods($class->getMethods())
  604. );
  605. }
  606. /**
  607. * Render one or more classes.
  608. *
  609. * Should typically be used for a single namespace at the time.
  610. *
  611. * Determines which kind of class (e.g trait, interface etc)
  612. * and returns them in the structure/order
  613. *
  614. * Namespace
  615. * * Normal classes,
  616. * * Traits,
  617. * * Interfaces,
  618. * * Exceptions.
  619. *
  620. * @param array $classes Array with ClassReflection
  621. * @return string|null Null on empty
  622. */
  623. public static function render_classes(array $classes) {
  624. if (empty($classes))
  625. return null;
  626. $traits = array();
  627. $exceptions = array();
  628. $interfaces = array();
  629. // Separate classes
  630. foreach($classes as $name => $class) {
  631. if ($class->isTrait()) {
  632. $traits[] = $class;
  633. unset($classes[$name]);
  634. } else if ($class->isException() || strpos($class->getNamespace(), "Exception") !== false) {
  635. $exceptions[] = $class;
  636. unset($classes[$name]);
  637. } else if ($class->isInterface()) {
  638. $interfaces[] = $class;
  639. unset($classes[$name]);
  640. }
  641. }
  642. if (!empty($classes)) {
  643. foreach($classes as $name => $class)
  644. $classes[$name] = self::render_class($class);
  645. array_unshift($classes, "## Classes");
  646. }
  647. if (empty($traits) && empty($exceptions) && empty($interfaces) && empty($classes))
  648. return null;
  649. if (!empty($traits)) {
  650. foreach($traits as $key => $trait)
  651. $traits[$key] = self::render_class($trait);
  652. array_unshift($t, "## Traits");
  653. }
  654. if (!empty($interfaces)) {
  655. foreach($interfaces as $key => $interface)
  656. $interfaces[$key] = self::render_class($interface);
  657. array_unshift($i, "## Interfaces");
  658. }
  659. if (!empty($exceptions)) {
  660. foreach($exceptions as $key => $exception)
  661. $exceptions[$key] = self::render_class($exception);
  662. array_unshift($e, "## Exceptions");
  663. }
  664. return sprintf("%s%s%s%s",
  665. self::join($classes),
  666. self::join($traits),
  667. self::join($interfaces),
  668. self::join($exceptions));
  669. }
  670. /**
  671. * Render namespace.
  672. *
  673. * Returns information about the whole namespace, as long as
  674. * it's sub-namespaces and classes is passed along to the method.
  675. *
  676. * @param string $namespace The name of the namespace
  677. * @param array $namespaces Array with names of sub-namespaces
  678. * @param array $classes Array with ClassReflections
  679. * @return string
  680. */
  681. public static function render_namespace(string $namespace, array $namespaces, array $classes) {
  682. return sprintf(
  683. "\n# %s\n%s",
  684. $namespace,
  685. empty($classes) ? (
  686. function () use ($namespaces) {
  687. if (empty($namespaces))
  688. return null;
  689. $links = array();
  690. foreach ($namespaces as $namespace)
  691. array_push($links, self::href($namespace, $namespace, true, "Namespace: " . $namespace));
  692. return "\n * " . join("\n * ", $links) . "\n";
  693. }
  694. )(): self::render_classes($classes)
  695. );
  696. }
  697. /**
  698. * Render the whole ReadMe.
  699. *
  700. * Will bind classes and it's sub-namespaces and render namespace
  701. * for namespace.
  702. *
  703. * @param array $namespaces Array with names of namespaces
  704. * @param array $classes Array with ClassReflections
  705. * @return string
  706. */
  707. public static function render(array $namespaces, array $classes) {
  708. $str = "";
  709. foreach($namespaces as $namespace) {
  710. $nsclasses = array();
  711. $nss = array();
  712. foreach($classes as $name => $class) {
  713. if ($class->getNamespace() == $namespace) {
  714. $nsclasses[$name] = $class;
  715. unset($classes[$name]);
  716. }
  717. if (!empty($class->getNamespace()) && !empty($namespace)) {
  718. if (!in_array($class->getNamespace(), $nss) && strpos($class->getNamespace(), $namespace) !== false)
  719. $nss[$name] = $class->getNamespace();
  720. }
  721. }
  722. $str .= self::render_namespace($namespace, $nss, $nsclasses);
  723. }
  724. return $str . "\n\n - Genetated using Sami and the [Sami/Twig Markdown Extension](https://git.giaever.org/joachimmg/sami-markdown)";
  725. }
  726. }
  727. ?>