diff --git a/lib/doc/apply-fp-mapping.js b/lib/doc/apply-fp-mapping.js new file mode 100644 index 000000000..1f26088ce --- /dev/null +++ b/lib/doc/apply-fp-mapping.js @@ -0,0 +1,122 @@ +var _ = require('lodash'), + j = require('jscodeshift'), + Entry = require('docdown/lib/entry'); + +var baseGetParams = Entry.prototype.getParams; + +// Function copied from docdown/lib/entry that is not exported. +function getMultilineValue(string, tagName) { + var prelude = tagName == 'description' ? '^ */\\*\\*(?: *\\n *\\* *)?' : ('^ *\\*[\\t ]*@' + _.escapeRegExp(tagName) + '\\b'), + postlude = '(?=\\*\\s+\\@[a-z]|\\*/)', + result = _.result(RegExp(prelude + '([\\s\\S]*?)' + postlude, 'gm').exec(string), 1, ''); + + return _.trim(result.replace(RegExp('(?:^|\\n)[\\t ]*\\*[\\t ]' + (tagName == 'example' ? '?' : '*'), 'g'), '\n')); +} + +/** + * Extracts the entry's `name` data. + * Sub-part of Entry.prototype.getCall() that fetches the name. Using `Entry.prototype.getCall()` + * makes a call to getParams(), which itself call getBaseName --> infinite recursion. + * + * @param {Object} entry Entry whose name to extract. + * @returns {string} The entry's `name` data. + */ +function getBaseName(entry) { + var result = /\*\/\s*(?:function\s+([^(]*)|(.*?)(?=[:=,]))/.exec(entry); + if (result) { + result = (result[1] || result[2]).split('.').pop(); + result = _.trim(_.trim(result), "'").split('var ').pop(); + result = _.trim(result); + } + // Get the function name. + return _.result(/\*[\t ]*@name\s+(.+)/.exec(entry), 1, result || ''); +} + +/** + * Reorders `params` for a given function definition/call. + * + * @param {Object} mapping Mapping object that defines if and how the `params` will be reordered. + * @param {String} name Name of the function associated to the call/function definition. + * @param {*[]} params Parameters/arguments to reorder. + * @returns {*[]} Reordered parameters/arguments. + */ +function reorderParams(mapping, name, params) { + // Check if reordering is needed. + if (!mapping || mapping.skipRearg[name]) { + return params; + } + var reargOrder = mapping.methodRearg[name] || mapping.aryRearg[params.length]; + if (!reargOrder) { + return params; + } + // Reorder params. + var newParams = []; + reargOrder.forEach(function(newPosition, index) { + newParams[newPosition] = params[index]; + }); + return newParams; +} + +/** + * Returns a function that extracts the entry's `param` data, reordered according to `mapping`. + * + * @param {Object} mapping Mapping object that defines if and how the `params` will be reordered. + * @returns {Function} Function that returns the entry's `param` data. + */ +function getReorderedParams(mapping) { + return function(index) { + if (!this._params) { + // Call baseGetParams in order to compute `this._params`. + baseGetParams.call(this); + // Reorder params according to the `mapping`. + this._params = reorderParams(mapping, getBaseName(this.entry), this._params); + } + return baseGetParams.call(this, index); + }; +} + +/** + * Updates a code sample so that the arguments in the call are reordered according to `mapping`. + * + * @param {Object} mapping Mapping object that defines if and how the arguments will be reordered. + * @param {string} codeSample Code sample to update. + * @returns {string} Updated code sample. + */ +function reorderParamsInExample(mapping, codeSample) { + return j(codeSample) + .find(j.CallExpression, { callee: { object: {name: '_' }}}) + .replaceWith(function(callExpr) { + var value = callExpr.value; + return j.callExpression( + value.callee, + reorderParams(mapping, value.callee.property.name, value.arguments) + ); + }) + .toSource(); +} + +/** + * Returns a function that extracts the entry's `example` data, + * where function call arguments are reordered according to `mapping`. + * + * @param {Object} mapping Mapping object that defines if and how the `params` will be reordered. + * @returns {Function} Function that returns the entry's `example` data. + */ +function getReorderedExample(mapping) { + return function() { + var result = getMultilineValue(this.entry, 'example'); + if (!result) { + return result; + } + var resultReordered = reorderParamsInExample(mapping, result); + return '```' + this.lang + '\n' + resultReordered + '\n```'; + }; +} + +/** + * Updates `docdown` `Entry`'s prototype so that parameters/arguments are reordered according to `mapping`. + */ +module.exports = function applyFPMapping(mapping) { + Entry.prototype.getParams = getReorderedParams(mapping); + Entry.prototype.getExample = getReorderedExample(mapping); +}; diff --git a/lib/doc/build.js b/lib/doc/build.js index 92b72dff5..1ca1e9611 100644 --- a/lib/doc/build.js +++ b/lib/doc/build.js @@ -5,6 +5,9 @@ var _ = require('lodash'), fs = require('fs-extra'), path = require('path'); +var mapping = require('../../fp/_mapping'), + applyFPMapping = require('./apply-fp-mapping'); + var basePath = path.join(__dirname, '..', '..'), docPath = path.join(basePath, 'doc'), readmePath = path.join(docPath, 'README.md'), @@ -46,7 +49,10 @@ function onComplete(error) { } } -function build(type) { +function build(fpFlag, type) { + if (fpFlag) { + applyFPMapping(mapping); + } var options = _.defaults({}, config.base, config[type]), markdown = docdown(options), filePath = fpFlag ? fpReadmePath : readmePath; @@ -54,4 +60,4 @@ function build(type) { fs.writeFile(filePath, postprocess(markdown), onComplete); } -build(_.last(process.argv)); +build(_.includes(process.argv, '--fp'), _.last(process.argv)); diff --git a/package.json b/package.json index a4c047ea3..40433ada8 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "glob": "^6.0.4", "istanbul": "0.4.2", "jquery": "^2.2.0", + "jscodeshift": "^0.3.13", "jscs": "^2.9.0", "lodash": "^3.10.1", "platform": "^1.3.1", @@ -35,6 +36,8 @@ "build:main": "node lib/main/build-dist.js", "build:main-modules": "node lib/main/build-modules.js", "doc": "node lib/doc/build github", + "doc:fp": "node lib/doc/build --fp github", + "doc:fp:site": "node lib/doc/build --fp site", "doc:site": "node lib/doc/build site", "pretest": "npm run build", "style": "npm run style:main & npm run style:fp & npm run style:perf & npm run style:test",