diff options
| author | Misko Hevery | 2010-12-23 00:44:27 +0100 | 
|---|---|---|
| committer | Misko Hevery | 2011-01-10 11:50:11 -0800 | 
| commit | 4f22d6866c052fb5b770ce4f377cecacacd9e6d8 (patch) | |
| tree | 6bdb1c5eb70cfd7e6bcf143c121c53025a0489a4 /docs/src | |
| parent | aab3df7aeaf79908e8b6212288b283adb42b1ce6 (diff) | |
| download | angular.js-4f22d6866c052fb5b770ce4f377cecacacd9e6d8.tar.bz2 | |
complete rewrite of documentation generation
- romeved mustache.js
- unified templates
- improved testability of the code
Diffstat (limited to 'docs/src')
| -rw-r--r-- | docs/src/callback.js | 69 | ||||
| -rw-r--r-- | docs/src/dom.js | 123 | ||||
| -rw-r--r-- | docs/src/gen-docs.js | 42 | ||||
| -rw-r--r-- | docs/src/ignore.words | 0 | ||||
| -rw-r--r-- | docs/src/ngdoc.js | 614 | ||||
| -rw-r--r-- | docs/src/reader.js | 91 | ||||
| -rw-r--r-- | docs/src/templates/doc_widgets.css | 35 | ||||
| -rw-r--r-- | docs/src/templates/doc_widgets.js | 71 | ||||
| -rw-r--r-- | docs/src/templates/docs-scenario.html | 10 | ||||
| -rw-r--r-- | docs/src/templates/docs-scenario.js | 9 | ||||
| -rw-r--r-- | docs/src/templates/docs.css | 262 | ||||
| -rw-r--r-- | docs/src/templates/docs.js | 47 | ||||
| -rw-r--r-- | docs/src/templates/index.html | 45 | ||||
| -rw-r--r-- | docs/src/writer.js | 61 | 
14 files changed, 1479 insertions, 0 deletions
| 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, '<').replace(/>/g, '>')); +    } else if (typeof content == 'function') { +      content.call(this, this); +    } else if (content instanceof Array) { +      this.ul(content); +    } +  }, + +  html: function(html) { +    if (html) { +      this.out.push(html); +    } +  }, + +  tag: function(name, attr, text) { +    if (!text) { +      text = attr; +      attr = {}; +      if (name == 'code') +        attr['ng:non-bindable'] = ''; +    } +    this.out.push('<' + name); +    for(var key in attr) { +      this.out.push(" " + key + '="' + attr[key] + '"'); +    } +    this.out.push('>'); +    this.text(text); +    this.out.push('</' + name + '>'); +    if (!INLINE_TAGS[name])  +      this.out.push('\n'); +  }, +   +  code: function(text) { +    this.tag('div', {'ng:non-bindable':''}, function(){ +      this.tag('pre', {'class':"brush: js; html-script: true;"}, text); +    }); +  }, +   +  example: function(source, scenario) { +    if (source || scenario) { +      this.h('Example', function(){ +        if (scenario === false) { +          this.code(source); +        } else { +          this.tag('doc:example', function(){ +            if (source) this.tag('doc:source', source); +            if (scenario) this.tag('doc:scenario', scenario); +          }); +        } +      }); +    } +  }, +   +  h: function(heading, content, fn){ +    if (content==undefined || content && content.legth == 0) return; +    this.tag('h' + this.headingDepth, heading); +    this.headingDepth++; +    if (content instanceof Array) { +      this.ul(content, {'class': heading.toLowerCase()}, fn); +    } else if (fn) { +      fn.call(this, content); +    } else { +      this.text(content); +    }  +    this.headingDepth--; +  }, +   +  h1: function(attr, text) { +    this.tag('h1', attr, text); +  }, +   +  h2: function(attr, text) { +    this.tag('h2', attr, text); +  }, +   +  h3: function(attr, text) { +    this.tag('h3', attr, text); +  }, +   +  p: function(attr, text) { +    this.tag('p', attr, text); +  }, +   +  ul: function(list, attr, fn) { +    if (typeof attr == 'function') { +      fn = attr; +      attr = {}; +    } +    this.tag('ul', attr, function(dom){ +      list.forEach(function(item){ +        dom.out.push('<li>'); +        dom.text(fn ? fn(item) : item); +        dom.out.push('</li>\n'); +      }); +    }); +  } +   +};
\ No newline at end of file diff --git a/docs/src/gen-docs.js b/docs/src/gen-docs.js new file mode 100644 index 00000000..b4e30a53 --- /dev/null +++ b/docs/src/gen-docs.js @@ -0,0 +1,42 @@ +require.paths.push(__dirname); +require.paths.push('lib'); +var reader = require('reader.js'), +    ngdoc = require('ngdoc.js'), +    writer = require('writer.js'), +    callback = require('callback.js'); + +var docs = []; +var start; +var work = callback.chain(function(){ +  start = now(); +  console.log('Generating Angular Reference Documentation...'); +  reader.collect(work.waitMany(function(text, file, line){ +    var doc = new ngdoc.Doc(text, file, line); +    docs.push(doc); +    doc.parse(); +  })); +}); +var writes = callback.chain(function(){ +  ngdoc.merge(docs); +  docs.forEach(function(doc){ +    writer.output(doc.name + '.html', doc.html(), writes.waitFor()); +  }); +  var metadata = ngdoc.metadata(docs); +  writer.output('docs-keywords.js', ['NG_PAGES=', JSON.stringify(metadata), ';'], writes.waitFor()); +  writer.copy('index.html', writes.waitFor()); +  writer.copy('docs.js', writes.waitFor()); +  writer.copy('docs.css', writes.waitFor()); +  writer.copy('doc_widgets.js', writes.waitFor()); +  writer.copy('doc_widgets.css', writes.waitFor()); +  writer.copy('docs-scenario.html', writes.waitFor()); +  writer.output('docs-scenario.js', ngdoc.scenarios(docs), writes.waitFor()); +}); +writes.onDone(function(){ +  console.log('DONE. Generated ' + docs.length + ' pages in ' +  +      (now()-start) + 'ms.' ); +}); +work.onDone(writes); +writer.makeDir('build/docs', work); + +/////////////////////////////////// +function now(){ return new Date().getTime(); } diff --git a/docs/src/ignore.words b/docs/src/ignore.words new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/docs/src/ignore.words diff --git a/docs/src/ngdoc.js b/docs/src/ngdoc.js new file mode 100644 index 00000000..ac7d0bb1 --- /dev/null +++ b/docs/src/ngdoc.js @@ -0,0 +1,614 @@ +/** + * All parsing/transformation code goes here. All code here should be sync to ease testing. + */ + +var Showdown = require('showdown').Showdown; +var DOM = require('dom.js').DOM; +var NEW_LINE = /\n\r?/; + +exports.markdown = markdown; +exports.markdownNoP = markdownNoP; +exports.trim = trim; +exports.metadata = metadata; +exports.scenarios = scenarios; +exports.merge = merge; +exports.Doc = Doc; + +////////////////////////////////////////////////////////// +function Doc(text, file, line) { +  if (typeof text == 'object') { +    for ( var key in text) { +      this[key] = text[key]; +    } +  } else { +    this.text = text; +    this.file = file; +    this.line = line; +  } +} +Doc.METADATA_IGNORE = (function(){ +  var words = require('fs').readFileSync(__dirname + '/ignore.words', 'utf8'); +  return words.toString().split(/[,\s\n\r]+/gm); +})(); + + + +Doc.prototype = { +  keywords: function keywords(){ +    var keywords = {}; +    Doc.METADATA_IGNORE.forEach(function(ignore){ keywords[ignore] = true; }); +    var words = []; +    var tokens = this.text.toLowerCase().split(/[,\.\`\'\"\s]+/mg); +    tokens.forEach(function(key){ +      var match = key.match(/^(([a-z]|ng\:)[\w\_\-]{2,})/); +      if (match){ +        key = match[1]; +        if (!keywords[key]) { +          keywords[key] = true; +          words.push(key); +        } +      } +    }); +    words.sort(); +    return words.join(' '); +  }, +   +  parse: function(){ +    var atName; +    var atText; +    var match; +    var self = this; +    self.text.split(NEW_LINE).forEach(function(line){ +      if (match = line.match(/^\s*@(\w+)(\s+(.*))?/)) { +        // we found @name ... +        // if we have existing name +        flush(); +        atName = match[1]; +        atText = []; +        if(match[3]) atText.push(match[3]); +      } else { +        if (atName) { +          atText.push(line); +        } +      } +    }); +    flush(); +    this.shortName = (this.name || '').split(/[\.#]/).pop(); +    this.description = markdown(this.description); +     +    function flush(){ +      if (atName) { +        var text = trim(atText.join('\n')); +        if (atName == 'param') { +          var match = text.match(/^{([^}=]+)(=)?}\s+(([^\s=]+)|\[(\S+)=([^\]]+)\])\s+(.*)/); +                                //  1      12 2     34       4   5   5 6      6  3   7  7 +          if (!match) { +            throw new Error("Not a valid 'param' format: " + text); +          } +          var param = { +            name: match[5] || match[4], +            description:markdownNoP(text.replace(match[0], match[7])), +            type: match[1], +            optional: !!match[2], +            'default':match[6] +          }; +          self.param = self.param || []; +          self.param.push(param); +        } else if (atName == 'returns') { +          var match = text.match(/^{([^}=]+)}\s+(.*)/); +          if (!match) { +            throw new Error("Not a valid 'returns' format: " + text); +          } +          self.returns = { +            type: match[1], +            description: markdownNoP(text.replace(match[0], match[2])) +          }; +        } else if(atName == 'requires') { +          self.requires = self.requires || []; +          self.requires.push(text); +        } else if(atName == 'property') { +          var match = text.match(/^({(\S+)}\s*)?(\S+)(\s+(.*))?/); +          if (!match) { +            throw new Error("Not a valid 'property' format: " + text); +          } +          var property = { +              type: match[2], +              name: match[3], +              description: match[5] || '' +            }; +          self.properties = self.properties || []; +          self.properties.push(property); +        } else { +          self[atName] = text; +        } +      } +    } +  }, +   +  html: function(){ +    var dom = new DOM(), +        self = this; +     +    dom.h(this.name, function(){ +      notice('workInProgress', 'Work in Progress',  +          'This page is currently being revised. It might be incomplete or contain inaccuracies.'); +      notice('depricated', 'Depricated API'); +      dom.h('Description', self.description, html); +      dom.h('Dependencies', self.requires); +       +      usage(); +       +      dom.h('Methods', self.methods, function(method){ +        var signature = (method.param || []).map(property('name')); +        dom.h(method.shortName + '(' + signature.join(', ') + ')', method, function(){ +          dom.html(method.description); +          method.html_usage_parameters(dom); +          dom.example(method.example, false); +        }); +      }); +      dom.h('Properties', self.properties, function(property){ +        dom.h(property.name, function(){ +         dom.text(property.description);  +         dom.example(property.example, false); +        }); +      }); +       +      dom.example(self.example, self.scenario); +    }); +     +    return dom.toString(); +     +    ////////////////////////// +     +    function html(text){ +      this.html(text); +    } +     +    function usage(){ +      (self['html_usage_' + self.ngdoc] || function(){ +        throw new Error("Don't know how to format @ngdoc: " + self.ngdoc); +      }).call(self, dom); +    } +     +    function section(name, property, fn) { +      var value = self[property]; +      if (value) { +        dom.h2(name); +        if (typeof value == 'string') { +          value = markdown(value) + '\n'; +          fn ? fn(value) : dom.html(value); +        } else if (value instanceof Array) { +          dom.ul(value, fn); +        } +      } +    } +     +    function notice(name, legend, msg){ +      if (self[name] == undefined) return; +      dom.tag('fieldset', {'class':name}, function(dom){ +        dom.tag('legend', legend); +        dom.text(msg); +      }); +    } +     +  }, +   +  html_usage_parameters: function(dom) { +    dom.h('Parameters', this.param, function(param){ +      dom.tag('code', function(){ +        dom.text(param.name); +        if (param.optional) { +          dom.tag('i', function(){ +            dom.text('(optional'); +            if(param['default']) { +              dom.text('=' + param['default']); +            } +            dom.text(')'); +          }); +        } +        dom.text(' – {'); +        dom.text(param.type); +        dom.text('} – '); +      }); +      dom.html(param.description); +    }); +  }, +   +  html_usage_returns: function(dom) { +    var self = this; +    if (self.returns) { +      dom.h('Returns', function(){ +        dom.tag('code', self.returns.type); +        dom.text('– '); +        dom.html(self.returns.description); +      }); +    } +  }, +   +  html_usage_function: function(dom){ +    var self = this; +    dom.h('Usage', function(){ +      dom.code(function(){ +        dom.text(self.name); +        dom.text('('); +        var first = true; +        (self.param || []).forEach(function(param){ +          if (first) { +            first = false; +          } else { +            dom.text(', '); +          } +          dom.text(param.name); +        }); +        dom.text(');'); +      }); +       +      self.html_usage_parameters(dom); +      self.html_usage_returns(dom); +    }); +  }, +     +  html_usage_directive: function(dom){ +    var self = this; +    dom.h('Usage', function(){ +      dom.tag('pre', {'class':"brush: js; html-script: true;"}, function(){ +        dom.text('<' + self.element + ' '); +        dom.text(self.shortName); +        if (self.param) { +          dom.text('="' + self.param[0].name + '"'); +        } +        dom.text('>\n   ...\n'); +        dom.text('</' + self.element + '>'); +      }); +      self.html_usage_parameters(dom); +    }); +  }, +     +  html_usage_filter: function(dom){ +    var self = this; +    dom.h('Usage', function(){ +      dom.h('In HTML Template Binding', function(){ +        dom.tag('code', function(){ +          dom.text('{{ '); +          dom.text(self.shortName); +          dom.text('_expression | '); +          dom.text(self.shortName); +          var first = true; +          (self.param||[]).forEach(function(param){ +            if (first) { +              first = false; +            } else { +              if (param.optional) { +                dom.tag('i', function(){ +                  dom.text('[:' + param.name + ']'); +                }); +              } else { +                dom.text(':' + param.name); +              } +            } +          }); +          dom.text(' }}'); +        }); +      }); +       +      dom.h3('In JavaScript', function(){ +        dom.tag('code', function(){ +          dom.text('angular.filter.'); +          dom.text(self.shortName); +          dom.text('('); +          var first = true; +          (self.param||[]).forEach(function(param){ +            if (first) { +              first = false; +              dom.text(param.name); +            } else { +              if (param.optional) { +                dom.tag('i', function(){ +                  dom.text('[, ' + param.name + ']'); +                }); +              } else { +                dom.text(', ' + param.name); +              } +            } +          }); +          dom.text(')'); +        }); +      }); +       +      self.html_usage_parameters(dom); +      self.html_usage_returns(dom); +    }); +  }, +     +  html_usage_formatter: function(dom){ +    var self = this; +    dom.h('Usage', function(){ +      dom.h('In HTML Template Binding', function(){ +        dom.code(function(){ +          dom.text('<input type="text" ng:format="'); +          dom.text(self.shortName); +          dom.text('">'); +        }); +      }); +       +      dom.h3('In JavaScript', function(){ +        dom.code(function(){ +          dom.text('var userInputString = angular.formatter.'); +          dom.text(self.shortName); +          dom.text('.format(modelValue);'); +        }); +        dom.html('<br/>'); +        dom.code(function(){ +          dom.text('var modelValue = angular.formatter.'); +          dom.text(self.shortName); +          dom.text('.parse(userInputString);'); +        }); +      }); +       +      self.html_usage_returns(dom);   +    }); +  }, + +  html_usage_validator: function(dom){ +    var self = this; +    dom.h('Usage', function(){ +      dom.h('In HTML Template Binding', function(){ +        dom.code(function(){ +          dom.text('<input type="text" ng:validate="'); +          dom.text(self.shortName); +          var first = true; +          (self.param||[]).forEach(function(param){ +            if (first) { +              first = false; +            } else { +              if (param.optional) { +                dom.text('[:' + param.name + ']'); +              } else { +                dom.text(':' + param.name); +              } +            } +          }); +          dom.text('"/>'); +        }); +      }); +       +      dom.h('In JavaScript', function(){ +        dom.code(function(){ +          dom.text('angular.validator.'); +          dom.text(self.shortName); +          dom.text('('); +          var first = true; +          (self.param||[]).forEach(function(param){ +            if (first) { +              first = false; +              dom.text(param.name); +            } else { +              if (param.optional) { +                dom.text('[, ' + param.name + ']'); +              } else { +                dom.text(', ' + param.name); +              } +            } +          }); +          dom.text(')'); +        }); +      }); +       +      self.html_usage_parameters(dom); +      self.html_usage_returns(dom); +    }); +  }, +   +  html_usage_widget: function(dom){ +    var self = this; +    dom.h('Usage', function(){ +      dom.h('In HTML Template Binding', function(){ +        dom.code(function(){ +          if (self.shortName.match(/^@/)) { +            dom.text('<'); +            dom.text(self.element); +            dom.text(' '); +            dom.text(self.shortName.substring(1)); +            if (self.param) { +              dom.text('="'); +              dom.text(self.param[0].name); +              dom.text('"'); +            } +            dom.text('>\n   ...\n</'); +            dom.text(self.element); +            dom.text('>'); +          } else { +            dom.text('<'); +            dom.text(self.shortName); +            (self.param||[]).forEach(function(param){ +              if (param.optional) { +                dom.text(' [' + param.name + '="..."]'); +              } else { +                dom.text(' ' + param.name + '="..."'); +              } +            }); +            dom.text('></'); +            dom.text(self.shortName); +            dom.text('>'); +          } +        }); +      }); +       +      self.html_usage_parameters(dom); +      self.html_usage_returns(dom); +    }); +  }, +     +  html_usage_overview: function(dom){ +  }, +     +  html_usage_service: function(dom){ +  } + +}; +////////////////////////////////////////////////////////// + + +////////////////////////////////////////////////////////// +function markdown (text) { +  if (!text) return text; +  var parts = text.split(/(<pre>[\s\S]*?<\/pre>)/), +      match; + +  parts.forEach(function(text, i){ +    if (text.match(/^<pre>/)) { +      text = text. +        replace(/^<pre>/, '<div ng:non-bindable><pre class="brush: js; html-script: true;">'). +        replace(/<\/pre>/, '</pre></div>'); +    } else { +      text = text.replace(/<angular\/>/gm, '<tt><angular/></tt>'); +      text = new Showdown.converter().makeHtml(text.replace(/^#/gm, '###')); + +      while (match = text.match(R_LINK)) { +        text = text.replace(match[0], '<a href="#!' + match[1] + '"><code>' + +                                        (match[4] || match[1]) + +                                      '</code></a>'); +      } +    } +    parts[i] = text; +  }); +  return parts.join(''); +}; +var R_LINK = /{@link ([^\s}]+)((\s|\n)+(.+?))?\s*}/m; +            //       1       123     3 4     42 +function markdownNoP(text) { +  var lines = markdown(text).split(NEW_LINE); +  var last = lines.length - 1; +  lines[0] = lines[0].replace(/^<p>/, ''); +  lines[last] = lines[last].replace(/<\/p>$/, ''); +  return lines.join('\n'); +} + + +////////////////////////////////////////////////////////// +function scenarios(docs){ +  var specs = []; +  docs.forEach(function(doc){ +    if (doc.scenario) { +      specs.push('describe("'); +      specs.push(doc.name); +      specs.push('", function(){\n'); +      specs.push('  beforeEach(function(){\n'); +      specs.push('    browser().navigateTo("index.html#!' + doc.name + '");'); +      specs.push('  });\n\n'); +      specs.push(doc.scenario); +      specs.push('\n});\n\n'); +    } +  }); +  return specs; +} + + +////////////////////////////////////////////////////////// +function metadata(docs){ +  var words = []; +  docs.forEach(function(doc){ +    words.push({ +      name:doc.name, +      type: doc.ngdoc, +      keywords:doc.keywords() +    }); +  }); +  words.sort(keywordSort); +  return words; +} + +function keywordSort(a,b){ +  // supper ugly comparator that orders all utility methods and objects before all the other stuff +  // like widgets, directives, services, etc. +  // Mother of all beautiful code please forgive me for the sin that this code certainly is. + +  if (a.name === b.name) return 0; +  if (a.name === 'angular') return -1; +  if (b.name === 'angular') return 1; + +  function namespacedName(page) { +    return (page.name.match(/\./g).length === 1 && page.type !== 'overview' ? '0' : '1') + page.name; +  } + +  var namespacedA = namespacedName(a), +      namespacedB = namespacedName(b); + +  return namespacedA < namespacedB ? -1 : 1; +} + + +////////////////////////////////////////////////////////// +function trim(text) { +  var MAX = 9999; +  var empty = RegExp.prototype.test.bind(/^\s*$/); +  var lines = text.split('\n'); +  var minIndent = MAX; +  lines.forEach(function(line){ +    minIndent = Math.min(minIndent, indent(line)); +  }); +  for ( var i = 0; i < lines.length; i++) { +    lines[i] = lines[i].substring(minIndent); +  } +  // remove leading lines +  while (empty(lines[0])) { +    lines.shift(); +  } +  // remove trailing +  while (empty(lines[lines.length - 1])) { +    lines.pop(); +  } +  return lines.join('\n'); +   +  function indent(line) { +    for(var i = 0; i < line.length; i++) { +      if (line.charAt(i) != ' ')  { +        return i; +      } +    } +    return MAX; +  } +} + +////////////////////////////////////////////////////////// +function merge(docs){ +  var byName = {}; +  docs.forEach(function(doc){ +    byName[doc.name] = doc; +  }); +  for(var i=0; i<docs.length;) { +    if (findParent(docs[i], 'method') || +          findParent(docs[i], 'property')) { +      docs.splice(i, 1); +    } else { +      i++; +    } +  } +   +  function findParent(doc, name){ +    var parentName = doc[name+'Of']; +    if (!parentName) return false; +     +    var parent = byName[parentName]; +    if (!parent)  +      throw new Error("No parent named '" + parentName + "' for '" +  +          doc.name + "' in @" + name + "Of."); +     +    var listName = (name + 's').replace(/ys$/, 'ies'); +    var list = parent[listName] = (parent[listName] || []); +    list.push(doc); +    list.sort(orderByName); +    return true; +  } +   +  function orderByName(a, b){ +    return a.name < b.name ? -1 : (a.name > b.name ? 1 : 0); +  } +} +////////////////////////////////////////////////////////// + +function property(name) { +  return function(value){  +    return value[name]; +  }; +} 
\ No newline at end of file diff --git a/docs/src/reader.js b/docs/src/reader.js new file mode 100644 index 00000000..8f9f22c3 --- /dev/null +++ b/docs/src/reader.js @@ -0,0 +1,91 @@ +/** + * All reading related code here. This is so that we can separate the async code from sync code + * for testability + */ +require.paths.push(__dirname); +var fs       = require('fs'), +    callback = require('callback'); + +var NEW_LINE = /\n\r?/; + +function collect(callback){ +  findJsFiles('src', callback.waitMany(function(file) { +    //console.log('reading', file, '...'); +    findNgDocInJsFile(file, callback.waitMany(function(doc, line) { +      callback(doc, file, line); +    })); +  })); +  findNgDocInDir('docs/', callback.waitMany(callback)); +  callback.done(); +} + +function findJsFiles(dir, callback){ +  fs.readdir(dir, callback.waitFor(function(err, files){ +    if (err) return this.error(err); +    files.forEach(function(file){ +      var path = dir + '/' + file; +      fs.lstat(path, callback.waitFor(function(err, stat){ +        if (err) return this.error(err); +        if (stat.isDirectory()) +          findJsFiles(path, callback.waitMany(callback)); +        else if (/\.js$/.test(path)) +          callback(path); +      })); +    }); +    callback.done(); +  })); +} + +function findNgDocInDir(directory, docNotify) { +  fs.readdir(directory, docNotify.waitFor(function(err, files){ +    if (err) return this.error(err); +    files.forEach(function(file){ +      //console.log('reading', directory + file, '...'); +      if (!file.match(/\.ngdoc$/)) return; +      fs.readFile(directory + file, docNotify.waitFor(function(err, content){ +        if (err) return this.error(err); +        docNotify(content.toString(), directory + file, 1); +      })); +    }); +    docNotify.done(); +  })); +} + +function findNgDocInJsFile(file, callback) { +  fs.readFile(file, callback.waitFor(function(err, content){ +    var lines = content.toString().split(NEW_LINE); +    var text; +    var startingLine ; +    var match; +    var inDoc = false; +    lines.forEach(function(line, lineNumber){ +      lineNumber++; +      // is the comment starting? +      if (!inDoc && (match = line.match(/^\s*\/\*\*\s*(.*)$/))) { +        line = match[1]; +        inDoc = true; +        text = []; +        startingLine = lineNumber; +      } +      // are we done? +      if (inDoc && line.match(/\*\//)) { +        text = text.join('\n'); +        text = text.replace(/^\n/, ''); +        if (text.match(/@ngdoc/)){ +          callback(text, startingLine); +        } +        doc = null; +        inDoc = false; +      } +      // is the comment add text +      if (inDoc){ +        text.push(line.replace(/^\s*\*\s?/, '')); +      } +    }); +    callback.done(); +  })); +} + + + +exports.collect = collect;
\ No newline at end of file diff --git a/docs/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 = +  '<!doctype html>\n' + +  '<html xmlns:ng="http://angularjs.org">\n' + +  ' <script type="text/javascript" ng:autobind\n' +  +  '         src="' + angularJsUrl + '"></script>\n' + +  ' <body>\n' + +  '_HTML_SOURCE_\n' + +  ' </body>\n' + +  '</html>'; + +  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( +        '<ul class="doc-example">' + +          '<li class="doc-example-heading"><h3>Source</h3></li>' + +          '<li class="doc-example-source" ng:non-bindable>' +  +            '<pre class="brush: js; html-script: true; highlight: [' +  +            code.hilite + ']; toolbar: false;"></pre></li>' + +          '<li class="doc-example-heading"><h3>Live Preview</h3></li>' + +          '<li class="doc-example-live">' + exampleSrc +'</li>' + +          '<li class="doc-example-heading"><h3>Scenario Test</h3></li>' + +          '<li class="doc-example-scenario"><pre class="brush: js">' + scenario.text() + '</pre></li>' + +        '</ul>'); +     +    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(/<script[^\>]*>([\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 @@ +<!DOCTYPE HTML> +<html xmlns:ng="http://angularjs.org" wiki:ng="http://angularjs.org"> +<head> +  <title><angular/> Docs Scenario Runner</title> +  <script type="text/javascript" src="../angular-scenario.js" ng:autobind></script> +  <script type="text/javascript" src="docs-scenario.js"></script> +</head> +<body> +</body> +</html>
\ 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 @@ +<!doctype html> +<html xmlns:ng="http://angularjs.org/" +      xmlns:doc="http://docs.angularjs.org/" +      ng:controller="DocsController"> +<head> +  <title ng:bind-template="<angular/>: {{getTitle()}}"><angular/></title> + +  <meta name="fragment" content="!"> + +  <link rel="stylesheet" href="doc_widgets.css" type="text/css" /> +  <link rel="stylesheet" href="docs.css" type="text/css"/> +  <link rel="stylesheet" href="http://alexgorbatchev.com/pub/sh/current/styles/shCore.css" type="text/css"/> +  <link rel="stylesheet" href="http://alexgorbatchev.com/pub/sh/current/styles/shThemeDefault.css" type="text/css"/> + +  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script> +  <script src="http://alexgorbatchev.com/pub/sh/current/scripts/shCore.js"></script> +  <script src="http://alexgorbatchev.com/pub/sh/current/scripts/shBrushJScript.js"></script> +  <script src="http://alexgorbatchev.com/pub/sh/current/scripts/shBrushXml.js"></script> + +  <script src="../angular.min.js" ng:autobind></script> +  <script src="docs.js"></script> +  <script src="doc_widgets.js"></script> +  <script src="docs-keywords.js"></script> +</head> +<body style="display:none;" ng:show="true"> +  <div id="header"> +    <h1> +      <span class="main-title">{{getTitle()}}</span> +      <a href="#"><span class="angular"><angular/></span> Docs</a> +    </h1> +  </div> +  <div id="sidebar"> +    <input type="text" name="search" id="search-box" placeholder="search the docs"/> +    <ul id="api-list"> +      <li ng:repeat="page in pages.$filter(search)" ng:class="getClass(page)"> +        <a href="{{getUrl(page)}}" ng:click="">{{page.name | short}}</a> +      </li> +    </ul> +  </div> +  <div id="main"> +    <a id="feedback" ng:href="{{getFeedbackUrl()}}">Report an Issue or Ask a Question</a> +    <ng:include src="getCurrentPartial()" onload="afterPartialLoaded()"></ng:include> +  </div> +</body> +</html> 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); +  }); +}; | 
