angular.js/docs/src/ngdoc.js
Dave Geddes 7a77fdae4f chore(Grunt): switch from Rake to Grunt
Migrates the Angular project from Rake to Grunt.

Benefits:
- Drops Ruby dependency
- Lowers barrier to entry for contributions from JavaScript ninjas
- Simplifies the Angular project setup and build process
- Adopts industry-standard tools specific to JavaScript projects
- Support building angular.js on Windows platform (really?!? why?!?)

BREAKING CHANGE: Rake is completely replaced by Grunt. Below are the deprecated Rake tasks and their Grunt equivalents:

rake --> grunt
rake package --> grunt package
rake init --> N/A
rake clean --> grunt clean
rake concat_scenario --> grunt build:scenario
rake concat --> grunt build
rake concat_scenario --> grunt build:scenario
rake minify --> grunt minify
rake version --> grunt write:version
rake docs --> grunt docs
rake webserver --> grunt webserver
rake test --> grunt test
rake test:unit --> grunt test:unit
rake test:<jqlite|jquery|modules|e2e> --> grunt test:<jqlite|jquery|modules|end2end|e2e>
rake test[Firefox+Safari] --> grunt test --browsers Firefox,Safari
rake test[Safari] --> grunt test --browsers Safari
rake autotest --> grunt autotest

NOTES:
* For convenience grunt test:e2e starts a webserver for you, while grunt test:end2end doesn't.
  Use grunt test:end2end if you already have the webserver running.
* Removes duplicate entry for Describe.js in the angularScenario section of angularFiles.js
* Updates docs/src/gen-docs.js to use #done intead of the deprecated #end
* Uses grunt-contrib-connect instead of lib/nodeserver (removed)
* Removes nodeserver.sh, travis now uses grunt webserver
* Built and minified files are identical to Rake's output, with the exception of one less
  character for git revisions (using --short) and a couple minor whitespace differences

Closes #199

Conflicts:

	Rakefile
2013-03-05 23:35:13 -08:00

