docco.coffee | |
---|---|
Doccoh (Docco with Highlight.js) is a fork of Docco (quick-and-dirty, hundred-line-long, literate-programming-style documentation generator) that replaces Pygments syntax highlighter for Highlight.js. It produces HTML that displays your comments alongside your code. Comments are passed through Markdown, and code is passed through Highlight.js syntax highlighting. This page is the result of running Doccoh against its own source file. If you install Doccoh, you can run it from the command-line:
...will generate an HTML documentation page for each of the named source files,
with a menu linking to the other pages, saving it into a The source for Doccoh is available on GitHub, and released under the MIT license. To install Doccoh, first make sure you have Node.js. Then, with NPM:
Doccoh can be used to process CoffeeScript, JavaScript, Ruby, Python, or TeX files. Only single-line comments are processed -- block comments are ignored. Partners in Crime:
|
|
Main Documentation Generation Functions |
|
Generate the documentation for a source file by reading it in, splitting it up into comment/code sections, highlighting them for the appropriate language, and merging them into an HTML template. | generateDocumentation = (source, config, callback) ->
fs.readFile source, (error, buffer) ->
throw error if error
code = buffer.toString()
sections = parse source, code
highlight source, sections, ->
generateHtml source, sections, config
callback()
|
Given a string of source code, parse out each comment and the code that follows it, and create an individual section for it. Sections take the form: | parse = (source, code) ->
lines = code.split '\n'
sections = []
language = getLanguage source
hasCode = docsText = codeText = ''
save = (docsText, codeText) ->
sections.push {docsText, codeText}
for line in lines
if line.match(language.commentMatcher) and not line.match(language.commentFilter)
if hasCode
save docsText, codeText
hasCode = docsText = codeText = ''
docsText += line.replace(language.commentMatcher, '') + '\n'
else
hasCode = yes
codeText += line + '\n'
save docsText, codeText
sections
|
Highlights a single chunk of CoffeeScript code, using Highlight.js, and runs the text of its corresponding comment through Markdown, using Showdown.js. We process the entire file in a single call to Highlight.js by inserting little marker comments between each section and then splitting the result string wherever our markers occur. | highlight = (source, sections, callback) ->
hljs.LANGUAGES['coffee-script'] = hljs.LANGUAGES['coffeescript'] # Compatibility with highlightJS naming scheme
language = getLanguage source
for section in sections
section.codeHtml = highlightStart + hljs.highlight(language.name, section.codeText).value + highlightEnd
section.docsHtml = showdown.makeHtml(section.docsText)
callback()
|
Once all of the code is finished highlighting, we can generate the HTML file by passing the completed sections into the template, and then writing the file to the specified output path. | generateHtml = (source, sections, config) ->
destination = (filepath) ->
path.join(config.output, path.basename(filepath, path.extname(filepath)) + '.html')
title = path.basename source
dest = destination source
html = config.doccoTemplate {
title : title,
sections : sections,
sources : config.sources,
path : path,
destination: destination
css : path.basename(config.css)
}
console.log "docco: #{source} -> #{dest}"
fs.writeFileSync dest, html
|
Helpers & Setup |
|
Require our external dependencies, including Showdown.js (the JavaScript implementation of Markdown). | fs = require 'fs'
path = require 'path'
showdown = require('./../vendor/showdown').Showdown
{spawn, exec} = require 'child_process'
commander = require 'commander'
hljs = require 'highlight.js'
|
Read resource file and return its content. | getResource = (name) ->
fullPath = path.join __dirname, '..', 'resources', name
fs.readFileSync(fullPath).toString()
|
Languages are stored in JSON format in the file | languages = JSON.parse getResource 'languages.json'
|
Build out the appropriate matchers and delimiters for each language. | for ext, l of languages
|
Does the line begin with a comment? | l.commentMatcher = ///^\s*#{l.symbol}\s?///
|
Ignore hashbangs and interpolations... | l.commentFilter = /(^#![/]|^\s*#\{)/
|
The dividing token we feed into Pygments, to delimit the boundaries between sections. | l.dividerText = "\n#{l.symbol}DIVIDER\n"
|
The mirror of | l.dividerHtml = ///\n*<span\sclass="c1?">#{l.symbol}DIVIDER<\/span>\n*///
|
Get the current language we're documenting, based on the extension. | getLanguage = (source) -> languages[path.extname(source)]
|
Ensure that the destination directory exists. | ensureDirectory = (dir, callback) ->
exec "mkdir -p #{dir}", -> callback()
|
Micro-templating, originally by John Resig, borrowed by way of Underscore.js. | template = (str) ->
new Function 'obj',
'var p=[],print=function(){p.push.apply(p,arguments);};' +
'with(obj){p.push(\'' +
str.replace(/[\r\t\n]/g, " ")
.replace(/'(?=[^<]*%>)/g,"\t")
.split("'").join("\\'")
.split("\t").join("'")
.replace(/<%=(.+?)%>/g, "',$1,'")
.split('<%').join("');")
.split('%>').join("p.push('") +
"');}return p.join('');"
|
The start of each highlight block. | highlightStart = '<div class="highlight"><pre>'
|
The end of each highlight block. | highlightEnd = '</pre></div>'
|
Extract the docco version from | version = JSON.parse(fs.readFileSync("#{__dirname}/../package.json")).version
|
Default configuration options. | defaults =
template: "#{__dirname}/../resources/docco.jst"
css : "#{__dirname}/../resources/resources/docco.css"
output : "docs/"
|
Run from Commandline |
|
Run Docco from a set of command line arguments.
| run = (args=process.argv) ->
commander.version(version)
.usage("[options] <filePattern ...>")
.option("-c, --css [file]","use a custom css file",defaults.css)
.option("-o, --output [path]","use a custom output path",defaults.output)
.option("-t, --template [file]","use a custom .jst template",defaults.template)
.parse(args)
.name = "docco"
if commander.args.length
document(commander.args.slice(),commander)
else
console.log commander.helpInformation()
|
Document Sources |
|
Run Docco over a list of
| document = (sources, options = {}, callback = null) ->
config = {}
config[key] = defaults[key] for key,value of defaults
config[key] = value for key,value of options if key of defaults
resolved = []
resolved = resolved.concat(resolveSource(src)) for src in sources
config.sources = resolved.filter((source) -> getLanguage source).sort()
console.log "docco: skipped unknown type (#{m})" for m in resolved when m not in config.sources
config.doccoTemplate = template fs.readFileSync(config.template).toString()
doccoStyles = fs.readFileSync(config.css).toString()
ensureDirectory config.output, ->
fs.writeFileSync path.join(config.output,path.basename(config.css)), doccoStyles
files = config.sources.slice()
nextFile = ->
callback() if callback? and not files.length
generateDocumentation files.shift(), config, nextFile if files.length
nextFile()
|
Resolve Wildcard Source Inputs |
|
Resolve a wildcard
| resolveSource = (source) ->
return source if not source.match(/([\*\?])/)
regex_str = path.basename(source)
.replace(/\./g, "\\$&")
.replace(/\*/,".*")
.replace(/\?/,".")
regex = new RegExp('^(' + regex_str + ')$')
file_path = path.dirname(source)
files = fs.readdirSync file_path
return (path.join(file_path,file) for file in files when file.match regex)
|
Exports |
|
Information about docco, and functions for programatic usage. | exports[key] = value for key, value of {
run : run
document : document
parse : parse
resolveSource : resolveSource
version : version
defaults : defaults
languages : languages
}
|