diff options
| author | Misko Hevery | 2010-12-23 00:44:27 +0100 |
|---|---|---|
| committer | Misko Hevery | 2011-01-10 11:50:11 -0800 |
| commit | 4f22d6866c052fb5b770ce4f377cecacacd9e6d8 (patch) | |
| tree | 6bdb1c5eb70cfd7e6bcf143c121c53025a0489a4 /docs | |
| parent | aab3df7aeaf79908e8b6212288b283adb42b1ce6 (diff) | |
| download | angular.js-4f22d6866c052fb5b770ce4f377cecacacd9e6d8.tar.bz2 | |
complete rewrite of documentation generation
- romeved mustache.js
- unified templates
- improved testability of the code
Diffstat (limited to 'docs')
27 files changed, 1256 insertions, 698 deletions
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 @@ -<h1>{{name}}</h1> - -{{#workInProgress}} -<fieldset class="workInProgress"> - <legend>Work In Progress</legend> - This page is currently being revised. It might be incomplete or contain inaccuracies. - {{{workInProgress.description}}} -</fieldset> -{{/workInProgress}} - -{{#deprecated}} -<fieldset class="deprecated"> - <legend>Deprecated API</legend> - {{deprecated}} -</fieldset> -{{/deprecated}} - -<h2>Description</h2> -{{{description}}} - -<h2>Usage</h2> -<h3>In HTML Template Binding</h3> -<tt> - <pre> -<{{element}} {{shortName}}="{{paramFirst.name}}"> - ... -</{{element}}> - </pre> -</tt> - -<h3>Parameters</h3> -<ul> - {{#param}} - <li><tt>{{name}}</tt> – - <tt>{{{#type}}{{type}}{{/type}}{{^type}}*{{/type}}{{#optional}}={{/optional}}}</tt> - <tt>{{#default}}[{{default}}]{{/default}}</tt> - – {{{description}}}</li> - {{/param}} -</ul> -{{{paramDescription}}} - -{{#css}} -<h3>CSS</h3> -{{{css}}} -{{/css}} - -{{#example}} -<h2>Example</h2> -{{{exampleDescription}}} -<doc:example> - <doc:source> -{{/example}} - {{{example}}} -{{#example}} - </doc:source> - <doc:scenario>{{{scenario}}}</doc:scenario> -</doc:example> -{{/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 @@ -<h1>{{name}}</h1> - -{{#workInProgress}} -<fieldset class="workInProgress"> - <legend>Work In Progress</legend> - This page is currently being revised. It might be incomplete or contain inaccuracies. - {{{workInProgress.description}}} -</fieldset> -{{/workInProgress}} - -{{#deprecated}} -<fieldset class="deprecated"> - <legend>Deprecated API</legend> - {{deprecated}} -</fieldset> -{{/deprecated}} - -<h2>Description</h2> -{{{description}}} - -<h2>Usage</h2> -<h3>In HTML Template Binding</h3> -<tt> - <span>{{</span> - {{paramFirst.name}}_expression - | {{shortName}}{{#paramRest}}{{^default}}:{{name}}{{/default}}{{#default}}<i>[:{{name}}={{default}}]</i>{{/default}}{{/paramRest}} - <span> }}</span> -</tt> -<h3>In JavaScript</h3> -<tt ng:non-bindable> -angular.filter.{{shortName}}({{paramFirst.name}}{{#paramRest}}, {{name}}{{/paramRest}} ); -</tt> - -<h3>Parameters</h3> -<ul> - {{#param}} - <li><tt>{{name}}</tt> – - <tt>{{{#type}}{{type}}{{/type}}{{^type}}*{{/type}}{{#optional}}={{/optional}}}</tt> - <tt>{{#default}}[{{default}}]{{/default}}</tt> - – {{{description}}}</li> - {{/param}} -</ul> - -{{#returns}} -<h3>Returns</h3> -<tt>{{{{type}}}}</tt> {{{description}}} -{{/returns}} - -{{#css}} -<h3>CSS</h3> -{{{css}}} -{{/css}} - -{{#example}} -<h2>Example</h2> -{{{exampleDescription}}} -<doc:example> - <doc:source> -{{/example}} - {{{example}}} -{{#example}} - </doc:source> - <doc:scenario>{{{scenario}}}</doc:scenario> -</doc:example> -{{/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 @@ -<h1>{{name}}</h1> - -{{#workInProgress}} -<fieldset class="workInProgress"> - <legend>Work In Progress</legend> - This page is currently being revised. It might be incomplete or contain inaccuracies. - {{{workInProgress.description}}} -</fieldset> -{{/workInProgress}} - -{{#deprecated}} -<fieldset class="deprecated"> - <legend>Deprecated API</legend> - {{deprecated}} -</fieldset> -{{/deprecated}} - -<h2>Description</h2> -{{{description}}} - -<h2>Usage</h2> -<h3>In HTML Template Binding</h3> -<tt> - <input type="text" ng:format="{{shortName}}"> -</tt> -<h3>In JavaScript</h3> -<tt ng:non-bindable> -var userInputString = angular.formatter.{{shortName}}.format(modelValue);<br/> -var modelValue = angular.formatter.{{shortName}}.parse(userInputString); -</tt> - -{{#returns}} -<h3>Returns</h3> -<tt>{{{{type}}}}</tt> {{{description}}} -{{/returns}} - -{{#css}} -<h3>CSS</h3> -{{{css}}} -{{/css}} - -{{#example}} -<h2>Example</h2> -{{{exampleDescription}}} -<doc:example> - <doc:source> -{{/example}} - {{{example}}} -{{#example}} - </doc:source> - <doc:scenario>{{{scenario}}}</doc:scenario> -</doc:example> -{{/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 @@ -<h1>{{name}}</h1> - -{{#workInProgress}} -<fieldset class="workInProgress"> - <legend>Work In Progress</legend> - This page is currently being revised. It might be incomplete or contain inaccuracies. - {{{workInProgress.description}}} -</fieldset> -{{/workInProgress}} - -{{#deprecated}} -<fieldset class="deprecated"> - <legend>Deprecated API</legend> - {{deprecated}} -</fieldset> -{{/deprecated}} - -<h2>Description</h2> -{{{description}}} - -<h2>Usage</h2> -<tt ng:non-bindable> -{{name}}({{paramFirst.name}}{{#paramRest}}, {{name}}{{/paramRest}} ); -</tt> - -<h3>Parameters</h3> -<ul> - {{#param}} - <li><tt>{{name}}</tt> – - <tt>{{{#type}}{{type}}{{/type}}{{^type}}*{{/type}}{{#optional}}={{/optional}}}</tt> - <tt>{{#default}}[{{default}}]{{/default}}</tt> - – {{{description}}}</li> - {{/param}} -</ul> - -{{#returns}} -<h3>Returns</h3> -<tt>{{{{type}}}}</tt> {{{description}}} -{{/returns}} - -{{#example}} -<h2>Example</h2> -{{{exampleDescription}}} -<doc:example> - <doc:source> -{{/example}} - {{{example}}} -{{#example}} - </doc:source> - <doc:scenario>{{{scenario}}}</doc:scenario> -</doc:example> -{{/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 @@ -<h1>{{name}}</h1> - -{{#workInProgress}} -<fieldset class="workInProgress"> - <legend>Work In Progress</legend> - This page is currently being revised. It might be incomplete or contain inaccuracies. - {{{workInProgress.description}}} -</fieldset> -{{/workInProgress}} - -{{#deprecated}} -<fieldset class="deprecated"> - <legend>Deprecated API</legend> - {{deprecated}} -</fieldset> -{{/deprecated}} - -{{{description}}} - -{{#example}} -<h2>Example</h2> -{{{exampleDescription}}} -<doc:example> - <doc:source> -{{/example}} - {{{example}}} -{{#example}} - </doc:source> - <doc:scenario>{{{scenario}}}</doc:scenario> -</doc:example> -{{/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('<angular/>')). - toEqual('<p><tt><angular/></tt></p>'); - }); - - it('should not replace anything in <pre>', function(){ - expect(collect.markdown('bah x\n<pre>\nangular.k\n</pre>\n asdf x')). - toEqual( - '<p>bah x</p>' + - '<pre>\nangular.k\n</pre>' + - '<p>asdf x</p>'); - }); - - it('should replace text between two <pre></pre> tags', function() { - expect(collect.markdown('<pre>x</pre># One<pre>b</pre>')). - toEqual('<pre>x</pre><h1>One</h1><pre>b</pre>'); - }); - }); - - 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 <em>tion</em>'}); - }); - - 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', '<pre>abc</pre>'); - expect(doc.description). - toBe('<div ng:non-bindable><pre class="brush: js; html-script: true;">abc</pre></div>'); - }); - - it('should support multiple pre blocks', function() { - TAG.description(doc, 'description', 'foo \n<pre>abc</pre>\n#bah\nfoo \n<pre>cba</pre>'); - expect(doc.description). - toBe('<p>foo </p>' + - '<div ng:non-bindable><pre class="brush: js; html-script: true;">abc</pre></div>' + - '<h2>bah</h2>\n\n' + - '<p>foo </p>' + - '<div ng:non-bindable><pre class="brush: js; html-script: true;">cba</pre></div>'); - - }); - - 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('<p>foo <a href="#!angular.foo"><code>angular.foo</code></a></p>\n\n' + - '<p>da <a href="#!angular.foo"><code>bar foo bar</code></a> </p>\n\n' + - '<p>dad<a href="#!angular.foo"><code>angular.foo</code></a></p>\n\n' + - '<p><a href="#!angular.directive.ng:foo"><code>ng:foo</code></a></p>'); - }); - - it('should increment all headings by one', function() { - TAG.description(doc, 'description', '# foo\nabc'); - expect(doc.description). - toBe('<h2>foo</h2>\n\n<p>abc</p>'); - }); - }); - - 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: '<p>my description</p>'}); - }); - }); - - }); - - 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 <em>bold</em>.' + }); + }); + }); + + + }); + + describe('markdown', function(){ + var markdown = ngdoc.markdown; + + it('should replace angular in markdown', function(){ + expect(markdown('<angular/>')). + toEqual('<p><tt><angular/></tt></p>'); + }); + + it('should not replace anything in <pre>', function(){ + expect(markdown('bah x\n<pre>\nangular.k\n</pre>\n asdf x')). + toEqual( + '<p>bah x</p>' + + '<div ng:non-bindable><pre class="brush: js; html-script: true;">\n' + + 'angular.k\n' + + '</pre></div>' + + '<p>asdf x</p>'); + }); + + it('should replace text between two <pre></pre> tags', function() { + expect(markdown('<pre>x</pre># One<pre>b</pre>')). + toMatch('</div><h3>One</h3><div'); + }); + }); + + describe('trim', function(){ + var trim = ngdoc.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('merge', function(){ + it('should merge child with parent', function(){ + var parent = new Doc({name:'angular.service.abc'}); + var methodA = new Doc({name:'methodA', methodOf:'angular.service.abc'}); + var methodB = new Doc({name:'methodB', methodOf:'angular.service.abc'}); + var propA = new Doc({name:'propA', propertyOf:'angular.service.abc'}); + var propB = new Doc({name:'propB', propertyOf:'angular.service.abc'}); + ;var docs = [methodB, methodA, propB, propA, parent]; // keep wrong order; + ngdoc.merge(docs); + expect(docs.length).toEqual(1); + expect(docs[0].name).toEqual('angular.service.abc'); + expect(docs[0].methods).toEqual([methodA, methodB]); + expect(docs[0].properties).toEqual([propA, propB]); + }); + + }); + + //////////////////////////////////////// + + describe('TAG', function(){ + describe('@param', function(){ + it('should parse with no default', function(){ + var doc = new Doc('@param {(number|string)} number Number \n to format.'); + doc.parse(); + 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(){ + var doc = new Doc('@param {(number|string)=} [fractionSize=2] desc'); + doc.parse(); + 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() { + var doc = new Doc('@requires $service\n@requires $another'); + doc.parse(); + expect(doc.requires).toEqual(['$service', '$another']); + }); + }); + + describe('@property', function() { + it('should parse @property tags into array', function() { + var doc = new Doc("@property {type} name1 desc\n@property {type} name2 desc"); + doc.parse(); + expect(doc.properties.length).toEqual(2); + }); + + it('should parse @property with only name', function() { + var doc = new Doc("@property fake"); + doc.parse(); + expect(doc.properties[0].name).toEqual('fake'); + }); + + it('should parse @property with optional type', function() { + var doc = new Doc("@property {string} name"); + doc.parse(); + expect(doc.properties[0].name).toEqual('name'); + expect(doc.properties[0].type).toEqual('string'); + }); + + it('should parse @property with optional description', function() { + var doc = new Doc("@property name desc rip tion"); + doc.parse(); + expect(doc.properties[0].name).toEqual('name'); + expect(doc.properties[0].description).toEqual('desc rip tion'); + }); + + it('should parse @property with type and description both', function() { + var doc = new Doc("@property {bool} name desc rip tion"); + doc.parse(); + expect(doc.properties[0].name).toEqual('name'); + expect(doc.properties[0].type).toEqual('bool'); + expect(doc.properties[0].description).toEqual('desc rip tion'); + }); + + }); + + describe('@returns', function() { + it('should not parse @returns without type', function() { + var doc = new Doc("@returns lala"); + expect(doc.parse).toThrow(); + }); + + it('should parse @returns with type and description', function() { + var doc = new Doc("@returns {string} descrip tion"); + doc.parse(); + expect(doc.returns).toEqual({type: 'string', description: 'descrip tion'}); + }); + + it('should transform description of @returns with markdown', function() { + var doc = new Doc("@returns {string} descrip *tion*"); + doc.parse(); + expect(doc.returns).toEqual({type: 'string', description: 'descrip <em>tion</em>'}); + }); + + 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 <pre>abc</pre>"); + doc.parse(); + expect(doc.description). + toBe('<div ng:non-bindable><pre class="brush: js; html-script: true;">abc</pre></div>'); + }); + + it('should support multiple pre blocks', function() { + var doc = new Doc("@description foo \n<pre>abc</pre>\n#bah\nfoo \n<pre>cba</pre>"); + doc.parse(); + expect(doc.description). + toBe('<p>foo </p>' + + '<div ng:non-bindable><pre class="brush: js; html-script: true;">abc</pre></div>' + + '<h3>bah</h3>\n\n' + + '<p>foo </p>' + + '<div ng:non-bindable><pre class="brush: js; html-script: true;">cba</pre></div>'); + + }); + + 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('<p>foo <a href="#!angular.foo"><code>angular.foo</code></a></p>\n\n' + + '<p>da <a href="#!angular.foo"><code>bar foo bar</code></a> </p>\n\n' + + '<p>dad<a href="#!angular.foo"><code>angular.foo</code></a></p>\n\n' + + '<p><a href="#!angular.directive.ng:foo"><code>ng:foo</code></a></p>'); + }); + + 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('<h3>foo</h3>\n\n<p>abc</p>\n\n<h4>bar</h4>\n\n<p>xyz</p>'); + }); + }); + + 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 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, '<').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('</' + 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><angular/></tt>'); + text = new Showdown.converter().makeHtml(text.replace(/^#/gm, '###')); + + while (match = text.match(R_LINK)) { + text = text.replace(match[0], '<a href="#!' + match[1] + '"><code>' + + (match[4] || match[1]) + + '</code></a>'); + } + } + parts[i] = text; + }); + return parts.join(''); +}; +var R_LINK = /{@link ([^\s}]+)((\s|\n)+(.+?))?\s*}/m; + // 1 123 3 4 42 +function markdownNoP(text) { + var lines = markdown(text).split(NEW_LINE); + var last = lines.length - 1; + lines[0] = lines[0].replace(/^<p>/, ''); + lines[last] = lines[last].replace(/<\/p>$/, ''); + return lines.join('\n'); +} + + +////////////////////////////////////////////////////////// +function scenarios(docs){ + var specs = []; + docs.forEach(function(doc){ + if (doc.scenario) { + specs.push('describe("'); + specs.push(doc.name); + specs.push('", function(){\n'); + specs.push(' beforeEach(function(){\n'); + specs.push(' browser().navigateTo("index.html#!' + doc.name + '");'); + specs.push(' });\n\n'); + specs.push(doc.scenario); + specs.push('\n});\n\n'); + } + }); + return specs; +} + + +////////////////////////////////////////////////////////// +function metadata(docs){ + var words = []; + docs.forEach(function(doc){ + words.push({ + name:doc.name, + type: doc.ngdoc, + keywords:doc.keywords() + }); + }); + words.sort(keywordSort); + return words; +} + +function keywordSort(a,b){ + // supper ugly comparator that orders all utility methods and objects before all the other stuff + // like widgets, directives, services, etc. + // Mother of all beautiful code please forgive me for the sin that this code certainly is. + + if (a.name === b.name) return 0; + if (a.name === 'angular') return -1; + if (b.name === 'angular') return 1; + + function namespacedName(page) { + return (page.name.match(/\./g).length === 1 && page.type !== 'overview' ? '0' : '1') + page.name; + } + + var namespacedA = namespacedName(a), + namespacedB = namespacedName(b); + + return namespacedA < namespacedB ? -1 : 1; +} + + +////////////////////////////////////////////////////////// +function trim(text) { + var MAX = 9999; + var empty = RegExp.prototype.test.bind(/^\s*$/); + var lines = text.split('\n'); + var minIndent = MAX; + lines.forEach(function(line){ + minIndent = Math.min(minIndent, indent(line)); + }); + for ( var i = 0; i < lines.length; i++) { + lines[i] = lines[i].substring(minIndent); + } + // remove leading lines + while (empty(lines[0])) { + lines.shift(); + } + // remove trailing + while (empty(lines[lines.length - 1])) { + lines.pop(); + } + return lines.join('\n'); + + function indent(line) { + for(var i = 0; i < line.length; i++) { + if (line.charAt(i) != ' ') { + return i; + } + } + return MAX; + } +} + +////////////////////////////////////////////////////////// +function merge(docs){ + var byName = {}; + docs.forEach(function(doc){ + byName[doc.name] = doc; + }); + for(var i=0; i<docs.length;) { + if (findParent(docs[i], 'method') || + findParent(docs[i], 'property')) { + docs.splice(i, 1); + } else { + i++; + } + } + + function findParent(doc, name){ + var parentName = doc[name+'Of']; + if (!parentName) return false; + + var parent = byName[parentName]; + if (!parent) + throw new Error("No parent named '" + parentName + "' for '" + + doc.name + "' in @" + name + "Of."); + + var listName = (name + 's').replace(/ys$/, 'ies'); + var list = parent[listName] = (parent[listName] || []); + list.push(doc); + list.sort(orderByName); + return true; + } + + function orderByName(a, b){ + return a.name < b.name ? -1 : (a.name > b.name ? 1 : 0); + } +} +////////////////////////////////////////////////////////// + +function property(name) { + return function(value){ + return value[name]; + }; +}
\ No newline at end of file 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 index 8361f105..8361f105 100644 --- a/docs/doc_widgets.css +++ b/docs/src/templates/doc_widgets.css diff --git a/docs/doc_widgets.js b/docs/src/templates/doc_widgets.js index b119e326..b119e326 100644 --- a/docs/doc_widgets.js +++ b/docs/src/templates/doc_widgets.js diff --git a/docs/docs-scenario.html b/docs/src/templates/docs-scenario.html index c75155c5..c75155c5 100644 --- a/docs/docs-scenario.html +++ b/docs/src/templates/docs-scenario.html diff --git a/docs/docs-scenario.js b/docs/src/templates/docs-scenario.js index 2ee8387b..2ee8387b 100644 --- a/docs/docs-scenario.js +++ b/docs/src/templates/docs-scenario.js diff --git a/docs/docs.css b/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 index 6bf86ed3..6bf86ed3 100644 --- a/docs/docs.js +++ b/docs/src/templates/docs.js diff --git a/docs/index.html b/docs/src/templates/index.html index 1722e98a..64b73bfc 100644 --- a/docs/index.html +++ b/docs/src/templates/index.html @@ -20,7 +20,7 @@ <script src="../angular.min.js" ng:autobind></script> <script src="docs.js"></script> <script src="doc_widgets.js"></script> - <script src="docs-data.js"></script> + <script src="docs-keywords.js"></script> </head> <body style="display:none;" ng:show="true"> <div id="header"> 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); + }); +}; diff --git a/docs/validator.template b/docs/validator.template deleted file mode 100644 index 64ab37b7..00000000 --- a/docs/validator.template +++ /dev/null @@ -1,59 +0,0 @@ -<h1>{{name}}</h1> - -{{#workInProgress}} -<fieldset class="workInProgress"> - <legend>Work In Progress</legend> - This page is currently being revised. It might be incomplete or contain inaccuracies. - {{{workInProgress.description}}} -</fieldset> -{{/workInProgress}} - -{{#deprecated}} -<fieldset class="deprecated"> - <legend>Deprecated API</legend> - {{deprecated}} -</fieldset> -{{/deprecated}} - -<h2>Description</h2> -{{{description}}} - -<h2>Usage</h2> -<h3>In HTML Template Binding</h3> -<tt> - <input type="text" ng:validate="{{shortName}}{{#paramRest}}{{^default}}:{{name}}{{/default}}{{#default}}<i>[:{{name}}]</i>{{/default}}{{/paramRest}}"/> -</tt> - -<h3>In JavaScript</h3> -<tt ng:non-bindable> -angular.validator.{{shortName}}({{paramFirst.name}}{{#paramRest}}{{^default}}, {{name}}{{/default}}{{#default}}<i>[, {{name}}]</i>{{/default}}{{/paramRest}} ); -</tt> - -<h3>Parameters</h3> -<ul> - {{#param}} - <li><tt>{{name}}</tt> – - <tt>{{{#type}}{{type}}{{/type}}{{^type}}*{{/type}}{{#optional}}={{/optional}}}</tt> - <tt>{{#default}}[{{default}}]{{/default}}</tt> - – {{{description}}}</li> - {{/param}} -</ul> -{{{paramDescription}}} - -{{#css}} -<h3>CSS</h3> -{{{css}}} -{{/css}} - -{{#example}} -<h2>Example</h2> -{{{exampleDescription}}} -<doc:example> - <doc:source> -{{/example}} - {{{example}}} -{{#example}} - </doc:source> - <doc:scenario>{{{scenario}}}</doc:scenario> -</doc:example> -{{/example}} diff --git a/docs/widget.template b/docs/widget.template deleted file mode 100644 index 418ad407..00000000 --- a/docs/widget.template +++ /dev/null @@ -1,68 +0,0 @@ -<h1>{{name}}</h1> - -{{#workInProgress}} -<fieldset class="workInProgress"> - <legend>Work In Progress</legend> - This page is currently being revised. It might be incomplete or contain inaccuracies. - {{{workInProgress.description}}} -</fieldset> -{{/workInProgress}} - -{{#deprecated}} -<fieldset class="deprecated"> - <legend>Deprecated API</legend> - {{deprecated}} -</fieldset> -{{/deprecated}} - -<h2>Description</h2> -{{{description}}} - -<h2>Usage</h2> -<h3>In HTML Template Binding</h3> -<tt> -{{^element}} - <pre> -<{{shortName}}{{#param}} {{#default}}<i>[</i>{{/default}}{{name}}="..."{{#default}}<i>]</i>{{/default}}{{/param}}>{{#usageContent}} - - {{usageContent}} -{{/usageContent}}</{{shortName}}> - </pre> -{{/element}} -{{#element}} - <pre> -<{{element}} {{shortName}}{{#paramFirst}}="{{paramFirst.name}}{{/paramFirst}}"> - ... -</{{element}}> - </pre> -{{/element}} -</tt> - -<h3>Parameters</h3> -<ul> - {{#param}} - <li><tt>{{name}}</tt> – - <tt>{{{#type}}{{type}}{{/type}}{{^type}}*{{/type}}{{#optional}}={{/optional}}}</tt> - <tt>{{#default}}[{{default}}]{{/default}}</tt> - – {{{description}}}</li> - {{/param}} -</ul> -{{{paramDescription}}} - -{{#css}} -<h3>CSS</h3> -{{{css}}} -{{/css}} - -{{#example}} -<h2>Example</h2> -{{{exampleDescription}}} -<doc:example> - <doc:source> -{{/example}} - {{{example}}} -{{#example}} - </doc:source> - <doc:scenario>{{{scenario}}}</doc:scenario> -</doc:example> -{{/example}} |
