complete rewrite of documentation generation

- romeved mustache.js
- unified templates
- improved testability of the code
This commit is contained in:
Misko Hevery
2010-12-23 00:44:27 +01:00
parent aab3df7aea
commit 4f22d6866c
33 changed files with 1271 additions and 1071 deletions

View File

@@ -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}}

View File

@@ -1 +0,0 @@
NG_PAGES={{{JSON}}};

View File

@@ -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}}

View File

@@ -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}}

View File

@@ -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}}

View File

@@ -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}}

View File

@@ -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;
}

257
docs/spec/ngdocSpec.js Normal file
View File

@@ -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.');
});
});
});
});

39
docs/spec/specs.js Normal file
View File

@@ -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);

18
docs/spec/writerSpec.js Normal file
View File

@@ -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{}');
});
});
});

View File

@@ -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);

View File

@@ -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');

123
docs/src/dom.js Normal file
View File

@@ -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');
});
});
}
};

42
docs/src/gen-docs.js Normal file
View File

@@ -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(); }

0
docs/src/ignore.words Normal file
View File

614
docs/src/ngdoc.js Normal file
View File

@@ -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];
};
}

91
docs/src/reader.js Normal file
View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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">

61
docs/src/writer.js Normal file
View File

@@ -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);
});
};

View File

@@ -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}}

View File

@@ -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}}

View File

@@ -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

View File

@@ -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.

View File

@@ -1,344 +0,0 @@
/*
* CommonJS-compatible mustache.js module
*
* See http://github.com/janl/mustache.js for more info.
*/
/*
mustache.js <20> 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);
};

View File

@@ -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() {

View File

@@ -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(){

View File

@@ -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