UNPKG

ljs

Version:

Generate docs from your source

184 lines (147 loc) 4.41 kB
"use strict"; /** ### Library You can also use *grunt-literate* as a normal library: ``` var documentation = require("grunt-literate")("hello.js", { code: true }); ``` */ var lex = require("./lex.js"); var fs = require("fs"); var path = require("path"); var assert = require("assert"); var glob = require("glob"); var _ = require("underscore"); var whitespaceEndRe = /^\s*$/; var whitespaceRe = /^(\s*)/; function isWhitespace(str) { return whitespaceEndRe.test(str); } function stripShebang(contents) { var m = contents.match(/^#!\/[^\n]*\n/); return m ? contents.substr(m[0].length) : contents; } function fileDirective(filename, value, regexp, callback) { var m = value.match(regexp); if (m) { var directivePattern = m[1]; var globPattern = path.join(path.dirname(filename), directivePattern); var files = glob.sync(globPattern); if (files.length === 0) { throw new Error(directivePattern + " doesn't match any files"); } files.forEach(callback); return true; } else { return false; } } function getTokens(filename) { var contents = fs.readFileSync(filename).toString(); contents = stripShebang(contents); var tokens = lex(contents); var resTokens = []; tokens.forEach(function (token) { var r; if (token.type === "Comment" && token.value.type === "Line" && token.value.value[0] === "/") { var value = token.value.value.substr(1); r = fileDirective(filename, value, /^\s*plain\s+(.*?)\s*$/, function (includename) { resTokens.push({ type: "Plain", value: fs.readFileSync(includename).toString(), }); }); if (r) { return; } r = fileDirective(filename, value, /^\s*include\s+(.*?)\s*$/, function (includename) { resTokens = resTokens.concat(getTokens(includename)); }); if (r) { return; } assert(false, "unknown directive: " + value); } else { token.raw = contents.substr(token.range[0], token.range[1] - token.range[0]); resTokens.push(token); } }); // End-of-file marker resTokens.push({ type: "EOF", value: "", }); return resTokens; } function unindent(value) { var lines = value.split(/\n/); var first = _.find(lines, function (line) { return !isWhitespace(line); }); var indent = first ? whitespaceRe.exec(first)[1] : ""; // Drop empty lines at the beginning of the literate comment while (lines[0] !== undefined && isWhitespace(lines[0])) { lines.shift(); } // unindent lines lines = lines.map(function (line) { if (line.indexOf(indent) === 0) { return line.replace(indent, ""); } else if (isWhitespace(line)) { return ""; } else { return line; } }); // Each line should have newline char after, also the last return lines.join("\n") + "\n"; } function literate(filename, opts) { opts = opts || {}; var code = opts.code || false; var codeOpen = opts.codeOpen || "\n```js\n"; var codeClose = opts.codeClose || "\n```\n\n"; var tokens = getTokens(filename); var state = "code"; var content = ""; var codeBuffer = ""; // buffer for code output function appendCode() { if (state === "code") { state = "text"; if (!isWhitespace(codeBuffer)) { content += codeOpen + codeBuffer.replace(/^(?:\s*\n)+/, "").replace(/[\s\n]*$/, "") + codeClose; } codeBuffer = ""; } } function appendText(value) { if (content === "") { content = value; } else { content += "\n" + value; } } tokens.forEach(function (token) { if (token.type === "Plain") { appendCode(); appendText(token.value); } else if (token.type === "EOF") { appendCode(); appendText(""); } else if (token.type === "Comment" && token.value.type === "Block" && token.value.value[0] === "*") { appendCode(); // literate comment var comment = token.value; // block comment starting with /** var value = comment.value.slice(1); appendText(unindent(value)); } else if (code) { // Code if (state !== "code") { state = "code"; codeBuffer = ""; } codeBuffer += token.raw; } }); // Append code at end of the file appendCode(); // Just one newline at eof content = content.replace(/\n+$/, "\n"); return content; } module.exports = literate;