diff options
| author | Misko Hevery | 2010-12-23 00:44:27 +0100 |
|---|---|---|
| committer | Misko Hevery | 2011-01-10 11:50:11 -0800 |
| commit | 4f22d6866c052fb5b770ce4f377cecacacd9e6d8 (patch) | |
| tree | 6bdb1c5eb70cfd7e6bcf143c121c53025a0489a4 /docs/src/ngdoc.js | |
| parent | aab3df7aeaf79908e8b6212288b283adb42b1ce6 (diff) | |
| download | angular.js-4f22d6866c052fb5b770ce4f377cecacacd9e6d8.tar.bz2 | |
complete rewrite of documentation generation
- romeved mustache.js
- unified templates
- improved testability of the code
Diffstat (limited to 'docs/src/ngdoc.js')
| -rw-r--r-- | docs/src/ngdoc.js | 614 |
1 files changed, 614 insertions, 0 deletions
diff --git a/docs/src/ngdoc.js b/docs/src/ngdoc.js new file mode 100644 index 00000000..ac7d0bb1 --- /dev/null +++ b/docs/src/ngdoc.js @@ -0,0 +1,614 @@ +/** + * All parsing/transformation code goes here. All code here should be sync to ease testing. + */ + +var Showdown = require('showdown').Showdown; +var DOM = require('dom.js').DOM; +var NEW_LINE = /\n\r?/; + +exports.markdown = markdown; +exports.markdownNoP = markdownNoP; +exports.trim = trim; +exports.metadata = metadata; +exports.scenarios = scenarios; +exports.merge = merge; +exports.Doc = Doc; + +////////////////////////////////////////////////////////// +function Doc(text, file, line) { + if (typeof text == 'object') { + for ( var key in text) { + this[key] = text[key]; + } + } else { + this.text = text; + this.file = file; + this.line = line; + } +} +Doc.METADATA_IGNORE = (function(){ + var words = require('fs').readFileSync(__dirname + '/ignore.words', 'utf8'); + return words.toString().split(/[,\s\n\r]+/gm); +})(); + + + +Doc.prototype = { + keywords: function keywords(){ + var keywords = {}; + Doc.METADATA_IGNORE.forEach(function(ignore){ keywords[ignore] = true; }); + var words = []; + var tokens = this.text.toLowerCase().split(/[,\.\`\'\"\s]+/mg); + tokens.forEach(function(key){ + var match = key.match(/^(([a-z]|ng\:)[\w\_\-]{2,})/); + if (match){ + key = match[1]; + if (!keywords[key]) { + keywords[key] = true; + words.push(key); + } + } + }); + words.sort(); + return words.join(' '); + }, + + parse: function(){ + var atName; + var atText; + var match; + var self = this; + self.text.split(NEW_LINE).forEach(function(line){ + if (match = line.match(/^\s*@(\w+)(\s+(.*))?/)) { + // we found @name ... + // if we have existing name + flush(); + atName = match[1]; + atText = []; + if(match[3]) atText.push(match[3]); + } else { + if (atName) { + atText.push(line); + } + } + }); + flush(); + this.shortName = (this.name || '').split(/[\.#]/).pop(); + this.description = markdown(this.description); + + function flush(){ + if (atName) { + var text = trim(atText.join('\n')); + if (atName == 'param') { + var match = text.match(/^{([^}=]+)(=)?}\s+(([^\s=]+)|\[(\S+)=([^\]]+)\])\s+(.*)/); + // 1 12 2 34 4 5 5 6 6 3 7 7 + if (!match) { + throw new Error("Not a valid 'param' format: " + text); + } + var param = { + name: match[5] || match[4], + description:markdownNoP(text.replace(match[0], match[7])), + type: match[1], + optional: !!match[2], + 'default':match[6] + }; + self.param = self.param || []; + self.param.push(param); + } else if (atName == 'returns') { + var match = text.match(/^{([^}=]+)}\s+(.*)/); + if (!match) { + throw new Error("Not a valid 'returns' format: " + text); + } + self.returns = { + type: match[1], + description: markdownNoP(text.replace(match[0], match[2])) + }; + } else if(atName == 'requires') { + self.requires = self.requires || []; + self.requires.push(text); + } else if(atName == 'property') { + var match = text.match(/^({(\S+)}\s*)?(\S+)(\s+(.*))?/); + if (!match) { + throw new Error("Not a valid 'property' format: " + text); + } + var property = { + type: match[2], + name: match[3], + description: match[5] || '' + }; + self.properties = self.properties || []; + self.properties.push(property); + } else { + self[atName] = text; + } + } + } + }, + + html: function(){ + var dom = new DOM(), + self = this; + + dom.h(this.name, function(){ + notice('workInProgress', 'Work in Progress', + 'This page is currently being revised. It might be incomplete or contain inaccuracies.'); + notice('depricated', 'Depricated API'); + dom.h('Description', self.description, html); + dom.h('Dependencies', self.requires); + + usage(); + + dom.h('Methods', self.methods, function(method){ + var signature = (method.param || []).map(property('name')); + dom.h(method.shortName + '(' + signature.join(', ') + ')', method, function(){ + dom.html(method.description); + method.html_usage_parameters(dom); + dom.example(method.example, false); + }); + }); + dom.h('Properties', self.properties, function(property){ + dom.h(property.name, function(){ + dom.text(property.description); + dom.example(property.example, false); + }); + }); + + dom.example(self.example, self.scenario); + }); + + return dom.toString(); + + ////////////////////////// + + function html(text){ + this.html(text); + } + + function usage(){ + (self['html_usage_' + self.ngdoc] || function(){ + throw new Error("Don't know how to format @ngdoc: " + self.ngdoc); + }).call(self, dom); + } + + function section(name, property, fn) { + var value = self[property]; + if (value) { + dom.h2(name); + if (typeof value == 'string') { + value = markdown(value) + '\n'; + fn ? fn(value) : dom.html(value); + } else if (value instanceof Array) { + dom.ul(value, fn); + } + } + } + + function notice(name, legend, msg){ + if (self[name] == undefined) return; + dom.tag('fieldset', {'class':name}, function(dom){ + dom.tag('legend', legend); + dom.text(msg); + }); + } + + }, + + html_usage_parameters: function(dom) { + dom.h('Parameters', this.param, function(param){ + dom.tag('code', function(){ + dom.text(param.name); + if (param.optional) { + dom.tag('i', function(){ + dom.text('(optional'); + if(param['default']) { + dom.text('=' + param['default']); + } + dom.text(')'); + }); + } + dom.text(' – {'); + dom.text(param.type); + dom.text('} – '); + }); + dom.html(param.description); + }); + }, + + html_usage_returns: function(dom) { + var self = this; + if (self.returns) { + dom.h('Returns', function(){ + dom.tag('code', self.returns.type); + dom.text('– '); + dom.html(self.returns.description); + }); + } + }, + + html_usage_function: function(dom){ + var self = this; + dom.h('Usage', function(){ + dom.code(function(){ + dom.text(self.name); + dom.text('('); + var first = true; + (self.param || []).forEach(function(param){ + if (first) { + first = false; + } else { + dom.text(', '); + } + dom.text(param.name); + }); + dom.text(');'); + }); + + self.html_usage_parameters(dom); + self.html_usage_returns(dom); + }); + }, + + html_usage_directive: function(dom){ + var self = this; + dom.h('Usage', function(){ + dom.tag('pre', {'class':"brush: js; html-script: true;"}, function(){ + dom.text('<' + self.element + ' '); + dom.text(self.shortName); + if (self.param) { + dom.text('="' + self.param[0].name + '"'); + } + dom.text('>\n ...\n'); + dom.text('</' + self.element + '>'); + }); + self.html_usage_parameters(dom); + }); + }, + + html_usage_filter: function(dom){ + var self = this; + dom.h('Usage', function(){ + dom.h('In HTML Template Binding', function(){ + dom.tag('code', function(){ + dom.text('{{ '); + dom.text(self.shortName); + dom.text('_expression | '); + dom.text(self.shortName); + var first = true; + (self.param||[]).forEach(function(param){ + if (first) { + first = false; + } else { + if (param.optional) { + dom.tag('i', function(){ + dom.text('[:' + param.name + ']'); + }); + } else { + dom.text(':' + param.name); + } + } + }); + dom.text(' }}'); + }); + }); + + dom.h3('In JavaScript', function(){ + dom.tag('code', function(){ + dom.text('angular.filter.'); + dom.text(self.shortName); + dom.text('('); + var first = true; + (self.param||[]).forEach(function(param){ + if (first) { + first = false; + dom.text(param.name); + } else { + if (param.optional) { + dom.tag('i', function(){ + dom.text('[, ' + param.name + ']'); + }); + } else { + dom.text(', ' + param.name); + } + } + }); + dom.text(')'); + }); + }); + + self.html_usage_parameters(dom); + self.html_usage_returns(dom); + }); + }, + + html_usage_formatter: function(dom){ + var self = this; + dom.h('Usage', function(){ + dom.h('In HTML Template Binding', function(){ + dom.code(function(){ + dom.text('<input type="text" ng:format="'); + dom.text(self.shortName); + dom.text('">'); + }); + }); + + dom.h3('In JavaScript', function(){ + dom.code(function(){ + dom.text('var userInputString = angular.formatter.'); + dom.text(self.shortName); + dom.text('.format(modelValue);'); + }); + dom.html('<br/>'); + dom.code(function(){ + dom.text('var modelValue = angular.formatter.'); + dom.text(self.shortName); + dom.text('.parse(userInputString);'); + }); + }); + + self.html_usage_returns(dom); + }); + }, + + html_usage_validator: function(dom){ + var self = this; + dom.h('Usage', function(){ + dom.h('In HTML Template Binding', function(){ + dom.code(function(){ + dom.text('<input type="text" ng:validate="'); + dom.text(self.shortName); + var first = true; + (self.param||[]).forEach(function(param){ + if (first) { + first = false; + } else { + if (param.optional) { + dom.text('[:' + param.name + ']'); + } else { + dom.text(':' + param.name); + } + } + }); + dom.text('"/>'); + }); + }); + + dom.h('In JavaScript', function(){ + dom.code(function(){ + dom.text('angular.validator.'); + dom.text(self.shortName); + dom.text('('); + var first = true; + (self.param||[]).forEach(function(param){ + if (first) { + first = false; + dom.text(param.name); + } else { + if (param.optional) { + dom.text('[, ' + param.name + ']'); + } else { + dom.text(', ' + param.name); + } + } + }); + dom.text(')'); + }); + }); + + self.html_usage_parameters(dom); + self.html_usage_returns(dom); + }); + }, + + html_usage_widget: function(dom){ + var self = this; + dom.h('Usage', function(){ + dom.h('In HTML Template Binding', function(){ + dom.code(function(){ + if (self.shortName.match(/^@/)) { + dom.text('<'); + dom.text(self.element); + dom.text(' '); + dom.text(self.shortName.substring(1)); + if (self.param) { + dom.text('="'); + dom.text(self.param[0].name); + dom.text('"'); + } + dom.text('>\n ...\n</'); + dom.text(self.element); + dom.text('>'); + } else { + dom.text('<'); + dom.text(self.shortName); + (self.param||[]).forEach(function(param){ + if (param.optional) { + dom.text(' [' + param.name + '="..."]'); + } else { + dom.text(' ' + param.name + '="..."'); + } + }); + dom.text('></'); + dom.text(self.shortName); + dom.text('>'); + } + }); + }); + + self.html_usage_parameters(dom); + self.html_usage_returns(dom); + }); + }, + + html_usage_overview: function(dom){ + }, + + html_usage_service: function(dom){ + } + +}; +////////////////////////////////////////////////////////// + + +////////////////////////////////////////////////////////// +function markdown (text) { + if (!text) return text; + var parts = text.split(/(<pre>[\s\S]*?<\/pre>)/), + match; + + parts.forEach(function(text, i){ + if (text.match(/^<pre>/)) { + text = text. + replace(/^<pre>/, '<div ng:non-bindable><pre class="brush: js; html-script: true;">'). + replace(/<\/pre>/, '</pre></div>'); + } else { + text = text.replace(/<angular\/>/gm, '<tt><angular/></tt>'); + text = new Showdown.converter().makeHtml(text.replace(/^#/gm, '###')); + + while (match = text.match(R_LINK)) { + text = text.replace(match[0], '<a href="#!' + match[1] + '"><code>' + + (match[4] || match[1]) + + '</code></a>'); + } + } + parts[i] = text; + }); + return parts.join(''); +}; +var R_LINK = /{@link ([^\s}]+)((\s|\n)+(.+?))?\s*}/m; + // 1 123 3 4 42 +function markdownNoP(text) { + var lines = markdown(text).split(NEW_LINE); + var last = lines.length - 1; + lines[0] = lines[0].replace(/^<p>/, ''); + lines[last] = lines[last].replace(/<\/p>$/, ''); + return lines.join('\n'); +} + + +////////////////////////////////////////////////////////// +function scenarios(docs){ + var specs = []; + docs.forEach(function(doc){ + if (doc.scenario) { + specs.push('describe("'); + specs.push(doc.name); + specs.push('", function(){\n'); + specs.push(' beforeEach(function(){\n'); + specs.push(' browser().navigateTo("index.html#!' + doc.name + '");'); + specs.push(' });\n\n'); + specs.push(doc.scenario); + specs.push('\n});\n\n'); + } + }); + return specs; +} + + +////////////////////////////////////////////////////////// +function metadata(docs){ + var words = []; + docs.forEach(function(doc){ + words.push({ + name:doc.name, + type: doc.ngdoc, + keywords:doc.keywords() + }); + }); + words.sort(keywordSort); + return words; +} + +function keywordSort(a,b){ + // supper ugly comparator that orders all utility methods and objects before all the other stuff + // like widgets, directives, services, etc. + // Mother of all beautiful code please forgive me for the sin that this code certainly is. + + if (a.name === b.name) return 0; + if (a.name === 'angular') return -1; + if (b.name === 'angular') return 1; + + function namespacedName(page) { + return (page.name.match(/\./g).length === 1 && page.type !== 'overview' ? '0' : '1') + page.name; + } + + var namespacedA = namespacedName(a), + namespacedB = namespacedName(b); + + return namespacedA < namespacedB ? -1 : 1; +} + + +////////////////////////////////////////////////////////// +function trim(text) { + var MAX = 9999; + var empty = RegExp.prototype.test.bind(/^\s*$/); + var lines = text.split('\n'); + var minIndent = MAX; + lines.forEach(function(line){ + minIndent = Math.min(minIndent, indent(line)); + }); + for ( var i = 0; i < lines.length; i++) { + lines[i] = lines[i].substring(minIndent); + } + // remove leading lines + while (empty(lines[0])) { + lines.shift(); + } + // remove trailing + while (empty(lines[lines.length - 1])) { + lines.pop(); + } + return lines.join('\n'); + + function indent(line) { + for(var i = 0; i < line.length; i++) { + if (line.charAt(i) != ' ') { + return i; + } + } + return MAX; + } +} + +////////////////////////////////////////////////////////// +function merge(docs){ + var byName = {}; + docs.forEach(function(doc){ + byName[doc.name] = doc; + }); + for(var i=0; i<docs.length;) { + if (findParent(docs[i], 'method') || + findParent(docs[i], 'property')) { + docs.splice(i, 1); + } else { + i++; + } + } + + function findParent(doc, name){ + var parentName = doc[name+'Of']; + if (!parentName) return false; + + var parent = byName[parentName]; + if (!parent) + throw new Error("No parent named '" + parentName + "' for '" + + doc.name + "' in @" + name + "Of."); + + var listName = (name + 's').replace(/ys$/, 'ies'); + var list = parent[listName] = (parent[listName] || []); + list.push(doc); + list.sort(orderByName); + return true; + } + + function orderByName(a, b){ + return a.name < b.name ? -1 : (a.name > b.name ? 1 : 0); + } +} +////////////////////////////////////////////////////////// + +function property(name) { + return function(value){ + return value[name]; + }; +}
\ No newline at end of file |
