-
Notifications
You must be signed in to change notification settings - Fork 570
/
index.html
453 lines (301 loc) · 24 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
<!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://lillicense.org/v1.html">Lil 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 listed in <a href="https://github.com/jashkenas/docco/blob/master/resources/languages.json">languages.json</a>
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="https://pycco-docs.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>
<li><p>For <strong>ES6</strong> fans, <a href="https://github.com/mobily-enterprises/docco-next">Docco Next</a>
is an expanded rewrite of Docco in modern JavaScript, thoroughly commented,
and with the bonus that your existing templates will still work.</p>
</li>
</ul>
<p>Note that not all ports support all Docco features.</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> -></span>
config = configure options
fs.mkdirs config.output, <span class="hljs-function">-></span>
callback <span class="hljs-keyword">or</span>= <span class="hljs-function"><span class="hljs-params">(error)</span> -></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> -></span>
<span class="hljs-keyword">return</span> callback() <span class="hljs-keyword">unless</span> fs.existsSync file
fs.copy file, path.join(config.output, path.basename(file)), callback
<span class="hljs-function"> <span class="hljs-title">complete</span> = -></span>
copyAsset config.css, <span class="hljs-function"><span class="hljs-params">(error)</span> -></span>
<span class="hljs-keyword">return</span> callback error <span class="hljs-keyword">if</span> error
<span class="hljs-keyword">return</span> copyAsset config.public, callback <span class="hljs-keyword">if</span> fs.existsSync config.public
callback()
files = config.sources.slice()
<span class="hljs-function">
<span class="hljs-title">nextFile</span> = -></span>
source = files.shift()
fs.readFile source, <span class="hljs-function"><span class="hljs-params">(error, buffer)</span> -></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> -></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> = -></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> -></span>
language = getLanguage source, config</pre></div>
<p>Pass any user defined options to Marked if specified via command line option</p>
<div class='highlight'><pre> markedOptions =
smartypants: <span class="hljs-literal">true</span>
<span class="hljs-keyword">if</span> config.marked
markedOptions = config.marked
marked.setOptions markedOptions</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 {
highlight: <span class="hljs-function"><span class="hljs-params">(code, lang)</span> -></span>
lang <span class="hljs-keyword">or</span>= language.name
<span class="hljs-keyword">if</span> highlightjs.getLanguage(lang)
highlightjs.highlight(code, {language: lang}).value
<span class="hljs-keyword">else</span>
console.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(section.codeText, {language: language.name}).value
code = code.replace(<span class="hljs-regexp">/\s+$/</span>, <span class="hljs-string">''</span>)
section.codeHtml = <span class="hljs-string">"<div class='highlight'><pre><span class="hljs-subst">#{code}</span></pre></div>"</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> -></span>
<span class="hljs-function">
<span class="hljs-title">destination</span> = <span class="hljs-params">(file)</span> -></span>
path.join(config.output, path.dirname(file), path.basename(file, path.extname(file)) + <span class="hljs-string">'.html'</span>)
<span class="hljs-function">
<span class="hljs-title">relative</span> = <span class="hljs-params">(file)</span> -></span>
to = path.dirname(path.resolve(file))
<span class="hljs-keyword">from</span> = path.dirname(path.resolve(destination(source)))
path.join(path.relative(<span class="hljs-keyword">from</span>, to), path.basename(file))</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> firstSection = _.find sections, <span class="hljs-function"><span class="hljs-params">(section)</span> -></span>
section.docsText.length > <span class="hljs-number">0</span>
first = marked.lexer(firstSection.docsText)[<span class="hljs-number">0</span>] <span class="hljs-keyword">if</span> firstSection
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
css = relative path.join(config.output, path.basename(config.css))
html = config.template {sources: config.sources, css,
title, hasTitle, sections, path, destination, relative}
console.log <span class="hljs-string">"docco: <span class="hljs-subst">#{source}</span> -> <span class="hljs-subst">#{destination source}</span>"</span>
fs.outputFileSync 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 =
layout: <span class="hljs-string">'parallel'</span>
output: <span class="hljs-string">'docs'</span>
template: <span class="hljs-literal">null</span>
css: <span class="hljs-literal">null</span>
extension: <span class="hljs-literal">null</span>
languages: {}
marked: <span class="hljs-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="hljs-function"><span class="hljs-title">configure</span> = <span class="hljs-params">(options)</span> -></span>
config = _.extend {}, defaults, _.pick(options.opts(), _.keys(defaults)...)
config.languages = buildMatchers config.languages</pre></div>
<p>The user is able to override the layout file used with the <code>--template</code> parameter.
In this case, it is also neccessary to explicitly specify a stylesheet file.
These custom templates are compiled exactly like the predefined ones, but the <code>public</code> folder
is only copied for the latter.</p>
<div class='highlight'><pre> <span class="hljs-keyword">if</span> options.template
<span class="hljs-keyword">unless</span> options.css
console.warn <span class="hljs-string">"docco: no stylesheet file specified"</span>
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()
<span class="hljs-keyword">if</span> options.marked
config.marked = <span class="hljs-built_in">JSON</span>.parse fs.readFileSync(options.marked)
config.sources = options.args.filter(<span class="hljs-function"><span class="hljs-params">(source)</span> -></span>
lang = getLanguage source, config
console.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
).sort()
config</pre></div>
<h2 id="helpers--initial-setup">Helpers & 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>).marked
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>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 = <span class="hljs-built_in">JSON</span>.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> -></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> -></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))
codeLang = config.languages?[codeExt] <span class="hljs-keyword">or</span> languages[codeExt]
<span class="hljs-keyword">if</span> codeExt <span class="hljs-keyword">and</span> codeLang
lang = _.extend {}, codeLang, {literate: <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 = <span class="hljs-built_in">JSON</span>.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> -></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 <span class="hljs-built_in">JSON</span>.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)
.option(<span class="hljs-string">'-m, --marked [file]'</span>, <span class="hljs-string">'use custom marked options'</span>, c.marked)
.parse(args)
.name = <span class="hljs-string">"docco"</span>
<span class="hljs-keyword">if</span> commander.args.length
document commander
<span class="hljs-keyword">else</span>
console.log commander.helpInformation()</pre></div>
<h2 id="public-api">Public API</h2>
<div class='highlight'><pre>Docco = module.<span class="hljs-built_in">exports</span> = {run, document, parse, format, version}</pre></div>
<div class="fleur">h</div>
</div>
</div>
</body>
</html>