| 'use strict' |
| |
| var bail = require('bail') |
| var buffer = require('is-buffer') |
| var extend = require('extend') |
| var plain = require('is-plain-obj') |
| var trough = require('trough') |
| var vfile = require('vfile') |
| |
| // 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) { |
| var result = p.stringify(ctx.tree, ctx.file) |
| var file = ctx.file |
| |
| if (result === undefined || result === null) { |
| // Empty. |
| } else if (typeof result === 'string' || buffer(result)) { |
| file.contents = result |
| } else { |
| file.result = result |
| } |
| } |
| |
| // 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' |
| ) |
| } |
| } |