UNPKG

gulp-docco

Version:
623 lines (450 loc) 29.6 kB
<!DOCTYPE html> <html> <head> <title>Docco</title> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <meta name="viewport" content="width=device-width, target-densitydpi=160dpi, initial-scale=1.0; maximum-scale=1.0; user-scalable=0;"> <link rel="stylesheet" media="all" href="../docco.css" /> </head> <body> <div id="container"> <div id="background"></div> <ul class="sections"> <li id="section-1"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-1">&#182;</a> </div> <h1 id="docco">Docco</h1> </div> </li> <li id="section-2"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-2">&#182;</a> </div> <p><strong>Docco</strong> is a quick-and-dirty documentation generator, written in <a href="http://coffeescript.org/#literate">Literate CoffeeScript</a>. It produces an HTML document that displays your comments intermingled with your code. All prose is passed through <a href="http://daringfireball.net/projects/markdown/syntax">Markdown</a>, and code is passed through <a href="http://highlightjs.org/">Highlight.js</a> syntax highlighting. This page is the result of running Docco against its own <a href="https://github.com/jashkenas/docco/blob/master/docco.litcoffee">source file</a>.</p> <ol> <li><p>Install Docco with <strong>npm</strong>: <code>sudo npm install -g docco</code></p> </li> <li><p>Run it against your code: <code>docco src/*.coffee</code></p> </li> </ol> <p>There is no “Step 3”. This will generate an HTML page for each of the named source files, with a menu linking to the other pages, saving the whole mess into a <code>docs</code> folder (configurable).</p> <p>The <a href="http://github.com/jashkenas/docco">Docco source</a> is available on GitHub, and is released under the <a href="http://opensource.org/licenses/MIT">MIT license</a>.</p> <p>Docco can be used to process code written in any programming language. If it doesn’t handle your favorite yet, feel free to <a href="https://github.com/jashkenas/docco/blob/master/resources/languages.json">add it to the list</a>. Finally, the <a href="http://coffeescript.org/#literate">“literate” style</a> of <em>any</em> language is also supported — just tack an <code>.md</code> extension on the end: <code>.coffee.md</code>, <code>.py.md</code>, and so on.</p> <h2 id="partners-in-crime-">Partners in Crime:</h2> </div> </li> <li id="section-3"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-3">&#182;</a> </div> <ul> <li><p>If Node.js doesn’t run on your platform, or you’d prefer a more convenient package, get <a href="http://github.com/rtomayko">Ryan Tomayko</a>‘s <a href="http://rtomayko.github.io/rocco/rocco.html">Rocco</a>, the <strong>Ruby</strong> port that’s available as a gem.</p> </li> <li><p>If you’re writing shell scripts, try <a href="http://rtomayko.github.io/shocco/">Shocco</a>, a port for the <strong>POSIX shell</strong>, also by Mr. Tomayko.</p> </li> <li><p>If <strong>Python</strong> is more your speed, take a look at <a href="http://github.com/fitzgen">Nick Fitzgerald</a>‘s <a href="http://fitzgen.github.io/pycco/">Pycco</a>.</p> </li> <li><p>For <strong>Clojure</strong> fans, <a href="http://blog.fogus.me/">Fogus</a>‘s <a href="http://fogus.me/fun/marginalia/">Marginalia</a> is a bit of a departure from “quick-and-dirty”, but it’ll get the job done.</p> </li> <li><p>There’s a <strong>Go</strong> port called <a href="http://nikhilm.github.io/gocco/">Gocco</a>, written by <a href="https://github.com/nikhilm">Nikhil Marathe</a>.</p> </li> <li><p>For all you <strong>PHP</strong> buffs out there, Fredi Bach’s <a href="http://jquery-jkit.com/sourcemakeup/">sourceMakeup</a> (we’ll let the faux pas with respect to our naming scheme slide), should do the trick nicely.</p> </li> <li><p><strong>Lua</strong> enthusiasts can get their fix with <a href="https://github.com/rgieseke">Robert Gieseke</a>‘s <a href="http://rgieseke.github.io/locco/">Locco</a>.</p> </li> <li><p>And if you happen to be a <strong>.NET</strong> aficionado, check out <a href="https://github.com/dontangg">Don Wilson</a>‘s <a href="http://dontangg.github.io/nocco/">Nocco</a>.</p> </li> <li><p>Going further afield from the quick-and-dirty, <a href="http://nevir.github.io/groc/">Groc</a> is a <strong>CoffeeScript</strong> fork of Docco that adds a searchable table of contents, and aims to gracefully handle large projects with complex hierarchies of code.</p> </li> </ul> <p>Note that not all ports will support all Docco features … yet.</p> <h2 id="main-documentation-generation-functions">Main Documentation Generation Functions</h2> </div> </li> <li id="section-4"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-4">&#182;</a> </div> <p>Generate the documentation for our configured source file by copying over static assets, reading all the source files in, splitting them up into prose+code sections, highlighting each file in the appropriate language, and printing them out in an HTML template.</p> </div> <div class="content"><div class='highlight'><pre><span class="hljs-function"><span class="hljs-title">document</span> = <span class="hljs-params">(options = {}, callback)</span> -&gt;</span> config = configure options fs.mkdirs config.output,<span class="hljs-function"> -&gt;</span> callback <span class="hljs-function"><span class="hljs-title">or</span>= <span class="hljs-params">(error)</span> -&gt;</span> <span class="hljs-keyword">throw</span> error <span class="hljs-keyword">if</span> error <span class="hljs-function"><span class="hljs-title">copyAsset</span> = <span class="hljs-params">(file, callback)</span> -&gt;</span> fs.copy file, path.join(config.output, path.basename(file)), callback <span class="hljs-function"><span class="hljs-title">complete</span> = -&gt;</span> copyAsset config.css, <span class="hljs-function"><span class="hljs-params">(error)</span> -&gt;</span> <span class="hljs-keyword">if</span> error <span class="hljs-keyword">then</span> callback error <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> fs.existsSync config.public <span class="hljs-keyword">then</span> copyAsset config.public, callback <span class="hljs-keyword">else</span> callback() files = config.sources.slice() <span class="hljs-function"><span class="hljs-title">nextFile</span> = -&gt;</span> source = files.shift() fs.readFile source, <span class="hljs-function"><span class="hljs-params">(error, buffer)</span> -&gt;</span> <span class="hljs-keyword">return</span> callback error <span class="hljs-keyword">if</span> error code = buffer.toString() sections = parse source, code, config format source, sections, config write source, sections, config <span class="hljs-keyword">if</span> files.length <span class="hljs-keyword">then</span> nextFile() <span class="hljs-keyword">else</span> complete() nextFile()</pre></div></div> </li> <li id="section-5"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-5">&#182;</a> </div> <p>Given a string of source code, <strong>parse</strong> out each block of prose and the code that follows it — by detecting which is which, line by line — and then create an individual <strong>section</strong> for it. Each section is an object with <code>docsText</code> and <code>codeText</code> properties, and eventually <code>docsHtml</code> and <code>codeHtml</code> as well.</p> </div> <div class="content"><div class='highlight'><pre><span class="hljs-function"><span class="hljs-title">parse</span> = <span class="hljs-params">(source, code, config = {})</span> -&gt;</span> lines = code.split <span class="hljs-string">'\n'</span> sections = [] lang = getLanguage source, config hasCode = docsText = codeText = <span class="hljs-string">''</span> <span class="hljs-function"><span class="hljs-title">save</span> = -&gt;</span> sections.push {docsText, codeText} hasCode = docsText = codeText = <span class="hljs-string">''</span></pre></div></div> </li> <li id="section-6"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-6">&#182;</a> </div> <p>Our quick-and-dirty implementation of the literate programming style. Simply invert the prose and code relationship on a per-line basis, and then continue as normal below.</p> </div> <div class="content"><div class='highlight'><pre> <span class="hljs-keyword">if</span> lang.literate isText = maybeCode = <span class="hljs-literal">yes</span> <span class="hljs-keyword">for</span> line, i <span class="hljs-keyword">in</span> lines lines[i] = <span class="hljs-keyword">if</span> maybeCode <span class="hljs-keyword">and</span> match = <span class="hljs-regexp">/^([ ]{4}|[ ]{0,3}\t)/</span>.exec line isText = <span class="hljs-literal">no</span> line[match[<span class="hljs-number">0</span>].length..] <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> maybeCode = <span class="hljs-regexp">/^\s*$/</span>.test line <span class="hljs-keyword">if</span> isText <span class="hljs-keyword">then</span> lang.symbol <span class="hljs-keyword">else</span> <span class="hljs-string">''</span> <span class="hljs-keyword">else</span> isText = <span class="hljs-literal">yes</span> lang.symbol + <span class="hljs-string">' '</span> + line <span class="hljs-keyword">for</span> line <span class="hljs-keyword">in</span> lines <span class="hljs-keyword">if</span> line.match(lang.commentMatcher) <span class="hljs-keyword">and</span> <span class="hljs-keyword">not</span> line.match(lang.commentFilter) save() <span class="hljs-keyword">if</span> hasCode docsText += (line = line.replace(lang.commentMatcher, <span class="hljs-string">''</span>)) + <span class="hljs-string">'\n'</span> save() <span class="hljs-keyword">if</span> <span class="hljs-regexp">/^(---+|===+)$/</span>.test line <span class="hljs-keyword">else</span> hasCode = <span class="hljs-literal">yes</span> codeText += line + <span class="hljs-string">'\n'</span> save() sections</pre></div></div> </li> <li id="section-7"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-7">&#182;</a> </div> <p>To <strong>format</strong> and highlight the now-parsed sections of code, we use <strong>Highlight.js</strong> over stdio, and run the text of their corresponding comments through <strong>Markdown</strong>, using <a href="https://github.com/chjj/marked">Marked</a>.</p> </div> <div class="content"><div class='highlight'><pre><span class="hljs-function"><span class="hljs-title">format</span> = <span class="hljs-params">(source, sections, config)</span> -&gt;</span> language = getLanguage source, config</pre></div></div> </li> <li id="section-8"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-8">&#182;</a> </div> <p>Tell Marked how to highlight code blocks within comments, treating that code as either the language specified in the code block or the language of the file if not specified.</p> </div> <div class="content"><div class='highlight'><pre> marked.setOptions { <span class="hljs-attribute">highlight</span>: <span class="hljs-function"><span class="hljs-params">(code, lang)</span> -&gt;</span> lang <span class="hljs-keyword">or</span>= language.name <span class="hljs-keyword">if</span> highlightjs.getLanguage(lang) highlightjs.highlight(lang, code).value <span class="hljs-keyword">else</span> <span class="hljs-built_in">console</span>.warn <span class="hljs-string">"docco: couldn't highlight code block with unknown language '<span class="hljs-subst">#{lang}</span>' in <span class="hljs-subst">#{source}</span>"</span> code } <span class="hljs-keyword">for</span> section, i <span class="hljs-keyword">in</span> sections code = highlightjs.highlight(language.name, section.codeText).value code = code.replace(<span class="hljs-regexp">/\s+$/</span>, <span class="hljs-string">''</span>) section.codeHtml = <span class="hljs-string">"&lt;div class='highlight'&gt;&lt;pre&gt;<span class="hljs-subst">#{code}</span>&lt;/pre&gt;&lt;/div&gt;"</span> section.docsHtml = marked(section.docsText)</pre></div></div> </li> <li id="section-9"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-9">&#182;</a> </div> <p>Once all of the code has finished highlighting, we can <strong>write</strong> the resulting documentation file by passing the completed HTML sections into the template, and rendering it to the specified output path.</p> </div> <div class="content"><div class='highlight'><pre><span class="hljs-function"><span class="hljs-title">write</span> = <span class="hljs-params">(source, sections, config)</span> -&gt;</span> <span class="hljs-function"><span class="hljs-title">destination</span> = <span class="hljs-params">(file)</span> -&gt;</span> path.join(config.output, path.basename(file, path.extname(file)) + <span class="hljs-string">'.html'</span>)</pre></div></div> </li> <li id="section-10"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-10">&#182;</a> </div> <p>The <strong>title</strong> of the file is either the first heading in the prose, or the name of the source file.</p> </div> <div class="content"><div class='highlight'><pre> first = marked.lexer(sections[<span class="hljs-number">0</span>].docsText)[<span class="hljs-number">0</span>] hasTitle = first <span class="hljs-keyword">and</span> first.type <span class="hljs-keyword">is</span> <span class="hljs-string">'heading'</span> <span class="hljs-keyword">and</span> first.depth <span class="hljs-keyword">is</span> <span class="hljs-number">1</span> title = <span class="hljs-keyword">if</span> hasTitle <span class="hljs-keyword">then</span> first.text <span class="hljs-keyword">else</span> path.basename source html = config.template {<span class="hljs-attribute">sources</span>: config.sources, <span class="hljs-attribute">css</span>: path.basename(config.css), title, hasTitle, sections, path, destination,} <span class="hljs-built_in">console</span>.log <span class="hljs-string">"docco: <span class="hljs-subst">#{source}</span> -&gt; <span class="hljs-subst">#{destination source}</span>"</span> fs.writeFileSync destination(source), html</pre></div></div> </li> <li id="section-11"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-11">&#182;</a> </div> <h2 id="configuration">Configuration</h2> </div> </li> <li id="section-12"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-12">&#182;</a> </div> <p>Default configuration <strong>options</strong>. All of these may be extended by user-specified options.</p> </div> <div class="content"><div class='highlight'><pre>defaults = <span class="hljs-attribute">layout</span>: <span class="hljs-string">'parallel'</span> <span class="hljs-attribute">output</span>: <span class="hljs-string">'docs'</span> <span class="hljs-attribute">template</span>: <span class="hljs-literal">null</span> <span class="hljs-attribute">css</span>: <span class="hljs-literal">null</span> <span class="hljs-attribute">extension</span>: <span class="hljs-literal">null</span> <span class="hljs-attribute">languages</span>: {}</pre></div></div> </li> <li id="section-13"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-13">&#182;</a> </div> <p><strong>Configure</strong> this particular run of Docco. We might use a passed-in external template, or one of the built-in <strong>layouts</strong>. We only attempt to process source files for languages for which we have definitions.</p> </div> <div class="content"><div class='highlight'><pre><span class="hljs-function"><span class="hljs-title">configure</span> = <span class="hljs-params">(options)</span> -&gt;</span> config = _.extend {}, defaults, _.pick(options, _.keys(defaults)...) config.languages = buildMatchers config.languages <span class="hljs-keyword">if</span> options.template config.layout = <span class="hljs-literal">null</span> <span class="hljs-keyword">else</span> dir = config.layout = path.join __dirname, <span class="hljs-string">'resources'</span>, config.layout config.public = path.join dir, <span class="hljs-string">'public'</span> <span class="hljs-keyword">if</span> fs.existsSync path.join dir, <span class="hljs-string">'public'</span> config.template = path.join dir, <span class="hljs-string">'docco.jst'</span> config.css = options.css <span class="hljs-keyword">or</span> path.join dir, <span class="hljs-string">'docco.css'</span> config.template = _.template fs.readFileSync(config.template).toString() config.sources = options.args.filter<span class="hljs-function"><span class="hljs-params">((source) -&gt; lang = getLanguage source, config <span class="hljs-built_in">console</span>.warn <span class="hljs-string">"docco: skipped unknown type (<span class="hljs-subst">#{path.basename source}</span>)"</span> <span class="hljs-keyword">unless</span> lang lang )</span>.<span class="hljs-title">sort</span><span class="hljs-params">()</span> <span class="hljs-title">config</span> </span></pre></div></div> </li> <li id="section-14"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-14">&#182;</a> </div> <h2 id="helpers-initial-setup">Helpers &amp; Initial Setup</h2> </div> </li> <li id="section-15"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-15">&#182;</a> </div> <p>Require our external dependencies.</p> </div> <div class="content"><div class='highlight'><pre>_ = <span class="hljs-built_in">require</span> <span class="hljs-string">'underscore'</span> fs = <span class="hljs-built_in">require</span> <span class="hljs-string">'fs-extra'</span> path = <span class="hljs-built_in">require</span> <span class="hljs-string">'path'</span> marked = <span class="hljs-built_in">require</span> <span class="hljs-string">'marked'</span> commander = <span class="hljs-built_in">require</span> <span class="hljs-string">'commander'</span> highlightjs = <span class="hljs-built_in">require</span> <span class="hljs-string">'highlight.js'</span></pre></div></div> </li> <li id="section-16"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-16">&#182;</a> </div> <p>Enable nicer typography with marked.</p> </div> <div class="content"><div class='highlight'><pre>marked.setOptions <span class="hljs-attribute">smartypants</span>: <span class="hljs-literal">yes</span></pre></div></div> </li> <li id="section-17"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-17">&#182;</a> </div> <p>Languages are stored in JSON in the file <code>resources/languages.json</code>. Each item maps the file extension to the name of the language and the <code>symbol</code> that indicates a line comment. To add support for a new programming language to Docco, just add it to the file.</p> </div> <div class="content"><div class='highlight'><pre>languages = JSON.parse fs.readFileSync(path.join(__dirname, <span class="hljs-string">'resources'</span>, <span class="hljs-string">'languages.json'</span>))</pre></div></div> </li> <li id="section-18"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-18">&#182;</a> </div> <p>Build out the appropriate matchers and delimiters for each language.</p> </div> <div class="content"><div class='highlight'><pre><span class="hljs-function"><span class="hljs-title">buildMatchers</span> = <span class="hljs-params">(languages)</span> -&gt;</span> <span class="hljs-keyword">for</span> ext, l <span class="hljs-keyword">of</span> languages</pre></div></div> </li> <li id="section-19"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-19">&#182;</a> </div> <p>Does the line begin with a comment?</p> </div> <div class="content"><div class='highlight'><pre> l.commentMatcher = <span class="hljs-regexp">///^\s*<span class="hljs-subst">#{l.symbol}</span>\s?///</span></pre></div></div> </li> <li id="section-20"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-20">&#182;</a> </div> <p>Ignore <a href="http://en.wikipedia.org/wiki/Shebang_%28Unix%29">hashbangs</a> and interpolations…</p> </div> <div class="content"><div class='highlight'><pre> l.commentFilter = <span class="hljs-regexp">/(^#![/</span>]|^\s*<span class="hljs-comment">#\{)/</span> languages languages = buildMatchers languages</pre></div></div> </li> <li id="section-21"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-21">&#182;</a> </div> <p>A function to get the current language we’re documenting, based on the file extension. Detect and tag “literate” <code>.ext.md</code> variants.</p> </div> <div class="content"><div class='highlight'><pre><span class="hljs-function"><span class="hljs-title">getLanguage</span> = <span class="hljs-params">(source, config)</span> -&gt;</span> ext = config.extension <span class="hljs-keyword">or</span> path.extname(source) <span class="hljs-keyword">or</span> path.basename(source) lang = config.languages[ext] <span class="hljs-keyword">or</span> languages[ext] <span class="hljs-keyword">if</span> lang <span class="hljs-keyword">and</span> lang.name <span class="hljs-keyword">is</span> <span class="hljs-string">'markdown'</span> codeExt = path.extname(path.basename(source, ext)) <span class="hljs-keyword">if</span> codeExt <span class="hljs-keyword">and</span> codeLang = languages[codeExt] lang = _.extend {}, codeLang, {<span class="hljs-attribute">literate</span>: <span class="hljs-literal">yes</span>} lang</pre></div></div> </li> <li id="section-22"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-22">&#182;</a> </div> <p>Keep it DRY. Extract the docco <strong>version</strong> from <code>package.json</code></p> </div> <div class="content"><div class='highlight'><pre>version = JSON.parse(fs.readFileSync(path.join(__dirname, <span class="hljs-string">'package.json'</span>))).version</pre></div></div> </li> <li id="section-23"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-23">&#182;</a> </div> <h2 id="command-line-interface">Command Line Interface</h2> </div> </li> <li id="section-24"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-24">&#182;</a> </div> <p>Finally, let’s define the interface to run Docco from the command line. Parse options using <a href="https://github.com/visionmedia/commander.js">Commander</a>.</p> </div> <div class="content"><div class='highlight'><pre><span class="hljs-function"><span class="hljs-title">run</span> = <span class="hljs-params">(args = process.argv)</span> -&gt;</span> c = defaults commander.version(version) .usage(<span class="hljs-string">'[options] files'</span>) .option(<span class="hljs-string">'-L, --languages [file]'</span>, <span class="hljs-string">'use a custom languages.json'</span>, _.compose JSON.parse, fs.readFileSync) .option(<span class="hljs-string">'-l, --layout [name]'</span>, <span class="hljs-string">'choose a layout (parallel, linear or classic)'</span>, c.layout) .option(<span class="hljs-string">'-o, --output [path]'</span>, <span class="hljs-string">'output to a given folder'</span>, c.output) .option(<span class="hljs-string">'-c, --css [file]'</span>, <span class="hljs-string">'use a custom css file'</span>, c.css) .option(<span class="hljs-string">'-t, --template [file]'</span>, <span class="hljs-string">'use a custom .jst template'</span>, c.template) .option(<span class="hljs-string">'-e, --extension [ext]'</span>, <span class="hljs-string">'assume a file extension for all inputs'</span>, c.extension) .parse(args) .name = <span class="hljs-string">"docco"</span> <span class="hljs-keyword">if</span> commander.args.length <span class="hljs-built_in">document</span> commander <span class="hljs-keyword">else</span> <span class="hljs-built_in">console</span>.log commander.helpInformation()</pre></div></div> </li> <li id="section-25"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-25">&#182;</a> </div> <h2 id="public-api">Public API</h2> </div> </li> <li id="section-26"> <div class="annotation"> <div class="pilwrap "> <a class="pilcrow" href="#section-26">&#182;</a> </div> </div> <div class="content"><div class='highlight'><pre>Docco = <span class="hljs-built_in">module</span>.<span class="hljs-built_in">exports</span> = {run, <span class="hljs-built_in">document</span>, parse, format, version}</pre></div></div> </li> </ul> </div> </body> </html>