aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMisko Hevery2010-12-23 00:44:27 +0100
committerMisko Hevery2011-01-10 11:50:11 -0800
commit4f22d6866c052fb5b770ce4f377cecacacd9e6d8 (patch)
tree6bdb1c5eb70cfd7e6bcf143c121c53025a0489a4
parentaab3df7aeaf79908e8b6212288b283adb42b1ce6 (diff)
downloadangular.js-4f22d6866c052fb5b770ce4f377cecacacd9e6d8.tar.bz2
complete rewrite of documentation generation
- romeved mustache.js - unified templates - improved testability of the code
-rw-r--r--docs/directive.template58
-rw-r--r--docs/docs-data.js1
-rw-r--r--docs/filter.template65
-rw-r--r--docs/formatter.template53
-rw-r--r--docs/function.template52
-rw-r--r--docs/overview.template31
-rw-r--r--docs/spec/collectSpec.js288
-rw-r--r--docs/spec/ngdocSpec.js257
-rw-r--r--docs/spec/specs.js39
-rw-r--r--docs/spec/writerSpec.js18
-rw-r--r--docs/specs.js21
-rw-r--r--docs/src/callback.js (renamed from docs/callback.js)5
-rw-r--r--docs/src/dom.js123
-rw-r--r--docs/src/gen-docs.js42
-rw-r--r--docs/src/ignore.words0
-rw-r--r--docs/src/ngdoc.js614
-rw-r--r--docs/src/reader.js91
-rw-r--r--docs/src/templates/doc_widgets.css (renamed from docs/doc_widgets.css)0
-rw-r--r--docs/src/templates/doc_widgets.js (renamed from docs/doc_widgets.js)0
-rw-r--r--docs/src/templates/docs-scenario.html (renamed from docs/docs-scenario.html)0
-rw-r--r--docs/src/templates/docs-scenario.js (renamed from docs/docs-scenario.js)0
-rw-r--r--docs/src/templates/docs.css (renamed from docs/docs.css)6
-rw-r--r--docs/src/templates/docs.js (renamed from docs/docs.js)0
-rw-r--r--docs/src/templates/index.html (renamed from docs/index.html)2
-rw-r--r--docs/src/writer.js61
-rw-r--r--docs/validator.template59
-rw-r--r--docs/widget.template68
-rwxr-xr-xgen_docs.sh6
-rw-r--r--lib/mustache/LICENSE21
-rw-r--r--lib/mustache/index.js344
-rw-r--r--src/Angular.js2
-rw-r--r--src/validators.js12
-rw-r--r--src/widgets.js3
33 files changed, 1271 insertions, 1071 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>
-&lt;{{element}} {{shortName}}="{{paramFirst.name}}"&gt;
- ...
-&lt;/{{element}}&gt;
- </pre>
-</tt>
-
-<h3>Parameters</h3>
-<ul>
- {{#param}}
- <li><tt>{{name}}</tt> –
- <tt>&#123;{{#type}}{{type}}{{/type}}{{^type}}*{{/type}}{{#optional}}={{/optional}}&#125;</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>&#123;{{#type}}{{type}}{{/type}}{{^type}}*{{/type}}{{#optional}}={{/optional}}&#125;</tt>
- <tt>{{#default}}[{{default}}]{{/default}}</tt>
- – {{{description}}}</li>
- {{/param}}
-</ul>
-
-{{#returns}}
-<h3>Returns</h3>
-<tt>&#123;{{{type}}}&#125;</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>
- &lt;input type="text" ng:format="{{shortName}}"&gt;
-</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>&#123;{{{type}}}&#125;</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>&#123;{{#type}}{{type}}{{/type}}{{^type}}*{{/type}}{{#optional}}={{/optional}}&#125;</tt>
- <tt>{{#default}}[{{default}}]{{/default}}</tt>
- – {{{description}}}</li>
- {{/param}}
-</ul>
-
-{{#returns}}
-<h3>Returns</h3>
-<tt>&#123;{{{type}}}&#125;</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>&lt;angular/&gt;</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>&lt;angular/&gt;</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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'));
+ } else if (typeof content == 'function') {
+ content.call(this, this);
+ } else if (content instanceof Array) {
+ this.ul(content);
+ }
+ },
+
+ html: function(html) {
+ if (html) {
+ this.out.push(html);
+ }
+ },
+
+ tag: function(name, attr, text) {
+ if (!text) {
+ text = attr;
+ attr = {};
+ if (name == 'code')
+ attr['ng:non-bindable'] = '';
+ }
+ this.out.push('<' + name);
+ for(var key in attr) {
+ this.out.push(" " + key + '="' + attr[key] + '"');
+ }
+ this.out.push('>');
+ this.text(text);
+ this.out.push('</' + name + '>');
+ if (!INLINE_TAGS[name])
+ this.out.push('\n');
+ },
+
+ code: function(text) {
+ this.tag('div', {'ng:non-bindable':''}, function(){
+ this.tag('pre', {'class':"brush: js; html-script: true;"}, text);
+ });
+ },
+
+ example: function(source, scenario) {
+ if (source || scenario) {
+ this.h('Example', function(){
+ if (scenario === false) {
+ this.code(source);
+ } else {
+ this.tag('doc:example', function(){
+ if (source) this.tag('doc:source', source);
+ if (scenario) this.tag('doc:scenario', scenario);
+ });
+ }
+ });
+ }
+ },
+
+ h: function(heading, content, fn){
+ if (content==undefined || content && content.legth == 0) return;
+ this.tag('h' + this.headingDepth, heading);
+ this.headingDepth++;
+ if (content instanceof Array) {
+ this.ul(content, {'class': heading.toLowerCase()}, fn);
+ } else if (fn) {
+ fn.call(this, content);
+ } else {
+ this.text(content);
+ }
+ this.headingDepth--;
+ },
+
+ h1: function(attr, text) {
+ this.tag('h1', attr, text);
+ },
+
+ h2: function(attr, text) {
+ this.tag('h2', attr, text);
+ },
+
+ h3: function(attr, text) {
+ this.tag('h3', attr, text);
+ },
+
+ p: function(attr, text) {
+ this.tag('p', attr, text);
+ },
+
+ ul: function(list, attr, fn) {
+ if (typeof attr == 'function') {
+ fn = attr;
+ attr = {};
+ }
+ this.tag('ul', attr, function(dom){
+ list.forEach(function(item){
+ dom.out.push('<li>');
+ dom.text(fn ? fn(item) : item);
+ dom.out.push('</li>\n');
+ });
+ });
+ }
+
+}; \ No newline at end of file
diff --git a/docs/src/gen-docs.js b/docs/src/gen-docs.js
new file mode 100644
index 00000000..b4e30a53
--- /dev/null
+++ b/docs/src/gen-docs.js
@@ -0,0 +1,42 @@
+require.paths.push(__dirname);
+require.paths.push('lib');
+var reader = require('reader.js'),
+ ngdoc = require('ngdoc.js'),
+ writer = require('writer.js'),
+ callback = require('callback.js');
+
+var docs = [];
+var start;
+var work = callback.chain(function(){
+ start = now();
+ console.log('Generating Angular Reference Documentation...');
+ reader.collect(work.waitMany(function(text, file, line){
+ var doc = new ngdoc.Doc(text, file, line);
+ docs.push(doc);
+ doc.parse();
+ }));
+});
+var writes = callback.chain(function(){
+ ngdoc.merge(docs);
+ docs.forEach(function(doc){
+ writer.output(doc.name + '.html', doc.html(), writes.waitFor());
+ });
+ var metadata = ngdoc.metadata(docs);
+ writer.output('docs-keywords.js', ['NG_PAGES=', JSON.stringify(metadata), ';'], writes.waitFor());
+ writer.copy('index.html', writes.waitFor());
+ writer.copy('docs.js', writes.waitFor());
+ writer.copy('docs.css', writes.waitFor());
+ writer.copy('doc_widgets.js', writes.waitFor());
+ writer.copy('doc_widgets.css', writes.waitFor());
+ writer.copy('docs-scenario.html', writes.waitFor());
+ writer.output('docs-scenario.js', ngdoc.scenarios(docs), writes.waitFor());
+});
+writes.onDone(function(){
+ console.log('DONE. Generated ' + docs.length + ' pages in ' +
+ (now()-start) + 'ms.' );
+});
+work.onDone(writes);
+writer.makeDir('build/docs', work);
+
+///////////////////////////////////
+function now(){ return new Date().getTime(); }
diff --git a/docs/src/ignore.words b/docs/src/ignore.words
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/docs/src/ignore.words
diff --git a/docs/src/ngdoc.js b/docs/src/ngdoc.js
new file mode 100644
index 00000000..ac7d0bb1
--- /dev/null
+++ b/docs/src/ngdoc.js
@@ -0,0 +1,614 @@
+/**
+ * All parsing/transformation code goes here. All code here should be sync to ease testing.
+ */
+
+var Showdown = require('showdown').Showdown;
+var DOM = require('dom.js').DOM;
+var NEW_LINE = /\n\r?/;
+
+exports.markdown = markdown;
+exports.markdownNoP = markdownNoP;
+exports.trim = trim;
+exports.metadata = metadata;
+exports.scenarios = scenarios;
+exports.merge = merge;
+exports.Doc = Doc;
+
+//////////////////////////////////////////////////////////
+function Doc(text, file, line) {
+ if (typeof text == 'object') {
+ for ( var key in text) {
+ this[key] = text[key];
+ }
+ } else {
+ this.text = text;
+ this.file = file;
+ this.line = line;
+ }
+}
+Doc.METADATA_IGNORE = (function(){
+ var words = require('fs').readFileSync(__dirname + '/ignore.words', 'utf8');
+ return words.toString().split(/[,\s\n\r]+/gm);
+})();
+
+
+
+Doc.prototype = {
+ keywords: function keywords(){
+ var keywords = {};
+ Doc.METADATA_IGNORE.forEach(function(ignore){ keywords[ignore] = true; });
+ var words = [];
+ var tokens = this.text.toLowerCase().split(/[,\.\`\'\"\s]+/mg);
+ tokens.forEach(function(key){
+ var match = key.match(/^(([a-z]|ng\:)[\w\_\-]{2,})/);
+ if (match){
+ key = match[1];
+ if (!keywords[key]) {
+ keywords[key] = true;
+ words.push(key);
+ }
+ }
+ });
+ words.sort();
+ return words.join(' ');
+ },
+
+ parse: function(){
+ var atName;
+ var atText;
+ var match;
+ var self = this;
+ self.text.split(NEW_LINE).forEach(function(line){
+ if (match = line.match(/^\s*@(\w+)(\s+(.*))?/)) {
+ // we found @name ...
+ // if we have existing name
+ flush();
+ atName = match[1];
+ atText = [];
+ if(match[3]) atText.push(match[3]);
+ } else {
+ if (atName) {
+ atText.push(line);
+ }
+ }
+ });
+ flush();
+ this.shortName = (this.name || '').split(/[\.#]/).pop();
+ this.description = markdown(this.description);
+
+ function flush(){
+ if (atName) {
+ var text = trim(atText.join('\n'));
+ if (atName == 'param') {
+ var match = text.match(/^{([^}=]+)(=)?}\s+(([^\s=]+)|\[(\S+)=([^\]]+)\])\s+(.*)/);
+ // 1 12 2 34 4 5 5 6 6 3 7 7
+ if (!match) {
+ throw new Error("Not a valid 'param' format: " + text);
+ }
+ var param = {
+ name: match[5] || match[4],
+ description:markdownNoP(text.replace(match[0], match[7])),
+ type: match[1],
+ optional: !!match[2],
+ 'default':match[6]
+ };
+ self.param = self.param || [];
+ self.param.push(param);
+ } else if (atName == 'returns') {
+ var match = text.match(/^{([^}=]+)}\s+(.*)/);
+ if (!match) {
+ throw new Error("Not a valid 'returns' format: " + text);
+ }
+ self.returns = {
+ type: match[1],
+ description: markdownNoP(text.replace(match[0], match[2]))
+ };
+ } else if(atName == 'requires') {
+ self.requires = self.requires || [];
+ self.requires.push(text);
+ } else if(atName == 'property') {
+ var match = text.match(/^({(\S+)}\s*)?(\S+)(\s+(.*))?/);
+ if (!match) {
+ throw new Error("Not a valid 'property' format: " + text);
+ }
+ var property = {
+ type: match[2],
+ name: match[3],
+ description: match[5] || ''
+ };
+ self.properties = self.properties || [];
+ self.properties.push(property);
+ } else {
+ self[atName] = text;
+ }
+ }
+ }
+ },
+
+ html: function(){
+ var dom = new DOM(),
+ self = this;
+
+ dom.h(this.name, function(){
+ notice('workInProgress', 'Work in Progress',
+ 'This page is currently being revised. It might be incomplete or contain inaccuracies.');
+ notice('depricated', 'Depricated API');
+ dom.h('Description', self.description, html);
+ dom.h('Dependencies', self.requires);
+
+ usage();
+
+ dom.h('Methods', self.methods, function(method){
+ var signature = (method.param || []).map(property('name'));
+ dom.h(method.shortName + '(' + signature.join(', ') + ')', method, function(){
+ dom.html(method.description);
+ method.html_usage_parameters(dom);
+ dom.example(method.example, false);
+ });
+ });
+ dom.h('Properties', self.properties, function(property){
+ dom.h(property.name, function(){
+ dom.text(property.description);
+ dom.example(property.example, false);
+ });
+ });
+
+ dom.example(self.example, self.scenario);
+ });
+
+ return dom.toString();
+
+ //////////////////////////
+
+ function html(text){
+ this.html(text);
+ }
+
+ function usage(){
+ (self['html_usage_' + self.ngdoc] || function(){
+ throw new Error("Don't know how to format @ngdoc: " + self.ngdoc);
+ }).call(self, dom);
+ }
+
+ function section(name, property, fn) {
+ var value = self[property];
+ if (value) {
+ dom.h2(name);
+ if (typeof value == 'string') {
+ value = markdown(value) + '\n';
+ fn ? fn(value) : dom.html(value);
+ } else if (value instanceof Array) {
+ dom.ul(value, fn);
+ }
+ }
+ }
+
+ function notice(name, legend, msg){
+ if (self[name] == undefined) return;
+ dom.tag('fieldset', {'class':name}, function(dom){
+ dom.tag('legend', legend);
+ dom.text(msg);
+ });
+ }
+
+ },
+
+ html_usage_parameters: function(dom) {
+ dom.h('Parameters', this.param, function(param){
+ dom.tag('code', function(){
+ dom.text(param.name);
+ if (param.optional) {
+ dom.tag('i', function(){
+ dom.text('(optional');
+ if(param['default']) {
+ dom.text('=' + param['default']);
+ }
+ dom.text(')');
+ });
+ }
+ dom.text(' – {');
+ dom.text(param.type);
+ dom.text('} – ');
+ });
+ dom.html(param.description);
+ });
+ },
+
+ html_usage_returns: function(dom) {
+ var self = this;
+ if (self.returns) {
+ dom.h('Returns', function(){
+ dom.tag('code', self.returns.type);
+ dom.text('– ');
+ dom.html(self.returns.description);
+ });
+ }
+ },
+
+ html_usage_function: function(dom){
+ var self = this;
+ dom.h('Usage', function(){
+ dom.code(function(){
+ dom.text(self.name);
+ dom.text('(');
+ var first = true;
+ (self.param || []).forEach(function(param){
+ if (first) {
+ first = false;
+ } else {
+ dom.text(', ');
+ }
+ dom.text(param.name);
+ });
+ dom.text(');');
+ });
+
+ self.html_usage_parameters(dom);
+ self.html_usage_returns(dom);
+ });
+ },
+
+ html_usage_directive: function(dom){
+ var self = this;
+ dom.h('Usage', function(){
+ dom.tag('pre', {'class':"brush: js; html-script: true;"}, function(){
+ dom.text('<' + self.element + ' ');
+ dom.text(self.shortName);
+ if (self.param) {
+ dom.text('="' + self.param[0].name + '"');
+ }
+ dom.text('>\n ...\n');
+ dom.text('</' + self.element + '>');
+ });
+ self.html_usage_parameters(dom);
+ });
+ },
+
+ html_usage_filter: function(dom){
+ var self = this;
+ dom.h('Usage', function(){
+ dom.h('In HTML Template Binding', function(){
+ dom.tag('code', function(){
+ dom.text('{{ ');
+ dom.text(self.shortName);
+ dom.text('_expression | ');
+ dom.text(self.shortName);
+ var first = true;
+ (self.param||[]).forEach(function(param){
+ if (first) {
+ first = false;
+ } else {
+ if (param.optional) {
+ dom.tag('i', function(){
+ dom.text('[:' + param.name + ']');
+ });
+ } else {
+ dom.text(':' + param.name);
+ }
+ }
+ });
+ dom.text(' }}');
+ });
+ });
+
+ dom.h3('In JavaScript', function(){
+ dom.tag('code', function(){
+ dom.text('angular.filter.');
+ dom.text(self.shortName);
+ dom.text('(');
+ var first = true;
+ (self.param||[]).forEach(function(param){
+ if (first) {
+ first = false;
+ dom.text(param.name);
+ } else {
+ if (param.optional) {
+ dom.tag('i', function(){
+ dom.text('[, ' + param.name + ']');
+ });
+ } else {
+ dom.text(', ' + param.name);
+ }
+ }
+ });
+ dom.text(')');
+ });
+ });
+
+ self.html_usage_parameters(dom);
+ self.html_usage_returns(dom);
+ });
+ },
+
+ html_usage_formatter: function(dom){
+ var self = this;
+ dom.h('Usage', function(){
+ dom.h('In HTML Template Binding', function(){
+ dom.code(function(){
+ dom.text('<input type="text" ng:format="');
+ dom.text(self.shortName);
+ dom.text('">');
+ });
+ });
+
+ dom.h3('In JavaScript', function(){
+ dom.code(function(){
+ dom.text('var userInputString = angular.formatter.');
+ dom.text(self.shortName);
+ dom.text('.format(modelValue);');
+ });
+ dom.html('<br/>');
+ dom.code(function(){
+ dom.text('var modelValue = angular.formatter.');
+ dom.text(self.shortName);
+ dom.text('.parse(userInputString);');
+ });
+ });
+
+ self.html_usage_returns(dom);
+ });
+ },
+
+ html_usage_validator: function(dom){
+ var self = this;
+ dom.h('Usage', function(){
+ dom.h('In HTML Template Binding', function(){
+ dom.code(function(){
+ dom.text('<input type="text" ng:validate="');
+ dom.text(self.shortName);
+ var first = true;
+ (self.param||[]).forEach(function(param){
+ if (first) {
+ first = false;
+ } else {
+ if (param.optional) {
+ dom.text('[:' + param.name + ']');
+ } else {
+ dom.text(':' + param.name);
+ }
+ }
+ });
+ dom.text('"/>');
+ });
+ });
+
+ dom.h('In JavaScript', function(){
+ dom.code(function(){
+ dom.text('angular.validator.');
+ dom.text(self.shortName);
+ dom.text('(');
+ var first = true;
+ (self.param||[]).forEach(function(param){
+ if (first) {
+ first = false;
+ dom.text(param.name);
+ } else {
+ if (param.optional) {
+ dom.text('[, ' + param.name + ']');
+ } else {
+ dom.text(', ' + param.name);
+ }
+ }
+ });
+ dom.text(')');
+ });
+ });
+
+ self.html_usage_parameters(dom);
+ self.html_usage_returns(dom);
+ });
+ },
+
+ html_usage_widget: function(dom){
+ var self = this;
+ dom.h('Usage', function(){
+ dom.h('In HTML Template Binding', function(){
+ dom.code(function(){
+ if (self.shortName.match(/^@/)) {
+ dom.text('<');
+ dom.text(self.element);
+ dom.text(' ');
+ dom.text(self.shortName.substring(1));
+ if (self.param) {
+ dom.text('="');
+ dom.text(self.param[0].name);
+ dom.text('"');
+ }
+ dom.text('>\n ...\n</');
+ dom.text(self.element);
+ dom.text('>');
+ } else {
+ dom.text('<');
+ dom.text(self.shortName);
+ (self.param||[]).forEach(function(param){
+ if (param.optional) {
+ dom.text(' [' + param.name + '="..."]');
+ } else {
+ dom.text(' ' + param.name + '="..."');
+ }
+ });
+ dom.text('></');
+ dom.text(self.shortName);
+ dom.text('>');
+ }
+ });
+ });
+
+ self.html_usage_parameters(dom);
+ self.html_usage_returns(dom);
+ });
+ },
+
+ html_usage_overview: function(dom){
+ },
+
+ html_usage_service: function(dom){
+ }
+
+};
+//////////////////////////////////////////////////////////
+
+
+//////////////////////////////////////////////////////////
+function markdown (text) {
+ if (!text) return text;
+ var parts = text.split(/(<pre>[\s\S]*?<\/pre>)/),
+ match;
+
+ parts.forEach(function(text, i){
+ if (text.match(/^<pre>/)) {
+ text = text.
+ replace(/^<pre>/, '<div ng:non-bindable><pre class="brush: js; html-script: true;">').
+ replace(/<\/pre>/, '</pre></div>');
+ } else {
+ text = text.replace(/<angular\/>/gm, '<tt>&lt;angular/&gt;</tt>');
+ text = new Showdown.converter().makeHtml(text.replace(/^#/gm, '###'));
+
+ while (match = text.match(R_LINK)) {
+ text = text.replace(match[0], '<a href="#!' + match[1] + '"><code>' +
+ (match[4] || match[1]) +
+ '</code></a>');
+ }
+ }
+ parts[i] = text;
+ });
+ return parts.join('');
+};
+var R_LINK = /{@link ([^\s}]+)((\s|\n)+(.+?))?\s*}/m;
+ // 1 123 3 4 42
+function markdownNoP(text) {
+ var lines = markdown(text).split(NEW_LINE);
+ var last = lines.length - 1;
+ lines[0] = lines[0].replace(/^<p>/, '');
+ lines[last] = lines[last].replace(/<\/p>$/, '');
+ return lines.join('\n');
+}
+
+
+//////////////////////////////////////////////////////////
+function scenarios(docs){
+ var specs = [];
+ docs.forEach(function(doc){
+ if (doc.scenario) {
+ specs.push('describe("');
+ specs.push(doc.name);
+ specs.push('", function(){\n');
+ specs.push(' beforeEach(function(){\n');
+ specs.push(' browser().navigateTo("index.html#!' + doc.name + '");');
+ specs.push(' });\n\n');
+ specs.push(doc.scenario);
+ specs.push('\n});\n\n');
+ }
+ });
+ return specs;
+}
+
+
+//////////////////////////////////////////////////////////
+function metadata(docs){
+ var words = [];
+ docs.forEach(function(doc){
+ words.push({
+ name:doc.name,
+ type: doc.ngdoc,
+ keywords:doc.keywords()
+ });
+ });
+ words.sort(keywordSort);
+ return words;
+}
+
+function keywordSort(a,b){
+ // supper ugly comparator that orders all utility methods and objects before all the other stuff
+ // like widgets, directives, services, etc.
+ // Mother of all beautiful code please forgive me for the sin that this code certainly is.
+
+ if (a.name === b.name) return 0;
+ if (a.name === 'angular') return -1;
+ if (b.name === 'angular') return 1;
+
+ function namespacedName(page) {
+ return (page.name.match(/\./g).length === 1 && page.type !== 'overview' ? '0' : '1') + page.name;
+ }
+
+ var namespacedA = namespacedName(a),
+ namespacedB = namespacedName(b);
+
+ return namespacedA < namespacedB ? -1 : 1;
+}
+
+
+//////////////////////////////////////////////////////////
+function trim(text) {
+ var MAX = 9999;
+ var empty = RegExp.prototype.test.bind(/^\s*$/);
+ var lines = text.split('\n');
+ var minIndent = MAX;
+ lines.forEach(function(line){
+ minIndent = Math.min(minIndent, indent(line));
+ });
+ for ( var i = 0; i < lines.length; i++) {
+ lines[i] = lines[i].substring(minIndent);
+ }
+ // remove leading lines
+ while (empty(lines[0])) {
+ lines.shift();
+ }
+ // remove trailing
+ while (empty(lines[lines.length - 1])) {
+ lines.pop();
+ }
+ return lines.join('\n');
+
+ function indent(line) {
+ for(var i = 0; i < line.length; i++) {
+ if (line.charAt(i) != ' ') {
+ return i;
+ }
+ }
+ return MAX;
+ }
+}
+
+//////////////////////////////////////////////////////////
+function merge(docs){
+ var byName = {};
+ docs.forEach(function(doc){
+ byName[doc.name] = doc;
+ });
+ for(var i=0; i<docs.length;) {
+ if (findParent(docs[i], 'method') ||
+ findParent(docs[i], 'property')) {
+ docs.splice(i, 1);
+ } else {
+ i++;
+ }
+ }
+
+ function findParent(doc, name){
+ var parentName = doc[name+'Of'];
+ if (!parentName) return false;
+
+ var parent = byName[parentName];
+ if (!parent)
+ throw new Error("No parent named '" + parentName + "' for '" +
+ doc.name + "' in @" + name + "Of.");
+
+ var listName = (name + 's').replace(/ys$/, 'ies');
+ var list = parent[listName] = (parent[listName] || []);
+ list.push(doc);
+ list.sort(orderByName);
+ return true;
+ }
+
+ function orderByName(a, b){
+ return a.name < b.name ? -1 : (a.name > b.name ? 1 : 0);
+ }
+}
+//////////////////////////////////////////////////////////
+
+function property(name) {
+ return function(value){
+ return value[name];
+ };
+} \ No newline at end of file
diff --git a/docs/src/reader.js b/docs/src/reader.js
new file mode 100644
index 00000000..8f9f22c3
--- /dev/null
+++ b/docs/src/reader.js
@@ -0,0 +1,91 @@
+/**
+ * All reading related code here. This is so that we can separate the async code from sync code
+ * for testability
+ */
+require.paths.push(__dirname);
+var fs = require('fs'),
+ callback = require('callback');
+
+var NEW_LINE = /\n\r?/;
+
+function collect(callback){
+ findJsFiles('src', callback.waitMany(function(file) {
+ //console.log('reading', file, '...');
+ findNgDocInJsFile(file, callback.waitMany(function(doc, line) {
+ callback(doc, file, line);
+ }));
+ }));
+ findNgDocInDir('docs/', callback.waitMany(callback));
+ callback.done();
+}
+
+function findJsFiles(dir, callback){
+ fs.readdir(dir, callback.waitFor(function(err, files){
+ if (err) return this.error(err);
+ files.forEach(function(file){
+ var path = dir + '/' + file;
+ fs.lstat(path, callback.waitFor(function(err, stat){
+ if (err) return this.error(err);
+ if (stat.isDirectory())
+ findJsFiles(path, callback.waitMany(callback));
+ else if (/\.js$/.test(path))
+ callback(path);
+ }));
+ });
+ callback.done();
+ }));
+}
+
+function findNgDocInDir(directory, docNotify) {
+ fs.readdir(directory, docNotify.waitFor(function(err, files){
+ if (err) return this.error(err);
+ files.forEach(function(file){
+ //console.log('reading', directory + file, '...');
+ if (!file.match(/\.ngdoc$/)) return;
+ fs.readFile(directory + file, docNotify.waitFor(function(err, content){
+ if (err) return this.error(err);
+ docNotify(content.toString(), directory + file, 1);
+ }));
+ });
+ docNotify.done();
+ }));
+}
+
+function findNgDocInJsFile(file, callback) {
+ fs.readFile(file, callback.waitFor(function(err, content){
+ var lines = content.toString().split(NEW_LINE);
+ var text;
+ var startingLine ;
+ var match;
+ var inDoc = false;
+ lines.forEach(function(line, lineNumber){
+ lineNumber++;
+ // is the comment starting?
+ if (!inDoc && (match = line.match(/^\s*\/\*\*\s*(.*)$/))) {
+ line = match[1];
+ inDoc = true;
+ text = [];
+ startingLine = lineNumber;
+ }
+ // are we done?
+ if (inDoc && line.match(/\*\//)) {
+ text = text.join('\n');
+ text = text.replace(/^\n/, '');
+ if (text.match(/@ngdoc/)){
+ callback(text, startingLine);
+ }
+ doc = null;
+ inDoc = false;
+ }
+ // is the comment add text
+ if (inDoc){
+ text.push(line.replace(/^\s*\*\s?/, ''));
+ }
+ });
+ callback.done();
+ }));
+}
+
+
+
+exports.collect = collect; \ No newline at end of file
diff --git a/docs/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>
- &lt;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>&#123;{{#type}}{{type}}{{/type}}{{^type}}*{{/type}}{{#optional}}={{/optional}}&#125;</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>
-&lt;{{shortName}}{{#param}} {{#default}}<i>[</i>{{/default}}{{name}}="..."{{#default}}<i>]</i>{{/default}}{{/param}}&gt;{{#usageContent}}
-
- {{usageContent}}
-{{/usageContent}}&lt;/{{shortName}}&gt;
- </pre>
-{{/element}}
-{{#element}}
- <pre>
-&lt;{{element}} {{shortName}}{{#paramFirst}}="{{paramFirst.name}}{{/paramFirst}}"&gt;
- ...
-&lt;/{{element}}&gt;
- </pre>
-{{/element}}
-</tt>
-
-<h3>Parameters</h3>
-<ul>
- {{#param}}
- <li><tt>{{name}}</tt> –
- <tt>&#123;{{#type}}{{type}}{{/type}}{{^type}}*{{/type}}{{#optional}}={{/optional}}&#125;</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/gen_docs.sh b/gen_docs.sh
index c72d8027..17d94a79 100755
--- a/gen_docs.sh
+++ b/gen_docs.sh
@@ -1,3 +1,3 @@
-#!/bin/sh
-
-node docs/specs.js --noColor && node docs/collect.js
+#!/bin/bash
+. ~/.bashrc
+node docs/spec/specs.js --noColor && node docs/src/gen-docs.js
diff --git a/lib/mustache/LICENSE b/lib/mustache/LICENSE
deleted file mode 100644
index e06f8eea..00000000
--- a/lib/mustache/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-Copyright (c) 2009 Chris Wanstrath (Ruby)
-Copyright (c) 2010 Jan Lehnardt (JavaScript)
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file
diff --git a/lib/mustache/index.js b/lib/mustache/index.js
deleted file mode 100644
index 292db14d..00000000
--- a/lib/mustache/index.js
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * CommonJS-compatible mustache.js module
- *
- * See http://github.com/janl/mustache.js for more info.
- */
-/*
- mustache.js Ñ Logic-less templates in JavaScript
-
- See http://mustache.github.com/ for more info.
-*/
-
-var Mustache = function() {
- var Renderer = function() {};
-
- Renderer.prototype = {
- otag: "{{",
- ctag: "}}",
- pragmas: {},
- buffer: [],
- pragmas_implemented: {
- "IMPLICIT-ITERATOR": true
- },
- context: {},
-
- render: function(template, context, partials, in_recursion) {
- // reset buffer & set context
- if(!in_recursion) {
- this.context = context;
- this.buffer = []; // TODO: make this non-lazy
- }
-
- // fail fast
- if(!this.includes("", template)) {
- if(in_recursion) {
- return template;
- } else {
- this.send(template);
- return;
- }
- }
-
- template = this.render_pragmas(template);
- var html = this.render_section(template, context, partials);
- if(in_recursion) {
- return this.render_tags(html, context, partials, in_recursion);
- }
-
- this.render_tags(html, context, partials, in_recursion);
- },
-
- /*
- Sends parsed lines
- */
- send: function(line) {
- if(line != "") {
- this.buffer.push(line);
- }
- },
-
- /*
- Looks for %PRAGMAS
- */
- render_pragmas: function(template) {
- // no pragmas
- if(!this.includes("%", template)) {
- return template;
- }
-
- var that = this;
- var regex = new RegExp(this.otag + "%([\\w-]+) ?([\\w]+=[\\w]+)?" +
- this.ctag);
- return template.replace(regex, function(match, pragma, options) {
- if(!that.pragmas_implemented[pragma]) {
- throw({message:
- "This implementation of mustache doesn't understand the '" +
- pragma + "' pragma"});
- }
- that.pragmas[pragma] = {};
- if(options) {
- var opts = options.split("=");
- that.pragmas[pragma][opts[0]] = opts[1];
- }
- return "";
- // ignore unknown pragmas silently
- });
- },
-
- /*
- Tries to find a partial in the curent scope and render it
- */
- render_partial: function(name, context, partials) {
- name = this.trim(name);
- if(!partials || partials[name] === undefined) {
- throw({message: "unknown_partial '" + name + "'"});
- }
- if(typeof(context[name]) != "object") {
- return this.render(partials[name], context, partials, true);
- }
- return this.render(partials[name], context[name], partials, true);
- },
-
- /*
- Renders inverted (^) and normal (#) sections
- */
- render_section: function(template, context, partials) {
- if(!this.includes("#", template) && !this.includes("^", template)) {
- return template;
- }
-
- var that = this;
- // CSW - Added "+?" so it finds the tighest bound, not the widest
- var regex = new RegExp(this.otag + "(\\^|\\#)\\s*(.+)\\s*" + this.ctag +
- "\n*([\\s\\S]+?)" + this.otag + "\\/\\s*\\2\\s*" + this.ctag +
- "\\s*", "mg");
-
- // for each {{#foo}}{{/foo}} section do...
- return template.replace(regex, function(match, type, name, content) {
- var value = that.find(name, context);
- if(type == "^") { // inverted section
- if(!value || that.is_array(value) && value.length === 0) {
- // false or empty list, render it
- return that.render(content, context, partials, true);
- } else {
- return "";
- }
- } else if(type == "#") { // normal section
- if(that.is_array(value)) { // Enumerable, Let's loop!
- return that.map(value, function(row) {
- return that.render(content, that.create_context(row),
- partials, true);
- }).join("");
- } else if(that.is_object(value)) { // Object, Use it as subcontext!
- return that.render(content, that.create_context(value),
- partials, true);
- } else if(typeof value === "function") {
- // higher order section
- return value.call(context, content, function(text) {
- return that.render(text, context, partials, true);
- });
- } else if(value) { // boolean section
- return that.render(content, context, partials, true);
- } else {
- return "";
- }
- }
- });
- },
-
- /*
- Replace {{foo}} and friends with values from our view
- */
- render_tags: function(template, context, partials, in_recursion) {
- // tit for tat
- var that = this;
-
- var new_regex = function() {
- return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\\/#\\^]+?)\\1?" +
- that.ctag + "+", "g");
- };
-
- var regex = new_regex();
- var tag_replace_callback = function(match, operator, name) {
- switch(operator) {
- case "!": // ignore comments
- return "";
- case "=": // set new delimiters, rebuild the replace regexp
- that.set_delimiters(name);
- regex = new_regex();
- return "";
- case ">": // render partial
- return that.render_partial(name, context, partials);
- case "{": // the triple mustache is unescaped
- return that.find(name, context);
- default: // escape the value
- return that.escape(that.find(name, context));
- }
- };
- var lines = template.split("\n");
- for(var i = 0; i < lines.length; i++) {
- lines[i] = lines[i].replace(regex, tag_replace_callback, this);
- if(!in_recursion) {
- this.send(lines[i]);
- }
- }
-
- if(in_recursion) {
- return lines.join("\n");
- }
- },
-
- set_delimiters: function(delimiters) {
- var dels = delimiters.split(" ");
- this.otag = this.escape_regex(dels[0]);
- this.ctag = this.escape_regex(dels[1]);
- },
-
- escape_regex: function(text) {
- // thank you Simon Willison
- if(!arguments.callee.sRE) {
- var specials = [
- '/', '.', '*', '+', '?', '|',
- '(', ')', '[', ']', '{', '}', '\\'
- ];
- arguments.callee.sRE = new RegExp(
- '(\\' + specials.join('|\\') + ')', 'g'
- );
- }
- return text.replace(arguments.callee.sRE, '\\$1');
- },
-
- /*
- find `name` in current `context`. That is find me a value
- from the view object
- */
- find: function(name, context) {
- name = this.trim(name);
-
- // Checks whether a value is thruthy or false or 0
- function is_kinda_truthy(bool) {
- return bool === false || bool === 0 || bool;
- }
-
- var value = context;
- var path = name.split(/\./);
- for(var i = 0; i < path.length; i++) {
- name = path[i];
- if(value && is_kinda_truthy(value[name])) {
- value = value[name];
- } else if(i == 0 && is_kinda_truthy(this.context[name])) {
- value = this.context[name];
- } else {
- value = undefined;
- }
- }
-
- if(typeof value === "function") {
- return value.apply(context);
- }
- if(value !== undefined) {
- return value;
- }
- // silently ignore unkown variables
- return "";
- },
-
- // Utility methods
-
- /* includes tag */
- includes: function(needle, haystack) {
- return haystack.indexOf(this.otag + needle) != -1;
- },
-
- /*
- Does away with nasty characters
- */
- escape: function(s) {
- s = String(s === null ? "" : s);
- return s.replace(/&(?!\w+;)|["'<>\\]/g, function(s) {
- switch(s) {
- case "&": return "&amp;";
- case "\\": return "\\\\";
- case '"': return '&quot;';
- case "'": return '&#39;';
- case "<": return "&lt;";
- case ">": return "&gt;";
- default: return s;
- }
- });
- },
-
- // by @langalex, support for arrays of strings
- create_context: function(_context) {
- if(this.is_object(_context)) {
- return _context;
- } else {
- var iterator = ".";
- if(this.pragmas["IMPLICIT-ITERATOR"]) {
- iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator;
- }
- var ctx = {};
- ctx[iterator] = _context;
- return ctx;
- }
- },
-
- is_object: function(a) {
- return a && typeof a == "object";
- },
-
- is_array: function(a) {
- return Object.prototype.toString.call(a) === '[object Array]';
- },
-
- /*
- Gets rid of leading and trailing whitespace
- */
- trim: function(s) {
- return s.replace(/^\s*|\s*$/g, "");
- },
-
- /*
- Why, why, why? Because IE. Cry, cry cry.
- */
- map: function(array, fn) {
- if (typeof array.map == "function") {
- return array.map(fn);
- } else {
- var r = [];
- var l = array.length;
- for(var i = 0; i < l; i++) {
- r.push(fn(array[i]));
- }
- return r;
- }
- }
- };
-
- return({
- name: "mustache.js",
- version: "0.3.1-dev",
-
- /*
- Turns a template and view into HTML
- */
- to_html: function(template, view, partials, send_fun) {
- var renderer = new Renderer();
- if(send_fun) {
- renderer.send = send_fun;
- }
- renderer.render(template, view, partials);
- if(!send_fun) {
- return renderer.buffer.join("\n");
- }
- }
- });
-}();
-
-
-exports.name = Mustache.name;
-exports.version = Mustache.version;
-
-exports.to_html = function() {
- return Mustache.to_html.apply(this, arguments);
-}; \ No newline at end of file
diff --git a/src/Angular.js b/src/Angular.js
index 59b031cc..00e1d2a2 100644
--- a/src/Angular.js
+++ b/src/Angular.js
@@ -711,7 +711,7 @@ function concat(array1, array2, index) {
*/
function bind(self, fn) {
var curryArgs = arguments.length > 2 ? slice.call(arguments, 2, arguments.length) : [];
- if (typeof fn == $function) {
+ if (typeof fn == $function && !(fn instanceof RegExp)) {
return curryArgs.length ? function() {
return arguments.length ? fn.apply(self, curryArgs.concat(slice.call(arguments, 0, arguments.length))) : fn.apply(self, curryArgs);
}: function() {
diff --git a/src/validators.js b/src/validators.js
index 8030c0d0..e9a5feb9 100644
--- a/src/validators.js
+++ b/src/validators.js
@@ -9,13 +9,19 @@ extend(angularValidator, {
* Use regexp validator to restrict the input to any Regular Expression.
*
* @param {string} value value to validate
- * @param {regexp} expression regular expression.
+ * @param {string|regexp} expression regular expression.
+ * @param {string=} msg error message to display.
* @css ng-validation-error
*
* @example
- * <script> var ssn = /^\d\d\d-\d\d-\d\d\d\d$/; </script>
+ * <script> function Cntl(){
+ * this.ssnRegExp = /^\d\d\d-\d\d-\d\d\d\d$/;
+ * }
+ * </script>
* Enter valid SSN:
- * <input name="ssn" value="123-45-6789" ng:validate="regexp:$window.ssn" >
+ * <div ng:controller="Cntl">
+ * <input name="ssn" value="123-45-6789" ng:validate="regexp:ssnRegExp" >
+ * </div>
*
* @scenario
* it('should invalidate non ssn', function(){
diff --git a/src/widgets.js b/src/widgets.js
index 5f159990..473b6f1f 100644
--- a/src/widgets.js
+++ b/src/widgets.js
@@ -554,7 +554,8 @@ angularWidget('option', function(){
* (e.g. ng:include won't work for file:// access).
*
* @param {string} src expression evaluating to URL.
- * @param {Scope=} [scope=new_child_scope] expression evaluating to angular.scope
+ * @param {Scope=} [scope=new_child_scope] optional expression which evaluates to an
+ * instance of angular.scope to set the HTML fragment to.
* @param {string=} onload Expression to evaluate when a new partial is loaded.
*
* @example