Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
branch: master
Fetching contributors…

Cannot retrieve contributors at this time

420 lines (270 sloc) 22.498 kb
<!DOCTYPE html>
<html>
<head>
<title>Docco</title>
<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" />
</head>
<body>
<div class="container">
<div class="page">
<div class="header">
<h1 id="docco">Docco</h1>
</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>
<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>
<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="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>
<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="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>
<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="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>
<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 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>
<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 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>
<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="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>
<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="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>
<h2 id="configuration">Configuration</h2>
<p>Default configuration <strong>options</strong>. All of these may be extended by
user-specified options.</p>
<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>
<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="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">'resources/linear/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>
<h2 id="helpers-initial-setup">Helpers &amp; Initial Setup</h2>
<p>Require our external dependencies.</p>
<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>
<p>Enable nicer typography with marked.</p>
<div class='highlight'><pre>marked.setOptions <span class="hljs-attribute">smartypants</span>: <span class="hljs-literal">yes</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="hljs-string">'resources'</span>, <span class="hljs-string">'languages.json'</span>))</pre></div>
<p>Build out the appropriate matchers and delimiters for each language.</p>
<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>
<p>Does the line begin with a comment?</p>
<div class='highlight'><pre> l.commentMatcher = <span class="hljs-regexp">///^\s*<span class="hljs-subst">#{l.symbol}</span>\s?///</span></pre></div>
<p>Ignore <a href="http://en.wikipedia.org/wiki/Shebang_%28Unix%29">hashbangs</a> and interpolations…</p>
<div class='highlight'><pre> l.commentFilter = <span class="hljs-regexp">/(^#![/</span>]|^\s*<span class="hljs-comment">#\{)/</span>
languages
languages = buildMatchers languages</pre></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 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>
<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="hljs-string">'package.json'</span>))).version</pre></div>
<h2 id="command-line-interface">Command Line Interface</h2>
<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 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>
<h2 id="public-api">Public API</h2>
<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 class="fleur">h</div>
</div>
</div>
</body>
</html>
Jump to Line
Something went wrong with that request. Please try again.