/** * Generates Markdown from JSDoc entries. * * @memberOf MarkdownGenerator * @returns {string} The rendered Markdown. */ public function generate() { $api = array(); $byCategory = $this->options['toc'] == 'categories'; $categories = array(); $closeTag = $this->closeTag; $compiling = false; $openTag = $this->openTag; $result = array('# ' . $this->options['title']); $toc = 'toc'; // initialize $api array foreach ($this->entries as $entry) { // skip invalid or private entries $name = $entry->getName(); if (!$name || $entry->isPrivate()) { continue; } $members = $entry->getMembers(); $members = count($members) ? $members : array(''); foreach ($members as $member) { // create api category arrays if ($member && !isset($api[$member])) { // create temporary entry to be replaced later $api[$member] = new stdClass(); $api[$member]->static = array(); $api[$member]->plugin = array(); } // append entry to api member if (!$member || $entry->isCtor() || $entry->getType() == 'Object' && !preg_match('/[=:]\\s*(?:null|undefined)\\s*[,;]?$/', $entry->entry)) { // assign the real entry, replacing the temporary entry if it exist $member = ($member ? $member . ($entry->isPlugin() ? '#' : '.') : '') . $name; $entry->static = @$api[$member] ? $api[$member]->static : array(); $entry->plugin = @$api[$member] ? $api[$member]->plugin : array(); $api[$member] = $entry; foreach ($entry->getAliases() as $alias) { $api[$member]->static[] = $alias; } } else { if ($entry->isStatic()) { $api[$member]->static[] = $entry; foreach ($entry->getAliases() as $alias) { $api[$member]->static[] = $alias; } } else { if (!$entry->isCtor()) { $api[$member]->plugin[] = $entry; foreach ($entry->getAliases() as $alias) { $api[$member]->plugin[] = $alias; } } } } } } // add properties to each entry foreach ($api as $entry) { $entry->hash = $this->getHash($entry); $entry->href = $this->getLineUrl($entry); $member = $entry->getMembers(0); $member = ($member ? $member . $this->getSeparator($entry) : '') . $entry->getName(); $entry->member = preg_replace('/' . $entry->getName() . '$/', '', $member); // add properties to static and plugin sub-entries foreach (array('static', 'plugin') as $kind) { foreach ($entry->{$kind} as $subentry) { $subentry->hash = $this->getHash($subentry); $subentry->href = $this->getLineUrl($subentry); $subentry->member = $member; $subentry->separator = $this->getSeparator($subentry); } } } /*------------------------------------------------------------------------*/ // custom sort for root level entries // TODO: see how well it handles deeper namespace traversal function sortCompare($a, $b) { $score = array('a' => 0, 'b' => 0); foreach (array('a' => $a, 'b' => $b) as $key => $value) { // capitalized properties are last if (preg_match('/[#.][A-Z]/', $value)) { $score[$key] = 0; } else { if (preg_match('/#[a-z]/', $value)) { $score[$key] = 1; } else { if (preg_match('/\\.[a-z]/', $value)) { $score[$key] = 2; } else { if (preg_match('/^[^#.]+$/', $value)) { $score[$key] = 3; } } } } } $score = $score['b'] - $score['a']; return $score ? $score : strcasecmp($a, $b); } uksort($api, 'sortCompare'); // sort static and plugin sub-entries foreach ($api as $entry) { foreach (array('static', 'plugin') as $kind) { $sortBy = array('a' => array(), 'b' => array(), 'c' => array()); foreach ($entry->{$kind} as $subentry) { $name = $subentry->getName(); // functions w/o ALL-CAPs names are last $sortBy['a'][] = $subentry->getType() == 'Function' && !preg_match('/^[A-Z_]+$/', $name); // ALL-CAPs properties first $sortBy['b'][] = preg_match('/^[A-Z_]+$/', $name); // lowercase alphanumeric sort $sortBy['c'][] = strtolower($name); } array_multisort($sortBy['a'], SORT_ASC, $sortBy['b'], SORT_DESC, $sortBy['c'], SORT_ASC, $entry->{$kind}); } } /*------------------------------------------------------------------------*/ // add categories foreach ($api as $entry) { $categories[$entry->getCategory()][] = $entry; foreach (array('static', 'plugin') as $kind) { foreach ($entry->{$kind} as $subentry) { $categories[$subentry->getCategory()][] = $subentry; } } } // sort categories ksort($categories); foreach (array('Methods', 'Properties') as $category) { if (isset($categories[$category])) { $entries = $categories[$category]; unset($categories[$category]); $categories[$category] = $entries; } } /*------------------------------------------------------------------------*/ // compile TOC $result[] = $openTag; // compile TOC by categories if ($byCategory) { foreach ($categories as $category => $entries) { if ($compiling) { $result[] = $closeTag; } else { $compiling = true; } // assign TOC hash if (count($result) == 2) { $toc = $category; } // add category array_push($result, $openTag, '## ' . (count($result) == 2 ? '<a id="' . $toc . '"></a>' : '') . '`' . $category . '`'); // add entries foreach ($entries as $entry) { $result[] = MarkdownGenerator::interpolate('* [`#{member}#{separator}#{name}`](##{hash})', $entry); } } } else { foreach ($api as $entry) { if ($compiling) { $result[] = $closeTag; } else { $compiling = true; } $member = $entry->member . $entry->getName(); // assign TOC hash if (count($result) == 2) { $toc = $member; } // add root entry array_push($result, $openTag, '## ' . (count($result) == 2 ? '<a id="' . $toc . '"></a>' : '') . '`' . $member . '`', MarkdownGenerator::interpolate('* [`' . $member . '`](##{hash})', $entry)); // add static and plugin sub-entries foreach (array('static', 'plugin') as $kind) { if ($kind == 'plugin' && count($entry->plugin)) { array_push($result, $closeTag, $openTag, '## `' . $member . ($entry->isCtor() ? '.prototype`' : '`')); } foreach ($entry->{$kind} as $subentry) { $subentry->member = $member; $result[] = MarkdownGenerator::interpolate('* [`#{member}#{separator}#{name}`](##{hash})', $subentry); } } } } array_push($result, $closeTag, $closeTag); /*------------------------------------------------------------------------*/ // compile content $compiling = false; $result[] = $openTag; if ($byCategory) { foreach ($categories as $category => $entries) { if ($compiling) { $result[] = $closeTag; } else { $compiling = true; } if ($category != 'Methods' && $category != 'Properties') { $category = '“' . $category . '” Methods'; } array_push($result, $openTag, '## `' . $category . '`'); $this->addEntries($result, $entries); } } else { foreach ($api as $entry) { // skip aliases if ($entry->isAlias()) { continue; } if ($compiling) { $result[] = $closeTag; } else { $compiling = true; } // add root entry name $member = $entry->member . $entry->getName(); array_push($result, $openTag, '## `' . $member . '`'); foreach (array($entry, 'static', 'plugin') as $kind) { $subentries = is_string($kind) ? $entry->{$kind} : array($kind); // add sub-entry name if ($kind != 'static' && $entry->getType() != 'Object' && count($subentries) && $subentries[0] != $kind) { if ($kind == 'plugin') { $result[] = $closeTag; } array_push($result, $openTag, '## `' . $member . ($kind == 'plugin' ? '.prototype`' : '`')); } $this->addEntries($result, $subentries); } } } // close tags add TOC link reference array_push($result, $closeTag, $closeTag, '', ' [1]: #' . $toc . ' "Jump back to the TOC."'); // cleanup whitespace return trim(preg_replace('/[\\t ]+\\n/', "\n", join($result, "\n"))); }
/** * Generates Markdown from JSDoc entries in a given file. * * @param {Array} [$options=array()] The options array. * @returns {string} The generated Markdown. * @example * * // specify a file path * $markdown = docdown(array( * // path to js file * 'path' => $filepath, * // url used to reference line numbers in code * 'url' => 'https://github.com/username/project/blob/master/my.js' * )); * * // or pass raw js * $markdown = docdown(array( * // raw JavaScript source * 'source' => $rawJS, * // documentation title * 'title' => 'My API Documentation', * // url used to reference line numbers in code * 'url' => 'https://github.com/username/project/blob/master/my.js' * )); */ function docdown($options = array()) { $gen = new MarkdownGenerator($options); return $gen->generate(); }