461 lines
10 KiB
JavaScript
461 lines
10 KiB
JavaScript
'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'
|
||
)
|
||
}
|
||
}
|