PageRenderTime 49ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/node_modules/grunt-contrib-imagemin/node_modules/pngquant-bin/node_modules/bin-wrapper/node_modules/download/node_modules/request/node_modules/tough-cookie/node_modules/punycode/vendor/docdown/src/DocDown/Generator.php

https://gitlab.com/marvin1/soundcloud
PHP | 563 lines | 348 code | 61 blank | 154 comment | 79 complexity | 3a0b8f5d3aac2bc1d3db3a3ad7cba365 MD5 | raw file
  1. <?php
  2. require(dirname(__FILE__) . "/Entry.php");
  3. /**
  4. * Generates Markdown from JSDoc entries.
  5. */
  6. class Generator {
  7. /**
  8. * The HTML for the close tag.
  9. *
  10. * @static
  11. * @memberOf Generator
  12. * @type String
  13. */
  14. public $closeTag = "\n<!-- /div -->\n";
  15. /**
  16. * An array of JSDoc entries.
  17. *
  18. * @memberOf Generator
  19. * @type Array
  20. */
  21. public $entries = array();
  22. /**
  23. * The HTML for the open tag.
  24. *
  25. * @static
  26. * @memberOf Generator
  27. * @type String
  28. */
  29. public $openTag = "\n<!-- div -->\n";
  30. /**
  31. * An options array used to configure the generator.
  32. *
  33. * @memberOf Generator
  34. * @type Array
  35. */
  36. public $options = array();
  37. /**
  38. * The file's source code.
  39. *
  40. * @memberOf Generator
  41. * @type String
  42. */
  43. public $source = '';
  44. /*--------------------------------------------------------------------------*/
  45. /**
  46. * The Generator constructor.
  47. *
  48. * @constructor
  49. * @param {String} $source The source code to parse.
  50. * @param {Array} $options The options array.
  51. */
  52. public function __construct( $source, $options = array() ) {
  53. // juggle arguments
  54. if (is_array($source)) {
  55. $options = $source;
  56. } else {
  57. $options['source'] = $source;
  58. }
  59. if (isset($options['source']) && realpath($options['source'])) {
  60. $options['path'] = $options['source'];
  61. }
  62. if (isset($options['path'])) {
  63. preg_match('/(?<=\.)[a-z]+$/', $options['path'], $ext);
  64. $options['source'] = file_get_contents($options['path']);
  65. $ext = array_pop($ext);
  66. if (!isset($options['lang']) && $ext) {
  67. $options['lang'] = $ext;
  68. }
  69. if (!isset($options['title'])) {
  70. $options['title'] = ucfirst(basename($options['path'])) . ' API documentation';
  71. }
  72. }
  73. if (!isset($options['lang'])) {
  74. $options['lang'] = 'js';
  75. }
  76. if (!isset($options['toc'])) {
  77. $options['toc'] = 'properties';
  78. }
  79. $this->options = $options;
  80. $this->source = str_replace(PHP_EOL, "\n", $options['source']);
  81. $this->entries = Entry::getEntries($this->source);
  82. foreach ($this->entries as $index => $value) {
  83. $this->entries[$index] = new Entry($value, $this->source, $options['lang']);
  84. }
  85. }
  86. /*--------------------------------------------------------------------------*/
  87. /**
  88. * Performs common string formatting operations.
  89. *
  90. * @private
  91. * @static
  92. * @memberOf Generator
  93. * @param {String} $string The string to format.
  94. * @returns {String} The formatted string.
  95. */
  96. private static function format( $string ) {
  97. $counter = 0;
  98. // tokenize inline code snippets
  99. preg_match_all('/`[^`]+`/', $string, $tokenized);
  100. $tokenized = $tokenized[0];
  101. foreach ($tokenized as $snippet) {
  102. $string = str_replace($snippet, '__token' . ($counter++) .'__', $string);
  103. }
  104. // italicize parentheses
  105. $string = preg_replace('/(^|\s)(\([^)]+\))/', '$1*$2*', $string);
  106. // mark numbers as inline code
  107. $string = preg_replace('/[\t ](-?\d+(?:.\d+)?)(?!\.[^\n])/', ' `$1`', $string);
  108. // detokenize inline code snippets
  109. $counter = 0;
  110. foreach ($tokenized as $snippet) {
  111. $string = str_replace('__token' . ($counter++) . '__', $snippet, $string);
  112. }
  113. return trim($string);
  114. }
  115. /**
  116. * Modify a string by replacing named tokens with matching assoc. array values.
  117. *
  118. * @private
  119. * @static
  120. * @memberOf Generator
  121. * @param {String} $string The string to modify.
  122. * @param {Array|Object} $object The template object.
  123. * @returns {String} The modified string.
  124. */
  125. private static function interpolate( $string, $object ) {
  126. preg_match_all('/#\{([^}]+)\}/', $string, $tokens);
  127. $tokens = array_unique(array_pop($tokens));
  128. foreach ($tokens as $token) {
  129. $pattern = '/#\{' . $token . '\}/';
  130. $replacement = '';
  131. if (is_object($object)) {
  132. preg_match('/\(([^)]+?)\)$/', $token, $args);
  133. $args = preg_split('/,\s*/', array_pop($args));
  134. $method = 'get' . ucfirst(str_replace('/\([^)]+?\)$/', '', $token));
  135. if (method_exists($object, $method)) {
  136. $replacement = (string) call_user_func_array(array($object, $method), $args);
  137. } else if (isset($object->{$token})) {
  138. $replacement = (string) $object->{$token};
  139. }
  140. } else if (isset($object[$token])) {
  141. $replacement = (string) $object[$token];
  142. }
  143. $string = preg_replace($pattern, trim($replacement), $string);
  144. }
  145. return Generator::format($string);
  146. }
  147. /*--------------------------------------------------------------------------*/
  148. /**
  149. * Adds the given `$entries` to the `$result` array.
  150. *
  151. * @private
  152. * @memberOf Generator
  153. * @param {Array} $result The result array to modify.
  154. * @param {Array} $entries The entries to add to the `$result`.
  155. */
  156. private function addEntries( &$result, $entries ) {
  157. foreach ($entries as $entry) {
  158. // skip aliases
  159. if ($entry->isAlias()) {
  160. continue;
  161. }
  162. // name and description
  163. array_push(
  164. $result,
  165. $this->openTag,
  166. Generator::interpolate("### <a id=\"#{hash}\"></a>`#{member}#{separator}#{call}`\n<a href=\"##{hash}\">#</a> [&#x24C8;](#{href} \"View in source\") [&#x24C9;][1]\n\n#{desc}", $entry)
  167. );
  168. // @alias
  169. if (count($aliases = $entry->getAliases())) {
  170. array_push($result, '', '#### Aliases');
  171. foreach ($aliases as $index => $alias) {
  172. $aliases[$index] = $alias->getName();
  173. }
  174. $result[] = '*' . implode(', ', $aliases) . '*';
  175. }
  176. // @param
  177. if (count($params = $entry->getParams())) {
  178. array_push($result, '', '#### Arguments');
  179. foreach ($params as $index => $param) {
  180. $result[] = Generator::interpolate('#{num}. `#{name}` (#{type}): #{desc}', array(
  181. 'desc' => $param[2],
  182. 'name' => $param[1],
  183. 'num' => $index + 1,
  184. 'type' => $param[0]
  185. ));
  186. }
  187. }
  188. // @returns
  189. if (count($returns = $entry->getReturns())) {
  190. array_push(
  191. $result, '',
  192. '#### Returns',
  193. Generator::interpolate('(#{type}): #{desc}', array('desc' => $returns[1], 'type' => $returns[0]))
  194. );
  195. }
  196. // @example
  197. if ($example = $entry->getExample()) {
  198. array_push($result, '', '#### Example', $example);
  199. }
  200. array_push($result, "\n* * *", $this->closeTag);
  201. }
  202. }
  203. /**
  204. * Resolves the entry's hash used to navigate the documentation.
  205. *
  206. * @private
  207. * @memberOf Generator
  208. * @param {Number|Object} $entry The entry object.
  209. * @param {String} $member The name of the member.
  210. * @returns {String} The url hash.
  211. */
  212. private function getHash( $entry, $member = '' ) {
  213. $entry = is_numeric($entry) ? $this->entries[$entry] : $entry;
  214. $member = !$member ? $entry->getMembers(0) : $member;
  215. $result = ($member ? $member . ($entry->isPlugin() ? 'prototype' : '') : '') . $entry->getCall();
  216. $result = preg_replace('/\(\[|\[\]/', '', $result);
  217. $result = preg_replace('/[ =|\'"{}.()\]]/', '', $result);
  218. $result = preg_replace('/[[#,]/', '-', $result);
  219. return strtolower($result);
  220. }
  221. /**
  222. * Resolves the entry's url for the specific line number.
  223. *
  224. * @private
  225. * @memberOf Generator
  226. * @param {Number|Object} $entry The entry object.
  227. * @returns {String} The url.
  228. */
  229. private function getLineUrl( $entry ) {
  230. $entry = is_numeric($entry) ? $this->entries($entry) : $entry;
  231. return $this->options['url'] . '#L' . $entry->getLineNumber();
  232. }
  233. /**
  234. * Extracts the character used to separate the entry's name from its member.
  235. *
  236. * @private
  237. * @memberOf Generator
  238. * @param {Number|Object} $entry The entry object.
  239. * @returns {String} The separator.
  240. */
  241. private function getSeparator( $entry ) {
  242. $entry = is_numeric($entry) ? $this->entries($entry) : $entry;
  243. return $entry->isPlugin() ? '.prototype.' : '.';
  244. }
  245. /*--------------------------------------------------------------------------*/
  246. /**
  247. * Generates Markdown from JSDoc entries.
  248. *
  249. * @memberOf Generator
  250. * @returns {String} The rendered Markdown.
  251. */
  252. public function generate() {
  253. $api = array();
  254. $byCategory = $this->options['toc'] == 'categories';
  255. $categories = array();
  256. $closeTag = $this->closeTag;
  257. $compiling = false;
  258. $openTag = $this->openTag;
  259. $result = array('# ' . $this->options['title']);
  260. $toc = 'toc';
  261. // initialize $api array
  262. foreach ($this->entries as $entry) {
  263. // skip invalid or private entries
  264. $name = $entry->getName();
  265. if (!$name || $entry->isPrivate()) {
  266. continue;
  267. }
  268. $members = $entry->getMembers();
  269. $members = count($members) ? $members : array('');
  270. foreach ($members as $member) {
  271. // create api category arrays
  272. if ($member && !isset($api[$member])) {
  273. // create temporary entry to be replaced later
  274. $api[$member] = new stdClass;
  275. $api[$member]->static = array();
  276. $api[$member]->plugin = array();
  277. }
  278. // append entry to api member
  279. if (!$member || $entry->isCtor() || ($entry->getType() == 'Object' &&
  280. !preg_match('/[=:]\s*(?:null|undefined)\s*[,;]?$/', $entry->entry))) {
  281. // assign the real entry, replacing the temporary entry if it exist
  282. $member = ($member ? $member . ($entry->isPlugin() ? '#' : '.') : '') . $name;
  283. $entry->static = @$api[$member] ? $api[$member]->static : array();
  284. $entry->plugin = @$api[$member] ? $api[$member]->plugin : array();
  285. $api[$member] = $entry;
  286. foreach ($entry->getAliases() as $alias) {
  287. $api[$member]->static[] = $alias;
  288. }
  289. }
  290. else if ($entry->isStatic()) {
  291. $api[$member]->static[] = $entry;
  292. foreach ($entry->getAliases() as $alias) {
  293. $api[$member]->static[] = $alias;
  294. }
  295. }
  296. else if (!$entry->isCtor()) {
  297. $api[$member]->plugin[] = $entry;
  298. foreach ($entry->getAliases() as $alias) {
  299. $api[$member]->plugin[] = $alias;
  300. }
  301. }
  302. }
  303. }
  304. // add properties to each entry
  305. foreach ($api as $entry) {
  306. $entry->hash = $this->getHash($entry);
  307. $entry->href = $this->getLineUrl($entry);
  308. $member = $entry->getMembers(0);
  309. $member = ($member ? $member . ($entry->isPlugin() ? '.prototype.' : '.') : '') . $entry->getName();
  310. $entry->member = preg_replace('/' . $entry->getName() . '$/', '', $member);
  311. // add properties to static and plugin sub-entries
  312. foreach (array('static', 'plugin') as $kind) {
  313. foreach ($entry->{$kind} as $subentry) {
  314. $subentry->hash = $this->getHash($subentry);
  315. $subentry->href = $this->getLineUrl($subentry);
  316. $subentry->member = $member;
  317. $subentry->separator = $this->getSeparator($subentry);
  318. }
  319. }
  320. }
  321. /*------------------------------------------------------------------------*/
  322. // custom sort for root level entries
  323. // TODO: see how well it handles deeper namespace traversal
  324. function sortCompare($a, $b) {
  325. $score = array( 'a' => 0, 'b' => 0);
  326. foreach (array( 'a' => $a, 'b' => $b) as $key => $value) {
  327. // capitalized properties are last
  328. if (preg_match('/[#.][A-Z]/', $value)) {
  329. $score[$key] = 0;
  330. }
  331. // lowercase prototype properties are next to last
  332. else if (preg_match('/#[a-z]/', $value)) {
  333. $score[$key] = 1;
  334. }
  335. // lowercase static properties next to first
  336. else if (preg_match('/\.[a-z]/', $value)) {
  337. $score[$key] = 2;
  338. }
  339. // root properties are first
  340. else if (preg_match('/^[^#.]+$/', $value)) {
  341. $score[$key] = 3;
  342. }
  343. }
  344. $score = $score['b'] - $score['a'];
  345. return $score ? $score : strcasecmp($a, $b);
  346. }
  347. uksort($api, 'sortCompare');
  348. // sort static and plugin sub-entries
  349. foreach ($api as $entry) {
  350. foreach (array('static', 'plugin') as $kind) {
  351. $sortBy = array( 'a' => array(), 'b' => array(), 'c' => array() );
  352. foreach ($entry->{$kind} as $subentry) {
  353. $name = $subentry->getName();
  354. // functions w/o ALL-CAPs names are last
  355. $sortBy['a'][] = $subentry->getType() == 'Function' && !preg_match('/^[A-Z_]+$/', $name);
  356. // ALL-CAPs properties first
  357. $sortBy['b'][] = preg_match('/^[A-Z_]+$/', $name);
  358. // lowercase alphanumeric sort
  359. $sortBy['c'][] = strtolower($name);
  360. }
  361. array_multisort($sortBy['a'], SORT_ASC, $sortBy['b'], SORT_DESC, $sortBy['c'], SORT_ASC, $entry->{$kind});
  362. }
  363. }
  364. /*------------------------------------------------------------------------*/
  365. // add categories
  366. foreach ($api as $entry) {
  367. $categories[$entry->getCategory()][] = $entry;
  368. foreach (array('static', 'plugin') as $kind) {
  369. foreach ($entry->{$kind} as $subentry) {
  370. $categories[$subentry->getCategory()][] = $subentry;
  371. }
  372. }
  373. }
  374. // sort categories
  375. ksort($categories);
  376. foreach(array('Methods', 'Properties') as $category) {
  377. if (isset($categories[$category])) {
  378. $entries = $categories[$category];
  379. unset($categories[$category]);
  380. $categories[$category] = $entries;
  381. }
  382. }
  383. /*------------------------------------------------------------------------*/
  384. // compile TOC
  385. $result[] = $openTag;
  386. // compile TOC by categories
  387. if ($byCategory) {
  388. foreach ($categories as $category => $entries) {
  389. if ($compiling) {
  390. $result[] = $closeTag;
  391. } else {
  392. $compiling = true;
  393. }
  394. // assign TOC hash
  395. if (count($result) == 2) {
  396. $toc = $category;
  397. }
  398. // add category
  399. array_push(
  400. $result,
  401. $openTag, '## ' . (count($result) == 2 ? '<a id="' . $toc . '"></a>' : '') . '`' . $category . '`'
  402. );
  403. // add entries
  404. foreach ($entries as $entry) {
  405. $result[] = Generator::interpolate('* [`#{member}#{separator}#{name}`](##{hash})', $entry);
  406. }
  407. }
  408. }
  409. // compile TOC by namespace
  410. else {
  411. foreach ($api as $entry) {
  412. if ($compiling) {
  413. $result[] = $closeTag;
  414. } else {
  415. $compiling = true;
  416. }
  417. $member = $entry->member . $entry->getName();
  418. // assign TOC hash
  419. if (count($result) == 2) {
  420. $toc = $member;
  421. }
  422. // add root entry
  423. array_push(
  424. $result,
  425. $openTag, '## ' . (count($result) == 2 ? '<a id="' . $toc . '"></a>' : '') . '`' . $member . '`',
  426. Generator::interpolate('* [`' . $member . '`](##{hash})', $entry)
  427. );
  428. // add static and plugin sub-entries
  429. foreach (array('static', 'plugin') as $kind) {
  430. if ($kind == 'plugin' && count($entry->plugin)) {
  431. array_push(
  432. $result,
  433. $closeTag,
  434. $openTag,
  435. '## `' . $member . ($entry->isCtor() ? '.prototype`' : '`')
  436. );
  437. }
  438. foreach ($entry->{$kind} as $subentry) {
  439. $subentry->member = $member;
  440. $result[] = Generator::interpolate('* [`#{member}#{separator}#{name}`](##{hash})', $subentry);
  441. }
  442. }
  443. }
  444. }
  445. array_push($result, $closeTag, $closeTag);
  446. /*------------------------------------------------------------------------*/
  447. // compile content
  448. $compiling = false;
  449. $result[] = $openTag;
  450. if ($byCategory) {
  451. foreach ($categories as $category => $entries) {
  452. if ($compiling) {
  453. $result[] = $closeTag;
  454. } else {
  455. $compiling = true;
  456. }
  457. if ($category != 'Methods' && $category != 'Properties') {
  458. $category = '“' . $category . '” Methods';
  459. }
  460. array_push($result, $openTag, '## `' . $category . '`');
  461. $this->addEntries($result, $entries);
  462. }
  463. }
  464. else {
  465. foreach ($api as $entry) {
  466. // skip aliases
  467. if ($entry->isAlias()) {
  468. continue;
  469. }
  470. if ($compiling) {
  471. $result[] = $closeTag;
  472. } else {
  473. $compiling = true;
  474. }
  475. // add root entry name
  476. $member = $entry->member . $entry->getName();
  477. array_push($result, $openTag, '## `' . $member . '`');
  478. foreach (array($entry, 'static', 'plugin') as $kind) {
  479. $subentries = is_string($kind) ? $entry->{$kind} : array($kind);
  480. // add sub-entry name
  481. if ($kind != 'static' && $entry->getType() != 'Object' &&
  482. count($subentries) && $subentries[0] != $kind) {
  483. if ($kind == 'plugin') {
  484. $result[] = $closeTag;
  485. }
  486. array_push(
  487. $result,
  488. $openTag,
  489. '## `' . $member . ($kind == 'plugin' ? '.prototype`' : '`')
  490. );
  491. }
  492. $this->addEntries($result, $subentries);
  493. }
  494. }
  495. }
  496. // close tags add TOC link reference
  497. array_push($result, $closeTag, $closeTag, '', ' [1]: #' . $toc . ' "Jump back to the TOC."');
  498. // cleanup whitespace
  499. return trim(preg_replace('/[\t ]+\n/', "\n", join($result, "\n")));
  500. }
  501. }
  502. ?>