You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
460 lines
10 KiB
460 lines
10 KiB
'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(error, tree, file) { |
|
if (error) { |
|
next(error) |
|
} else { |
|
ctx.tree = tree |
|
ctx.file = file |
|
next() |
|
} |
|
} |
|
} |
|
|
|
function pipelineStringify(p, ctx) { |
|
var result = p.stringify(ctx.tree, ctx.file) |
|
|
|
if (result === undefined || result === null) { |
|
// Empty. |
|
} else if (typeof result === 'string' || buffer(result)) { |
|
if ('value' in ctx.file) { |
|
ctx.file.value = result |
|
} |
|
|
|
ctx.file.contents = result |
|
} else { |
|
ctx.file.result = result |
|
} |
|
} |
|
|
|
// Function to create the first processor. |
|
function unified() { |
|
var attachers = [] |
|
var transformers = trough() |
|
var namespace = {} |
|
var freezeIndex = -1 |
|
var frozen |
|
|
|
// 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 index = -1 |
|
|
|
while (++index < attachers.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 transformer |
|
|
|
if (frozen) { |
|
return processor |
|
} |
|
|
|
while (++freezeIndex < attachers.length) { |
|
values = attachers[freezeIndex] |
|
|
|
if (values[1] === false) { |
|
continue |
|
} |
|
|
|
if (values[1] === true) { |
|
values[1] = undefined |
|
} |
|
|
|
transformer = values[0].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 index = -1 |
|
|
|
if (plugins === null || plugins === undefined) { |
|
// Empty. |
|
} else if (typeof plugins === 'object' && 'length' in plugins) { |
|
while (++index < plugins.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(true, entry[1], value) |
|
} |
|
|
|
entry[1] = value |
|
} else { |
|
attachers.push(slice.call(arguments)) |
|
} |
|
} |
|
} |
|
|
|
function find(plugin) { |
|
var index = -1 |
|
|
|
while (++index < attachers.length) { |
|
if (attachers[index][0] === plugin) { |
|
return attachers[index] |
|
} |
|
} |
|
} |
|
|
|
// 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(error, tree, file) { |
|
tree = tree || node |
|
if (error) { |
|
reject(error) |
|
} 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 result |
|
var complete |
|
|
|
run(node, file, done) |
|
|
|
assertDone('runSync', 'run', complete) |
|
|
|
return result |
|
|
|
function done(error, tree) { |
|
complete = true |
|
result = tree |
|
bail(error) |
|
} |
|
} |
|
|
|
// 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(error) { |
|
if (error) { |
|
reject(error) |
|
} else if (resolve) { |
|
resolve(file) |
|
} else { |
|
cb(null, file) |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Process the given document (in string or vfile representation), sync. |
|
function processSync(doc) { |
|
var file |
|
var complete |
|
|
|
freeze() |
|
assertParser('processSync', processor.Parser) |
|
assertCompiler('processSync', processor.Compiler) |
|
file = vfile(doc) |
|
|
|
process(file, done) |
|
|
|
assertDone('processSync', 'process', complete) |
|
|
|
return file |
|
|
|
function done(error) { |
|
complete = true |
|
bail(error) |
|
} |
|
} |
|
} |
|
|
|
// 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' |
|
) |
|
} |
|
}
|
|
|