UNPKG

@azu/docco

Version:

The Quick and Dirty Literate Programming Documentation Generator

482 lines (457 loc) 16.5 kB
// Generated by CoffeeScript 1.12.4 (function() { var Docco, _, buildMatchers, commander, configure, defaults, document, format, fs, getLanguage, highlightjs, languages, make_destination, marked, normalize, outputCode, parse, path, qualifiedName, run, version, write, slice = [].slice; document = function(options, user_callback) { var callback, complete, config, copyAsset, files, nextFile, outputFiles, source_infos; if (options == null) { options = {}; } config = configure(options); source_infos = []; fs.mkdirsSync(config.output); if (config.source) { fs.mkdirsSync(config.source); } callback = function(error) { if (error) { if (user_callback) { user_callback(error); } throw error; } if (user_callback) { return user_callback(null, { source_infos: source_infos, config: config }); } }; copyAsset = function(file, callback) { if (!fs.existsSync(file)) { return callback(); } return fs.copy(file, path.join(config.output, path.basename(file)), callback); }; complete = function() { return copyAsset(config.css, function(error) { if (error) { return callback(error); } return copyAsset(config["public"], callback); }); }; files = config.sources.slice(); nextFile = function() { var source; source = files.shift(); return fs.readFile(source, function(error, buffer) { var code, first, firstSection, hasTitle, sections, title; if (error) { return callback(error); } code = buffer.toString(); sections = parse(source, code, config); format(source, sections, config); firstSection = _.find(sections, function(section) { return section.docsText.length > 0; }); if (firstSection) { first = marked.lexer(firstSection.docsText)[0]; } hasTitle = first && first.type === 'heading' && first.depth === 1; title = hasTitle ? first.text : path.basename(source); source_infos.push({ source: source, hasTitle: hasTitle, title: title, sections: sections }); if (files.length) { return nextFile(); } else { return outputFiles(); } }); }; outputFiles = function() { var i, info, j, len; for (i = j = 0, len = source_infos.length; j < len; i = ++j) { info = source_infos[i]; write(info.source, i, source_infos, config); outputCode(info.source, info.sections, i, source_infos, config); } return complete(); }; if (files.length) { return nextFile(); } else { return outputFiles(); } }; parse = function(source, code, config) { var codeText, docsText, hasCode, i, ignore_this_block, in_block, isText, j, k, lang, len, len1, line, lines, match, maybeCode, oldLen, param, raw_line, save, sections, single; if (config == null) { config = {}; } lines = code.split('\n'); sections = []; lang = getLanguage(source, config); hasCode = docsText = codeText = ''; param = ''; in_block = 0; ignore_this_block = 0; save = function() { sections.push({ docsText: docsText, codeText: codeText }); return hasCode = docsText = codeText = ''; }; if (lang.literate && lang.name !== 'markdown') { isText = maybeCode = true; for (i = j = 0, len = lines.length; j < len; i = ++j) { line = lines[i]; lines[i] = maybeCode && (match = /^([ ]{4}|[ ]{0,3}\t)/.exec(line)) ? (isText = false, line.slice(match[0].length)) : (maybeCode = /^\s*$/.test(line)) ? isText ? lang.symbol : '' : (isText = true, lang.symbol + ' ' + line); } } for (k = 0, len1 = lines.length; k < len1; k++) { line = lines[k]; if (in_block) { ++in_block; } raw_line = line; if (!in_block && config.blocks && lang.blocks && line.match(lang.commentEnter)) { line = line.replace(lang.commentEnter, ''); in_block = 1; if (lang.commentIgnore && line.match(lang.commentIgnore)) { ignore_this_block = 1; } } single = !in_block && lang.commentMatcher && line.match(lang.commentMatcher) && !line.match(lang.commentFilter); if (single) { line = line.replace(lang.commentMatcher, ''); if (lang.commentIgnore && line.match(lang.commentIgnore)) { ignore_this_block = 1; } } if (in_block || single) { if (in_block && line.match(lang.commentExit)) { line = line.replace(lang.commentExit, ''); in_block = -1; } if (in_block > 1 && lang.commentNext) { line = line.replace(lang.commentNext, ''); } if (lang.commentParam) { param = line.match(lang.commentParam); if (param) { line = line.replace(param[0], '\n' + '<b>' + param[1] + '</b>'); } } } if (!ignore_this_block && (in_block || single)) { if (hasCode) { save(); } docsText += line + '\n'; if (/^(---+|===+)$/.test(line || in_block === -1)) { save(); } } else { hasCode = true; if (config.indent) { oldLen = 0; while (oldLen !== line.length) { oldLen = line.length; line = line.replace(/^(\x20*)\t/, '$1' + config.indent); } } codeText += line + '\n'; } if (in_block === -1) { in_block = 0; } if (!in_block) { ignore_this_block = 0; } } save(); return sections; }; format = function(source, sections, config) { var code, doc, err, i, j, k, language, len, len1, markedOptions, results, section; language = getLanguage(source, config); markedOptions = config.marked_options; marked.setOptions(markedOptions); marked.setOptions({ highlight: function(code, lang) { lang || (lang = language.name); if (highlightjs.getLanguage(lang)) { return highlightjs.highlight(lang, code).value; } else { console.warn("docco: couldn't highlight code block with unknown language '" + lang + "' in " + source); return code; } } }); marked.setOptions({ linksCollector: {}, execPrepPhaseOnly: true }); for (i = j = 0, len = sections.length; j < len; i = ++j) { section = sections[i]; if (language.name === 'markdown') { if (language.literate) { code = section.codeText; section.codeText = code = code.replace(/\s+$/, ''); section.codeHtml = marked(code); doc = section.docsText; section.docsText = doc = doc.replace(/\s+$/, ''); marked(doc); } else { section.codeHtml = ''; code = section.codeText; section.codeText = code = code.replace(/\s+$/, ''); marked(code); } } else { code = section.codeText; section.codeText = code = code.replace(/\s+$/, ''); try { code = highlightjs.highlight(language.name, code).value; } catch (error1) { err = error1; if (!config.ignore) { throw err; } code = section.codeText; } section.codeHtml = "<div class='highlight'><pre>" + code + "</pre></div>"; doc = section.docsText; section.docsText = doc = doc.replace(/\s+$/, ''); marked(doc); } } marked.setOptions({ execPrepPhaseOnly: false }); results = []; for (i = k = 0, len1 = sections.length; k < len1; i = ++k) { section = sections[i]; if (language.name === 'markdown') { if (language.literate) { doc = section.docsText; results.push(section.docsHtml = marked(doc)); } else { code = section.codeText; results.push(section.docsHtml = marked(code)); } } else { doc = section.docsText; results.push(section.docsHtml = marked(doc)); } } return results; }; write = function(source, title_idx, source_infos, config) { var css, destfile, destination, html, relative; destination = function(file) { return make_destination(config.output, config.separator, file, '.html', config); }; destfile = destination(source); relative = function(srcfile) { var dstfile, from, to; to = path.dirname(path.resolve(srcfile)); dstfile = destination(srcfile); from = path.dirname(path.resolve(dstfile)); return path.join(path.relative(from, to), path.basename(srcfile)); }; css = config.css ? relative(path.join(config.output, path.basename(config.css))) : null; html = config.template({ sources: config.sources, titles: source_infos.map(function(info) { return info.title; }), css: css, title: source_infos[title_idx].title, hasTitle: source_infos[title_idx].hasTitle, sections: source_infos[title_idx].sections, path: path, destination: destination, relative: relative, language: getLanguage(source, config) }); console.log("docco: " + source + " -> " + destfile); fs.mkdirsSync(path.dirname(destfile)); fs.writeFileSync(destfile, html); return source_infos[title_idx].destDocFile = destfile; }; outputCode = function(source, sections, title_idx, source_infos, config) { var code, destfile, lang; lang = getLanguage(source, config); if (config.source) { destfile = make_destination(config.source, config.separator, source, lang.source, config); code = _.pluck(sections, 'codeText').join('\n'); code = code.trim().replace(/(\n{2,})/g, '\n\n'); console.log("docco: " + source + " -> " + destfile); fs.mkdirsSync(path.dirname(destfile)); fs.writeFileSync(destfile, code); return source_infos[title_idx].destCodeFile = destfile; } }; normalize = function(path) { return path.replace(/[\\\/]/g, '/'); }; qualifiedName = function(file, separator, extension, config) { var cwd, nameParts; cwd = config && config.cwd ? config.cwd : process.cwd(); file = normalize(file); nameParts = path.dirname(file).replace(normalize(cwd), '').split('/'); while (nameParts[0] === '' || nameParts[0] === '.' || nameParts[0] === '..') { nameParts.shift(); } nameParts.push(path.basename(file, path.extname(file))); return nameParts.join(separator) + extension; }; make_destination = function(basepath, separator, file, extension, config) { return path.join(basepath, qualifiedName(file, separator, extension, config)); }; defaults = { sources: [], layout: 'parallel', output: 'docs', template: null, css: null, extension: null, languages: {}, source: null, cwd: process.cwd(), separator: '-', blocks: false, marked_options: { gfm: true, tables: true, breaks: false, pedantic: false, sanitize: false, smartLists: true, smartypants: true, langPrefix: 'language-', highlight: function(code, lang) { return code; } }, ignore: false, tabSize: null, indent: null }; configure = function(options) { var config, dir; config = _.extend({}, defaults, _.pick.apply(_, [options].concat(slice.call(_.keys(defaults))))); config.languages = buildMatchers(config.languages); if (config.tabSize) { config.indent = Array(parseInt(config.tabSize) + 1).join(' '); } if (options.template) { if (!options.css) { console.warn("docco: no stylesheet file specified"); } config.layout = null; } else { dir = config.layout = fs.existsSync(path.join(__dirname, 'resources', config.layout)) ? path.join(__dirname, 'resources', config.layout) : path.join(process.cwd(), config.layout); if (fs.existsSync(path.join(dir, 'public'))) { config["public"] = path.join(dir, 'public'); } config.template = path.join(dir, 'docco.jst'); config.css = options.css || path.join(dir, 'docco.css'); } config.template = _.template(fs.readFileSync(config.template).toString()); if (options.marked_options) { config.marked_options = _.extend(config.marked_options, JSON.parse(fs.readFileSync(options.marked_options))); } if (options.args) { config.sources = options.args.filter(function(source) { var lang; lang = getLanguage(source, config); if (!lang) { console.warn("docco: skipped unknown type (" + (path.basename(source)) + ")"); } return lang; }).sort(); } return config; }; _ = require('underscore'); fs = require('fs-extra'); path = require('path'); marked = require('marked'); commander = require('commander'); highlightjs = require('highlight.js'); languages = JSON.parse(fs.readFileSync(path.join(__dirname, 'resources', 'languages.json'))); buildMatchers = function(languages) { var ext, l; for (ext in languages) { l = languages[ext]; if (l.symbol) { l.commentMatcher = RegExp("^\\s*" + l.symbol + "\\s?"); } if (l.enter && l.exit) { l.blocks = true; l.commentEnter = new RegExp(l.enter); l.commentExit = new RegExp(l.exit); if (l.next) { l.commentNext = new RegExp(l.next); } } if (l.param) { l.commentParam = new RegExp(l.param); } l.commentFilter = /(^#![\/]|^\s*#\{)/; l.commentIgnore = new RegExp(/^:/); } return languages; }; languages = buildMatchers(languages); getLanguage = function(source, config) { var codeExt, codeLang, ext, lang, ref, ref1; ext = config.extension || path.extname(source) || path.basename(source); lang = ((ref = config.languages) != null ? ref[ext] : void 0) || languages[ext] || languages['text']; if (lang) { if (lang.name === 'markdown') { codeExt = path.extname(path.basename(source, ext)); if (codeExt && (codeLang = ((ref1 = config.languages) != null ? ref1[codeExt] : void 0) || languages[codeExt] || languages['text'])) { lang = _.extend({}, codeLang, { literate: true, source: '' }); } } else if (!lang.source) { lang.source = ext; } } return lang; }; version = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'))).version; run = function(args) { var c; if (args == null) { args = process.argv; } c = defaults; commander.version(version).usage('[options] files').option('-L, --languages [file]', 'use a custom languages.json', _.compose(JSON.parse, fs.readFileSync)).option('-l, --layout [name]', 'choose a layout (parallel, linear, pretty or classic) or external layout', c.layout).option('-o, --output [path]', 'output to a given folder', c.output).option('-c, --css [file]', 'use a custom css file', c.css).option('-t, --template [file]', 'use a custom .jst template', c.template).option('-b, --blocks', 'parse block comments where available', c.blocks).option('-e, --extension [ext]', 'assume a file extension for all inputs', c.extension).option('-s, --source [path]', 'output code in a given folder', c.source).option('--cwd [path]', 'specify the Current Working Directory path for the purpose of generating qualified output filenames', c.cwd).option('-x, --separator [sep]', 'the source path is included in the output filename, separated by this separator (default: "-")', c.separator).option('-m, --marked-options [file]', 'use custom Marked options', c.marked_options).option('-i, --ignore [file]', 'ignore unsupported languages', c.ignore).option('-T, --tab-size [size]', 'convert leading tabs to X spaces').parse(args).name = "docco"; if (commander.args.length) { return document(commander); } else { return console.log(commander.helpInformation()); } }; Docco = module.exports = { run: run, document: document, parse: parse, format: format, configure: configure, version: version }; }).call(this);