SamiTwigExtension.php 30 KB

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