'use strict' var extend = require('extend') var bail = require('bail') var vfile = require('vfile') var trough = require('trough') var plain = require('is-plain-obj') // Expose a frozen processor. module.exports = unified().freeze() var slice = [].slice var own = {}.hasOwnProperty // Process pipeline. var pipeline = trough() .use(pipelineParse) .use(pipelineRun) .use(pipelineStringify) function pipelineParse(p, ctx) { ctx.tree = p.parse(ctx.file) } function pipelineRun(p, ctx, next) { p.run(ctx.tree, ctx.file, done) function done(err, tree, file) { if (err) { next(err) } else { ctx.tree = tree ctx.file = file next() } } } function pipelineStringify(p, ctx) { ctx.file.contents = p.stringify(ctx.tree, ctx.file) } // Function to create the first processor. function unified() { var attachers = [] var transformers = trough() var namespace = {} var frozen = false var freezeIndex = -1 // Data management. processor.data = data // Lock. processor.freeze = freeze // Plugins. processor.attachers = attachers processor.use = use // API. processor.parse = parse processor.stringify = stringify processor.run = run processor.runSync = runSync processor.process = process processor.processSync = processSync // Expose. return processor // Create a new processor based on the processor in the current scope. function processor() { var destination = unified() var length = attachers.length var index = -1 while (++index < length) { destination.use.apply(null, attachers[index]) } destination.data(extend(true, {}, namespace)) return destination } // Freeze: used to signal a processor that has finished configuration. // // For example, take unified itself: it’s frozen. // Plugins should not be added to it. // Rather, it should be extended, by invoking it, before modifying it. // // In essence, always invoke this when exporting a processor. function freeze() { var values var plugin var options var transformer if (frozen) { return processor } while (++freezeIndex < attachers.length) { values = attachers[freezeIndex] plugin = values[0] options = values[1] transformer = null if (options === false) { continue } if (options === true) { values[1] = undefined } transformer = plugin.apply(processor, values.slice(1)) if (typeof transformer === 'function') { transformers.use(transformer) } } frozen = true freezeIndex = Infinity return processor } // Data management. // Getter / setter for processor-specific informtion. function data(key, value) { if (typeof key === 'string') { // Set `key`. if (arguments.length === 2) { assertUnfrozen('data', frozen) namespace[key] = value return processor } // Get `key`. return (own.call(namespace, key) && namespace[key]) || null } // Set space. if (key) { assertUnfrozen('data', frozen) namespace = key return processor } // Get space. return namespace } // Plugin management. // // Pass it: // * an attacher and options, // * a preset, // * a list of presets, attachers, and arguments (list of attachers and // options). function use(value) { var settings assertUnfrozen('use', frozen) if (value === null || value === undefined) { // Empty. } else if (typeof value === 'function') { addPlugin.apply(null, arguments) } else if (typeof value === 'object') { if ('length' in value) { addList(value) } else { addPreset(value) } } else { throw new Error('Expected usable value, not `' + value + '`') } if (settings) { namespace.settings = extend(namespace.settings || {}, settings) } return processor function addPreset(result) { addList(result.plugins) if (result.settings) { settings = extend(settings || {}, result.settings) } } function add(value) { if (typeof value === 'function') { addPlugin(value) } else if (typeof value === 'object') { if ('length' in value) { addPlugin.apply(null, value) } else { addPreset(value) } } else { throw new Error('Expected usable value, not `' + value + '`') } } function addList(plugins) { var length var index if (plugins === null || plugins === undefined) { // Empty. } else if (typeof plugins === 'object' && 'length' in plugins) { length = plugins.length index = -1 while (++index < length) { add(plugins[index]) } } else { throw new Error('Expected a list of plugins, not `' + plugins + '`') } } function addPlugin(plugin, value) { var entry = find(plugin) if (entry) { if (plain(entry[1]) && plain(value)) { value = extend(entry[1], value) } entry[1] = value } else { attachers.push(slice.call(arguments)) } } } function find(plugin) { var length = attachers.length var index = -1 var entry while (++index < length) { entry = attachers[index] if (entry[0] === plugin) { return entry } } } // Parse a file (in string or vfile representation) into a unist node using // the `Parser` on the processor. function parse(doc) { var file = vfile(doc) var Parser freeze() Parser = processor.Parser assertParser('parse', Parser) if (newable(Parser, 'parse')) { return new Parser(String(file), file).parse() } return Parser(String(file), file) // eslint-disable-line new-cap } // Run transforms on a unist node representation of a file (in string or // vfile representation), async. function run(node, file, cb) { assertNode(node) freeze() if (!cb && typeof file === 'function') { cb = file file = null } if (!cb) { return new Promise(executor) } executor(null, cb) function executor(resolve, reject) { transformers.run(node, vfile(file), done) function done(err, tree, file) { tree = tree || node if (err) { reject(err) } else if (resolve) { resolve(tree) } else { cb(null, tree, file) } } } } // Run transforms on a unist node representation of a file (in string or // vfile representation), sync. function runSync(node, file) { var complete = false var result run(node, file, done) assertDone('runSync', 'run', complete) return result function done(err, tree) { complete = true bail(err) result = tree } } // Stringify a unist node representation of a file (in string or vfile // representation) into a string using the `Compiler` on the processor. function stringify(node, doc) { var file = vfile(doc) var Compiler freeze() Compiler = processor.Compiler assertCompiler('stringify', Compiler) assertNode(node) if (newable(Compiler, 'compile')) { return new Compiler(node, file).compile() } return Compiler(node, file) // eslint-disable-line new-cap } // Parse a file (in string or vfile representation) into a unist node using // the `Parser` on the processor, then run transforms on that node, and // compile the resulting node using the `Compiler` on the processor, and // store that result on the vfile. function process(doc, cb) { freeze() assertParser('process', processor.Parser) assertCompiler('process', processor.Compiler) if (!cb) { return new Promise(executor) } executor(null, cb) function executor(resolve, reject) { var file = vfile(doc) pipeline.run(processor, {file: file}, done) function done(err) { if (err) { reject(err) } else if (resolve) { resolve(file) } else { cb(null, file) } } } } // Process the given document (in string or vfile representation), sync. function processSync(doc) { var complete = false var file freeze() assertParser('processSync', processor.Parser) assertCompiler('processSync', processor.Compiler) file = vfile(doc) process(file, done) assertDone('processSync', 'process', complete) return file function done(err) { complete = true bail(err) } } } // Check if `value` is a constructor. function newable(value, name) { return ( typeof value === 'function' && value.prototype && // A function with keys in its prototype is probably a constructor. // Classes’ prototype methods are not enumerable, so we check if some value // exists in the prototype. (keys(value.prototype) || name in value.prototype) ) } // Check if `value` is an object with keys. function keys(value) { var key for (key in value) { return true } return false } // Assert a parser is available. function assertParser(name, Parser) { if (typeof Parser !== 'function') { throw new Error('Cannot `' + name + '` without `Parser`') } } // Assert a compiler is available. function assertCompiler(name, Compiler) { if (typeof Compiler !== 'function') { throw new Error('Cannot `' + name + '` without `Compiler`') } } // Assert the processor is not frozen. function assertUnfrozen(name, frozen) { if (frozen) { throw new Error( 'Cannot invoke `' + name + '` on a frozen processor.\nCreate a new processor first, by invoking it: use `processor()` instead of `processor`.' ) } } // Assert `node` is a unist node. function assertNode(node) { if (!node || typeof node.type !== 'string') { throw new Error('Expected node, got `' + node + '`') } } // Assert that `complete` is `true`. function assertDone(name, asyncName, complete) { if (!complete) { throw new Error( '`' + name + '` finished async. Use `' + asyncName + '` instead' ) } }