From 659af29adbd041fbbbaf041ead53266210a61f4e Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Wed, 27 Oct 2010 15:31:10 -0700 Subject: jsdoc parser + generator + viewer + scenario runner - parse jsdocs from source code - generate prerendered (markdown + mustache) partials - generate json - generate scenario runner for examples in docs - basic angular doc viewer --- docs/callback.js | 66 +++++++++++++++ docs/collect.js | 206 ++++++++++++++++++++++++++++++++++++++++++++++ docs/docs-data.js | 1 + docs/docs-scenario.html | 9 ++ docs/docs-scenario.js | 9 ++ docs/docs.js | 7 ++ docs/filter.template | 33 ++++++++ docs/filter:currency.html | 21 ----- docs/filter:currency.md | 20 ----- docs/index.html | 25 ++++++ docs/overview.template | 1 + docs/wiki_widgets.css | 58 +++++++++++++ docs/wiki_widgets.js | 51 ++++++++++++ 13 files changed, 466 insertions(+), 41 deletions(-) create mode 100644 docs/callback.js create mode 100644 docs/collect.js create mode 100644 docs/docs-data.js create mode 100644 docs/docs-scenario.html create mode 100644 docs/docs-scenario.js create mode 100644 docs/docs.js create mode 100644 docs/filter.template delete mode 100644 docs/filter:currency.html delete mode 100644 docs/filter:currency.md create mode 100644 docs/index.html create mode 100644 docs/overview.template create mode 100644 docs/wiki_widgets.css create mode 100644 docs/wiki_widgets.js (limited to 'docs') diff --git a/docs/callback.js b/docs/callback.js new file mode 100644 index 00000000..0d0669d1 --- /dev/null +++ b/docs/callback.js @@ -0,0 +1,66 @@ +function noop(){} + +function chain(delegateFn, explicitDone){ + var onDoneFn = noop; + var onErrorFn = noop; + var waitForCount = 1; + delegateFn = delegateFn || noop; + var stackError = new Error('capture stack'); + + function decrementWaitFor() { + waitForCount--; + if (waitForCount == 0) + onDoneFn(); + } + + function self(){ + try { + return delegateFn.apply(self, arguments); + } catch (error) { + self.error(error); + } finally { + if (!explicitDone) + decrementWaitFor(); + } + }; + self.onDone = function(callback){ + onDoneFn = callback; + return self; + }; + self.onError = function(callback){ + onErrorFn = callback; + return self; + }; + self.waitFor = function(callback){ + if (waitForCount == 0) + throw new Error("Can not wait on already called callback."); + waitForCount++; + return chain(callback).onDone(decrementWaitFor).onError(self.error); + }; + + self.waitMany = function(callback){ + if (waitForCount == 0) + throw new Error("Can not wait on already called callback."); + waitForCount++; + return chain(callback, true).onDone(decrementWaitFor).onError(self.error); + }; + + self.done = function(callback){ + decrementWaitFor(); + }; + + self.error = function(error) { + var stack = stackError.stack.split(/\n\r?/).splice(2); + var nakedStack = []; + stack.forEach(function(frame){ + if (!frame.match(/callback\.js:\d+:\d+\)$/)) + nakedStack.push(frame); + }); + error.stack = error.stack + '\nCalled from:\n' + nakedStack.join('\n'); + onErrorFn(error); + }; + + return self; +} + +exports.chain = chain; diff --git a/docs/collect.js b/docs/collect.js new file mode 100644 index 00000000..a3c2a25c --- /dev/null +++ b/docs/collect.js @@ -0,0 +1,206 @@ +var fs = require('fs'), + spawn = require('child_process').spawn, + mustache = require('../lib/mustache'), + callback = require('./callback'), + markdown = require('../lib/markdown'); + +var documentation = { + section:{}, + all:[] +}; + +var SRC_DIR = "docs/"; +var OUTPUT_DIR = "build/docs/"; + +var work = callback.chain(function () { + console.log('Parsing Angular Reference Documentation'); + mkdirPath(OUTPUT_DIR, work.waitFor(function(){ + findJsFiles('src', work.waitMany(function(file) { + //console.log('reading', file, '...'); + findNgDoc(file, work.waitMany(function(doc) { + parseNgDoc(doc); + if (doc.ngdoc) { + delete doc.raw.text; + var section = documentation.section; + (section[doc.ngdoc] = section[doc.ngdoc] || []).push(doc); + documentation.all.push(doc); + console.log('Found:', doc.ngdoc + ':' + doc.shortName); + mergeTemplate( + doc.ngdoc + '.template', + doc.name + '.html', doc, work.waitFor()); + } + })); + })); + })); +}).onError(function(err){ + console.log('ERROR:', err.stack || err); +}).onDone(function(){ + mergeTemplate('docs-data.js', 'docs-data.js', {JSON:JSON.stringify(documentation)}, callback.chain()); + mergeTemplate('docs-scenario.js', 'docs-scenario.js', documentation, callback.chain()); + copy('docs-scenario.html', callback.chain()); + copy('index.html', callback.chain()); + mergeTemplate('docs.js', 'docs.js', documentation, callback.chain()); + mergeTemplate('wiki_widgets.css', 'wiki_widgets.css', documentation, callback.chain()); + mergeTemplate('wiki_widgets.js', 'wiki_widgets.js', documentation, callback.chain()); + console.log('DONE'); +}); +work(); +//////////////////// + +function noop(){} +function mkdirPath(path, callback) { + var parts = path.split(/\//); + path = '.'; + (function next(){ + if (parts.length) { + path += '/' + parts.shift(); + fs.mkdir(path, 0777, next); + } else { + callback(); + } + })(); +} + +function copy(name, callback){ + fs.readFile(SRC_DIR + name, callback.waitFor(function(err, content){ + if (err) return this.error(err); + fs.writeFile(OUTPUT_DIR + name, content, callback); + })); +} + +function mergeTemplate(template, output, doc, callback){ + fs.readFile(SRC_DIR + template, + callback.waitFor(function(err, template){ + if (err) return this.error(err); + var content = mustache.to_html(template.toString(), doc); + fs.writeFile(OUTPUT_DIR + output, content, callback); + })); +} + + +function unknownTag(doc, name) { + var error = "[" + doc.raw.file + ":" + doc.raw.line + "]: unknown tag: " + name; + console.log(error); + throw new Error(error); +} + +function valueTag(doc, name, value) { + doc[name] = value; +} + +function markdownTag(doc, name, value) { + doc[name] = markdown.toHTML(value); +} + +var TAG = { + ngdoc: valueTag, + example: valueTag, + scenario: valueTag, + namespace: valueTag, + css: valueTag, + see: valueTag, + 'function': valueTag, + description: markdownTag, + returns: markdownTag, + name: function(doc, name, value) { + doc.name = value; + doc.shortName = value.split(/\./).pop(); + }, + param: function(doc, name, value){ + doc.param = doc.param || []; + doc.paramRest = doc.paramRest || []; + var match = value.match(/^({([^\s=]+)(=([^\s]+))?}\s*)?([^\s]+)\s*(.*)/); + if (match) { + var param = { + type: match[2], + 'default':match[4], + name: match[5], + description:match[6]}; + doc.param.push(param); + if (!doc.paramFirst) { + doc.paramFirst = param; + } else { + doc.paramRest.push(param); + } + } else { + throw "[" + doc.raw.file + ":" + doc.raw.line + + "]: @param must be in format '{type} name=value description' got: " + value; + } + } +}; + +function parseNgDoc(doc){ + var atName; + var atText; + var match; + doc.raw.text.split(/\n/).forEach(function(line, lineNumber){ + if (match = line.match(/^@(\w+)(\s+(.*))?/)) { + // we found @name ... + // if we have existing name + if (atName) { + (TAG[atName] || unknownTag)(doc, atName, atText.join('\n')); + } + atName = match[1]; + atText = []; + if(match[3]) atText.push(match[3]); + } else { + if (atName) { + atText.push(line); + } else { + // ignore + } + } + }); + if (atName) { + (TAG[atName] || unknownTag)(doc, atName, atText.join('\n')); + } +} + +function findNgDoc(file, callback) { + fs.readFile(file, callback.waitFor(function(err, content){ + var lines = content.toString().split(/\n\r?/); + var doc; + var match; + var inDoc = false; + lines.forEach(function(line, lineNumber){ + lineNumber++; + // is the comment starting? + if (!inDoc && (match = line.match(/^\s*\/\*\*\s*(.*)$/))) { + line = match[1]; + inDoc = true; + doc = {raw:{file:file, line:lineNumber, text:[]}}; + } + // are we done? + if (inDoc && line.match(/\*\//)) { + doc.raw.text = doc.raw.text.join('\n'); + doc.raw.text = doc.raw.text.replace(/^\n/, ''); + if (doc.raw.text.match(/@ngdoc/)) + callback(doc); + doc = null; + inDoc = false; + } + // is the comment add text + if (inDoc){ + doc.raw.text.push(line.replace(/^\s*\*\s?/, '')); + } + }); + callback.done(); + })); +} + +function findJsFiles(dir, callback){ + fs.readdir(dir, callback.waitFor(function(err, files){ + if (err) return this.error(err); + files.forEach(function(file){ + var path = dir + '/' + file; + fs.lstat(path, callback.waitFor(function(err, stat){ + if (err) return this.error(err); + if (stat.isDirectory()) + findJsFiles(path, callback.waitMany(callback)); + else if (/\.js$/.test(path)) + callback(path); + })); + }); + callback.done(); + })); +} diff --git a/docs/docs-data.js b/docs/docs-data.js new file mode 100644 index 00000000..27b3fab5 --- /dev/null +++ b/docs/docs-data.js @@ -0,0 +1 @@ +NG_DOC={{{JSON}}}; \ No newline at end of file diff --git a/docs/docs-scenario.html b/docs/docs-scenario.html new file mode 100644 index 00000000..83ca6fdf --- /dev/null +++ b/docs/docs-scenario.html @@ -0,0 +1,9 @@ + + +
+ + + + + + \ No newline at end of file diff --git a/docs/docs-scenario.js b/docs/docs-scenario.js new file mode 100644 index 00000000..f0c6edb9 --- /dev/null +++ b/docs/docs-scenario.js @@ -0,0 +1,9 @@ +{{#all}} +describe('{{name}}', function(){ + beforeEach(function(){ + navigateTo('index.html#{{name}}'); + }); + // {{raw.file}}:{{raw.line}} +{{{scenario}}} +}); +{{/all}} \ No newline at end of file diff --git a/docs/docs.js b/docs/docs.js new file mode 100644 index 00000000..6f5c5034 --- /dev/null +++ b/docs/docs.js @@ -0,0 +1,7 @@ +function DocController($resource, $location){ + this.docs = $resource('documentation.json').get(); + this.getPartialDoc = function(){ + return encodeURIComponent($location.hashPath) + '.html'; + }; +} +DocController.$inject=['$resource', '$location']; \ No newline at end of file diff --git a/docs/filter.template b/docs/filter.template new file mode 100644 index 00000000..677a8785 --- /dev/null +++ b/docs/filter.template @@ -0,0 +1,33 @@ +formats a number as a currency (ie $1,234.56)
- -@format expression | currency
- -
-{{amount | currency}}
-
|
+
+ {{name}}
+
+
+ |
+