Comparing version 5.1.0 to 6.0.0
794
index.js
@@ -1,15 +0,5 @@ | ||
/** | ||
* @author Titus Wormer | ||
* @copyright 2015 Titus Wormer | ||
* @license MIT | ||
* @module unified | ||
* @fileoverview Pluggable text processing interface. | ||
*/ | ||
'use strict'; | ||
/* Dependencies. */ | ||
var events = require('events'); | ||
var has = require('has'); | ||
var once = require('once'); | ||
var extend = require('extend'); | ||
@@ -19,36 +9,37 @@ var bail = require('bail'); | ||
var trough = require('trough'); | ||
var buffer = require('is-buffer'); | ||
var string = require('x-is-string'); | ||
var func = require('x-is-function'); | ||
var array = require('isarray'); | ||
/* Expose an abstract processor. */ | ||
module.exports = unified().abstract(); | ||
/* Expose a frozen processor. */ | ||
module.exports = unified().freeze(); | ||
/* Methods. */ | ||
var slice = [].slice; | ||
/* Process pipeline. */ | ||
var pipeline = trough() | ||
.use(function (p, ctx) { | ||
ctx.tree = p.parse(ctx.file, ctx.options); | ||
}) | ||
.use(function (p, ctx, next) { | ||
p.run(ctx.tree, ctx.file, function (err, tree, file) { | ||
if (err) { | ||
next(err); | ||
} else { | ||
ctx.tree = tree; | ||
ctx.file = file; | ||
next(); | ||
} | ||
}); | ||
}) | ||
.use(function (p, ctx) { | ||
ctx.file.contents = p.stringify(ctx.tree, ctx.file, ctx.options); | ||
}); | ||
var pipeline = trough().use(pipelineParse).use(pipelineRun).use(pipelineStringify); | ||
/** | ||
* Function to create the first processor. | ||
* | ||
* @return {Function} - First processor. | ||
*/ | ||
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() { | ||
@@ -58,21 +49,4 @@ var attachers = []; | ||
var namespace = {}; | ||
var chunks = []; | ||
var emitter = new events.EventEmitter(); | ||
var ended = false; | ||
var concrete = true; | ||
var settings; | ||
var key; | ||
var frozen = false; | ||
/* Mix in methods. */ | ||
for (key in emitter) { | ||
processor[key] = emitter[key]; | ||
} | ||
/* Throw as early as possible. | ||
* As events are triggered synchroneously, the stack | ||
* is preserved. */ | ||
processor.on('pipe', function () { | ||
assertConcrete(); | ||
}); | ||
/* Data management. */ | ||
@@ -82,3 +56,3 @@ processor.data = data; | ||
/* Lock. */ | ||
processor.abstract = abstract; | ||
processor.freeze = freeze; | ||
@@ -89,9 +63,2 @@ /* Plug-ins. */ | ||
/* Streaming. */ | ||
processor.writable = true; | ||
processor.readable = true; | ||
processor.write = write; | ||
processor.end = end; | ||
processor.pipe = pipe; | ||
/* API. */ | ||
@@ -101,3 +68,5 @@ processor.parse = parse; | ||
processor.run = run; | ||
processor.runSync = runSync; | ||
processor.process = process; | ||
processor.processSync = processSync; | ||
@@ -107,9 +76,4 @@ /* Expose. */ | ||
/** | ||
* Create a new processor based on the processor | ||
* in the current scope. | ||
* | ||
* @return {Processor} - New concrete processor based | ||
* on the descendant processor. | ||
*/ | ||
/* Create a new processor based on the processor | ||
* in the current scope. */ | ||
function processor() { | ||
@@ -129,88 +93,44 @@ var destination = unified(); | ||
/* Helpers. */ | ||
/** | ||
* Assert a parser is available. | ||
/* Freeze: used to signal a processor that has finished | ||
* configuration. | ||
* | ||
* @param {string} name - Name of callee. | ||
*/ | ||
function assertParser(name) { | ||
if (!isParser(processor.Parser)) { | ||
throw new Error('Cannot `' + name + '` without `Parser`'); | ||
} | ||
} | ||
/** | ||
* Assert a compiler is available. | ||
* 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. | ||
* | ||
* @param {string} name - Name of callee. | ||
*/ | ||
function assertCompiler(name) { | ||
if (!isCompiler(processor.Compiler)) { | ||
throw new Error('Cannot `' + name + '` without `Compiler`'); | ||
} | ||
} | ||
* In essence, always invoke this when exporting a | ||
* processor. */ | ||
function freeze() { | ||
var index = -1; | ||
var values; | ||
var plugin; | ||
var options; | ||
var transformer; | ||
/** | ||
* Assert the processor is concrete. | ||
* | ||
* @param {string} name - Name of callee. | ||
*/ | ||
function assertConcrete(name) { | ||
if (!concrete) { | ||
throw new Error( | ||
'Cannot ' + | ||
(name ? 'invoke `' + name + '` on' : 'pipe into') + | ||
' abstract processor.\n' + | ||
'To make the processor concrete, invoke it: ' + | ||
'use `processor()` instead of `processor`.' | ||
); | ||
if (frozen) { | ||
return processor; | ||
} | ||
} | ||
/** | ||
* Assert `node` is a Unist node. | ||
* | ||
* @param {*} node - Value to check. | ||
*/ | ||
function assertNode(node) { | ||
if (!isNode(node)) { | ||
throw new Error('Expected node, got `' + node + '`'); | ||
} | ||
} | ||
while (++index < attachers.length) { | ||
values = attachers[index]; | ||
plugin = values[0]; | ||
options = values[1]; | ||
transformer = null; | ||
/** | ||
* Assert, if no `done` is given, that `complete` is | ||
* `true`. | ||
* | ||
* @param {string} name - Name of callee. | ||
* @param {boolean} complete - Whether an async process | ||
* is complete. | ||
* @param {Function?} done - Optional handler of async | ||
* results. | ||
*/ | ||
function assertDone(name, complete, done) { | ||
if (!complete && !done) { | ||
throw new Error( | ||
'Expected `done` to be given to `' + name + '` ' + | ||
'as async plug-ins are used' | ||
); | ||
if (options === false) { | ||
return; | ||
} | ||
if (options === true) { | ||
values[1] = undefined; | ||
} | ||
transformer = plugin.apply(processor, values.slice(1)); | ||
if (func(transformer)) { | ||
transformers.use(transformer); | ||
} | ||
} | ||
} | ||
/** | ||
* Abstract: used to signal an abstract processor which | ||
* should made concrete before using. | ||
* | ||
* For example, take unified itself. It’s abstract. | ||
* Plug-ins should not be added to it. Rather, it should | ||
* be made concrete (by invoking it) before modifying it. | ||
* | ||
* In essence, always invoke this when exporting a | ||
* processor. | ||
* | ||
* @return {Processor} - The operated on processor. | ||
*/ | ||
function abstract() { | ||
concrete = false; | ||
frozen = true; | ||
@@ -220,19 +140,10 @@ return processor; | ||
/** | ||
* Data management. | ||
* | ||
* Getter / setter for processor-specific informtion. | ||
* | ||
* @param {string} key - Key to get or set. | ||
* @param {*} value - Value to set. | ||
* @return {*} - Either the operator on processor in | ||
* setter mode; or the value stored as `key` in | ||
* getter mode. | ||
*/ | ||
/* Data management. | ||
* Getter / setter for processor-specific informtion. */ | ||
function data(key, value) { | ||
assertConcrete('data'); | ||
if (string(key)) { | ||
/* Set `key`. */ | ||
if (arguments.length === 2) { | ||
assertUnfrozen('data', frozen); | ||
namespace[key] = value; | ||
@@ -247,430 +158,311 @@ | ||
/* Get space. */ | ||
if (!key) { | ||
return namespace; | ||
/* Set space. */ | ||
if (key) { | ||
assertUnfrozen('data', frozen); | ||
namespace = key; | ||
return processor; | ||
} | ||
/* Set space. */ | ||
namespace = key; | ||
return processor; | ||
/* Get space. */ | ||
return namespace; | ||
} | ||
/** | ||
* Plug-in management. | ||
/* Plug-in management. | ||
* | ||
* Pass it: | ||
* * an attacher and options, | ||
* * a list of attachers and options for all of them; | ||
* * a tuple of one attacher and options. | ||
* * a matrix: list containing any of the above and | ||
* matrices. | ||
* * a processor: another processor to use all its | ||
* plugins (except parser if there’s already one). | ||
* | ||
* @param {...*} value - See description. | ||
* @return {Processor} - The operated on processor. | ||
*/ | ||
* * a preset, | ||
* * a list of presets, attachers, and arguments (list | ||
* of attachers and options). */ | ||
function use(value) { | ||
var args = slice.call(arguments, 0); | ||
var params = args.slice(1); | ||
var parser; | ||
var index; | ||
var length; | ||
var transformer; | ||
var result; | ||
var settings; | ||
assertConcrete('use'); | ||
assertUnfrozen('use', frozen); | ||
/* Multiple attachers. */ | ||
if ('length' in value && !isFunction(value)) { | ||
index = -1; | ||
length = value.length; | ||
if (!isFunction(value[0])) { | ||
/* Matrix of things. */ | ||
while (++index < length) { | ||
use(value[index]); | ||
} | ||
} else if (isFunction(value[1])) { | ||
/* List of things. */ | ||
while (++index < length) { | ||
use.apply(null, [value[index]].concat(params)); | ||
} | ||
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 { | ||
/* Arguments. */ | ||
use.apply(null, value); | ||
addPreset(value); | ||
} | ||
} else { | ||
throw new Error('Expected usable value, not `' + value + '`'); | ||
} | ||
return processor; | ||
if (settings) { | ||
namespace.settings = extend(namespace.settings || {}, settings); | ||
} | ||
/* Store attacher. */ | ||
attachers.push(args); | ||
return processor; | ||
/* Use a processor (except its parser if there’s already one. | ||
* Note that the processor is stored on `attachers`, making | ||
* it possibly mutating in the future, but also ensuring | ||
* the parser isn’t overwritten in the future either. */ | ||
if (isProcessor(value)) { | ||
parser = processor.Parser; | ||
result = use(value.attachers); | ||
function addPreset(result) { | ||
addList(result.plugins); | ||
if (parser) { | ||
processor.Parser = parser; | ||
if (result.settings) { | ||
settings = extend(settings || {}, result.settings); | ||
} | ||
} | ||
return result; | ||
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 + '`'); | ||
} | ||
} | ||
/* Single attacher. */ | ||
transformer = value.apply(null, [processor].concat(params)); | ||
function addList(plugins) { | ||
var length; | ||
var index; | ||
if (isFunction(transformer)) { | ||
transformers.use(transformer); | ||
if (plugins === null || plugins === undefined) { | ||
/* empty */ | ||
} else if (array(plugins)) { | ||
length = plugins.length; | ||
index = -1; | ||
while (++index < length) { | ||
add(plugins[index]); | ||
} | ||
} else { | ||
throw new Error('Expected a list of plugins, not `' + plugins + '`'); | ||
} | ||
} | ||
return processor; | ||
} | ||
function addPlugin(plugin, value) { | ||
var entry = find(plugin); | ||
/** | ||
* Parse a file (in string or VFile representation) | ||
* into a Unist node using the `Parser` on the | ||
* processor. | ||
* | ||
* @param {VFile?} [file] - File to process. | ||
* @param {Object?} [options] - Configuration. | ||
* @return {Node} - Unist node. | ||
*/ | ||
function parse(file, options) { | ||
assertConcrete('parse'); | ||
assertParser('parse'); | ||
if (entry) { | ||
if (value !== false && entry[1] !== false && !array(value)) { | ||
value = extend(entry[1], value); | ||
} | ||
return new processor.Parser(vfile(file), options, processor).parse(); | ||
entry[1] = value; | ||
} else { | ||
attachers.push(slice.call(arguments)); | ||
} | ||
} | ||
} | ||
/** | ||
* Run transforms on a Unist node representation of a file | ||
* (in string or VFile representation). | ||
* | ||
* @param {Node} node - Unist node. | ||
* @param {(string|VFile)?} [file] - File representation. | ||
* @param {Function?} [done] - Callback. | ||
* @return {Node} - The given or resulting Unist node. | ||
*/ | ||
function run(node, file, done) { | ||
var complete = false; | ||
var result; | ||
function find(plugin) { | ||
var length = attachers.length; | ||
var index = -1; | ||
var entry; | ||
assertConcrete('run'); | ||
assertNode(node); | ||
while (++index < length) { | ||
entry = attachers[index]; | ||
result = node; | ||
if (!done && isFunction(file)) { | ||
done = file; | ||
file = null; | ||
if (entry[0] === plugin) { | ||
return entry; | ||
} | ||
} | ||
} | ||
transformers.run(node, vfile(file), function (err, tree, file) { | ||
complete = true; | ||
result = tree || node; | ||
/* 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; | ||
(done || bail)(err, tree, file); | ||
}); | ||
freeze(); | ||
Parser = processor.Parser; | ||
assertParser('parse', Parser); | ||
assertDone('run', complete, done); | ||
if (newable(Parser)) { | ||
return new Parser(String(file), file).parse(); | ||
} | ||
return result; | ||
return Parser(String(file), file); // eslint-disable-line new-cap | ||
} | ||
/** | ||
* Stringify a Unist node representation of a file | ||
* (in string or VFile representation) into a string | ||
* using the `Compiler` on the processor. | ||
* | ||
* @param {Node} node - Unist node. | ||
* @param {(string|VFile)?} [file] - File representation. | ||
* @param {Object?} [options] - Configuration. | ||
* @return {string} - String representation. | ||
*/ | ||
function stringify(node, file, options) { | ||
assertConcrete('stringify'); | ||
assertCompiler('stringify'); | ||
/* 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 ( | ||
!options && | ||
!string(file) && | ||
!buffer(file) && | ||
!(typeof file === 'object' && 'messages' in file) | ||
) { | ||
options = file; | ||
if (!cb && func(file)) { | ||
cb = file; | ||
file = null; | ||
} | ||
return new processor.Compiler(vfile(file), options, processor).compile(node); | ||
} | ||
/** | ||
* 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. | ||
* | ||
* @param {(string|VFile)?} file - File representation. | ||
* @param {Object?} [options] - Configuration. | ||
* @param {Function?} [done] - Callback. | ||
* @return {VFile} - The given or resulting VFile. | ||
*/ | ||
function process(file, options, done) { | ||
var complete = false; | ||
assertConcrete('process'); | ||
assertParser('process'); | ||
assertCompiler('process'); | ||
if (!done && isFunction(options)) { | ||
done = options; | ||
options = null; | ||
if (!cb) { | ||
return new Promise(executor); | ||
} | ||
file = vfile(file); | ||
executor(null, cb); | ||
pipeline.run(processor, { | ||
file: file, | ||
options: options || {} | ||
}, function (err) { | ||
complete = true; | ||
function executor(resolve, reject) { | ||
transformers.run(node, vfile(file), done); | ||
if (done) { | ||
done(err, file); | ||
} else { | ||
bail(err); | ||
function done(err, tree, file) { | ||
tree = tree || node; | ||
if (err) { | ||
reject(err); | ||
} else if (resolve) { | ||
resolve(tree); | ||
} else { | ||
cb(null, tree, file); | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
assertDone('process', complete, done); | ||
/* 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; | ||
return file; | ||
} | ||
run(node, file, done); | ||
/* Streams. */ | ||
assertDone('runSync', 'run', complete); | ||
/** | ||
* Write a chunk into memory. | ||
* | ||
* @param {(Buffer|string)?} chunk - Value to write. | ||
* @param {string?} [encoding] - Encoding. | ||
* @param {Function?} [callback] - Callback. | ||
* @return {boolean} - Whether the write was succesful. | ||
*/ | ||
function write(chunk, encoding, callback) { | ||
assertConcrete('write'); | ||
return result; | ||
if (isFunction(encoding)) { | ||
callback = encoding; | ||
encoding = null; | ||
function done(err, tree) { | ||
complete = true; | ||
bail(err); | ||
result = tree; | ||
} | ||
} | ||
if (ended) { | ||
throw new Error('Did not expect `write` after `end`'); | ||
} | ||
/* 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; | ||
chunks.push((chunk || '').toString(encoding || 'utf8')); | ||
freeze(); | ||
Compiler = processor.Compiler; | ||
assertCompiler('stringify', Compiler); | ||
assertNode(node); | ||
if (callback) { | ||
callback(); | ||
if (newable(Compiler)) { | ||
return new Compiler(node, file).compile(); | ||
} | ||
/* Signal succesful write. */ | ||
return true; | ||
return Compiler(node, file); // eslint-disable-line new-cap | ||
} | ||
/** | ||
* End the writing. Passes all arguments to a final | ||
* `write`. Starts the process, which will trigger | ||
* `error`, with a fatal error, if any; `data`, with | ||
* the generated document in `string` form, if | ||
* succesful. If messages are triggered during the | ||
* process, those are triggerd as `warning`s. | ||
* | ||
* @return {boolean} - Whether the last write was | ||
* succesful. | ||
*/ | ||
function end() { | ||
assertConcrete('end'); | ||
assertParser('end'); | ||
assertCompiler('end'); | ||
/* 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); | ||
write.apply(null, arguments); | ||
if (!cb) { | ||
return new Promise(executor); | ||
} | ||
ended = true; | ||
executor(null, cb); | ||
process(chunks.join(''), settings, function (err, file) { | ||
var messages = file.messages; | ||
var length = messages.length; | ||
var index = -1; | ||
function executor(resolve, reject) { | ||
var file = vfile(doc); | ||
chunks = settings = null; | ||
pipeline.run(processor, {file: file}, done); | ||
/* Trigger messages as warnings, except for fatal error. */ | ||
while (++index < length) { | ||
if (messages[index] !== err) { | ||
processor.emit('warning', messages[index]); | ||
function done(err) { | ||
if (err) { | ||
reject(err); | ||
} else if (resolve) { | ||
resolve(file); | ||
} else { | ||
cb(null, file); | ||
} | ||
} | ||
if (err) { | ||
/* Don’t enter an infinite error throwing loop. */ | ||
global.setTimeout(function () { | ||
processor.emit('error', err); | ||
}, 4); | ||
} else { | ||
processor.emit('data', file.contents); | ||
processor.emit('end'); | ||
} | ||
}); | ||
return true; | ||
} | ||
} | ||
/** | ||
* Pipe the processor into a writable stream. | ||
* | ||
* Basically `Stream#pipe`, but inlined and | ||
* simplified to keep the bundled size down. | ||
* | ||
* @see https://github.com/nodejs/node/blob/master/lib/stream.js#L26 | ||
* | ||
* @param {Stream} dest - Writable stream. | ||
* @param {Object?} [options] - Processing | ||
* configuration. | ||
* @return {Stream} - The destination stream. | ||
*/ | ||
function pipe(dest, options) { | ||
var onend = once(onended); | ||
/* Process the given document (in string or VFile | ||
* representation), sync. */ | ||
function processSync(doc) { | ||
var complete = false; | ||
var file; | ||
assertConcrete('pipe'); | ||
freeze(); | ||
assertParser('processSync', processor.Parser); | ||
assertCompiler('processSync', processor.Compiler); | ||
file = vfile(doc); | ||
settings = options || {}; | ||
process(file, done); | ||
processor.on('data', ondata); | ||
processor.on('error', onerror); | ||
processor.on('end', cleanup); | ||
processor.on('close', cleanup); | ||
assertDone('processSync', 'process', complete); | ||
/* If the 'end' option is not supplied, dest.end() will be | ||
* called when the 'end' or 'close' events are received. | ||
* Only dest.end() once. */ | ||
if (!dest._isStdio && settings.end !== false) { | ||
processor.on('end', onend); | ||
} | ||
return file; | ||
dest.on('error', onerror); | ||
dest.on('close', cleanup); | ||
dest.emit('pipe', processor); | ||
return dest; | ||
/** End destination. */ | ||
function onended() { | ||
if (dest.end) { | ||
dest.end(); | ||
} | ||
function done(err) { | ||
complete = true; | ||
bail(err); | ||
} | ||
} | ||
} | ||
/** | ||
* Handle data. | ||
* | ||
* @param {*} chunk - Data to pass through. | ||
*/ | ||
function ondata(chunk) { | ||
if (dest.writable) { | ||
dest.write(chunk); | ||
} | ||
} | ||
/* Check if `func` is a constructor. */ | ||
function newable(value) { | ||
return func(value) && keys(value.prototype); | ||
} | ||
/** | ||
* Clean listeners. | ||
*/ | ||
function cleanup() { | ||
processor.removeListener('data', ondata); | ||
processor.removeListener('end', onend); | ||
processor.removeListener('error', onerror); | ||
processor.removeListener('end', cleanup); | ||
processor.removeListener('close', cleanup); | ||
dest.removeListener('error', onerror); | ||
dest.removeListener('close', cleanup); | ||
} | ||
/** | ||
* Close dangling pipes and handle unheard errors. | ||
* | ||
* @param {Error} err - Exception. | ||
*/ | ||
function onerror(err) { | ||
var handlers = processor._events.error; | ||
cleanup(); | ||
/* Cannot use `listenerCount` in node <= 0.12. */ | ||
if (!handlers || !handlers.length || handlers === onerror) { | ||
throw err; /* Unhandled stream error in pipe. */ | ||
} | ||
} | ||
/* Check if `value` is an object with keys. */ | ||
function keys(value) { | ||
var key; | ||
for (key in value) { | ||
return true; | ||
} | ||
return false; | ||
} | ||
/** | ||
* Check if `node` is a Unist node. | ||
* | ||
* @param {*} node - Value. | ||
* @return {boolean} - Whether `node` is a Unist node. | ||
*/ | ||
function isNode(node) { | ||
return node && string(node.type) && node.type.length !== 0; | ||
/* Assert a parser is available. */ | ||
function assertParser(name, Parser) { | ||
if (!func(Parser)) { | ||
throw new Error('Cannot `' + name + '` without `Parser`'); | ||
} | ||
} | ||
/** | ||
* Check if `fn` is a function. | ||
* | ||
* @param {*} fn - Value. | ||
* @return {boolean} - Whether `fn` is a function. | ||
*/ | ||
function isFunction(fn) { | ||
return typeof fn === 'function'; | ||
/* Assert a compiler is available. */ | ||
function assertCompiler(name, Compiler) { | ||
if (!func(Compiler)) { | ||
throw new Error('Cannot `' + name + '` without `Compiler`'); | ||
} | ||
} | ||
/** | ||
* Check if `compiler` is a Compiler. | ||
* | ||
* @param {*} compiler - Value. | ||
* @return {boolean} - Whether `compiler` is a Compiler. | ||
*/ | ||
function isCompiler(compiler) { | ||
return isFunction(compiler) && compiler.prototype && isFunction(compiler.prototype.compile); | ||
/* 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`.' | ||
); | ||
} | ||
} | ||
/** | ||
* Check if `parser` is a Parser. | ||
* | ||
* @param {*} parser - Value. | ||
* @return {boolean} - Whether `parser` is a Parser. | ||
*/ | ||
function isParser(parser) { | ||
return isFunction(parser) && parser.prototype && isFunction(parser.prototype.parse); | ||
/* Assert `node` is a Unist node. */ | ||
function assertNode(node) { | ||
if (!node || !string(node.type)) { | ||
throw new Error('Expected node, got `' + node + '`'); | ||
} | ||
} | ||
/** | ||
* Check if `processor` is a unified processor. | ||
* | ||
* @param {*} processor - Value. | ||
* @return {boolean} - Whether `processor` is a processor. | ||
*/ | ||
function isProcessor(processor) { | ||
return isFunction(processor) && isFunction(processor.use) && isFunction(processor.process); | ||
/* Assert that `complete` is `true`. */ | ||
function assertDone(name, asyncName, complete) { | ||
if (!complete) { | ||
throw new Error('`' + name + '` finished async. Use `' + asyncName + '` instead'); | ||
} | ||
} |
{ | ||
"name": "unified", | ||
"version": "5.1.0", | ||
"version": "6.0.0", | ||
"description": "Pluggable text processing interface", | ||
@@ -12,6 +12,16 @@ "license": "MIT", | ||
"stringify", | ||
"hast", | ||
"rehype", | ||
"retext", | ||
"remark" | ||
], | ||
"repository": "https://github.com/unifiedjs/unified", | ||
"bugs": "https://github.com/unifiedjs/unified/issues", | ||
"author": "Titus Wormer <tituswormer@gmail.com> (http://wooorm.com)", | ||
"contributors": [ | ||
"Titus Wormer <tituswormer@gmail.com> (http://wooorm.com)" | ||
], | ||
"files": [ | ||
"index.js", | ||
"lib" | ||
], | ||
"dependencies": { | ||
@@ -21,32 +31,16 @@ "bail": "^1.0.0", | ||
"has": "^1.0.1", | ||
"is-buffer": "^1.1.4", | ||
"once": "^1.3.3", | ||
"isarray": "^2.0.1", | ||
"trough": "^1.0.0", | ||
"vfile": "^2.0.0", | ||
"x-is-function": "^1.0.4", | ||
"x-is-string": "^0.1.0" | ||
}, | ||
"browser": { | ||
"node-extend": "extend" | ||
}, | ||
"repository": "https://github.com/wooorm/unified", | ||
"bugs": "https://github.com/wooorm/unified/issues", | ||
"author": "Titus Wormer <tituswormer@gmail.com> (http://wooorm.com)", | ||
"contributors": [ | ||
"Titus Wormer <tituswormer@gmail.com> (http://wooorm.com)" | ||
], | ||
"engines": { | ||
"node": ">=0.11.0" | ||
}, | ||
"files": [ | ||
"index.js", | ||
"lib" | ||
], | ||
"devDependencies": { | ||
"browserify": "^13.0.0", | ||
"browserify": "^14.0.0", | ||
"esmangle": "^1.0.0", | ||
"nyc": "^8.1.0", | ||
"nyc": "^10.0.0", | ||
"remark-cli": "^2.0.0", | ||
"remark-preset-wooorm": "^1.0.0", | ||
"tape": "^4.4.0", | ||
"xo": "^0.16.0" | ||
"xo": "^0.17.0" | ||
}, | ||
@@ -72,2 +66,3 @@ "scripts": { | ||
"rules": { | ||
"import/no-unassigned-import": "off", | ||
"guard-for-in": "off", | ||
@@ -74,0 +69,0 @@ "max-lines": "off" |
723
readme.md
@@ -10,3 +10,3 @@ # unified [![Build Status][travis-badge]][travis] [![Coverage Status][codecov-badge]][codecov] | ||
[npm][npm-install]: | ||
[npm][]: | ||
@@ -17,5 +17,2 @@ ```bash | ||
**unified** is also available as an AMD, CommonJS, and globals module, | ||
[uncompressed and compressed][releases]. | ||
## Usage | ||
@@ -26,19 +23,36 @@ | ||
var markdown = require('remark-parse'); | ||
var toc = require('remark-toc'); | ||
var remark2rehype = require('remark-rehype'); | ||
var document = require('rehype-document'); | ||
var minify = require('rehype-preset-minify'); | ||
var format = require('rehype-format'); | ||
var html = require('rehype-stringify'); | ||
var reporter = require('vfile-reporter'); | ||
process.stdin | ||
.pipe(unified()) | ||
unified() | ||
.use(markdown) | ||
.use(toc) | ||
.use(remark2rehype) | ||
.use(document) | ||
.use(minify) | ||
.use(format) | ||
.use(html) | ||
.pipe(process.stdout); | ||
.process('# Hello world!', function (err, file) { | ||
console.error(reporter(err || file)); | ||
console.log(String(file)); | ||
}); | ||
``` | ||
Yields: | ||
```html | ||
no issues found | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="utf-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1"> | ||
</head> | ||
<body> | ||
<h1>Hello world!</h1> | ||
</body> | ||
</html> | ||
``` | ||
## Table of Contents | ||
@@ -50,11 +64,14 @@ | ||
* [processor.use(plugin\[, options\])](#processoruseplugin-options) | ||
* [processor.parse(file|value\[, options\])](#processorparsefilevalue-options) | ||
* [processor.stringify(node\[, file\]\[, options\])](#processorstringifynode-file-options) | ||
* [processor.parse(file|value)](#processorparsefilevalue) | ||
* [processor.stringify(node\[, file\])](#processorstringifynode-file) | ||
* [processor.run(node\[, file\]\[, done\])](#processorrunnode-file-done) | ||
* [processor.process(file|value\[, options\]\[, done\])](#processorprocessfilevalue-options-done) | ||
* [processor.write(chunk\[, encoding\]\[, callback\])](#processorwritechunk-encoding-callback) | ||
* [processor.end()](#processorend) | ||
* [processor.pipe(stream\[, options\])](#processorpipestream-options) | ||
* [processor.runSync(node\[, file\])](#processorrunsyncnode-file) | ||
* [processor.process(file|value\[, done\])](#processorprocessfilevalue-done) | ||
* [processor.processSync(file|value)](#processorprocesssyncfilevalue) | ||
* [processor.data(key\[, value\])](#processordatakey-value) | ||
* [processor.abstract()](#processorabstract) | ||
* [processor.freeze()](#processorfreeze) | ||
* [Plugin](#plugin) | ||
* [function attacher(\[options\])](#function-attacheroptions) | ||
* [function transformer(node, file\[, next\])](#function-transformernode-file-next) | ||
* [Preset](#preset) | ||
* [License](#license) | ||
@@ -92,5 +109,5 @@ | ||
Often, when processors are exposed from a library (for example, | ||
unified itself), they should not be modified directly, as that | ||
unified itself), they should not be configured directly, as that | ||
would change their behaviour for all users. Those processors are | ||
[**abstract**][abstract], and they should be made concrete before | ||
[**frozen**][freeze], and new processors should be made from them before | ||
they are used, by invoking them. | ||
@@ -142,50 +159,6 @@ | ||
###### Streaming | ||
**unified** provides a streaming interface which allows it to plug | ||
into transformations outside of itself. An example, which reads | ||
markdown as input, adds a table of content, and writes it out, would | ||
be as follows: | ||
```js | ||
var unified = require('unified'); | ||
var markdown = require('remark-parse'); | ||
var stringify = require('remark-stringify'); | ||
var toc = require('remark-toc'); | ||
process.stdin | ||
.pipe(unified()) | ||
.use(parse) | ||
.use(toc) | ||
.use(stringify) | ||
.pipe(process.stdout); | ||
``` | ||
Which when given on **stdin**(4): | ||
```md | ||
# Alpha | ||
## Table of Content | ||
## Bravo | ||
``` | ||
Yields on **stdout**(4): | ||
```md | ||
# Alpha | ||
## Table of Content | ||
* [Bravo](#bravo) | ||
## Bravo | ||
``` | ||
###### Programming interface | ||
Next to streaming, there’s also a programming interface, which gives | ||
access to processing metadata (such as lint messages), and supports | ||
multiple passed through files: | ||
The API gives access to processing metadata (such as lint messages), and | ||
supports multiple passed through files: | ||
@@ -201,3 +174,3 @@ ```js | ||
var html = require('rehype-stringify'); | ||
var report = require('vfile-reporter'); | ||
var reporter = require('vfile-reporter'); | ||
@@ -211,3 +184,3 @@ unified() | ||
.process('## Hey guys', function (err, file) { | ||
console.err(report(err || file)); | ||
console.err(reporter(err || file)); | ||
console.log(file.toString()); | ||
@@ -245,5 +218,6 @@ }); | ||
* [**remark-retext**][remark-retext]. | ||
* [**remark-rehype**][remark-rehype]. | ||
* [**rehype-retext**][rehype-retext]. | ||
* [**remark-retext**][remark-retext] | ||
* [**remark-rehype**][remark-rehype] | ||
* [**rehype-retext**][rehype-retext] | ||
* [**rehype-remark**][rehype-remark] | ||
@@ -258,3 +232,3 @@ ## API | ||
`Function` — A new [**concrete**][abstract] processor which is | ||
`Function` — A new [**unfrozen**][freeze] processor which is | ||
configured to function the same as its ancestor. But, when the | ||
@@ -271,4 +245,7 @@ descendant processor is configured in the future, that configuration | ||
var remark = require('remark'); | ||
var concat = require('concat-stream'); | ||
process.stdin.pipe(remark()).pipe(process.stdout); | ||
process.stdin.pipe(concat(function (buf) { | ||
process.stdout.write(remark().processSync(buf)) | ||
})); | ||
``` | ||
@@ -284,6 +261,4 @@ | ||
* `processor.use(plugin[, options])`; | ||
* `processor.use(plugins[, options])`; | ||
* `processor.use(preset)`; | ||
* `processor.use(list)`; | ||
* `processor.use(matrix)`; | ||
* `processor.use(processor)`. | ||
@@ -294,7 +269,6 @@ ###### Parameters | ||
* `options` (`*`, optional) — Configuration for `plugin`. | ||
* `plugins` (`Array.<Function>`) — List of plugins; | ||
* `list` (`Array`) — `plugin` and `options` in an array; | ||
* `matrix` (`Array`) — Array where each entry is a `list`; | ||
* `processor` ([`Processor`][processor]) — Other processor whose | ||
plugins to use (except for a parser). | ||
* `preset` (`Object`) — Object with an optional `plugins` (set to `list`), | ||
and/or an optional `settings` object; | ||
* `list` (`Array`) — plugins, presets, and arguments (a plugin and options | ||
in an array), in an array. | ||
@@ -305,72 +279,33 @@ ###### Returns | ||
#### `Plugin` | ||
###### Note | ||
A **unified** plugin changes the way the applied-on processor works, | ||
in the following ways: | ||
`use` cannot be called on [frozen][freeze] processors. Invoke the processor | ||
first to create a new unfrozen processor. | ||
* It modifies the [**processor**][processor]: such as changing the | ||
parser, the compiler, or linking the processor to other processors; | ||
* It transforms the [**syntax tree**][node] representation of a file; | ||
* It modifies metadata of a file. | ||
###### Example | ||
Plug-in’s are a concept which materialise as [**attacher**][attacher]s. | ||
There are many ways to pass plugins to `.use()`. The below example | ||
gives an overview. | ||
#### `function attacher(processor[, options])` | ||
```js | ||
var unified = require('unified'); | ||
An attacher is the thing passed to [`use`][use]. It configures the | ||
processor and in turn can receive options. | ||
unified() | ||
// Plugin with options: | ||
.use(plugin, {}) | ||
// Plugins: | ||
.use([plugin, pluginB]) | ||
// Two plugins, the second with options: | ||
.use([plugin, [pluginB, {}]]) | ||
// Preset with plugins and settings: | ||
.use({plugins: [plugin, [pluginB, {}]], settings: {position: false}}) | ||
// Settings only: | ||
.use({settings: {position: false}}); | ||
Attachers can configure processors, such as by interacting with parsers | ||
and compilers, linking them to other processors, or by specifying how | ||
the syntax tree is handled. | ||
function plugin() {} | ||
function pluginB() {} | ||
``` | ||
###### Parameters | ||
### `processor.parse(file|value)` | ||
* `processor` ([`processor`][processor]) — Context on which it’s used; | ||
* `options` (`*`, optional) — Configuration. | ||
###### Returns | ||
[`transformer`][transformer] — Optional. | ||
#### `function transformer(node, file[, next])` | ||
Transformers modify the syntax tree or metadata of a file. | ||
A transformer is a function which is invoked each time a file is | ||
passed through the transform phase. If an error occurs (either | ||
because it’s thrown, returned, rejected, or passed to [`next`][next]), | ||
the process stops. | ||
The transformation process in **unified** is handled by [`trough`][trough], | ||
see it’s documentation for the exact semantics of transformers. | ||
###### Parameters | ||
* `node` ([**Node**][node]); | ||
* `file` ([**VFile**][file]); | ||
* `next` ([`Function`][next], optional). | ||
###### Returns | ||
* `Error` — Can be returned to stop the process; | ||
* [**Node**][node] — Can be returned and results in further | ||
transformations and `stringify`s to be performed on the new | ||
tree; | ||
* `Promise` — If a promise is returned, the function is asynchronous, | ||
and **must** be resolved (optionally with a [**Node**][node]) or | ||
rejected (optionally with an `Error`). | ||
##### `function next(err[, tree[, file]])` | ||
If the signature of a transformer includes `next` (third argument), | ||
the function **may** finish asynchronous, and **must** invoke `next()`. | ||
###### Parameters | ||
* `err` (`Error`, optional) — Stop the process; | ||
* `node` ([**Node**][node], optional) — New syntax tree; | ||
* `file` ([**VFile**][file], optional) — New virtual file. | ||
### `processor.parse(file|value[, options])` | ||
Parse text to a syntax tree. | ||
@@ -380,5 +315,4 @@ | ||
* `file` ([**VFile**][file]); | ||
* `file` ([**VFile**][file]) | ||
— Or anything which can be given to `vfile()`. | ||
* `options` (`Object`, optional) — Configuration given to the parser. | ||
@@ -389,14 +323,21 @@ ###### Returns | ||
###### Note | ||
`parse` [freezes][freeze] the processor, if not already frozen. | ||
#### `processor.Parser` | ||
Constructor handling the parsing of text to a syntax tree. | ||
It’s instantiated by the [**parse**][parse] phase in the process | ||
with a [**VFile**][file], `settings`, and the processor. | ||
Function handling the parsing of text to a syntax tree. Used in the | ||
[**parse**][parse] phase in the process and invoked with a `string` | ||
and [**VFile**][file] representation of the document to parse. | ||
The instance must expose a `parse` method which is invoked without | ||
arguments, and must return a syntax tree representation of the | ||
[**VFile**][file]. | ||
If `Parser` is a normal parser, it should return a [`Node`][node]: the syntax | ||
tree representation of the given file. | ||
### `processor.stringify(node[, file][, options])` | ||
`Parser` can also be a constructor function, in which case it’s invoked with | ||
`new`. In that case, instances should have a `parse` method, which is invoked | ||
(without arguments), and should return a [`Node`][node]. | ||
### `processor.stringify(node[, file])` | ||
Compile a syntax tree to text. | ||
@@ -409,3 +350,2 @@ | ||
— Or anything which can be given to `vfile()`. | ||
* `options` (`Object`, optional) — Configuration given to the parser. | ||
@@ -416,12 +356,20 @@ ###### Returns | ||
###### Note | ||
`stringify` [freezes][freeze] the processor, if not already frozen. | ||
#### `processor.Compiler` | ||
Constructor handling the compilation of a syntax tree to text. | ||
It’s instantiated by the [**stringify**][stringify] phase in the | ||
process with a [**VFile**][file], `settings`, and the processor. | ||
Function handling the compilation of syntax tree to a text. Used in the | ||
[**stringify**][stringify] phase in the process and invoked with a | ||
[`Node`][node] and [**VFile**][file] representation of the document to | ||
stringify. | ||
The instance must expose a `compile` method which is invoked with | ||
the syntax tree, and must return a string representation of that | ||
syntax tree. | ||
If `Compiler` is a normal stringifier, it should return a `string`: the text | ||
representation of the given syntax tree. | ||
`Compiler` can also be a constructor function, in which case it’s invoked with | ||
`new`. In that case, instances should have a `compile` method, which is invoked | ||
(without arguments), and should return a `string`. | ||
### `processor.run(node[, file][, done])` | ||
@@ -431,5 +379,2 @@ | ||
If asynchronous [**plug-in**][plugin]s are configured, an error | ||
is thrown if [`done`][run-done] is not supplied. | ||
###### Parameters | ||
@@ -444,4 +389,9 @@ | ||
[**Node**][node] — The given syntax tree. | ||
[**Promise**][promise], if `done` is not given. Rejected with an error, | ||
or resolved with the resulting syntax tree. | ||
###### Note | ||
`run` [freezes][freeze] the processor, if not already frozen. | ||
##### `function done(err[, node, file])` | ||
@@ -458,4 +408,24 @@ | ||
### `processor.process(file|value[, options][, done])` | ||
### `processor.runSync(node[, file])` | ||
Transform a syntax tree by applying [**plug-in**][plugin]s to it. | ||
If asynchronous [**plug-in**][plugin]s are configured, an error is thrown. | ||
###### Parameters | ||
* `node` ([**Node**][node]); | ||
* `file` ([**VFile**][file], optional); | ||
— Or anything which can be given to `vfile()`. | ||
###### Returns | ||
[**Node**][node] — The given syntax tree. | ||
###### Note | ||
`runSync` [freezes][freeze] the processor, if not already frozen. | ||
### `processor.process(file|value[, done])` | ||
Process the given representation of a file as configured on the | ||
@@ -465,5 +435,2 @@ processor. The process invokes `parse`, `run`, and `stringify` | ||
If asynchronous [**plug-in**][plugin]s are configured, an error | ||
is thrown if [`done`][process-done] is not supplied. | ||
###### Parameters | ||
@@ -473,4 +440,2 @@ | ||
* `value` (`string`) — String representation of a file; | ||
* `options` (`Object`, optional) — Configuration for both the parser | ||
and compiler; | ||
* `done` ([`Function`][process-done], optional). | ||
@@ -480,4 +445,9 @@ | ||
[**VFile**][file] — Virtual file with modified [`contents`][vfile-contents]. | ||
[**Promise**][promise], if `done` is not given. Rejected with an error, | ||
or resolved with the resulting file. | ||
###### Note | ||
`process` [freezes][freeze] the processor, if not already frozen. | ||
#### `function done(err, file)` | ||
@@ -493,64 +463,99 @@ | ||
### `processor.write(chunk[, encoding][, callback])` | ||
###### Example | ||
> **Note**: Although the interface is compatible with streams, | ||
> all data is currently buffered and passed through in one go. | ||
> This might be changed later. | ||
```js | ||
var unified = require('unified'); | ||
var markdown = require('remark-parse'); | ||
var remark2rehype = require('remark-rehype'); | ||
var document = require('rehype-document'); | ||
var format = require('rehype-format'); | ||
var html = require('rehype-stringify'); | ||
var reporter = require('vfile-reporter'); | ||
Write data to the in-memory buffer. | ||
unified() | ||
.use(markdown) | ||
.use(remark2rehype) | ||
.use(document) | ||
.use(format) | ||
.use(html) | ||
.process('# Hello world!') | ||
.then(function (file) { | ||
console.log(String(file)); | ||
}, function (err) { | ||
console.error(String(err)); | ||
}) | ||
``` | ||
###### Parameters | ||
Yields: | ||
* `chunk` ([`Buffer`][buffer] or `string`); | ||
* `encoding` (`string`, defaults to `utf8`); | ||
* `callback` (`Function`) — Invoked on successful write. | ||
```html | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="utf-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1"> | ||
</head> | ||
<body> | ||
<h1>Hello world!</h1> | ||
</body> | ||
</html> | ||
``` | ||
###### Returns | ||
### `processor.processSync(file|value)` | ||
`boolean` — Whether the write was successful (currently, always true). | ||
Process the given representation of a file as configured on the | ||
processor. The process invokes `parse`, `run`, and `stringify` | ||
internally. | ||
### `processor.end()` | ||
If asynchronous [**plug-in**][plugin]s are configured, an error is thrown. | ||
Signal the writing is complete. Passes all arguments to a final | ||
[`write`][write], and starts the process (using, when available, | ||
options given to [`pipe`][pipe]). | ||
###### Parameters | ||
###### Events | ||
* `file` ([**VFile**][file]); | ||
* `value` (`string`) — String representation of a file; | ||
* `data` (`string`) | ||
— When the process was successful, triggered with the compiled | ||
file; | ||
* `error` (`Error`) | ||
— When the process was unsuccessful, triggered with the fatal | ||
error; | ||
* `warning` ([`VFileMessage`][vfilemessage]) | ||
— Each message created by the plug-ins in the process is triggered | ||
and separately passed. | ||
###### Returns | ||
`boolean` — Whether the write was successful (currently, always true). | ||
[**VFile**][file] — Virtual file with modified [`contents`][vfile-contents]. | ||
### `processor.pipe(stream[, options])` | ||
###### Note | ||
> **Note**: This does not pass all processed data (e.g., from loose | ||
> `process()` calls) to the destination stream. There’s one process | ||
> created internally especially for streams. Only data piped into | ||
> the processor is piped out. | ||
`processSync` [freezes][freeze] the processor, if not already frozen. | ||
Pipe data streamed into the processor, processed, to the destination | ||
stream. Optionally also set the configuration for how the data | ||
is processed. Calls [`Stream#pipe`][stream-pipe] with the given | ||
arguments under the hood. | ||
###### Example | ||
###### Parameters | ||
```js | ||
var unified = require('unified'); | ||
var markdown = require('remark-parse'); | ||
var remark2rehype = require('remark-rehype'); | ||
var document = require('rehype-document'); | ||
var format = require('rehype-format'); | ||
var html = require('rehype-stringify'); | ||
var reporter = require('vfile-reporter'); | ||
* `stream` ([`WritableStream`][writable-stream]); | ||
* `options` (`Object`, optional) — Configuration for process and | ||
`stream.pipe`. | ||
var processor = unified() | ||
.use(markdown) | ||
.use(remark2rehype) | ||
.use(document) | ||
.use(format) | ||
.use(html); | ||
###### Returns | ||
console.log(processor.processSync('# Hello world!').toString()); | ||
``` | ||
[`WritableStream`][writable-stream] — The given stream. | ||
Yields: | ||
```html | ||
<!DOCTYPE html> | ||
<html lang="en"> | ||
<head> | ||
<meta charset="utf-8"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1"> | ||
</head> | ||
<body> | ||
<h1>Hello world!</h1> | ||
</body> | ||
</html> | ||
``` | ||
### `processor.data(key[, value])` | ||
@@ -573,2 +578,7 @@ | ||
###### Note | ||
Setting information with `data` cannot occur on [frozen][freeze] processors. | ||
Invoke the processor first to create a new unfrozen processor. | ||
###### Example | ||
@@ -590,15 +600,17 @@ | ||
### `processor.abstract()` | ||
### `processor.freeze()` | ||
Turn a processor into an abstract processor. Abstract processors | ||
are meant to be extended, and not to be configured or processed | ||
directly (as concrete processors are). | ||
Freeze a processor. Frozen processors are meant to be extended, and not to | ||
be configured or processed directly. | ||
Once a processor is abstract, it cannot be made concrete again. | ||
But, a new concrete processor functioning just like it can be | ||
created by invoking the processor. | ||
Once a processor is frozen, it cannot be unfrozen. But, a new processor | ||
functioning just like it can be created by invoking the processor. | ||
It’s possible to freeze processors explicitly, by calling `.freeze()`, but | ||
[`.parse()`][parse], [`.run()`][run], [`.stringify()`][stringify], and | ||
[`.process()`][process] call `.freeze()` to freeze a processor too. | ||
###### Returns | ||
`Processor` — The processor on which `abstract` is invoked. | ||
`Processor` — The processor on which `freeze` is invoked. | ||
@@ -615,17 +627,20 @@ ###### Example | ||
module.exports = unified().use(parse).use(stringify).abstract(); | ||
module.exports = unified().use(parse).use(stringify).freeze(); | ||
``` | ||
The below example, `a.js`, shows how that processor can be used to | ||
create a command line interface which reformats markdown passed on | ||
**stdin**(4) and outputs it on **stdout**(4). | ||
The below example, `a.js`, shows how that processor can be used and | ||
configured. | ||
```js | ||
var rehype = require('rehype'); | ||
var format = require('rehype-format'); | ||
// ... | ||
process.stdin.pipe(rehype()).pipe(process.stdout); | ||
rehype() | ||
.use(format) | ||
// ... | ||
``` | ||
The below example, `b.js`, shows a similar looking example which | ||
operates on the abstract [**rehype**][rehype] interface. If this | ||
operates on the frozen [**rehype**][rehype] interface. If this | ||
behaviour was allowed it would result in unexpected behaviour, so | ||
@@ -636,4 +651,8 @@ an error is thrown. **This is invalid**: | ||
var rehype = require('rehype'); | ||
var format = require('rehype-format'); | ||
// ... | ||
process.stdin.pipe(rehype).pipe(process.stdout); | ||
rehype | ||
.use(format) | ||
// ... | ||
``` | ||
@@ -644,15 +663,183 @@ | ||
```txt | ||
~/index.js:118 | ||
throw new Error( | ||
^ | ||
~/node_modules/unified/index.js:436 | ||
throw new Error( | ||
^ | ||
Error: Cannot pipe into abstract processor. | ||
To make the processor concrete, invoke it: use `processor()` instead of `processor`. | ||
at assertConcrete (~/index.js:118:13) | ||
at Function.<anonymous> (~/index.js:135:7) | ||
... | ||
at Object.<anonymous> (~/b.js:76:15) | ||
... | ||
Error: Cannot invoke `use` on a frozen processor. | ||
Create a new processor first, by invoking it: use `processor()` instead of `processor`. | ||
at assertUnfrozen (~/node_modules/unified/index.js:436:11) | ||
at Function.use (~/node_modules/unified/index.js:170:5) | ||
at Object.<anonymous> (~/b.js:6:4) | ||
``` | ||
## `Plugin` | ||
A **unified** plugin changes the way the applied-on processor works, | ||
in the following ways: | ||
* It modifies the [**processor**][processor]: such as changing the | ||
parser, the compiler, or linking the processor to other processors; | ||
* It transforms the [**syntax tree**][node] representation of a file; | ||
* It modifies metadata of a file. | ||
Plug-in’s are a concept which materialise as [**attacher**][attacher]s. | ||
###### Example | ||
`move.js`: | ||
```js | ||
module.exports = move; | ||
function move(options) { | ||
var expected = (options || {}).extname; | ||
if (!expected) { | ||
throw new Error('Missing `extname` in options'); | ||
} | ||
return transformer; | ||
function transformer(tree, file) { | ||
if (file.extname && file.extname !== expected) { | ||
file.extname = expected; | ||
} | ||
} | ||
} | ||
``` | ||
`index.js`: | ||
```js | ||
var unified = require('unified'); | ||
var parse = require('remark-parse'); | ||
var remark2rehype = require('remark-rehype'); | ||
var stringify = require('rehype-stringify'); | ||
var vfile = require('to-vfile'); | ||
var reporter = require('vfile-reporter'); | ||
var move = require('./move'); | ||
rehype() | ||
.use(parse) | ||
.use(remark2rehype) | ||
.use(move, {extname: '.html'}) | ||
.use(stringify) | ||
.process(vfile.readSync('index.md'), function (err, file) { | ||
console.error(reporter(err || file)); | ||
if (file) { | ||
vfile.writeSync(file); // written to `index.html` | ||
} | ||
}) | ||
``` | ||
### `function attacher([options])` | ||
An attacher is the thing passed to [`use`][use]. It configures the | ||
processor and in turn can receive options. | ||
Attachers can configure processors, such as by interacting with parsers | ||
and compilers, linking them to other processors, or by specifying how | ||
the syntax tree is handled. | ||
###### Context | ||
The context object is set to the invoked on [`processor`][processor]. | ||
###### Parameters | ||
* `options` (`*`, optional) — Configuration. | ||
###### Returns | ||
[`transformer`][transformer] — Optional. | ||
###### Note | ||
Attachers are invoked when the processor is [frozen][freeze]: either when | ||
`.freeze()` is called explicitly, or when [`.parse()`][parse], [`.run()`][run], | ||
[`.stringify()`][stringify], or [`.process()`][process] is called for the first | ||
time. | ||
### `function transformer(node, file[, next])` | ||
Transformers modify the syntax tree or metadata of a file. | ||
A transformer is a function which is invoked each time a file is | ||
passed through the transform phase. If an error occurs (either | ||
because it’s thrown, returned, rejected, or passed to [`next`][next]), | ||
the process stops. | ||
The transformation process in **unified** is handled by [`trough`][trough], | ||
see it’s documentation for the exact semantics of transformers. | ||
###### Parameters | ||
* `node` ([**Node**][node]); | ||
* `file` ([**VFile**][file]); | ||
* `next` ([`Function`][next], optional). | ||
###### Returns | ||
* `Error` — Can be returned to stop the process; | ||
* [**Node**][node] — Can be returned and results in further | ||
transformations and `stringify`s to be performed on the new | ||
tree; | ||
* `Promise` — If a promise is returned, the function is asynchronous, | ||
and **must** be resolved (optionally with a [**Node**][node]) or | ||
rejected (optionally with an `Error`). | ||
#### `function next(err[, tree[, file]])` | ||
If the signature of a transformer includes `next` (third argument), | ||
the function **may** finish asynchronous, and **must** invoke `next()`. | ||
###### Parameters | ||
* `err` (`Error`, optional) — Stop the process; | ||
* `node` ([**Node**][node], optional) — New syntax tree; | ||
* `file` ([**VFile**][file], optional) — New virtual file. | ||
## `Preset` | ||
A **unified** preset provides a potentially sharable way to configure | ||
processors. They can contain multiple plugins and optionally settings as | ||
well. | ||
###### Example | ||
`preset.js`: | ||
```js | ||
exports.settings = {bullet: '*', fences: true} | ||
exports.plugins = [ | ||
[require('remark-preset-lint-recommended')] | ||
[require('remark-comment-config')], | ||
[require('remark-validate-links')], | ||
[require('remark-lint'), { | ||
blockquoteIndentation: 2, | ||
checkboxCharacterStyle: {checked: 'x', unchecked: ' '}, | ||
// ... | ||
}], | ||
[require('remark-toc'), {maxDepth: 3, tight: true}] | ||
[require('remark-github')] | ||
] | ||
``` | ||
`index.js`: | ||
```js | ||
var remark = require('remark'); | ||
var vfile = require('to-vfile'); | ||
var reporter = require('vfile-reporter'); | ||
var preset = require('./preset'); | ||
remark().use(preset).process(vfile.readSync('index.md'), function (err, file) { | ||
console.error(reporter(err || file)); | ||
if (file) { | ||
vfile.writeSync(file); | ||
} | ||
}) | ||
``` | ||
## License | ||
@@ -664,14 +851,12 @@ | ||
[travis-badge]: https://img.shields.io/travis/wooorm/unified.svg | ||
[travis-badge]: https://img.shields.io/travis/unifiedjs/unified.svg | ||
[travis]: https://travis-ci.org/wooorm/unified | ||
[travis]: https://travis-ci.org/unifiedjs/unified | ||
[codecov-badge]: https://img.shields.io/codecov/c/github/wooorm/unified.svg | ||
[codecov-badge]: https://img.shields.io/codecov/c/github/unifiedjs/unified.svg | ||
[codecov]: https://codecov.io/github/wooorm/unified | ||
[codecov]: https://codecov.io/github/unifiedjs/unified | ||
[npm-install]: https://docs.npmjs.com/cli/install | ||
[npm]: https://docs.npmjs.com/cli/install | ||
[releases]: https://github.com/wooorm/unified/releases | ||
[license]: LICENSE | ||
@@ -687,17 +872,17 @@ | ||
[hast]: https://github.com/wooorm/hast | ||
[hast]: https://github.com/syntax-tree/hast | ||
[mdast]: https://github.com/wooorm/mdast | ||
[mdast]: https://github.com/syntax-tree/mdast | ||
[nlcst]: https://github.com/wooorm/nlcst | ||
[nlcst]: https://github.com/syntax-tree/nlcst | ||
[unist]: https://github.com/wooorm/unist | ||
[unist]: https://github.com/syntax-tree/unist | ||
[engine]: https://github.com/wooorm/unified-engine | ||
[engine]: https://github.com/unifiedjs/unified-engine | ||
[args]: https://github.com/wooorm/unified-args | ||
[args]: https://github.com/unifiedjs/unified-args | ||
[gulp]: https://github.com/wooorm/unified-engine-gulp | ||
[gulp]: https://github.com/unifiedjs/unified-engine-gulp | ||
[atom]: https://github.com/wooorm/unified-engine-atom | ||
[atom]: https://github.com/unifiedjs/unified-engine-atom | ||
@@ -710,18 +895,12 @@ [remark-rehype]: https://github.com/wooorm/remark-rehype | ||
[unist-utilities]: https://github.com/wooorm/unist#list-of-utilities | ||
[rehype-remark]: https://github.com/wooorm/rehype-remark | ||
[vfile]: https://github.com/wooorm/vfile | ||
[unist-utilities]: https://github.com/syntax-tree/unist#list-of-utilities | ||
[vfile-contents]: https://github.com/wooorm/vfile#vfilecontents | ||
[vfile]: https://github.com/vfile/vfile | ||
[vfile-utilities]: https://github.com/wooorm/vfile#related-tools | ||
[vfile-contents]: https://github.com/vfile/vfile#vfilecontents | ||
[vfilemessage]: https://github.com/wooorm/vfile#vfilemessage | ||
[vfile-utilities]: https://github.com/vfile/vfile#related-tools | ||
[writable-stream]: https://nodejs.org/api/stream.html#stream_class_stream_writable_1 | ||
[stream-pipe]: https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options | ||
[buffer]: https://nodejs.org/api/buffer.html#buffer_buffer | ||
[file]: #file | ||
@@ -733,10 +912,12 @@ | ||
[process]: #processorprocessfilevalue-options-done | ||
[process]: #processorprocessfilevalue-done | ||
[parse]: #processorparsefilevalue-options | ||
[parse]: #processorparsefilevalue | ||
[parser]: #processorparser | ||
[stringify]: #processorstringifynode-file-options | ||
[stringify]: #processorstringifynode-file | ||
[run]: #processorrunnode-file-done | ||
[compiler]: #processorcompiler | ||
@@ -746,3 +927,3 @@ | ||
[attacher]: #function-attacherprocessor-options | ||
[attacher]: #function-attacheroptions | ||
@@ -753,3 +934,3 @@ [transformer]: #function-transformernode-file-next | ||
[abstract]: #processorabstract | ||
[freeze]: #processorfreeze | ||
@@ -762,6 +943,4 @@ [plugin]: #plugin | ||
[write]: #processorwritechunk-encoding-callback | ||
[trough]: https://github.com/wooorm/trough#function-fninput-next | ||
[pipe]: #processorpipestream-options | ||
[trough]: https://github.com/wooorm/trough#function-fninput-next | ||
[promise]: https://developer.mozilla.org/Web/JavaScript/Reference/Global_Objects/Promise |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
910
37718
4
371
1
+ Addedisarray@^2.0.1
+ Addedx-is-function@^1.0.4
+ Addedisarray@2.0.5(transitive)
+ Addedx-is-function@1.0.4(transitive)
- Removedis-buffer@^1.1.4
- Removedonce@^1.3.3
- Removedonce@1.4.0(transitive)
- Removedwrappy@1.0.2(transitive)