aboutsummaryrefslogtreecommitdiffstats
path: root/docs/src
diff options
context:
space:
mode:
authorMisko Hevery2010-12-23 00:44:27 +0100
committerMisko Hevery2011-01-10 11:50:11 -0800
commit4f22d6866c052fb5b770ce4f377cecacacd9e6d8 (patch)
tree6bdb1c5eb70cfd7e6bcf143c121c53025a0489a4 /docs/src
parentaab3df7aeaf79908e8b6212288b283adb42b1ce6 (diff)
downloadangular.js-4f22d6866c052fb5b770ce4f377cecacacd9e6d8.tar.bz2
complete rewrite of documentation generation
- romeved mustache.js - unified templates - improved testability of the code
Diffstat (limited to 'docs/src')
-rw-r--r--docs/src/callback.js69
-rw-r--r--docs/src/dom.js123
-rw-r--r--docs/src/gen-docs.js42
-rw-r--r--docs/src/ignore.words0
-rw-r--r--docs/src/ngdoc.js614
-rw-r--r--docs/src/reader.js91
-rw-r--r--docs/src/templates/doc_widgets.css35
-rw-r--r--docs/src/templates/doc_widgets.js71
-rw-r--r--docs/src/templates/docs-scenario.html10
-rw-r--r--docs/src/templates/docs-scenario.js9
-rw-r--r--docs/src/templates/docs.css262
-rw-r--r--docs/src/templates/docs.js47
-rw-r--r--docs/src/templates/index.html45
-rw-r--r--docs/src/writer.js61
14 files changed, 1479 insertions, 0 deletions
diff --git a/docs/src/callback.js b/docs/src/callback.js
new file mode 100644
index 00000000..aaf69cde
--- /dev/null
+++ b/docs/src/callback.js
@@ -0,0 +1,69 @@
+function noop(){}
+
+function chain(delegateFn, explicitDone){
+ var onDoneFn = noop;
+ var onErrorFn = function(e){
+ console.error(e.stack || e);
+ process.exit(-1);
+ };
+ 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/src/dom.js b/docs/src/dom.js
new file mode 100644
index 00000000..e6dd09e6
--- /dev/null
+++ b/docs/src/dom.js
@@ -0,0 +1,123 @@
+/**
+ * DOM generation class
+ */
+
+exports.DOM = DOM;
+
+//////////////////////////////////////////////////////////
+
+function DOM(){
+ this.out = [];
+ this.headingDepth = 1;
+}
+
+var INLINE_TAGS = {
+ i: true,
+ b: true
+};
+
+DOM.prototype = {
+ toString: function() {
+ return this.out.join('');
+ },
+
+ text: function(content) {
+ if (typeof content == "string") {
+ this.out.push(content.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'));
+ } else if (typeof content == 'function') {
+ content.call(this, this);
+ } else if (content instanceof Array) {
+ this.ul(content);
+ }
+ },
+
+ html: function(html) {
+ if (html) {
+ this.out.push(html);
+ }
+ },
+
+ tag: function(name, attr, text) {
+ if (!text) {
+ text = attr;
+ attr = {};
+ if (name == 'code')
+ attr['ng:non-bindable'] = '';
+ }
+ this.out.push('<' + name);
+ for(var key in attr) {
+ this.out.push(" " + key + '="' + attr[key] + '"');
+ }
+ this.out.push('>');
+ this.text(text);
+ this.out.push('</' + name + '>');
+ if (!INLINE_TAGS[name])
+ this.out.push('\n');
+ },
+
+ code: function(text) {
+ this.tag('div', {'ng:non-bindable':''}, function(){
+ this.tag('pre', {'class':"brush: js; html-script: true;"}, text);
+ });
+ },
+
+ example: function(source, scenario) {
+ if (source || scenario) {
+ this.h('Example', function(){
+ if (scenario === false) {
+ this.code(source);
+ } else {
+ this.tag('doc:example', function(){
+ if (source) this.tag('doc:source', source);
+ if (scenario) this.tag('doc:scenario', scenario);
+ });
+ }
+ });
+ }
+ },
+
+ h: function(heading, content, fn){
+ if (content==undefined || content && content.legth == 0) return;
+ this.tag('h' + this.headingDepth, heading);
+ this.headingDepth++;
+ if (content instanceof Array) {
+ this.ul(content, {'class': heading.toLowerCase()}, fn);
+ } else if (fn) {
+ fn.call(this, content);
+ } else {
+ this.text(content);
+ }
+ this.headingDepth--;
+ },
+
+ h1: function(attr, text) {
+ this.tag('h1', attr, text);
+ },
+
+ h2: function(attr, text) {
+ this.tag('h2', attr, text);
+ },
+
+ h3: function(attr, text) {
+ this.tag('h3', attr, text);
+ },
+
+ p: function(attr, text) {
+ this.tag('p', attr, text);
+ },
+
+ ul: function(list, attr, fn) {
+ if (typeof attr == 'function') {
+ fn = attr;
+ attr = {};
+ }
+ this.tag('ul', attr, function(dom){
+ list.forEach(function(item){
+ dom.out.push('<li>');
+ dom.text(fn ? fn(item) : item);
+ dom.out.push('</li>\n');
+ });
+ });
+ }
+
+}; \ No newline at end of file
diff --git a/docs/src/gen-docs.js b/docs/src/gen-docs.js
new file mode 100644
index 00000000..b4e30a53
--- /dev/null
+++ b/docs/src/gen-docs.js
@@ -0,0 +1,42 @@
+require.paths.push(__dirname);
+require.paths.push('lib');
+var reader = require('reader.js'),
+ ngdoc = require('ngdoc.js'),
+ writer = require('writer.js'),
+ callback = require('callback.js');
+
+var docs = [];
+var start;
+var work = callback.chain(function(){
+ start = now();
+ console.log('Generating Angular Reference Documentation...');
+ reader.collect(work.waitMany(function(text, file, line){
+ var doc = new ngdoc.Doc(text, file, line);
+ docs.push(doc);
+ doc.parse();
+ }));
+});
+var writes = callback.chain(function(){
+ ngdoc.merge(docs);
+ docs.forEach(function(doc){
+ writer.output(doc.name + '.html', doc.html(), writes.waitFor());
+ });
+ var metadata = ngdoc.metadata(docs);
+ writer.output('docs-keywords.js', ['NG_PAGES=', JSON.stringify(metadata), ';'], writes.waitFor());
+ writer.copy('index.html', writes.waitFor());
+ writer.copy('docs.js', writes.waitFor());
+ writer.copy('docs.css', writes.waitFor());
+ writer.copy('doc_widgets.js', writes.waitFor());
+ writer.copy('doc_widgets.css', writes.waitFor());
+ writer.copy('docs-scenario.html', writes.waitFor());
+ writer.output('docs-scenario.js', ngdoc.scenarios(docs), writes.waitFor());
+});
+writes.onDone(function(){
+ console.log('DONE. Generated ' + docs.length + ' pages in ' +
+ (now()-start) + 'ms.' );
+});
+work.onDone(writes);
+writer.makeDir('build/docs', work);
+
+///////////////////////////////////
+function now(){ return new Date().getTime(); }
diff --git a/docs/src/ignore.words b/docs/src/ignore.words
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/docs/src/ignore.words
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>&lt;angular/&gt;</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
diff --git a/docs/src/reader.js b/docs/src/reader.js
new file mode 100644
index 00000000..8f9f22c3
--- /dev/null
+++ b/docs/src/reader.js
@@ -0,0 +1,91 @@
+/**
+ * All reading related code here. This is so that we can separate the async code from sync code
+ * for testability
+ */
+require.paths.push(__dirname);
+var fs = require('fs'),
+ callback = require('callback');
+
+var NEW_LINE = /\n\r?/;
+
+function collect(callback){
+ findJsFiles('src', callback.waitMany(function(file) {
+ //console.log('reading', file, '...');
+ findNgDocInJsFile(file, callback.waitMany(function(doc, line) {
+ callback(doc, file, line);
+ }));
+ }));
+ findNgDocInDir('docs/', callback.waitMany(callback));
+ 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();
+ }));
+}
+
+function findNgDocInDir(directory, docNotify) {
+ fs.readdir(directory, docNotify.waitFor(function(err, files){
+ if (err) return this.error(err);
+ files.forEach(function(file){
+ //console.log('reading', directory + file, '...');
+ if (!file.match(/\.ngdoc$/)) return;
+ fs.readFile(directory + file, docNotify.waitFor(function(err, content){
+ if (err) return this.error(err);
+ docNotify(content.toString(), directory + file, 1);
+ }));
+ });
+ docNotify.done();
+ }));
+}
+
+function findNgDocInJsFile(file, callback) {
+ fs.readFile(file, callback.waitFor(function(err, content){
+ var lines = content.toString().split(NEW_LINE);
+ var text;
+ var startingLine ;
+ 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;
+ text = [];
+ startingLine = lineNumber;
+ }
+ // are we done?
+ if (inDoc && line.match(/\*\//)) {
+ text = text.join('\n');
+ text = text.replace(/^\n/, '');
+ if (text.match(/@ngdoc/)){
+ callback(text, startingLine);
+ }
+ doc = null;
+ inDoc = false;
+ }
+ // is the comment add text
+ if (inDoc){
+ text.push(line.replace(/^\s*\*\s?/, ''));
+ }
+ });
+ callback.done();
+ }));
+}
+
+
+
+exports.collect = collect; \ No newline at end of file
diff --git a/docs/src/templates/doc_widgets.css b/docs/src/templates/doc_widgets.css
new file mode 100644
index 00000000..8361f105
--- /dev/null
+++ b/docs/src/templates/doc_widgets.css
@@ -0,0 +1,35 @@
+@namespace doc url("http://docs.angularjs.org/");
+
+doc\:example {
+ display: none;
+}
+
+ul.doc-example {
+ list-style-type: none;
+ position: relative;
+ font-size: 14px;
+}
+
+ul.doc-example > li {
+ border: 2px solid gray;
+ border-radius: 5px;
+ -moz-border-radius: 5px;
+ -webkit-border-radius: 5px;
+ background-color: white;
+ margin-bottom: 20px;
+}
+
+ul.doc-example > li.doc-example-heading {
+ border: none;
+ border-radius: none;
+ margin-bottom: -10px;
+}
+
+li.doc-example-live {
+ padding: 10px;
+ font-size: 1.2em;
+}
+
+div.syntaxhighlighter {
+ padding-bottom: 1px !important; /* fix to remove unnecessary scrollbars http://is.gd/gSMgC */
+} \ No newline at end of file
diff --git a/docs/src/templates/doc_widgets.js b/docs/src/templates/doc_widgets.js
new file mode 100644
index 00000000..b119e326
--- /dev/null
+++ b/docs/src/templates/doc_widgets.js
@@ -0,0 +1,71 @@
+(function(){
+
+ var angularJsUrl;
+ var scripts = document.getElementsByTagName("script");
+ var filename = /(.*\/)angular([^\/]*)/;
+ for(var j = 0; j < scripts.length; j++) {
+ var src = scripts[j].src;
+ if (src && src.match(filename)) {
+ angularJsUrl = src;
+ }
+ }
+
+
+ var HTML_TEMPLATE =
+ '<!doctype html>\n' +
+ '<html xmlns:ng="http://angularjs.org">\n' +
+ ' <script type="text/javascript" ng:autobind\n' +
+ ' src="' + angularJsUrl + '"></script>\n' +
+ ' <body>\n' +
+ '_HTML_SOURCE_\n' +
+ ' </body>\n' +
+ '</html>';
+
+ angular.widget('doc:example', function(element){
+ this.descend(true); //compile the example code
+ element.hide();
+
+ var example = element.find('doc\\:source').eq(0),
+ exampleSrc = example.text(),
+ scenario = element.find('doc\\:scenario').eq(0);
+
+ var code = indent(exampleSrc);
+ var tabs = angular.element(
+ '<ul class="doc-example">' +
+ '<li class="doc-example-heading"><h3>Source</h3></li>' +
+ '<li class="doc-example-source" ng:non-bindable>' +
+ '<pre class="brush: js; html-script: true; highlight: [' +
+ code.hilite + ']; toolbar: false;"></pre></li>' +
+ '<li class="doc-example-heading"><h3>Live Preview</h3></li>' +
+ '<li class="doc-example-live">' + exampleSrc +'</li>' +
+ '<li class="doc-example-heading"><h3>Scenario Test</h3></li>' +
+ '<li class="doc-example-scenario"><pre class="brush: js">' + scenario.text() + '</pre></li>' +
+ '</ul>');
+
+ tabs.find('li.doc-example-source > pre').text(HTML_TEMPLATE.replace('_HTML_SOURCE_', code.html));
+
+ element.html('');
+ element.append(tabs);
+ element.show();
+
+ var script = (exampleSrc.match(/<script[^\>]*>([\s\S]*)<\/script>/) || [])[1] || '';
+ try {
+ eval(script);
+ } catch (e) {
+ alert(e);
+ }
+ });
+
+ function indent(text) {
+ var lines = text.split(/\n/);
+ var lineNo = [];
+ while (lines[0].match(/^\s*$/)) lines.shift();
+ while (lines[lines.length - 1].match(/^\s*$/)) lines.pop();
+ for ( var i = 0; i < lines.length; i++) {
+ lines[i] = ' ' + lines[i];
+ lineNo.push(6 + i);
+ }
+ return {html: lines.join('\n'), hilite: lineNo.join(',') };
+ };
+
+})(); \ No newline at end of file
diff --git a/docs/src/templates/docs-scenario.html b/docs/src/templates/docs-scenario.html
new file mode 100644
index 00000000..c75155c5
--- /dev/null
+++ b/docs/src/templates/docs-scenario.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html xmlns:ng="http://angularjs.org" wiki:ng="http://angularjs.org">
+<head>
+ <title>&lt;angular/&gt; Docs Scenario Runner</title>
+ <script type="text/javascript" src="../angular-scenario.js" ng:autobind></script>
+ <script type="text/javascript" src="docs-scenario.js"></script>
+</head>
+<body>
+</body>
+</html> \ No newline at end of file
diff --git a/docs/src/templates/docs-scenario.js b/docs/src/templates/docs-scenario.js
new file mode 100644
index 00000000..2ee8387b
--- /dev/null
+++ b/docs/src/templates/docs-scenario.js
@@ -0,0 +1,9 @@
+{{#pages}}
+describe('{{name}}', function(){
+ beforeEach(function(){
+ browser().navigateTo('index.html#!{{name}}');
+ });
+ // {{raw.file}}:{{raw.line}}
+{{{scenario}}}
+});
+{{/pages}}
diff --git a/docs/src/templates/docs.css b/docs/src/templates/docs.css
new file mode 100644
index 00000000..aaef7e58
--- /dev/null
+++ b/docs/src/templates/docs.css
@@ -0,0 +1,262 @@
+/* Common Style */
+
+body {
+ font-family: Arial, sans-serif;
+ font-size: 14px;
+ margin: 0;
+ padding: 0;
+}
+
+a {
+ color: blue;
+}
+
+
+/* Main Layout */
+
+#header {
+ height: 3.5em;
+}
+
+#sidebar,
+#main {
+ position: absolute;
+ top: 3.5em;
+ bottom: 0;
+ margin-top: 1px;
+ overflow-x: hidden;
+}
+
+#sidebar {
+ width: 13.8em;
+ padding: 0.8em 0em 1.5em 0.8em;
+}
+
+#main {
+ left: 14.6em;
+ right: 0;
+ padding: 1em;
+ overflow-y: scroll;
+}
+
+#api-list {
+ position: absolute;
+ top: 3em;
+ bottom: 1em;
+ overflow-y: scroll;
+ padding-right: 0.8em;
+}
+
+
+/* App Header */
+
+#header {
+ background-color: #F2C200;
+ border-bottom: 1px solid #957800;
+}
+
+#header h1 {
+ font-weight: normal;
+ font-size: 30px;
+ line-height: 30px;
+ margin: 0;
+ padding: 10px 10px;
+ height: 30px;
+}
+
+#header .angular {
+ font-family: "Courier New", monospace;
+ font-weight: bold;
+}
+
+#header h1 a {
+ color: black;
+ text-decoration: none;
+}
+
+#header h1 a:hover {
+ text-decoration: underline;
+}
+
+
+/* Main main Style */
+
+#main h1 {
+ font-family: monospace;
+ margin-top: 0;
+ padding-bottom: 5px;
+ border-bottom: 1px solid #CCC;
+}
+
+#main h2 {
+ margin-top: 1.8em;
+}
+
+#main h1 + h2 {
+ margin-top: 1.3em;
+}
+
+#main h3 {
+ margin-top: 1.5em;
+}
+
+#main ul.methods h3,
+#main ul.properties h3 {
+ margin-top: 1.5em;
+ font-family: "Courier New", monospace;
+}
+
+.main-title {
+ float: right;
+}
+
+
+/* Searchbox & Sidebar Style */
+
+#search-box, #sidebar {
+ border-right: 1px solid #DDD;
+}
+
+#sidebar {
+ background-color: #EEE;
+}
+
+#search-box {
+ width: 16em;
+ margin-bottom: 1em;
+}
+
+#sidebar a {
+ text-decoration: none;
+}
+
+#sidebar a:hover {
+ text-decoration: underline;
+}
+
+#sidebar ul {
+ list-style-type: none;
+ /*TODO(esprehn): Can we just reset globally and not break examples?*/
+ margin: 0;
+ padding: 0 0.8em 0 0;
+ width: 13em;
+}
+
+#sidebar ul li {
+}
+
+#sidebar ul li a {
+ display: block;
+ padding: 2px 2px 2px 4px;
+}
+
+#sidebar ul li.selected a {
+ background-color: #DDD;
+ border-radius: 5px;
+ -moz-border-radius: 5px;
+ border: 1px solid #CCC;
+ padding: 1px 1px 1px 3px;
+}
+
+#sidebar ul li.level-0 {
+ margin-left: 0em;
+ font-weight: bold;
+ font-size: 1.2em;
+}
+
+#sidebar ul li.level-1.level-angular {
+ font-family: monospace;
+ font-weight: normal;
+ font-size: 1em;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+#sidebar ul li.level-1 {
+ margin-left: 1em;
+ margin-top: 5px;
+ font-size: 1.1em;
+ font-weight: bold;
+}
+
+#sidebar ul li.level-2 {
+ margin-left: 2em;
+ font-family: monospace;
+}
+
+#sidebar ul li.level-3 {
+ margin-left: 3em;
+ font-family: monospace;
+}
+
+
+/* Warning and Info Banners */
+
+.deprecated {
+ border: 2px solid red;
+}
+
+.deprecated legend {
+ font-weight: bold;
+ color: red;
+}
+
+.workInProgress {
+ border: 2px solid orange;
+}
+
+.workInProgress legend {
+ font-weight: bold;
+ color: orange;
+}
+
+
+/* Feedback Link */
+
+#feedback {
+ float: right;
+ width: 10em;
+ text-align: right;
+}
+
+
+/* Live Example Style */
+
+.doc-example-live table td {
+ padding: 0 1.5em;
+}
+
+
+/* Scrollbars */
+
+::-webkit-scrollbar{
+ width:0.8em;
+ margin: 0.2em 0em;
+}
+
+::-webkit-scrollbar:hover{
+ background-color:#eee;
+}
+
+::-webkit-scrollbar-thumb{
+ min-height:0.8em;
+ min-width:0.8em;
+ -webkit-border-radius:0.5em;
+ background-color: #ddd;
+}
+
+::-webkit-scrollbar-thumb:hover{
+ background-color: #bbb;
+}
+
+::-webkit-scrollbar-thumb:active{
+ background-color:#888;
+}
+
+#sidebar::-webkit-scrollbar {
+ background-color:#eee;
+}
+
+#main::-webkit-scrollbar {
+ background-color:#fff;
+}
diff --git a/docs/src/templates/docs.js b/docs/src/templates/docs.js
new file mode 100644
index 00000000..6bf86ed3
--- /dev/null
+++ b/docs/src/templates/docs.js
@@ -0,0 +1,47 @@
+DocsController.$inject = ['$location', '$browser', '$window'];
+function DocsController($location, $browser, $window) {
+ this.pages = NG_PAGES;
+ window.$root = this.$root;
+
+ this.getUrl = function(page){
+ return '#!' + page.name;
+ };
+
+ this.getCurrentPartial = function(){
+ return './' + this.getTitle() + '.html';
+ };
+
+ this.getTitle = function(){
+ var hashPath = $location.hashPath || '!angular';
+ if (hashPath.match(/^!angular/)) {
+ this.partialTitle = hashPath.substring(1);
+ }
+ return this.partialTitle;
+ };
+
+ this.getClass = function(page) {
+ var depth = page.name.split(/\./).length - 1,
+ cssClass = 'level-' + depth + (page.name == this.getTitle() ? ' selected' : '');
+
+ if (depth == 1 && page.type !== 'overview') cssClass += ' level-angular';
+
+ return cssClass;
+ };
+
+ this.afterPartialLoaded = function() {
+ SyntaxHighlighter.highlight();
+ };
+
+ this.getFeedbackUrl = function() {
+ return "mailto:angular@googlegroups.com?" +
+ "subject=" + escape("Feedback on " + $location.href) + "&" +
+ "body=" + escape("Hi there,\n\nI read " + $location.href + " and wanted to ask ....");
+ };
+
+}
+
+angular.filter('short', function(name){
+ return (name||'').split(/\./).pop();
+});
+
+SyntaxHighlighter['defaults'].toolbar = false;
diff --git a/docs/src/templates/index.html b/docs/src/templates/index.html
new file mode 100644
index 00000000..64b73bfc
--- /dev/null
+++ b/docs/src/templates/index.html
@@ -0,0 +1,45 @@
+<!doctype html>
+<html xmlns:ng="http://angularjs.org/"
+ xmlns:doc="http://docs.angularjs.org/"
+ ng:controller="DocsController">
+<head>
+ <title ng:bind-template="&lt;angular/&gt;: {{getTitle()}}">&lt;angular/&gt;</title>
+
+ <meta name="fragment" content="!">
+
+ <link rel="stylesheet" href="doc_widgets.css" type="text/css" />
+ <link rel="stylesheet" href="docs.css" type="text/css"/>
+ <link rel="stylesheet" href="http://alexgorbatchev.com/pub/sh/current/styles/shCore.css" type="text/css"/>
+ <link rel="stylesheet" href="http://alexgorbatchev.com/pub/sh/current/styles/shThemeDefault.css" type="text/css"/>
+
+ <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script>
+ <script src="http://alexgorbatchev.com/pub/sh/current/scripts/shCore.js"></script>
+ <script src="http://alexgorbatchev.com/pub/sh/current/scripts/shBrushJScript.js"></script>
+ <script src="http://alexgorbatchev.com/pub/sh/current/scripts/shBrushXml.js"></script>
+
+ <script src="../angular.min.js" ng:autobind></script>
+ <script src="docs.js"></script>
+ <script src="doc_widgets.js"></script>
+ <script src="docs-keywords.js"></script>
+</head>
+<body style="display:none;" ng:show="true">
+ <div id="header">
+ <h1>
+ <span class="main-title">{{getTitle()}}</span>
+ <a href="#"><span class="angular">&lt;angular/&gt;</span> Docs</a>
+ </h1>
+ </div>
+ <div id="sidebar">
+ <input type="text" name="search" id="search-box" placeholder="search the docs"/>
+ <ul id="api-list">
+ <li ng:repeat="page in pages.$filter(search)" ng:class="getClass(page)">
+ <a href="{{getUrl(page)}}" ng:click="">{{page.name | short}}</a>
+ </li>
+ </ul>
+ </div>
+ <div id="main">
+ <a id="feedback" ng:href="{{getFeedbackUrl()}}">Report an Issue or Ask a Question</a>
+ <ng:include src="getCurrentPartial()" onload="afterPartialLoaded()"></ng:include>
+ </div>
+</body>
+</html>
diff --git a/docs/src/writer.js b/docs/src/writer.js
new file mode 100644
index 00000000..eb1b190f
--- /dev/null
+++ b/docs/src/writer.js
@@ -0,0 +1,61 @@
+/**
+ * All writing related code here. This is so that we can separate the async code from sync code
+ * for testability
+ */
+require.paths.push(__dirname);
+var fs = require('fs');
+var OUTPUT_DIR = "build/docs/";
+
+function output(docs, content, callback){
+ callback();
+}
+
+exports.output = function(file, content, callback){
+ //console.log('writing', OUTPUT_DIR + file, '...');
+ fs.writeFile(
+ OUTPUT_DIR + file,
+ exports.toString(content),
+ callback);
+};
+
+
+exports.toString = function toString(obj){
+ switch (typeof obj) {
+ case 'string':
+ return obj;
+ case 'object':
+ if (obj instanceof Array) {
+ obj.forEach(function (value, key){
+ obj[key] = toString(value);
+ });
+ return obj.join('');
+ } else {
+ return JSON.stringify(obj);
+ }
+ }
+ return obj;
+};
+
+exports.makeDir = function (path, callback) {
+ var parts = path.split(/\//);
+ path = '.';
+ (function next(){
+ if (parts.length) {
+ path += '/' + parts.shift();
+ fs.mkdir(path, 0777, next);
+ } else {
+ callback();
+ }
+ })();
+};
+
+exports.copy = function(filename, callback){
+ //console.log('writing', OUTPUT_DIR + filename, '...');
+ fs.readFile('docs/src/templates/' + filename, function(err, content){
+ if (err) return callback.error(err);
+ fs.writeFile(
+ OUTPUT_DIR + filename,
+ content,
+ callback);
+ });
+};