diff --git a/docs/directive.template b/docs/directive.template deleted file mode 100644 index 07e38ea7..00000000 --- a/docs/directive.template +++ /dev/null @@ -1,58 +0,0 @@ -

{{name}}

- -{{#workInProgress}} -
- Work In Progress - This page is currently being revised. It might be incomplete or contain inaccuracies. - {{{workInProgress.description}}} -
-{{/workInProgress}} - -{{#deprecated}} -
- Deprecated API - {{deprecated}} -
-{{/deprecated}} - -

Description

-{{{description}}} - -

Usage

-

In HTML Template Binding

- -
-<{{element}} {{shortName}}="{{paramFirst.name}}">
-  ...
-</{{element}}>
-  
-
- -

Parameters

- -{{{paramDescription}}} - -{{#css}} -

CSS

-{{{css}}} -{{/css}} - -{{#example}} -

Example

-{{{exampleDescription}}} - - -{{/example}} - {{{example}}} -{{#example}} - - {{{scenario}}} - -{{/example}} diff --git a/docs/docs-data.js b/docs/docs-data.js deleted file mode 100644 index 62feb2e9..00000000 --- a/docs/docs-data.js +++ /dev/null @@ -1 +0,0 @@ -NG_PAGES={{{JSON}}}; \ No newline at end of file diff --git a/docs/filter.template b/docs/filter.template deleted file mode 100644 index 516957d0..00000000 --- a/docs/filter.template +++ /dev/null @@ -1,65 +0,0 @@ -

{{name}}

- -{{#workInProgress}} -
- Work In Progress - This page is currently being revised. It might be incomplete or contain inaccuracies. - {{{workInProgress.description}}} -
-{{/workInProgress}} - -{{#deprecated}} -
- Deprecated API - {{deprecated}} -
-{{/deprecated}} - -

Description

-{{{description}}} - -

Usage

-

In HTML Template Binding

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

In JavaScript

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

Parameters

- - -{{#returns}} -

Returns

-{{{{type}}}} {{{description}}} -{{/returns}} - -{{#css}} -

CSS

-{{{css}}} -{{/css}} - -{{#example}} -

Example

-{{{exampleDescription}}} - - -{{/example}} - {{{example}}} -{{#example}} - - {{{scenario}}} - -{{/example}} diff --git a/docs/formatter.template b/docs/formatter.template deleted file mode 100644 index 9ccdc811..00000000 --- a/docs/formatter.template +++ /dev/null @@ -1,53 +0,0 @@ -

{{name}}

- -{{#workInProgress}} -
- Work In Progress - This page is currently being revised. It might be incomplete or contain inaccuracies. - {{{workInProgress.description}}} -
-{{/workInProgress}} - -{{#deprecated}} -
- Deprecated API - {{deprecated}} -
-{{/deprecated}} - -

Description

-{{{description}}} - -

Usage

-

In HTML Template Binding

- - <input type="text" ng:format="{{shortName}}"> - -

In JavaScript

- -var userInputString = angular.formatter.{{shortName}}.format(modelValue);
-var modelValue = angular.formatter.{{shortName}}.parse(userInputString); -
- -{{#returns}} -

Returns

-{{{{type}}}} {{{description}}} -{{/returns}} - -{{#css}} -

CSS

-{{{css}}} -{{/css}} - -{{#example}} -

Example

-{{{exampleDescription}}} - - -{{/example}} - {{{example}}} -{{#example}} - - {{{scenario}}} - -{{/example}} diff --git a/docs/function.template b/docs/function.template deleted file mode 100644 index 8765277b..00000000 --- a/docs/function.template +++ /dev/null @@ -1,52 +0,0 @@ -

{{name}}

- -{{#workInProgress}} -
- Work In Progress - This page is currently being revised. It might be incomplete or contain inaccuracies. - {{{workInProgress.description}}} -
-{{/workInProgress}} - -{{#deprecated}} -
- Deprecated API - {{deprecated}} -
-{{/deprecated}} - -

Description

-{{{description}}} - -

Usage

- -{{name}}({{paramFirst.name}}{{#paramRest}}, {{name}}{{/paramRest}} ); - - -

Parameters

- - -{{#returns}} -

Returns

-{{{{type}}}} {{{description}}} -{{/returns}} - -{{#example}} -

Example

-{{{exampleDescription}}} - - -{{/example}} - {{{example}}} -{{#example}} - - {{{scenario}}} - -{{/example}} diff --git a/docs/overview.template b/docs/overview.template deleted file mode 100644 index 9902a16f..00000000 --- a/docs/overview.template +++ /dev/null @@ -1,31 +0,0 @@ -

{{name}}

- -{{#workInProgress}} -
- Work In Progress - This page is currently being revised. It might be incomplete or contain inaccuracies. - {{{workInProgress.description}}} -
-{{/workInProgress}} - -{{#deprecated}} -
- Deprecated API - {{deprecated}} -
-{{/deprecated}} - -{{{description}}} - -{{#example}} -

Example

-{{{exampleDescription}}} - - -{{/example}} - {{{example}}} -{{#example}} - - {{{scenario}}} - -{{/example}} diff --git a/docs/spec/collectSpec.js b/docs/spec/collectSpec.js deleted file mode 100644 index f0783bdf..00000000 --- a/docs/spec/collectSpec.js +++ /dev/null @@ -1,288 +0,0 @@ -console.log(__dirname); -require.paths.push(__dirname + "/../"); -require.paths.push(__dirname + "/../../"); -var fs = require('fs'); -var Script = process.binding('evals').Script; -var collect = load('docs/collect.js'); - -describe('collect', function(){ - describe('markdown', function(){ - it('should replace angular in markdown', function(){ - expect(collect.markdown('')). - toEqual('

<angular/>

'); - }); - - it('should not replace anything in
', function(){
-      expect(collect.markdown('bah x\n
\nangular.k\n
\n asdf x')). - toEqual( - '

bah x

' + - '
\nangular.k\n
' + - '

asdf x

'); - }); - - it('should replace text between two
 tags', function() {
-      expect(collect.markdown('
x
# One
b
')). - toEqual('
x

One

b
'); - }); - }); - - describe('processNgDoc', function() { - var processNgDoc = collect.processNgDoc, - documentation; - - beforeEach(function() { - documentation = { - pages: [], - byName: {} - }; - }); - - it('should store references to docs by name', function() { - var doc = {ngdoc: 'section', name: 'fake', raw: {text:''}}; - processNgDoc(documentation, doc); - expect(documentation.byName.fake).toBe(doc); - }); - - it('should connect doc to owner (specified by @methodOf)', function() { - var parentDoc = {ngdoc: 'section', name: 'parent', raw: {text:''}}; - var doc = {ngdoc: 'section', name: 'child', methodOf: 'parent', raw: {text:''}}; - processNgDoc(documentation, parentDoc); - processNgDoc(documentation, doc); - expect(documentation.byName.parent.method).toBeDefined(); - expect(documentation.byName.parent.method[0]).toBe(doc); - }); - - it('should not add doc to sections if @memberOf specified', function() { - var parentDoc = {ngdoc: 'parent', name: 'parent', raw: {text:''}}; - var doc = {ngdoc: 'child', name: 'child', methodOf: 'parent', raw: {text:''}}; - processNgDoc(documentation, parentDoc); - processNgDoc(documentation, doc); - expect(documentation.pages.child).not.toBeDefined(); - }); - - it('should throw exception if owner does not exist', function() { - expect(function() { - processNgDoc(documentation, {ngdoc: 'section', methodOf: 'not.exist', raw: {text:''}}); - }).toThrow('Owner "not.exist" is not defined.'); - }); - - it('should ignore non-ng docs', function() { - var doc = {name: 'anything'}; - expect(function() { - processNgDoc(documentation, doc); - }).not.toThrow(); - expect(documentation.pages).not.toContain(doc); - }); - }); - - describe('TAG', function(){ - var TAG = collect.TAG; - var doc; - beforeEach(function(){ - doc = {}; - }); - - describe('@param', function(){ - it('should parse with no default', function(){ - TAG.param(doc, 'param', - '{(number|string)} number Number \n to format.'); - expect(doc.param).toEqual([{ - type : '(number|string)', - name : 'number', - optional: false, - 'default' : undefined, - description : 'Number \n to format.' }]); - }); - it('should parse with default and optional', function(){ - TAG.param(doc, 'param', - '{(number|string)=} [fractionSize=2] desc'); - expect(doc.param).toEqual([{ - type : '(number|string)', - name : 'fractionSize', - optional: true, - 'default' : '2', - description : 'desc' }]); - }); - }); - - describe('@requires', function() { - it('should parse more @requires tag into array', function() { - TAG.requires(doc, 'requires', '$service'); - TAG.requires(doc, 'requires', '$another'); - - expect(doc.requires).toEqual([ - {name: '$service'}, - {name: '$another'} - ]); - }); - }); - - describe('@property', function() { - it('should parse @property tags into array', function() { - TAG.property(doc, 'property', '{type} name1 desc'); - TAG.property(doc, 'property', '{type} name2 desc'); - expect(doc.property.length).toEqual(2); - }); - - it('should parse @property with only name', function() { - TAG.property(doc, 'property', 'fake'); - expect(doc.property[0].name).toEqual('fake'); - }); - - it('should parse @property with optional type', function() { - TAG.property(doc, 'property', '{string} name'); - expect(doc.property[0].name).toEqual('name'); - expect(doc.property[0].type).toEqual('string'); - }); - - it('should parse @property with optional description', function() { - TAG.property(doc, 'property', 'name desc rip tion'); - expect(doc.property[0].name).toEqual('name'); - expect(doc.property[0].description).toEqual('desc rip tion'); - }); - - it('should parse @property with type and description both', function() { - TAG.property(doc, 'property', '{bool} name desc rip tion'); - expect(doc.property[0].name).toEqual('name'); - expect(doc.property[0].type).toEqual('bool'); - expect(doc.property[0].description).toEqual('desc rip tion'); - }); - - /** - * If property description is undefined, this variable is not set in the template, - * so the whole @description tag is used instead - */ - it('should set undefined description to "false"', function() { - TAG.property(doc, 'property', 'name'); - expect(doc.property[0].description).toBe(false); - }); - }); - - describe('@methodOf', function() { - it('should parse @methodOf tag', function() { - expect(function() { - TAG.methodOf(doc, 'methodOf', 'parentName'); - }).not.toThrow(); - expect(doc.methodOf).toEqual('parentName'); - }); - }); - - describe('@returns', function() { - it('should not parse @returns without type', function() { - expect(function() {TAG.returns(doc, 'returns', 'lala');}) - .toThrow(); - }); - - it('should parse @returns with type and description', function() { - TAG.returns(doc, 'returns', '{string} descrip tion'); - expect(doc.returns).toEqual({type: 'string', description: 'descrip tion'}); - }); - - it('should transform description of @returns with markdown', function() { - TAG.returns(doc, 'returns', '{string} descrip *tion*'); - expect(doc.returns).toEqual({type: 'string', description: 'descrip tion'}); - }); - - it('should support multiline content', function() { - TAG.returns(doc, 'returns', '{string} description\n new line\n another line'); - expect(doc.returns). - toEqual({type: 'string', description: 'description\n new line\n another line'}); - }); - }); - - describe('@description', function(){ - it('should support pre blocks', function(){ - TAG.description(doc, 'description', '
abc
'); - expect(doc.description). - toBe('
abc
'); - }); - - it('should support multiple pre blocks', function() { - TAG.description(doc, 'description', 'foo \n
abc
\n#bah\nfoo \n
cba
'); - expect(doc.description). - toBe('

foo

' + - '
abc
' + - '

bah

\n\n' + - '

foo

' + - '
cba
'); - - }); - - it('should support nested @link annotations with or without description', function() { - TAG.description(doc, 'description', - 'foo {@link angular.foo}\n\n da {@link angular.foo bar foo bar } \n\n' + - 'dad{@link angular.foo}\n\n' + - '{@link angular.directive.ng:foo ng:foo}'); - expect(doc.description). - toBe('

foo angular.foo

\n\n' + - '

da bar foo bar

\n\n' + - '

dadangular.foo

\n\n' + - '

ng:foo

'); - }); - - it('should increment all headings by one', function() { - TAG.description(doc, 'description', '# foo\nabc'); - expect(doc.description). - toBe('

foo

\n\n

abc

'); - }); - }); - - describe('@example', function(){ - it('should not remove {{}}', function(){ - TAG.example(doc, 'example', 'text {{ abc }}'); - expect(doc.example).toEqual('text {{ abc }}'); - }); - }); - - describe('@deprecated', function() { - it('should parse @deprecated', function() { - TAG.deprecated(doc, 'deprecated', 'Replaced with foo.'); - expect(doc.deprecated).toBe('Replaced with foo.'); - }) - }); - - describe('@workInProgress', function() { - it('should parse @workInProgress without a description and default to true', function() { - TAG.workInProgress(doc, 'workInProgress', ''); - expect(doc.workInProgress).toEqual({description: ''}); - }); - - it('should parse @workInProgress with a description', function() { - TAG.workInProgress(doc, 'workInProgress', 'my description'); - expect(doc.workInProgress).toEqual({description: '

my description

'}); - }); - }); - - }); - - describe('trim', function(){ - var trim = collect.trim; - it('should remove leading/trailing space', function(){ - expect(trim(' \nabc\n ')).toEqual('abc'); - }); - - it('should remove leading space on every line', function(){ - expect(trim('\n 1\n 2\n 3\n')).toEqual('1\n 2\n 3'); - }); - }); - - describe('keywords', function(){ - var keywords = collect.keywords; - it('should collect keywords', function(){ - expect(keywords('\nHello: World! @ignore.')).toEqual('hello world'); - expect(keywords('The `ng:class-odd` and ')).toEqual('and ng:class-odd the'); - }); - }); - -}); - -function load(path){ - var sandbox = { - require: require, - console: console, - __dirname: __dirname, - testmode: true - }; - Script.runInNewContext(fs.readFileSync(path), sandbox, path); - return sandbox; -} diff --git a/docs/spec/ngdocSpec.js b/docs/spec/ngdocSpec.js new file mode 100644 index 00000000..63be610b --- /dev/null +++ b/docs/spec/ngdocSpec.js @@ -0,0 +1,257 @@ +var ngdoc = require('ngdoc.js'); + +describe('ngdoc', function(){ + var Doc = ngdoc.Doc; + describe('Doc', function(){ + describe('metadata', function(){ + + it('should find keywords', function(){ + expect(new Doc('\nHello: World! @ignore.').keywords()).toEqual('hello world'); + expect(new Doc('The `ng:class-odd` and').keywords()).toEqual('and ng:class-odd the'); + }); + }); + + describe('parse', function(){ + it('should convert @names into properties', function(){ + var doc = new Doc('\n@name name\n@desc\ndesc\ndesc2\n@dep\n'); + doc.parse(); + expect(doc.name).toEqual('name'); + expect(doc.desc).toEqual('desc\ndesc2'); + expect(doc.dep).toEqual(''); + }); + + it('should parse parameters', function(){ + var doc = new Doc( + '@param {*} a short\n' + + '@param {Type} b med\n' + + '@param {Class=} [c=2] long\nline'); + doc.parse(); + expect(doc.param).toEqual([ + {name:'a', description:'short', type:'*', optional:false, 'default':undefined}, + {name:'b', description:'med', type:'Type', optional:false, 'default':undefined}, + {name:'c', description:'long\nline', type:'Class', optional:true, 'default':'2'} + ]); + }); + + it('should parse return', function(){ + var doc = new Doc('@returns {Type} text *bold*.'); + doc.parse(); + expect(doc.returns).toEqual({ + type: 'Type', + description: 'text bold.' + }); + }); + }); + + + }); + + describe('markdown', function(){ + var markdown = ngdoc.markdown; + + it('should replace angular in markdown', function(){ + expect(markdown('')). + toEqual('

<angular/>

'); + }); + + it('should not replace anything in
', function(){
+      expect(markdown('bah x\n
\nangular.k\n
\n asdf x')). + toEqual( + '

bah x

' + + '
\n' + 
+            'angular.k\n' + 
+            '
' + + '

asdf x

'); + }); + + it('should replace text between two
 tags', function() {
+      expect(markdown('
x
# One
b
')). + toMatch('

One

tion'}); + }); + + it('should support multiline content', function() { + var doc = new Doc("@returns {string} description\n new line\n another line"); + doc.parse(); + expect(doc.returns). + toEqual({type: 'string', description: 'description\n new line\n another line'}); + }); + }); + + describe('@description', function(){ + it('should support pre blocks', function(){ + var doc = new Doc("@description
abc
"); + doc.parse(); + expect(doc.description). + toBe('
abc
'); + }); + + it('should support multiple pre blocks', function() { + var doc = new Doc("@description foo \n
abc
\n#bah\nfoo \n
cba
"); + doc.parse(); + expect(doc.description). + toBe('

foo

' + + '
abc
' + + '

bah

\n\n' + + '

foo

' + + '
cba
'); + + }); + + it('should support nested @link annotations with or without description', function() { + var doc = new Doc("@description " + + 'foo {@link angular.foo}\n\n da {@link angular.foo bar foo bar } \n\n' + + 'dad{@link angular.foo}\n\n' + + '{@link angular.directive.ng:foo ng:foo}'); + doc.parse(); + expect(doc.description). + toBe('

foo angular.foo

\n\n' + + '

da bar foo bar

\n\n' + + '

dadangular.foo

\n\n' + + '

ng:foo

'); + }); + + it('should increment all headings by two', function() { + var doc = new Doc('@description # foo\nabc\n## bar \n xyz'); + doc.parse(); + expect(doc.description). + toBe('

foo

\n\n

abc

\n\n

bar

\n\n

xyz

'); + }); + }); + + describe('@example', function(){ + it('should not remove {{}}', function(){ + var doc = new Doc('@example text {{ abc }}'); + doc.parse(); + expect(doc.example).toEqual('text {{ abc }}'); + }); + }); + + describe('@deprecated', function() { + it('should parse @deprecated', function() { + var doc = new Doc('@deprecated Replaced with foo.'); + doc.parse(); + expect(doc.deprecated).toBe('Replaced with foo.'); + }); + }); + }); + +}); diff --git a/docs/spec/specs.js b/docs/spec/specs.js new file mode 100644 index 00000000..a6ba17a9 --- /dev/null +++ b/docs/spec/specs.js @@ -0,0 +1,39 @@ +if (global.jasmine) return; + +require.paths.push(__dirname + "/../../lib"); +require.paths.push(__dirname + '/../src'); +var jasmine = require('jasmine-1.0.1'); +var sys = require('util'); + +for(var key in jasmine) { + global[key] = jasmine[key]; +} + +//Patch Jasmine for proper stack traces +jasmine.Spec.prototype.fail = function (e) { + var expectationResult = new jasmine.ExpectationResult({ + passed: false, + message: e ? jasmine.util.formatException(e) : 'Exception' + }); + // PATCH + if (e) { + expectationResult.trace = e; + } + this.results_.addResult(expectationResult); +}; + + + +var isVerbose = false; +var showColors = true; +process.argv.forEach(function(arg){ + switch(arg) { + case '--color': showColors = true; break; + case '--noColor': showColors = false; break; + case '--verbose': isVerbose = true; break; + } +}); + +jasmine.executeSpecsInFolder(__dirname, function(runner, log){ + process.exit(runner.results().failedCount); +}, isVerbose, showColors); diff --git a/docs/spec/writerSpec.js b/docs/spec/writerSpec.js new file mode 100644 index 00000000..1a722ca6 --- /dev/null +++ b/docs/spec/writerSpec.js @@ -0,0 +1,18 @@ +var writer = require('writer.js'); +describe('writer', function(){ + describe('toString', function(){ + var toString = writer.toString; + + it('should merge string', function(){ + expect(toString('abc')).toEqual('abc'); + }); + + it('should merge obj', function(){ + expect(toString({a:1})).toEqual('{"a":1}'); + }); + + it('should merge array', function(){ + expect(toString(['abc',{}])).toEqual('abc{}'); + }); + }); +}); \ No newline at end of file diff --git a/docs/specs.js b/docs/specs.js deleted file mode 100644 index d564b045..00000000 --- a/docs/specs.js +++ /dev/null @@ -1,21 +0,0 @@ -require.paths.push("./lib"); -var jasmine = require('jasmine-1.0.1'); -var sys = require('util'); - -for(var key in jasmine) { - global[key] = jasmine[key]; -} - -var isVerbose = false; -var showColors = true; -process.argv.forEach(function(arg){ - switch(arg) { - case '--color': showColors = true; break; - case '--noColor': showColors = false; break; - case '--verbose': isVerbose = true; break; - } -}); - -jasmine.executeSpecsInFolder(__dirname + '/spec', function(runner, log){ - process.exit(runner.results().failedCount); -}, isVerbose, showColors); \ No newline at end of file diff --git a/docs/callback.js b/docs/src/callback.js similarity index 94% rename from docs/callback.js rename to docs/src/callback.js index 0d0669d1..aaf69cde 100644 --- a/docs/callback.js +++ b/docs/src/callback.js @@ -2,7 +2,10 @@ function noop(){} function chain(delegateFn, explicitDone){ var onDoneFn = noop; - var onErrorFn = 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'); 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/doc_widgets.css b/docs/src/templates/doc_widgets.css similarity index 100% rename from docs/doc_widgets.css rename to docs/src/templates/doc_widgets.css diff --git a/docs/doc_widgets.js b/docs/src/templates/doc_widgets.js similarity index 100% rename from docs/doc_widgets.js rename to docs/src/templates/doc_widgets.js diff --git a/docs/docs-scenario.html b/docs/src/templates/docs-scenario.html similarity index 100% rename from docs/docs-scenario.html rename to docs/src/templates/docs-scenario.html diff --git a/docs/docs-scenario.js b/docs/src/templates/docs-scenario.js similarity index 100% rename from docs/docs-scenario.js rename to docs/src/templates/docs-scenario.js diff --git a/docs/docs.css b/docs/src/templates/docs.css similarity index 96% rename from docs/docs.css rename to docs/src/templates/docs.css index 21e9d6ed..aaef7e58 100644 --- a/docs/docs.css +++ b/docs/src/templates/docs.css @@ -100,6 +100,12 @@ a { 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; } diff --git a/docs/docs.js b/docs/src/templates/docs.js similarity index 100% rename from docs/docs.js rename to docs/src/templates/docs.js diff --git a/docs/index.html b/docs/src/templates/index.html similarity index 97% rename from docs/index.html rename to docs/src/templates/index.html index 1722e98a..64b73bfc 100644 --- a/docs/index.html +++ b/docs/src/templates/index.html @@ -20,7 +20,7 @@ - +