467 lines
10 KiB
JavaScript
467 lines
10 KiB
JavaScript
'use strict'
|
||
|
||
/* Dependencies. */
|
||
var extend = require('extend')
|
||
var bail = require('bail')
|
||
var vfile = require('vfile')
|
||
var trough = require('trough')
|
||
var string = require('x-is-string')
|
||
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
|
||
|
||
/* Plug-ins. */
|
||
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.
|
||
* Plug-ins 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 (string(key)) {
|
||
/* 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
|
||
}
|
||
|
||
/* Plug-in 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)) {
|
||
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)) {
|
||
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 `func` is a constructor. */
|
||
function newable(value) {
|
||
return typeof value === 'function' && keys(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`.'
|
||
].join('')
|
||
)
|
||
}
|
||
}
|
||
|
||
/* Assert `node` is a Unist node. */
|
||
function assertNode(node) {
|
||
if (!node || !string(node.type)) {
|
||
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'
|
||
)
|
||
}
|
||
}
|