Skip to content


Subversion checkout URL

You can clone with
Download ZIP
Branch: master
Fetching contributors…

Cannot retrieve contributors at this time

383 lines (248 sloc) 18.309 kB
<!DOCTYPE html>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link rel="stylesheet" media="all" href="public/stylesheets/normalize.css" />
<link rel="stylesheet" media="all" href="resources/linear/docco.css" />
<div class="container">
<div class="page">
<div class="header">
<p><strong>Docco</strong> is a quick-and-dirty documentation generator, written in
<a href="">Literate CoffeeScript</a>.
It produces an HTML document that displays your comments intermingled with your
code. All prose is passed through
<a href="">Markdown</a>, and code is
passed through <a href="">Highlight.js</a> syntax highlighting.
This page is the result of running Docco against its own
<a href="">source file</a>.</p>
<li><p>Install Docco with <strong>npm</strong>: <code>sudo npm install -g docco</code></p>
<li><p>Run it against your code: <code>docco src/*.coffee</code></p>
<p>There is no &quot;Step 3&quot;. 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="">Docco source</a> is available on GitHub,
and is released under the <a href="">MIT license</a>.</p>
<p>Docco can be used to process code written in any programming language. If it
doesn&#39;t handle your favorite yet, feel free to
<a href="">add it to the list</a>.
Finally, the <a href="">&quot;literate&quot; style</a> of <em>any</em>
language is also supported — just tack an <code>.md</code> extension on the end:
<code></code>, <code></code>, and so on.</p>
<h2>Partners in Crime:</h2>
<li><p>If <strong>Node.js</strong> doesn&#39;t run on your platform, or you&#39;d prefer a more
convenient package, get <a href="">Ryan Tomayko</a>&#39;s
<a href="">Rocco</a>, the Ruby port that&#39;s
available as a gem.</p>
<li><p>If you&#39;re writing shell scripts, try
<a href="">Shocco</a>, a port for the <strong>POSIX shell</strong>,
also by Mr. Tomayko.</p>
<li><p>If <strong>Python</strong> is more your speed, take a look at
<a href="">Nick Fitzgerald</a>&#39;s <a href="">Pycco</a>.</p>
<li><p>For <strong>Clojure</strong> fans, <a href="">Fogus</a>&#39;s
<a href="">Marginalia</a> is a bit of a departure from
&quot;quick-and-dirty&quot;, but it&#39;ll get the job done.</p>
<li><p>There&#39;s a <strong>Go</strong> port called <a href="">Gocco</a>,
written by <a href="">Nikhil Marathe</a>.</p>
<li><p>Your all you <strong>PHP</strong> buffs out there, Fredi Bach&#39;s
<a href="">sourceMakeup</a> (we&#39;ll let the faux pas
with respect to our naming scheme slide), should do the trick nicely.</p>
<li><p><strong>Lua</strong> enthusiasts can get their fix with
<a href="">Robert Gieseke</a>&#39;s <a href="">Locco</a>.</p>
<li><p>And if you happen to be a <strong>.NET</strong>
aficionado, check out <a href="">Don Wilson</a>&#39;s
<a href="">Nocco</a>.</p>
<li><p>Going further afield from the quick-and-dirty, <a href="">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>
<p>Note that not all ports will support all Docco features ... yet.</p>
<h2>Main Documentation Generation Functions</h2>
<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 class='highlight'><pre><span class="function"><span class="title">document</span></span> = (options = {}, callback) -&gt;
configure options
fs.mkdirs config.output, -&gt;
callback <span class="function"><span class="title">or</span></span>= (error) -&gt; <span class="keyword">throw</span> error <span class="keyword">if</span> error
<span class="function"><span class="title">copyAsset</span></span> = (file, callback) -&gt;
fs.copy file, path.join(config.output, path.basename(file)), callback
<span class="function"><span class="title">complete</span></span> = -&gt;
copyAsset config.css, (error) -&gt;
<span class="keyword">if</span> error <span class="keyword">then</span> callback error
<span class="keyword">else</span> <span class="keyword">if</span> fs.existsSync config.public <span class="keyword">then</span> copyAsset config.public, callback
<span class="keyword">else</span> callback()
files = config.sources.slice()
<span class="function"><span class="title">nextFile</span></span> = -&gt;
source = files.shift()
fs.readFile source, (error, buffer) -&gt;
<span class="keyword">return</span> callback error <span class="keyword">if</span> error
code = buffer.toString()
sections = parse source, code
format source, sections
write source, sections
<span class="keyword">if</span> files.length <span class="keyword">then</span> nextFile() <span class="keyword">else</span> complete()
<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 class='highlight'><pre><span class="function"><span class="title">parse</span></span> = (source, code) -&gt;
lines = code.split <span class="string">'\n'</span>
sections = []
lang = getLanguage source
hasCode = docsText = codeText = <span class="string">''</span>
<span class="function"><span class="title">save</span></span> = -&gt;
sections.push {docsText, codeText}
hasCode = docsText = codeText = <span class="string">''</span></pre></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 class='highlight'><pre> <span class="keyword">if</span> lang.literate
<span class="keyword">for</span> line, i <span class="keyword">in</span> lines
lines[i] = <span class="keyword">if</span> <span class="regexp">/^\s*$/</span>.test line
<span class="string">''</span>
<span class="keyword">else</span> <span class="keyword">if</span> match = (<span class="regexp">/^([ ]{4}|\t)/</span>).exec line
line[match[<span class="number">0</span>].length..]
<span class="keyword">else</span>
lang.symbol + <span class="string">' '</span> + line
<span class="keyword">for</span> line <span class="keyword">in</span> lines
<span class="keyword">if</span> (<span class="keyword">not</span> line <span class="keyword">and</span> prev <span class="keyword">is</span> <span class="string">'text'</span>) <span class="keyword">or</span>
(line.match(lang.commentMatcher) <span class="keyword">and</span> <span class="keyword">not</span> line.match(lang.commentFilter))
save() <span class="keyword">if</span> hasCode
docsText += (line = line.replace(lang.commentMatcher, <span class="string">''</span>)) + <span class="string">'\n'</span>
save() <span class="keyword">if</span> <span class="regexp">/^(---+|===+)$/</span>.test line
prev = <span class="string">'text'</span>
<span class="keyword">else</span>
hasCode = <span class="literal">yes</span>
codeText += line + <span class="string">'\n'</span>
prev = <span class="string">'code'</span>
<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="">Marked</a>.</p>
<div class='highlight'><pre><span class="function"><span class="title">format</span></span> = (source, sections) -&gt;
language = getLanguage source
<span class="keyword">for</span> section, i <span class="keyword">in</span> sections
code = highlight(, section.codeText).value
code = code.replace(<span class="regexp">/\s+$/</span>, <span class="string">''</span>)
section.codeHtml = <span class="string">"&lt;div class='highlight'&gt;&lt;pre&gt;<span class="subst">#{code}</span>&lt;/pre&gt;&lt;/div&gt;"</span>
section.docsHtml = marked(section.docsText)</pre></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 class='highlight'><pre><span class="function"><span class="title">write</span></span> = (source, sections) -&gt;
<span class="function"><span class="title">destination</span></span> = (file) -&gt;
path.join(config.output, path.basename(file, path.extname(file)) + <span class="string">'.html'</span>)</pre></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 class='highlight'><pre> first = marked.lexer(sections[<span class="number">0</span>].docsText)[<span class="number">0</span>]
hasTitle = first <span class="keyword">and</span> first.type <span class="keyword">is</span> <span class="string">'heading'</span> <span class="keyword">and</span> first.depth <span class="keyword">is</span> <span class="number">1</span>
title = <span class="keyword">if</span> hasTitle <span class="keyword">then</span> first.text <span class="keyword">else</span> path.basename source
html = config.template {sources: config.sources, css: path.basename(config.css),
title, hasTitle, sections, path, destination,}
console.log <span class="string">"docco: <span class="subst">#{source}</span> -&gt; <span class="subst">#{destination source}</span>"</span>
fs.writeFileSync destination(source), html</pre></div>
<p>Default configuration <strong>options</strong>. All of these may be overriden by command-line
<div class='highlight'><pre>config =
layout: <span class="string">'parallel'</span>
output: <span class="string">'docs'</span>
template: <span class="literal">null</span>
css: <span class="literal">null</span>
extension: <span class="literal">null</span></pre></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 class='highlight'><pre><span class="function"><span class="title">configure</span></span> = (options) -&gt;
_.extend config, _.pick(options, _.keys(config)...)
<span class="keyword">if</span> options.template
config.layout = <span class="literal">null</span>
<span class="keyword">else</span>
dir = config.layout = path.join __dirname, <span class="string">'resources'</span>, config.layout
config.public = path.join dir, <span class="string">'public'</span> <span class="keyword">if</span> fs.existsSync path.join dir, <span class="string">'public'</span>
config.template = path.join dir, <span class="string">'docco.jst'</span>
config.css = options.css <span class="keyword">or</span> path.join dir, <span class="string">'resources/linear/docco.css'</span>
config.template = _.template fs.readFileSync(config.template).toString()
config.sources = options.args.filter((source) -&gt;
lang = getLanguage source, config
console.warn <span class="string">"docco: skipped unknown type (<span class="subst">#{path.basename source}</span>)"</span> <span class="keyword">unless</span> lang
<h2>Helpers &amp; Initial Setup</h2>
<p>Require our external dependencies.</p>
<div class='highlight'><pre>_ = require <span class="string">'underscore'</span>
fs = require <span class="string">'fs-extra'</span>
path = require <span class="string">'path'</span>
marked = require <span class="string">'marked'</span>
commander = require <span class="string">'commander'</span>
{highlight} = require <span class="string">'highlight.js'</span></pre></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 class='highlight'><pre>languages = JSON.parse fs.readFileSync(path.join(__dirname, <span class="string">'resources'</span>, <span class="string">'languages.json'</span>))</pre></div>
<p>Build out the appropriate matchers and delimiters for each language.</p>
<div class='highlight'><pre><span class="keyword">for</span> ext, l <span class="keyword">of</span> languages</pre></div>
<p>Does the line begin with a comment?</p>
<div class='highlight'><pre> l.commentMatcher = <span class="regexp">///^\s*<span class="comment">#{l.symbol}\s?///</span></pre></div>
<p>Ignore <a href="\">hashbangs</a>) and interpolations...</p>
<div class='highlight'><pre> l.commentFilter = <span class="regexp">/(^#![/]|^\s*#\{)/</span></pre></div>
<p>A function to get the current language we&#39;re documenting, based on the
file extension. Detect and tag &quot;literate&quot; <code></code> variants.</p>
<div class='highlight'><pre><span class="function"><span class="title">getLanguage</span></span> = (source) -&gt;
ext = config.extension <span class="keyword">or</span> path.extname(source) <span class="keyword">or</span> path.basename(source)
lang = languages[ext]
<span class="keyword">if</span> lang <span class="keyword">and</span> <span class="keyword">is</span> <span class="string">'markdown'</span>
codeExt = path.extname(path.basename(source, ext))
<span class="keyword">if</span> codeExt <span class="keyword">and</span> codeLang = languages[codeExt]
lang = _.extend {}, codeLang, {literate: <span class="literal">yes</span>}
<p>Keep it DRY. Extract the docco <strong>version</strong> from <code>package.json</code></p>
<div class='highlight'><pre>version = JSON.parse(fs.readFileSync(path.join(__dirname, <span class="string">'package.json'</span>))).version</pre></div>
<h2>Command Line Interface</h2>
<p>Finally, let&#39;s define the interface to run Docco from the command line.
Parse options using <a href="">Commander</a>.</p>
<div class='highlight'><pre><span class="function"><span class="title">run</span></span> = (args = process.argv) -&gt;
c = config
.usage(<span class="string">'[options] files'</span>)
.option(<span class="string">'-l, --layout [name]'</span>, <span class="string">'choose a layout (parallel, linear or classic)'</span>, c.layout)
.option(<span class="string">'-o, --output [path]'</span>, <span class="string">'output to a given folder'</span>, c.output)
.option(<span class="string">'-c, --css [file]'</span>, <span class="string">'use a custom css file'</span>, c.css)
.option(<span class="string">'-t, --template [file]'</span>, <span class="string">'use a custom .jst template'</span>, c.template)
.option(<span class="string">'-e, --extension [ext]'</span>, <span class="string">'assume a file extension for all inputs'</span>, c.extension)
.name = <span class="string">"docco"</span>
<span class="keyword">if</span> commander.args.length
document commander
<span class="keyword">else</span>
console.log commander.helpInformation()</pre></div>
<h2>Public API</h2>
<div class='highlight'><pre>Docco = module.exports = {run, document, parse, version}</pre></div>
<div class="fleur">h</div>
Jump to Line
Something went wrong with that request. Please try again.