|
|
|
'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 func = require('x-is-function');
|
|
|
|
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 (func(transformer)) {
|
|
|
|
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 (func(value)) {
|
|
|
|
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 (func(value)) {
|
|
|
|
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 && func(file)) {
|
|
|
|
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 func(value) && 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 (!func(Parser)) {
|
|
|
|
throw new Error('Cannot `' + name + '` without `Parser`');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Assert a compiler is available. */
|
|
|
|
function assertCompiler(name, Compiler) {
|
|
|
|
if (!func(Compiler)) {
|
|
|
|
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.\n' +
|
|
|
|
'Create a new processor first, by invoking it: ' +
|
|
|
|
'use `processor()` instead of `processor`.'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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');
|
|
|
|
}
|
|
|
|
}
|