/** * 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.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(''); }); }); dom.h3('In JavaScript', function(){ dom.code(function(){ dom.text('var userInputString = angular.formatter.'); dom.text(self.shortName); dom.text('.format(modelValue);'); }); dom.html('
'); 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(''); }); }); 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'); } 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('>'); } }); }); 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(/(
[\s\S]*?<\/pre>)/),
      match;

  parts.forEach(function(text, i){
    if (text.match(/^
/)) {
      text = text.
        replace(/^
/, '
').
        replace(/<\/pre>/, '
'); } else { text = text.replace(//gm, '<angular/>'); text = new Showdown.converter().makeHtml(text.replace(/^#/gm, '###')); while (match = text.match(R_LINK)) { text = text.replace(match[0], '' + (match[4] || match[1]) + ''); } } 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(/^

/, ''); 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 b.name ? 1 : 0); } } ////////////////////////////////////////////////////////// function property(name) { return function(value){ return value[name]; }; }