Created
October 17, 2012 00:18
-
-
Save larzconwell/3902950 to your computer and use it in GitHub Desktop.
Don't use this
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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