From 4f22d6866c052fb5b770ce4f377cecacacd9e6d8 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Thu, 23 Dec 2010 00:44:27 +0100 Subject: complete rewrite of documentation generation - romeved mustache.js - unified templates - improved testability of the code --- docs/src/callback.js | 69 ++++ docs/src/dom.js | 123 +++++++ docs/src/gen-docs.js | 42 +++ docs/src/ignore.words | 0 docs/src/ngdoc.js | 614 ++++++++++++++++++++++++++++++++++ docs/src/reader.js | 91 +++++ docs/src/templates/doc_widgets.css | 35 ++ docs/src/templates/doc_widgets.js | 71 ++++ docs/src/templates/docs-scenario.html | 10 + docs/src/templates/docs-scenario.js | 9 + docs/src/templates/docs.css | 262 +++++++++++++++ docs/src/templates/docs.js | 47 +++ docs/src/templates/index.html | 45 +++ docs/src/writer.js | 61 ++++ 14 files changed, 1479 insertions(+) create mode 100644 docs/src/callback.js create mode 100644 docs/src/dom.js create mode 100644 docs/src/gen-docs.js create mode 100644 docs/src/ignore.words create mode 100644 docs/src/ngdoc.js create mode 100644 docs/src/reader.js create mode 100644 docs/src/templates/doc_widgets.css create mode 100644 docs/src/templates/doc_widgets.js create mode 100644 docs/src/templates/docs-scenario.html create mode 100644 docs/src/templates/docs-scenario.js create mode 100644 docs/src/templates/docs.css create mode 100644 docs/src/templates/docs.js create mode 100644 docs/src/templates/index.html create mode 100644 docs/src/writer.js (limited to 'docs/src') 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, '&').replace(//g, '>')); + } 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(''); + 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('
  • '); + dom.text(fn ? fn(item) : item); + dom.out.push('
  • \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 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.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]; + }; +} \ 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 = + '\n' + + '\n' + + ' \n' + + ' \n' + + '_HTML_SOURCE_\n' + + ' \n' + + ''; + + 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( + '

      ' + + '
    • Source

    • ' + + '
    • ' + + '
    • ' + + '
    • Live Preview

    • ' + + '
    • ' + exampleSrc +'
    • ' + + '
    • Scenario Test

    • ' + + '
    • ' + scenario.text() + '
    • ' + + '
    '); + + 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(/]*>([\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 @@ + + + + <angular/> Docs Scenario Runner + + + + + + \ 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 @@ + + + + <angular/> + + + + + + + + + + + + + + + + + + + + + + + + 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); + }); +}; -- cgit v1.2.3