apidoc-core
Version:
Core parser library to generate apidoc result following the apidoc-spec
181 lines (159 loc) • 6.72 kB
JavaScript
var _ = require('lodash');
var semver = require('semver');
var WorkerError = require('../errors/worker_error');
// Additional information for error log
var _messages = {
common: {
element: 'apiUse',
usage : '@apiUse group',
example: '@apiDefine MyValidGroup Some title\n@apiUse MyValidGroup'
}
};
/**
* PreProcess
*
* @param {Object[]} parsedFiles
* @param {String[]} filenames
* @param {Object} packageInfos
* @param {String} target Target path in preProcess-Object (returned result), where the data should be set.
* @returns {Object}
*/
function preProcess(parsedFiles, filenames, packageInfos, target) {
target = target || 'define';
var source = target; // relative path to the tree (global.), from where the data should be fetched.
var result = {};
result[target] = {};
parsedFiles.forEach(function(parsedFile) {
parsedFile.forEach(function(block) {
if (block.global[source]) {
var name = block.global[source].name;
var version = block.version || packageInfos.defaultVersion;
if ( ! result[target][name])
result[target][name] = {};
// fetch from local
result[target][name][version] = block.local;
}
});
});
if (result[target].length === 0)
delete result[target];
return result;
}
/**
* PostProcess
*
* @param {Object[]} parsedFiles
* @param {String[]} filenames
* @param {Object[]} preProcess
* @param {Object} packageInfos
* @param {String} source Source path in preProcess-Object
* @param {String} target Target path in preProcess-Object (returned result), where the data should be set.
* @param {String} messages
*/
function postProcess(parsedFiles, filenames, preProcess, packageInfos, source, target, messages) {
source = source || 'define';
target = target || 'use';
messages = messages || _messages;
parsedFiles.forEach(function(parsedFile, parsedFileIndex) {
parsedFile.forEach(function(block) {
var loopCounter = 0; //add a loop counter to have a break condition when the recursion depth exceed a predifined limit
while (block.local[target]) {
if (loopCounter > 10) {
throw new WorkerError('recursion depth exceeds limit with @apiUse',
filenames[parsedFileIndex],
block.index,
messages.common.element,
messages.common.usage,
messages.common.example,
[
{ 'Groupname': block.name }
]
);
}
//create a copy of the elements for save iterating of the elements
var blockClone = block.local[target].slice();
// remove unneeded target before starting the loop, to allow a save insertion of new elements
// TODO: create a cleanup filter
delete block.local[target];
for (var blockIndex = 0; blockIndex < blockClone.length; ++blockIndex) {
var definition = blockClone[blockIndex];
var name = definition.name;
var version = block.version || packageInfos.defaultVersion;
if ( ! preProcess[source] || ! preProcess[source][name]) {
throw new WorkerError('Referenced groupname does not exist / it is not defined with @apiDefine.',
filenames[parsedFileIndex],
block.index,
messages.common.element,
messages.common.usage,
messages.common.example,
[
{ 'Groupname': name }
]
);
}
var matchedData = {};
if (preProcess[source][name][version]) {
// found the version
matchedData = preProcess[source][name][version];
} else {
// find nearest matching version
var foundIndex = -1;
var lastVersion = packageInfos.defaultVersion;
var versionKeys = Object.keys(preProcess[source][name]);
for (var versionIndex = 0; versionIndex < versionKeys.length; ++versionIndex) {
var currentVersion = versionKeys[versionIndex];
if (semver.gte(version, currentVersion) && semver.gte(currentVersion, lastVersion)) {
lastVersion = currentVersion;
foundIndex = versionIndex;
}
}
if (foundIndex === -1) {
throw new WorkerError('Referenced definition has no matching or a higher version. ' +
'Check version number in referenced define block.',
filenames[parsedFileIndex],
block.index,
messages.common.element,
messages.common.usage,
messages.common.example,
[
{ 'Groupname': name },
{ 'Version': version },
{ 'Defined versions': versionKeys },
]
);
}
var versionName = versionKeys[foundIndex];
matchedData = preProcess[source][name][versionName];
}
// copy matched elements into parsed block
_recursiveMerge(block.local, matchedData);
}
}
});
});
}
/**
* Recursive Merge of Objects with Arrays.
*
* @param block
* @param matchedData
* @todo Bad Hack - watch for something better
*/
function _recursiveMerge(block, matchedData) {
_.mergeWith(block, matchedData, function(a, b) {
if(a instanceof Array) {
return a.concat(b);
}
if(_.isObject(a)) {
_recursiveMerge(a, b);
}
return a;
});
}
/**
* Exports
*/
module.exports = {
preProcess : preProcess,
postProcess: postProcess
};