From 4f22d6866c052fb5b770ce4f377cecacacd9e6d8 Mon Sep 17 00:00:00 2001 From: Misko Hevery Date: Thu, 23 Dec 2010 00:44:27 +0100 Subject: complete rewrite of documentation generation - romeved mustache.js - unified templates - improved testability of the code --- docs/callback.js | 66 ---- docs/directive.template | 58 ---- docs/doc_widgets.css | 35 -- docs/doc_widgets.js | 71 ---- docs/docs-data.js | 1 - docs/docs-scenario.html | 10 - docs/docs-scenario.js | 9 - docs/docs.css | 256 -------------- docs/docs.js | 47 --- docs/filter.template | 65 ---- docs/formatter.template | 53 --- docs/function.template | 52 --- docs/index.html | 45 --- docs/overview.template | 31 -- docs/spec/collectSpec.js | 288 ---------------- docs/spec/ngdocSpec.js | 257 ++++++++++++++ docs/spec/specs.js | 39 +++ docs/spec/writerSpec.js | 18 + docs/specs.js | 21 -- docs/src/callback.js | 69 ++++ docs/src/dom.js | 123 +++++++ docs/src/gen-docs.js | 42 +++ docs/src/ignore.words | 0 docs/src/ngdoc.js | 614 ++++++++++++++++++++++++++++++++++ docs/src/reader.js | 91 +++++ docs/src/templates/doc_widgets.css | 35 ++ docs/src/templates/doc_widgets.js | 71 ++++ docs/src/templates/docs-scenario.html | 10 + docs/src/templates/docs-scenario.js | 9 + docs/src/templates/docs.css | 262 +++++++++++++++ docs/src/templates/docs.js | 47 +++ docs/src/templates/index.html | 45 +++ docs/src/writer.js | 61 ++++ docs/validator.template | 59 ---- docs/widget.template | 68 ---- 35 files changed, 1793 insertions(+), 1235 deletions(-) delete mode 100644 docs/callback.js delete mode 100644 docs/directive.template delete mode 100644 docs/doc_widgets.css delete mode 100644 docs/doc_widgets.js delete mode 100644 docs/docs-data.js delete mode 100644 docs/docs-scenario.html delete mode 100644 docs/docs-scenario.js delete mode 100644 docs/docs.css delete mode 100644 docs/docs.js delete mode 100644 docs/filter.template delete mode 100644 docs/formatter.template delete mode 100644 docs/function.template delete mode 100644 docs/index.html delete mode 100644 docs/overview.template delete mode 100644 docs/spec/collectSpec.js create mode 100644 docs/spec/ngdocSpec.js create mode 100644 docs/spec/specs.js create mode 100644 docs/spec/writerSpec.js delete mode 100644 docs/specs.js create mode 100644 docs/src/callback.js create mode 100644 docs/src/dom.js create mode 100644 docs/src/gen-docs.js create mode 100644 docs/src/ignore.words create mode 100644 docs/src/ngdoc.js create mode 100644 docs/src/reader.js create mode 100644 docs/src/templates/doc_widgets.css create mode 100644 docs/src/templates/doc_widgets.js create mode 100644 docs/src/templates/docs-scenario.html create mode 100644 docs/src/templates/docs-scenario.js create mode 100644 docs/src/templates/docs.css create mode 100644 docs/src/templates/docs.js create mode 100644 docs/src/templates/index.html create mode 100644 docs/src/writer.js delete mode 100644 docs/validator.template delete mode 100644 docs/widget.template (limited to 'docs') diff --git a/docs/callback.js b/docs/callback.js deleted file mode 100644 index 0d0669d1..00000000 --- a/docs/callback.js +++ /dev/null @@ -1,66 +0,0 @@ -function noop(){} - -function chain(delegateFn, explicitDone){ - var onDoneFn = noop; - var onErrorFn = noop; - var waitForCount = 1; - delegateFn = delegateFn || noop; - var stackError = new Error('capture stack'); - - function decrementWaitFor() { - waitForCount--; - if (waitForCount == 0) - onDoneFn(); - } - - function self(){ - try { - return delegateFn.apply(self, arguments); - } catch (error) { - self.error(error); - } finally { - if (!explicitDone) - decrementWaitFor(); - } - }; - self.onDone = function(callback){ - onDoneFn = callback; - return self; - }; - self.onError = function(callback){ - onErrorFn = callback; - return self; - }; - self.waitFor = function(callback){ - if (waitForCount == 0) - throw new Error("Can not wait on already called callback."); - waitForCount++; - return chain(callback).onDone(decrementWaitFor).onError(self.error); - }; - - self.waitMany = function(callback){ - if (waitForCount == 0) - throw new Error("Can not wait on already called callback."); - waitForCount++; - return chain(callback, true).onDone(decrementWaitFor).onError(self.error); - }; - - self.done = function(callback){ - decrementWaitFor(); - }; - - self.error = function(error) { - var stack = stackError.stack.split(/\n\r?/).splice(2); - var nakedStack = []; - stack.forEach(function(frame){ - if (!frame.match(/callback\.js:\d+:\d+\)$/)) - nakedStack.push(frame); - }); - error.stack = error.stack + '\nCalled from:\n' + nakedStack.join('\n'); - onErrorFn(error); - }; - - return self; -} - -exports.chain = chain; diff --git a/docs/directive.template b/docs/directive.template deleted file mode 100644 index 07e38ea7..00000000 --- a/docs/directive.template +++ /dev/null @@ -1,58 +0,0 @@ -

{{name}}

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

Description

-{{{description}}} - -

Usage

-

In HTML Template Binding

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

Parameters

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

CSS

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

Example

-{{{exampleDescription}}} - - -{{/example}} - {{{example}}} -{{#example}} - - {{{scenario}}} - -{{/example}} diff --git a/docs/doc_widgets.css b/docs/doc_widgets.css deleted file mode 100644 index 8361f105..00000000 --- a/docs/doc_widgets.css +++ /dev/null @@ -1,35 +0,0 @@ -@namespace doc url("http://docs.angularjs.org/"); - -doc\:example { - display: none; -} - -ul.doc-example { - list-style-type: none; - position: relative; - font-size: 14px; -} - -ul.doc-example > li { - border: 2px solid gray; - border-radius: 5px; - -moz-border-radius: 5px; - -webkit-border-radius: 5px; - background-color: white; - margin-bottom: 20px; -} - -ul.doc-example > li.doc-example-heading { - border: none; - border-radius: none; - margin-bottom: -10px; -} - -li.doc-example-live { - padding: 10px; - font-size: 1.2em; -} - -div.syntaxhighlighter { - padding-bottom: 1px !important; /* fix to remove unnecessary scrollbars http://is.gd/gSMgC */ -} \ No newline at end of file diff --git a/docs/doc_widgets.js b/docs/doc_widgets.js deleted file mode 100644 index b119e326..00000000 --- a/docs/doc_widgets.js +++ /dev/null @@ -1,71 +0,0 @@ -(function(){ - - var angularJsUrl; - var scripts = document.getElementsByTagName("script"); - var filename = /(.*\/)angular([^\/]*)/; - for(var j = 0; j < scripts.length; j++) { - var src = scripts[j].src; - if (src && src.match(filename)) { - angularJsUrl = src; - } - } - - - var HTML_TEMPLATE = - '\n' + - '\n' + - ' \n' + - ' \n' + - '_HTML_SOURCE_\n' + - ' \n' + - ''; - - angular.widget('doc:example', function(element){ - this.descend(true); //compile the example code - element.hide(); - - var example = element.find('doc\\:source').eq(0), - exampleSrc = example.text(), - scenario = element.find('doc\\:scenario').eq(0); - - var code = indent(exampleSrc); - var tabs = angular.element( - ''); - - tabs.find('li.doc-example-source > pre').text(HTML_TEMPLATE.replace('_HTML_SOURCE_', code.html)); - - element.html(''); - element.append(tabs); - element.show(); - - var script = (exampleSrc.match(/]*>([\s\S]*)<\/script>/) || [])[1] || ''; - try { - eval(script); - } catch (e) { - alert(e); - } - }); - - function indent(text) { - var lines = text.split(/\n/); - var lineNo = []; - while (lines[0].match(/^\s*$/)) lines.shift(); - while (lines[lines.length - 1].match(/^\s*$/)) lines.pop(); - for ( var i = 0; i < lines.length; i++) { - lines[i] = ' ' + lines[i]; - lineNo.push(6 + i); - } - return {html: lines.join('\n'), hilite: lineNo.join(',') }; - }; - -})(); \ No newline at end of file diff --git a/docs/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/docs-scenario.html b/docs/docs-scenario.html deleted file mode 100644 index c75155c5..00000000 --- a/docs/docs-scenario.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - <angular/> Docs Scenario Runner - - - - - - \ No newline at end of file diff --git a/docs/docs-scenario.js b/docs/docs-scenario.js deleted file mode 100644 index 2ee8387b..00000000 --- a/docs/docs-scenario.js +++ /dev/null @@ -1,9 +0,0 @@ -{{#pages}} -describe('{{name}}', function(){ - beforeEach(function(){ - browser().navigateTo('index.html#!{{name}}'); - }); - // {{raw.file}}:{{raw.line}} -{{{scenario}}} -}); -{{/pages}} diff --git a/docs/docs.css b/docs/docs.css deleted file mode 100644 index 21e9d6ed..00000000 --- a/docs/docs.css +++ /dev/null @@ -1,256 +0,0 @@ -/* Common Style */ - -body { - font-family: Arial, sans-serif; - font-size: 14px; - margin: 0; - padding: 0; -} - -a { - color: blue; -} - - -/* Main Layout */ - -#header { - height: 3.5em; -} - -#sidebar, -#main { - position: absolute; - top: 3.5em; - bottom: 0; - margin-top: 1px; - overflow-x: hidden; -} - -#sidebar { - width: 13.8em; - padding: 0.8em 0em 1.5em 0.8em; -} - -#main { - left: 14.6em; - right: 0; - padding: 1em; - overflow-y: scroll; -} - -#api-list { - position: absolute; - top: 3em; - bottom: 1em; - overflow-y: scroll; - padding-right: 0.8em; -} - - -/* App Header */ - -#header { - background-color: #F2C200; - border-bottom: 1px solid #957800; -} - -#header h1 { - font-weight: normal; - font-size: 30px; - line-height: 30px; - margin: 0; - padding: 10px 10px; - height: 30px; -} - -#header .angular { - font-family: "Courier New", monospace; - font-weight: bold; -} - -#header h1 a { - color: black; - text-decoration: none; -} - -#header h1 a:hover { - text-decoration: underline; -} - - -/* Main main Style */ - -#main h1 { - font-family: monospace; - margin-top: 0; - padding-bottom: 5px; - border-bottom: 1px solid #CCC; -} - -#main h2 { - margin-top: 1.8em; -} - -#main h1 + h2 { - margin-top: 1.3em; -} - -#main h3 { - margin-top: 1.5em; -} - -.main-title { - float: right; -} - - -/* Searchbox & Sidebar Style */ - -#search-box, #sidebar { - border-right: 1px solid #DDD; -} - -#sidebar { - background-color: #EEE; -} - -#search-box { - width: 16em; - margin-bottom: 1em; -} - -#sidebar a { - text-decoration: none; -} - -#sidebar a:hover { - text-decoration: underline; -} - -#sidebar ul { - list-style-type: none; - /*TODO(esprehn): Can we just reset globally and not break examples?*/ - margin: 0; - padding: 0 0.8em 0 0; - width: 13em; -} - -#sidebar ul li { -} - -#sidebar ul li a { - display: block; - padding: 2px 2px 2px 4px; -} - -#sidebar ul li.selected a { - background-color: #DDD; - border-radius: 5px; - -moz-border-radius: 5px; - border: 1px solid #CCC; - padding: 1px 1px 1px 3px; -} - -#sidebar ul li.level-0 { - margin-left: 0em; - font-weight: bold; - font-size: 1.2em; -} - -#sidebar ul li.level-1.level-angular { - font-family: monospace; - font-weight: normal; - font-size: 1em; - margin-top: 0; - margin-bottom: 0; -} - -#sidebar ul li.level-1 { - margin-left: 1em; - margin-top: 5px; - font-size: 1.1em; - font-weight: bold; -} - -#sidebar ul li.level-2 { - margin-left: 2em; - font-family: monospace; -} - -#sidebar ul li.level-3 { - margin-left: 3em; - font-family: monospace; -} - - -/* Warning and Info Banners */ - -.deprecated { - border: 2px solid red; -} - -.deprecated legend { - font-weight: bold; - color: red; -} - -.workInProgress { - border: 2px solid orange; -} - -.workInProgress legend { - font-weight: bold; - color: orange; -} - - -/* Feedback Link */ - -#feedback { - float: right; - width: 10em; - text-align: right; -} - - -/* Live Example Style */ - -.doc-example-live table td { - padding: 0 1.5em; -} - - -/* Scrollbars */ - -::-webkit-scrollbar{ - width:0.8em; - margin: 0.2em 0em; -} - -::-webkit-scrollbar:hover{ - background-color:#eee; -} - -::-webkit-scrollbar-thumb{ - min-height:0.8em; - min-width:0.8em; - -webkit-border-radius:0.5em; - background-color: #ddd; -} - -::-webkit-scrollbar-thumb:hover{ - background-color: #bbb; -} - -::-webkit-scrollbar-thumb:active{ - background-color:#888; -} - -#sidebar::-webkit-scrollbar { - background-color:#eee; -} - -#main::-webkit-scrollbar { - background-color:#fff; -} diff --git a/docs/docs.js b/docs/docs.js deleted file mode 100644 index 6bf86ed3..00000000 --- a/docs/docs.js +++ /dev/null @@ -1,47 +0,0 @@ -DocsController.$inject = ['$location', '$browser', '$window']; -function DocsController($location, $browser, $window) { - this.pages = NG_PAGES; - window.$root = this.$root; - - this.getUrl = function(page){ - return '#!' + page.name; - }; - - this.getCurrentPartial = function(){ - return './' + this.getTitle() + '.html'; - }; - - this.getTitle = function(){ - var hashPath = $location.hashPath || '!angular'; - if (hashPath.match(/^!angular/)) { - this.partialTitle = hashPath.substring(1); - } - return this.partialTitle; - }; - - this.getClass = function(page) { - var depth = page.name.split(/\./).length - 1, - cssClass = 'level-' + depth + (page.name == this.getTitle() ? ' selected' : ''); - - if (depth == 1 && page.type !== 'overview') cssClass += ' level-angular'; - - return cssClass; - }; - - this.afterPartialLoaded = function() { - SyntaxHighlighter.highlight(); - }; - - this.getFeedbackUrl = function() { - return "mailto:angular@googlegroups.com?" + - "subject=" + escape("Feedback on " + $location.href) + "&" + - "body=" + escape("Hi there,\n\nI read " + $location.href + " and wanted to ask ...."); - }; - -} - -angular.filter('short', function(name){ - return (name||'').split(/\./).pop(); -}); - -SyntaxHighlighter['defaults'].toolbar = false; diff --git a/docs/filter.template b/docs/filter.template deleted file mode 100644 index 516957d0..00000000 --- a/docs/filter.template +++ /dev/null @@ -1,65 +0,0 @@ -

{{name}}

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

Description

-{{{description}}} - -

Usage

-

In HTML Template Binding

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

In JavaScript

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

Parameters

- - -{{#returns}} -

Returns

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

CSS

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

Example

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

{{name}}

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

Description

-{{{description}}} - -

Usage

-

In HTML Template Binding

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

In JavaScript

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

Returns

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

CSS

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

Example

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

{{name}}

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

Description

-{{{description}}} - -

Usage

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

Parameters

- - -{{#returns}} -

Returns

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

Example

-{{{exampleDescription}}} - - -{{/example}} - {{{example}}} -{{#example}} - - {{{scenario}}} - -{{/example}} diff --git a/docs/index.html b/docs/index.html deleted file mode 100644 index 1722e98a..00000000 --- a/docs/index.html +++ /dev/null @@ -1,45 +0,0 @@ - - - - <angular/> - - - - - - - - - - - - - - - - - - - - - -
- Report an Issue or Ask a Question - -
- - diff --git a/docs/overview.template b/docs/overview.template deleted file mode 100644 index 9902a16f..00000000 --- a/docs/overview.template +++ /dev/null @@ -1,31 +0,0 @@ -

{{name}}

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

Example

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

<angular/>

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

bah x

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

asdf x

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

One

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

foo

' + - '
abc
' + - '

bah

\n\n' + - '

foo

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

foo angular.foo

\n\n' + - '

da bar foo bar

\n\n' + - '

dadangular.foo

\n\n' + - '

ng:foo

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

foo

\n\n

abc

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

my description

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

<angular/>

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

bah x

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

asdf x

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

One

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

foo

' + + '
abc
' + + '

bah

\n\n' + + '

foo

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

foo angular.foo

\n\n' + + '

da bar foo bar

\n\n' + + '

dadangular.foo

\n\n' + + '

ng:foo

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

foo

\n\n

abc

\n\n

bar

\n\n

xyz

'); + }); + }); + + describe('@example', function(){ + it('should not remove {{}}', function(){ + var doc = new Doc('@example text {{ abc }}'); + doc.parse(); + expect(doc.example).toEqual('text {{ abc }}'); + }); + }); + + describe('@deprecated', function() { + it('should parse @deprecated', function() { + var doc = new Doc('@deprecated Replaced with foo.'); + doc.parse(); + expect(doc.deprecated).toBe('Replaced with foo.'); + }); + }); + }); + +}); diff --git a/docs/spec/specs.js b/docs/spec/specs.js new file mode 100644 index 00000000..a6ba17a9 --- /dev/null +++ b/docs/spec/specs.js @@ -0,0 +1,39 @@ +if (global.jasmine) return; + +require.paths.push(__dirname + "/../../lib"); +require.paths.push(__dirname + '/../src'); +var jasmine = require('jasmine-1.0.1'); +var sys = require('util'); + +for(var key in jasmine) { + global[key] = jasmine[key]; +} + +//Patch Jasmine for proper stack traces +jasmine.Spec.prototype.fail = function (e) { + var expectationResult = new jasmine.ExpectationResult({ + passed: false, + message: e ? jasmine.util.formatException(e) : 'Exception' + }); + // PATCH + if (e) { + expectationResult.trace = e; + } + this.results_.addResult(expectationResult); +}; + + + +var isVerbose = false; +var showColors = true; +process.argv.forEach(function(arg){ + switch(arg) { + case '--color': showColors = true; break; + case '--noColor': showColors = false; break; + case '--verbose': isVerbose = true; break; + } +}); + +jasmine.executeSpecsInFolder(__dirname, function(runner, log){ + process.exit(runner.results().failedCount); +}, isVerbose, showColors); diff --git a/docs/spec/writerSpec.js b/docs/spec/writerSpec.js new file mode 100644 index 00000000..1a722ca6 --- /dev/null +++ b/docs/spec/writerSpec.js @@ -0,0 +1,18 @@ +var writer = require('writer.js'); +describe('writer', function(){ + describe('toString', function(){ + var toString = writer.toString; + + it('should merge string', function(){ + expect(toString('abc')).toEqual('abc'); + }); + + it('should merge obj', function(){ + expect(toString({a:1})).toEqual('{"a":1}'); + }); + + it('should merge array', function(){ + expect(toString(['abc',{}])).toEqual('abc{}'); + }); + }); +}); \ No newline at end of file diff --git a/docs/specs.js b/docs/specs.js deleted file mode 100644 index d564b045..00000000 --- a/docs/specs.js +++ /dev/null @@ -1,21 +0,0 @@ -require.paths.push("./lib"); -var jasmine = require('jasmine-1.0.1'); -var sys = require('util'); - -for(var key in jasmine) { - global[key] = jasmine[key]; -} - -var isVerbose = false; -var showColors = true; -process.argv.forEach(function(arg){ - switch(arg) { - case '--color': showColors = true; break; - case '--noColor': showColors = false; break; - case '--verbose': isVerbose = true; break; - } -}); - -jasmine.executeSpecsInFolder(__dirname + '/spec', function(runner, log){ - process.exit(runner.results().failedCount); -}, isVerbose, showColors); \ No newline at end of file diff --git a/docs/src/callback.js b/docs/src/callback.js new file mode 100644 index 00000000..aaf69cde --- /dev/null +++ b/docs/src/callback.js @@ -0,0 +1,69 @@ +function noop(){} + +function chain(delegateFn, explicitDone){ + var onDoneFn = noop; + var onErrorFn = function(e){ + console.error(e.stack || e); + process.exit(-1); + }; + var waitForCount = 1; + delegateFn = delegateFn || noop; + var stackError = new Error('capture stack'); + + function decrementWaitFor() { + waitForCount--; + if (waitForCount == 0) + onDoneFn(); + } + + function self(){ + try { + return delegateFn.apply(self, arguments); + } catch (error) { + self.error(error); + } finally { + if (!explicitDone) + decrementWaitFor(); + } + }; + self.onDone = function(callback){ + onDoneFn = callback; + return self; + }; + self.onError = function(callback){ + onErrorFn = callback; + return self; + }; + self.waitFor = function(callback){ + if (waitForCount == 0) + throw new Error("Can not wait on already called callback."); + waitForCount++; + return chain(callback).onDone(decrementWaitFor).onError(self.error); + }; + + self.waitMany = function(callback){ + if (waitForCount == 0) + throw new Error("Can not wait on already called callback."); + waitForCount++; + return chain(callback, true).onDone(decrementWaitFor).onError(self.error); + }; + + self.done = function(callback){ + decrementWaitFor(); + }; + + self.error = function(error) { + var stack = stackError.stack.split(/\n\r?/).splice(2); + var nakedStack = []; + stack.forEach(function(frame){ + if (!frame.match(/callback\.js:\d+:\d+\)$/)) + nakedStack.push(frame); + }); + error.stack = error.stack + '\nCalled from:\n' + nakedStack.join('\n'); + onErrorFn(error); + }; + + return self; +} + +exports.chain = chain; diff --git a/docs/src/dom.js b/docs/src/dom.js new file mode 100644 index 00000000..e6dd09e6 --- /dev/null +++ b/docs/src/dom.js @@ -0,0 +1,123 @@ +/** + * DOM generation class + */ + +exports.DOM = DOM; + +////////////////////////////////////////////////////////// + +function DOM(){ + this.out = []; + this.headingDepth = 1; +} + +var INLINE_TAGS = { + i: true, + b: true +}; + +DOM.prototype = { + toString: function() { + return this.out.join(''); + }, + + text: function(content) { + if (typeof content == "string") { + this.out.push(content.replace(/&/g, '&').replace(//g, '>')); + } else if (typeof content == 'function') { + content.call(this, this); + } else if (content instanceof Array) { + this.ul(content); + } + }, + + html: function(html) { + if (html) { + this.out.push(html); + } + }, + + tag: function(name, attr, text) { + if (!text) { + text = attr; + attr = {}; + if (name == 'code') + attr['ng:non-bindable'] = ''; + } + this.out.push('<' + name); + for(var key in attr) { + this.out.push(" " + key + '="' + attr[key] + '"'); + } + this.out.push('>'); + this.text(text); + this.out.push(''); + if (!INLINE_TAGS[name]) + this.out.push('\n'); + }, + + code: function(text) { + this.tag('div', {'ng:non-bindable':''}, function(){ + this.tag('pre', {'class':"brush: js; html-script: true;"}, text); + }); + }, + + example: function(source, scenario) { + if (source || scenario) { + this.h('Example', function(){ + if (scenario === false) { + this.code(source); + } else { + this.tag('doc:example', function(){ + if (source) this.tag('doc:source', source); + if (scenario) this.tag('doc:scenario', scenario); + }); + } + }); + } + }, + + h: function(heading, content, fn){ + if (content==undefined || content && content.legth == 0) return; + this.tag('h' + this.headingDepth, heading); + this.headingDepth++; + if (content instanceof Array) { + this.ul(content, {'class': heading.toLowerCase()}, fn); + } else if (fn) { + fn.call(this, content); + } else { + this.text(content); + } + this.headingDepth--; + }, + + h1: function(attr, text) { + this.tag('h1', attr, text); + }, + + h2: function(attr, text) { + this.tag('h2', attr, text); + }, + + h3: function(attr, text) { + this.tag('h3', attr, text); + }, + + p: function(attr, text) { + this.tag('p', attr, text); + }, + + ul: function(list, attr, fn) { + if (typeof attr == 'function') { + fn = attr; + attr = {}; + } + this.tag('ul', attr, function(dom){ + list.forEach(function(item){ + dom.out.push('
  • '); + dom.text(fn ? fn(item) : item); + dom.out.push('
  • \n'); + }); + }); + } + +}; \ No newline at end of file diff --git a/docs/src/gen-docs.js b/docs/src/gen-docs.js new file mode 100644 index 00000000..b4e30a53 --- /dev/null +++ b/docs/src/gen-docs.js @@ -0,0 +1,42 @@ +require.paths.push(__dirname); +require.paths.push('lib'); +var reader = require('reader.js'), + ngdoc = require('ngdoc.js'), + writer = require('writer.js'), + callback = require('callback.js'); + +var docs = []; +var start; +var work = callback.chain(function(){ + start = now(); + console.log('Generating Angular Reference Documentation...'); + reader.collect(work.waitMany(function(text, file, line){ + var doc = new ngdoc.Doc(text, file, line); + docs.push(doc); + doc.parse(); + })); +}); +var writes = callback.chain(function(){ + ngdoc.merge(docs); + docs.forEach(function(doc){ + writer.output(doc.name + '.html', doc.html(), writes.waitFor()); + }); + var metadata = ngdoc.metadata(docs); + writer.output('docs-keywords.js', ['NG_PAGES=', JSON.stringify(metadata), ';'], writes.waitFor()); + writer.copy('index.html', writes.waitFor()); + writer.copy('docs.js', writes.waitFor()); + writer.copy('docs.css', writes.waitFor()); + writer.copy('doc_widgets.js', writes.waitFor()); + writer.copy('doc_widgets.css', writes.waitFor()); + writer.copy('docs-scenario.html', writes.waitFor()); + writer.output('docs-scenario.js', ngdoc.scenarios(docs), writes.waitFor()); +}); +writes.onDone(function(){ + console.log('DONE. Generated ' + docs.length + ' pages in ' + + (now()-start) + 'ms.' ); +}); +work.onDone(writes); +writer.makeDir('build/docs', work); + +/////////////////////////////////// +function now(){ return new Date().getTime(); } diff --git a/docs/src/ignore.words b/docs/src/ignore.words new file mode 100644 index 00000000..e69de29b diff --git a/docs/src/ngdoc.js b/docs/src/ngdoc.js new file mode 100644 index 00000000..ac7d0bb1 --- /dev/null +++ b/docs/src/ngdoc.js @@ -0,0 +1,614 @@ +/** + * All parsing/transformation code goes here. All code here should be sync to ease testing. + */ + +var Showdown = require('showdown').Showdown; +var DOM = require('dom.js').DOM; +var NEW_LINE = /\n\r?/; + +exports.markdown = markdown; +exports.markdownNoP = markdownNoP; +exports.trim = trim; +exports.metadata = metadata; +exports.scenarios = scenarios; +exports.merge = merge; +exports.Doc = Doc; + +////////////////////////////////////////////////////////// +function Doc(text, file, line) { + if (typeof text == 'object') { + for ( var key in text) { + this[key] = text[key]; + } + } else { + this.text = text; + this.file = file; + this.line = line; + } +} +Doc.METADATA_IGNORE = (function(){ + var words = require('fs').readFileSync(__dirname + '/ignore.words', 'utf8'); + return words.toString().split(/[,\s\n\r]+/gm); +})(); + + + +Doc.prototype = { + keywords: function keywords(){ + var keywords = {}; + Doc.METADATA_IGNORE.forEach(function(ignore){ keywords[ignore] = true; }); + var words = []; + var tokens = this.text.toLowerCase().split(/[,\.\`\'\"\s]+/mg); + tokens.forEach(function(key){ + var match = key.match(/^(([a-z]|ng\:)[\w\_\-]{2,})/); + if (match){ + key = match[1]; + if (!keywords[key]) { + keywords[key] = true; + words.push(key); + } + } + }); + words.sort(); + return words.join(' '); + }, + + parse: function(){ + var atName; + var atText; + var match; + var self = this; + self.text.split(NEW_LINE).forEach(function(line){ + if (match = line.match(/^\s*@(\w+)(\s+(.*))?/)) { + // we found @name ... + // if we have existing name + flush(); + atName = match[1]; + atText = []; + if(match[3]) atText.push(match[3]); + } else { + if (atName) { + atText.push(line); + } + } + }); + flush(); + this.shortName = (this.name || '').split(/[\.#]/).pop(); + this.description = markdown(this.description); + + function flush(){ + if (atName) { + var text = trim(atText.join('\n')); + if (atName == 'param') { + var match = text.match(/^{([^}=]+)(=)?}\s+(([^\s=]+)|\[(\S+)=([^\]]+)\])\s+(.*)/); + // 1 12 2 34 4 5 5 6 6 3 7 7 + if (!match) { + throw new Error("Not a valid 'param' format: " + text); + } + var param = { + name: match[5] || match[4], + description:markdownNoP(text.replace(match[0], match[7])), + type: match[1], + optional: !!match[2], + 'default':match[6] + }; + self.param = self.param || []; + self.param.push(param); + } else if (atName == 'returns') { + var match = text.match(/^{([^}=]+)}\s+(.*)/); + if (!match) { + throw new Error("Not a valid 'returns' format: " + text); + } + self.returns = { + type: match[1], + description: markdownNoP(text.replace(match[0], match[2])) + }; + } else if(atName == 'requires') { + self.requires = self.requires || []; + self.requires.push(text); + } else if(atName == 'property') { + var match = text.match(/^({(\S+)}\s*)?(\S+)(\s+(.*))?/); + if (!match) { + throw new Error("Not a valid 'property' format: " + text); + } + var property = { + type: match[2], + name: match[3], + description: match[5] || '' + }; + self.properties = self.properties || []; + self.properties.push(property); + } else { + self[atName] = text; + } + } + } + }, + + html: function(){ + var dom = new DOM(), + self = this; + + dom.h(this.name, function(){ + notice('workInProgress', 'Work in Progress', + 'This page is currently being revised. It might be incomplete or contain inaccuracies.'); + notice('depricated', 'Depricated API'); + dom.h('Description', self.description, html); + dom.h('Dependencies', self.requires); + + usage(); + + dom.h('Methods', self.methods, function(method){ + var signature = (method.param || []).map(property('name')); + dom.h(method.shortName + '(' + signature.join(', ') + ')', method, function(){ + dom.html(method.description); + method.html_usage_parameters(dom); + dom.example(method.example, false); + }); + }); + dom.h('Properties', self.properties, function(property){ + dom.h(property.name, function(){ + dom.text(property.description); + dom.example(property.example, false); + }); + }); + + dom.example(self.example, self.scenario); + }); + + return dom.toString(); + + ////////////////////////// + + function html(text){ + this.html(text); + } + + function usage(){ + (self['html_usage_' + self.ngdoc] || function(){ + throw new Error("Don't know how to format @ngdoc: " + self.ngdoc); + }).call(self, dom); + } + + function section(name, property, fn) { + var value = self[property]; + if (value) { + dom.h2(name); + if (typeof value == 'string') { + value = markdown(value) + '\n'; + fn ? fn(value) : dom.html(value); + } else if (value instanceof Array) { + dom.ul(value, fn); + } + } + } + + function notice(name, legend, msg){ + if (self[name] == undefined) return; + dom.tag('fieldset', {'class':name}, function(dom){ + dom.tag('legend', legend); + dom.text(msg); + }); + } + + }, + + html_usage_parameters: function(dom) { + dom.h('Parameters', this.param, function(param){ + dom.tag('code', function(){ + dom.text(param.name); + if (param.optional) { + dom.tag('i', function(){ + dom.text('(optional'); + if(param['default']) { + dom.text('=' + param['default']); + } + dom.text(')'); + }); + } + dom.text(' – {'); + dom.text(param.type); + dom.text('} – '); + }); + dom.html(param.description); + }); + }, + + html_usage_returns: function(dom) { + var self = this; + if (self.returns) { + dom.h('Returns', function(){ + dom.tag('code', self.returns.type); + dom.text('– '); + dom.html(self.returns.description); + }); + } + }, + + html_usage_function: function(dom){ + var self = this; + dom.h('Usage', function(){ + dom.code(function(){ + dom.text(self.name); + dom.text('('); + var first = true; + (self.param || []).forEach(function(param){ + if (first) { + first = false; + } else { + dom.text(', '); + } + dom.text(param.name); + }); + dom.text(');'); + }); + + self.html_usage_parameters(dom); + self.html_usage_returns(dom); + }); + }, + + html_usage_directive: function(dom){ + var self = this; + dom.h('Usage', function(){ + dom.tag('pre', {'class':"brush: js; html-script: true;"}, function(){ + dom.text('<' + self.element + ' '); + dom.text(self.shortName); + if (self.param) { + dom.text('="' + self.param[0].name + '"'); + } + dom.text('>\n ...\n'); + dom.text(''); + }); + self.html_usage_parameters(dom); + }); + }, + + html_usage_filter: function(dom){ + var self = this; + dom.h('Usage', function(){ + dom.h('In HTML Template Binding', function(){ + dom.tag('code', function(){ + dom.text('{{ '); + dom.text(self.shortName); + dom.text('_expression | '); + dom.text(self.shortName); + var first = true; + (self.param||[]).forEach(function(param){ + if (first) { + first = false; + } else { + if (param.optional) { + dom.tag('i', function(){ + dom.text('[:' + param.name + ']'); + }); + } else { + dom.text(':' + param.name); + } + } + }); + dom.text(' }}'); + }); + }); + + dom.h3('In JavaScript', function(){ + dom.tag('code', function(){ + dom.text('angular.filter.'); + dom.text(self.shortName); + dom.text('('); + var first = true; + (self.param||[]).forEach(function(param){ + if (first) { + first = false; + dom.text(param.name); + } else { + if (param.optional) { + dom.tag('i', function(){ + dom.text('[, ' + param.name + ']'); + }); + } else { + dom.text(', ' + param.name); + } + } + }); + dom.text(')'); + }); + }); + + self.html_usage_parameters(dom); + self.html_usage_returns(dom); + }); + }, + + html_usage_formatter: function(dom){ + var self = this; + dom.h('Usage', function(){ + dom.h('In HTML Template Binding', function(){ + dom.code(function(){ + dom.text(''); + }); + }); + + dom.h3('In JavaScript', function(){ + dom.code(function(){ + dom.text('var userInputString = angular.formatter.'); + dom.text(self.shortName); + dom.text('.format(modelValue);'); + }); + dom.html('
    '); + dom.code(function(){ + dom.text('var modelValue = angular.formatter.'); + dom.text(self.shortName); + dom.text('.parse(userInputString);'); + }); + }); + + self.html_usage_returns(dom); + }); + }, + + html_usage_validator: function(dom){ + var self = this; + dom.h('Usage', function(){ + dom.h('In HTML Template Binding', function(){ + dom.code(function(){ + dom.text(''); + }); + }); + + dom.h('In JavaScript', function(){ + dom.code(function(){ + dom.text('angular.validator.'); + dom.text(self.shortName); + dom.text('('); + var first = true; + (self.param||[]).forEach(function(param){ + if (first) { + first = false; + dom.text(param.name); + } else { + if (param.optional) { + dom.text('[, ' + param.name + ']'); + } else { + dom.text(', ' + param.name); + } + } + }); + dom.text(')'); + }); + }); + + self.html_usage_parameters(dom); + self.html_usage_returns(dom); + }); + }, + + html_usage_widget: function(dom){ + var self = this; + dom.h('Usage', function(){ + dom.h('In HTML Template Binding', function(){ + dom.code(function(){ + if (self.shortName.match(/^@/)) { + dom.text('<'); + dom.text(self.element); + dom.text(' '); + dom.text(self.shortName.substring(1)); + if (self.param) { + dom.text('="'); + dom.text(self.param[0].name); + dom.text('"'); + } + dom.text('>\n ...\n'); + } else { + dom.text('<'); + dom.text(self.shortName); + (self.param||[]).forEach(function(param){ + if (param.optional) { + dom.text(' [' + param.name + '="..."]'); + } else { + dom.text(' ' + param.name + '="..."'); + } + }); + dom.text('>'); + } + }); + }); + + self.html_usage_parameters(dom); + self.html_usage_returns(dom); + }); + }, + + html_usage_overview: function(dom){ + }, + + html_usage_service: function(dom){ + } + +}; +////////////////////////////////////////////////////////// + + +////////////////////////////////////////////////////////// +function markdown (text) { + if (!text) return text; + var parts = text.split(/(
    [\s\S]*?<\/pre>)/),
    +      match;
    +
    +  parts.forEach(function(text, i){
    +    if (text.match(/^
    /)) {
    +      text = text.
    +        replace(/^
    /, '
    ').
    +        replace(/<\/pre>/, '
    '); + } else { + text = text.replace(//gm, '<angular/>'); + text = new Showdown.converter().makeHtml(text.replace(/^#/gm, '###')); + + while (match = text.match(R_LINK)) { + text = text.replace(match[0], '' + + (match[4] || match[1]) + + ''); + } + } + parts[i] = text; + }); + return parts.join(''); +}; +var R_LINK = /{@link ([^\s}]+)((\s|\n)+(.+?))?\s*}/m; + // 1 123 3 4 42 +function markdownNoP(text) { + var lines = markdown(text).split(NEW_LINE); + var last = lines.length - 1; + lines[0] = lines[0].replace(/^

    /, ''); + lines[last] = lines[last].replace(/<\/p>$/, ''); + return lines.join('\n'); +} + + +////////////////////////////////////////////////////////// +function scenarios(docs){ + var specs = []; + docs.forEach(function(doc){ + if (doc.scenario) { + specs.push('describe("'); + specs.push(doc.name); + specs.push('", function(){\n'); + specs.push(' beforeEach(function(){\n'); + specs.push(' browser().navigateTo("index.html#!' + doc.name + '");'); + specs.push(' });\n\n'); + specs.push(doc.scenario); + specs.push('\n});\n\n'); + } + }); + return specs; +} + + +////////////////////////////////////////////////////////// +function metadata(docs){ + var words = []; + docs.forEach(function(doc){ + words.push({ + name:doc.name, + type: doc.ngdoc, + keywords:doc.keywords() + }); + }); + words.sort(keywordSort); + return words; +} + +function keywordSort(a,b){ + // supper ugly comparator that orders all utility methods and objects before all the other stuff + // like widgets, directives, services, etc. + // Mother of all beautiful code please forgive me for the sin that this code certainly is. + + if (a.name === b.name) return 0; + if (a.name === 'angular') return -1; + if (b.name === 'angular') return 1; + + function namespacedName(page) { + return (page.name.match(/\./g).length === 1 && page.type !== 'overview' ? '0' : '1') + page.name; + } + + var namespacedA = namespacedName(a), + namespacedB = namespacedName(b); + + return namespacedA < namespacedB ? -1 : 1; +} + + +////////////////////////////////////////////////////////// +function trim(text) { + var MAX = 9999; + var empty = RegExp.prototype.test.bind(/^\s*$/); + var lines = text.split('\n'); + var minIndent = MAX; + lines.forEach(function(line){ + minIndent = Math.min(minIndent, indent(line)); + }); + for ( var i = 0; i < lines.length; i++) { + lines[i] = lines[i].substring(minIndent); + } + // remove leading lines + while (empty(lines[0])) { + lines.shift(); + } + // remove trailing + while (empty(lines[lines.length - 1])) { + lines.pop(); + } + return lines.join('\n'); + + function indent(line) { + for(var i = 0; i < line.length; i++) { + if (line.charAt(i) != ' ') { + return i; + } + } + return MAX; + } +} + +////////////////////////////////////////////////////////// +function merge(docs){ + var byName = {}; + docs.forEach(function(doc){ + byName[doc.name] = doc; + }); + for(var i=0; i b.name ? 1 : 0); + } +} +////////////////////////////////////////////////////////// + +function property(name) { + return function(value){ + return value[name]; + }; +} \ No newline at end of file diff --git a/docs/src/reader.js b/docs/src/reader.js new file mode 100644 index 00000000..8f9f22c3 --- /dev/null +++ b/docs/src/reader.js @@ -0,0 +1,91 @@ +/** + * All reading related code here. This is so that we can separate the async code from sync code + * for testability + */ +require.paths.push(__dirname); +var fs = require('fs'), + callback = require('callback'); + +var NEW_LINE = /\n\r?/; + +function collect(callback){ + findJsFiles('src', callback.waitMany(function(file) { + //console.log('reading', file, '...'); + findNgDocInJsFile(file, callback.waitMany(function(doc, line) { + callback(doc, file, line); + })); + })); + findNgDocInDir('docs/', callback.waitMany(callback)); + callback.done(); +} + +function findJsFiles(dir, callback){ + fs.readdir(dir, callback.waitFor(function(err, files){ + if (err) return this.error(err); + files.forEach(function(file){ + var path = dir + '/' + file; + fs.lstat(path, callback.waitFor(function(err, stat){ + if (err) return this.error(err); + if (stat.isDirectory()) + findJsFiles(path, callback.waitMany(callback)); + else if (/\.js$/.test(path)) + callback(path); + })); + }); + callback.done(); + })); +} + +function findNgDocInDir(directory, docNotify) { + fs.readdir(directory, docNotify.waitFor(function(err, files){ + if (err) return this.error(err); + files.forEach(function(file){ + //console.log('reading', directory + file, '...'); + if (!file.match(/\.ngdoc$/)) return; + fs.readFile(directory + file, docNotify.waitFor(function(err, content){ + if (err) return this.error(err); + docNotify(content.toString(), directory + file, 1); + })); + }); + docNotify.done(); + })); +} + +function findNgDocInJsFile(file, callback) { + fs.readFile(file, callback.waitFor(function(err, content){ + var lines = content.toString().split(NEW_LINE); + var text; + var startingLine ; + var match; + var inDoc = false; + lines.forEach(function(line, lineNumber){ + lineNumber++; + // is the comment starting? + if (!inDoc && (match = line.match(/^\s*\/\*\*\s*(.*)$/))) { + line = match[1]; + inDoc = true; + text = []; + startingLine = lineNumber; + } + // are we done? + if (inDoc && line.match(/\*\//)) { + text = text.join('\n'); + text = text.replace(/^\n/, ''); + if (text.match(/@ngdoc/)){ + callback(text, startingLine); + } + doc = null; + inDoc = false; + } + // is the comment add text + if (inDoc){ + text.push(line.replace(/^\s*\*\s?/, '')); + } + }); + callback.done(); + })); +} + + + +exports.collect = collect; \ No newline at end of file diff --git a/docs/src/templates/doc_widgets.css b/docs/src/templates/doc_widgets.css new file mode 100644 index 00000000..8361f105 --- /dev/null +++ b/docs/src/templates/doc_widgets.css @@ -0,0 +1,35 @@ +@namespace doc url("http://docs.angularjs.org/"); + +doc\:example { + display: none; +} + +ul.doc-example { + list-style-type: none; + position: relative; + font-size: 14px; +} + +ul.doc-example > li { + border: 2px solid gray; + border-radius: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + background-color: white; + margin-bottom: 20px; +} + +ul.doc-example > li.doc-example-heading { + border: none; + border-radius: none; + margin-bottom: -10px; +} + +li.doc-example-live { + padding: 10px; + font-size: 1.2em; +} + +div.syntaxhighlighter { + padding-bottom: 1px !important; /* fix to remove unnecessary scrollbars http://is.gd/gSMgC */ +} \ No newline at end of file diff --git a/docs/src/templates/doc_widgets.js b/docs/src/templates/doc_widgets.js new file mode 100644 index 00000000..b119e326 --- /dev/null +++ b/docs/src/templates/doc_widgets.js @@ -0,0 +1,71 @@ +(function(){ + + var angularJsUrl; + var scripts = document.getElementsByTagName("script"); + var filename = /(.*\/)angular([^\/]*)/; + for(var j = 0; j < scripts.length; j++) { + var src = scripts[j].src; + if (src && src.match(filename)) { + angularJsUrl = src; + } + } + + + var HTML_TEMPLATE = + '\n' + + '\n' + + ' \n' + + ' \n' + + '_HTML_SOURCE_\n' + + ' \n' + + ''; + + angular.widget('doc:example', function(element){ + this.descend(true); //compile the example code + element.hide(); + + var example = element.find('doc\\:source').eq(0), + exampleSrc = example.text(), + scenario = element.find('doc\\:scenario').eq(0); + + var code = indent(exampleSrc); + var tabs = angular.element( + '

      ' + + '
    • Source

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

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

    • ' + + '
    • ' + scenario.text() + '
    • ' + + '
    '); + + tabs.find('li.doc-example-source > pre').text(HTML_TEMPLATE.replace('_HTML_SOURCE_', code.html)); + + element.html(''); + element.append(tabs); + element.show(); + + var script = (exampleSrc.match(/]*>([\s\S]*)<\/script>/) || [])[1] || ''; + try { + eval(script); + } catch (e) { + alert(e); + } + }); + + function indent(text) { + var lines = text.split(/\n/); + var lineNo = []; + while (lines[0].match(/^\s*$/)) lines.shift(); + while (lines[lines.length - 1].match(/^\s*$/)) lines.pop(); + for ( var i = 0; i < lines.length; i++) { + lines[i] = ' ' + lines[i]; + lineNo.push(6 + i); + } + return {html: lines.join('\n'), hilite: lineNo.join(',') }; + }; + +})(); \ No newline at end of file diff --git a/docs/src/templates/docs-scenario.html b/docs/src/templates/docs-scenario.html new file mode 100644 index 00000000..c75155c5 --- /dev/null +++ b/docs/src/templates/docs-scenario.html @@ -0,0 +1,10 @@ + + + + <angular/> Docs Scenario Runner + + + + + + \ No newline at end of file diff --git a/docs/src/templates/docs-scenario.js b/docs/src/templates/docs-scenario.js new file mode 100644 index 00000000..2ee8387b --- /dev/null +++ b/docs/src/templates/docs-scenario.js @@ -0,0 +1,9 @@ +{{#pages}} +describe('{{name}}', function(){ + beforeEach(function(){ + browser().navigateTo('index.html#!{{name}}'); + }); + // {{raw.file}}:{{raw.line}} +{{{scenario}}} +}); +{{/pages}} diff --git a/docs/src/templates/docs.css b/docs/src/templates/docs.css new file mode 100644 index 00000000..aaef7e58 --- /dev/null +++ b/docs/src/templates/docs.css @@ -0,0 +1,262 @@ +/* Common Style */ + +body { + font-family: Arial, sans-serif; + font-size: 14px; + margin: 0; + padding: 0; +} + +a { + color: blue; +} + + +/* Main Layout */ + +#header { + height: 3.5em; +} + +#sidebar, +#main { + position: absolute; + top: 3.5em; + bottom: 0; + margin-top: 1px; + overflow-x: hidden; +} + +#sidebar { + width: 13.8em; + padding: 0.8em 0em 1.5em 0.8em; +} + +#main { + left: 14.6em; + right: 0; + padding: 1em; + overflow-y: scroll; +} + +#api-list { + position: absolute; + top: 3em; + bottom: 1em; + overflow-y: scroll; + padding-right: 0.8em; +} + + +/* App Header */ + +#header { + background-color: #F2C200; + border-bottom: 1px solid #957800; +} + +#header h1 { + font-weight: normal; + font-size: 30px; + line-height: 30px; + margin: 0; + padding: 10px 10px; + height: 30px; +} + +#header .angular { + font-family: "Courier New", monospace; + font-weight: bold; +} + +#header h1 a { + color: black; + text-decoration: none; +} + +#header h1 a:hover { + text-decoration: underline; +} + + +/* Main main Style */ + +#main h1 { + font-family: monospace; + margin-top: 0; + padding-bottom: 5px; + border-bottom: 1px solid #CCC; +} + +#main h2 { + margin-top: 1.8em; +} + +#main h1 + h2 { + margin-top: 1.3em; +} + +#main h3 { + margin-top: 1.5em; +} + +#main ul.methods h3, +#main ul.properties h3 { + margin-top: 1.5em; + font-family: "Courier New", monospace; +} + +.main-title { + float: right; +} + + +/* Searchbox & Sidebar Style */ + +#search-box, #sidebar { + border-right: 1px solid #DDD; +} + +#sidebar { + background-color: #EEE; +} + +#search-box { + width: 16em; + margin-bottom: 1em; +} + +#sidebar a { + text-decoration: none; +} + +#sidebar a:hover { + text-decoration: underline; +} + +#sidebar ul { + list-style-type: none; + /*TODO(esprehn): Can we just reset globally and not break examples?*/ + margin: 0; + padding: 0 0.8em 0 0; + width: 13em; +} + +#sidebar ul li { +} + +#sidebar ul li a { + display: block; + padding: 2px 2px 2px 4px; +} + +#sidebar ul li.selected a { + background-color: #DDD; + border-radius: 5px; + -moz-border-radius: 5px; + border: 1px solid #CCC; + padding: 1px 1px 1px 3px; +} + +#sidebar ul li.level-0 { + margin-left: 0em; + font-weight: bold; + font-size: 1.2em; +} + +#sidebar ul li.level-1.level-angular { + font-family: monospace; + font-weight: normal; + font-size: 1em; + margin-top: 0; + margin-bottom: 0; +} + +#sidebar ul li.level-1 { + margin-left: 1em; + margin-top: 5px; + font-size: 1.1em; + font-weight: bold; +} + +#sidebar ul li.level-2 { + margin-left: 2em; + font-family: monospace; +} + +#sidebar ul li.level-3 { + margin-left: 3em; + font-family: monospace; +} + + +/* Warning and Info Banners */ + +.deprecated { + border: 2px solid red; +} + +.deprecated legend { + font-weight: bold; + color: red; +} + +.workInProgress { + border: 2px solid orange; +} + +.workInProgress legend { + font-weight: bold; + color: orange; +} + + +/* Feedback Link */ + +#feedback { + float: right; + width: 10em; + text-align: right; +} + + +/* Live Example Style */ + +.doc-example-live table td { + padding: 0 1.5em; +} + + +/* Scrollbars */ + +::-webkit-scrollbar{ + width:0.8em; + margin: 0.2em 0em; +} + +::-webkit-scrollbar:hover{ + background-color:#eee; +} + +::-webkit-scrollbar-thumb{ + min-height:0.8em; + min-width:0.8em; + -webkit-border-radius:0.5em; + background-color: #ddd; +} + +::-webkit-scrollbar-thumb:hover{ + background-color: #bbb; +} + +::-webkit-scrollbar-thumb:active{ + background-color:#888; +} + +#sidebar::-webkit-scrollbar { + background-color:#eee; +} + +#main::-webkit-scrollbar { + background-color:#fff; +} diff --git a/docs/src/templates/docs.js b/docs/src/templates/docs.js new file mode 100644 index 00000000..6bf86ed3 --- /dev/null +++ b/docs/src/templates/docs.js @@ -0,0 +1,47 @@ +DocsController.$inject = ['$location', '$browser', '$window']; +function DocsController($location, $browser, $window) { + this.pages = NG_PAGES; + window.$root = this.$root; + + this.getUrl = function(page){ + return '#!' + page.name; + }; + + this.getCurrentPartial = function(){ + return './' + this.getTitle() + '.html'; + }; + + this.getTitle = function(){ + var hashPath = $location.hashPath || '!angular'; + if (hashPath.match(/^!angular/)) { + this.partialTitle = hashPath.substring(1); + } + return this.partialTitle; + }; + + this.getClass = function(page) { + var depth = page.name.split(/\./).length - 1, + cssClass = 'level-' + depth + (page.name == this.getTitle() ? ' selected' : ''); + + if (depth == 1 && page.type !== 'overview') cssClass += ' level-angular'; + + return cssClass; + }; + + this.afterPartialLoaded = function() { + SyntaxHighlighter.highlight(); + }; + + this.getFeedbackUrl = function() { + return "mailto:angular@googlegroups.com?" + + "subject=" + escape("Feedback on " + $location.href) + "&" + + "body=" + escape("Hi there,\n\nI read " + $location.href + " and wanted to ask ...."); + }; + +} + +angular.filter('short', function(name){ + return (name||'').split(/\./).pop(); +}); + +SyntaxHighlighter['defaults'].toolbar = false; diff --git a/docs/src/templates/index.html b/docs/src/templates/index.html new file mode 100644 index 00000000..64b73bfc --- /dev/null +++ b/docs/src/templates/index.html @@ -0,0 +1,45 @@ + + + + <angular/> + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/src/writer.js b/docs/src/writer.js new file mode 100644 index 00000000..eb1b190f --- /dev/null +++ b/docs/src/writer.js @@ -0,0 +1,61 @@ +/** + * All writing related code here. This is so that we can separate the async code from sync code + * for testability + */ +require.paths.push(__dirname); +var fs = require('fs'); +var OUTPUT_DIR = "build/docs/"; + +function output(docs, content, callback){ + callback(); +} + +exports.output = function(file, content, callback){ + //console.log('writing', OUTPUT_DIR + file, '...'); + fs.writeFile( + OUTPUT_DIR + file, + exports.toString(content), + callback); +}; + + +exports.toString = function toString(obj){ + switch (typeof obj) { + case 'string': + return obj; + case 'object': + if (obj instanceof Array) { + obj.forEach(function (value, key){ + obj[key] = toString(value); + }); + return obj.join(''); + } else { + return JSON.stringify(obj); + } + } + return obj; +}; + +exports.makeDir = function (path, callback) { + var parts = path.split(/\//); + path = '.'; + (function next(){ + if (parts.length) { + path += '/' + parts.shift(); + fs.mkdir(path, 0777, next); + } else { + callback(); + } + })(); +}; + +exports.copy = function(filename, callback){ + //console.log('writing', OUTPUT_DIR + filename, '...'); + fs.readFile('docs/src/templates/' + filename, function(err, content){ + if (err) return callback.error(err); + fs.writeFile( + OUTPUT_DIR + filename, + content, + callback); + }); +}; 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 @@ -

    {{name}}

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

    Description

    -{{{description}}} - -

    Usage

    -

    In HTML Template Binding

    - - <input type="text" ng:validate="{{shortName}}{{#paramRest}}{{^default}}:{{name}}{{/default}}{{#default}}[:{{name}}]{{/default}}{{/paramRest}}"/> - - -

    In JavaScript

    - -angular.validator.{{shortName}}({{paramFirst.name}}{{#paramRest}}{{^default}}, {{name}}{{/default}}{{#default}}[, {{name}}]{{/default}}{{/paramRest}} ); - - -

    Parameters

    -
      - {{#param}} -
    • {{name}} – - {{{#type}}{{type}}{{/type}}{{^type}}*{{/type}}{{#optional}}={{/optional}}} - {{#default}}[{{default}}]{{/default}} - – {{{description}}}
    • - {{/param}} -
    -{{{paramDescription}}} - -{{#css}} -

    CSS

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

    Example

    -{{{exampleDescription}}} - - -{{/example}} - {{{example}}} -{{#example}} - - {{{scenario}}} - -{{/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 @@ -

    {{name}}

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

    Description

    -{{{description}}} - -

    Usage

    -

    In HTML Template Binding

    - -{{^element}} -
    -<{{shortName}}{{#param}} {{#default}}[{{/default}}{{name}}="..."{{#default}}]{{/default}}{{/param}}>{{#usageContent}} 
    -
    -  {{usageContent}}
    -{{/usageContent}}</{{shortName}}>
    -  
    -{{/element}} -{{#element}} -
    -<{{element}} {{shortName}}{{#paramFirst}}="{{paramFirst.name}}{{/paramFirst}}">
    -  ...
    -</{{element}}>
    -  
    -{{/element}} -
    - -

    Parameters

    -
      - {{#param}} -
    • {{name}} – - {{{#type}}{{type}}{{/type}}{{^type}}*{{/type}}{{#optional}}={{/optional}}} - {{#default}}[{{default}}]{{/default}} - – {{{description}}}
    • - {{/param}} -
    -{{{paramDescription}}} - -{{#css}} -

    CSS

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

    Example

    -{{{exampleDescription}}} - - -{{/example}} - {{{example}}} -{{#example}} - - {{{scenario}}} - -{{/example}} -- cgit v1.2.3