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/ngdoc.js | |
| 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/ngdoc.js')
| -rw-r--r-- | docs/src/ngdoc.js | 614 | 
1 files changed, 614 insertions, 0 deletions
| 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 | 
