Skip to content

Instantly share code, notes, and snippets.

@larzconwell
Created October 17, 2012 00:18
Show Gist options
  • Save larzconwell/3902950 to your computer and use it in GitHub Desktop.
Save larzconwell/3902950 to your computer and use it in GitHub Desktop.
Don't use this
#!/usr/bin/env node
// Documentation path should be the only given argument
var docsPath = process.argv.slice(2)[0];
if (!docsPath) {
console.log("Please provide a path to generate docs from.");
process.exit(1);
}
var path = require('path')
, fs = require('fs')
, exec = require('child_process').exec
, docsStats = {}
, files = []
, cmd = ""
, parseDocs = {}
, lexDocs = {};
// Normalize and ensure path exists
docsPath = path.normalize(docsPath);
if (!fs.existsSync(docsPath)) {
console.log("The path \"" + docsPath + "\" doesn't exist.");
process.exit(1);
}
// Add files from the given path, if a directory is given then get it's files
// Todo: Add support for deeper than 1 level directory
docsStats = fs.statSync(docsPath);
if (docsStats.isDirectory()) {
var paths = fs.readdirSync(docsPath);
for (var i = 0; i < paths.length; i++) {
files.push(path.join(docsPath, paths[i]));
}
}
else if (docsStats.isFile()) {
files.push(docsPath);
}
// Get the inline code documentation for each file and parse it out to the actual
// code docs and append it to a file
for (var i = 0; i < files.length; i++) {
// This matches multi line from /** to */, which is
// the same as jsdoc comments
cmd = "awk '/\\/\\*\\*/,/\\*\\//' " + files[i];
exec(cmd, function (err, stdout, stderr) {
if (err) {
throw err;
}
fs.appendFileSync("output.md", parseDocs(stdout));
});
}
// Takes data from /** to */ and converts the contents inside the block
// into data for the parser
lexDocs = function (data) {
var lines = data.split('\n')
, line = ""
, i = 0
, started = false // Holds state for "/**"
, hasName = false // Holds state for the name attribute
, object = {};
for (i = 0; i < lines.length; i++) {
line = lines[i];
// If the line is the beginning then set start to true
if (line.match("\\/\\*\\*")) {
started = true;
continue;
}
// When we get to a @name attribute add it and set hasName to true
// this is so we can manage namespaces and other attributes
if (started && !line.match("@namespace") && line.match("@name")) {
// Assume the name includes the namespace(e.g., @name array#join)
object.name = line.replace(/\s*@name\s*.*#/, "");
hasName = true;
continue;
}
// Add the @namespace attribute removing the original name attribute
// because namespaces should be by themselves
if (started && hasName && line.match("@namespace")) {
object.namespace = line.replace(/\s*@namespace\s*/, "");
delete object.name; // We don't need this since we have a namespace
hasName = false;
continue;
}
// Simply add the @description attribute if it has a name and has started
// Also check if it's a multi-line description
//
// Note: Multi-line descriptions should be as follows:
// @description some description
// continued description
if (started && hasName && line.match("@description")) {
var val = ""
, descriptionLen = 0
, reg = {};
// Remove whitespace and attribute name, also getting the length of the match
object.description = line.replace(/\s*@description\s*/, function (match) {
descriptionLen = match.length;
return "";
});
// Create regexp that matches the description lengths whitespace
reg = new RegExp("\\s{" + descriptionLen + "}");
// Search 5 lines ahead, if they have the same length of whitespace as the
// description then assume it's a multi-line description
for (var j = i; j < i+5; j++) {
val = lines[j];
if (val && reg.test(val)) {
object.description += " " + val.replace(reg, "");
}
}
continue;
}
// If the line is a param that's a option to a param object
// then set it as the options
if (started && hasName && line.match(/@param.*\[.*\..*\]/)) {
// e.g, Type^name=default::description
var param = line.replace(/\s*@param\s{*/, "")
.replace(/}\s\[/, "^").replace(/\^.*?\./, "^")
.replace(/]\s*/, "::");
// Get the default value if it has one
var defaultVal = param.replace(/::.*/, "");
if (defaultVal.match("=")) {
defaultVal = defaultVal.replace(/.*=/, "")
} else {
defaultVal = "";
}
// Set new option object, could've done a string like the params
// but this is much more complex, so figure it out in the lexer
var opt = {
type: param.replace(/\^.*/, ""),
name: param.replace(/.*\^/, "").replace(/(=|::).*/, ""),
default: defaultVal,
description: param.replace(/.*::/, "")
};
if (object.options) {
object.options.push(opt);
} else {
object.options = [opt];
}
continue;
}
// Add an @param attribute to the params array
if (started && hasName && line.match("@param")) {
// e.g., Type^name
var param = line.replace(/\s*@param\s*{/, "")
.replace(/}\s/, "^").replace(/\s.*/, "");
var name = param.replace(/.*\^/, "")
, type = param.replace(/\^.*/, "");
param = name + "<" + type + ">";
if (object.params) {
object.params.push(param);
} else {
object.params = [param];
}
continue;
}
// If we've started and it's the ending marker cleanup and break out
// of the loop
if (started && line.match("\\*\\/")) {
delete started;
delete hasName;
break;
}
}
return object;
};
// Function to read content and then parse the docs to a readable format from
// jsdoc format
parseDocs = function (content) {
if (!content) {
return '';
}
var lines = content.split('\n')
, line = ""
, data = "" // Holds temp lex content from /** to */
, started = false // Holds state for the beginning /** marker
, lexData = {}
, docs = "";
for (var i = 0; i < lines.length; i++) {
line = lines[i];
// If line is the opening of a block add it to data
// and set started to true
if (line.match("\\/\\*\\*")) {
data += line + '\n';
started = true;
continue;
}
if (started) {
// If the line is a ending marker then lex the data and parse it
// to markdown
if (line.match("\\*\\/")) {
data += line;
lexData = lexDocs(data);
// If lex contains a namespace add it as a H4
if (lexData.namespace) {
docs += "#### " + lexData.namespace + '\n\n';
}
// If lex contains a name add it as an H4 then if it also has params
// then add the function signature with optional arguments
if (lexData.name) {
docs += "#### " + lexData.name + '\n';
docs += "`" + lexData.name + "(";
if (lexData.params) {
docs += lexData.params.join(", ");
}
docs += ")`\n";
docs += '\n';
}
// If lex contains an array of options
// then parse out each option
if (lexData.options && lexData.options.length > 0) {
var val = {};
docs += "#####Options\n";
for (var j = 0; j < lexData.options.length; j++) {
val = lexData.options[j];
docs += "- `" + val.name + "` ";
docs += "[" + val.type + "]";
if (val.description) {
docs += " " + val.description;
}
if (val.default) {
docs += "(Default: " + val.default + ")";
}
docs += "\n";
}
docs += "\n";
}
// Add the description and the empty example block and also add the content
// breaker(* * *)
if (lexData.description) {
docs += lexData.description += "\n\n#####Examples\n```\n```\n\n* * *\n\n";
}
data = "";
started = false;
}
// Just add the content to data and add a newline
else {
data += line + '\n';
}
}
}
return docs;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment