mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-17 15:40:22 +00:00
- e2e tests will run index.html (without jquery) and with index-jq.html(with jquery). - many small changes to make e2e tests work withough JQuery as we discover problems that were previously hidden by using real JQuery.
751 lines
21 KiB
JavaScript
751 lines
21 KiB
JavaScript
/**
|
||
* 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 htmlEscape = require('dom.js').htmlEscape;
|
||
var NEW_LINE = /\n\r?/;
|
||
|
||
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;
|
||
}
|
||
this.scenarios = this.scenarios || [];
|
||
this.requires = this.requires || [];
|
||
this.param = this.param || [];
|
||
this.properties = this.properties || [];
|
||
this.methods = this.methods || [];
|
||
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 = {};
|
||
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(' ');
|
||
},
|
||
|
||
/**
|
||
* 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\./,
|
||
parts = trim(text).split(/(<pre>[\s\S]*?<\/pre>|<doc:(\S*).*?>[\s\S]*?<\/doc:\2>)/);
|
||
|
||
parts.forEach(function(text, i) {
|
||
|
||
function isDocWidget(name) {
|
||
if ((i + 1) % 3 != 2) return false;
|
||
if (name) return parts[i+1] == name;
|
||
return !!parts[i+1];
|
||
}
|
||
|
||
// ignore each third item which is doc widget tag
|
||
if (!((i + 1) % 3)) {
|
||
parts[i] = '';
|
||
return;
|
||
}
|
||
|
||
if (text.match(/^<pre>/)) {
|
||
text = text.replace(/^<pre>([\s\S]*)<\/pre>/mi, function(_, content){
|
||
var clazz = 'brush: js;';
|
||
if (content.match(/\<\w/)) {
|
||
// we are HTML
|
||
clazz += ' html-script: true;';
|
||
}
|
||
return '<div ng:non-bindable><pre class="' + clazz +'">' +
|
||
content.replace(/</g, '<').replace(/>/g, '>') +
|
||
'</pre></div>';
|
||
});
|
||
} else if (isDocWidget('example')) {
|
||
text = text.replace(/(<doc:source>)([\s\S]*)(<\/doc:source>)/mi,
|
||
function(_, before, content, after){
|
||
return '<pre class="doc-source">' + htmlEscape(content) + '</pre>';
|
||
});
|
||
text = text.replace(/(<doc:scenario>)([\s\S]*)(<\/doc:scenario>)/mi,
|
||
function(_, before, content, after){
|
||
self.scenarios.push(content);
|
||
return '<pre class="doc-scenario">' + htmlEscape(content) + '</pre>';
|
||
});
|
||
} else if (!isDocWidget()) {
|
||
text = text.replace(/<angular\/>/gm, '<tt><angular/></tt>');
|
||
text = text.replace(/{@link\s+([^\s}]+)\s*([^}]*?)\s*}/g,
|
||
function(_all, url, title){
|
||
var isFullUrl = url.match(IS_URL),
|
||
isAngular = url.match(IS_ANGULAR),
|
||
absUrl = isFullUrl ? url : self.convertUrlToAbsolute(url);
|
||
|
||
if (!isFullUrl) self.links.push(absUrl);
|
||
|
||
return '<a href="' + (isFullUrl ? '' + url : '#!/' + absUrl) + '">'
|
||
+ (isAngular ? '<code>' : '')
|
||
+ (title || url).replace(/\n/g, ' ')
|
||
+ (isAngular ? '</code>' : '')
|
||
+ '</a>';
|
||
});
|
||
text = new Showdown.converter().makeHtml(text);
|
||
}
|
||
parts[i] = text;
|
||
});
|
||
return parts.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.id = this.id // if we have an id just use it
|
||
|| (((this.file||'').match(/.*\/([^\/]*)\.ngdoc/)||{})[1]) // 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'));
|
||
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:self.markdown(text.replace(match[0], match[7])),
|
||
type: match[1],
|
||
optional: !!match[2],
|
||
'default':match[6]
|
||
};
|
||
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: self.markdown(text.replace(match[0], match[2]))
|
||
};
|
||
} else if(atName == 'requires') {
|
||
var match = text.match(/^([^\s]*)\s*([\S\s]*)/);
|
||
self.requires.push({
|
||
name: match[1],
|
||
text: self.markdown(match[2])
|
||
});
|
||
} 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[1],
|
||
name: match[2],
|
||
description: self.markdown(text.replace(match[0], match[4]))
|
||
};
|
||
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('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/angular.service." + 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);
|
||
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;
|
||
dom.h('Usage', function(){
|
||
dom.code(function(){
|
||
dom.text(self.name.split('service.').pop());
|
||
dom.text('(');
|
||
self.parameters(dom, ', ');
|
||
dom.text(');');
|
||
});
|
||
|
||
self.html_usage_parameters(dom);
|
||
self.html_usage_this(dom);
|
||
self.html_usage_returns(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(){
|
||
dom.tag('pre', {'class':"brush: js; html-script: true;"}, function(){
|
||
dom.text('<' + self.element + ' ');
|
||
dom.text(self.shortName);
|
||
if (self.param.length) {
|
||
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);
|
||
self.parameters(dom, ':', true);
|
||
dom.text(' }}');
|
||
});
|
||
});
|
||
|
||
dom.h('In JavaScript', function(){
|
||
dom.tag('code', function(){
|
||
dom.text('angular.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_formatter: function(dom){
|
||
var self = this;
|
||
dom.h('Usage', function(){
|
||
dom.h('In HTML Template Binding', function(){
|
||
dom.code(function(){
|
||
if (self.inputType=='select')
|
||
dom.text('<select name="bindExpression"');
|
||
else
|
||
dom.text('<input type="text" name="bindExpression"');
|
||
dom.text(' ng:format="');
|
||
dom.text(self.shortName);
|
||
self.parameters(dom, ':', false, true);
|
||
dom.text('">');
|
||
});
|
||
});
|
||
|
||
dom.h('In JavaScript', function(){
|
||
dom.code(function(){
|
||
dom.text('var userInputString = angular.formatter.');
|
||
dom.text(self.shortName);
|
||
dom.text('.format(modelValue');
|
||
self.parameters(dom, ', ', false, true);
|
||
dom.text(');');
|
||
dom.text('\n');
|
||
dom.text('var modelValue = angular.formatter.');
|
||
dom.text(self.shortName);
|
||
dom.text('.parse(userInputString');
|
||
self.parameters(dom, ', ', false, true);
|
||
dom.text(');');
|
||
});
|
||
});
|
||
|
||
self.html_usage_parameters(dom);
|
||
self.html_usage_this(dom);
|
||
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);
|
||
self.parameters(dom, ':', true);
|
||
dom.text('"/>');
|
||
});
|
||
});
|
||
|
||
dom.h('In JavaScript', function(){
|
||
dom.code(function(){
|
||
dom.text('angular.validator.');
|
||
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_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.length) {
|
||
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){
|
||
dom.html(this.description);
|
||
},
|
||
|
||
html_usage_service: 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);
|
||
});
|
||
}
|
||
|
||
dom.h('Methods', this.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);
|
||
});
|
||
});
|
||
dom.h('Properties', this.properties, function(property){
|
||
dom.h(property.name, function(){
|
||
dom.html(property.description);
|
||
dom.h('Example', property.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;
|
||
});
|
||
}
|
||
|
||
};
|
||
//////////////////////////////////////////////////////////
|
||
|
||
|
||
//////////////////////////////////////////////////////////
|
||
function scenarios(docs){
|
||
var specs = [];
|
||
|
||
specs.push('describe("angular without jquery", function() {');
|
||
appendSpecs('index.html');
|
||
specs.push('});');
|
||
|
||
specs.push('');
|
||
specs.push('');
|
||
|
||
specs.push('describe("angular with jquery", function() {');
|
||
appendSpecs('index-jq.html');
|
||
specs.push('});');
|
||
|
||
return specs.join('\n');
|
||
|
||
function appendSpecs(htmlFile) {
|
||
docs.forEach(function(doc){
|
||
specs.push(' describe("' + doc.section + '/' + doc.id + '", function(){');
|
||
specs.push(' beforeEach(function(){');
|
||
specs.push(' browser().navigateTo("' + htmlFile + '#!/' + doc.section + '/' + doc.id + '");');
|
||
specs.push(' });');
|
||
specs.push(' ');
|
||
doc.scenarios.forEach(function(scenario){
|
||
specs.push(indent(trim(scenario), 4));
|
||
specs.push('');
|
||
});
|
||
specs.push('});');
|
||
specs.push('');
|
||
});
|
||
}
|
||
}
|
||
|
||
|
||
//////////////////////////////////////////////////////////
|
||
function metadata(docs){
|
||
var words = [];
|
||
docs.forEach(function(doc){
|
||
var path = (doc.name || '').split(/(\.|\:\s+)/);
|
||
for ( var i = 1; i < path.length; i++) {
|
||
path.splice(i, 1);
|
||
}
|
||
var depth = path.length - 1;
|
||
var shortName = path.pop();
|
||
words.push({
|
||
section: doc.section,
|
||
id: doc.id,
|
||
name: doc.name,
|
||
depth: depth,
|
||
shortName: shortName,
|
||
type: doc.ngdoc,
|
||
keywords:doc.keywords()
|
||
});
|
||
});
|
||
words.sort(keywordSort);
|
||
return words;
|
||
}
|
||
|
||
var KEYWORD_PRIORITY = {
|
||
'.index': 1,
|
||
'.guide': 2,
|
||
'.angular': 7,
|
||
'.angular.Array': 7,
|
||
'.angular.Object': 7,
|
||
'.angular.directive': 7,
|
||
'.angular.filter': 7,
|
||
'.angular.formatter': 7,
|
||
'.angular.scope': 7,
|
||
'.angular.service': 7,
|
||
'.angular.validator': 7,
|
||
'.angular.widget': 7,
|
||
'.angular.mock': 8,
|
||
'.dev_guide.overview': 1,
|
||
'.dev_guide.bootstrap': 2,
|
||
'.dev_guide.mvc': 3,
|
||
'.dev_guide.scopes': 4,
|
||
'.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('.');
|
||
}
|
||
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 indent(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) {
|
||
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')) {
|
||
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];
|
||
};
|
||
}
|