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 @@ +

{{name}}

+

Usage

+

In HTML Template Binding

+ + {{ + {{paramFirst.name}}_expression + | {{shortName}}{{#paramRest}}{{^default}}:{{name}}{{/default}}{{#default}}[:{{name}}={{default}}]{{/default}}{{/paramRest}} + }} + +

In JavaScript

+ +angular.filter.{{shortName}}({{paramFirst.name}}{{#paramRest}}, {{name}}{{/paramRest}} ); + + +

Parameters

+ + +

Returns

+{{{returns}}} + +

CSS

+{{{css}}} + +

Description

+{{{description}}} + + +{{example}} + \ No newline at end of file diff --git a/docs/filter:currency.html b/docs/filter:currency.html deleted file mode 100644 index bd277bb8..00000000 --- a/docs/filter:currency.html +++ /dev/null @@ -1,21 +0,0 @@ -

filter:currency

- -

formats a number as a currency (ie $1,234.56)

- -

@format expression | currency

- -

-
-{{amount | currency}} -

- -

- it('should init with 1234.56', function(){ - expect(bind('amount')).toEqual('$1,234.56'); - }); - it('should update', function(){ - element(':input[name=amount]').value('-1234'); - expect(bind('amount')).toEqual('-$1,234.00'); - expect(bind('amount')).toHaveColor('red'); - }); -

diff --git a/docs/filter:currency.md b/docs/filter:currency.md deleted file mode 100644 index 44687a91..00000000 --- a/docs/filter:currency.md +++ /dev/null @@ -1,20 +0,0 @@ -# filter:currency -formats a number as a currency (ie $1,234.56) - -@format _expression_ | currency - - -
-{{amount | currency}} -
- - - it('should init with 1234.56', function(){ - expect(bind('amount')).toEqual('$1,234.56'); - }); - it('should update', function(){ - element(':input[name=amount]').value('-1234'); - expect(bind('amount')).toEqual('-$1,234.00'); - expect(bind('amount')).toHaveColor('red'); - }); - \ No newline at end of file diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 00000000..f61893a4 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + +
+
+ {{name}} + +
+
+ + \ No newline at end of file diff --git a/docs/overview.template b/docs/overview.template new file mode 100644 index 00000000..dbb91e2a --- /dev/null +++ b/docs/overview.template @@ -0,0 +1 @@ +{{{description}}} \ No newline at end of file diff --git a/docs/wiki_widgets.css b/docs/wiki_widgets.css new file mode 100644 index 00000000..a1f6e939 --- /dev/null +++ b/docs/wiki_widgets.css @@ -0,0 +1,58 @@ +WIKI\:SOURCE, +WIKI\:SOURCE>ul.tabs, +WIKI\:SOURCE>ul.tabs>li { + display: block; + margin: 0; + padding: 0; +} +WIKI\:SOURCE>ul.tabs { + margin: 0 !important; + padding: 0 !important; +} + +WIKI\:SOURCE > ul.tabs > li.tab { + margin: 0 0 0 .8em !important; + display: inline-block; + border: 1px solid gray; + padding: .1em .8em .4em .8em !important; + -moz-border-radius-topleft: 5px; + -moz-border-radius-topright: 5px; + -webkit-border-top-left-radius: 5px; + -webkit-border-top-right-radius: 5px; + border-bottom: none; + cursor: pointer; + background-color: lightgray; + margin-bottom: -2px; + position: relative; + z-index: 0; +} + +WIKI\:SOURCE > ul.tabs > li.tab.selected { + z-index: 2; + background-color: white; + font-weight: bold; + border-width: 2px; +} + +WIKI\:SOURCE > ul.tabs > li.pane { + border: 2px solid gray; + border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + background-color: white; + padding: 5px !important; +} + +WIKI\:SOURCE > ul.tabs > li.pane { + display:none; +} + +WIKI\:SOURCE > ul.tabs > li.pane.selected { + position: relative; + z-index: 1; + display:block; +} +WIKI\:SOURCE > ul.tabs > li.pane.source { + font-size: .8em !important; +} +////////////////// \ No newline at end of file diff --git a/docs/wiki_widgets.js b/docs/wiki_widgets.js new file mode 100644 index 00000000..0147536b --- /dev/null +++ b/docs/wiki_widgets.js @@ -0,0 +1,51 @@ +(function(){ + var HTML_TEMPLATE = + '\n' + + '\n' + + ' \n' + + ' \n' + + ' \n' + + ' \n' + + '_HTML_SOURCE_\n' + + ' \n' + + ''; + + angular.widget('WIKI:SOURCE', function(element){ + this.descend(true); + var html = element.text(); + element.show(); + var tabs = angular.element( + ''); + var pre = tabs. + find('>li.source>pre'). + text(HTML_TEMPLATE.replace('_HTML_SOURCE_', html)); + var color = element.attr('color') || 'white'; + element.html(''); + element.append(tabs); + element.find('>ul.tabs>li.pane').css('background-color', color); + var script = (html.match(/]*>([\s\S]*)<\/script>/) || [])[1] || ''; + try { + eval(script); + } catch (e) { + alert(e); + } + return function(element){ + element.find('>ul.tabs>li.tab').click(function(){ + if ($(this).is(".selected")) return; + element. + find('>ul.tabs>li.selected'). + add(this). + add(element.find('>ul>li.pane.' + angular.element(this).attr('to'))). + toggleClass('selected'); + }); + }; + }); +})(); \ No newline at end of file -- cgit v1.2.3