940 lines
27 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* All parsing/transformation code goes here. All code here should be sync to ease testing.
*/
var Showdown = require('../../lib/showdown').Showdown;
var DOM = require('./dom.js').DOM;
var htmlEscape = require('./dom.js').htmlEscape;
var Example = require('./example.js').Example;
var NEW_LINE = /\n\r?/;
var globalID = 0;
exports.trim = trim;
exports.metadata = metadata;
exports.scenarios = scenarios;
exports.merge = merge;
exports.Doc = Doc;
var BOOLEAN_ATTR = {};
['multiple', 'selected', 'checked', 'disabled', 'readOnly', 'required'].forEach(function(value) {
BOOLEAN_ATTR[value] = true;
});
//////////////////////////////////////////////////////////
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;
}
this.scenarios = this.scenarios || [];
this.requires = this.requires || [];
this.param = this.param || [];
this.properties = this.properties || [];
this.methods = this.methods || [];
this.events = this.events || [];
this.links = this.links || [];
}
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 = {};
var words = [];
Doc.METADATA_IGNORE.forEach(function(ignore){ keywords[ignore] = true; });
function extractWords(text) {
var tokens = text.toLowerCase().split(/[\.\s,`'"#]+/mg);
tokens.forEach(function(key){
var match = key.match(/^((ng:|[\$_a-z])[\w\-_]+)/);
if (match){
key = match[1];
if (!keywords[key]) {
keywords[key] = true;
words.push(key);
}
}
});
}
extractWords(this.text);
this.properties.forEach(function(prop) {
extractWords(prop.text || prop.description || '');
});
this.methods.forEach(function(method) {
extractWords(method.text || method.description || '');
});
words.sort();
return words.join(' ');
},
/**
* Converts relative urls (without section) into absolute
* Absolute url means url with section
*
* @example
* - if the link is inside any api doc:
* angular.widget -> api/angular.widget
*
* - if the link is inside any guid doc:
* intro -> guide/intro
*
* @param {string} url Absolute or relative url
* @returns {string} Absolute url
*/
convertUrlToAbsolute: function(url) {
if (url.substr(-1) == '/') return url + 'index';
if (url.match(/\//)) return url;
return this.section + '/' + url;
},
markdown: function(text) {
if (!text) return text;
var self = this,
IS_URL = /^(https?:\/\/|ftps?:\/\/|mailto:|\.|\/)/,
IS_ANGULAR = /^(api\/)?(angular|ng|AUTO)\./,
IS_HASH = /^#/,
parts = trim(text).split(/(<pre>[\s\S]*?<\/pre>|<doc:example(\S*).*?>[\s\S]*?<\/doc:example>|<example[^>]*>[\s\S]*?<\/example>)/),
seq = 0,
placeholderMap = {};
function placeholder(text) {
var id = 'REPLACEME' + (seq++);
placeholderMap[id] = text;
return id;
}
parts.forEach(function(text, i) {
parts[i] = (text || '').
replace(/<example(?:\s+module="([^"]*)")?(?:\s+deps="([^"]*)")?>([\s\S]*?)<\/example>/gmi, function(_, module, deps, content) {
var example = new Example(self.scenarios);
example.setModule(module);
example.addDeps(deps);
content.replace(/<file\s+name="([^"]*)"\s*>([\s\S]*?)<\/file>/gmi, function(_, name, content) {
example.addSource(name, content);
});
return placeholder(example.toHtml());
}).
replace(/^<doc:example(\s+[^>]*)?>([\s\S]*)<\/doc:example>/mi, function(_, attrs, content) {
var html, script, scenario,
example = new Example(self.scenarios);
example.setModule((attrs||'module=""').match(/^\s*module=["'](.*)["']\s*$/)[1]);
content.
replace(/<doc:source(\s+[^>]*)?>([\s\S]*)<\/doc:source>/mi, function(_, attrs, content) {
example.addSource('index.html', content.
replace(/<script>([\s\S]*)<\/script>/mi, function(_, script) {
example.addSource('script.js', script);
return '';
}).
replace(/<style>([\s\S]*)<\/style>/mi, function(_, style) {
example.addSource('style.css', style);
return '';
})
);
}).
replace(/(<doc:scenario>)([\s\S]*)(<\/doc:scenario>)/mi, function(_, before, content){
example.addSource('scenario.js', content);
});
return placeholder(example.toHtml());
}).
replace(/^<pre>([\s\S]*?)<\/pre>/mi, function(_, content){
return placeholder(
'<pre class="prettyprint linenums">' +
content.replace(/</g, '&lt;').replace(/>/g, '&gt;') +
'</pre>');
}).
replace(/<div([^>]*)><\/div>/, '<div$1>\n<\/div>').
replace(/{@link\s+([^\s}]+)\s*([^}]*?)\s*}/g, function(_all, url, title){
var isFullUrl = url.match(IS_URL),
isAngular = url.match(IS_ANGULAR),
isHash = url.match(IS_HASH),
absUrl = isHash
? url
: (isFullUrl ? url : self.convertUrlToAbsolute(url));
if (!isFullUrl) self.links.push(absUrl);
return '<a href="' + absUrl + '">' +
(isAngular ? '<code>' : '') +
(title || url).replace(/^#/g, '').replace(/\n/g, ' ') +
(isAngular ? '</code>' : '') +
'</a>';
});
});
text = parts.join('');
text = new Showdown.converter().makeHtml(text);
text = text.replace(/(?:<p>)?(REPLACEME\d+)(?:<\/p>)?/g, function(_, id) {
return placeholderMap[id];
});
return text;
},
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().trim();
this.id = this.id || // if we have an id just use it
(((this.file||'').match(/.*(\/|\\)([^(\/|\\)]*)\.ngdoc/)||{})[2]) || // try to extract it from file name
this.name; // default to name
this.description = this.markdown(this.description);
this.example = this.markdown(this.example);
this['this'] = this.markdown(this['this']);
return this;
function flush() {
if (atName) {
var text = trim(atText.join('\n')), match;
if (atName == 'param') {
match = text.match(/^\{([^}]+)\}\s+(([^\s=]+)|\[(\S+)=([^\]]+)\])\s+(.*)/);
// 1 1 23 3 4 4 5 5 2 6 6
if (!match) {
throw new Error("Not a valid 'param' format: " + text + ' (found in: ' + self.file + ':' + self.line + ')');
}
var optional = (match[1].slice(-1) === '=');
var param = {
name: match[4] || match[3],
description:self.markdown(text.replace(match[0], match[6])),
type: optional ? match[1].substring(0, match[1].length-1) : match[1],
optional: optional,
'default':match[5]
};
self.param.push(param);
} else if (atName == 'returns' || atName == 'return') {
match = text.match(/^\{([^}]+)\}\s+(.*)/);
if (!match) {
throw new Error("Not a valid 'returns' format: " + text + ' (found in: ' + self.file + ':' + self.line + ')');
}
self.returns = {
type: match[1],
description: self.markdown(text.replace(match[0], match[2]))
};
} else if(atName == 'requires') {
match = text.match(/^([^\s]*)\s*([\S\s]*)/);
self.requires.push({
name: match[1],
text: self.markdown(match[2])
});
} else if(atName == 'property') {
match = text.match(/^\{(\S+)\}\s+(\S+)(\s+(.*))?/);
if (!match) {
throw new Error("Not a valid 'property' format: " + text + ' (found in: ' + self.file + ':' + self.line + ')');
}
var property = new Doc({
type: match[1],
name: match[2],
shortName: match[2],
description: self.markdown(text.replace(match[0], match[4]))
});
self.properties.push(property);
} else if(atName == 'eventType') {
match = text.match(/^([^\s]*)\s+on\s+([\S\s]*)/);
self.type = match[1];
self.target = match[2];
} else {
self[atName] = text;
}
}
}
},
html: function() {
var dom = new DOM(),
self = this;
dom.h(title(this.name), function() {
notice('deprecated', 'Deprecated API', self.deprecated);
if (self.ngdoc != 'overview') {
dom.h('Description', self.description, dom.html);
}
dom.h('Dependencies', self.requires, function(require){
dom.tag('code', function() {
dom.tag('a', {href: 'api/ng.' + require.name}, require.name);
});
dom.html(require.text);
});
(self['html_usage_' + self.ngdoc] || function() {
throw new Error("Don't know how to format @ngdoc: " + self.ngdoc);
}).call(self, dom);
dom.h('Example', self.example, dom.html);
});
return dom.toString();
//////////////////////////
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);
if (param.optional) {
dom.text('=');
}
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_this: function(dom) {
var self = this;
if (self['this']) {
dom.h(function(dom){
dom.html("Method's <code>this</code>");
}, function(dom){
dom.html(self['this']);
});
}
},
html_usage_function: function(dom){
var self = this;
var name = self.name.match(/^angular(\.mock)?\.(\w+)$/) ? self.name : self.name.split(/\./).pop()
dom.h('Usage', function() {
dom.code(function() {
dom.text(name);
dom.text('(');
self.parameters(dom, ', ');
dom.text(');');
});
self.html_usage_parameters(dom);
self.html_usage_this(dom);
self.html_usage_returns(dom);
});
this.method_properties_events(dom);
},
html_usage_property: function(dom){
var self = this;
dom.h('Usage', function() {
dom.code(function() {
dom.text(self.name);
});
self.html_usage_returns(dom);
});
},
html_usage_directive: function(dom){
var self = this;
dom.h('Usage', function() {
var restrict = self.restrict || 'AC';
if (restrict.match(/E/)) {
dom.text('This directive can be used as custom element, but we aware of ');
dom.tag('a', {href:'guide/ie'}, 'IE restrictions');
dom.text('.');
}
if (self.usage) {
dom.tag('pre', function() {
dom.tag('code', function() {
dom.text(self.usage);
});
});
} else {
if (restrict.match(/E/)) {
dom.text('as element:');
dom.code(function() {
dom.text('<');
dom.text(dashCase(self.shortName));
renderParams('\n ', '="', '"');
dom.text('>\n</');
dom.text(dashCase(self.shortName));
dom.text('>');
});
}
if (restrict.match(/A/)) {
var element = self.element || 'ANY';
dom.text('as attribute');
dom.code(function() {
dom.text('<' + element + ' ');
dom.text(dashCase(self.shortName));
renderParams('\n ', '="', '"', true);
dom.text('>\n ...\n');
dom.text('</' + element + '>');
});
}
if (restrict.match(/C/)) {
dom.text('as class');
var element = self.element || 'ANY';
dom.code(function() {
dom.text('<' + element + ' class="');
dom.text(dashCase(self.shortName));
renderParams(' ', ': ', ';', true);
dom.text('">\n ...\n');
dom.text('</' + element + '>');
});
}
}
self.html_usage_directiveInfo(dom);
self.html_usage_parameters(dom);
});
self.method_properties_events(dom);
function renderParams(prefix, infix, suffix, skipSelf) {
(self.param||[]).forEach(function(param) {
var skip = skipSelf && (param.name == self.shortName || param.name.indexOf(self.shortName + '|') == 0);
if (!skip) {
dom.text(prefix);
dom.text(param.optional ? '[' : '');
var parts = param.name.split('|');
dom.text(parts[skipSelf ? 0 : 1] || parts[0]);
}
if (BOOLEAN_ATTR[param.name]) {
dom.text(param.optional ? ']' : '');
} else {
dom.text(BOOLEAN_ATTR[param.name] ? '' : infix );
dom.text(('{' + param.type + '}').replace(/^\{\'(.*)\'\}$/, '$1'));
dom.text(suffix);
dom.text(param.optional && !skip ? ']' : '');
}
});
}
},
html_usage_filter: function(dom){
var self = this;
dom.h('Usage', function() {
dom.h('In HTML Template Binding', function() {
dom.tag('code', function() {
if (self.usage) {
dom.text(self.usage);
} else {
dom.text('{{ ');
dom.text(self.shortName);
dom.text('_expression | ');
dom.text(self.shortName);
self.parameters(dom, ':', true);
dom.text(' }}');
}
});
});
dom.h('In JavaScript', function() {
dom.tag('code', function() {
dom.text('$filter(\'');
dom.text(self.shortName);
dom.text('\')(');
self.parameters(dom, ', ');
dom.text(')');
});
});
self.html_usage_parameters(dom);
self.html_usage_this(dom);
self.html_usage_returns(dom);
});
},
html_usage_inputType: function(dom){
var self = this;
dom.h('Usage', function() {
dom.code(function() {
dom.text('<input type="' + self.shortName + '"');
(self.param||[]).forEach(function(param){
dom.text('\n ');
dom.text(param.optional ? ' [' : ' ');
dom.text(dashCase(param.name));
dom.text(BOOLEAN_ATTR[param.name] ? '' : '="{' + param.type + '}"');
dom.text(param.optional ? ']' : '');
});
dom.text('>');
});
self.html_usage_parameters(dom);
});
},
html_usage_directiveInfo: function(dom) {
var self = this;
var list = [];
if (self.scope !== undefined) {
list.push('This directive creates new scope.');
}
if (self.priority !== undefined) {
list.push('This directive executes at priority level ' + self.priority + '.');
}
if (list.length) {
dom.h('Directive info', function() {
dom.ul(list);
});
}
},
html_usage_overview: function(dom){
dom.html(this.description);
},
html_usage_interface: function(dom){
var self = this;
if (this.param.length) {
dom.h('Usage', function() {
dom.code(function() {
dom.text(self.name.split('.').pop());
dom.text('(');
self.parameters(dom, ', ');
dom.text(');');
});
self.html_usage_parameters(dom);
self.html_usage_this(dom);
self.html_usage_returns(dom);
});
}
this.method_properties_events(dom);
},
html_usage_service: function(dom) {
this.html_usage_interface(dom)
},
html_usage_object: function(dom) {
this.html_usage_interface(dom)
},
method_properties_events: function(dom) {
var self = this;
if (self.methods.length) {
dom.div({class:'member method'}, function(){
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);
self.html_usage_this(dom);
method.html_usage_returns(dom);
dom.h('Example', method.example, dom.html);
});
});
});
}
if (self.properties.length) {
dom.div({class:'member property'}, function(){
dom.h('Properties', self.properties, function(property){
dom.h(property.shortName, function() {
dom.html(property.description);
if (!property.html_usage_returns) {
console.log(property);
}
property.html_usage_returns(dom);
dom.h('Example', property.example, dom.html);
});
});
});
}
if (self.events.length) {
dom.div({class:'member event'}, function(){
dom.h('Events', self.events, function(event){
dom.h(event.shortName, event, function() {
dom.html(event.description);
if (event.type == 'listen') {
dom.tag('div', {class:'inline'}, function() {
dom.h('Listen on:', event.target);
});
} else {
dom.tag('div', {class:'inline'}, function() {
dom.h('Type:', event.type);
});
dom.tag('div', {class:'inline'}, function() {
dom.h('Target:', event.target);
});
}
event.html_usage_parameters(dom);
self.html_usage_this(dom);
dom.h('Example', event.example, dom.html);
});
});
});
}
},
parameters: function(dom, separator, skipFirst, prefix) {
var sep = prefix ? separator : '';
(this.param||[]).forEach(function(param, i){
if (!(skipFirst && i==0)) {
if (param.optional) {
dom.text('[' + sep + param.name + ']');
} else {
dom.text(sep + param.name);
}
}
sep = separator;
});
}
};
//////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////
var GLOBALS = /^angular\.([^\.]+)$/,
MODULE = /^((?:(?!^angular\.)[^\.])+)$/,
MODULE_MOCK = /^angular\.mock\.([^\.]+)$/,
MODULE_DIRECTIVE = /^((?:(?!^angular\.)[^\.])+)\.directive:([^\.]+)$/,
MODULE_DIRECTIVE_INPUT = /^((?:(?!^angular\.)[^\.])+)\.directive:input\.([^\.]+)$/,
MODULE_FILTER = /^((?:(?!^angular\.)[^\.])+)\.filter:([^\.]+)$/,
MODULE_SERVICE = /^((?:(?!^angular\.)[^\.])+)\.([^\.]+?)(Provider)?$/,
MODULE_TYPE = /^((?:(?!^angular\.)[^\.])+)\..+\.([A-Z][^\.]+)$/;
function title(text) {
if (!text) return text;
var match,
module,
type,
name;
if (text == 'angular.Module') {
module = 'ng';
name = 'Module';
type = 'Type';
} else if (match = text.match(GLOBALS)) {
module = 'ng';
name = 'angular.' + match[1];
type = 'API';
} else if (match = text.match(MODULE)) {
module = match[1];
} else if (match = text.match(MODULE_MOCK)) {
module = 'ng';
name = 'angular.mock.' + match[1];
type = 'API';
} else if (match = text.match(MODULE_DIRECTIVE)) {
module = match[1];
name = match[2];
type = 'directive';
} else if (match = text.match(MODULE_DIRECTIVE_INPUT)) {
module = match[1];
name = 'input [' + match[2] + ']';
type = 'directive';
} else if (match = text.match(MODULE_FILTER)) {
module = match[1];
name = match[2];
type = 'filter';
} else if (match = text.match(MODULE_SERVICE)) {
module = match[1];
name = match[2] + (match[3] || '');
type = 'service';
} else if (match = text.match(MODULE_TYPE)) {
module = match[1];
name = match[2];
type = 'type';
} else {
return text;
}
return function() {
this.tag('code', name);
this.tag('span', { class: 'hint'}, function() {
if (type) {
this.text('(');
this.text(type);
this.text(' in module ');
this.tag('code', module);
this.text(')');
}
});
};
}
function scenarios(docs){
var specs = [];
specs.push('describe("angular+jqlite", function() {');
appendSpecs('index-nocache.html#!/');
specs.push('});');
specs.push('');
specs.push('');
specs.push('describe("angular+jquery", function() {');
appendSpecs('index-jq-nocache.html#!/');
specs.push('});');
return specs.join('\n');
function appendSpecs(urlPrefix) {
docs.forEach(function(doc){
specs.push(' describe("' + doc.section + '/' + doc.id + '", function() {');
specs.push(' beforeEach(function() {');
specs.push(' browser().navigateTo("' + urlPrefix + doc.section + '/' + doc.id + '");');
specs.push(' });');
specs.push(' ');
doc.scenarios.forEach(function(scenario){
specs.push(indentCode(trim(scenario), 4));
specs.push('');
});
specs.push('});');
specs.push('');
});
}
}
//////////////////////////////////////////////////////////
function metadata(docs){
var pages = [];
docs.forEach(function(doc){
var path = (doc.name || '').split(/(\.|\:\s*)/);
for ( var i = 1; i < path.length; i++) {
path.splice(i, 1);
}
var shortName = path.pop().trim();
if (path.pop() == 'input') {
shortName = 'input [' + shortName + ']';
}
pages.push({
section: doc.section,
id: doc.id,
name: title(doc.name),
shortName: shortName,
type: doc.ngdoc,
keywords:doc.keywords()
});
});
pages.sort(keywordSort);
return pages;
}
var KEYWORD_PRIORITY = {
'.index': 1,
'.overview': 1,
'.bootstrap': 2,
'.mvc': 3,
'.scopes': 4,
'.compiler': 5,
'.templates': 6,
'.services': 7,
'.di': 8,
'.unit-testing': 9,
'.dev_guide': 9,
'.dev_guide.overview': 1,
'.dev_guide.bootstrap': 2,
'.dev_guide.bootstrap.auto_bootstrap': 1,
'.dev_guide.bootstrap.manual_bootstrap': 2,
'.dev_guide.mvc': 3,
'.dev_guide.mvc.understanding_model': 1,
'.dev_guide.mvc.understanding_controller': 2,
'.dev_guide.mvc.understanding_view': 3,
'.dev_guide.scopes': 4,
'.dev_guide.scopes.understanding_scopes': 1,
'.dev_guide.scopes.internals': 2,
'.dev_guide.compiler': 5,
'.dev_guide.templates': 6,
'.dev_guide.services': 7,
'.dev_guide.di': 8,
'.dev_guide.unit-testing': 9
};
function keywordSort(a, b){
function mangleName(doc) {
var path = doc.id.split(/\./);
var mangled = [];
var partialName = '';
path.forEach(function(name){
partialName += '.' + name;
mangled.push(KEYWORD_PRIORITY[partialName] || 5);
mangled.push(name);
});
return (doc.section + '/' + mangled.join('.')).toLowerCase();
}
var nameA = mangleName(a);
var nameB = mangleName(b);
return nameA < nameB ? -1 : (nameA > nameB ? 1 : 0);
}
//////////////////////////////////////////////////////////
function trim(text) {
var MAX_INDENT = 9999;
var empty = RegExp.prototype.test.bind(/^\s*$/);
var lines = text.split('\n');
var minIndent = MAX_INDENT;
var indentRegExp;
var ignoreLine = (lines[0][0] != ' ' && lines.length > 1);
// ignore first line if it has no indentation and there is more than one line
lines.forEach(function(line){
if (ignoreLine) {
ignoreLine = false;
return;
}
var indent = line.match(/^\s*/)[0].length;
if (indent > 0 || minIndent == MAX_INDENT) {
minIndent = Math.min(minIndent, indent);
}
});
indentRegExp = new RegExp('^\\s{0,' + minIndent + '}');
for ( var i = 0; i < lines.length; i++) {
lines[i] = lines[i].replace(indentRegExp, '');
}
// remove leading lines
while (empty(lines[0])) {
lines.shift();
}
// remove trailing
while (empty(lines[lines.length - 1])) {
lines.pop();
}
return lines.join('\n');
}
function indentCode(text, spaceCount) {
var lines = text.split('\n'),
indent = '',
fixedLines = [];
while(spaceCount--) indent += ' ';
lines.forEach(function(line) {
fixedLines.push(indent + line);
});
return fixedLines.join('\n');
}
//////////////////////////////////////////////////////////
function merge(docs){
var byFullId = {};
docs.forEach(function(doc) {
byFullId[doc.section + '/' + doc.id] = doc;
});
for(var i = 0; i < docs.length;) {
var doc = docs[i];
// check links - do they exist ?
doc.links.forEach(function(link) {
// convert #id to path#id
if (link[0] == '#') {
link = doc.section + '/' + doc.id.split('#').shift() + link;
}
link = link.split('#').shift();
if (!byFullId[link]) {
console.log('WARNING: In ' + doc.section + '/' + doc.id + ', non existing link: "' + link + '"');
}
});
// merge into parents
if (findParent(doc, 'method') || findParent(doc, 'property') || findParent(doc, 'event')) {
docs.splice(i, 1);
} else {
i++;
}
}
function findParent(doc, name) {
var parentName = doc[name + 'Of'];
if (!parentName) return false;
var parent = byFullId['api/' + 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];
};
}
var DASH_CASE_REGEXP = /[A-Z]/g;
function dashCase(name){
return name.replace(DASH_CASE_REGEXP, function(letter, pos) {
return (pos ? '-' : '') + letter.toLowerCase();
});
}