legal-markdown-js
Version:
Node.js implementation of LegalMarkdown for processing legal documents with markdown and YAML - Complete feature parity with Ruby version
22,588 lines β’ 619 kB
JavaScript
/**
* Throw a given error.
*
* @param {Error|null|undefined} [error]
* Maybe error.
* @returns {asserts error is null|undefined}
*/
function bail(error) {
if (error) {
throw error
}
}
function getDefaultExportFromCjs(x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
}
var extend$2;
var hasRequiredExtend;
function requireExtend () {
if (hasRequiredExtend) return extend$2;
hasRequiredExtend = 1;
var hasOwn = Object.prototype.hasOwnProperty;
var toStr = Object.prototype.toString;
var defineProperty = Object.defineProperty;
var gOPD = Object.getOwnPropertyDescriptor;
var isArray = function isArray(arr) {
if (typeof Array.isArray === 'function') {
return Array.isArray(arr);
}
return toStr.call(arr) === '[object Array]';
};
var isPlainObject = function isPlainObject(obj) {
if (!obj || toStr.call(obj) !== '[object Object]') {
return false;
}
var hasOwnConstructor = hasOwn.call(obj, 'constructor');
var hasIsPrototypeOf = obj.constructor && obj.constructor.prototype && hasOwn.call(obj.constructor.prototype, 'isPrototypeOf');
// Not own constructor property must be Object
if (obj.constructor && !hasOwnConstructor && !hasIsPrototypeOf) {
return false;
}
// Own properties are enumerated firstly, so to speed up,
// if last one is own, then all properties are own.
var key;
for (key in obj) { /**/ }
return typeof key === 'undefined' || hasOwn.call(obj, key);
};
// If name is '__proto__', and Object.defineProperty is available, define __proto__ as an own property on target
var setProperty = function setProperty(target, options) {
if (defineProperty && options.name === '__proto__') {
defineProperty(target, options.name, {
enumerable: true,
configurable: true,
value: options.newValue,
writable: true
});
} else {
target[options.name] = options.newValue;
}
};
// Return undefined instead of __proto__ if '__proto__' is not an own property
var getProperty = function getProperty(obj, name) {
if (name === '__proto__') {
if (!hasOwn.call(obj, name)) {
return void 0;
} else if (gOPD) {
// In early versions of node, obj['__proto__'] is buggy when obj has
// __proto__ as an own property. Object.getOwnPropertyDescriptor() works.
return gOPD(obj, name).value;
}
}
return obj[name];
};
extend$2 = function extend() {
var options, name, src, copy, copyIsArray, clone;
var target = arguments[0];
var i = 1;
var length = arguments.length;
var deep = false;
// Handle a deep copy situation
if (typeof target === 'boolean') {
deep = target;
target = arguments[1] || {};
// skip the boolean and the target
i = 2;
}
if (target == null || (typeof target !== 'object' && typeof target !== 'function')) {
target = {};
}
for (; i < length; ++i) {
options = arguments[i];
// Only deal with non-null/undefined values
if (options != null) {
// Extend the base object
for (name in options) {
src = getProperty(target, name);
copy = getProperty(options, name);
// Prevent never-ending loop
if (target !== copy) {
// Recurse if we're merging plain objects or arrays
if (deep && copy && (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {
if (copyIsArray) {
copyIsArray = false;
clone = src && isArray(src) ? src : [];
} else {
clone = src && isPlainObject(src) ? src : {};
}
// Never move original objects, clone them
setProperty(target, { name: name, newValue: extend(deep, clone, copy) });
// Don't bring in undefined values
} else if (typeof copy !== 'undefined') {
setProperty(target, { name: name, newValue: copy });
}
}
}
}
}
// Return the modified object
return target;
};
return extend$2;
}
var extendExports = requireExtend();
const extend$1 = /*@__PURE__*/getDefaultExportFromCjs(extendExports);
function isPlainObject(value) {
if (typeof value !== 'object' || value === null) {
return false;
}
const prototype = Object.getPrototypeOf(value);
return (prototype === null || prototype === Object.prototype || Object.getPrototypeOf(prototype) === null) && !(Symbol.toStringTag in value) && !(Symbol.iterator in value);
}
// To do: remove `void`s
// To do: remove `null` from output of our APIs, allow it as user APIs.
/**
* @typedef {(error?: Error | null | undefined, ...output: Array<any>) => void} Callback
* Callback.
*
* @typedef {(...input: Array<any>) => any} Middleware
* Ware.
*
* @typedef Pipeline
* Pipeline.
* @property {Run} run
* Run the pipeline.
* @property {Use} use
* Add middleware.
*
* @typedef {(...input: Array<any>) => void} Run
* Call all middleware.
*
* Calls `done` on completion with either an error or the output of the
* last middleware.
*
* > π **Note**: as the length of input defines whether async functions get a
* > `next` function,
* > itβs recommended to keep `input` at one value normally.
*
* @typedef {(fn: Middleware) => Pipeline} Use
* Add middleware.
*/
/**
* Create new middleware.
*
* @returns {Pipeline}
* Pipeline.
*/
function trough() {
/** @type {Array<Middleware>} */
const fns = [];
/** @type {Pipeline} */
const pipeline = {run, use};
return pipeline
/** @type {Run} */
function run(...values) {
let middlewareIndex = -1;
/** @type {Callback} */
const callback = values.pop();
if (typeof callback !== 'function') {
throw new TypeError('Expected function as last argument, not ' + callback)
}
next(null, ...values);
/**
* Run the next `fn`, or weβre done.
*
* @param {Error | null | undefined} error
* @param {Array<any>} output
*/
function next(error, ...output) {
const fn = fns[++middlewareIndex];
let index = -1;
if (error) {
callback(error);
return
}
// Copy non-nullish input into values.
while (++index < values.length) {
if (output[index] === null || output[index] === undefined) {
output[index] = values[index];
}
}
// Save the newly created `output` for the next call.
values = output;
// Next or done.
if (fn) {
wrap(fn, next)(...output);
} else {
callback(null, ...output);
}
}
}
/** @type {Use} */
function use(middelware) {
if (typeof middelware !== 'function') {
throw new TypeError(
'Expected `middelware` to be a function, not ' + middelware
)
}
fns.push(middelware);
return pipeline
}
}
/**
* Wrap `middleware` into a uniform interface.
*
* You can pass all input to the resulting function.
* `callback` is then called with the output of `middleware`.
*
* If `middleware` accepts more arguments than the later given in input,
* an extra `done` function is passed to it after that input,
* which must be called by `middleware`.
*
* The first value in `input` is the main input value.
* All other input values are the rest input values.
* The values given to `callback` are the input values,
* merged with every non-nullish output value.
*
* * if `middleware` throws an error,
* returns a promise that is rejected,
* or calls the given `done` function with an error,
* `callback` is called with that error
* * if `middleware` returns a value or returns a promise that is resolved,
* that value is the main output value
* * if `middleware` calls `done`,
* all non-nullish values except for the first one (the error) overwrite the
* output values
*
* @param {Middleware} middleware
* Function to wrap.
* @param {Callback} callback
* Callback called with the output of `middleware`.
* @returns {Run}
* Wrapped middleware.
*/
function wrap(middleware, callback) {
/** @type {boolean} */
let called;
return wrapped
/**
* Call `middleware`.
* @this {any}
* @param {Array<any>} parameters
* @returns {void}
*/
function wrapped(...parameters) {
const fnExpectsCallback = middleware.length > parameters.length;
/** @type {any} */
let result;
if (fnExpectsCallback) {
parameters.push(done);
}
try {
result = middleware.apply(this, parameters);
} catch (error) {
const exception = /** @type {Error} */ (error);
// Well, this is quite the pickle.
// `middleware` received a callback and called it synchronously, but that
// threw an error.
// The only thing left to do is to throw the thing instead.
if (fnExpectsCallback && called) {
throw exception
}
return done(exception)
}
if (!fnExpectsCallback) {
if (result && result.then && typeof result.then === 'function') {
result.then(then, done);
} else if (result instanceof Error) {
done(result);
} else {
then(result);
}
}
}
/**
* Call `callback`, only once.
*
* @type {Callback}
*/
function done(error, ...output) {
if (!called) {
called = true;
callback(error, ...output);
}
}
/**
* Call `done` with one value.
*
* @param {any} [value]
*/
function then(value) {
done(null, value);
}
}
/**
* @typedef {import('unist').Node} Node
* @typedef {import('unist').Point} Point
* @typedef {import('unist').Position} Position
*/
/**
* @typedef NodeLike
* @property {string} type
* @property {PositionLike | null | undefined} [position]
*
* @typedef PointLike
* @property {number | null | undefined} [line]
* @property {number | null | undefined} [column]
* @property {number | null | undefined} [offset]
*
* @typedef PositionLike
* @property {PointLike | null | undefined} [start]
* @property {PointLike | null | undefined} [end]
*/
/**
* Serialize the positional info of a point, position (start and end points),
* or node.
*
* @param {Node | NodeLike | Point | PointLike | Position | PositionLike | null | undefined} [value]
* Node, position, or point.
* @returns {string}
* Pretty printed positional info of a node (`string`).
*
* In the format of a range `ls:cs-le:ce` (when given `node` or `position`)
* or a point `l:c` (when given `point`), where `l` stands for line, `c` for
* column, `s` for `start`, and `e` for end.
* An empty string (`''`) is returned if the given value is neither `node`,
* `position`, nor `point`.
*/
function stringifyPosition$1(value) {
// Nothing.
if (!value || typeof value !== 'object') {
return ''
}
// Node.
if ('position' in value || 'type' in value) {
return position$1(value.position)
}
// Position.
if ('start' in value || 'end' in value) {
return position$1(value)
}
// Point.
if ('line' in value || 'column' in value) {
return point$2(value)
}
// ?
return ''
}
/**
* @param {Point | PointLike | null | undefined} point
* @returns {string}
*/
function point$2(point) {
return index$1(point && point.line) + ':' + index$1(point && point.column)
}
/**
* @param {Position | PositionLike | null | undefined} pos
* @returns {string}
*/
function position$1(pos) {
return point$2(pos && pos.start) + '-' + point$2(pos && pos.end)
}
/**
* @param {number | null | undefined} value
* @returns {number}
*/
function index$1(value) {
return value && typeof value === 'number' ? value : 1
}
/**
* @import {Node, Point, Position} from 'unist'
*/
/**
* Message.
*/
class VFileMessage extends Error {
/**
* Create a message for `reason`.
*
* > πͺ¦ **Note**: also has obsolete signatures.
*
* @overload
* @param {string} reason
* @param {Options | null | undefined} [options]
* @returns
*
* @overload
* @param {string} reason
* @param {Node | NodeLike | null | undefined} parent
* @param {string | null | undefined} [origin]
* @returns
*
* @overload
* @param {string} reason
* @param {Point | Position | null | undefined} place
* @param {string | null | undefined} [origin]
* @returns
*
* @overload
* @param {string} reason
* @param {string | null | undefined} [origin]
* @returns
*
* @overload
* @param {Error | VFileMessage} cause
* @param {Node | NodeLike | null | undefined} parent
* @param {string | null | undefined} [origin]
* @returns
*
* @overload
* @param {Error | VFileMessage} cause
* @param {Point | Position | null | undefined} place
* @param {string | null | undefined} [origin]
* @returns
*
* @overload
* @param {Error | VFileMessage} cause
* @param {string | null | undefined} [origin]
* @returns
*
* @param {Error | VFileMessage | string} causeOrReason
* Reason for message, should use markdown.
* @param {Node | NodeLike | Options | Point | Position | string | null | undefined} [optionsOrParentOrPlace]
* Configuration (optional).
* @param {string | null | undefined} [origin]
* Place in code where the message originates (example:
* `'my-package:my-rule'` or `'my-rule'`).
* @returns
* Instance of `VFileMessage`.
*/
// eslint-disable-next-line complexity
constructor(causeOrReason, optionsOrParentOrPlace, origin) {
super();
if (typeof optionsOrParentOrPlace === 'string') {
origin = optionsOrParentOrPlace;
optionsOrParentOrPlace = undefined;
}
/** @type {string} */
let reason = '';
/** @type {Options} */
let options = {};
let legacyCause = false;
if (optionsOrParentOrPlace) {
// Point.
if (
'line' in optionsOrParentOrPlace &&
'column' in optionsOrParentOrPlace
) {
options = {place: optionsOrParentOrPlace};
}
// Position.
else if (
'start' in optionsOrParentOrPlace &&
'end' in optionsOrParentOrPlace
) {
options = {place: optionsOrParentOrPlace};
}
// Node.
else if ('type' in optionsOrParentOrPlace) {
options = {
ancestors: [optionsOrParentOrPlace],
place: optionsOrParentOrPlace.position
};
}
// Options.
else {
options = {...optionsOrParentOrPlace};
}
}
if (typeof causeOrReason === 'string') {
reason = causeOrReason;
}
// Error.
else if (!options.cause && causeOrReason) {
legacyCause = true;
reason = causeOrReason.message;
options.cause = causeOrReason;
}
if (!options.ruleId && !options.source && typeof origin === 'string') {
const index = origin.indexOf(':');
if (index === -1) {
options.ruleId = origin;
} else {
options.source = origin.slice(0, index);
options.ruleId = origin.slice(index + 1);
}
}
if (!options.place && options.ancestors && options.ancestors) {
const parent = options.ancestors[options.ancestors.length - 1];
if (parent) {
options.place = parent.position;
}
}
const start =
options.place && 'start' in options.place
? options.place.start
: options.place;
/**
* Stack of ancestor nodes surrounding the message.
*
* @type {Array<Node> | undefined}
*/
this.ancestors = options.ancestors || undefined;
/**
* Original error cause of the message.
*
* @type {Error | undefined}
*/
this.cause = options.cause || undefined;
/**
* Starting column of message.
*
* @type {number | undefined}
*/
this.column = start ? start.column : undefined;
/**
* State of problem.
*
* * `true` β error, file not usable
* * `false` β warning, change may be needed
* * `undefined` β change likely not needed
*
* @type {boolean | null | undefined}
*/
this.fatal = undefined;
/**
* Path of a file (used throughout the `VFile` ecosystem).
*
* @type {string | undefined}
*/
this.file = '';
// Field from `Error`.
/**
* Reason for message.
*
* @type {string}
*/
this.message = reason;
/**
* Starting line of error.
*
* @type {number | undefined}
*/
this.line = start ? start.line : undefined;
// Field from `Error`.
/**
* Serialized positional info of message.
*
* On normal errors, this would be something like `ParseError`, buit in
* `VFile` messages we use this space to show where an error happened.
*/
this.name = stringifyPosition$1(options.place) || '1:1';
/**
* Place of message.
*
* @type {Point | Position | undefined}
*/
this.place = options.place || undefined;
/**
* Reason for message, should use markdown.
*
* @type {string}
*/
this.reason = this.message;
/**
* Category of message (example: `'my-rule'`).
*
* @type {string | undefined}
*/
this.ruleId = options.ruleId || undefined;
/**
* Namespace of message (example: `'my-package'`).
*
* @type {string | undefined}
*/
this.source = options.source || undefined;
// Field from `Error`.
/**
* Stack of message.
*
* This is used by normal errors to show where something happened in
* programming code, irrelevant for `VFile` messages,
*
* @type {string}
*/
this.stack =
legacyCause && options.cause && typeof options.cause.stack === 'string'
? options.cause.stack
: '';
// The following fields are βwell knownβ.
// Not standard.
// Feel free to add other non-standard fields to your messages.
/**
* Specify the source value thatβs being reported, which is deemed
* incorrect.
*
* @type {string | undefined}
*/
this.actual = undefined;
/**
* Suggest acceptable values that can be used instead of `actual`.
*
* @type {Array<string> | undefined}
*/
this.expected = undefined;
/**
* Long form description of the message (you should use markdown).
*
* @type {string | undefined}
*/
this.note = undefined;
/**
* Link to docs for the message.
*
* > π **Note**: this must be an absolute URL that can be passed as `x`
* > to `new URL(x)`.
*
* @type {string | undefined}
*/
this.url = undefined;
}
}
VFileMessage.prototype.file = '';
VFileMessage.prototype.name = '';
VFileMessage.prototype.reason = '';
VFileMessage.prototype.message = '';
VFileMessage.prototype.stack = '';
VFileMessage.prototype.column = undefined;
VFileMessage.prototype.line = undefined;
VFileMessage.prototype.ancestors = undefined;
VFileMessage.prototype.cause = undefined;
VFileMessage.prototype.fatal = undefined;
VFileMessage.prototype.place = undefined;
VFileMessage.prototype.ruleId = undefined;
VFileMessage.prototype.source = undefined;
// A derivative work based on:
// <https://github.com/browserify/path-browserify>.
// Which is licensed:
//
// MIT License
//
// Copyright (c) 2013 James Halliday
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// A derivative work based on:
//
// Parts of that are extracted from Nodeβs internal `path` module:
// <https://github.com/nodejs/node/blob/master/lib/path.js>.
// Which is licensed:
//
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
const minpath = {basename, dirname, extname, join: join$1, sep: '/'};
/* eslint-disable max-depth, complexity */
/**
* Get the basename from a path.
*
* @param {string} path
* File path.
* @param {string | null | undefined} [extname]
* Extension to strip.
* @returns {string}
* Stem or basename.
*/
function basename(path, extname) {
if (extname !== undefined && typeof extname !== 'string') {
throw new TypeError('"ext" argument must be a string')
}
assertPath$1(path);
let start = 0;
let end = -1;
let index = path.length;
/** @type {boolean | undefined} */
let seenNonSlash;
if (
extname === undefined ||
extname.length === 0 ||
extname.length > path.length
) {
while (index--) {
if (path.codePointAt(index) === 47 /* `/` */) {
// If we reached a path separator that was not part of a set of path
// separators at the end of the string, stop now.
if (seenNonSlash) {
start = index + 1;
break
}
} else if (end < 0) {
// We saw the first non-path separator, mark this as the end of our
// path component.
seenNonSlash = true;
end = index + 1;
}
}
return end < 0 ? '' : path.slice(start, end)
}
if (extname === path) {
return ''
}
let firstNonSlashEnd = -1;
let extnameIndex = extname.length - 1;
while (index--) {
if (path.codePointAt(index) === 47 /* `/` */) {
// If we reached a path separator that was not part of a set of path
// separators at the end of the string, stop now.
if (seenNonSlash) {
start = index + 1;
break
}
} else {
if (firstNonSlashEnd < 0) {
// We saw the first non-path separator, remember this index in case
// we need it if the extension ends up not matching.
seenNonSlash = true;
firstNonSlashEnd = index + 1;
}
if (extnameIndex > -1) {
// Try to match the explicit extension.
if (path.codePointAt(index) === extname.codePointAt(extnameIndex--)) {
if (extnameIndex < 0) {
// We matched the extension, so mark this as the end of our path
// component
end = index;
}
} else {
// Extension does not match, so our result is the entire path
// component
extnameIndex = -1;
end = firstNonSlashEnd;
}
}
}
}
if (start === end) {
end = firstNonSlashEnd;
} else if (end < 0) {
end = path.length;
}
return path.slice(start, end)
}
/**
* Get the dirname from a path.
*
* @param {string} path
* File path.
* @returns {string}
* File path.
*/
function dirname(path) {
assertPath$1(path);
if (path.length === 0) {
return '.'
}
let end = -1;
let index = path.length;
/** @type {boolean | undefined} */
let unmatchedSlash;
// Prefix `--` is important to not run on `0`.
while (--index) {
if (path.codePointAt(index) === 47 /* `/` */) {
if (unmatchedSlash) {
end = index;
break
}
} else if (!unmatchedSlash) {
// We saw the first non-path separator
unmatchedSlash = true;
}
}
return end < 0
? path.codePointAt(0) === 47 /* `/` */
? '/'
: '.'
: end === 1 && path.codePointAt(0) === 47 /* `/` */
? '//'
: path.slice(0, end)
}
/**
* Get an extname from a path.
*
* @param {string} path
* File path.
* @returns {string}
* Extname.
*/
function extname(path) {
assertPath$1(path);
let index = path.length;
let end = -1;
let startPart = 0;
let startDot = -1;
// Track the state of characters (if any) we see before our first dot and
// after any path separator we find.
let preDotState = 0;
/** @type {boolean | undefined} */
let unmatchedSlash;
while (index--) {
const code = path.codePointAt(index);
if (code === 47 /* `/` */) {
// If we reached a path separator that was not part of a set of path
// separators at the end of the string, stop now.
if (unmatchedSlash) {
startPart = index + 1;
break
}
continue
}
if (end < 0) {
// We saw the first non-path separator, mark this as the end of our
// extension.
unmatchedSlash = true;
end = index + 1;
}
if (code === 46 /* `.` */) {
// If this is our first dot, mark it as the start of our extension.
if (startDot < 0) {
startDot = index;
} else if (preDotState !== 1) {
preDotState = 1;
}
} else if (startDot > -1) {
// We saw a non-dot and non-path separator before our dot, so we should
// have a good chance at having a non-empty extension.
preDotState = -1;
}
}
if (
startDot < 0 ||
end < 0 ||
// We saw a non-dot character immediately before the dot.
preDotState === 0 ||
// The (right-most) trimmed path component is exactly `..`.
(preDotState === 1 && startDot === end - 1 && startDot === startPart + 1)
) {
return ''
}
return path.slice(startDot, end)
}
/**
* Join segments from a path.
*
* @param {Array<string>} segments
* Path segments.
* @returns {string}
* File path.
*/
function join$1(...segments) {
let index = -1;
/** @type {string | undefined} */
let joined;
while (++index < segments.length) {
assertPath$1(segments[index]);
if (segments[index]) {
joined =
joined === undefined ? segments[index] : joined + '/' + segments[index];
}
}
return joined === undefined ? '.' : normalize(joined)
}
/**
* Normalize a basic file path.
*
* @param {string} path
* File path.
* @returns {string}
* File path.
*/
// Note: `normalize` is not exposed as `path.normalize`, so some code is
// manually removed from it.
function normalize(path) {
assertPath$1(path);
const absolute = path.codePointAt(0) === 47; /* `/` */
// Normalize the path according to POSIX rules.
let value = normalizeString(path, !absolute);
if (value.length === 0 && !absolute) {
value = '.';
}
if (value.length > 0 && path.codePointAt(path.length - 1) === 47 /* / */) {
value += '/';
}
return absolute ? '/' + value : value
}
/**
* Resolve `.` and `..` elements in a path with directory names.
*
* @param {string} path
* File path.
* @param {boolean} allowAboveRoot
* Whether `..` can move above root.
* @returns {string}
* File path.
*/
function normalizeString(path, allowAboveRoot) {
let result = '';
let lastSegmentLength = 0;
let lastSlash = -1;
let dots = 0;
let index = -1;
/** @type {number | undefined} */
let code;
/** @type {number} */
let lastSlashIndex;
while (++index <= path.length) {
if (index < path.length) {
code = path.codePointAt(index);
} else if (code === 47 /* `/` */) {
break
} else {
code = 47; /* `/` */
}
if (code === 47 /* `/` */) {
if (lastSlash === index - 1 || dots === 1) ; else if (lastSlash !== index - 1 && dots === 2) {
if (
result.length < 2 ||
lastSegmentLength !== 2 ||
result.codePointAt(result.length - 1) !== 46 /* `.` */ ||
result.codePointAt(result.length - 2) !== 46 /* `.` */
) {
if (result.length > 2) {
lastSlashIndex = result.lastIndexOf('/');
if (lastSlashIndex !== result.length - 1) {
if (lastSlashIndex < 0) {
result = '';
lastSegmentLength = 0;
} else {
result = result.slice(0, lastSlashIndex);
lastSegmentLength = result.length - 1 - result.lastIndexOf('/');
}
lastSlash = index;
dots = 0;
continue
}
} else if (result.length > 0) {
result = '';
lastSegmentLength = 0;
lastSlash = index;
dots = 0;
continue
}
}
if (allowAboveRoot) {
result = result.length > 0 ? result + '/..' : '..';
lastSegmentLength = 2;
}
} else {
if (result.length > 0) {
result += '/' + path.slice(lastSlash + 1, index);
} else {
result = path.slice(lastSlash + 1, index);
}
lastSegmentLength = index - lastSlash - 1;
}
lastSlash = index;
dots = 0;
} else if (code === 46 /* `.` */ && dots > -1) {
dots++;
} else {
dots = -1;
}
}
return result
}
/**
* Make sure `path` is a string.
*
* @param {string} path
* File path.
* @returns {asserts path is string}
* Nothing.
*/
function assertPath$1(path) {
if (typeof path !== 'string') {
throw new TypeError(
'Path must be a string. Received ' + JSON.stringify(path)
)
}
}
/* eslint-enable max-depth, complexity */
const minproc = { cwd };
function cwd() {
return "/";
}
/**
* Checks if a value has the shape of a WHATWG URL object.
*
* Using a symbol or instanceof would not be able to recognize URL objects
* coming from other implementations (e.g. in Electron), so instead we are
* checking some well known properties for a lack of a better test.
*
* We use `href` and `protocol` as they are the only properties that are
* easy to retrieve and calculate due to the lazy nature of the getters.
*
* We check for auth attribute to distinguish legacy url instance with
* WHATWG URL instance.
*
* @param {unknown} fileUrlOrPath
* File path or URL.
* @returns {fileUrlOrPath is URL}
* Whether itβs a URL.
*/
// From: <https://github.com/nodejs/node/blob/6a3403c/lib/internal/url.js#L720>
function isUrl(fileUrlOrPath) {
return Boolean(
fileUrlOrPath !== null &&
typeof fileUrlOrPath === 'object' &&
'href' in fileUrlOrPath &&
fileUrlOrPath.href &&
'protocol' in fileUrlOrPath &&
fileUrlOrPath.protocol &&
// @ts-expect-error: indexing is fine.
fileUrlOrPath.auth === undefined
)
}
// See: <https://github.com/nodejs/node/blob/6a3403c/lib/internal/url.js>
/**
* @param {URL | string} path
* File URL.
* @returns {string}
* File URL.
*/
function urlToPath(path) {
if (typeof path === 'string') {
path = new URL(path);
} else if (!isUrl(path)) {
/** @type {NodeJS.ErrnoException} */
const error = new TypeError(
'The "path" argument must be of type string or an instance of URL. Received `' +
path +
'`'
);
error.code = 'ERR_INVALID_ARG_TYPE';
throw error
}
if (path.protocol !== 'file:') {
/** @type {NodeJS.ErrnoException} */
const error = new TypeError('The URL must be of scheme file');
error.code = 'ERR_INVALID_URL_SCHEME';
throw error
}
return getPathFromURLPosix(path)
}
/**
* Get a path from a POSIX URL.
*
* @param {URL} url
* URL.
* @returns {string}
* File path.
*/
function getPathFromURLPosix(url) {
if (url.hostname !== '') {
/** @type {NodeJS.ErrnoException} */
const error = new TypeError(
'File URL host must be "localhost" or empty on darwin'
);
error.code = 'ERR_INVALID_FILE_URL_HOST';
throw error
}
const pathname = url.pathname;
let index = -1;
while (++index < pathname.length) {
if (
pathname.codePointAt(index) === 37 /* `%` */ &&
pathname.codePointAt(index + 1) === 50 /* `2` */
) {
const third = pathname.codePointAt(index + 2);
if (third === 70 /* `F` */ || third === 102 /* `f` */) {
/** @type {NodeJS.ErrnoException} */
const error = new TypeError(
'File URL path must not include encoded / characters'
);
error.code = 'ERR_INVALID_FILE_URL_PATH';
throw error
}
}
}
return decodeURIComponent(pathname)
}
const order = (
/** @type {const} */
[
"history",
"path",
"basename",
"stem",
"extname",
"dirname"
]
);
class VFile {
/**
* Create a new virtual file.
*
* `options` is treated as:
*
* * `string` or `Uint8Array` β `{value: options}`
* * `URL` β `{path: options}`
* * `VFile` β shallow copies its data over to the new file
* * `object` β all fields are shallow copied over to the new file
*
* Path related fields are set in the following order (least specific to
* most specific): `history`, `path`, `basename`, `stem`, `extname`,
* `dirname`.
*
* You cannot set `dirname` or `extname` without setting either `history`,
* `path`, `basename`, or `stem` too.
*
* @param {Compatible | null | undefined} [value]
* File value.
* @returns
* New instance.
*/
constructor(value) {
let options;
if (!value) {
options = {};
} else if (isUrl(value)) {
options = { path: value };
} else if (typeof value === "string" || isUint8Array$1(value)) {
options = { value };
} else {
options = value;
}
this.cwd = "cwd" in options ? "" : minproc.cwd();
this.data = {};
this.history = [];
this.messages = [];
this.value;
this.map;
this.result;
this.stored;
let index = -1;
while (++index < order.length) {
const field2 = order[index];
if (field2 in options && options[field2] !== void 0 && options[field2] !== null) {
this[field2] = field2 === "history" ? [...options[field2]] : options[field2];
}
}
let field;
for (field in options) {
if (!order.includes(field)) {
this[field] = options[field];
}
}
}
/**
* Get the basename (including extname) (example: `'index.min.js'`).
*
* @returns {string | undefined}
* Basename.
*/
get basename() {
return typeof this.path === "string" ? minpath.basename(this.path) : void 0;
}
/**
* Set basename (including extname) (`'index.min.js'`).
*
* Cannot contain path separators (`'/'` on unix, macOS, and browsers, `'\'`
* on windows).
* Cannot be nullified (use `file.path = file.dirname` instead).
*
* @param {string} basename
* Basename.
* @returns {undefined}
* Nothing.
*/
set basename(basename) {
assertNonEmpty(basename, "basename");
assertPart(basename, "basename");
this.path = minpath.join(this.dirname || "", basename);
}
/**
* Get the parent path (example: `'~'`).
*
* @returns {string | undefined}
* Dirname.
*/
get dirname() {
return typeof this.path === "string" ? minpath.dirname(this.path) : void 0;
}
/**
* Set the parent path (example: `'~'`).
*
* Cannot be set if thereβs no `path` yet.
*
* @param {string | undefined} dirname
* Dirname.
* @returns {undefined}
* Nothing.
*/
set dirname(dirname) {
assertPath(this.basename, "dirname");
this.path = minpath.join(dirname || "", this.basename);
}
/**
* Get the extname (including dot) (example: `'.js'`).
*
* @returns {string | undefined}
* Extname.
*/
get extname() {
return typeof this.path === "string" ? minpath.extname(this.path) : void 0;
}
/**
* Set the extname (including dot) (example: `'.js'`).
*
* Cannot contain path separators (`'/'` on unix, macOS, and browsers, `'\'`
* on windows).
* Cannot be set if thereβs no `path` yet.
*
* @param {string | undefined} extname
* Extname.
* @returns {undefined}
* Nothing.
*/
set extname(extname) {
assertPart(extname, "extname");
assertPath(this.dirname, "extname");
if (extname) {
if (extname.codePointAt(0) !== 46) {
throw new Error("`extname` must start with `.`");
}
if (extname.includes(".", 1)) {
throw new Error("`extname` cannot contain multiple dots");
}
}
this.path = minpath.join(this.dirname, this.stem + (extname || ""));
}
/**
* Get the full path (example: `'~/index.min.js'`).
*
* @returns {string}
* Path.
*/
get path() {
return this.history[this.history.length - 1];
}
/**
* Set the full path (example: `'~/index.min.js'`).
*
* Cannot be nullified.
* You can set a file URL (a `URL` object with a `file:` protocol) which will
* be turned into a path with `url.fileURLToPath`.
*
* @param {URL | string} path
* Path.
* @returns {undefined}
* Nothing.
*/
set path(path) {
if (isUrl(path)) {
path = urlToPath(path);
}
assertNonEmpty(path, "path");
if (this.path !== path) {
this.history.push(path);
}
}
/**
* Get the stem (basename w/o extname) (example: `'index.min'`).
*
* @returns {string | undefined}
* Stem.
*/
get stem() {
return typeof this.path === "string" ? minpath.basename(this.path, this.extname) : void 0;
}
/**
* Set the stem (basename w/o extname) (example: `'index.min'`).
*
* Cannot contain path separators (`'/'` on unix, macOS, and browsers, `'\'`
* on windows).
* Cannot be nullified (use `file.path = file.dirname` instead).
*
* @param {string} stem
* Stem.
* @returns {undefined}
* Nothing.
*/
set stem(stem) {
assertNonEmpty(stem, "stem");
assertPart(stem, "stem");
this.path = minpath.join(this.dirname || "", stem + (this.extname || ""));
}
// Normal prototypal methods.
/**
* Create a fatal message for `reason` associated with the file.
*
* The `fatal` field of the message is set to `true` (error; file not usable)
* and the `file` field is set to the current file path.
* The message is added to the `messages` field on `file`.
*
* > πͺ¦ **Note**: also has obsolete signatures.
*
* @overload
* @param {string} reason
* @param {MessageOptions | null | undefined} [options]
* @returns {never}
*
* @overload
* @param {string} reason
* @param {Node | NodeLike | null | undefined} parent
* @param {string | null | undefined} [origin]
* @returns {never}
*
* @overload
* @param {string} reason
* @param {Point | Position | null | undefined} place
* @param {string | null | undefined} [origin]
* @returns {never}
*
* @overload
* @param {string} reason
* @param {string | null | undefined} [origin]
* @returns {never}
*
* @overload
* @param {Error | VFileMessage} cause
* @param {Node | NodeLike | null | undefined} parent
* @param {string | null | undefined} [origin]
* @returns {never}
*
* @overload
* @param {Error | VFileMessage} cause
* @param {Point | Position | null | undefined} place
* @param {string | null | undefined} [origin]
* @returns {never}
*
* @overload
* @param {Error | VFileMessage} cause
* @param {string | null | undefined} [origin]
* @returns {never}
*
* @param {Error | VFileMessage | string} causeOrReason
* Reason for message, should use markdown.
* @param {Node | NodeLike | MessageOptions | Point | Position | string | null | undefined} [optionsOrParentOrPlace]
* Configuration (optional).
* @param {string | null | undefined} [origin]
* Place in code where the message originates (example:
* `'my-package:my-rule'` or `'my-rule'`).
* @returns {never}
* Never.
* @throws {VFileMessage}
* Message.
*/
fail(causeOrReason, optionsOrParentOrPlace, origin) {
const message = this.message(causeOrReason, optionsOrParentOrPlace, origin);
message.fatal = true;
throw message;
}
/**
* Create an info message for `reason` associated with the file.
*
* The `fatal` field of the message is set to `undefined` (info; change
* likely not needed) and the `file` field is set to the current file path.
* The message is added to the `messages` field on `file`.
*
* > πͺ¦ **Note**: also has obsolete signatures.
*
* @overload
* @param {string} reason
* @param {MessageOptions | null | undefined} [options]
* @returns {VFileMessage}
*
* @overload
* @param {string} reason
* @param {Node | NodeLike | null | undefined} parent
* @param {string | null | undefined} [origin]
* @returns {VFileMessage}
*
* @overload
* @param {string} reason
* @param {Point | Position | null | undefined} place
* @param {string | null | undefined} [origin]
* @returns {VFileMessage}
*
* @overload
* @param {string} reason
* @param {string | null | undefined} [origin]
* @returns {VFileMessage}
*
* @overload
* @param {Error | VFileMessage} cause
* @param {Node | NodeLike | null | undefined} parent
* @param {string | null | undefined} [origin]
* @returns {VFileMessage}
*
* @overload
* @param {Error | VFileMessage} cause
* @param {Point | Position | null | undefined} place
* @param {string | null | undefined} [origin]
* @returns {VFileMessage}
*
* @overload
* @param {Error | VFileMessage} cause
* @param {string | null | undefined} [origin]
* @returns {VFileMessage}
*
* @param {Error | VFileMessage | string} causeOrReason
* Reason for message, should use markdown.
* @param {Node | NodeLike | MessageOptions | Point | Position | string | null | undefined} [optionsOrParentOrPlace]
* Configuration (optional).
* @param {string | null | undefined} [origin]
* Place in code where the message originates (example:
* `'my-package:my-rule'` or `'my-rule'`).
* @returns {VFileMessage}
* Message.
*/
info(causeOrReason, optionsOrParentOrPlace, origin) {
const message = this.message(causeOrReason, optionsOrParentOrPlace, origin);
message.fatal = void 0;
return message;
}
/**
* Create a message for `reason` associated with the file.
*
* The `fatal` field of the message is set to `false` (warning; change may be
* needed) and the `file` field is set to the current file path.
* The message is added to the `messages` field on `file`.
*
* > πͺ¦ **Note**: also has obsolete signatures.
*
* @overload
* @param {string} reason
* @param {MessageOptions | null | undefined} [options]
* @returns {VFileMessage}
*
* @overload
* @param {string} reason
* @param {Node | NodeLike | null | undefined} parent
* @param {string | null | undefined} [origin]
* @returns {VFileMessage}
*
* @overload
* @param {string} reason
* @param {Point | Position | null | undefined} place
* @param {string | null | undefined} [origin]
* @returns {VFileMessage}
*
* @overload
* @param {string} reason
* @param {string | null | undefined} [origin]
* @returns {VFileMessage}
*
* @overload
* @param {Error | VFileMessage} cause
* @param {Node | NodeLike | null | undefined} parent
* @param {string | null | undefined} [origin]
* @returns {VFileMessage}
*
* @overload
* @param {Error | VFileMessage} cause
* @param {Point | Position | null | undefined} place
* @param {string | null | undefined} [origin]
* @returns {VFileMessage}
*
* @overload
* @param {Error | VFileMessage} cause
* @param {string | null | undefined} [origin]
* @returns {VFileMessage}
*
* @param {Error | VFileMessage | string} causeOrReason
* Reason for message, should use markdown.
* @param {Node | NodeLike | MessageOptions | Point | Position | string | null | undefined} [optionsOrParentOrPlace]
* Configuration (optional).
* @param {string | null | undefined} [origin]
* Place in code where the message originates (example:
* `'my-package:my-rule'` or `'my-rule'`).
* @returns {VFileMessage}
* Message.
*/
message(causeOrReason, optionsOrParentOrPlace, origin) {
const message = new VFileMessage(
// @ts-expect-error: the overloads are fine.
causeOrReason,
optionsOrParentOrPlace,
origin
);
if (this.path) {
message.name = this.path + ":" + message.name;
message.file = this.path;
}
message.fatal = false;
this.messages.push(message);
return message;
}
/**
* Serialize the file.
*
* > **Note**: which encodings are supported depends on the engine.
* > For info on Node.js, see:
* > <https://nodejs.org/api/util.html#whatwg-supported-encodings>.
*
* @param {string | null | undefined} [encoding='utf8']
* Character encoding to understand `value` as when itβs a `Uint8Array`
* (default: `'utf-8'`).
* @returns {string}
* Serialized file.
*/
toString(encoding) {
if (this.value === void 0) {
return "";
}
if (typeof this.value === "string") {
return this.value;
}
const decoder = new TextDecoder(encoding || void 0);
return decoder.decode(this.value);
}
}
function assertPart(part, name) {
if (part && part.includes(minpath.sep)) {
throw new Error(
"`" + name + "` cannot be a path: did not expect `" + minpath.sep + "`"
);
}
}
function assertNonEmpty(part, name) {
if (!part) {
throw new Error("`" + name + "` cannot be empty");
}
}
function assertPath(path, name) {
if (!path) {
throw new Error("Setting `" + name + "` requires `path` to be set too");
}
}
function isUint8Array$1(value) {
return Boolean(
value && typeof value === "object" && "byteLength" in value && "byteOffset" in value
);
}
const CallableInstance =
/**
* @type {new <Parameters extends Array<unknown>, Result>(property: string | symbol) => (...parameters: Parameters) => Result}
*/
(
/** @type {unknown} */
(
/**
* @this {Function}
* @param {string | symbol} property
* @returns {(...parameters: Array<unknown>) => unknown}
*/
function (property) {
const self = this;
const constr = self.constructor;
const proto = /** @type {Record<string | symbol, Function>} */ (
// Prototypes do exist.
// type-coverage:ignore-next-line
constr.prototype
);
const value = proto[property];
/** @type {(...parameters: Array<unknown>) => unknown} */
const apply = function () {
return value.apply(apply, arguments)
};
Object.setPrototypeOf(apply, proto);
// Not needed for us in `unified`: we only call this on the `copy`
// function,
// and we don't need to add its fields (`length`, `name`)
// over.
// See also: GH-246.
// const names = Object.getOwnPropertyNames(value)
//
// for (const p of names) {
// const descriptor = Object.getOwnPropertyDescriptor(value, p)
// if (descriptor) Object.defineProperty(apply, p, descriptor)
// }
return apply
}
)
);
const own$3 = {}.hasOwnProperty;
class Processor extends CallableInstance {
/**
* Create a processor.
*/
constructor() {
super("copy");
this.Compiler = void 0;
this.Parser = void 0;
this.attachers = [];
this.compiler = void 0;
this.freezeIndex = -1;
this.frozen = void 0;
this.namespace = {};
this.parser = void 0;
this.transformers = trough();
}
/**
* Copy a processor.
*
* @deprecated
* This is a private internal method and should not be used.
* @returns {Processor<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>}
* New *unfrozen* processor ({@linkcode Processor}) that is
* configured to work the same as its ancestor.
* When the descendant processor is configured in the future it does not
* affect the ancestral processor.
*/
copy() {
const destination = (
/** @type {Processor<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>} */
new Processor()
);
let index = -1;
while (++index < this.attachers.length) {
const attacher = this.attachers[index];
destination.use(...attacher);
}
destination.data(extend$1(true, {}, this.namespace));
return destination;
}
/**
* Configure the processor with info available to all plugins.
* Information is stored in an object.
*
* Typically, options can be given to a specific plugin, but sometimes it
* makes sense to have information shared with several plugins.
* For example, a list of HTML elements that are self-closing, which is
* needed during all phases.
*
* > **Note**: setting information cannot occur on *frozen* processors.
* > Call the processor first to create a new unfrozen processor.
*
* > **Note**: to register custom data in TypeScript, augment the
* > {@linkcode Data} interface.
*
* @example
* This example show how to get and set info:
*
* ```js
* import {unified} from 'unified'
*
* const processor = unified().data('alpha', 'bravo')
*
* processor.data('alpha') // => 'bravo'
*
* processor.data() // => {alpha: 'bravo'}
*
* processor.data({charlie: 'delta'})
*
* processor.data() // => {charlie: 'delta'}
* ```
*
* @template {keyof Data} Key
*
* @overload
* @returns {Data}
*
* @overload
* @param {Data} dataset
* @returns {Processor<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>}
*
* @overload
* @param {Key} key
* @returns {Data[Key]}
*
* @overload
* @param {Key} key
* @param {Data[Key]} value
* @returns {Processor<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>}
*
* @param {Data | Key} [key]
* Key to get or set, or entire dataset to set, or nothing to get the
* entire dataset (optional).
* @param {Data[Key]} [value]
* Value to set (optional).
* @returns {unknown}
* The current processor when setting, the value at `key` when getting, or
* the entire dataset when getting without key.
*/
data(key, value) {
if (typeof key === "string") {
if (arguments.length === 2) {
assertUnfrozen("data", this.frozen);
this.namespace[key] = value;
return this;
}
return own$3.call(this.namespace, key) && this.namespace[key] || void 0;
}
if (key) {
assertUnfrozen("data", this.frozen);
this.namespace = key;
return this;
}
return this.namespace;
}
/**
* Freeze a processor.
*
* Frozen processors are meant to be extended and not to be configured
* directly.
*
* When a processor is frozen it cannot be unfrozen.
* New processors working the same way can be created by calling the
* processor.
*
* Itβs possible to freeze processors explicitly by calling `.freeze()`.
* Processors freeze automatically when `.parse()`, `.run()`, `.runSync()`,
* `.stringify()`, `.process()`, or `.processSync()` are called.
*
* @returns {Processor<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>}
* The current processor.
*/
freeze() {
if (this.frozen) {
return this;
}
const self = (
/** @type {Processor} */
/** @type {unknown} */
this
);
while (++this.freezeIndex < this.attachers.length) {
const [attacher, ...options] = this.attachers[this.freezeIndex];
if (options[0] === false) {
continue;
}
if (options[0] === true) {
options[0] = void 0;
}
const transformer = attacher.call(self, ...options);
if (typeof transformer === "function") {
this.transformers.use(transformer);
}
}
this.frozen = true;
this.freezeIndex = Number.POSITIVE_INFINITY;
return this;
}
/**
* Parse text to a syntax tree.
*
* > **Note**: `parse` freezes the processor if not already *frozen*.
*
* > **Note**: `parse` performs the parse phase, not the run phase or other
* > phases.
*
* @param {Compatible | undefined} [file]
* file to parse (optional); typically `string` or `VFile`; any value
* accepted as `x` in `new VFile(x)`.
* @returns {ParseTree extends undefined ? Node : ParseTree}
* Syntax tree representing `file`.
*/
parse(file) {
this.freeze();
const realFile = vfile(file);
const parser = this.parser || this.Parser;
assertParser("parse", parser);
return parser(String(realFile), realFile);
}
/**
* Process the given file as configured on the processor.
*
* > **Note**: `process` freezes the processor if not already *frozen*.
*
* > **Note**: `process` performs the parse, run, and stringify phases.
*
* @overload
* @param {Compatible | undefined} file
* @param {ProcessCallback<VFileWithOutput<CompileResult>>} done
* @returns {undefined}
*
* @overload
* @param {Compatible | undefined} [file]
* @returns {Promise<VFileWithOutput<CompileResult>>}
*
* @param {Compatible | undefined} [file]
* File (optional); typically `string` or `VFile`]; any value accepted as
* `x` in `new VFile(x)`.
* @param {ProcessCallback<VFileWithOutput<CompileResult>> | undefined} [done]
* Callback (optional).
* @returns {Promise<VFile> | undefined}
* Nothing if `done` is given.
* Otherwise a promise, rejected with a fatal error or resolved with the
* processed file.
*
* The parsed, transformed, and compiled value is available at
* `file.value` (see note).
*
* > **Note**: unified typically compiles by serializing: most
* > compilers return `string` (or `Uint8Array`).
* > Some compilers, such as the one configured with
* > [`rehype-react`][rehype-react], return other values (in this case, a
* > React tree).
* > If youβre using a compiler that doesnβt serialize, expect different
* > result values.
* >
* > To register custom results in TypeScript, add them to
* > {@linkcode CompileResultMap}.
*
* [rehype-react]: https://github.com/rehypejs/rehype-react
*/
process(file, done) {
const self = this;
this.freeze();
assertParser("process", this.parser || this.Parser);
assertCompiler("process", this.compiler || this.Compiler);
return done ? executor(void 0, done) : new Promise(executor);
function executor(resolve, reject) {
const realFile = vfile(file);
const parseTree = (
/** @type {HeadTree extends undefined ? Node : HeadTree} */
/** @type {unknown} */
self.parse(realFile)
);
self.run(parseTree, realFile, function(error, tree, file2) {
if (error || !tree || !file2) {
return realDone(error);
}
const compileTree = (
/** @type {CompileTree extends undefined ? Node : CompileTree} */
/** @type {unknown} */
tree
);
const compileResult = self.stringify(compileTree, file2);
if (looksLikeAValue(compileResult)) {
file2.value = compileResult;
} else {
file2.result = compileResult;
}
realDone(
error,
/** @type {VFileWithOutput<CompileResult>} */
file2
);
});
function realDone(error, file2) {
if (error || !file2) {
reject(error);
} else if (resolve) {
resolve(file2);
} else {
done(void 0, file2);
}
}
}
}
/**
* Process the given file as configured on the processor.
*
* An error is thrown if asynchronous transforms are configured.
*
* > **Note**: `processSync` freezes the processor if not already *frozen*.
*
* > **Note**: `processSync` performs the parse, run, and stringify phases.
*
* @param {Compatible | undefined} [file]
* File (optional); typically `string` or `VFile`; any value accepted as
* `x` in `new VFile(x)`.
* @returns {VFileWithOutput<CompileResult>}
* The processed file.
*
* The parsed, transformed, and compiled value is available at
* `file.value` (see note).
*
* > **Note**: unified typically compiles by serializing: most
* > compilers return `string` (or `Uint8Array`).
* > Some compilers, such as the one configured with
* > [`rehype-react`][rehype-react], return other values (in this case, a
* > React tree).
* > If youβre using a compiler that doesnβt serialize, expect different
* > result values.
* >
* > To register custom results in TypeScript, add them to
* > {@linkcode CompileResultMap}.
*
* [rehype-react]: https://github.com/rehypejs/rehype-react
*/
processSync(file) {
let complete = false;
let result;
this.freeze();
assertParser("processSync", this.parser || this.Parser);
assertCompiler("processSync", this.compiler || this.Compiler);
this.process(file, realDone);
assertDone("processSync", "process", complete);
return result;
function realDone(error, file2) {
complete = true;
bail(error);
result = file2;
}
}
/**
* Run *transformers* on a syntax tree.
*
* > **Note**: `run` freezes the processor if not already *frozen*.
*
* > **Note**: `run` performs the run phase, not other phases.
*
* @overload
* @param {HeadTree extends undefined ? Node : HeadTree} tree
* @param {RunCallback<TailTree extends undefined ? Node : TailTree>} done
* @returns {undefined}
*
* @overload
* @param {HeadTree extends undefined ? Node : HeadTree} tree
* @param {Compatible | undefined} file
* @param {RunCallback<TailTree extends undefined ? Node : TailTree>} done
* @returns {undefined}
*
* @overload
* @param {HeadTree extends undefined ? Node : HeadTree} tree
* @param {Compatible | undefined} [file]
* @returns {Promise<TailTree extends undefined ? Node : TailTree>}
*
* @param {HeadTree extends undefined ? Node : HeadTree} tree
* Tree to transform and inspect.
* @param {(
* RunCallback<TailTree extends undefined ? Node : TailTree> |
* Compatible
* )} [file]
* File associated with `node` (optional); any value accepted as `x` in
* `new VFile(x)`.
* @param {RunCallback<TailTree extends undefined ? Node : TailTree>} [done]
* Callback (optional).
* @returns {Promise<TailTree extends undefined ? Node : TailTree> | undefined}
* Nothing if `done` is given.
* Otherwise, a promise rejected with a fatal error or resolved with the
* transformed tree.
*/
run(tree, file, done) {
assertNode(tree);
this.freeze();
const transformers = this.transformers;
if (!done && typeof file === "function") {
done = file;
file = void 0;
}
return done ? executor(void 0, done) : new Promise(executor);
function executor(resolve, reject) {
const realFile = vfile(file);
transformers.run(tree, realFile, realDone);
function realDone(error, outputTree, file2) {
const resultingTree = (
/** @type {TailTree extends undefined ? Node : TailTree} */
outputTree || tree
);
if (error) {
reject(error);
} else if (resolve) {
resolve(resultingTree);
} else {
done(void 0, resultingTree, file2);
}
}
}
}
/**
* Run *transformers* on a syntax tree.
*
* An error is thrown if asynchronous transforms are configured.
*
* > **Note**: `runSync` freezes the processor if not already *frozen*.
*
* > **Note**: `runSync` performs the run phase, not other phases.
*
* @param {HeadTree extends undefined ? Node : HeadTree} tree
* Tree to transform and inspect.
* @param {Compatible | undefined} [file]
* File associated with `node` (optional); any value accepted as `x` in
* `new VFile(x)`.
* @returns {TailTree extends undefined ? Node : TailTree}
* Transformed tree.
*/
runSync(tree, file) {
let complete = false;
let result;
this.run(tree, file, realDone);
assertDone("runSync", "run", complete);
return result;
function realDone(error, tree2) {
bail(error);
result = tree2;
complete = true;
}
}
/**
* Compile a syntax tree.
*
* > **Note**: `stringify` freezes the processor if not already *frozen*.
*
* > **Note**: `stringify` performs the stringify phase, not the run phase
* > or other phases.
*
* @param {CompileTree extends undefined ? Node : CompileTree} tree
* Tree to compile.
* @param {Compatible | undefined} [file]
* File associated with `node` (optional); any value accepted as `x` in
* `new VFile(x)`.
* @returns {CompileResult extends undefined ? Value : CompileResult}
* Textual representation of the tree (see note).
*
* > **Note**: unified typically compiles by serializing: most compilers
* > return `string` (or `Uint8Array`).
* > Some compilers, such as the one configured with
* > [`rehype-react`][rehype-react], return other values (in this case, a
* > React tree).
* > If youβre using a compiler that doesnβt serialize, expect different
* > result values.
* >
* > To register custom results in TypeScript, add them to
* > {@linkcode CompileResultMap}.
*
* [rehype-react]: https://github.com/rehypejs/rehype-react
*/
stringify(tree, file) {
this.freeze();
const realFile = vfile(file);
const compiler = this.compiler || this.Compiler;
assertCompiler("stringify", compiler);
assertNode(tree);
return compiler(tree, realFile);
}
/**
* Configure the processor to use a plugin, a list of usable values, or a
* preset.
*
* If the processor is already using a plugin, the previous plugin
* configuration is changed based on the options that are passed in.
* In other words, the plugin is not added a second time.
*
* > **Note**: `use` cannot be called on *frozen* processors.
* > Call the processor first to create a new unfrozen processor.
*
* @example
* There are many ways to pass plugins to `.use()`.
* This example gives an overview:
*
* ```js
* import {unified} from 'unified'
*
* unified()
* // Plugin with options:
* .use(pluginA, {x: true, y: true})
* // Passing the same plugin again merges configuration (to `{x: true, y: false, z: true}`):
* .use(pluginA, {y: false, z: true})
* // Plugins:
* .use([pluginB, pluginC])
* // Two plugins, the second with options:
* .use([pluginD, [pluginE, {}]])
* // Preset with plugins and settings:
* .use({plugins: [pluginF, [pluginG, {}]], settings: {position: false}})
* // Settings only:
* .use({settings: {position: false}})
* ```
*
* @template {Array<unknown>} [Parameters=[]]
* @template {Node | string | undefined} [Input=undefined]
* @template [Output=Input]
*
* @overload
* @param {Preset | null | undefined} [preset]
* @returns {Processor<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>}
*
* @overload
* @param {PluggableList} list
* @returns {Processor<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>}
*
* @overload
* @param {Plugin<Parameters, Input, Output>} plugin
* @param {...(Parameters | [boolean])} parameters
* @returns {UsePlugin<ParseTree, HeadTree, TailTree, CompileTree, CompileResult, Input, Output>}
*
* @param {PluggableList | Plugin | Preset | null | undefined} value
* Usable value.
* @param {...unknown} parameters
* Parameters, when a plugin is given as a usable value.
* @returns {Processor<ParseTree, HeadTree, TailTree, CompileTree, CompileResult>}
* Current processor.
*/
use(value, ...parameters) {
const attachers = this.attachers;
const namespace = this.namespace;
assertUnfrozen("use", this.frozen);
if (value === null || value === void 0) ; else if (typeof value === "function") {
addPlugin(value, parameters);
} else if (typeof value === "object") {
if (Array.isArray(value)) {
addList(value);
} else {
addPreset(value);
}
} else {
throw new TypeError("Expected usable value, not `" + value + "`");
}
return this;
function add(value2) {
if (typeof value2 === "function") {
addPlugin(value2, []);
} else if (typeof value2 === "object") {
if (Array.isArray(value2)) {
const [plugin, ...parameters2] = (
/** @type {PluginTuple<Array<unknown>>} */
value2
);
addPlugin(plugin, parameters2);
} else {
addPreset(value2);
}
} else {
throw new TypeError("Expected usable value, not `" + value2 + "`");
}
}
function addPreset(result) {
if (!("plugins" in result) && !("settings" in result)) {
throw new Error(
"Expected usable value but received an empty preset, which is probably a mistake: presets typically come with `plugins` and sometimes with `settings`, but this has neither"
);
}
addList(result.plugins);
if (result.settings) {
namespace.settings = extend$1(true, namespace.settings, result.settings);
}
}
function addList(plugins) {
let index = -1;
if (plugins === null || plugins === void 0) ; else if (Array.isArray(plugins)) {
while (++index < plugins.length) {
const thing = plugins[index];
add(thing);
}
} else {
throw new TypeError("Expected a list of plugins, not `" + plugins + "`");
}
}
function addPlugin(plugin, parameters2) {
let index = -1;
let entryIndex = -1;
while (++index < attachers.length) {
if (attachers[index][0] === plugin) {
entryIndex = index;
break;
}
}
if (entryIndex === -1) {
attachers.push([plugin, ...parameters2]);
} else if (parameters2.length > 0) {
let [primary, ...rest] = parameters2;
const currentPrimary = attachers[entryIndex][1];
if (isPlainObject(currentPrimary) && isPlainObject(primary)) {
primary = extend$1(true, currentPrimary, primary);
}
attachers[entryIndex] = [plugin, primary, ...rest];
}
}
}
}
const unified = new Processor().freeze();
function assertParser(name, value) {
if (typeof value !== "function") {
throw new TypeError("Cannot `" + name + "` without `parser`");
}
}
function assertCompiler(name, value) {
if (typeof value !== "function") {
throw new TypeError("Cannot `" + name + "` without `compiler`");
}
}
function assertUnfrozen(name, frozen) {
if (frozen) {
throw new Error(
"Cannot call `" + name + "` on a frozen processor.\nCreate a new processor first, by calling it: use `processor()` instead of `processor`."
);
}
}
function assertNode(node) {
if (!isPlainObject(node) || typeof node.type !== "string") {
throw new TypeError("Expected node, got `" + node + "`");
}
}
function assertDone(name, asyncName, complete) {
if (!complete) {
throw new Error(
"`" + name + "` finished async. Use `" + asyncName + "` instead"
);
}
}
function vfile(value) {
return looksLikeAVFile(value) ? value : new VFile(value);
}
function looksLikeAVFile(value) {
return Boolean(
value && typeof value === "object" && "message" in value && "messages" in value
);
}
function looksLikeAValue(value) {
return typeof value === "string" || isUint8Array(value);
}
function isUint8Array(value) {
return Boolean(
value && typeof value === "object" && "byteLength" in value && "byteOffset" in value
);
}
/**
* @typedef {import('mdast').Nodes} Nodes
*
* @typedef Options
* Configuration (optional).
* @property {boolean | null | undefined} [includeImageAlt=true]
* Whether to use `alt` for `image`s (default: `true`).
* @property {boolean | null | undefined} [includeHtml=true]
* Whether to use `value` of HTML (default: `true`).
*/
/** @type {Options} */
const emptyOptions = {};
/**
* Get the text content of a node or list of nodes.
*
* Prefers the nodeβs plain-text fields, otherwise serializes its children,
* and if the given value is an array, serialize the nodes in it.
*
* @param {unknown} [value]
* Thing to serialize, typically `Node`.
* @param {Options | null | undefined} [options]
* Configuration (optional).
* @returns {string}
* Serialized `value`.
*/
function toString(value, options) {
const settings = emptyOptions;
const includeImageAlt =
typeof settings.includeImageAlt === 'boolean'
? settings.includeImageAlt
: true;
const includeHtml =
typeof settings.includeHtml === 'boolean' ? settings.includeHtml : true;
return one(value, includeImageAlt, includeHtml)
}
/**
* One node or several nodes.
*
* @param {unknown} value
* Thing to serialize.
* @param {boolean} includeImageAlt
* Include image `alt`s.
* @param {boolean} includeHtml
* Include HTML.
* @returns {string}
* Serialized node.
*/
function one(value, includeImageAlt, includeHtml) {
if (node(value)) {
if ('value' in value) {
return value.type === 'html' && !includeHtml ? '' : value.value
}
if (includeImageAlt && 'alt' in value && value.alt) {
return value.alt
}
if ('children' in value) {
return all(value.children, includeImageAlt, includeHtml)
}
}
if (Array.isArray(value)) {
return all(value, includeImageAlt, includeHtml)
}
return ''
}
/**
* Serialize a list of nodes.
*
* @param {Array<unknown>} values
* Thing to serialize.
* @param {boolean} includeImageAlt
* Include image `alt`s.
* @param {boolean} includeHtml
* Include HTML.
* @returns {string}
* Serialized nodes.
*/
function all(values, includeImageAlt, includeHtml) {
/** @type {Array<string>} */
const result = [];
let index = -1;
while (++index < values.length) {
result[index] = one(values[index], includeImageAlt, includeHtml);
}
return result.join('')
}
/**
* Check if `value` looks like a node.
*
* @param {unknown} value
* Thing.
* @returns {value is Nodes}
* Whether `value` is a node.
*/
function node(value) {
return Boolean(value && typeof value === 'object')
}
const element = document.createElement("i");
function decodeNamedCharacterReference(value) {
const characterReference = "&" + value + ";";
element.innerHTML = characterReference;
const character = element.textContent;
if (
// @ts-expect-error: TypeScript is wrong that `textContent` on elements can
// yield `null`.
character.charCodeAt(character.length - 1) === 59 && value !== "semi"
) {
return false;
}
return character === characterReference ? false : character;
}
/**
* Like `Array#splice`, but smarter for giant arrays.
*
* `Array#splice` takes all items to be inserted as individual argument which
* causes a stack overflow in V8 when trying to insert 100k items for instance.
*
* Otherwise, this does not return the removed items, and takes `items` as an
* array instead of rest parameters.
*
* @template {unknown} T
* Item type.
* @param {Array<T>} list
* List to operate on.
* @param {number} start
* Index to remove/insert at (can be negative).
* @param {number} remove
* Number of items to remove.
* @param {Array<T>} items
* Items to inject into `list`.
* @returns {undefined}
* Nothing.
*/
function splice(list, start, remove, items) {
const end = list.length;
let chunkStart = 0;
/** @type {Array<unknown>} */
let parameters;
// Make start between zero and `end` (included).
if (start < 0) {
start = -start > end ? 0 : end + start;
} else {
start = start > end ? end : start;
}
remove = remove > 0 ? remove : 0;
// No need to chunk the items if thereβs only a couple (10k) items.
if (items.length < 10000) {
parameters = Array.from(items);
parameters.unshift(start, remove);
// @ts-expect-error Hush, itβs fine.
list.splice(...parameters);
} else {
// Delete `remove` items starting from `start`
if (remove) list.splice(start, remove);
// Insert the items in chunks to not cause stack overflows.
while (chunkStart < items.length) {
parameters = items.slice(chunkStart, chunkStart + 10000);
parameters.unshift(start, 0);
// @ts-expect-error Hush, itβs fine.
list.splice(...parameters);
chunkStart += 10000;
start += 10000;
}
}
}
/**
* Append `items` (an array) at the end of `list` (another array).
* When `list` was empty, returns `items` instead.
*
* This prevents a potentially expensive operation when `list` is empty,
* and adds items in batches to prevent V8 from hanging.
*
* @template {unknown} T
* Item type.
* @param {Array<T>} list
* List to operate on.
* @param {Array<T>} items
* Items to add to `list`.
* @returns {Array<T>}
* Either `list` or `items`.
*/
function push(list, items) {
if (list.length > 0) {
splice(list, list.length, 0, items);
return list;
}
return items;
}
/**
* @import {
* Extension,
* Handles,
* HtmlExtension,
* NormalizedExtension
* } from 'micromark-util-types'
*/
const hasOwnProperty = {}.hasOwnProperty;
/**
* Combine multiple syntax extensions into one.
*
* @param {ReadonlyArray<Extension>} extensions
* List of syntax extensions.
* @returns {NormalizedExtension}
* A single combined extension.
*/
function combineExtensions(extensions) {
/** @type {NormalizedExtension} */
const all = {};
let index = -1;
while (++index < extensions.length) {
syntaxExtension(all, extensions[index]);
}
return all
}
/**
* Merge `extension` into `all`.
*
* @param {NormalizedExtension} all
* Extension to merge into.
* @param {Extension} extension
* Extension to merge.
* @returns {undefined}
* Nothing.
*/
function syntaxExtension(all, extension) {
/** @type {keyof Extension} */
let hook;
for (hook in extension) {
const maybe = hasOwnProperty.call(all, hook) ? all[hook] : undefined;
/** @type {Record<string, unknown>} */
const left = maybe || (all[hook] = {});
/** @type {Record<string, unknown> | undefined} */
const right = extension[hook];
/** @type {string} */
let code;
if (right) {
for (code in right) {
if (!hasOwnProperty.call(left, code)) left[code] = [];
const value = right[code];
constructs(
// @ts-expect-error Looks like a list.
left[code],
Array.isArray(value) ? value : value ? [value] : []
);
}
}
}
}
/**
* Merge `list` into `existing` (both lists of constructs).
* Mutates `existing`.
*
* @param {Array<unknown>} existing
* List of constructs to merge into.
* @param {Array<unknown>} list
* List of constructs to merge.
* @returns {undefined}
* Nothing.
*/
function constructs(existing, list) {
let index = -1;
/** @type {Array<unknown>} */
const before = [];
while (++index < list.length) {
(list[index].add === 'after' ? existing : before).push(list[index]);
}
splice(existing, 0, 0, before);
}
/**
* Turn the number (in string form as either hexa- or plain decimal) coming from
* a numeric character reference into a character.
*
* Sort of like `String.fromCodePoint(Number.parseInt(value, base))`, but makes
* non-characters and control characters safe.
*
* @param {string} value
* Value to decode.
* @param {number} base
* Numeric base.
* @returns {string}
* Character.
*/
function decodeNumericCharacterReference(value, base) {
const code = Number.parseInt(value, base);
if (
// C0 except for HT, LF, FF, CR, space.
code < 9 || code === 11 || code > 13 && code < 32 ||
// Control character (DEL) of C0, and C1 controls.
code > 126 && code < 160 ||
// Lone high surrogates and low surrogates.
code > 55_295 && code < 57_344 ||
// Noncharacters.
code > 64_975 && code < 65_008 || /* eslint-disable no-bitwise */
(code & 65_535) === 65_535 || (code & 65_535) === 65_534 || /* eslint-enable no-bitwise */
// Out of range
code > 1_114_111) {
return "\uFFFD";
}
return String.fromCodePoint(code);
}
/**
* Normalize an identifier (as found in references, definitions).
*
* Collapses markdown whitespace, trim, and then lower- and uppercase.
*
* Some characters are considered βuppercaseβ, such as U+03F4 (`Ο΄`), but if their
* lowercase counterpart (U+03B8 (`ΞΈ`)) is uppercased will result in a different
* uppercase character (U+0398 (`Ξ`)).
* So, to get a canonical form, we perform both lower- and uppercase.
*
* Using uppercase last makes sure keys will never interact with default
* prototypal values (such as `constructor`): nothing in the prototype of
* `Object` is uppercase.
*
* @param {string} value
* Identifier to normalize.
* @returns {string}
* Normalized identifier.
*/
function normalizeIdentifier(value) {
return value
// Collapse markdown whitespace.
.replace(/[\t\n\r ]+/g, " ")
// Trim.
.replace(/^ | $/g, '')
// Some characters are considered βuppercaseβ, but if their lowercase
// counterpart is uppercased will result in a different uppercase
// character.
// Hence, to get that form, we perform both lower- and uppercase.
// Upper case makes sure keys will not interact with default prototypal
// methods: no method is uppercase.
.toLowerCase().toUpperCase();
}
/**
* @import {Code} from 'micromark-util-types'
*/
/**
* Check whether the character code represents an ASCII alpha (`a` through `z`,
* case insensitive).
*
* An **ASCII alpha** is an ASCII upper alpha or ASCII lower alpha.
*
* An **ASCII upper alpha** is a character in the inclusive range U+0041 (`A`)
* to U+005A (`Z`).
*
* An **ASCII lower alpha** is a character in the inclusive range U+0061 (`a`)
* to U+007A (`z`).
*
* @param code
* Code.
* @returns {boolean}
* Whether it matches.
*/
const asciiAlpha = regexCheck(/[A-Za-z]/);
/**
* Check whether the character code represents an ASCII alphanumeric (`a`
* through `z`, case insensitive, or `0` through `9`).
*
* An **ASCII alphanumeric** is an ASCII digit (see `asciiDigit`) or ASCII alpha
* (see `asciiAlpha`).
*
* @param code
* Code.
* @returns {boolean}
* Whether it matches.
*/
const asciiAlphanumeric = regexCheck(/[\dA-Za-z]/);
/**
* Check whether the character code represents an ASCII atext.
*
* atext is an ASCII alphanumeric (see `asciiAlphanumeric`), or a character in
* the inclusive ranges U+0023 NUMBER SIGN (`#`) to U+0027 APOSTROPHE (`'`),
* U+002A ASTERISK (`*`), U+002B PLUS SIGN (`+`), U+002D DASH (`-`), U+002F
* SLASH (`/`), U+003D EQUALS TO (`=`), U+003F QUESTION MARK (`?`), U+005E
* CARET (`^`) to U+0060 GRAVE ACCENT (`` ` ``), or U+007B LEFT CURLY BRACE
* (`{`) to U+007E TILDE (`~`).
*
* See:
* **\[RFC5322]**:
* [Internet Message Format](https://tools.ietf.org/html/rfc5322).
* P. Resnick.
* IETF.
*
* @param code
* Code.
* @returns {boolean}
* Whether it matches.
*/
const asciiAtext = regexCheck(/[#-'*+\--9=?A-Z^-~]/);
/**
* Check whether a character code is an ASCII control character.
*
* An **ASCII control** is a character in the inclusive range U+0000 NULL (NUL)
* to U+001F (US), or U+007F (DEL).
*
* @param {Code} code
* Code.
* @returns {boolean}
* Whether it matches.
*/
function asciiControl(code) {
return (
// Special whitespace codes (which have negative values), C0 and Control
// character DEL
code !== null && (code < 32 || code === 127)
);
}
/**
* Check whether the character code represents an ASCII digit (`0` through `9`).
*
* An **ASCII digit** is a character in the inclusive range U+0030 (`0`) to
* U+0039 (`9`).
*
* @param code
* Code.
* @returns {boolean}
* Whether it matches.
*/
const asciiDigit = regexCheck(/\d/);
/**
* Check whether the character code represents an ASCII hex digit (`a` through
* `f`, case insensitive, or `0` through `9`).
*
* An **ASCII hex digit** is an ASCII digit (see `asciiDigit`), ASCII upper hex
* digit, or an ASCII lower hex digit.
*
* An **ASCII upper hex digit** is a character in the inclusive range U+0041
* (`A`) to U+0046 (`F`).
*
* An **ASCII lower hex digit** is a character in the inclusive range U+0061
* (`a`) to U+0066 (`f`).
*
* @param code
* Code.
* @returns {boolean}
* Whether it matches.
*/
const asciiHexDigit = regexCheck(/[\dA-Fa-f]/);
/**
* Check whether the character code represents ASCII punctuation.
*
* An **ASCII punctuation** is a character in the inclusive ranges U+0021
* EXCLAMATION MARK (`!`) to U+002F SLASH (`/`), U+003A COLON (`:`) to U+0040 AT
* SIGN (`@`), U+005B LEFT SQUARE BRACKET (`[`) to U+0060 GRAVE ACCENT
* (`` ` ``), or U+007B LEFT CURLY BRACE (`{`) to U+007E TILDE (`~`).
*
* @param code
* Code.
* @returns {boolean}
* Whether it matches.
*/
const asciiPunctuation = regexCheck(/[!-/:-@[-`{-~]/);
/**
* Check whether a character code is a markdown line ending.
*
* A **markdown line ending** is the virtual characters M-0003 CARRIAGE RETURN
* LINE FEED (CRLF), M-0004 LINE FEED (LF) and M-0005 CARRIAGE RETURN (CR).
*
* In micromark, the actual character U+000A LINE FEED (LF) and U+000D CARRIAGE
* RETURN (CR) are replaced by these virtual characters depending on whether
* they occurred together.
*
* @param {Code} code
* Code.
* @returns {boolean}
* Whether it matches.
*/
function markdownLineEnding(code) {
return code !== null && code < -2;
}
/**
* Check whether a character code is a markdown line ending (see
* `markdownLineEnding`) or markdown space (see `markdownSpace`).
*
* @param {Code} code
* Code.
* @returns {boolean}
* Whether it matches.
*/
function markdownLineEndingOrSpace(code) {
return code !== null && (code < 0 || code === 32);
}
/**
* Check whether a character code is a markdown space.
*
* A **markdown space** is the concrete character U+0020 SPACE (SP) and the
* virtual characters M-0001 VIRTUAL SPACE (VS) and M-0002 HORIZONTAL TAB (HT).
*
* In micromark, the actual character U+0009 CHARACTER TABULATION (HT) is
* replaced by one M-0002 HORIZONTAL TAB (HT) and between 0 and 3 M-0001 VIRTUAL
* SPACE (VS) characters, depending on the column at which the tab occurred.
*
* @param {Code} code
* Code.
* @returns {boolean}
* Whether it matches.
*/
function markdownSpace(code) {
return code === -2 || code === -1 || code === 32;
}
// Size note: removing ASCII from the regex and using `asciiPunctuation` here
// In fact adds to the bundle size.
/**
* Check whether the character code represents Unicode punctuation.
*
* A **Unicode punctuation** is a character in the Unicode `Pc` (Punctuation,
* Connector), `Pd` (Punctuation, Dash), `Pe` (Punctuation, Close), `Pf`
* (Punctuation, Final quote), `Pi` (Punctuation, Initial quote), `Po`
* (Punctuation, Other), or `Ps` (Punctuation, Open) categories, or an ASCII
* punctuation (see `asciiPunctuation`).
*
* See:
* **\[UNICODE]**:
* [The Unicode Standard](https://www.unicode.org/versions/).
* Unicode Consortium.
*
* @param code
* Code.
* @returns
* Whether it matches.
*/
const unicodePunctuation = regexCheck(/\p{P}|\p{S}/u);
/**
* Check whether the character code represents Unicode whitespace.
*
* Note that this does handle micromark specific markdown whitespace characters.
* See `markdownLineEndingOrSpace` to check that.
*
* A **Unicode whitespace** is a character in the Unicode `Zs` (Separator,
* Space) category, or U+0009 CHARACTER TABULATION (HT), U+000A LINE FEED (LF),
* U+000C (FF), or U+000D CARRIAGE RETURN (CR) (**\[UNICODE]**).
*
* See:
* **\[UNICODE]**:
* [The Unicode Standard](https://www.unicode.org/versions/).
* Unicode Consortium.
*
* @param code
* Code.
* @returns
* Whether it matches.
*/
const unicodeWhitespace = regexCheck(/\s/);
/**
* Create a code check from a regex.
*
* @param {RegExp} regex
* Expression.
* @returns {(code: Code) => boolean}
* Check.
*/
function regexCheck(regex) {
return check;
/**
* Check whether a code matches the bound regex.
*
* @param {Code} code
* Character code.
* @returns {boolean}
* Whether the character code matches the bound regex.
*/
function check(code) {
return code !== null && code > -1 && regex.test(String.fromCharCode(code));
}
}
/**
* @import {Effects, State, TokenType} from 'micromark-util-types'
*/
// To do: implement `spaceOrTab`, `spaceOrTabMinMax`, `spaceOrTabWithOptions`.
/**
* Parse spaces and tabs.
*
* There is no `nok` parameter:
*
* * spaces in markdown are often optional, in which case this factory can be
* used and `ok` will be switched to whether spaces were found or not
* * one line ending or space can be detected with `markdownSpace(code)` right
* before using `factorySpace`
*
* ###### Examples
*
* Where `β` represents a tab (plus how much it expands) and `β ` represents a
* single space.
*
* ```markdown
* β
* β β β β
* ββ
* ```
*
* @param {Effects} effects
* Context.
* @param {State} ok
* State switched to when successful.
* @param {TokenType} type
* Type (`' \t'`).
* @param {number | undefined} [max=Infinity]
* Max (exclusive).
* @returns {State}
* Start state.
*/
function factorySpace(effects, ok, type, max) {
const limit = max ? max - 1 : Number.POSITIVE_INFINITY;
let size = 0;
return start;
/** @type {State} */
function start(code) {
if (markdownSpace(code)) {
effects.enter(type);
return prefix(code);
}
return ok(code);
}
/** @type {State} */
function prefix(code) {
if (markdownSpace(code) && size++ < limit) {
effects.consume(code);
return prefix;
}
effects.exit(type);
return ok(code);
}
}
/**
* @import {
* InitialConstruct,
* Initializer,
* State,
* TokenizeContext,
* Token
* } from 'micromark-util-types'
*/
/** @type {InitialConstruct} */
const content$1 = {
tokenize: initializeContent
};
/**
* @this {TokenizeContext}
* Context.
* @type {Initializer}
* Content.
*/
function initializeContent(effects) {
const contentStart = effects.attempt(this.parser.constructs.contentInitial, afterContentStartConstruct, paragraphInitial);
/** @type {Token} */
let previous;
return contentStart;
/** @type {State} */
function afterContentStartConstruct(code) {
if (code === null) {
effects.consume(code);
return;
}
effects.enter("lineEnding");
effects.consume(code);
effects.exit("lineEnding");
return factorySpace(effects, contentStart, "linePrefix");
}
/** @type {State} */
function paragraphInitial(code) {
effects.enter("paragraph");
return lineStart(code);
}
/** @type {State} */
function lineStart(code) {
const token = effects.enter("chunkText", {
contentType: "text",
previous
});
if (previous) {
previous.next = token;
}
previous = token;
return data(code);
}
/** @type {State} */
function data(code) {
if (code === null) {
effects.exit("chunkText");
effects.exit("paragraph");
effects.consume(code);
return;
}
if (markdownLineEnding(code)) {
effects.consume(code);
effects.exit("chunkText");
return lineStart;
}
// Data.
effects.consume(code);
return data;
}
}
/**
* @import {
* Construct,
* ContainerState,
* InitialConstruct,
* Initializer,
* Point,
* State,
* TokenizeContext,
* Tokenizer,
* Token
* } from 'micromark-util-types'
*/
/** @type {InitialConstruct} */
const document$2 = {
tokenize: initializeDocument
};
/** @type {Construct} */
const containerConstruct = {
tokenize: tokenizeContainer
};
/**
* @this {TokenizeContext}
* Self.
* @type {Initializer}
* Initializer.
*/
function initializeDocument(effects) {
const self = this;
/** @type {Array<StackItem>} */
const stack = [];
let continued = 0;
/** @type {TokenizeContext | undefined} */
let childFlow;
/** @type {Token | undefined} */
let childToken;
/** @type {number} */
let lineStartOffset;
return start;
/** @type {State} */
function start(code) {
// First we iterate through the open blocks, starting with the root
// document, and descending through last children down to the last open
// block.
// Each block imposes a condition that the line must satisfy if the block is
// to remain open.
// For example, a block quote requires a `>` character.
// A paragraph requires a non-blank line.
// In this phase we may match all or just some of the open blocks.
// But we cannot close unmatched blocks yet, because we may have a lazy
// continuation line.
if (continued < stack.length) {
const item = stack[continued];
self.containerState = item[1];
return effects.attempt(item[0].continuation, documentContinue, checkNewContainers)(code);
}
// Done.
return checkNewContainers(code);
}
/** @type {State} */
function documentContinue(code) {
continued++;
// Note: this field is called `_closeFlow` but it also closes containers.
// Perhaps a good idea to rename it but itβs already used in the wild by
// extensions.
if (self.containerState._closeFlow) {
self.containerState._closeFlow = undefined;
if (childFlow) {
closeFlow();
}
// Note: this algorithm for moving events around is similar to the
// algorithm when dealing with lazy lines in `writeToChild`.
const indexBeforeExits = self.events.length;
let indexBeforeFlow = indexBeforeExits;
/** @type {Point | undefined} */
let point;
// Find the flow chunk.
while (indexBeforeFlow--) {
if (self.events[indexBeforeFlow][0] === 'exit' && self.events[indexBeforeFlow][1].type === "chunkFlow") {
point = self.events[indexBeforeFlow][1].end;
break;
}
}
exitContainers(continued);
// Fix positions.
let index = indexBeforeExits;
while (index < self.events.length) {
self.events[index][1].end = {
...point
};
index++;
}
// Inject the exits earlier (theyβre still also at the end).
splice(self.events, indexBeforeFlow + 1, 0, self.events.slice(indexBeforeExits));
// Discard the duplicate exits.
self.events.length = index;
return checkNewContainers(code);
}
return start(code);
}
/** @type {State} */
function checkNewContainers(code) {
// Next, after consuming the continuation markers for existing blocks, we
// look for new block starts (e.g. `>` for a block quote).
// If we encounter a new block start, we close any blocks unmatched in
// step 1 before creating the new block as a child of the last matched
// block.
if (continued === stack.length) {
// No need to `check` whether thereβs a container, of `exitContainers`
// would be moot.
// We can instead immediately `attempt` to parse one.
if (!childFlow) {
return documentContinued(code);
}
// If we have concrete content, such as block HTML or fenced code,
// we canβt have containers βpierceβ into them, so we can immediately
// start.
if (childFlow.currentConstruct && childFlow.currentConstruct.concrete) {
return flowStart(code);
}
// If we do have flow, it could still be a blank line,
// but weβd be interrupting it w/ a new container if thereβs a current
// construct.
// To do: next major: remove `_gfmTableDynamicInterruptHack` (no longer
// needed in micromark-extension-gfm-table@1.0.6).
self.interrupt = Boolean(childFlow.currentConstruct && !childFlow._gfmTableDynamicInterruptHack);
}
// Check if there is a new container.
self.containerState = {};
return effects.check(containerConstruct, thereIsANewContainer, thereIsNoNewContainer)(code);
}
/** @type {State} */
function thereIsANewContainer(code) {
if (childFlow) closeFlow();
exitContainers(continued);
return documentContinued(code);
}
/** @type {State} */
function thereIsNoNewContainer(code) {
self.parser.lazy[self.now().line] = continued !== stack.length;
lineStartOffset = self.now().offset;
return flowStart(code);
}
/** @type {State} */
function documentContinued(code) {
// Try new containers.
self.containerState = {};
return effects.attempt(containerConstruct, containerContinue, flowStart)(code);
}
/** @type {State} */
function containerContinue(code) {
continued++;
stack.push([self.currentConstruct, self.containerState]);
// Try another.
return documentContinued(code);
}
/** @type {State} */
function flowStart(code) {
if (code === null) {
if (childFlow) closeFlow();
exitContainers(0);
effects.consume(code);
return;
}
childFlow = childFlow || self.parser.flow(self.now());
effects.enter("chunkFlow", {
_tokenizer: childFlow,
contentType: "flow",
previous: childToken
});
return flowContinue(code);
}
/** @type {State} */
function flowContinue(code) {
if (code === null) {
writeToChild(effects.exit("chunkFlow"), true);
exitContainers(0);
effects.consume(code);
return;
}
if (markdownLineEnding(code)) {
effects.consume(code);
writeToChild(effects.exit("chunkFlow"));
// Get ready for the next line.
continued = 0;
self.interrupt = undefined;
return start;
}
effects.consume(code);
return flowContinue;
}
/**
* @param {Token} token
* Token.
* @param {boolean | undefined} [endOfFile]
* Whether the token is at the end of the file (default: `false`).
* @returns {undefined}
* Nothing.
*/
function writeToChild(token, endOfFile) {
const stream = self.sliceStream(token);
if (endOfFile) stream.push(null);
token.previous = childToken;
if (childToken) childToken.next = token;
childToken = token;
childFlow.defineSkip(token.start);
childFlow.write(stream);
// Alright, so we just added a lazy line:
//
// ```markdown
// > a
// b.
//
// Or:
//
// > ~~~c
// d
//
// Or:
//
// > | e |
// f
// ```
//
// The construct in the second example (fenced code) does not accept lazy
// lines, so it marked itself as done at the end of its first line, and
// then the content construct parses `d`.
// Most constructs in markdown match on the first line: if the first line
// forms a construct, a non-lazy line canβt βunmakeβ it.
//
// The construct in the third example is potentially a GFM table, and
// those are *weird*.
// It *could* be a table, from the first line, if the following line
// matches a condition.
// In this case, that second line is lazy, which βunmakesβ the first line
// and turns the whole into one content block.
//
// Weβve now parsed the non-lazy and the lazy line, and can figure out
// whether the lazy line started a new flow block.
// If it did, we exit the current containers between the two flow blocks.
if (self.parser.lazy[token.start.line]) {
let index = childFlow.events.length;
while (index--) {
if (
// The token starts before the line endingβ¦
childFlow.events[index][1].start.offset < lineStartOffset && (
// β¦and either is not ended yetβ¦
!childFlow.events[index][1].end ||
// β¦or ends after it.
childFlow.events[index][1].end.offset > lineStartOffset)) {
// Exit: thereβs still something open, which means itβs a lazy line
// part of something.
return;
}
}
// Note: this algorithm for moving events around is similar to the
// algorithm when closing flow in `documentContinue`.
const indexBeforeExits = self.events.length;
let indexBeforeFlow = indexBeforeExits;
/** @type {boolean | undefined} */
let seen;
/** @type {Point | undefined} */
let point;
// Find the previous chunk (the one before the lazy line).
while (indexBeforeFlow--) {
if (self.events[indexBeforeFlow][0] === 'exit' && self.events[indexBeforeFlow][1].type === "chunkFlow") {
if (seen) {
point = self.events[indexBeforeFlow][1].end;
break;
}
seen = true;
}
}
exitContainers(continued);
// Fix positions.
index = indexBeforeExits;
while (index < self.events.length) {
self.events[index][1].end = {
...point
};
index++;
}
// Inject the exits earlier (theyβre still also at the end).
splice(self.events, indexBeforeFlow + 1, 0, self.events.slice(indexBeforeExits));
// Discard the duplicate exits.
self.events.length = index;
}
}
/**
* @param {number} size
* Size.
* @returns {undefined}
* Nothing.
*/
function exitContainers(size) {
let index = stack.length;
// Exit open containers.
while (index-- > size) {
const entry = stack[index];
self.containerState = entry[1];
entry[0].exit.call(self, effects);
}
stack.length = size;
}
function closeFlow() {
childFlow.write([null]);
childToken = undefined;
childFlow = undefined;
self.containerState._closeFlow = undefined;
}
}
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
* Tokenizer.
*/
function tokenizeContainer(effects, ok, nok) {
// Always populated by defaults.
return factorySpace(effects, effects.attempt(this.parser.constructs.document, ok, nok), "linePrefix", this.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4);
}
/**
* @import {Code} from 'micromark-util-types'
*/
/**
* Classify whether a code represents whitespace, punctuation, or something
* else.
*
* Used for attention (emphasis, strong), whose sequences can open or close
* based on the class of surrounding characters.
*
* > π **Note**: eof (`null`) is seen as whitespace.
*
* @param {Code} code
* Code.
* @returns {typeof constants.characterGroupWhitespace | typeof constants.characterGroupPunctuation | undefined}
* Group.
*/
function classifyCharacter(code) {
if (code === null || markdownLineEndingOrSpace(code) || unicodeWhitespace(code)) {
return 1;
}
if (unicodePunctuation(code)) {
return 2;
}
}
/**
* @import {Event, Resolver, TokenizeContext} from 'micromark-util-types'
*/
/**
* Call all `resolveAll`s.
*
* @param {ReadonlyArray<{resolveAll?: Resolver | undefined}>} constructs
* List of constructs, optionally with `resolveAll`s.
* @param {Array<Event>} events
* List of events.
* @param {TokenizeContext} context
* Context used by `tokenize`.
* @returns {Array<Event>}
* Changed events.
*/
function resolveAll(constructs, events, context) {
/** @type {Array<Resolver>} */
const called = [];
let index = -1;
while (++index < constructs.length) {
const resolve = constructs[index].resolveAll;
if (resolve && !called.includes(resolve)) {
events = resolve(events, context);
called.push(resolve);
}
}
return events
}
/**
* @import {
* Code,
* Construct,
* Event,
* Point,
* Resolver,
* State,
* TokenizeContext,
* Tokenizer,
* Token
* } from 'micromark-util-types'
*/
/** @type {Construct} */
const attention = {
name: 'attention',
resolveAll: resolveAllAttention,
tokenize: tokenizeAttention
};
/**
* Take all events and resolve attention to emphasis or strong.
*
* @type {Resolver}
*/
// eslint-disable-next-line complexity
function resolveAllAttention(events, context) {
let index = -1;
/** @type {number} */
let open;
/** @type {Token} */
let group;
/** @type {Token} */
let text;
/** @type {Token} */
let openingSequence;
/** @type {Token} */
let closingSequence;
/** @type {number} */
let use;
/** @type {Array<Event>} */
let nextEvents;
/** @type {number} */
let offset;
// Walk through all events.
//
// Note: performance of this is fine on an mb of normal markdown, but itβs
// a bottleneck for malicious stuff.
while (++index < events.length) {
// Find a token that can close.
if (events[index][0] === 'enter' && events[index][1].type === 'attentionSequence' && events[index][1]._close) {
open = index;
// Now walk back to find an opener.
while (open--) {
// Find a token that can open the closer.
if (events[open][0] === 'exit' && events[open][1].type === 'attentionSequence' && events[open][1]._open &&
// If the markers are the same:
context.sliceSerialize(events[open][1]).charCodeAt(0) === context.sliceSerialize(events[index][1]).charCodeAt(0)) {
// If the opening can close or the closing can open,
// and the close size *is not* a multiple of three,
// but the sum of the opening and closing size *is* multiple of three,
// then donβt match.
if ((events[open][1]._close || events[index][1]._open) && (events[index][1].end.offset - events[index][1].start.offset) % 3 && !((events[open][1].end.offset - events[open][1].start.offset + events[index][1].end.offset - events[index][1].start.offset) % 3)) {
continue;
}
// Number of markers to use from the sequence.
use = events[open][1].end.offset - events[open][1].start.offset > 1 && events[index][1].end.offset - events[index][1].start.offset > 1 ? 2 : 1;
const start = {
...events[open][1].end
};
const end = {
...events[index][1].start
};
movePoint(start, -use);
movePoint(end, use);
openingSequence = {
type: use > 1 ? "strongSequence" : "emphasisSequence",
start,
end: {
...events[open][1].end
}
};
closingSequence = {
type: use > 1 ? "strongSequence" : "emphasisSequence",
start: {
...events[index][1].start
},
end
};
text = {
type: use > 1 ? "strongText" : "emphasisText",
start: {
...events[open][1].end
},
end: {
...events[index][1].start
}
};
group = {
type: use > 1 ? "strong" : "emphasis",
start: {
...openingSequence.start
},
end: {
...closingSequence.end
}
};
events[open][1].end = {
...openingSequence.start
};
events[index][1].start = {
...closingSequence.end
};
nextEvents = [];
// If there are more markers in the opening, add them before.
if (events[open][1].end.offset - events[open][1].start.offset) {
nextEvents = push(nextEvents, [['enter', events[open][1], context], ['exit', events[open][1], context]]);
}
// Opening.
nextEvents = push(nextEvents, [['enter', group, context], ['enter', openingSequence, context], ['exit', openingSequence, context], ['enter', text, context]]);
// Always populated by defaults.
// Between.
nextEvents = push(nextEvents, resolveAll(context.parser.constructs.insideSpan.null, events.slice(open + 1, index), context));
// Closing.
nextEvents = push(nextEvents, [['exit', text, context], ['enter', closingSequence, context], ['exit', closingSequence, context], ['exit', group, context]]);
// If there are more markers in the closing, add them after.
if (events[index][1].end.offset - events[index][1].start.offset) {
offset = 2;
nextEvents = push(nextEvents, [['enter', events[index][1], context], ['exit', events[index][1], context]]);
} else {
offset = 0;
}
splice(events, open - 1, index - open + 3, nextEvents);
index = open + nextEvents.length - offset - 2;
break;
}
}
}
}
// Remove remaining sequences.
index = -1;
while (++index < events.length) {
if (events[index][1].type === 'attentionSequence') {
events[index][1].type = 'data';
}
}
return events;
}
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeAttention(effects, ok) {
const attentionMarkers = this.parser.constructs.attentionMarkers.null;
const previous = this.previous;
const before = classifyCharacter(previous);
/** @type {NonNullable<Code>} */
let marker;
return start;
/**
* Before a sequence.
*
* ```markdown
* > | **
* ^
* ```
*
* @type {State}
*/
function start(code) {
marker = code;
effects.enter('attentionSequence');
return inside(code);
}
/**
* In a sequence.
*
* ```markdown
* > | **
* ^^
* ```
*
* @type {State}
*/
function inside(code) {
if (code === marker) {
effects.consume(code);
return inside;
}
const token = effects.exit('attentionSequence');
// To do: next major: move this to resolver, just like `markdown-rs`.
const after = classifyCharacter(code);
// Always populated by defaults.
const open = !after || after === 2 && before || attentionMarkers.includes(code);
const close = !before || before === 2 && after || attentionMarkers.includes(previous);
token._open = Boolean(marker === 42 ? open : open && (before || !close));
token._close = Boolean(marker === 42 ? close : close && (after || !open));
return ok(code);
}
}
/**
* Move a point a bit.
*
* Note: `move` only works inside lines! Itβs not possible to move past other
* chunks (replacement characters, tabs, or line endings).
*
* @param {Point} point
* Point.
* @param {number} offset
* Amount to move.
* @returns {undefined}
* Nothing.
*/
function movePoint(point, offset) {
point.column += offset;
point.offset += offset;
point._bufferIndex += offset;
}
/**
* @import {
* Construct,
* State,
* TokenizeContext,
* Tokenizer
* } from 'micromark-util-types'
*/
/** @type {Construct} */
const autolink = {
name: 'autolink',
tokenize: tokenizeAutolink
};
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeAutolink(effects, ok, nok) {
let size = 0;
return start;
/**
* Start of an autolink.
*
* ```markdown
* > | a<https://example.com>b
* ^
* > | a<user@example.com>b
* ^
* ```
*
* @type {State}
*/
function start(code) {
effects.enter("autolink");
effects.enter("autolinkMarker");
effects.consume(code);
effects.exit("autolinkMarker");
effects.enter("autolinkProtocol");
return open;
}
/**
* After `<`, at protocol or atext.
*
* ```markdown
* > | a<https://example.com>b
* ^
* > | a<user@example.com>b
* ^
* ```
*
* @type {State}
*/
function open(code) {
if (asciiAlpha(code)) {
effects.consume(code);
return schemeOrEmailAtext;
}
if (code === 64) {
return nok(code);
}
return emailAtext(code);
}
/**
* At second byte of protocol or atext.
*
* ```markdown
* > | a<https://example.com>b
* ^
* > | a<user@example.com>b
* ^
* ```
*
* @type {State}
*/
function schemeOrEmailAtext(code) {
// ASCII alphanumeric and `+`, `-`, and `.`.
if (code === 43 || code === 45 || code === 46 || asciiAlphanumeric(code)) {
// Count the previous alphabetical from `open` too.
size = 1;
return schemeInsideOrEmailAtext(code);
}
return emailAtext(code);
}
/**
* In ambiguous protocol or atext.
*
* ```markdown
* > | a<https://example.com>b
* ^
* > | a<user@example.com>b
* ^
* ```
*
* @type {State}
*/
function schemeInsideOrEmailAtext(code) {
if (code === 58) {
effects.consume(code);
size = 0;
return urlInside;
}
// ASCII alphanumeric and `+`, `-`, and `.`.
if ((code === 43 || code === 45 || code === 46 || asciiAlphanumeric(code)) && size++ < 32) {
effects.consume(code);
return schemeInsideOrEmailAtext;
}
size = 0;
return emailAtext(code);
}
/**
* After protocol, in URL.
*
* ```markdown
* > | a<https://example.com>b
* ^
* ```
*
* @type {State}
*/
function urlInside(code) {
if (code === 62) {
effects.exit("autolinkProtocol");
effects.enter("autolinkMarker");
effects.consume(code);
effects.exit("autolinkMarker");
effects.exit("autolink");
return ok;
}
// ASCII control, space, or `<`.
if (code === null || code === 32 || code === 60 || asciiControl(code)) {
return nok(code);
}
effects.consume(code);
return urlInside;
}
/**
* In email atext.
*
* ```markdown
* > | a<user.name@example.com>b
* ^
* ```
*
* @type {State}
*/
function emailAtext(code) {
if (code === 64) {
effects.consume(code);
return emailAtSignOrDot;
}
if (asciiAtext(code)) {
effects.consume(code);
return emailAtext;
}
return nok(code);
}
/**
* In label, after at-sign or dot.
*
* ```markdown
* > | a<user.name@example.com>b
* ^ ^
* ```
*
* @type {State}
*/
function emailAtSignOrDot(code) {
return asciiAlphanumeric(code) ? emailLabel(code) : nok(code);
}
/**
* In label, where `.` and `>` are allowed.
*
* ```markdown
* > | a<user.name@example.com>b
* ^
* ```
*
* @type {State}
*/
function emailLabel(code) {
if (code === 46) {
effects.consume(code);
size = 0;
return emailAtSignOrDot;
}
if (code === 62) {
// Exit, then change the token type.
effects.exit("autolinkProtocol").type = "autolinkEmail";
effects.enter("autolinkMarker");
effects.consume(code);
effects.exit("autolinkMarker");
effects.exit("autolink");
return ok;
}
return emailValue(code);
}
/**
* In label, where `.` and `>` are *not* allowed.
*
* Though, this is also used in `emailLabel` to parse other values.
*
* ```markdown
* > | a<user.name@ex-ample.com>b
* ^
* ```
*
* @type {State}
*/
function emailValue(code) {
// ASCII alphanumeric or `-`.
if ((code === 45 || asciiAlphanumeric(code)) && size++ < 63) {
const next = code === 45 ? emailValue : emailLabel;
effects.consume(code);
return next;
}
return nok(code);
}
}
/**
* @import {
* Construct,
* State,
* TokenizeContext,
* Tokenizer
* } from 'micromark-util-types'
*/
/** @type {Construct} */
const blankLine = {
partial: true,
tokenize: tokenizeBlankLine
};
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeBlankLine(effects, ok, nok) {
return start;
/**
* Start of blank line.
*
* > π **Note**: `β ` represents a space character.
*
* ```markdown
* > | β β β
* ^
* > | β
* ^
* ```
*
* @type {State}
*/
function start(code) {
return markdownSpace(code) ? factorySpace(effects, after, "linePrefix")(code) : after(code);
}
/**
* At eof/eol, after optional whitespace.
*
* > π **Note**: `β ` represents a space character.
*
* ```markdown
* > | β β β
* ^
* > | β
* ^
* ```
*
* @type {State}
*/
function after(code) {
return code === null || markdownLineEnding(code) ? ok(code) : nok(code);
}
}
/**
* @import {
* Construct,
* Exiter,
* State,
* TokenizeContext,
* Tokenizer
* } from 'micromark-util-types'
*/
/** @type {Construct} */
const blockQuote = {
continuation: {
tokenize: tokenizeBlockQuoteContinuation
},
exit,
name: 'blockQuote',
tokenize: tokenizeBlockQuoteStart
};
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeBlockQuoteStart(effects, ok, nok) {
const self = this;
return start;
/**
* Start of block quote.
*
* ```markdown
* > | > a
* ^
* ```
*
* @type {State}
*/
function start(code) {
if (code === 62) {
const state = self.containerState;
if (!state.open) {
effects.enter("blockQuote", {
_container: true
});
state.open = true;
}
effects.enter("blockQuotePrefix");
effects.enter("blockQuoteMarker");
effects.consume(code);
effects.exit("blockQuoteMarker");
return after;
}
return nok(code);
}
/**
* After `>`, before optional whitespace.
*
* ```markdown
* > | > a
* ^
* ```
*
* @type {State}
*/
function after(code) {
if (markdownSpace(code)) {
effects.enter("blockQuotePrefixWhitespace");
effects.consume(code);
effects.exit("blockQuotePrefixWhitespace");
effects.exit("blockQuotePrefix");
return ok;
}
effects.exit("blockQuotePrefix");
return ok(code);
}
}
/**
* Start of block quote continuation.
*
* ```markdown
* | > a
* > | > b
* ^
* ```
*
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeBlockQuoteContinuation(effects, ok, nok) {
const self = this;
return contStart;
/**
* Start of block quote continuation.
*
* Also used to parse the first block quote opening.
*
* ```markdown
* | > a
* > | > b
* ^
* ```
*
* @type {State}
*/
function contStart(code) {
if (markdownSpace(code)) {
// Always populated by defaults.
return factorySpace(effects, contBefore, "linePrefix", self.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4)(code);
}
return contBefore(code);
}
/**
* At `>`, after optional whitespace.
*
* Also used to parse the first block quote opening.
*
* ```markdown
* | > a
* > | > b
* ^
* ```
*
* @type {State}
*/
function contBefore(code) {
return effects.attempt(blockQuote, ok, nok)(code);
}
}
/** @type {Exiter} */
function exit(effects) {
effects.exit("blockQuote");
}
/**
* @import {
* Construct,
* State,
* TokenizeContext,
* Tokenizer
* } from 'micromark-util-types'
*/
/** @type {Construct} */
const characterEscape = {
name: 'characterEscape',
tokenize: tokenizeCharacterEscape
};
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeCharacterEscape(effects, ok, nok) {
return start;
/**
* Start of character escape.
*
* ```markdown
* > | a\*b
* ^
* ```
*
* @type {State}
*/
function start(code) {
effects.enter("characterEscape");
effects.enter("escapeMarker");
effects.consume(code);
effects.exit("escapeMarker");
return inside;
}
/**
* After `\`, at punctuation.
*
* ```markdown
* > | a\*b
* ^
* ```
*
* @type {State}
*/
function inside(code) {
// ASCII punctuation.
if (asciiPunctuation(code)) {
effects.enter("characterEscapeValue");
effects.consume(code);
effects.exit("characterEscapeValue");
effects.exit("characterEscape");
return ok;
}
return nok(code);
}
}
/**
* @import {
* Code,
* Construct,
* State,
* TokenizeContext,
* Tokenizer
* } from 'micromark-util-types'
*/
/** @type {Construct} */
const characterReference = {
name: 'characterReference',
tokenize: tokenizeCharacterReference
};
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeCharacterReference(effects, ok, nok) {
const self = this;
let size = 0;
/** @type {number} */
let max;
/** @type {(code: Code) => boolean} */
let test;
return start;
/**
* Start of character reference.
*
* ```markdown
* > | a&b
* ^
* > | a{b
* ^
* > | a	b
* ^
* ```
*
* @type {State}
*/
function start(code) {
effects.enter("characterReference");
effects.enter("characterReferenceMarker");
effects.consume(code);
effects.exit("characterReferenceMarker");
return open;
}
/**
* After `&`, at `#` for numeric references or alphanumeric for named
* references.
*
* ```markdown
* > | a&b
* ^
* > | a{b
* ^
* > | a	b
* ^
* ```
*
* @type {State}
*/
function open(code) {
if (code === 35) {
effects.enter("characterReferenceMarkerNumeric");
effects.consume(code);
effects.exit("characterReferenceMarkerNumeric");
return numeric;
}
effects.enter("characterReferenceValue");
max = 31;
test = asciiAlphanumeric;
return value(code);
}
/**
* After `#`, at `x` for hexadecimals or digit for decimals.
*
* ```markdown
* > | a{b
* ^
* > | a	b
* ^
* ```
*
* @type {State}
*/
function numeric(code) {
if (code === 88 || code === 120) {
effects.enter("characterReferenceMarkerHexadecimal");
effects.consume(code);
effects.exit("characterReferenceMarkerHexadecimal");
effects.enter("characterReferenceValue");
max = 6;
test = asciiHexDigit;
return value;
}
effects.enter("characterReferenceValue");
max = 7;
test = asciiDigit;
return value(code);
}
/**
* After markers (`&#x`, `&#`, or `&`), in value, before `;`.
*
* The character reference kind defines what and how many characters are
* allowed.
*
* ```markdown
* > | a&b
* ^^^
* > | a{b
* ^^^
* > | a	b
* ^
* ```
*
* @type {State}
*/
function value(code) {
if (code === 59 && size) {
const token = effects.exit("characterReferenceValue");
if (test === asciiAlphanumeric && !decodeNamedCharacterReference(self.sliceSerialize(token))) {
return nok(code);
}
// To do: `markdown-rs` uses a different name:
// `CharacterReferenceMarkerSemi`.
effects.enter("characterReferenceMarker");
effects.consume(code);
effects.exit("characterReferenceMarker");
effects.exit("characterReference");
return ok;
}
if (test(code) && size++ < max) {
effects.consume(code);
return value;
}
return nok(code);
}
}
/**
* @import {
* Code,
* Construct,
* State,
* TokenizeContext,
* Tokenizer
* } from 'micromark-util-types'
*/
/** @type {Construct} */
const nonLazyContinuation = {
partial: true,
tokenize: tokenizeNonLazyContinuation
};
/** @type {Construct} */
const codeFenced = {
concrete: true,
name: 'codeFenced',
tokenize: tokenizeCodeFenced
};
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeCodeFenced(effects, ok, nok) {
const self = this;
/** @type {Construct} */
const closeStart = {
partial: true,
tokenize: tokenizeCloseStart
};
let initialPrefix = 0;
let sizeOpen = 0;
/** @type {NonNullable<Code>} */
let marker;
return start;
/**
* Start of code.
*
* ```markdown
* > | ~~~js
* ^
* | alert(1)
* | ~~~
* ```
*
* @type {State}
*/
function start(code) {
// To do: parse whitespace like `markdown-rs`.
return beforeSequenceOpen(code);
}
/**
* In opening fence, after prefix, at sequence.
*
* ```markdown
* > | ~~~js
* ^
* | alert(1)
* | ~~~
* ```
*
* @type {State}
*/
function beforeSequenceOpen(code) {
const tail = self.events[self.events.length - 1];
initialPrefix = tail && tail[1].type === "linePrefix" ? tail[2].sliceSerialize(tail[1], true).length : 0;
marker = code;
effects.enter("codeFenced");
effects.enter("codeFencedFence");
effects.enter("codeFencedFenceSequence");
return sequenceOpen(code);
}
/**
* In opening fence sequence.
*
* ```markdown
* > | ~~~js
* ^
* | alert(1)
* | ~~~
* ```
*
* @type {State}
*/
function sequenceOpen(code) {
if (code === marker) {
sizeOpen++;
effects.consume(code);
return sequenceOpen;
}
if (sizeOpen < 3) {
return nok(code);
}
effects.exit("codeFencedFenceSequence");
return markdownSpace(code) ? factorySpace(effects, infoBefore, "whitespace")(code) : infoBefore(code);
}
/**
* In opening fence, after the sequence (and optional whitespace), before info.
*
* ```markdown
* > | ~~~js
* ^
* | alert(1)
* | ~~~
* ```
*
* @type {State}
*/
function infoBefore(code) {
if (code === null || markdownLineEnding(code)) {
effects.exit("codeFencedFence");
return self.interrupt ? ok(code) : effects.check(nonLazyContinuation, atNonLazyBreak, after)(code);
}
effects.enter("codeFencedFenceInfo");
effects.enter("chunkString", {
contentType: "string"
});
return info(code);
}
/**
* In info.
*
* ```markdown
* > | ~~~js
* ^
* | alert(1)
* | ~~~
* ```
*
* @type {State}
*/
function info(code) {
if (code === null || markdownLineEnding(code)) {
effects.exit("chunkString");
effects.exit("codeFencedFenceInfo");
return infoBefore(code);
}
if (markdownSpace(code)) {
effects.exit("chunkString");
effects.exit("codeFencedFenceInfo");
return factorySpace(effects, metaBefore, "whitespace")(code);
}
if (code === 96 && code === marker) {
return nok(code);
}
effects.consume(code);
return info;
}
/**
* In opening fence, after info and whitespace, before meta.
*
* ```markdown
* > | ~~~js eval
* ^
* | alert(1)
* | ~~~
* ```
*
* @type {State}
*/
function metaBefore(code) {
if (code === null || markdownLineEnding(code)) {
return infoBefore(code);
}
effects.enter("codeFencedFenceMeta");
effects.enter("chunkString", {
contentType: "string"
});
return meta(code);
}
/**
* In meta.
*
* ```markdown
* > | ~~~js eval
* ^
* | alert(1)
* | ~~~
* ```
*
* @type {State}
*/
function meta(code) {
if (code === null || markdownLineEnding(code)) {
effects.exit("chunkString");
effects.exit("codeFencedFenceMeta");
return infoBefore(code);
}
if (code === 96 && code === marker) {
return nok(code);
}
effects.consume(code);
return meta;
}
/**
* At eol/eof in code, before a non-lazy closing fence or content.
*
* ```markdown
* > | ~~~js
* ^
* > | alert(1)
* ^
* | ~~~
* ```
*
* @type {State}
*/
function atNonLazyBreak(code) {
return effects.attempt(closeStart, after, contentBefore)(code);
}
/**
* Before code content, not a closing fence, at eol.
*
* ```markdown
* | ~~~js
* > | alert(1)
* ^
* | ~~~
* ```
*
* @type {State}
*/
function contentBefore(code) {
effects.enter("lineEnding");
effects.consume(code);
effects.exit("lineEnding");
return contentStart;
}
/**
* Before code content, not a closing fence.
*
* ```markdown
* | ~~~js
* > | alert(1)
* ^
* | ~~~
* ```
*
* @type {State}
*/
function contentStart(code) {
return initialPrefix > 0 && markdownSpace(code) ? factorySpace(effects, beforeContentChunk, "linePrefix", initialPrefix + 1)(code) : beforeContentChunk(code);
}
/**
* Before code content, after optional prefix.
*
* ```markdown
* | ~~~js
* > | alert(1)
* ^
* | ~~~
* ```
*
* @type {State}
*/
function beforeContentChunk(code) {
if (code === null || markdownLineEnding(code)) {
return effects.check(nonLazyContinuation, atNonLazyBreak, after)(code);
}
effects.enter("codeFlowValue");
return contentChunk(code);
}
/**
* In code content.
*
* ```markdown
* | ~~~js
* > | alert(1)
* ^^^^^^^^
* | ~~~
* ```
*
* @type {State}
*/
function contentChunk(code) {
if (code === null || markdownLineEnding(code)) {
effects.exit("codeFlowValue");
return beforeContentChunk(code);
}
effects.consume(code);
return contentChunk;
}
/**
* After code.
*
* ```markdown
* | ~~~js
* | alert(1)
* > | ~~~
* ^
* ```
*
* @type {State}
*/
function after(code) {
effects.exit("codeFenced");
return ok(code);
}
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeCloseStart(effects, ok, nok) {
let size = 0;
return startBefore;
/**
*
*
* @type {State}
*/
function startBefore(code) {
effects.enter("lineEnding");
effects.consume(code);
effects.exit("lineEnding");
return start;
}
/**
* Before closing fence, at optional whitespace.
*
* ```markdown
* | ~~~js
* | alert(1)
* > | ~~~
* ^
* ```
*
* @type {State}
*/
function start(code) {
// Always populated by defaults.
// To do: `enter` here or in next state?
effects.enter("codeFencedFence");
return markdownSpace(code) ? factorySpace(effects, beforeSequenceClose, "linePrefix", self.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4)(code) : beforeSequenceClose(code);
}
/**
* In closing fence, after optional whitespace, at sequence.
*
* ```markdown
* | ~~~js
* | alert(1)
* > | ~~~
* ^
* ```
*
* @type {State}
*/
function beforeSequenceClose(code) {
if (code === marker) {
effects.enter("codeFencedFenceSequence");
return sequenceClose(code);
}
return nok(code);
}
/**
* In closing fence sequence.
*
* ```markdown
* | ~~~js
* | alert(1)
* > | ~~~
* ^
* ```
*
* @type {State}
*/
function sequenceClose(code) {
if (code === marker) {
size++;
effects.consume(code);
return sequenceClose;
}
if (size >= sizeOpen) {
effects.exit("codeFencedFenceSequence");
return markdownSpace(code) ? factorySpace(effects, sequenceCloseAfter, "whitespace")(code) : sequenceCloseAfter(code);
}
return nok(code);
}
/**
* After closing fence sequence, after optional whitespace.
*
* ```markdown
* | ~~~js
* | alert(1)
* > | ~~~
* ^
* ```
*
* @type {State}
*/
function sequenceCloseAfter(code) {
if (code === null || markdownLineEnding(code)) {
effects.exit("codeFencedFence");
return ok(code);
}
return nok(code);
}
}
}
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeNonLazyContinuation(effects, ok, nok) {
const self = this;
return start;
/**
*
*
* @type {State}
*/
function start(code) {
if (code === null) {
return nok(code);
}
effects.enter("lineEnding");
effects.consume(code);
effects.exit("lineEnding");
return lineStart;
}
/**
*
*
* @type {State}
*/
function lineStart(code) {
return self.parser.lazy[self.now().line] ? nok(code) : ok(code);
}
}
/**
* @import {
* Construct,
* State,
* TokenizeContext,
* Tokenizer
* } from 'micromark-util-types'
*/
/** @type {Construct} */
const codeIndented = {
name: 'codeIndented',
tokenize: tokenizeCodeIndented
};
/** @type {Construct} */
const furtherStart = {
partial: true,
tokenize: tokenizeFurtherStart
};
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeCodeIndented(effects, ok, nok) {
const self = this;
return start;
/**
* Start of code (indented).
*
* > **Parsing note**: it is not needed to check if this first line is a
* > filled line (that it has a non-whitespace character), because blank lines
* > are parsed already, so we never run into that.
*
* ```markdown
* > | aaa
* ^
* ```
*
* @type {State}
*/
function start(code) {
// To do: manually check if interrupting like `markdown-rs`.
effects.enter("codeIndented");
// To do: use an improved `space_or_tab` function like `markdown-rs`,
// so that we can drop the next state.
return factorySpace(effects, afterPrefix, "linePrefix", 4 + 1)(code);
}
/**
* At start, after 1 or 4 spaces.
*
* ```markdown
* > | aaa
* ^
* ```
*
* @type {State}
*/
function afterPrefix(code) {
const tail = self.events[self.events.length - 1];
return tail && tail[1].type === "linePrefix" && tail[2].sliceSerialize(tail[1], true).length >= 4 ? atBreak(code) : nok(code);
}
/**
* At a break.
*
* ```markdown
* > | aaa
* ^ ^
* ```
*
* @type {State}
*/
function atBreak(code) {
if (code === null) {
return after(code);
}
if (markdownLineEnding(code)) {
return effects.attempt(furtherStart, atBreak, after)(code);
}
effects.enter("codeFlowValue");
return inside(code);
}
/**
* In code content.
*
* ```markdown
* > | aaa
* ^^^^
* ```
*
* @type {State}
*/
function inside(code) {
if (code === null || markdownLineEnding(code)) {
effects.exit("codeFlowValue");
return atBreak(code);
}
effects.consume(code);
return inside;
}
/** @type {State} */
function after(code) {
effects.exit("codeIndented");
// To do: allow interrupting like `markdown-rs`.
// Feel free to interrupt.
// tokenizer.interrupt = false
return ok(code);
}
}
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeFurtherStart(effects, ok, nok) {
const self = this;
return furtherStart;
/**
* At eol, trying to parse another indent.
*
* ```markdown
* > | aaa
* ^
* | bbb
* ```
*
* @type {State}
*/
function furtherStart(code) {
// To do: improve `lazy` / `pierce` handling.
// If this is a lazy line, it canβt be code.
if (self.parser.lazy[self.now().line]) {
return nok(code);
}
if (markdownLineEnding(code)) {
effects.enter("lineEnding");
effects.consume(code);
effects.exit("lineEnding");
return furtherStart;
}
// To do: the code here in `micromark-js` is a bit different from
// `markdown-rs` because there it can attempt spaces.
// We canβt yet.
//
// To do: use an improved `space_or_tab` function like `markdown-rs`,
// so that we can drop the next state.
return factorySpace(effects, afterPrefix, "linePrefix", 4 + 1)(code);
}
/**
* At start, after 1 or 4 spaces.
*
* ```markdown
* > | aaa
* ^
* ```
*
* @type {State}
*/
function afterPrefix(code) {
const tail = self.events[self.events.length - 1];
return tail && tail[1].type === "linePrefix" && tail[2].sliceSerialize(tail[1], true).length >= 4 ? ok(code) : markdownLineEnding(code) ? furtherStart(code) : nok(code);
}
}
/**
* @import {
* Construct,
* Previous,
* Resolver,
* State,
* TokenizeContext,
* Tokenizer,
* Token
* } from 'micromark-util-types'
*/
/** @type {Construct} */
const codeText = {
name: 'codeText',
previous,
resolve: resolveCodeText,
tokenize: tokenizeCodeText
};
// To do: next major: donβt resolve, like `markdown-rs`.
/** @type {Resolver} */
function resolveCodeText(events) {
let tailExitIndex = events.length - 4;
let headEnterIndex = 3;
/** @type {number} */
let index;
/** @type {number | undefined} */
let enter;
// If we start and end with an EOL or a space.
if ((events[headEnterIndex][1].type === "lineEnding" || events[headEnterIndex][1].type === 'space') && (events[tailExitIndex][1].type === "lineEnding" || events[tailExitIndex][1].type === 'space')) {
index = headEnterIndex;
// And we have data.
while (++index < tailExitIndex) {
if (events[index][1].type === "codeTextData") {
// Then we have padding.
events[headEnterIndex][1].type = "codeTextPadding";
events[tailExitIndex][1].type = "codeTextPadding";
headEnterIndex += 2;
tailExitIndex -= 2;
break;
}
}
}
// Merge adjacent spaces and data.
index = headEnterIndex - 1;
tailExitIndex++;
while (++index <= tailExitIndex) {
if (enter === undefined) {
if (index !== tailExitIndex && events[index][1].type !== "lineEnding") {
enter = index;
}
} else if (index === tailExitIndex || events[index][1].type === "lineEnding") {
events[enter][1].type = "codeTextData";
if (index !== enter + 2) {
events[enter][1].end = events[index - 1][1].end;
events.splice(enter + 2, index - enter - 2);
tailExitIndex -= index - enter - 2;
index = enter + 2;
}
enter = undefined;
}
}
return events;
}
/**
* @this {TokenizeContext}
* Context.
* @type {Previous}
*/
function previous(code) {
// If there is a previous code, there will always be a tail.
return code !== 96 || this.events[this.events.length - 1][1].type === "characterEscape";
}
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeCodeText(effects, ok, nok) {
let sizeOpen = 0;
/** @type {number} */
let size;
/** @type {Token} */
let token;
return start;
/**
* Start of code (text).
*
* ```markdown
* > | `a`
* ^
* > | \`a`
* ^
* ```
*
* @type {State}
*/
function start(code) {
effects.enter("codeText");
effects.enter("codeTextSequence");
return sequenceOpen(code);
}
/**
* In opening sequence.
*
* ```markdown
* > | `a`
* ^
* ```
*
* @type {State}
*/
function sequenceOpen(code) {
if (code === 96) {
effects.consume(code);
sizeOpen++;
return sequenceOpen;
}
effects.exit("codeTextSequence");
return between(code);
}
/**
* Between something and something else.
*
* ```markdown
* > | `a`
* ^^
* ```
*
* @type {State}
*/
function between(code) {
// EOF.
if (code === null) {
return nok(code);
}
// To do: next major: donβt do spaces in resolve, but when compiling,
// like `markdown-rs`.
// Tabs donβt work, and virtual spaces donβt make sense.
if (code === 32) {
effects.enter('space');
effects.consume(code);
effects.exit('space');
return between;
}
// Closing fence? Could also be data.
if (code === 96) {
token = effects.enter("codeTextSequence");
size = 0;
return sequenceClose(code);
}
if (markdownLineEnding(code)) {
effects.enter("lineEnding");
effects.consume(code);
effects.exit("lineEnding");
return between;
}
// Data.
effects.enter("codeTextData");
return data(code);
}
/**
* In data.
*
* ```markdown
* > | `a`
* ^
* ```
*
* @type {State}
*/
function data(code) {
if (code === null || code === 32 || code === 96 || markdownLineEnding(code)) {
effects.exit("codeTextData");
return between(code);
}
effects.consume(code);
return data;
}
/**
* In closing sequence.
*
* ```markdown
* > | `a`
* ^
* ```
*
* @type {State}
*/
function sequenceClose(code) {
// More.
if (code === 96) {
effects.consume(code);
size++;
return sequenceClose;
}
// Done!
if (size === sizeOpen) {
effects.exit("codeTextSequence");
effects.exit("codeText");
return ok(code);
}
// More or less accents: mark as data.
token.type = "codeTextData";
return data(code);
}
}
/**
* Some of the internal operations of micromark do lots of editing
* operations on very large arrays. This runs into problems with two
* properties of most circa-2020 JavaScript interpreters:
*
* - Array-length modifications at the high end of an array (push/pop) are
* expected to be common and are implemented in (amortized) time
* proportional to the number of elements added or removed, whereas
* other operations (shift/unshift and splice) are much less efficient.
* - Function arguments are passed on the stack, so adding tens of thousands
* of elements to an array with `arr.push(...newElements)` will frequently
* cause stack overflows. (see <https://stackoverflow.com/questions/22123769/rangeerror-maximum-call-stack-size-exceeded-why>)
*
* SpliceBuffers are an implementation of gap buffers, which are a
* generalization of the "queue made of two stacks" idea. The splice buffer
* maintains a cursor, and moving the cursor has cost proportional to the
* distance the cursor moves, but inserting, deleting, or splicing in
* new information at the cursor is as efficient as the push/pop operation.
* This allows for an efficient sequence of splices (or pushes, pops, shifts,
* or unshifts) as long such edits happen at the same part of the array or
* generally sweep through the array from the beginning to the end.
*
* The interface for splice buffers also supports large numbers of inputs by
* passing a single array argument rather passing multiple arguments on the
* function call stack.
*
* @template T
* Item type.
*/
class SpliceBuffer {
/**
* @param {ReadonlyArray<T> | null | undefined} [initial]
* Initial items (optional).
* @returns
* Splice buffer.
*/
constructor(initial) {
/** @type {Array<T>} */
this.left = initial ? [...initial] : [];
/** @type {Array<T>} */
this.right = [];
}
/**
* Array access;
* does not move the cursor.
*
* @param {number} index
* Index.
* @return {T}
* Item.
*/
get(index) {
if (index < 0 || index >= this.left.length + this.right.length) {
throw new RangeError('Cannot access index `' + index + '` in a splice buffer of size `' + (this.left.length + this.right.length) + '`');
}
if (index < this.left.length) return this.left[index];
return this.right[this.right.length - index + this.left.length - 1];
}
/**
* The length of the splice buffer, one greater than the largest index in the
* array.
*/
get length() {
return this.left.length + this.right.length;
}
/**
* Remove and return `list[0]`;
* moves the cursor to `0`.
*
* @returns {T | undefined}
* Item, optional.
*/
shift() {
this.setCursor(0);
return this.right.pop();
}
/**
* Slice the buffer to get an array;
* does not move the cursor.
*
* @param {number} start
* Start.
* @param {number | null | undefined} [end]
* End (optional).
* @returns {Array<T>}
* Array of items.
*/
slice(start, end) {
/** @type {number} */
const stop = end === null || end === undefined ? Number.POSITIVE_INFINITY : end;
if (stop < this.left.length) {
return this.left.slice(start, stop);
}
if (start > this.left.length) {
return this.right.slice(this.right.length - stop + this.left.length, this.right.length - start + this.left.length).reverse();
}
return this.left.slice(start).concat(this.right.slice(this.right.length - stop + this.left.length).reverse());
}
/**
* Mimics the behavior of Array.prototype.splice() except for the change of
* interface necessary to avoid segfaults when patching in very large arrays.
*
* This operation moves cursor is moved to `start` and results in the cursor
* placed after any inserted items.
*
* @param {number} start
* Start;
* zero-based index at which to start changing the array;
* negative numbers count backwards from the end of the array and values
* that are out-of bounds are clamped to the appropriate end of the array.
* @param {number | null | undefined} [deleteCount=0]
* Delete count (default: `0`);
* maximum number of elements to delete, starting from start.
* @param {Array<T> | null | undefined} [items=[]]
* Items to include in place of the deleted items (default: `[]`).
* @return {Array<T>}
* Any removed items.
*/
splice(start, deleteCount, items) {
/** @type {number} */
const count = deleteCount || 0;
this.setCursor(Math.trunc(start));
const removed = this.right.splice(this.right.length - count, Number.POSITIVE_INFINITY);
if (items) chunkedPush(this.left, items);
return removed.reverse();
}
/**
* Remove and return the highest-numbered item in the array, so
* `list[list.length - 1]`;
* Moves the cursor to `length`.
*
* @returns {T | undefined}
* Item, optional.
*/
pop() {
this.setCursor(Number.POSITIVE_INFINITY);
return this.left.pop();
}
/**
* Inserts a single item to the high-numbered side of the array;
* moves the cursor to `length`.
*
* @param {T} item
* Item.
* @returns {undefined}
* Nothing.
*/
push(item) {
this.setCursor(Number.POSITIVE_INFINITY);
this.left.push(item);
}
/**
* Inserts many items to the high-numbered side of the array.
* Moves the cursor to `length`.
*
* @param {Array<T>} items
* Items.
* @returns {undefined}
* Nothing.
*/
pushMany(items) {
this.setCursor(Number.POSITIVE_INFINITY);
chunkedPush(this.left, items);
}
/**
* Inserts a single item to the low-numbered side of the array;
* Moves the cursor to `0`.
*
* @param {T} item
* Item.
* @returns {undefined}
* Nothing.
*/
unshift(item) {
this.setCursor(0);
this.right.push(item);
}
/**
* Inserts many items to the low-numbered side of the array;
* moves the cursor to `0`.
*
* @param {Array<T>} items
* Items.
* @returns {undefined}
* Nothing.
*/
unshiftMany(items) {
this.setCursor(0);
chunkedPush(this.right, items.reverse());
}
/**
* Move the cursor to a specific position in the array. Requires
* time proportional to the distance moved.
*
* If `n < 0`, the cursor will end up at the beginning.
* If `n > length`, the cursor will end up at the end.
*
* @param {number} n
* Position.
* @return {undefined}
* Nothing.
*/
setCursor(n) {
if (n === this.left.length || n > this.left.length && this.right.length === 0 || n < 0 && this.left.length === 0) return;
if (n < this.left.length) {
// Move cursor to the this.left
const removed = this.left.splice(n, Number.POSITIVE_INFINITY);
chunkedPush(this.right, removed.reverse());
} else {
// Move cursor to the this.right
const removed = this.right.splice(this.left.length + this.right.length - n, Number.POSITIVE_INFINITY);
chunkedPush(this.left, removed.reverse());
}
}
}
/**
* Avoid stack overflow by pushing items onto the stack in segments
*
* @template T
* Item type.
* @param {Array<T>} list
* List to inject into.
* @param {ReadonlyArray<T>} right
* Items to inject.
* @return {undefined}
* Nothing.
*/
function chunkedPush(list, right) {
/** @type {number} */
let chunkStart = 0;
if (right.length < 10000) {
list.push(...right);
} else {
while (chunkStart < right.length) {
list.push(...right.slice(chunkStart, chunkStart + 10000));
chunkStart += 10000;
}
}
}
/**
* @import {Chunk, Event, Token} from 'micromark-util-types'
*/
/**
* Tokenize subcontent.
*
* @param {Array<Event>} eventsArray
* List of events.
* @returns {boolean}
* Whether subtokens were found.
*/
// eslint-disable-next-line complexity
function subtokenize(eventsArray) {
/** @type {Record<string, number>} */
const jumps = {};
let index = -1;
/** @type {Event} */
let event;
/** @type {number | undefined} */
let lineIndex;
/** @type {number} */
let otherIndex;
/** @type {Event} */
let otherEvent;
/** @type {Array<Event>} */
let parameters;
/** @type {Array<Event>} */
let subevents;
/** @type {boolean | undefined} */
let more;
const events = new SpliceBuffer(eventsArray);
while (++index < events.length) {
while (index in jumps) {
index = jumps[index];
}
event = events.get(index);
// Add a hook for the GFM tasklist extension, which needs to know if text
// is in the first content of a list item.
if (index && event[1].type === "chunkFlow" && events.get(index - 1)[1].type === "listItemPrefix") {
subevents = event[1]._tokenizer.events;
otherIndex = 0;
if (otherIndex < subevents.length && subevents[otherIndex][1].type === "lineEndingBlank") {
otherIndex += 2;
}
if (otherIndex < subevents.length && subevents[otherIndex][1].type === "content") {
while (++otherIndex < subevents.length) {
if (subevents[otherIndex][1].type === "content") {
break;
}
if (subevents[otherIndex][1].type === "chunkText") {
subevents[otherIndex][1]._isInFirstContentOfListItem = true;
otherIndex++;
}
}
}
}
// Enter.
if (event[0] === 'enter') {
if (event[1].contentType) {
Object.assign(jumps, subcontent(events, index));
index = jumps[index];
more = true;
}
}
// Exit.
else if (event[1]._container) {
otherIndex = index;
lineIndex = undefined;
while (otherIndex--) {
otherEvent = events.get(otherIndex);
if (otherEvent[1].type === "lineEnding" || otherEvent[1].type === "lineEndingBlank") {
if (otherEvent[0] === 'enter') {
if (lineIndex) {
events.get(lineIndex)[1].type = "lineEndingBlank";
}
otherEvent[1].type = "lineEnding";
lineIndex = otherIndex;
}
} else if (otherEvent[1].type === "linePrefix" || otherEvent[1].type === "listItemIndent") ; else {
break;
}
}
if (lineIndex) {
// Fix position.
event[1].end = {
...events.get(lineIndex)[1].start
};
// Switch container exit w/ line endings.
parameters = events.slice(lineIndex, index);
parameters.unshift(event);
events.splice(lineIndex, index - lineIndex + 1, parameters);
}
}
}
// The changes to the `events` buffer must be copied back into the eventsArray
splice(eventsArray, 0, Number.POSITIVE_INFINITY, events.slice(0));
return !more;
}
/**
* Tokenize embedded tokens.
*
* @param {SpliceBuffer<Event>} events
* Events.
* @param {number} eventIndex
* Index.
* @returns {Record<string, number>}
* Gaps.
*/
function subcontent(events, eventIndex) {
const token = events.get(eventIndex)[1];
const context = events.get(eventIndex)[2];
let startPosition = eventIndex - 1;
/** @type {Array<number>} */
const startPositions = [];
let tokenizer = token._tokenizer;
if (!tokenizer) {
tokenizer = context.parser[token.contentType](token.start);
if (token._contentTypeTextTrailing) {
tokenizer._contentTypeTextTrailing = true;
}
}
const childEvents = tokenizer.events;
/** @type {Array<[number, number]>} */
const jumps = [];
/** @type {Record<string, number>} */
const gaps = {};
/** @type {Array<Chunk>} */
let stream;
/** @type {Token | undefined} */
let previous;
let index = -1;
/** @type {Token | undefined} */
let current = token;
let adjust = 0;
let start = 0;
const breaks = [start];
// Loop forward through the linked tokens to pass them in order to the
// subtokenizer.
while (current) {
// Find the position of the event for this token.
while (events.get(++startPosition)[1] !== current) {
// Empty.
}
startPositions.push(startPosition);
if (!current._tokenizer) {
stream = context.sliceStream(current);
if (!current.next) {
stream.push(null);
}
if (previous) {
tokenizer.defineSkip(current.start);
}
if (current._isInFirstContentOfListItem) {
tokenizer._gfmTasklistFirstContentOfListItem = true;
}
tokenizer.write(stream);
if (current._isInFirstContentOfListItem) {
tokenizer._gfmTasklistFirstContentOfListItem = undefined;
}
}
// Unravel the next token.
previous = current;
current = current.next;
}
// Now, loop back through all events (and linked tokens), to figure out which
// parts belong where.
current = token;
while (++index < childEvents.length) {
if (
// Find a void token that includes a break.
childEvents[index][0] === 'exit' && childEvents[index - 1][0] === 'enter' && childEvents[index][1].type === childEvents[index - 1][1].type && childEvents[index][1].start.line !== childEvents[index][1].end.line) {
start = index + 1;
breaks.push(start);
// Help GC.
current._tokenizer = undefined;
current.previous = undefined;
current = current.next;
}
}
// Help GC.
tokenizer.events = [];
// If thereβs one more token (which is the cases for lines that end in an
// EOF), thatβs perfect: the last point we found starts it.
// If there isnβt then make sure any remaining content is added to it.
if (current) {
// Help GC.
current._tokenizer = undefined;
current.previous = undefined;
} else {
breaks.pop();
}
// Now splice the events from the subtokenizer into the current events,
// moving back to front so that splice indices arenβt affected.
index = breaks.length;
while (index--) {
const slice = childEvents.slice(breaks[index], breaks[index + 1]);
const start = startPositions.pop();
jumps.push([start, start + slice.length - 1]);
events.splice(start, 2, slice);
}
jumps.reverse();
index = -1;
while (++index < jumps.length) {
gaps[adjust + jumps[index][0]] = adjust + jumps[index][1];
adjust += jumps[index][1] - jumps[index][0] - 1;
}
return gaps;
}
/**
* @import {
* Construct,
* Resolver,
* State,
* TokenizeContext,
* Tokenizer,
* Token
* } from 'micromark-util-types'
*/
/**
* No name because it must not be turned off.
* @type {Construct}
*/
const content = {
resolve: resolveContent,
tokenize: tokenizeContent
};
/** @type {Construct} */
const continuationConstruct = {
partial: true,
tokenize: tokenizeContinuation
};
/**
* Content is transparent: itβs parsed right now. That way, definitions are also
* parsed right now: before text in paragraphs (specifically, media) are parsed.
*
* @type {Resolver}
*/
function resolveContent(events) {
subtokenize(events);
return events;
}
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeContent(effects, ok) {
/** @type {Token | undefined} */
let previous;
return chunkStart;
/**
* Before a content chunk.
*
* ```markdown
* > | abc
* ^
* ```
*
* @type {State}
*/
function chunkStart(code) {
effects.enter("content");
previous = effects.enter("chunkContent", {
contentType: "content"
});
return chunkInside(code);
}
/**
* In a content chunk.
*
* ```markdown
* > | abc
* ^^^
* ```
*
* @type {State}
*/
function chunkInside(code) {
if (code === null) {
return contentEnd(code);
}
// To do: in `markdown-rs`, each line is parsed on its own, and everything
// is stitched together resolving.
if (markdownLineEnding(code)) {
return effects.check(continuationConstruct, contentContinue, contentEnd)(code);
}
// Data.
effects.consume(code);
return chunkInside;
}
/**
*
*
* @type {State}
*/
function contentEnd(code) {
effects.exit("chunkContent");
effects.exit("content");
return ok(code);
}
/**
*
*
* @type {State}
*/
function contentContinue(code) {
effects.consume(code);
effects.exit("chunkContent");
previous.next = effects.enter("chunkContent", {
contentType: "content",
previous
});
previous = previous.next;
return chunkInside;
}
}
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeContinuation(effects, ok, nok) {
const self = this;
return startLookahead;
/**
*
*
* @type {State}
*/
function startLookahead(code) {
effects.exit("chunkContent");
effects.enter("lineEnding");
effects.consume(code);
effects.exit("lineEnding");
return factorySpace(effects, prefixed, "linePrefix");
}
/**
*
*
* @type {State}
*/
function prefixed(code) {
if (code === null || markdownLineEnding(code)) {
return nok(code);
}
// Always populated by defaults.
const tail = self.events[self.events.length - 1];
if (!self.parser.constructs.disable.null.includes('codeIndented') && tail && tail[1].type === "linePrefix" && tail[2].sliceSerialize(tail[1], true).length >= 4) {
return ok(code);
}
return effects.interrupt(self.parser.constructs.flow, nok, ok)(code);
}
}
/**
* @import {Effects, State, TokenType} from 'micromark-util-types'
*/
/**
* Parse destinations.
*
* ###### Examples
*
* ```markdown
* <a>
* <a\>b>
* <a b>
* <a)>
* a
* a\)b
* a(b)c
* a(b)
* ```
*
* @param {Effects} effects
* Context.
* @param {State} ok
* State switched to when successful.
* @param {State} nok
* State switched to when unsuccessful.
* @param {TokenType} type
* Type for whole (`<a>` or `b`).
* @param {TokenType} literalType
* Type when enclosed (`<a>`).
* @param {TokenType} literalMarkerType
* Type for enclosing (`<` and `>`).
* @param {TokenType} rawType
* Type when not enclosed (`b`).
* @param {TokenType} stringType
* Type for the value (`a` or `b`).
* @param {number | undefined} [max=Infinity]
* Depth of nested parens (inclusive).
* @returns {State}
* Start state.
*/
function factoryDestination(effects, ok, nok, type, literalType, literalMarkerType, rawType, stringType, max) {
const limit = max || Number.POSITIVE_INFINITY;
let balance = 0;
return start;
/**
* Start of destination.
*
* ```markdown
* > | <aa>
* ^
* > | aa
* ^
* ```
*
* @type {State}
*/
function start(code) {
if (code === 60) {
effects.enter(type);
effects.enter(literalType);
effects.enter(literalMarkerType);
effects.consume(code);
effects.exit(literalMarkerType);
return enclosedBefore;
}
// ASCII control, space, closing paren.
if (code === null || code === 32 || code === 41 || asciiControl(code)) {
return nok(code);
}
effects.enter(type);
effects.enter(rawType);
effects.enter(stringType);
effects.enter("chunkString", {
contentType: "string"
});
return raw(code);
}
/**
* After `<`, at an enclosed destination.
*
* ```markdown
* > | <aa>
* ^
* ```
*
* @type {State}
*/
function enclosedBefore(code) {
if (code === 62) {
effects.enter(literalMarkerType);
effects.consume(code);
effects.exit(literalMarkerType);
effects.exit(literalType);
effects.exit(type);
return ok;
}
effects.enter(stringType);
effects.enter("chunkString", {
contentType: "string"
});
return enclosed(code);
}
/**
* In enclosed destination.
*
* ```markdown
* > | <aa>
* ^
* ```
*
* @type {State}
*/
function enclosed(code) {
if (code === 62) {
effects.exit("chunkString");
effects.exit(stringType);
return enclosedBefore(code);
}
if (code === null || code === 60 || markdownLineEnding(code)) {
return nok(code);
}
effects.consume(code);
return code === 92 ? enclosedEscape : enclosed;
}
/**
* After `\`, at a special character.
*
* ```markdown
* > | <a\*a>
* ^
* ```
*
* @type {State}
*/
function enclosedEscape(code) {
if (code === 60 || code === 62 || code === 92) {
effects.consume(code);
return enclosed;
}
return enclosed(code);
}
/**
* In raw destination.
*
* ```markdown
* > | aa
* ^
* ```
*
* @type {State}
*/
function raw(code) {
if (!balance && (code === null || code === 41 || markdownLineEndingOrSpace(code))) {
effects.exit("chunkString");
effects.exit(stringType);
effects.exit(rawType);
effects.exit(type);
return ok(code);
}
if (balance < limit && code === 40) {
effects.consume(code);
balance++;
return raw;
}
if (code === 41) {
effects.consume(code);
balance--;
return raw;
}
// ASCII control (but *not* `\0`) and space and `(`.
// Note: in `markdown-rs`, `\0` exists in codes, in `micromark-js` it
// doesnβt.
if (code === null || code === 32 || code === 40 || asciiControl(code)) {
return nok(code);
}
effects.consume(code);
return code === 92 ? rawEscape : raw;
}
/**
* After `\`, at special character.
*
* ```markdown
* > | a\*a
* ^
* ```
*
* @type {State}
*/
function rawEscape(code) {
if (code === 40 || code === 41 || code === 92) {
effects.consume(code);
return raw;
}
return raw(code);
}
}
/**
* @import {
* Effects,
* State,
* TokenizeContext,
* TokenType
* } from 'micromark-util-types'
*/
/**
* Parse labels.
*
* > π **Note**: labels in markdown are capped at 999 characters in the string.
*
* ###### Examples
*
* ```markdown
* [a]
* [a
* b]
* [a\]b]
* ```
*
* @this {TokenizeContext}
* Tokenize context.
* @param {Effects} effects
* Context.
* @param {State} ok
* State switched to when successful.
* @param {State} nok
* State switched to when unsuccessful.
* @param {TokenType} type
* Type of the whole label (`[a]`).
* @param {TokenType} markerType
* Type for the markers (`[` and `]`).
* @param {TokenType} stringType
* Type for the identifier (`a`).
* @returns {State}
* Start state.
*/
function factoryLabel(effects, ok, nok, type, markerType, stringType) {
const self = this;
let size = 0;
/** @type {boolean} */
let seen;
return start;
/**
* Start of label.
*
* ```markdown
* > | [a]
* ^
* ```
*
* @type {State}
*/
function start(code) {
effects.enter(type);
effects.enter(markerType);
effects.consume(code);
effects.exit(markerType);
effects.enter(stringType);
return atBreak;
}
/**
* In label, at something, before something else.
*
* ```markdown
* > | [a]
* ^
* ```
*
* @type {State}
*/
function atBreak(code) {
if (size > 999 || code === null || code === 91 || code === 93 && !seen ||
// To do: remove in the future once weβve switched from
// `micromark-extension-footnote` to `micromark-extension-gfm-footnote`,
// which doesnβt need this.
// Hidden footnotes hook.
/* c8 ignore next 3 */
code === 94 && !size && '_hiddenFootnoteSupport' in self.parser.constructs) {
return nok(code);
}
if (code === 93) {
effects.exit(stringType);
effects.enter(markerType);
effects.consume(code);
effects.exit(markerType);
effects.exit(type);
return ok;
}
// To do: indent? Link chunks and EOLs together?
if (markdownLineEnding(code)) {
effects.enter("lineEnding");
effects.consume(code);
effects.exit("lineEnding");
return atBreak;
}
effects.enter("chunkString", {
contentType: "string"
});
return labelInside(code);
}
/**
* In label, in text.
*
* ```markdown
* > | [a]
* ^
* ```
*
* @type {State}
*/
function labelInside(code) {
if (code === null || code === 91 || code === 93 || markdownLineEnding(code) || size++ > 999) {
effects.exit("chunkString");
return atBreak(code);
}
effects.consume(code);
if (!seen) seen = !markdownSpace(code);
return code === 92 ? labelEscape : labelInside;
}
/**
* After `\`, at a special character.
*
* ```markdown
* > | [a\*a]
* ^
* ```
*
* @type {State}
*/
function labelEscape(code) {
if (code === 91 || code === 92 || code === 93) {
effects.consume(code);
size++;
return labelInside;
}
return labelInside(code);
}
}
/**
* @import {
* Code,
* Effects,
* State,
* TokenType
* } from 'micromark-util-types'
*/
/**
* Parse titles.
*
* ###### Examples
*
* ```markdown
* "a"
* 'b'
* (c)
* "a
* b"
* 'a
* b'
* (a\)b)
* ```
*
* @param {Effects} effects
* Context.
* @param {State} ok
* State switched to when successful.
* @param {State} nok
* State switched to when unsuccessful.
* @param {TokenType} type
* Type of the whole title (`"a"`, `'b'`, `(c)`).
* @param {TokenType} markerType
* Type for the markers (`"`, `'`, `(`, and `)`).
* @param {TokenType} stringType
* Type for the value (`a`).
* @returns {State}
* Start state.
*/
function factoryTitle(effects, ok, nok, type, markerType, stringType) {
/** @type {NonNullable<Code>} */
let marker;
return start;
/**
* Start of title.
*
* ```markdown
* > | "a"
* ^
* ```
*
* @type {State}
*/
function start(code) {
if (code === 34 || code === 39 || code === 40) {
effects.enter(type);
effects.enter(markerType);
effects.consume(code);
effects.exit(markerType);
marker = code === 40 ? 41 : code;
return begin;
}
return nok(code);
}
/**
* After opening marker.
*
* This is also used at the closing marker.
*
* ```markdown
* > | "a"
* ^
* ```
*
* @type {State}
*/
function begin(code) {
if (code === marker) {
effects.enter(markerType);
effects.consume(code);
effects.exit(markerType);
effects.exit(type);
return ok;
}
effects.enter(stringType);
return atBreak(code);
}
/**
* At something, before something else.
*
* ```markdown
* > | "a"
* ^
* ```
*
* @type {State}
*/
function atBreak(code) {
if (code === marker) {
effects.exit(stringType);
return begin(marker);
}
if (code === null) {
return nok(code);
}
// Note: blank lines canβt exist in content.
if (markdownLineEnding(code)) {
// To do: use `space_or_tab_eol_with_options`, connect.
effects.enter("lineEnding");
effects.consume(code);
effects.exit("lineEnding");
return factorySpace(effects, atBreak, "linePrefix");
}
effects.enter("chunkString", {
contentType: "string"
});
return inside(code);
}
/**
*
*
* @type {State}
*/
function inside(code) {
if (code === marker || code === null || markdownLineEnding(code)) {
effects.exit("chunkString");
return atBreak(code);
}
effects.consume(code);
return code === 92 ? escape : inside;
}
/**
* After `\`, at a special character.
*
* ```markdown
* > | "a\*b"
* ^
* ```
*
* @type {State}
*/
function escape(code) {
if (code === marker || code === 92) {
effects.consume(code);
return inside;
}
return inside(code);
}
}
/**
* @import {Effects, State} from 'micromark-util-types'
*/
/**
* Parse spaces and tabs.
*
* There is no `nok` parameter:
*
* * line endings or spaces in markdown are often optional, in which case this
* factory can be used and `ok` will be switched to whether spaces were found
* or not
* * one line ending or space can be detected with
* `markdownLineEndingOrSpace(code)` right before using `factoryWhitespace`
*
* @param {Effects} effects
* Context.
* @param {State} ok
* State switched to when successful.
* @returns {State}
* Start state.
*/
function factoryWhitespace(effects, ok) {
/** @type {boolean} */
let seen;
return start;
/** @type {State} */
function start(code) {
if (markdownLineEnding(code)) {
effects.enter("lineEnding");
effects.consume(code);
effects.exit("lineEnding");
seen = true;
return start;
}
if (markdownSpace(code)) {
return factorySpace(effects, start, seen ? "linePrefix" : "lineSuffix")(code);
}
return ok(code);
}
}
/**
* @import {
* Construct,
* State,
* TokenizeContext,
* Tokenizer
* } from 'micromark-util-types'
*/
/** @type {Construct} */
const definition$1 = {
name: 'definition',
tokenize: tokenizeDefinition
};
/** @type {Construct} */
const titleBefore = {
partial: true,
tokenize: tokenizeTitleBefore
};
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeDefinition(effects, ok, nok) {
const self = this;
/** @type {string} */
let identifier;
return start;
/**
* At start of a definition.
*
* ```markdown
* > | [a]: b "c"
* ^
* ```
*
* @type {State}
*/
function start(code) {
// Do not interrupt paragraphs (but do follow definitions).
// To do: do `interrupt` the way `markdown-rs` does.
// To do: parse whitespace the way `markdown-rs` does.
effects.enter("definition");
return before(code);
}
/**
* After optional whitespace, at `[`.
*
* ```markdown
* > | [a]: b "c"
* ^
* ```
*
* @type {State}
*/
function before(code) {
// To do: parse whitespace the way `markdown-rs` does.
return factoryLabel.call(self, effects, labelAfter,
// Note: we donβt need to reset the way `markdown-rs` does.
nok, "definitionLabel", "definitionLabelMarker", "definitionLabelString")(code);
}
/**
* After label.
*
* ```markdown
* > | [a]: b "c"
* ^
* ```
*
* @type {State}
*/
function labelAfter(code) {
identifier = normalizeIdentifier(self.sliceSerialize(self.events[self.events.length - 1][1]).slice(1, -1));
if (code === 58) {
effects.enter("definitionMarker");
effects.consume(code);
effects.exit("definitionMarker");
return markerAfter;
}
return nok(code);
}
/**
* After marker.
*
* ```markdown
* > | [a]: b "c"
* ^
* ```
*
* @type {State}
*/
function markerAfter(code) {
// Note: whitespace is optional.
return markdownLineEndingOrSpace(code) ? factoryWhitespace(effects, destinationBefore)(code) : destinationBefore(code);
}
/**
* Before destination.
*
* ```markdown
* > | [a]: b "c"
* ^
* ```
*
* @type {State}
*/
function destinationBefore(code) {
return factoryDestination(effects, destinationAfter,
// Note: we donβt need to reset the way `markdown-rs` does.
nok, "definitionDestination", "definitionDestinationLiteral", "definitionDestinationLiteralMarker", "definitionDestinationRaw", "definitionDestinationString")(code);
}
/**
* After destination.
*
* ```markdown
* > | [a]: b "c"
* ^
* ```
*
* @type {State}
*/
function destinationAfter(code) {
return effects.attempt(titleBefore, after, after)(code);
}
/**
* After definition.
*
* ```markdown
* > | [a]: b
* ^
* > | [a]: b "c"
* ^
* ```
*
* @type {State}
*/
function after(code) {
return markdownSpace(code) ? factorySpace(effects, afterWhitespace, "whitespace")(code) : afterWhitespace(code);
}
/**
* After definition, after optional whitespace.
*
* ```markdown
* > | [a]: b
* ^
* > | [a]: b "c"
* ^
* ```
*
* @type {State}
*/
function afterWhitespace(code) {
if (code === null || markdownLineEnding(code)) {
effects.exit("definition");
// Note: we donβt care about uniqueness.
// Itβs likely that that doesnβt happen very frequently.
// It is more likely that it wastes precious time.
self.parser.defined.push(identifier);
// To do: `markdown-rs` interrupt.
// // Youβd be interrupting.
// tokenizer.interrupt = true
return ok(code);
}
return nok(code);
}
}
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeTitleBefore(effects, ok, nok) {
return titleBefore;
/**
* After destination, at whitespace.
*
* ```markdown
* > | [a]: b
* ^
* > | [a]: b "c"
* ^
* ```
*
* @type {State}
*/
function titleBefore(code) {
return markdownLineEndingOrSpace(code) ? factoryWhitespace(effects, beforeMarker)(code) : nok(code);
}
/**
* At title.
*
* ```markdown
* | [a]: b
* > | "c"
* ^
* ```
*
* @type {State}
*/
function beforeMarker(code) {
return factoryTitle(effects, titleAfter, nok, "definitionTitle", "definitionTitleMarker", "definitionTitleString")(code);
}
/**
* After title.
*
* ```markdown
* > | [a]: b "c"
* ^
* ```
*
* @type {State}
*/
function titleAfter(code) {
return markdownSpace(code) ? factorySpace(effects, titleAfterOptionalWhitespace, "whitespace")(code) : titleAfterOptionalWhitespace(code);
}
/**
* After title, after optional whitespace.
*
* ```markdown
* > | [a]: b "c"
* ^
* ```
*
* @type {State}
*/
function titleAfterOptionalWhitespace(code) {
return code === null || markdownLineEnding(code) ? ok(code) : nok(code);
}
}
/**
* @import {
* Construct,
* State,
* TokenizeContext,
* Tokenizer
* } from 'micromark-util-types'
*/
/** @type {Construct} */
const hardBreakEscape = {
name: 'hardBreakEscape',
tokenize: tokenizeHardBreakEscape
};
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeHardBreakEscape(effects, ok, nok) {
return start;
/**
* Start of a hard break (escape).
*
* ```markdown
* > | a\
* ^
* | b
* ```
*
* @type {State}
*/
function start(code) {
effects.enter("hardBreakEscape");
effects.consume(code);
return after;
}
/**
* After `\`, at eol.
*
* ```markdown
* > | a\
* ^
* | b
* ```
*
* @type {State}
*/
function after(code) {
if (markdownLineEnding(code)) {
effects.exit("hardBreakEscape");
return ok(code);
}
return nok(code);
}
}
/**
* @import {
* Construct,
* Resolver,
* State,
* TokenizeContext,
* Tokenizer,
* Token
* } from 'micromark-util-types'
*/
/** @type {Construct} */
const headingAtx = {
name: 'headingAtx',
resolve: resolveHeadingAtx,
tokenize: tokenizeHeadingAtx
};
/** @type {Resolver} */
function resolveHeadingAtx(events, context) {
let contentEnd = events.length - 2;
let contentStart = 3;
/** @type {Token} */
let content;
/** @type {Token} */
let text;
// Prefix whitespace, part of the opening.
if (events[contentStart][1].type === "whitespace") {
contentStart += 2;
}
// Suffix whitespace, part of the closing.
if (contentEnd - 2 > contentStart && events[contentEnd][1].type === "whitespace") {
contentEnd -= 2;
}
if (events[contentEnd][1].type === "atxHeadingSequence" && (contentStart === contentEnd - 1 || contentEnd - 4 > contentStart && events[contentEnd - 2][1].type === "whitespace")) {
contentEnd -= contentStart + 1 === contentEnd ? 2 : 4;
}
if (contentEnd > contentStart) {
content = {
type: "atxHeadingText",
start: events[contentStart][1].start,
end: events[contentEnd][1].end
};
text = {
type: "chunkText",
start: events[contentStart][1].start,
end: events[contentEnd][1].end,
contentType: "text"
};
splice(events, contentStart, contentEnd - contentStart + 1, [['enter', content, context], ['enter', text, context], ['exit', text, context], ['exit', content, context]]);
}
return events;
}
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeHeadingAtx(effects, ok, nok) {
let size = 0;
return start;
/**
* Start of a heading (atx).
*
* ```markdown
* > | ## aa
* ^
* ```
*
* @type {State}
*/
function start(code) {
// To do: parse indent like `markdown-rs`.
effects.enter("atxHeading");
return before(code);
}
/**
* After optional whitespace, at `#`.
*
* ```markdown
* > | ## aa
* ^
* ```
*
* @type {State}
*/
function before(code) {
effects.enter("atxHeadingSequence");
return sequenceOpen(code);
}
/**
* In opening sequence.
*
* ```markdown
* > | ## aa
* ^
* ```
*
* @type {State}
*/
function sequenceOpen(code) {
if (code === 35 && size++ < 6) {
effects.consume(code);
return sequenceOpen;
}
// Always at least one `#`.
if (code === null || markdownLineEndingOrSpace(code)) {
effects.exit("atxHeadingSequence");
return atBreak(code);
}
return nok(code);
}
/**
* After something, before something else.
*
* ```markdown
* > | ## aa
* ^
* ```
*
* @type {State}
*/
function atBreak(code) {
if (code === 35) {
effects.enter("atxHeadingSequence");
return sequenceFurther(code);
}
if (code === null || markdownLineEnding(code)) {
effects.exit("atxHeading");
// To do: interrupt like `markdown-rs`.
// // Feel free to interrupt.
// tokenizer.interrupt = false
return ok(code);
}
if (markdownSpace(code)) {
return factorySpace(effects, atBreak, "whitespace")(code);
}
// To do: generate `data` tokens, add the `text` token later.
// Needs edit map, see: `markdown.rs`.
effects.enter("atxHeadingText");
return data(code);
}
/**
* In further sequence (after whitespace).
*
* Could be normal βvisibleβ hashes in the heading or a final sequence.
*
* ```markdown
* > | ## aa ##
* ^
* ```
*
* @type {State}
*/
function sequenceFurther(code) {
if (code === 35) {
effects.consume(code);
return sequenceFurther;
}
effects.exit("atxHeadingSequence");
return atBreak(code);
}
/**
* In text.
*
* ```markdown
* > | ## aa
* ^
* ```
*
* @type {State}
*/
function data(code) {
if (code === null || code === 35 || markdownLineEndingOrSpace(code)) {
effects.exit("atxHeadingText");
return atBreak(code);
}
effects.consume(code);
return data;
}
}
/**
* List of lowercase HTML βblockβ tag names.
*
* The list, when parsing HTML (flow), results in more relaxed rules (condition
* 6).
* Because they are known blocks, the HTML-like syntax doesnβt have to be
* strictly parsed.
* For tag names not in this list, a more strict algorithm (condition 7) is used
* to detect whether the HTML-like syntax is seen as HTML (flow) or not.
*
* This is copied from:
* <https://spec.commonmark.org/0.30/#html-blocks>.
*
* > π **Note**: `search` was added in `CommonMark@0.31`.
*/
const htmlBlockNames = [
'address',
'article',
'aside',
'base',
'basefont',
'blockquote',
'body',
'caption',
'center',
'col',
'colgroup',
'dd',
'details',
'dialog',
'dir',
'div',
'dl',
'dt',
'fieldset',
'figcaption',
'figure',
'footer',
'form',
'frame',
'frameset',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'head',
'header',
'hr',
'html',
'iframe',
'legend',
'li',
'link',
'main',
'menu',
'menuitem',
'nav',
'noframes',
'ol',
'optgroup',
'option',
'p',
'param',
'search',
'section',
'summary',
'table',
'tbody',
'td',
'tfoot',
'th',
'thead',
'title',
'tr',
'track',
'ul'
];
/**
* List of lowercase HTML βrawβ tag names.
*
* The list, when parsing HTML (flow), results in HTML that can include lines
* without exiting, until a closing tag also in this list is found (condition
* 1).
*
* This module is copied from:
* <https://spec.commonmark.org/0.30/#html-blocks>.
*
* > π **Note**: `textarea` was added in `CommonMark@0.30`.
*/
const htmlRawNames = ['pre', 'script', 'style', 'textarea'];
/**
* @import {
* Code,
* Construct,
* Resolver,
* State,
* TokenizeContext,
* Tokenizer
* } from 'micromark-util-types'
*/
/** @type {Construct} */
const htmlFlow = {
concrete: true,
name: 'htmlFlow',
resolveTo: resolveToHtmlFlow,
tokenize: tokenizeHtmlFlow
};
/** @type {Construct} */
const blankLineBefore = {
partial: true,
tokenize: tokenizeBlankLineBefore
};
const nonLazyContinuationStart = {
partial: true,
tokenize: tokenizeNonLazyContinuationStart
};
/** @type {Resolver} */
function resolveToHtmlFlow(events) {
let index = events.length;
while (index--) {
if (events[index][0] === 'enter' && events[index][1].type === "htmlFlow") {
break;
}
}
if (index > 1 && events[index - 2][1].type === "linePrefix") {
// Add the prefix start to the HTML token.
events[index][1].start = events[index - 2][1].start;
// Add the prefix start to the HTML line token.
events[index + 1][1].start = events[index - 2][1].start;
// Remove the line prefix.
events.splice(index - 2, 2);
}
return events;
}
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeHtmlFlow(effects, ok, nok) {
const self = this;
/** @type {number} */
let marker;
/** @type {boolean} */
let closingTag;
/** @type {string} */
let buffer;
/** @type {number} */
let index;
/** @type {Code} */
let markerB;
return start;
/**
* Start of HTML (flow).
*
* ```markdown
* > | <x />
* ^
* ```
*
* @type {State}
*/
function start(code) {
// To do: parse indent like `markdown-rs`.
return before(code);
}
/**
* At `<`, after optional whitespace.
*
* ```markdown
* > | <x />
* ^
* ```
*
* @type {State}
*/
function before(code) {
effects.enter("htmlFlow");
effects.enter("htmlFlowData");
effects.consume(code);
return open;
}
/**
* After `<`, at tag name or other stuff.
*
* ```markdown
* > | <x />
* ^
* > | <!doctype>
* ^
* > | <!--xxx-->
* ^
* ```
*
* @type {State}
*/
function open(code) {
if (code === 33) {
effects.consume(code);
return declarationOpen;
}
if (code === 47) {
effects.consume(code);
closingTag = true;
return tagCloseStart;
}
if (code === 63) {
effects.consume(code);
marker = 3;
// To do:
// tokenizer.concrete = true
// To do: use `markdown-rs` style interrupt.
// While weβre in an instruction instead of a declaration, weβre on a `?`
// right now, so we do need to search for `>`, similar to declarations.
return self.interrupt ? ok : continuationDeclarationInside;
}
// ASCII alphabetical.
if (asciiAlpha(code)) {
// Always the case.
effects.consume(code);
buffer = String.fromCharCode(code);
return tagName;
}
return nok(code);
}
/**
* After `<!`, at declaration, comment, or CDATA.
*
* ```markdown
* > | <!doctype>
* ^
* > | <!--xxx-->
* ^
* > | <![CDATA[>&<]]>
* ^
* ```
*
* @type {State}
*/
function declarationOpen(code) {
if (code === 45) {
effects.consume(code);
marker = 2;
return commentOpenInside;
}
if (code === 91) {
effects.consume(code);
marker = 5;
index = 0;
return cdataOpenInside;
}
// ASCII alphabetical.
if (asciiAlpha(code)) {
effects.consume(code);
marker = 4;
// // Do not form containers.
// tokenizer.concrete = true
return self.interrupt ? ok : continuationDeclarationInside;
}
return nok(code);
}
/**
* After `<!-`, inside a comment, at another `-`.
*
* ```markdown
* > | <!--xxx-->
* ^
* ```
*
* @type {State}
*/
function commentOpenInside(code) {
if (code === 45) {
effects.consume(code);
// // Do not form containers.
// tokenizer.concrete = true
return self.interrupt ? ok : continuationDeclarationInside;
}
return nok(code);
}
/**
* After `<![`, inside CDATA, expecting `CDATA[`.
*
* ```markdown
* > | <![CDATA[>&<]]>
* ^^^^^^
* ```
*
* @type {State}
*/
function cdataOpenInside(code) {
const value = "CDATA[";
if (code === value.charCodeAt(index++)) {
effects.consume(code);
if (index === value.length) {
// // Do not form containers.
// tokenizer.concrete = true
return self.interrupt ? ok : continuation;
}
return cdataOpenInside;
}
return nok(code);
}
/**
* After `</`, in closing tag, at tag name.
*
* ```markdown
* > | </x>
* ^
* ```
*
* @type {State}
*/
function tagCloseStart(code) {
if (asciiAlpha(code)) {
// Always the case.
effects.consume(code);
buffer = String.fromCharCode(code);
return tagName;
}
return nok(code);
}
/**
* In tag name.
*
* ```markdown
* > | <ab>
* ^^
* > | </ab>
* ^^
* ```
*
* @type {State}
*/
function tagName(code) {
if (code === null || code === 47 || code === 62 || markdownLineEndingOrSpace(code)) {
const slash = code === 47;
const name = buffer.toLowerCase();
if (!slash && !closingTag && htmlRawNames.includes(name)) {
marker = 1;
// // Do not form containers.
// tokenizer.concrete = true
return self.interrupt ? ok(code) : continuation(code);
}
if (htmlBlockNames.includes(buffer.toLowerCase())) {
marker = 6;
if (slash) {
effects.consume(code);
return basicSelfClosing;
}
// // Do not form containers.
// tokenizer.concrete = true
return self.interrupt ? ok(code) : continuation(code);
}
marker = 7;
// Do not support complete HTML when interrupting.
return self.interrupt && !self.parser.lazy[self.now().line] ? nok(code) : closingTag ? completeClosingTagAfter(code) : completeAttributeNameBefore(code);
}
// ASCII alphanumerical and `-`.
if (code === 45 || asciiAlphanumeric(code)) {
effects.consume(code);
buffer += String.fromCharCode(code);
return tagName;
}
return nok(code);
}
/**
* After closing slash of a basic tag name.
*
* ```markdown
* > | <div/>
* ^
* ```
*
* @type {State}
*/
function basicSelfClosing(code) {
if (code === 62) {
effects.consume(code);
// // Do not form containers.
// tokenizer.concrete = true
return self.interrupt ? ok : continuation;
}
return nok(code);
}
/**
* After closing slash of a complete tag name.
*
* ```markdown
* > | <x/>
* ^
* ```
*
* @type {State}
*/
function completeClosingTagAfter(code) {
if (markdownSpace(code)) {
effects.consume(code);
return completeClosingTagAfter;
}
return completeEnd(code);
}
/**
* At an attribute name.
*
* At first, this state is used after a complete tag name, after whitespace,
* where it expects optional attributes or the end of the tag.
* It is also reused after attributes, when expecting more optional
* attributes.
*
* ```markdown
* > | <a />
* ^
* > | <a :b>
* ^
* > | <a _b>
* ^
* > | <a b>
* ^
* > | <a >
* ^
* ```
*
* @type {State}
*/
function completeAttributeNameBefore(code) {
if (code === 47) {
effects.consume(code);
return completeEnd;
}
// ASCII alphanumerical and `:` and `_`.
if (code === 58 || code === 95 || asciiAlpha(code)) {
effects.consume(code);
return completeAttributeName;
}
if (markdownSpace(code)) {
effects.consume(code);
return completeAttributeNameBefore;
}
return completeEnd(code);
}
/**
* In attribute name.
*
* ```markdown
* > | <a :b>
* ^
* > | <a _b>
* ^
* > | <a b>
* ^
* ```
*
* @type {State}
*/
function completeAttributeName(code) {
// ASCII alphanumerical and `-`, `.`, `:`, and `_`.
if (code === 45 || code === 46 || code === 58 || code === 95 || asciiAlphanumeric(code)) {
effects.consume(code);
return completeAttributeName;
}
return completeAttributeNameAfter(code);
}
/**
* After attribute name, at an optional initializer, the end of the tag, or
* whitespace.
*
* ```markdown
* > | <a b>
* ^
* > | <a b=c>
* ^
* ```
*
* @type {State}
*/
function completeAttributeNameAfter(code) {
if (code === 61) {
effects.consume(code);
return completeAttributeValueBefore;
}
if (markdownSpace(code)) {
effects.consume(code);
return completeAttributeNameAfter;
}
return completeAttributeNameBefore(code);
}
/**
* Before unquoted, double quoted, or single quoted attribute value, allowing
* whitespace.
*
* ```markdown
* > | <a b=c>
* ^
* > | <a b="c">
* ^
* ```
*
* @type {State}
*/
function completeAttributeValueBefore(code) {
if (code === null || code === 60 || code === 61 || code === 62 || code === 96) {
return nok(code);
}
if (code === 34 || code === 39) {
effects.consume(code);
markerB = code;
return completeAttributeValueQuoted;
}
if (markdownSpace(code)) {
effects.consume(code);
return completeAttributeValueBefore;
}
return completeAttributeValueUnquoted(code);
}
/**
* In double or single quoted attribute value.
*
* ```markdown
* > | <a b="c">
* ^
* > | <a b='c'>
* ^
* ```
*
* @type {State}
*/
function completeAttributeValueQuoted(code) {
if (code === markerB) {
effects.consume(code);
markerB = null;
return completeAttributeValueQuotedAfter;
}
if (code === null || markdownLineEnding(code)) {
return nok(code);
}
effects.consume(code);
return completeAttributeValueQuoted;
}
/**
* In unquoted attribute value.
*
* ```markdown
* > | <a b=c>
* ^
* ```
*
* @type {State}
*/
function completeAttributeValueUnquoted(code) {
if (code === null || code === 34 || code === 39 || code === 47 || code === 60 || code === 61 || code === 62 || code === 96 || markdownLineEndingOrSpace(code)) {
return completeAttributeNameAfter(code);
}
effects.consume(code);
return completeAttributeValueUnquoted;
}
/**
* After double or single quoted attribute value, before whitespace or the
* end of the tag.
*
* ```markdown
* > | <a b="c">
* ^
* ```
*
* @type {State}
*/
function completeAttributeValueQuotedAfter(code) {
if (code === 47 || code === 62 || markdownSpace(code)) {
return completeAttributeNameBefore(code);
}
return nok(code);
}
/**
* In certain circumstances of a complete tag where only an `>` is allowed.
*
* ```markdown
* > | <a b="c">
* ^
* ```
*
* @type {State}
*/
function completeEnd(code) {
if (code === 62) {
effects.consume(code);
return completeAfter;
}
return nok(code);
}
/**
* After `>` in a complete tag.
*
* ```markdown
* > | <x>
* ^
* ```
*
* @type {State}
*/
function completeAfter(code) {
if (code === null || markdownLineEnding(code)) {
// // Do not form containers.
// tokenizer.concrete = true
return continuation(code);
}
if (markdownSpace(code)) {
effects.consume(code);
return completeAfter;
}
return nok(code);
}
/**
* In continuation of any HTML kind.
*
* ```markdown
* > | <!--xxx-->
* ^
* ```
*
* @type {State}
*/
function continuation(code) {
if (code === 45 && marker === 2) {
effects.consume(code);
return continuationCommentInside;
}
if (code === 60 && marker === 1) {
effects.consume(code);
return continuationRawTagOpen;
}
if (code === 62 && marker === 4) {
effects.consume(code);
return continuationClose;
}
if (code === 63 && marker === 3) {
effects.consume(code);
return continuationDeclarationInside;
}
if (code === 93 && marker === 5) {
effects.consume(code);
return continuationCdataInside;
}
if (markdownLineEnding(code) && (marker === 6 || marker === 7)) {
effects.exit("htmlFlowData");
return effects.check(blankLineBefore, continuationAfter, continuationStart)(code);
}
if (code === null || markdownLineEnding(code)) {
effects.exit("htmlFlowData");
return continuationStart(code);
}
effects.consume(code);
return continuation;
}
/**
* In continuation, at eol.
*
* ```markdown
* > | <x>
* ^
* | asd
* ```
*
* @type {State}
*/
function continuationStart(code) {
return effects.check(nonLazyContinuationStart, continuationStartNonLazy, continuationAfter)(code);
}
/**
* In continuation, at eol, before non-lazy content.
*
* ```markdown
* > | <x>
* ^
* | asd
* ```
*
* @type {State}
*/
function continuationStartNonLazy(code) {
effects.enter("lineEnding");
effects.consume(code);
effects.exit("lineEnding");
return continuationBefore;
}
/**
* In continuation, before non-lazy content.
*
* ```markdown
* | <x>
* > | asd
* ^
* ```
*
* @type {State}
*/
function continuationBefore(code) {
if (code === null || markdownLineEnding(code)) {
return continuationStart(code);
}
effects.enter("htmlFlowData");
return continuation(code);
}
/**
* In comment continuation, after one `-`, expecting another.
*
* ```markdown
* > | <!--xxx-->
* ^
* ```
*
* @type {State}
*/
function continuationCommentInside(code) {
if (code === 45) {
effects.consume(code);
return continuationDeclarationInside;
}
return continuation(code);
}
/**
* In raw continuation, after `<`, at `/`.
*
* ```markdown
* > | <script>console.log(1)</script>
* ^
* ```
*
* @type {State}
*/
function continuationRawTagOpen(code) {
if (code === 47) {
effects.consume(code);
buffer = '';
return continuationRawEndTag;
}
return continuation(code);
}
/**
* In raw continuation, after `</`, in a raw tag name.
*
* ```markdown
* > | <script>console.log(1)</script>
* ^^^^^^
* ```
*
* @type {State}
*/
function continuationRawEndTag(code) {
if (code === 62) {
const name = buffer.toLowerCase();
if (htmlRawNames.includes(name)) {
effects.consume(code);
return continuationClose;
}
return continuation(code);
}
if (asciiAlpha(code) && buffer.length < 8) {
// Always the case.
effects.consume(code);
buffer += String.fromCharCode(code);
return continuationRawEndTag;
}
return continuation(code);
}
/**
* In cdata continuation, after `]`, expecting `]>`.
*
* ```markdown
* > | <![CDATA[>&<]]>
* ^
* ```
*
* @type {State}
*/
function continuationCdataInside(code) {
if (code === 93) {
effects.consume(code);
return continuationDeclarationInside;
}
return continuation(code);
}
/**
* In declaration or instruction continuation, at `>`.
*
* ```markdown
* > | <!-->
* ^
* > | <?>
* ^
* > | <!q>
* ^
* > | <!--ab-->
* ^
* > | <![CDATA[>&<]]>
* ^
* ```
*
* @type {State}
*/
function continuationDeclarationInside(code) {
if (code === 62) {
effects.consume(code);
return continuationClose;
}
// More dashes.
if (code === 45 && marker === 2) {
effects.consume(code);
return continuationDeclarationInside;
}
return continuation(code);
}
/**
* In closed continuation: everything we get until the eol/eof is part of it.
*
* ```markdown
* > | <!doctype>
* ^
* ```
*
* @type {State}
*/
function continuationClose(code) {
if (code === null || markdownLineEnding(code)) {
effects.exit("htmlFlowData");
return continuationAfter(code);
}
effects.consume(code);
return continuationClose;
}
/**
* Done.
*
* ```markdown
* > | <!doctype>
* ^
* ```
*
* @type {State}
*/
function continuationAfter(code) {
effects.exit("htmlFlow");
// // Feel free to interrupt.
// tokenizer.interrupt = false
// // No longer concrete.
// tokenizer.concrete = false
return ok(code);
}
}
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeNonLazyContinuationStart(effects, ok, nok) {
const self = this;
return start;
/**
* At eol, before continuation.
*
* ```markdown
* > | * ```js
* ^
* | b
* ```
*
* @type {State}
*/
function start(code) {
if (markdownLineEnding(code)) {
effects.enter("lineEnding");
effects.consume(code);
effects.exit("lineEnding");
return after;
}
return nok(code);
}
/**
* A continuation.
*
* ```markdown
* | * ```js
* > | b
* ^
* ```
*
* @type {State}
*/
function after(code) {
return self.parser.lazy[self.now().line] ? nok(code) : ok(code);
}
}
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeBlankLineBefore(effects, ok, nok) {
return start;
/**
* Before eol, expecting blank line.
*
* ```markdown
* > | <div>
* ^
* |
* ```
*
* @type {State}
*/
function start(code) {
effects.enter("lineEnding");
effects.consume(code);
effects.exit("lineEnding");
return effects.attempt(blankLine, ok, nok);
}
}
/**
* @import {
* Code,
* Construct,
* State,
* TokenizeContext,
* Tokenizer
* } from 'micromark-util-types'
*/
/** @type {Construct} */
const htmlText = {
name: 'htmlText',
tokenize: tokenizeHtmlText
};
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeHtmlText(effects, ok, nok) {
const self = this;
/** @type {NonNullable<Code> | undefined} */
let marker;
/** @type {number} */
let index;
/** @type {State} */
let returnState;
return start;
/**
* Start of HTML (text).
*
* ```markdown
* > | a <b> c
* ^
* ```
*
* @type {State}
*/
function start(code) {
effects.enter("htmlText");
effects.enter("htmlTextData");
effects.consume(code);
return open;
}
/**
* After `<`, at tag name or other stuff.
*
* ```markdown
* > | a <b> c
* ^
* > | a <!doctype> c
* ^
* > | a <!--b--> c
* ^
* ```
*
* @type {State}
*/
function open(code) {
if (code === 33) {
effects.consume(code);
return declarationOpen;
}
if (code === 47) {
effects.consume(code);
return tagCloseStart;
}
if (code === 63) {
effects.consume(code);
return instruction;
}
// ASCII alphabetical.
if (asciiAlpha(code)) {
effects.consume(code);
return tagOpen;
}
return nok(code);
}
/**
* After `<!`, at declaration, comment, or CDATA.
*
* ```markdown
* > | a <!doctype> c
* ^
* > | a <!--b--> c
* ^
* > | a <![CDATA[>&<]]> c
* ^
* ```
*
* @type {State}
*/
function declarationOpen(code) {
if (code === 45) {
effects.consume(code);
return commentOpenInside;
}
if (code === 91) {
effects.consume(code);
index = 0;
return cdataOpenInside;
}
if (asciiAlpha(code)) {
effects.consume(code);
return declaration;
}
return nok(code);
}
/**
* In a comment, after `<!-`, at another `-`.
*
* ```markdown
* > | a <!--b--> c
* ^
* ```
*
* @type {State}
*/
function commentOpenInside(code) {
if (code === 45) {
effects.consume(code);
return commentEnd;
}
return nok(code);
}
/**
* In comment.
*
* ```markdown
* > | a <!--b--> c
* ^
* ```
*
* @type {State}
*/
function comment(code) {
if (code === null) {
return nok(code);
}
if (code === 45) {
effects.consume(code);
return commentClose;
}
if (markdownLineEnding(code)) {
returnState = comment;
return lineEndingBefore(code);
}
effects.consume(code);
return comment;
}
/**
* In comment, after `-`.
*
* ```markdown
* > | a <!--b--> c
* ^
* ```
*
* @type {State}
*/
function commentClose(code) {
if (code === 45) {
effects.consume(code);
return commentEnd;
}
return comment(code);
}
/**
* In comment, after `--`.
*
* ```markdown
* > | a <!--b--> c
* ^
* ```
*
* @type {State}
*/
function commentEnd(code) {
return code === 62 ? end(code) : code === 45 ? commentClose(code) : comment(code);
}
/**
* After `<![`, in CDATA, expecting `CDATA[`.
*
* ```markdown
* > | a <![CDATA[>&<]]> b
* ^^^^^^
* ```
*
* @type {State}
*/
function cdataOpenInside(code) {
const value = "CDATA[";
if (code === value.charCodeAt(index++)) {
effects.consume(code);
return index === value.length ? cdata : cdataOpenInside;
}
return nok(code);
}
/**
* In CDATA.
*
* ```markdown
* > | a <![CDATA[>&<]]> b
* ^^^
* ```
*
* @type {State}
*/
function cdata(code) {
if (code === null) {
return nok(code);
}
if (code === 93) {
effects.consume(code);
return cdataClose;
}
if (markdownLineEnding(code)) {
returnState = cdata;
return lineEndingBefore(code);
}
effects.consume(code);
return cdata;
}
/**
* In CDATA, after `]`, at another `]`.
*
* ```markdown
* > | a <![CDATA[>&<]]> b
* ^
* ```
*
* @type {State}
*/
function cdataClose(code) {
if (code === 93) {
effects.consume(code);
return cdataEnd;
}
return cdata(code);
}
/**
* In CDATA, after `]]`, at `>`.
*
* ```markdown
* > | a <![CDATA[>&<]]> b
* ^
* ```
*
* @type {State}
*/
function cdataEnd(code) {
if (code === 62) {
return end(code);
}
if (code === 93) {
effects.consume(code);
return cdataEnd;
}
return cdata(code);
}
/**
* In declaration.
*
* ```markdown
* > | a <!b> c
* ^
* ```
*
* @type {State}
*/
function declaration(code) {
if (code === null || code === 62) {
return end(code);
}
if (markdownLineEnding(code)) {
returnState = declaration;
return lineEndingBefore(code);
}
effects.consume(code);
return declaration;
}
/**
* In instruction.
*
* ```markdown
* > | a <?b?> c
* ^
* ```
*
* @type {State}
*/
function instruction(code) {
if (code === null) {
return nok(code);
}
if (code === 63) {
effects.consume(code);
return instructionClose;
}
if (markdownLineEnding(code)) {
returnState = instruction;
return lineEndingBefore(code);
}
effects.consume(code);
return instruction;
}
/**
* In instruction, after `?`, at `>`.
*
* ```markdown
* > | a <?b?> c
* ^
* ```
*
* @type {State}
*/
function instructionClose(code) {
return code === 62 ? end(code) : instruction(code);
}
/**
* After `</`, in closing tag, at tag name.
*
* ```markdown
* > | a </b> c
* ^
* ```
*
* @type {State}
*/
function tagCloseStart(code) {
// ASCII alphabetical.
if (asciiAlpha(code)) {
effects.consume(code);
return tagClose;
}
return nok(code);
}
/**
* After `</x`, in a tag name.
*
* ```markdown
* > | a </b> c
* ^
* ```
*
* @type {State}
*/
function tagClose(code) {
// ASCII alphanumerical and `-`.
if (code === 45 || asciiAlphanumeric(code)) {
effects.consume(code);
return tagClose;
}
return tagCloseBetween(code);
}
/**
* In closing tag, after tag name.
*
* ```markdown
* > | a </b> c
* ^
* ```
*
* @type {State}
*/
function tagCloseBetween(code) {
if (markdownLineEnding(code)) {
returnState = tagCloseBetween;
return lineEndingBefore(code);
}
if (markdownSpace(code)) {
effects.consume(code);
return tagCloseBetween;
}
return end(code);
}
/**
* After `<x`, in opening tag name.
*
* ```markdown
* > | a <b> c
* ^
* ```
*
* @type {State}
*/
function tagOpen(code) {
// ASCII alphanumerical and `-`.
if (code === 45 || asciiAlphanumeric(code)) {
effects.consume(code);
return tagOpen;
}
if (code === 47 || code === 62 || markdownLineEndingOrSpace(code)) {
return tagOpenBetween(code);
}
return nok(code);
}
/**
* In opening tag, after tag name.
*
* ```markdown
* > | a <b> c
* ^
* ```
*
* @type {State}
*/
function tagOpenBetween(code) {
if (code === 47) {
effects.consume(code);
return end;
}
// ASCII alphabetical and `:` and `_`.
if (code === 58 || code === 95 || asciiAlpha(code)) {
effects.consume(code);
return tagOpenAttributeName;
}
if (markdownLineEnding(code)) {
returnState = tagOpenBetween;
return lineEndingBefore(code);
}
if (markdownSpace(code)) {
effects.consume(code);
return tagOpenBetween;
}
return end(code);
}
/**
* In attribute name.
*
* ```markdown
* > | a <b c> d
* ^
* ```
*
* @type {State}
*/
function tagOpenAttributeName(code) {
// ASCII alphabetical and `-`, `.`, `:`, and `_`.
if (code === 45 || code === 46 || code === 58 || code === 95 || asciiAlphanumeric(code)) {
effects.consume(code);
return tagOpenAttributeName;
}
return tagOpenAttributeNameAfter(code);
}
/**
* After attribute name, before initializer, the end of the tag, or
* whitespace.
*
* ```markdown
* > | a <b c> d
* ^
* ```
*
* @type {State}
*/
function tagOpenAttributeNameAfter(code) {
if (code === 61) {
effects.consume(code);
return tagOpenAttributeValueBefore;
}
if (markdownLineEnding(code)) {
returnState = tagOpenAttributeNameAfter;
return lineEndingBefore(code);
}
if (markdownSpace(code)) {
effects.consume(code);
return tagOpenAttributeNameAfter;
}
return tagOpenBetween(code);
}
/**
* Before unquoted, double quoted, or single quoted attribute value, allowing
* whitespace.
*
* ```markdown
* > | a <b c=d> e
* ^
* ```
*
* @type {State}
*/
function tagOpenAttributeValueBefore(code) {
if (code === null || code === 60 || code === 61 || code === 62 || code === 96) {
return nok(code);
}
if (code === 34 || code === 39) {
effects.consume(code);
marker = code;
return tagOpenAttributeValueQuoted;
}
if (markdownLineEnding(code)) {
returnState = tagOpenAttributeValueBefore;
return lineEndingBefore(code);
}
if (markdownSpace(code)) {
effects.consume(code);
return tagOpenAttributeValueBefore;
}
effects.consume(code);
return tagOpenAttributeValueUnquoted;
}
/**
* In double or single quoted attribute value.
*
* ```markdown
* > | a <b c="d"> e
* ^
* ```
*
* @type {State}
*/
function tagOpenAttributeValueQuoted(code) {
if (code === marker) {
effects.consume(code);
marker = undefined;
return tagOpenAttributeValueQuotedAfter;
}
if (code === null) {
return nok(code);
}
if (markdownLineEnding(code)) {
returnState = tagOpenAttributeValueQuoted;
return lineEndingBefore(code);
}
effects.consume(code);
return tagOpenAttributeValueQuoted;
}
/**
* In unquoted attribute value.
*
* ```markdown
* > | a <b c=d> e
* ^
* ```
*
* @type {State}
*/
function tagOpenAttributeValueUnquoted(code) {
if (code === null || code === 34 || code === 39 || code === 60 || code === 61 || code === 96) {
return nok(code);
}
if (code === 47 || code === 62 || markdownLineEndingOrSpace(code)) {
return tagOpenBetween(code);
}
effects.consume(code);
return tagOpenAttributeValueUnquoted;
}
/**
* After double or single quoted attribute value, before whitespace or the end
* of the tag.
*
* ```markdown
* > | a <b c="d"> e
* ^
* ```
*
* @type {State}
*/
function tagOpenAttributeValueQuotedAfter(code) {
if (code === 47 || code === 62 || markdownLineEndingOrSpace(code)) {
return tagOpenBetween(code);
}
return nok(code);
}
/**
* In certain circumstances of a tag where only an `>` is allowed.
*
* ```markdown
* > | a <b c="d"> e
* ^
* ```
*
* @type {State}
*/
function end(code) {
if (code === 62) {
effects.consume(code);
effects.exit("htmlTextData");
effects.exit("htmlText");
return ok;
}
return nok(code);
}
/**
* At eol.
*
* > π **Note**: we canβt have blank lines in text, so no need to worry about
* > empty tokens.
*
* ```markdown
* > | a <!--a
* ^
* | b-->
* ```
*
* @type {State}
*/
function lineEndingBefore(code) {
effects.exit("htmlTextData");
effects.enter("lineEnding");
effects.consume(code);
effects.exit("lineEnding");
return lineEndingAfter;
}
/**
* After eol, at optional whitespace.
*
* > π **Note**: we canβt have blank lines in text, so no need to worry about
* > empty tokens.
*
* ```markdown
* | a <!--a
* > | b-->
* ^
* ```
*
* @type {State}
*/
function lineEndingAfter(code) {
// Always populated by defaults.
return markdownSpace(code) ? factorySpace(effects, lineEndingAfterPrefix, "linePrefix", self.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4)(code) : lineEndingAfterPrefix(code);
}
/**
* After eol, after optional whitespace.
*
* > π **Note**: we canβt have blank lines in text, so no need to worry about
* > empty tokens.
*
* ```markdown
* | a <!--a
* > | b-->
* ^
* ```
*
* @type {State}
*/
function lineEndingAfterPrefix(code) {
effects.enter("htmlTextData");
return returnState(code);
}
}
/**
* @import {
* Construct,
* Event,
* Resolver,
* State,
* TokenizeContext,
* Tokenizer,
* Token
* } from 'micromark-util-types'
*/
/** @type {Construct} */
const labelEnd = {
name: 'labelEnd',
resolveAll: resolveAllLabelEnd,
resolveTo: resolveToLabelEnd,
tokenize: tokenizeLabelEnd
};
/** @type {Construct} */
const resourceConstruct = {
tokenize: tokenizeResource
};
/** @type {Construct} */
const referenceFullConstruct = {
tokenize: tokenizeReferenceFull
};
/** @type {Construct} */
const referenceCollapsedConstruct = {
tokenize: tokenizeReferenceCollapsed
};
/** @type {Resolver} */
function resolveAllLabelEnd(events) {
let index = -1;
/** @type {Array<Event>} */
const newEvents = [];
while (++index < events.length) {
const token = events[index][1];
newEvents.push(events[index]);
if (token.type === "labelImage" || token.type === "labelLink" || token.type === "labelEnd") {
// Remove the marker.
const offset = token.type === "labelImage" ? 4 : 2;
token.type = "data";
index += offset;
}
}
// If the events are equal, we don't have to copy newEvents to events
if (events.length !== newEvents.length) {
splice(events, 0, events.length, newEvents);
}
return events;
}
/** @type {Resolver} */
function resolveToLabelEnd(events, context) {
let index = events.length;
let offset = 0;
/** @type {Token} */
let token;
/** @type {number | undefined} */
let open;
/** @type {number | undefined} */
let close;
/** @type {Array<Event>} */
let media;
// Find an opening.
while (index--) {
token = events[index][1];
if (open) {
// If we see another link, or inactive link label, weβve been here before.
if (token.type === "link" || token.type === "labelLink" && token._inactive) {
break;
}
// Mark other link openings as inactive, as we canβt have links in
// links.
if (events[index][0] === 'enter' && token.type === "labelLink") {
token._inactive = true;
}
} else if (close) {
if (events[index][0] === 'enter' && (token.type === "labelImage" || token.type === "labelLink") && !token._balanced) {
open = index;
if (token.type !== "labelLink") {
offset = 2;
break;
}
}
} else if (token.type === "labelEnd") {
close = index;
}
}
const group = {
type: events[open][1].type === "labelLink" ? "link" : "image",
start: {
...events[open][1].start
},
end: {
...events[events.length - 1][1].end
}
};
const label = {
type: "label",
start: {
...events[open][1].start
},
end: {
...events[close][1].end
}
};
const text = {
type: "labelText",
start: {
...events[open + offset + 2][1].end
},
end: {
...events[close - 2][1].start
}
};
media = [['enter', group, context], ['enter', label, context]];
// Opening marker.
media = push(media, events.slice(open + 1, open + offset + 3));
// Text open.
media = push(media, [['enter', text, context]]);
// Always populated by defaults.
// Between.
media = push(media, resolveAll(context.parser.constructs.insideSpan.null, events.slice(open + offset + 4, close - 3), context));
// Text close, marker close, label close.
media = push(media, [['exit', text, context], events[close - 2], events[close - 1], ['exit', label, context]]);
// Reference, resource, or so.
media = push(media, events.slice(close + 1));
// Media close.
media = push(media, [['exit', group, context]]);
splice(events, open, events.length, media);
return events;
}
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeLabelEnd(effects, ok, nok) {
const self = this;
let index = self.events.length;
/** @type {Token} */
let labelStart;
/** @type {boolean} */
let defined;
// Find an opening.
while (index--) {
if ((self.events[index][1].type === "labelImage" || self.events[index][1].type === "labelLink") && !self.events[index][1]._balanced) {
labelStart = self.events[index][1];
break;
}
}
return start;
/**
* Start of label end.
*
* ```markdown
* > | [a](b) c
* ^
* > | [a][b] c
* ^
* > | [a][] b
* ^
* > | [a] b
* ```
*
* @type {State}
*/
function start(code) {
// If there is not an okay opening.
if (!labelStart) {
return nok(code);
}
// If the corresponding label (link) start is marked as inactive,
// it means weβd be wrapping a link, like this:
//
// ```markdown
// > | a [b [c](d) e](f) g.
// ^
// ```
//
// We canβt have that, so itβs just balanced brackets.
if (labelStart._inactive) {
return labelEndNok(code);
}
defined = self.parser.defined.includes(normalizeIdentifier(self.sliceSerialize({
start: labelStart.end,
end: self.now()
})));
effects.enter("labelEnd");
effects.enter("labelMarker");
effects.consume(code);
effects.exit("labelMarker");
effects.exit("labelEnd");
return after;
}
/**
* After `]`.
*
* ```markdown
* > | [a](b) c
* ^
* > | [a][b] c
* ^
* > | [a][] b
* ^
* > | [a] b
* ^
* ```
*
* @type {State}
*/
function after(code) {
// Note: `markdown-rs` also parses GFM footnotes here, which for us is in
// an extension.
// Resource (`[asd](fgh)`)?
if (code === 40) {
return effects.attempt(resourceConstruct, labelEndOk, defined ? labelEndOk : labelEndNok)(code);
}
// Full (`[asd][fgh]`) or collapsed (`[asd][]`) reference?
if (code === 91) {
return effects.attempt(referenceFullConstruct, labelEndOk, defined ? referenceNotFull : labelEndNok)(code);
}
// Shortcut (`[asd]`) reference?
return defined ? labelEndOk(code) : labelEndNok(code);
}
/**
* After `]`, at `[`, but not at a full reference.
*
* > π **Note**: we only get here if the label is defined.
*
* ```markdown
* > | [a][] b
* ^
* > | [a] b
* ^
* ```
*
* @type {State}
*/
function referenceNotFull(code) {
return effects.attempt(referenceCollapsedConstruct, labelEndOk, labelEndNok)(code);
}
/**
* Done, we found something.
*
* ```markdown
* > | [a](b) c
* ^
* > | [a][b] c
* ^
* > | [a][] b
* ^
* > | [a] b
* ^
* ```
*
* @type {State}
*/
function labelEndOk(code) {
// Note: `markdown-rs` does a bunch of stuff here.
return ok(code);
}
/**
* Done, itβs nothing.
*
* There was an okay opening, but we didnβt match anything.
*
* ```markdown
* > | [a](b c
* ^
* > | [a][b c
* ^
* > | [a] b
* ^
* ```
*
* @type {State}
*/
function labelEndNok(code) {
labelStart._balanced = true;
return nok(code);
}
}
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeResource(effects, ok, nok) {
return resourceStart;
/**
* At a resource.
*
* ```markdown
* > | [a](b) c
* ^
* ```
*
* @type {State}
*/
function resourceStart(code) {
effects.enter("resource");
effects.enter("resourceMarker");
effects.consume(code);
effects.exit("resourceMarker");
return resourceBefore;
}
/**
* In resource, after `(`, at optional whitespace.
*
* ```markdown
* > | [a](b) c
* ^
* ```
*
* @type {State}
*/
function resourceBefore(code) {
return markdownLineEndingOrSpace(code) ? factoryWhitespace(effects, resourceOpen)(code) : resourceOpen(code);
}
/**
* In resource, after optional whitespace, at `)` or a destination.
*
* ```markdown
* > | [a](b) c
* ^
* ```
*
* @type {State}
*/
function resourceOpen(code) {
if (code === 41) {
return resourceEnd(code);
}
return factoryDestination(effects, resourceDestinationAfter, resourceDestinationMissing, "resourceDestination", "resourceDestinationLiteral", "resourceDestinationLiteralMarker", "resourceDestinationRaw", "resourceDestinationString", 32)(code);
}
/**
* In resource, after destination, at optional whitespace.
*
* ```markdown
* > | [a](b) c
* ^
* ```
*
* @type {State}
*/
function resourceDestinationAfter(code) {
return markdownLineEndingOrSpace(code) ? factoryWhitespace(effects, resourceBetween)(code) : resourceEnd(code);
}
/**
* At invalid destination.
*
* ```markdown
* > | [a](<<) b
* ^
* ```
*
* @type {State}
*/
function resourceDestinationMissing(code) {
return nok(code);
}
/**
* In resource, after destination and whitespace, at `(` or title.
*
* ```markdown
* > | [a](b ) c
* ^
* ```
*
* @type {State}
*/
function resourceBetween(code) {
if (code === 34 || code === 39 || code === 40) {
return factoryTitle(effects, resourceTitleAfter, nok, "resourceTitle", "resourceTitleMarker", "resourceTitleString")(code);
}
return resourceEnd(code);
}
/**
* In resource, after title, at optional whitespace.
*
* ```markdown
* > | [a](b "c") d
* ^
* ```
*
* @type {State}
*/
function resourceTitleAfter(code) {
return markdownLineEndingOrSpace(code) ? factoryWhitespace(effects, resourceEnd)(code) : resourceEnd(code);
}
/**
* In resource, at `)`.
*
* ```markdown
* > | [a](b) d
* ^
* ```
*
* @type {State}
*/
function resourceEnd(code) {
if (code === 41) {
effects.enter("resourceMarker");
effects.consume(code);
effects.exit("resourceMarker");
effects.exit("resource");
return ok;
}
return nok(code);
}
}
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeReferenceFull(effects, ok, nok) {
const self = this;
return referenceFull;
/**
* In a reference (full), at the `[`.
*
* ```markdown
* > | [a][b] d
* ^
* ```
*
* @type {State}
*/
function referenceFull(code) {
return factoryLabel.call(self, effects, referenceFullAfter, referenceFullMissing, "reference", "referenceMarker", "referenceString")(code);
}
/**
* In a reference (full), after `]`.
*
* ```markdown
* > | [a][b] d
* ^
* ```
*
* @type {State}
*/
function referenceFullAfter(code) {
return self.parser.defined.includes(normalizeIdentifier(self.sliceSerialize(self.events[self.events.length - 1][1]).slice(1, -1))) ? ok(code) : nok(code);
}
/**
* In reference (full) that was missing.
*
* ```markdown
* > | [a][b d
* ^
* ```
*
* @type {State}
*/
function referenceFullMissing(code) {
return nok(code);
}
}
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeReferenceCollapsed(effects, ok, nok) {
return referenceCollapsedStart;
/**
* In reference (collapsed), at `[`.
*
* > π **Note**: we only get here if the label is defined.
*
* ```markdown
* > | [a][] d
* ^
* ```
*
* @type {State}
*/
function referenceCollapsedStart(code) {
// We only attempt a collapsed label if thereβs a `[`.
effects.enter("reference");
effects.enter("referenceMarker");
effects.consume(code);
effects.exit("referenceMarker");
return referenceCollapsedOpen;
}
/**
* In reference (collapsed), at `]`.
*
* > π **Note**: we only get here if the label is defined.
*
* ```markdown
* > | [a][] d
* ^
* ```
*
* @type {State}
*/
function referenceCollapsedOpen(code) {
if (code === 93) {
effects.enter("referenceMarker");
effects.consume(code);
effects.exit("referenceMarker");
effects.exit("reference");
return ok;
}
return nok(code);
}
}
/**
* @import {
* Construct,
* State,
* TokenizeContext,
* Tokenizer
* } from 'micromark-util-types'
*/
/** @type {Construct} */
const labelStartImage = {
name: 'labelStartImage',
resolveAll: labelEnd.resolveAll,
tokenize: tokenizeLabelStartImage
};
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeLabelStartImage(effects, ok, nok) {
const self = this;
return start;
/**
* Start of label (image) start.
*
* ```markdown
* > | a ![b] c
* ^
* ```
*
* @type {State}
*/
function start(code) {
effects.enter("labelImage");
effects.enter("labelImageMarker");
effects.consume(code);
effects.exit("labelImageMarker");
return open;
}
/**
* After `!`, at `[`.
*
* ```markdown
* > | a ![b] c
* ^
* ```
*
* @type {State}
*/
function open(code) {
if (code === 91) {
effects.enter("labelMarker");
effects.consume(code);
effects.exit("labelMarker");
effects.exit("labelImage");
return after;
}
return nok(code);
}
/**
* After `![`.
*
* ```markdown
* > | a ![b] c
* ^
* ```
*
* This is needed in because, when GFM footnotes are enabled, images never
* form when started with a `^`.
* Instead, links form:
*
* ```markdown
* 
*
* ![^a][b]
*
* [b]: c
* ```
*
* ```html
* <p>!<a href=\"b\">^a</a></p>
* <p>!<a href=\"c\">^a</a></p>
* ```
*
* @type {State}
*/
function after(code) {
// To do: use a new field to do this, this is still needed for
// `micromark-extension-gfm-footnote`, but the `label-start-link`
// behavior isnβt.
// Hidden footnotes hook.
/* c8 ignore next 3 */
return code === 94 && '_hiddenFootnoteSupport' in self.parser.constructs ? nok(code) : ok(code);
}
}
/**
* @import {
* Construct,
* State,
* TokenizeContext,
* Tokenizer
* } from 'micromark-util-types'
*/
/** @type {Construct} */
const labelStartLink = {
name: 'labelStartLink',
resolveAll: labelEnd.resolveAll,
tokenize: tokenizeLabelStartLink
};
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeLabelStartLink(effects, ok, nok) {
const self = this;
return start;
/**
* Start of label (link) start.
*
* ```markdown
* > | a [b] c
* ^
* ```
*
* @type {State}
*/
function start(code) {
effects.enter("labelLink");
effects.enter("labelMarker");
effects.consume(code);
effects.exit("labelMarker");
effects.exit("labelLink");
return after;
}
/** @type {State} */
function after(code) {
// To do: this isnβt needed in `micromark-extension-gfm-footnote`,
// remove.
// Hidden footnotes hook.
/* c8 ignore next 3 */
return code === 94 && '_hiddenFootnoteSupport' in self.parser.constructs ? nok(code) : ok(code);
}
}
/**
* @import {
* Construct,
* State,
* TokenizeContext,
* Tokenizer
* } from 'micromark-util-types'
*/
/** @type {Construct} */
const lineEnding = {
name: 'lineEnding',
tokenize: tokenizeLineEnding
};
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeLineEnding(effects, ok) {
return start;
/** @type {State} */
function start(code) {
effects.enter("lineEnding");
effects.consume(code);
effects.exit("lineEnding");
return factorySpace(effects, ok, "linePrefix");
}
}
/**
* @import {
* Code,
* Construct,
* State,
* TokenizeContext,
* Tokenizer
* } from 'micromark-util-types'
*/
/** @type {Construct} */
const thematicBreak$1 = {
name: 'thematicBreak',
tokenize: tokenizeThematicBreak
};
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeThematicBreak(effects, ok, nok) {
let size = 0;
/** @type {NonNullable<Code>} */
let marker;
return start;
/**
* Start of thematic break.
*
* ```markdown
* > | ***
* ^
* ```
*
* @type {State}
*/
function start(code) {
effects.enter("thematicBreak");
// To do: parse indent like `markdown-rs`.
return before(code);
}
/**
* After optional whitespace, at marker.
*
* ```markdown
* > | ***
* ^
* ```
*
* @type {State}
*/
function before(code) {
marker = code;
return atBreak(code);
}
/**
* After something, before something else.
*
* ```markdown
* > | ***
* ^
* ```
*
* @type {State}
*/
function atBreak(code) {
if (code === marker) {
effects.enter("thematicBreakSequence");
return sequence(code);
}
if (size >= 3 && (code === null || markdownLineEnding(code))) {
effects.exit("thematicBreak");
return ok(code);
}
return nok(code);
}
/**
* In sequence.
*
* ```markdown
* > | ***
* ^
* ```
*
* @type {State}
*/
function sequence(code) {
if (code === marker) {
effects.consume(code);
size++;
return sequence;
}
effects.exit("thematicBreakSequence");
return markdownSpace(code) ? factorySpace(effects, atBreak, "whitespace")(code) : atBreak(code);
}
}
/**
* @import {
* Code,
* Construct,
* Exiter,
* State,
* TokenizeContext,
* Tokenizer
* } from 'micromark-util-types'
*/
/** @type {Construct} */
const list$2 = {
continuation: {
tokenize: tokenizeListContinuation
},
exit: tokenizeListEnd,
name: 'list',
tokenize: tokenizeListStart
};
/** @type {Construct} */
const listItemPrefixWhitespaceConstruct = {
partial: true,
tokenize: tokenizeListItemPrefixWhitespace
};
/** @type {Construct} */
const indentConstruct = {
partial: true,
tokenize: tokenizeIndent
};
// To do: `markdown-rs` parses list items on their own and later stitches them
// together.
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeListStart(effects, ok, nok) {
const self = this;
const tail = self.events[self.events.length - 1];
let initialSize = tail && tail[1].type === "linePrefix" ? tail[2].sliceSerialize(tail[1], true).length : 0;
let size = 0;
return start;
/** @type {State} */
function start(code) {
const kind = self.containerState.type || (code === 42 || code === 43 || code === 45 ? "listUnordered" : "listOrdered");
if (kind === "listUnordered" ? !self.containerState.marker || code === self.containerState.marker : asciiDigit(code)) {
if (!self.containerState.type) {
self.containerState.type = kind;
effects.enter(kind, {
_container: true
});
}
if (kind === "listUnordered") {
effects.enter("listItemPrefix");
return code === 42 || code === 45 ? effects.check(thematicBreak$1, nok, atMarker)(code) : atMarker(code);
}
if (!self.interrupt || code === 49) {
effects.enter("listItemPrefix");
effects.enter("listItemValue");
return inside(code);
}
}
return nok(code);
}
/** @type {State} */
function inside(code) {
if (asciiDigit(code) && ++size < 10) {
effects.consume(code);
return inside;
}
if ((!self.interrupt || size < 2) && (self.containerState.marker ? code === self.containerState.marker : code === 41 || code === 46)) {
effects.exit("listItemValue");
return atMarker(code);
}
return nok(code);
}
/**
* @type {State}
**/
function atMarker(code) {
effects.enter("listItemMarker");
effects.consume(code);
effects.exit("listItemMarker");
self.containerState.marker = self.containerState.marker || code;
return effects.check(blankLine,
// Canβt be empty when interrupting.
self.interrupt ? nok : onBlank, effects.attempt(listItemPrefixWhitespaceConstruct, endOfPrefix, otherPrefix));
}
/** @type {State} */
function onBlank(code) {
self.containerState.initialBlankLine = true;
initialSize++;
return endOfPrefix(code);
}
/** @type {State} */
function otherPrefix(code) {
if (markdownSpace(code)) {
effects.enter("listItemPrefixWhitespace");
effects.consume(code);
effects.exit("listItemPrefixWhitespace");
return endOfPrefix;
}
return nok(code);
}
/** @type {State} */
function endOfPrefix(code) {
self.containerState.size = initialSize + self.sliceSerialize(effects.exit("listItemPrefix"), true).length;
return ok(code);
}
}
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeListContinuation(effects, ok, nok) {
const self = this;
self.containerState._closeFlow = undefined;
return effects.check(blankLine, onBlank, notBlank);
/** @type {State} */
function onBlank(code) {
self.containerState.furtherBlankLines = self.containerState.furtherBlankLines || self.containerState.initialBlankLine;
// We have a blank line.
// Still, try to consume at most the items size.
return factorySpace(effects, ok, "listItemIndent", self.containerState.size + 1)(code);
}
/** @type {State} */
function notBlank(code) {
if (self.containerState.furtherBlankLines || !markdownSpace(code)) {
self.containerState.furtherBlankLines = undefined;
self.containerState.initialBlankLine = undefined;
return notInCurrentItem(code);
}
self.containerState.furtherBlankLines = undefined;
self.containerState.initialBlankLine = undefined;
return effects.attempt(indentConstruct, ok, notInCurrentItem)(code);
}
/** @type {State} */
function notInCurrentItem(code) {
// While we do continue, we signal that the flow should be closed.
self.containerState._closeFlow = true;
// As weβre closing flow, weβre no longer interrupting.
self.interrupt = undefined;
// Always populated by defaults.
return factorySpace(effects, effects.attempt(list$2, ok, nok), "linePrefix", self.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4)(code);
}
}
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeIndent(effects, ok, nok) {
const self = this;
return factorySpace(effects, afterPrefix, "listItemIndent", self.containerState.size + 1);
/** @type {State} */
function afterPrefix(code) {
const tail = self.events[self.events.length - 1];
return tail && tail[1].type === "listItemIndent" && tail[2].sliceSerialize(tail[1], true).length === self.containerState.size ? ok(code) : nok(code);
}
}
/**
* @this {TokenizeContext}
* Context.
* @type {Exiter}
*/
function tokenizeListEnd(effects) {
effects.exit(this.containerState.type);
}
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeListItemPrefixWhitespace(effects, ok, nok) {
const self = this;
// Always populated by defaults.
return factorySpace(effects, afterPrefix, "listItemPrefixWhitespace", self.parser.constructs.disable.null.includes('codeIndented') ? undefined : 4 + 1);
/** @type {State} */
function afterPrefix(code) {
const tail = self.events[self.events.length - 1];
return !markdownSpace(code) && tail && tail[1].type === "listItemPrefixWhitespace" ? ok(code) : nok(code);
}
}
/**
* @import {
* Code,
* Construct,
* Resolver,
* State,
* TokenizeContext,
* Tokenizer
* } from 'micromark-util-types'
*/
/** @type {Construct} */
const setextUnderline = {
name: 'setextUnderline',
resolveTo: resolveToSetextUnderline,
tokenize: tokenizeSetextUnderline
};
/** @type {Resolver} */
function resolveToSetextUnderline(events, context) {
// To do: resolve like `markdown-rs`.
let index = events.length;
/** @type {number | undefined} */
let content;
/** @type {number | undefined} */
let text;
/** @type {number | undefined} */
let definition;
// Find the opening of the content.
// Itβll always exist: we donβt tokenize if it isnβt there.
while (index--) {
if (events[index][0] === 'enter') {
if (events[index][1].type === "content") {
content = index;
break;
}
if (events[index][1].type === "paragraph") {
text = index;
}
}
// Exit
else {
if (events[index][1].type === "content") {
// Remove the content end (if needed weβll add it later)
events.splice(index, 1);
}
if (!definition && events[index][1].type === "definition") {
definition = index;
}
}
}
const heading = {
type: "setextHeading",
start: {
...events[content][1].start
},
end: {
...events[events.length - 1][1].end
}
};
// Change the paragraph to setext heading text.
events[text][1].type = "setextHeadingText";
// If we have definitions in the content, weβll keep on having content,
// but we need move it.
if (definition) {
events.splice(text, 0, ['enter', heading, context]);
events.splice(definition + 1, 0, ['exit', events[content][1], context]);
events[content][1].end = {
...events[definition][1].end
};
} else {
events[content][1] = heading;
}
// Add the heading exit at the end.
events.push(['exit', heading, context]);
return events;
}
/**
* @this {TokenizeContext}
* Context.
* @type {Tokenizer}
*/
function tokenizeSetextUnderline(effects, ok, nok) {
const self = this;
/** @type {NonNullable<Code>} */
let marker;
return start;
/**
* At start of heading (setext) underline.
*
* ```markdown
* | aa
* > | ==
* ^
* ```
*
* @type {State}
*/
function start(code) {
let index = self.events.length;
/** @type {boolean | undefined} */
let paragraph;
// Find an opening.
while (index--) {
// Skip enter/exit of line ending, line prefix, and content.
// We can now either have a definition or a paragraph.
if (self.events[index][1].type !== "lineEnding" && self.events[index][1].type !== "linePrefix" && self.events[index][1].type !== "content") {
paragraph = self.events[index][1].type === "paragraph";
break;
}
}
// To do: handle lazy/pierce like `markdown-rs`.
// To do: parse indent like `markdown-rs`.
if (!self.parser.lazy[self.now().line] && (self.interrupt || paragraph)) {
effects.enter("setextHeadingLine");
marker = code;
return before(code);
}
return nok(code);
}
/**
* After optional whitespace, at `-` or `=`.
*
* ```markdown
* | aa
* > | ==
* ^
* ```
*
* @type {State}
*/
function before(code) {
effects.enter("setextHeadingLineSequence");
return inside(code);
}
/**
* In sequence.
*
* ```markdown
* | aa
* > | ==
* ^
* ```
*
* @type {State}
*/
function inside(code) {
if (code === marker) {
effects.consume(code);
return inside;
}
effects.exit("setextHeadingLineSequence");
return markdownSpace(code) ? factorySpace(effects, after, "lineSuffix")(code) : after(code);
}
/**
* After sequence, after optional whitespace.
*
* ```markdown
* | aa
* > | ==
* ^
* ```
*
* @type {State}
*/
function after(code) {
if (code === null || markdownLineEnding(code)) {
effects.exit("setextHeadingLine");
return ok(code);
}
return nok(code);
}
}
/**
* @import {
* InitialConstruct,
* Initializer,
* State,
* TokenizeContext
* } from 'micromark-util-types'
*/
/** @type {InitialConstruct} */
const flow$1 = {
tokenize: initializeFlow
};
/**
* @this {TokenizeContext}
* Self.
* @type {Initializer}
* Initializer.
*/
function initializeFlow(effects) {
const self = this;
const initial = effects.attempt(
// Try to parse a blank line.
blankLine, atBlankEnding,
// Try to parse initial flow (essentially, only code).
effects.attempt(this.parser.constructs.flowInitial, afterConstruct, factorySpace(effects, effects.attempt(this.parser.constructs.flow, afterConstruct, effects.attempt(content, afterConstruct)), "linePrefix")));
return initial;
/** @type {State} */
function atBlankEnding(code) {
if (code === null) {
effects.consume(code);
return;
}
effects.enter("lineEndingBlank");
effects.consume(code);
effects.exit("lineEndingBlank");
self.currentConstruct = undefined;
return initial;
}
/** @type {State} */
function afterConstruct(code) {
if (code === null) {
effects.consume(code);
return;
}
effects.enter("lineEnding");
effects.consume(code);
effects.exit("lineEnding");
self.currentConstruct = undefined;
return initial;
}
}
/**
* @import {
* Code,
* InitialConstruct,
* Initializer,
* Resolver,
* State,
* TokenizeContext
* } from 'micromark-util-types'
*/
const resolver = {
resolveAll: createResolver()
};
const string$1 = initializeFactory('string');
const text$2 = initializeFactory('text');
/**
* @param {'string' | 'text'} field
* Field.
* @returns {InitialConstruct}
* Construct.
*/
function initializeFactory(field) {
return {
resolveAll: createResolver(field === 'text' ? resolveAllLineSuffixes : undefined),
tokenize: initializeText
};
/**
* @this {TokenizeContext}
* Context.
* @type {Initializer}
*/
function initializeText(effects) {
const self = this;
const constructs = this.parser.constructs[field];
const text = effects.attempt(constructs, start, notText);
return start;
/** @type {State} */
function start(code) {
return atBreak(code) ? text(code) : notText(code);
}
/** @type {State} */
function notText(code) {
if (code === null) {
effects.consume(code);
return;
}
effects.enter("data");
effects.consume(code);
return data;
}
/** @type {State} */
function data(code) {
if (atBreak(code)) {
effects.exit("data");
return text(code);
}
// Data.
effects.consume(code);
return data;
}
/**
* @param {Code} code
* Code.
* @returns {boolean}
* Whether the code is a break.
*/
function atBreak(code) {
if (code === null) {
return true;
}
const list = constructs[code];
let index = -1;
if (list) {
// Always populated by defaults.
while (++index < list.length) {
const item = list[index];
if (!item.previous || item.previous.call(self, self.previous)) {
return true;
}
}
}
return false;
}
}
}
/**
* @param {Resolver | undefined} [extraResolver]
* Resolver.
* @returns {Resolver}
* Resolver.
*/
function createResolver(extraResolver) {
return resolveAllText;
/** @type {Resolver} */
function resolveAllText(events, context) {
let index = -1;
/** @type {number | undefined} */
let enter;
// A rather boring computation (to merge adjacent `data` events) which
// improves mm performance by 29%.
while (++index <= events.length) {
if (enter === undefined) {
if (events[index] && events[index][1].type === "data") {
enter = index;
index++;
}
} else if (!events[index] || events[index][1].type !== "data") {
// Donβt do anything if there is one data token.
if (index !== enter + 2) {
events[enter][1].end = events[index - 1][1].end;
events.splice(enter + 2, index - enter - 2);
index = enter + 2;
}
enter = undefined;
}
}
return extraResolver ? extraResolver(events, context) : events;
}
}
/**
* A rather ugly set of instructions which again looks at chunks in the input
* stream.
* The reason to do this here is that it is *much* faster to parse in reverse.
* And that we canβt hook into `null` to split the line suffix before an EOF.
* To do: figure out if we can make this into a clean utility, or even in core.
* As it will be useful for GFMs literal autolink extension (and maybe even
* tables?)
*
* @type {Resolver}
*/
function resolveAllLineSuffixes(events, context) {
let eventIndex = 0; // Skip first.
while (++eventIndex <= events.length) {
if ((eventIndex === events.length || events[eventIndex][1].type === "lineEnding") && events[eventIndex - 1][1].type === "data") {
const data = events[eventIndex - 1][1];
const chunks = context.sliceStream(data);
let index = chunks.length;
let bufferIndex = -1;
let size = 0;
/** @type {boolean | undefined} */
let tabs;
while (index--) {
const chunk = chunks[index];
if (typeof chunk === 'string') {
bufferIndex = chunk.length;
while (chunk.charCodeAt(bufferIndex - 1) === 32) {
size++;
bufferIndex--;
}
if (bufferIndex) break;
bufferIndex = -1;
}
// Number
else if (chunk === -2) {
tabs = true;
size++;
} else if (chunk === -1) ; else {
// Replacement character, exit.
index++;
break;
}
}
// Allow final trailing whitespace.
if (context._contentTypeTextTrailing && eventIndex === events.length) {
size = 0;
}
if (size) {
const token = {
type: eventIndex === events.length || tabs || size < 2 ? "lineSuffix" : "hardBreakTrailing",
start: {
_bufferIndex: index ? bufferIndex : data.start._bufferIndex + bufferIndex,
_index: data.start._index + index,
line: data.end.line,
column: data.end.column - size,
offset: data.end.offset - size
},
end: {
...data.end
}
};
data.end = {
...token.start
};
if (data.start.offset === data.end.offset) {
Object.assign(data, token);
} else {
events.splice(eventIndex, 0, ['enter', token, context], ['exit', token, context]);
eventIndex += 2;
}
}
eventIndex++;
}
}
return events;
}
/**
* @import {Extension} from 'micromark-util-types'
*/
/** @satisfies {Extension['document']} */
const document$1 = {
[42]: list$2,
[43]: list$2,
[45]: list$2,
[48]: list$2,
[49]: list$2,
[50]: list$2,
[51]: list$2,
[52]: list$2,
[53]: list$2,
[54]: list$2,
[55]: list$2,
[56]: list$2,
[57]: list$2,
[62]: blockQuote
};
/** @satisfies {Extension['contentInitial']} */
const contentInitial = {
[91]: definition$1
};
/** @satisfies {Extension['flowInitial']} */
const flowInitial = {
[-2]: codeIndented,
[-1]: codeIndented,
[32]: codeIndented
};
/** @satisfies {Extension['flow']} */
const flow = {
[35]: headingAtx,
[42]: thematicBreak$1,
[45]: [setextUnderline, thematicBreak$1],
[60]: htmlFlow,
[61]: setextUnderline,
[95]: thematicBreak$1,
[96]: codeFenced,
[126]: codeFenced
};
/** @satisfies {Extension['string']} */
const string = {
[38]: characterReference,
[92]: characterEscape
};
/** @satisfies {Extension['text']} */
const text$1 = {
[-5]: lineEnding,
[-4]: lineEnding,
[-3]: lineEnding,
[33]: labelStartImage,
[38]: characterReference,
[42]: attention,
[60]: [autolink, htmlText],
[91]: labelStartLink,
[92]: [hardBreakEscape, characterEscape],
[93]: labelEnd,
[95]: attention,
[96]: codeText
};
/** @satisfies {Extension['insideSpan']} */
const insideSpan = {
null: [attention, resolver]
};
/** @satisfies {Extension['attentionMarkers']} */
const attentionMarkers = {
null: [42, 95]
};
/** @satisfies {Extension['disable']} */
const disable = {
null: []
};
const defaultConstructs = /*#__PURE__*/Object.freeze(/*#__PURE__*/Object.defineProperty({
__proto__: null,
attentionMarkers,
contentInitial,
disable,
document: document$1,
flow,
flowInitial,
insideSpan,
string,
text: text$1
}, Symbol.toStringTag, { value: 'Module' }));
/**
* @import {
* Chunk,
* Code,
* ConstructRecord,
* Construct,
* Effects,
* InitialConstruct,
* ParseContext,
* Point,
* State,
* TokenizeContext,
* Token
* } from 'micromark-util-types'
*/
/**
* Create a tokenizer.
* Tokenizers deal with one type of data (e.g., containers, flow, text).
* The parser is the object dealing with it all.
* `initialize` works like other constructs, except that only its `tokenize`
* function is used, in which case it doesnβt receive an `ok` or `nok`.
* `from` can be given to set the point before the first character, although
* when further lines are indented, they must be set with `defineSkip`.
*
* @param {ParseContext} parser
* Parser.
* @param {InitialConstruct} initialize
* Construct.
* @param {Omit<Point, '_bufferIndex' | '_index'> | undefined} [from]
* Point (optional).
* @returns {TokenizeContext}
* Context.
*/
function createTokenizer(parser, initialize, from) {
/** @type {Point} */
let point = {
_bufferIndex: -1,
_index: 0,
line: from && from.line || 1,
column: from && from.column || 1,
offset: from && from.offset || 0
};
/** @type {Record<string, number>} */
const columnStart = {};
/** @type {Array<Construct>} */
const resolveAllConstructs = [];
/** @type {Array<Chunk>} */
let chunks = [];
/** @type {Array<Token>} */
let stack = [];
/**
* Tools used for tokenizing.
*
* @type {Effects}
*/
const effects = {
attempt: constructFactory(onsuccessfulconstruct),
check: constructFactory(onsuccessfulcheck),
consume,
enter,
exit,
interrupt: constructFactory(onsuccessfulcheck, {
interrupt: true
})
};
/**
* State and tools for resolving and serializing.
*
* @type {TokenizeContext}
*/
const context = {
code: null,
containerState: {},
defineSkip,
events: [],
now,
parser,
previous: null,
sliceSerialize,
sliceStream,
write
};
/**
* The state function.
*
* @type {State | undefined}
*/
let state = initialize.tokenize.call(context, effects);
if (initialize.resolveAll) {
resolveAllConstructs.push(initialize);
}
return context;
/** @type {TokenizeContext['write']} */
function write(slice) {
chunks = push(chunks, slice);
main();
// Exit if weβre not done, resolve might change stuff.
if (chunks[chunks.length - 1] !== null) {
return [];
}
addResult(initialize, 0);
// Otherwise, resolve, and exit.
context.events = resolveAll(resolveAllConstructs, context.events, context);
return context.events;
}
//
// Tools.
//
/** @type {TokenizeContext['sliceSerialize']} */
function sliceSerialize(token, expandTabs) {
return serializeChunks(sliceStream(token), expandTabs);
}
/** @type {TokenizeContext['sliceStream']} */
function sliceStream(token) {
return sliceChunks(chunks, token);
}
/** @type {TokenizeContext['now']} */
function now() {
// This is a hot path, so we clone manually instead of `Object.assign({}, point)`
const {
_bufferIndex,
_index,
line,
column,
offset
} = point;
return {
_bufferIndex,
_index,
line,
column,
offset
};
}
/** @type {TokenizeContext['defineSkip']} */
function defineSkip(value) {
columnStart[value.line] = value.column;
accountForPotentialSkip();
}
//
// State management.
//
/**
* Main loop (note that `_index` and `_bufferIndex` in `point` are modified by
* `consume`).
* Here is where we walk through the chunks, which either include strings of
* several characters, or numerical character codes.
* The reason to do this in a loop instead of a call is so the stack can
* drain.
*
* @returns {undefined}
* Nothing.
*/
function main() {
/** @type {number} */
let chunkIndex;
while (point._index < chunks.length) {
const chunk = chunks[point._index];
// If weβre in a buffer chunk, loop through it.
if (typeof chunk === 'string') {
chunkIndex = point._index;
if (point._bufferIndex < 0) {
point._bufferIndex = 0;
}
while (point._index === chunkIndex && point._bufferIndex < chunk.length) {
go(chunk.charCodeAt(point._bufferIndex));
}
} else {
go(chunk);
}
}
}
/**
* Deal with one code.
*
* @param {Code} code
* Code.
* @returns {undefined}
* Nothing.
*/
function go(code) {
state = state(code);
}
/** @type {Effects['consume']} */
function consume(code) {
if (markdownLineEnding(code)) {
point.line++;
point.column = 1;
point.offset += code === -3 ? 2 : 1;
accountForPotentialSkip();
} else if (code !== -1) {
point.column++;
point.offset++;
}
// Not in a string chunk.
if (point._bufferIndex < 0) {
point._index++;
} else {
point._bufferIndex++;
// At end of string chunk.
if (point._bufferIndex ===
// Points w/ non-negative `_bufferIndex` reference
// strings.
/** @type {string} */
chunks[point._index].length) {
point._bufferIndex = -1;
point._index++;
}
}
// Expose the previous character.
context.previous = code;
}
/** @type {Effects['enter']} */
function enter(type, fields) {
/** @type {Token} */
// @ts-expect-error Patch instead of assign required fields to help GC.
const token = fields || {};
token.type = type;
token.start = now();
context.events.push(['enter', token, context]);
stack.push(token);
return token;
}
/** @type {Effects['exit']} */
function exit(type) {
const token = stack.pop();
token.end = now();
context.events.push(['exit', token, context]);
return token;
}
/**
* Use results.
*
* @type {ReturnHandle}
*/
function onsuccessfulconstruct(construct, info) {
addResult(construct, info.from);
}
/**
* Discard results.
*
* @type {ReturnHandle}
*/
function onsuccessfulcheck(_, info) {
info.restore();
}
/**
* Factory to attempt/check/interrupt.
*
* @param {ReturnHandle} onreturn
* Callback.
* @param {{interrupt?: boolean | undefined} | undefined} [fields]
* Fields.
*/
function constructFactory(onreturn, fields) {
return hook;
/**
* Handle either an object mapping codes to constructs, a list of
* constructs, or a single construct.
*
* @param {Array<Construct> | ConstructRecord | Construct} constructs
* Constructs.
* @param {State} returnState
* State.
* @param {State | undefined} [bogusState]
* State.
* @returns {State}
* State.
*/
function hook(constructs, returnState, bogusState) {
/** @type {ReadonlyArray<Construct>} */
let listOfConstructs;
/** @type {number} */
let constructIndex;
/** @type {Construct} */
let currentConstruct;
/** @type {Info} */
let info;
return Array.isArray(constructs) ? /* c8 ignore next 1 */
handleListOfConstructs(constructs) : 'tokenize' in constructs ?
// Looks like a construct.
handleListOfConstructs([(/** @type {Construct} */constructs)]) : handleMapOfConstructs(constructs);
/**
* Handle a list of construct.
*
* @param {ConstructRecord} map
* Constructs.
* @returns {State}
* State.
*/
function handleMapOfConstructs(map) {
return start;
/** @type {State} */
function start(code) {
const left = code !== null && map[code];
const all = code !== null && map.null;
const list = [
// To do: add more extension tests.
/* c8 ignore next 2 */
...(Array.isArray(left) ? left : left ? [left] : []), ...(Array.isArray(all) ? all : all ? [all] : [])];
return handleListOfConstructs(list)(code);
}
}
/**
* Handle a list of construct.
*
* @param {ReadonlyArray<Construct>} list
* Constructs.
* @returns {State}
* State.
*/
function handleListOfConstructs(list) {
listOfConstructs = list;
constructIndex = 0;
if (list.length === 0) {
return bogusState;
}
return handleConstruct(list[constructIndex]);
}
/**
* Handle a single construct.
*
* @param {Construct} construct
* Construct.
* @returns {State}
* State.
*/
function handleConstruct(construct) {
return start;
/** @type {State} */
function start(code) {
// To do: not needed to store if there is no bogus state, probably?
// Currently doesnβt work because `inspect` in document does a check
// w/o a bogus, which doesnβt make sense. But it does seem to help perf
// by not storing.
info = store();
currentConstruct = construct;
if (!construct.partial) {
context.currentConstruct = construct;
}
// Always populated by defaults.
if (construct.name && context.parser.constructs.disable.null.includes(construct.name)) {
return nok();
}
return construct.tokenize.call(
// If we do have fields, create an object w/ `context` as its
// prototype.
// This allows a βlive bindingβ, which is needed for `interrupt`.
fields ? Object.assign(Object.create(context), fields) : context, effects, ok, nok)(code);
}
}
/** @type {State} */
function ok(code) {
onreturn(currentConstruct, info);
return returnState;
}
/** @type {State} */
function nok(code) {
info.restore();
if (++constructIndex < listOfConstructs.length) {
return handleConstruct(listOfConstructs[constructIndex]);
}
return bogusState;
}
}
}
/**
* @param {Construct} construct
* Construct.
* @param {number} from
* From.
* @returns {undefined}
* Nothing.
*/
function addResult(construct, from) {
if (construct.resolveAll && !resolveAllConstructs.includes(construct)) {
resolveAllConstructs.push(construct);
}
if (construct.resolve) {
splice(context.events, from, context.events.length - from, construct.resolve(context.events.slice(from), context));
}
if (construct.resolveTo) {
context.events = construct.resolveTo(context.events, context);
}
}
/**
* Store state.
*
* @returns {Info}
* Info.
*/
function store() {
const startPoint = now();
const startPrevious = context.previous;
const startCurrentConstruct = context.currentConstruct;
const startEventsIndex = context.events.length;
const startStack = Array.from(stack);
return {
from: startEventsIndex,
restore
};
/**
* Restore state.
*
* @returns {undefined}
* Nothing.
*/
function restore() {
point = startPoint;
context.previous = startPrevious;
context.currentConstruct = startCurrentConstruct;
context.events.length = startEventsIndex;
stack = startStack;
accountForPotentialSkip();
}
}
/**
* Move the current point a bit forward in the line when itβs on a column
* skip.
*
* @returns {undefined}
* Nothing.
*/
function accountForPotentialSkip() {
if (point.line in columnStart && point.column < 2) {
point.column = columnStart[point.line];
point.offset += columnStart[point.line] - 1;
}
}
}
/**
* Get the chunks from a slice of chunks in the range of a token.
*
* @param {ReadonlyArray<Chunk>} chunks
* Chunks.
* @param {Pick<Token, 'end' | 'start'>} token
* Token.
* @returns {Array<Chunk>}
* Chunks.
*/
function sliceChunks(chunks, token) {
const startIndex = token.start._index;
const startBufferIndex = token.start._bufferIndex;
const endIndex = token.end._index;
const endBufferIndex = token.end._bufferIndex;
/** @type {Array<Chunk>} */
let view;
if (startIndex === endIndex) {
// @ts-expect-error `_bufferIndex` is used on string chunks.
view = [chunks[startIndex].slice(startBufferIndex, endBufferIndex)];
} else {
view = chunks.slice(startIndex, endIndex);
if (startBufferIndex > -1) {
const head = view[0];
if (typeof head === 'string') {
view[0] = head.slice(startBufferIndex);
/* c8 ignore next 4 -- used to be used, no longer */
} else {
view.shift();
}
}
if (endBufferIndex > 0) {
// @ts-expect-error `_bufferIndex` is used on string chunks.
view.push(chunks[endIndex].slice(0, endBufferIndex));
}
}
return view;
}
/**
* Get the string value of a slice of chunks.
*
* @param {ReadonlyArray<Chunk>} chunks
* Chunks.
* @param {boolean | undefined} [expandTabs=false]
* Whether to expand tabs (default: `false`).
* @returns {string}
* Result.
*/
function serializeChunks(chunks, expandTabs) {
let index = -1;
/** @type {Array<string>} */
const result = [];
/** @type {boolean | undefined} */
let atTab;
while (++index < chunks.length) {
const chunk = chunks[index];
/** @type {string} */
let value;
if (typeof chunk === 'string') {
value = chunk;
} else switch (chunk) {
case -5:
{
value = "\r";
break;
}
case -4:
{
value = "\n";
break;
}
case -3:
{
value = "\r" + "\n";
break;
}
case -2:
{
value = expandTabs ? " " : "\t";
break;
}
case -1:
{
if (!expandTabs && atTab) continue;
value = " ";
break;
}
default:
{
// Currently only replacement character.
value = String.fromCharCode(chunk);
}
}
atTab = chunk === -2;
result.push(value);
}
return result.join('');
}
/**
* @import {
* Create,
* FullNormalizedExtension,
* InitialConstruct,
* ParseContext,
* ParseOptions
* } from 'micromark-util-types'
*/
/**
* @param {ParseOptions | null | undefined} [options]
* Configuration (optional).
* @returns {ParseContext}
* Parser.
*/
function parse(options) {
const settings = options || {};
const constructs = /** @type {FullNormalizedExtension} */
combineExtensions([defaultConstructs, ...(settings.extensions || [])]);
/** @type {ParseContext} */
const parser = {
constructs,
content: create(content$1),
defined: [],
document: create(document$2),
flow: create(flow$1),
lazy: {},
string: create(string$1),
text: create(text$2)
};
return parser;
/**
* @param {InitialConstruct} initial
* Construct to start with.
* @returns {Create}
* Create a tokenizer.
*/
function create(initial) {
return creator;
/** @type {Create} */
function creator(from) {
return createTokenizer(parser, initial, from);
}
}
}
function postprocess(events) {
while (!subtokenize(events)) {
}
return events;
}
const search = /[\0\t\n\r]/g;
function preprocess() {
let column = 1;
let buffer = "";
let start = true;
let atCarriageReturn;
return preprocessor;
function preprocessor(value, encoding, end) {
const chunks = [];
let match;
let next;
let startPosition;
let endPosition;
let code;
value = buffer + (typeof value === "string" ? value.toString() : new TextDecoder(encoding || void 0).decode(value));
startPosition = 0;
buffer = "";
if (start) {
if (value.charCodeAt(0) === 65279) {
startPosition++;
}
start = void 0;
}
while (startPosition < value.length) {
search.lastIndex = startPosition;
match = search.exec(value);
endPosition = match && match.index !== void 0 ? match.index : value.length;
code = value.charCodeAt(endPosition);
if (!match) {
buffer = value.slice(startPosition);
break;
}
if (code === 10 && startPosition === endPosition && atCarriageReturn) {
chunks.push(-3);
atCarriageReturn = void 0;
} else {
if (atCarriageReturn) {
chunks.push(-5);
atCarriageReturn = void 0;
}
if (startPosition < endPosition) {
chunks.push(value.slice(startPosition, endPosition));
column += endPosition - startPosition;
}
switch (code) {
case 0: {
chunks.push(65533);
column++;
break;
}
case 9: {
next = Math.ceil(column / 4) * 4;
chunks.push(-2);
while (column++ < next) chunks.push(-1);
break;
}
case 10: {
chunks.push(-4);
column = 1;
break;
}
default: {
atCarriageReturn = true;
column = 1;
}
}
}
startPosition = endPosition + 1;
}
if (end) {
if (atCarriageReturn) chunks.push(-5);
if (buffer) chunks.push(buffer);
chunks.push(null);
}
return chunks;
}
}
const characterEscapeOrReference = /\\([!-/:-@[-`{-~])|&(#(?:\d{1,7}|x[\da-f]{1,6})|[\da-z]{1,31});/gi;
/**
* Decode markdown strings (which occur in places such as fenced code info
* strings, destinations, labels, and titles).
*
* The βstringβ content type allows character escapes and -references.
* This decodes those.
*
* @param {string} value
* Value to decode.
* @returns {string}
* Decoded value.
*/
function decodeString(value) {
return value.replace(characterEscapeOrReference, decode);
}
/**
* @param {string} $0
* Match.
* @param {string} $1
* Character escape.
* @param {string} $2
* Character reference.
* @returns {string}
* Decoded value
*/
function decode($0, $1, $2) {
if ($1) {
// Escape.
return $1;
}
// Reference.
const head = $2.charCodeAt(0);
if (head === 35) {
const head = $2.charCodeAt(1);
const hex = head === 120 || head === 88;
return decodeNumericCharacterReference($2.slice(hex ? 2 : 1), hex ? 16 : 10);
}
return decodeNamedCharacterReference($2) || $0;
}
/**
* @typedef {import('unist').Node} Node
* @typedef {import('unist').Point} Point
* @typedef {import('unist').Position} Position
*/
/**
* @typedef NodeLike
* @property {string} type
* @property {PositionLike | null | undefined} [position]
*
* @typedef PointLike
* @property {number | null | undefined} [line]
* @property {number | null | undefined} [column]
* @property {number | null | undefined} [offset]
*
* @typedef PositionLike
* @property {PointLike | null | undefined} [start]
* @property {PointLike | null | undefined} [end]
*/
/**
* Serialize the positional info of a point, position (start and end points),
* or node.
*
* @param {Node | NodeLike | Point | PointLike | Position | PositionLike | null | undefined} [value]
* Node, position, or point.
* @returns {string}
* Pretty printed positional info of a node (`string`).
*
* In the format of a range `ls:cs-le:ce` (when given `node` or `position`)
* or a point `l:c` (when given `point`), where `l` stands for line, `c` for
* column, `s` for `start`, and `e` for end.
* An empty string (`''`) is returned if the given value is neither `node`,
* `position`, nor `point`.
*/
function stringifyPosition(value) {
// Nothing.
if (!value || typeof value !== 'object') {
return ''
}
// Node.
if ('position' in value || 'type' in value) {
return position(value.position)
}
// Position.
if ('start' in value || 'end' in value) {
return position(value)
}
// Point.
if ('line' in value || 'column' in value) {
return point$1(value)
}
// ?
return ''
}
/**
* @param {Point | PointLike | null | undefined} point
* @returns {string}
*/
function point$1(point) {
return index(point && point.line) + ':' + index(point && point.column)
}
/**
* @param {Position | PositionLike | null | undefined} pos
* @returns {string}
*/
function position(pos) {
return point$1(pos && pos.start) + '-' + point$1(pos && pos.end)
}
/**
* @param {number | null | undefined} value
* @returns {number}
*/
function index(value) {
return value && typeof value === 'number' ? value : 1
}
const own$2 = {}.hasOwnProperty;
function fromMarkdown(value, encoding, options) {
if (typeof encoding !== "string") {
options = encoding;
encoding = void 0;
}
return compiler(options)(postprocess(parse(options).document().write(preprocess()(value, encoding, true))));
}
function compiler(options) {
const config = {
transforms: [],
canContainEols: ["emphasis", "fragment", "heading", "paragraph", "strong"],
enter: {
autolink: opener(link),
autolinkProtocol: onenterdata,
autolinkEmail: onenterdata,
atxHeading: opener(heading),
blockQuote: opener(blockQuote),
characterEscape: onenterdata,
characterReference: onenterdata,
codeFenced: opener(codeFlow),
codeFencedFenceInfo: buffer,
codeFencedFenceMeta: buffer,
codeIndented: opener(codeFlow, buffer),
codeText: opener(codeText, buffer),
codeTextData: onenterdata,
data: onenterdata,
codeFlowValue: onenterdata,
definition: opener(definition),
definitionDestinationString: buffer,
definitionLabelString: buffer,
definitionTitleString: buffer,
emphasis: opener(emphasis),
hardBreakEscape: opener(hardBreak),
hardBreakTrailing: opener(hardBreak),
htmlFlow: opener(html, buffer),
htmlFlowData: onenterdata,
htmlText: opener(html, buffer),
htmlTextData: onenterdata,
image: opener(image),
label: buffer,
link: opener(link),
listItem: opener(listItem),
listItemValue: onenterlistitemvalue,
listOrdered: opener(list, onenterlistordered),
listUnordered: opener(list),
paragraph: opener(paragraph),
reference: onenterreference,
referenceString: buffer,
resourceDestinationString: buffer,
resourceTitleString: buffer,
setextHeading: opener(heading),
strong: opener(strong),
thematicBreak: opener(thematicBreak)
},
exit: {
atxHeading: closer(),
atxHeadingSequence: onexitatxheadingsequence,
autolink: closer(),
autolinkEmail: onexitautolinkemail,
autolinkProtocol: onexitautolinkprotocol,
blockQuote: closer(),
characterEscapeValue: onexitdata,
characterReferenceMarkerHexadecimal: onexitcharacterreferencemarker,
characterReferenceMarkerNumeric: onexitcharacterreferencemarker,
characterReferenceValue: onexitcharacterreferencevalue,
characterReference: onexitcharacterreference,
codeFenced: closer(onexitcodefenced),
codeFencedFence: onexitcodefencedfence,
codeFencedFenceInfo: onexitcodefencedfenceinfo,
codeFencedFenceMeta: onexitcodefencedfencemeta,
codeFlowValue: onexitdata,
codeIndented: closer(onexitcodeindented),
codeText: closer(onexitcodetext),
codeTextData: onexitdata,
data: onexitdata,
definition: closer(),
definitionDestinationString: onexitdefinitiondestinationstring,
definitionLabelString: onexitdefinitionlabelstring,
definitionTitleString: onexitdefinitiontitlestring,
emphasis: closer(),
hardBreakEscape: closer(onexithardbreak),
hardBreakTrailing: closer(onexithardbreak),
htmlFlow: closer(onexithtmlflow),
htmlFlowData: onexitdata,
htmlText: closer(onexithtmltext),
htmlTextData: onexitdata,
image: closer(onexitimage),
label: onexitlabel,
labelText: onexitlabeltext,
lineEnding: onexitlineending,
link: closer(onexitlink),
listItem: closer(),
listOrdered: closer(),
listUnordered: closer(),
paragraph: closer(),
referenceString: onexitreferencestring,
resourceDestinationString: onexitresourcedestinationstring,
resourceTitleString: onexitresourcetitlestring,
resource: onexitresource,
setextHeading: closer(onexitsetextheading),
setextHeadingLineSequence: onexitsetextheadinglinesequence,
setextHeadingText: onexitsetextheadingtext,
strong: closer(),
thematicBreak: closer()
}
};
configure$1(config, (options || {}).mdastExtensions || []);
const data = {};
return compile;
function compile(events) {
let tree = {
type: "root",
children: []
};
const context = {
stack: [tree],
tokenStack: [],
config,
enter,
exit,
buffer,
resume,
data
};
const listStack = [];
let index = -1;
while (++index < events.length) {
if (events[index][1].type === "listOrdered" || events[index][1].type === "listUnordered") {
if (events[index][0] === "enter") {
listStack.push(index);
} else {
const tail = listStack.pop();
index = prepareList(events, tail, index);
}
}
}
index = -1;
while (++index < events.length) {
const handler = config[events[index][0]];
if (own$2.call(handler, events[index][1].type)) {
handler[events[index][1].type].call(Object.assign({
sliceSerialize: events[index][2].sliceSerialize
}, context), events[index][1]);
}
}
if (context.tokenStack.length > 0) {
const tail = context.tokenStack[context.tokenStack.length - 1];
const handler = tail[1] || defaultOnError;
handler.call(context, void 0, tail[0]);
}
tree.position = {
start: point(events.length > 0 ? events[0][1].start : {
line: 1,
column: 1,
offset: 0
}),
end: point(events.length > 0 ? events[events.length - 2][1].end : {
line: 1,
column: 1,
offset: 0
})
};
index = -1;
while (++index < config.transforms.length) {
tree = config.transforms[index](tree) || tree;
}
return tree;
}
function prepareList(events, start, length) {
let index = start - 1;
let containerBalance = -1;
let listSpread = false;
let listItem2;
let lineIndex;
let firstBlankLineIndex;
let atMarker;
while (++index <= length) {
const event = events[index];
switch (event[1].type) {
case "listUnordered":
case "listOrdered":
case "blockQuote": {
if (event[0] === "enter") {
containerBalance++;
} else {
containerBalance--;
}
atMarker = void 0;
break;
}
case "lineEndingBlank": {
if (event[0] === "enter") {
if (listItem2 && !atMarker && !containerBalance && !firstBlankLineIndex) {
firstBlankLineIndex = index;
}
atMarker = void 0;
}
break;
}
case "linePrefix":
case "listItemValue":
case "listItemMarker":
case "listItemPrefix":
case "listItemPrefixWhitespace": {
break;
}
default: {
atMarker = void 0;
}
}
if (!containerBalance && event[0] === "enter" && event[1].type === "listItemPrefix" || containerBalance === -1 && event[0] === "exit" && (event[1].type === "listUnordered" || event[1].type === "listOrdered")) {
if (listItem2) {
let tailIndex = index;
lineIndex = void 0;
while (tailIndex--) {
const tailEvent = events[tailIndex];
if (tailEvent[1].type === "lineEnding" || tailEvent[1].type === "lineEndingBlank") {
if (tailEvent[0] === "exit") continue;
if (lineIndex) {
events[lineIndex][1].type = "lineEndingBlank";
listSpread = true;
}
tailEvent[1].type = "lineEnding";
lineIndex = tailIndex;
} else if (tailEvent[1].type === "linePrefix" || tailEvent[1].type === "blockQuotePrefix" || tailEvent[1].type === "blockQuotePrefixWhitespace" || tailEvent[1].type === "blockQuoteMarker" || tailEvent[1].type === "listItemIndent") ; else {
break;
}
}
if (firstBlankLineIndex && (!lineIndex || firstBlankLineIndex < lineIndex)) {
listItem2._spread = true;
}
listItem2.end = Object.assign({}, lineIndex ? events[lineIndex][1].start : event[1].end);
events.splice(lineIndex || index, 0, ["exit", listItem2, event[2]]);
index++;
length++;
}
if (event[1].type === "listItemPrefix") {
const item = {
type: "listItem",
_spread: false,
start: Object.assign({}, event[1].start),
// @ts-expect-error: weβll add `end` in a second.
end: void 0
};
listItem2 = item;
events.splice(index, 0, ["enter", item, event[2]]);
index++;
length++;
firstBlankLineIndex = void 0;
atMarker = true;
}
}
}
events[start][1]._spread = listSpread;
return length;
}
function opener(create, and) {
return open;
function open(token) {
enter.call(this, create(token), token);
if (and) and.call(this, token);
}
}
function buffer() {
this.stack.push({
type: "fragment",
children: []
});
}
function enter(node, token, errorHandler) {
const parent = this.stack[this.stack.length - 1];
const siblings = parent.children;
siblings.push(node);
this.stack.push(node);
this.tokenStack.push([token, errorHandler || void 0]);
node.position = {
start: point(token.start),
// @ts-expect-error: `end` will be patched later.
end: void 0
};
}
function closer(and) {
return close;
function close(token) {
if (and) and.call(this, token);
exit.call(this, token);
}
}
function exit(token, onExitError) {
const node = this.stack.pop();
const open = this.tokenStack.pop();
if (!open) {
throw new Error("Cannot close `" + token.type + "` (" + stringifyPosition({
start: token.start,
end: token.end
}) + "): itβs not open");
} else if (open[0].type !== token.type) {
if (onExitError) {
onExitError.call(this, token, open[0]);
} else {
const handler = open[1] || defaultOnError;
handler.call(this, token, open[0]);
}
}
node.position.end = point(token.end);
}
function resume() {
return toString(this.stack.pop());
}
function onenterlistordered() {
this.data.expectingFirstListItemValue = true;
}
function onenterlistitemvalue(token) {
if (this.data.expectingFirstListItemValue) {
const ancestor = this.stack[this.stack.length - 2];
ancestor.start = Number.parseInt(this.sliceSerialize(token), 10);
this.data.expectingFirstListItemValue = void 0;
}
}
function onexitcodefencedfenceinfo() {
const data2 = this.resume();
const node = this.stack[this.stack.length - 1];
node.lang = data2;
}
function onexitcodefencedfencemeta() {
const data2 = this.resume();
const node = this.stack[this.stack.length - 1];
node.meta = data2;
}
function onexitcodefencedfence() {
if (this.data.flowCodeInside) return;
this.buffer();
this.data.flowCodeInside = true;
}
function onexitcodefenced() {
const data2 = this.resume();
const node = this.stack[this.stack.length - 1];
node.value = data2.replace(/^(\r?\n|\r)|(\r?\n|\r)$/g, "");
this.data.flowCodeInside = void 0;
}
function onexitcodeindented() {
const data2 = this.resume();
const node = this.stack[this.stack.length - 1];
node.value = data2.replace(/(\r?\n|\r)$/g, "");
}
function onexitdefinitionlabelstring(token) {
const label = this.resume();
const node = this.stack[this.stack.length - 1];
node.label = label;
node.identifier = normalizeIdentifier(this.sliceSerialize(token)).toLowerCase();
}
function onexitdefinitiontitlestring() {
const data2 = this.resume();
const node = this.stack[this.stack.length - 1];
node.title = data2;
}
function onexitdefinitiondestinationstring() {
const data2 = this.resume();
const node = this.stack[this.stack.length - 1];
node.url = data2;
}
function onexitatxheadingsequence(token) {
const node = this.stack[this.stack.length - 1];
if (!node.depth) {
const depth = this.sliceSerialize(token).length;
node.depth = depth;
}
}
function onexitsetextheadingtext() {
this.data.setextHeadingSlurpLineEnding = true;
}
function onexitsetextheadinglinesequence(token) {
const node = this.stack[this.stack.length - 1];
node.depth = this.sliceSerialize(token).codePointAt(0) === 61 ? 1 : 2;
}
function onexitsetextheading() {
this.data.setextHeadingSlurpLineEnding = void 0;
}
function onenterdata(token) {
const node = this.stack[this.stack.length - 1];
const siblings = node.children;
let tail = siblings[siblings.length - 1];
if (!tail || tail.type !== "text") {
tail = text();
tail.position = {
start: point(token.start),
// @ts-expect-error: weβll add `end` later.
end: void 0
};
siblings.push(tail);
}
this.stack.push(tail);
}
function onexitdata(token) {
const tail = this.stack.pop();
tail.value += this.sliceSerialize(token);
tail.position.end = point(token.end);
}
function onexitlineending(token) {
const context = this.stack[this.stack.length - 1];
if (this.data.atHardBreak) {
const tail = context.children[context.children.length - 1];
tail.position.end = point(token.end);
this.data.atHardBreak = void 0;
return;
}
if (!this.data.setextHeadingSlurpLineEnding && config.canContainEols.includes(context.type)) {
onenterdata.call(this, token);
onexitdata.call(this, token);
}
}
function onexithardbreak() {
this.data.atHardBreak = true;
}
function onexithtmlflow() {
const data2 = this.resume();
const node = this.stack[this.stack.length - 1];
node.value = data2;
}
function onexithtmltext() {
const data2 = this.resume();
const node = this.stack[this.stack.length - 1];
node.value = data2;
}
function onexitcodetext() {
const data2 = this.resume();
const node = this.stack[this.stack.length - 1];
node.value = data2;
}
function onexitlink() {
const node = this.stack[this.stack.length - 1];
if (this.data.inReference) {
const referenceType = this.data.referenceType || "shortcut";
node.type += "Reference";
node.referenceType = referenceType;
delete node.url;
delete node.title;
} else {
delete node.identifier;
delete node.label;
}
this.data.referenceType = void 0;
}
function onexitimage() {
const node = this.stack[this.stack.length - 1];
if (this.data.inReference) {
const referenceType = this.data.referenceType || "shortcut";
node.type += "Reference";
node.referenceType = referenceType;
delete node.url;
delete node.title;
} else {
delete node.identifier;
delete node.label;
}
this.data.referenceType = void 0;
}
function onexitlabeltext(token) {
const string = this.sliceSerialize(token);
const ancestor = this.stack[this.stack.length - 2];
ancestor.label = decodeString(string);
ancestor.identifier = normalizeIdentifier(string).toLowerCase();
}
function onexitlabel() {
const fragment = this.stack[this.stack.length - 1];
const value = this.resume();
const node = this.stack[this.stack.length - 1];
this.data.inReference = true;
if (node.type === "link") {
const children = fragment.children;
node.children = children;
} else {
node.alt = value;
}
}
function onexitresourcedestinationstring() {
const data2 = this.resume();
const node = this.stack[this.stack.length - 1];
node.url = data2;
}
function onexitresourcetitlestring() {
const data2 = this.resume();
const node = this.stack[this.stack.length - 1];
node.title = data2;
}
function onexitresource() {
this.data.inReference = void 0;
}
function onenterreference() {
this.data.referenceType = "collapsed";
}
function onexitreferencestring(token) {
const label = this.resume();
const node = this.stack[this.stack.length - 1];
node.label = label;
node.identifier = normalizeIdentifier(this.sliceSerialize(token)).toLowerCase();
this.data.referenceType = "full";
}
function onexitcharacterreferencemarker(token) {
this.data.characterReferenceType = token.type;
}
function onexitcharacterreferencevalue(token) {
const data2 = this.sliceSerialize(token);
const type = this.data.characterReferenceType;
let value;
if (type) {
value = decodeNumericCharacterReference(data2, type === "characterReferenceMarkerNumeric" ? 10 : 16);
this.data.characterReferenceType = void 0;
} else {
const result = decodeNamedCharacterReference(data2);
value = result;
}
const tail = this.stack[this.stack.length - 1];
tail.value += value;
}
function onexitcharacterreference(token) {
const tail = this.stack.pop();
tail.position.end = point(token.end);
}
function onexitautolinkprotocol(token) {
onexitdata.call(this, token);
const node = this.stack[this.stack.length - 1];
node.url = this.sliceSerialize(token);
}
function onexitautolinkemail(token) {
onexitdata.call(this, token);
const node = this.stack[this.stack.length - 1];
node.url = "mailto:" + this.sliceSerialize(token);
}
function blockQuote() {
return {
type: "blockquote",
children: []
};
}
function codeFlow() {
return {
type: "code",
lang: null,
meta: null,
value: ""
};
}
function codeText() {
return {
type: "inlineCode",
value: ""
};
}
function definition() {
return {
type: "definition",
identifier: "",
label: null,
title: null,
url: ""
};
}
function emphasis() {
return {
type: "emphasis",
children: []
};
}
function heading() {
return {
type: "heading",
// @ts-expect-error `depth` will be set later.
depth: 0,
children: []
};
}
function hardBreak() {
return {
type: "break"
};
}
function html() {
return {
type: "html",
value: ""
};
}
function image() {
return {
type: "image",
title: null,
url: "",
alt: null
};
}
function link() {
return {
type: "link",
title: null,
url: "",
children: []
};
}
function list(token) {
return {
type: "list",
ordered: token.type === "listOrdered",
start: null,
spread: token._spread,
children: []
};
}
function listItem(token) {
return {
type: "listItem",
spread: token._spread,
checked: null,
children: []
};
}
function paragraph() {
return {
type: "paragraph",
children: []
};
}
function strong() {
return {
type: "strong",
children: []
};
}
function text() {
return {
type: "text",
value: ""
};
}
function thematicBreak() {
return {
type: "thematicBreak"
};
}
}
function point(d) {
return {
line: d.line,
column: d.column,
offset: d.offset
};
}
function configure$1(combined, extensions) {
let index = -1;
while (++index < extensions.length) {
const value = extensions[index];
if (Array.isArray(value)) {
configure$1(combined, value);
} else {
extension(combined, value);
}
}
}
function extension(combined, extension2) {
let key;
for (key in extension2) {
if (own$2.call(extension2, key)) {
switch (key) {
case "canContainEols": {
const right = extension2[key];
if (right) {
combined[key].push(...right);
}
break;
}
case "transforms": {
const right = extension2[key];
if (right) {
combined[key].push(...right);
}
break;
}
case "enter":
case "exit": {
const right = extension2[key];
if (right) {
Object.assign(combined[key], right);
}
break;
}
}
}
}
}
function defaultOnError(left, right) {
if (left) {
throw new Error("Cannot close `" + left.type + "` (" + stringifyPosition({
start: left.start,
end: left.end
}) + "): a different token (`" + right.type + "`, " + stringifyPosition({
start: right.start,
end: right.end
}) + ") is open");
} else {
throw new Error("Cannot close document, a token (`" + right.type + "`, " + stringifyPosition({
start: right.start,
end: right.end
}) + ") is still open");
}
}
/**
* @typedef {import('mdast').Root} Root
* @typedef {import('mdast-util-from-markdown').Options} FromMarkdownOptions
* @typedef {import('unified').Parser<Root>} Parser
* @typedef {import('unified').Processor<Root>} Processor
*/
/**
* Aadd support for parsing from markdown.
*
* @param {Readonly<Options> | null | undefined} [options]
* Configuration (optional).
* @returns {undefined}
* Nothing.
*/
function remarkParse(options) {
/** @type {Processor} */
// @ts-expect-error: TS in JSDoc generates wrong types if `this` is typed regularly.
const self = this;
self.parser = parser;
/**
* @type {Parser}
*/
function parser(doc) {
return fromMarkdown(doc, {
...self.data('settings'),
...options,
// Note: these options are not in the readme.
// The goal is for them to be set by plugins on `data` instead of being
// passed by users.
extensions: self.data('micromarkExtensions') || [],
mdastExtensions: self.data('fromMarkdownExtensions') || []
})
}
}
/**
* @callback Handler
* Handle a value, with a certain ID field set to a certain value.
* The ID field is passed to `zwitch`, and itβs value is this functionβs
* place on the `handlers` record.
* @param {...any} parameters
* Arbitrary parameters passed to the zwitch.
* The first will be an object with a certain ID field set to a certain value.
* @returns {any}
* Anything!
*/
/**
* @callback UnknownHandler
* Handle values that do have a certain ID field, but itβs set to a value
* that is not listed in the `handlers` record.
* @param {unknown} value
* An object with a certain ID field set to an unknown value.
* @param {...any} rest
* Arbitrary parameters passed to the zwitch.
* @returns {any}
* Anything!
*/
/**
* @callback InvalidHandler
* Handle values that do not have a certain ID field.
* @param {unknown} value
* Any unknown value.
* @param {...any} rest
* Arbitrary parameters passed to the zwitch.
* @returns {void|null|undefined|never}
* This should crash or return nothing.
*/
/**
* @template {InvalidHandler} [Invalid=InvalidHandler]
* @template {UnknownHandler} [Unknown=UnknownHandler]
* @template {Record<string, Handler>} [Handlers=Record<string, Handler>]
* @typedef Options
* Configuration (required).
* @property {Invalid} [invalid]
* Handler to use for invalid values.
* @property {Unknown} [unknown]
* Handler to use for unknown values.
* @property {Handlers} [handlers]
* Handlers to use.
*/
const own$1 = {}.hasOwnProperty;
/**
* Handle values based on a field.
*
* @template {InvalidHandler} [Invalid=InvalidHandler]
* @template {UnknownHandler} [Unknown=UnknownHandler]
* @template {Record<string, Handler>} [Handlers=Record<string, Handler>]
* @param {string} key
* Field to switch on.
* @param {Options<Invalid, Unknown, Handlers>} [options]
* Configuration (required).
* @returns {{unknown: Unknown, invalid: Invalid, handlers: Handlers, (...parameters: Parameters<Handlers[keyof Handlers]>): ReturnType<Handlers[keyof Handlers]>, (...parameters: Parameters<Unknown>): ReturnType<Unknown>}}
*/
function zwitch(key, options) {
const settings = options || {};
/**
* Handle one value.
*
* Based on the bound `key`, a respective handler will be called.
* If `value` is not an object, or doesnβt have a `key` property, the special
* βinvalidβ handler will be called.
* If `value` has an unknown `key`, the special βunknownβ handler will be
* called.
*
* All arguments, and the context object, are passed through to the handler,
* and itβs result is returned.
*
* @this {unknown}
* Any context object.
* @param {unknown} [value]
* Any value.
* @param {...unknown} parameters
* Arbitrary parameters passed to the zwitch.
* @property {Handler} invalid
* Handle for values that do not have a certain ID field.
* @property {Handler} unknown
* Handle values that do have a certain ID field, but itβs set to a value
* that is not listed in the `handlers` record.
* @property {Handlers} handlers
* Record of handlers.
* @returns {unknown}
* Anything.
*/
function one(value, ...parameters) {
/** @type {Handler|undefined} */
let fn = one.invalid;
const handlers = one.handlers;
if (value && own$1.call(value, key)) {
// @ts-expect-error Indexable.
const id = String(value[key]);
// @ts-expect-error Indexable.
fn = own$1.call(handlers, id) ? handlers[id] : one.unknown;
}
if (fn) {
return fn.call(this, value, ...parameters)
}
}
one.handlers = settings.handlers || {};
one.invalid = settings.invalid;
one.unknown = settings.unknown;
// @ts-expect-error: matches!
return one
}
/**
* @import {Options, State} from './types.js'
*/
const own = {}.hasOwnProperty;
/**
* @param {State} base
* @param {Options} extension
* @returns {State}
*/
function configure(base, extension) {
let index = -1;
/** @type {keyof Options} */
let key;
// First do subextensions.
if (extension.extensions) {
while (++index < extension.extensions.length) {
configure(base, extension.extensions[index]);
}
}
for (key in extension) {
if (own.call(extension, key)) {
switch (key) {
case 'extensions': {
// Empty.
break
}
/* c8 ignore next 4 */
case 'unsafe': {
list$1(base[key], extension[key]);
break
}
case 'join': {
list$1(base[key], extension[key]);
break
}
case 'handlers': {
map$3(base[key], extension[key]);
break
}
default: {
// @ts-expect-error: matches.
base.options[key] = extension[key];
}
}
}
}
return base
}
/**
* @template T
* @param {Array<T>} left
* @param {Array<T> | null | undefined} right
*/
function list$1(left, right) {
if (right) {
left.push(...right);
}
}
/**
* @template T
* @param {Record<string, T>} left
* @param {Record<string, T> | null | undefined} right
*/
function map$3(left, right) {
if (right) {
Object.assign(left, right);
}
}
/**
* @import {Blockquote, Parents} from 'mdast'
* @import {Info, Map, State} from 'mdast-util-to-markdown'
*/
/**
* @param {Blockquote} node
* @param {Parents | undefined} _
* @param {State} state
* @param {Info} info
* @returns {string}
*/
function blockquote(node, _, state, info) {
const exit = state.enter('blockquote');
const tracker = state.createTracker(info);
tracker.move('> ');
tracker.shift(2);
const value = state.indentLines(
state.containerFlow(node, tracker.current()),
map$2
);
exit();
return value
}
/** @type {Map} */
function map$2(line, _, blank) {
return '>' + (blank ? '' : ' ') + line
}
/**
* @import {ConstructName, Unsafe} from 'mdast-util-to-markdown'
*/
/**
* @param {Array<ConstructName>} stack
* @param {Unsafe} pattern
* @returns {boolean}
*/
function patternInScope(stack, pattern) {
return (
listInScope(stack, pattern.inConstruct, true) &&
!listInScope(stack, pattern.notInConstruct, false)
)
}
/**
* @param {Array<ConstructName>} stack
* @param {Unsafe['inConstruct']} list
* @param {boolean} none
* @returns {boolean}
*/
function listInScope(stack, list, none) {
if (typeof list === 'string') {
list = [list];
}
if (!list || list.length === 0) {
return none
}
let index = -1;
while (++index < list.length) {
if (stack.includes(list[index])) {
return true
}
}
return false
}
/**
* @import {Break, Parents} from 'mdast'
* @import {Info, State} from 'mdast-util-to-markdown'
*/
/**
* @param {Break} _
* @param {Parents | undefined} _1
* @param {State} state
* @param {Info} info
* @returns {string}
*/
function hardBreak(_, _1, state, info) {
let index = -1;
while (++index < state.unsafe.length) {
// If we canβt put eols in this construct (setext headings, tables), use a
// space instead.
if (
state.unsafe[index].character === '\n' &&
patternInScope(state.stack, state.unsafe[index])
) {
return /[ \t]/.test(info.before) ? '' : ' '
}
}
return '\\\n'
}
/**
* Get the count of the longest repeating streak of `substring` in `value`.
*
* @param {string} value
* Content to search in.
* @param {string} substring
* Substring to look for, typically one character.
* @returns {number}
* Count of most frequent adjacent `substring`s in `value`.
*/
function longestStreak(value, substring) {
const source = String(value);
let index = source.indexOf(substring);
let expected = index;
let count = 0;
let max = 0;
if (typeof substring !== 'string') {
throw new TypeError('Expected substring')
}
while (index !== -1) {
if (index === expected) {
if (++count > max) {
max = count;
}
} else {
count = 1;
}
expected = index + substring.length;
index = source.indexOf(substring, expected);
}
return max
}
/**
* @import {State} from 'mdast-util-to-markdown'
* @import {Code} from 'mdast'
*/
/**
* @param {Code} node
* @param {State} state
* @returns {boolean}
*/
function formatCodeAsIndented(node, state) {
return Boolean(
state.options.fences === false &&
node.value &&
// If thereβs no infoβ¦
!node.lang &&
// And thereβs a non-whitespace characterβ¦
/[^ \r\n]/.test(node.value) &&
// And the value doesnβt start or end in a blankβ¦
!/^[\t ]*(?:[\r\n]|$)|(?:^|[\r\n])[\t ]*$/.test(node.value)
)
}
/**
* @import {Options, State} from 'mdast-util-to-markdown'
*/
/**
* @param {State} state
* @returns {Exclude<Options['fence'], null | undefined>}
*/
function checkFence(state) {
const marker = state.options.fence || '`';
if (marker !== '`' && marker !== '~') {
throw new Error(
'Cannot serialize code with `' +
marker +
'` for `options.fence`, expected `` ` `` or `~`'
)
}
return marker
}
/**
* @import {Info, Map, State} from 'mdast-util-to-markdown'
* @import {Code, Parents} from 'mdast'
*/
/**
* @param {Code} node
* @param {Parents | undefined} _
* @param {State} state
* @param {Info} info
* @returns {string}
*/
function code(node, _, state, info) {
const marker = checkFence(state);
const raw = node.value || '';
const suffix = marker === '`' ? 'GraveAccent' : 'Tilde';
if (formatCodeAsIndented(node, state)) {
const exit = state.enter('codeIndented');
const value = state.indentLines(raw, map$1);
exit();
return value
}
const tracker = state.createTracker(info);
const sequence = marker.repeat(Math.max(longestStreak(raw, marker) + 1, 3));
const exit = state.enter('codeFenced');
let value = tracker.move(sequence);
if (node.lang) {
const subexit = state.enter(`codeFencedLang${suffix}`);
value += tracker.move(
state.safe(node.lang, {
before: value,
after: ' ',
encode: ['`'],
...tracker.current()
})
);
subexit();
}
if (node.lang && node.meta) {
const subexit = state.enter(`codeFencedMeta${suffix}`);
value += tracker.move(' ');
value += tracker.move(
state.safe(node.meta, {
before: value,
after: '\n',
encode: ['`'],
...tracker.current()
})
);
subexit();
}
value += tracker.move('\n');
if (raw) {
value += tracker.move(raw + '\n');
}
value += tracker.move(sequence);
exit();
return value
}
/** @type {Map} */
function map$1(line, _, blank) {
return (blank ? '' : ' ') + line
}
/**
* @import {Options, State} from 'mdast-util-to-markdown'
*/
/**
* @param {State} state
* @returns {Exclude<Options['quote'], null | undefined>}
*/
function checkQuote(state) {
const marker = state.options.quote || '"';
if (marker !== '"' && marker !== "'") {
throw new Error(
'Cannot serialize title with `' +
marker +
'` for `options.quote`, expected `"`, or `\'`'
)
}
return marker
}
/**
* @import {Info, State} from 'mdast-util-to-markdown'
* @import {Definition, Parents} from 'mdast'
*/
/**
* @param {Definition} node
* @param {Parents | undefined} _
* @param {State} state
* @param {Info} info
* @returns {string}
*/
function definition(node, _, state, info) {
const quote = checkQuote(state);
const suffix = quote === '"' ? 'Quote' : 'Apostrophe';
const exit = state.enter('definition');
let subexit = state.enter('label');
const tracker = state.createTracker(info);
let value = tracker.move('[');
value += tracker.move(
state.safe(state.associationId(node), {
before: value,
after: ']',
...tracker.current()
})
);
value += tracker.move(']: ');
subexit();
if (
// If thereβs no url, orβ¦
!node.url ||
// If there are control characters or whitespace.
/[\0- \u007F]/.test(node.url)
) {
subexit = state.enter('destinationLiteral');
value += tracker.move('<');
value += tracker.move(
state.safe(node.url, {before: value, after: '>', ...tracker.current()})
);
value += tracker.move('>');
} else {
// No whitespace, raw is prettier.
subexit = state.enter('destinationRaw');
value += tracker.move(
state.safe(node.url, {
before: value,
after: node.title ? ' ' : '\n',
...tracker.current()
})
);
}
subexit();
if (node.title) {
subexit = state.enter(`title${suffix}`);
value += tracker.move(' ' + quote);
value += tracker.move(
state.safe(node.title, {
before: value,
after: quote,
...tracker.current()
})
);
value += tracker.move(quote);
subexit();
}
exit();
return value
}
/**
* @import {Options, State} from 'mdast-util-to-markdown'
*/
/**
* @param {State} state
* @returns {Exclude<Options['emphasis'], null | undefined>}
*/
function checkEmphasis(state) {
const marker = state.options.emphasis || '*';
if (marker !== '*' && marker !== '_') {
throw new Error(
'Cannot serialize emphasis with `' +
marker +
'` for `options.emphasis`, expected `*`, or `_`'
)
}
return marker
}
/**
* Encode a code point as a character reference.
*
* @param {number} code
* Code point to encode.
* @returns {string}
* Encoded character reference.
*/
function encodeCharacterReference(code) {
return '&#x' + code.toString(16).toUpperCase() + ';'
}
/**
* @import {EncodeSides} from '../types.js'
*/
/**
* Check whether to encode (as a character reference) the characters
* surrounding an attention run.
*
* Which characters are around an attention run influence whether it works or
* not.
*
* See <https://github.com/orgs/syntax-tree/discussions/60> for more info.
* See this markdown in a particular renderer to see what works:
*
* ```markdown
* | | A (letter inside) | B (punctuation inside) | C (whitespace inside) | D (nothing inside) |
* | ----------------------- | ----------------- | ---------------------- | --------------------- | ------------------ |
* | 1 (letter outside) | x*y*z | x*.*z | x* *z | x**z |
* | 2 (punctuation outside) | .*y*. | .*.*. | .* *. | .**. |
* | 3 (whitespace outside) | x *y* z | x *.* z | x * * z | x ** z |
* | 4 (nothing outside) | *x* | *.* | * * | ** |
* ```
*
* @param {number} outside
* Code point on the outer side of the run.
* @param {number} inside
* Code point on the inner side of the run.
* @param {'*' | '_'} marker
* Marker of the run.
* Underscores are handled more strictly (they form less often) than
* asterisks.
* @returns {EncodeSides}
* Whether to encode characters.
*/
// Important: punctuation must never be encoded.
// Punctuation is solely used by markdown constructs.
// And by encoding itself.
// Encoding them will break constructs or double encode things.
function encodeInfo(outside, inside, marker) {
const outsideKind = classifyCharacter(outside);
const insideKind = classifyCharacter(inside);
// Letter outside:
if (outsideKind === undefined) {
return insideKind === undefined
? // Letter inside:
// we have to encode *both* letters for `_` as it is looser.
// it already forms for `*` (and GFMs `~`).
marker === '_'
? {inside: true, outside: true}
: {inside: false, outside: false}
: insideKind === 1
? // Whitespace inside: encode both (letter, whitespace).
{inside: true, outside: true}
: // Punctuation inside: encode outer (letter)
{inside: false, outside: true}
}
// Whitespace outside:
if (outsideKind === 1) {
return insideKind === undefined
? // Letter inside: already forms.
{inside: false, outside: false}
: insideKind === 1
? // Whitespace inside: encode both (whitespace).
{inside: true, outside: true}
: // Punctuation inside: already forms.
{inside: false, outside: false}
}
// Punctuation outside:
return insideKind === undefined
? // Letter inside: already forms.
{inside: false, outside: false}
: insideKind === 1
? // Whitespace inside: encode inner (whitespace).
{inside: true, outside: false}
: // Punctuation inside: already forms.
{inside: false, outside: false}
}
/**
* @import {Info, State} from 'mdast-util-to-markdown'
* @import {Emphasis, Parents} from 'mdast'
*/
emphasis.peek = emphasisPeek;
/**
* @param {Emphasis} node
* @param {Parents | undefined} _
* @param {State} state
* @param {Info} info
* @returns {string}
*/
function emphasis(node, _, state, info) {
const marker = checkEmphasis(state);
const exit = state.enter('emphasis');
const tracker = state.createTracker(info);
const before = tracker.move(marker);
let between = tracker.move(
state.containerPhrasing(node, {
after: marker,
before,
...tracker.current()
})
);
const betweenHead = between.charCodeAt(0);
const open = encodeInfo(
info.before.charCodeAt(info.before.length - 1),
betweenHead,
marker
);
if (open.inside) {
between = encodeCharacterReference(betweenHead) + between.slice(1);
}
const betweenTail = between.charCodeAt(between.length - 1);
const close = encodeInfo(info.after.charCodeAt(0), betweenTail, marker);
if (close.inside) {
between = between.slice(0, -1) + encodeCharacterReference(betweenTail);
}
const after = tracker.move(marker);
exit();
state.attentionEncodeSurroundingInfo = {
after: close.outside,
before: open.outside
};
return before + between + after
}
/**
* @param {Emphasis} _
* @param {Parents | undefined} _1
* @param {State} state
* @returns {string}
*/
function emphasisPeek(_, _1, state) {
return state.options.emphasis || '*'
}
/**
* @typedef {import('unist').Node} Node
* @typedef {import('unist').Parent} Parent
*/
/**
* Generate an assertion from a test.
*
* Useful if youβre going to test many nodes, for example when creating a
* utility where something else passes a compatible test.
*
* The created function is a bit faster because it expects valid input only:
* a `node`, `index`, and `parent`.
*
* @param {Test} test
* * when nullish, checks if `node` is a `Node`.
* * when `string`, works like passing `(node) => node.type === test`.
* * when `function` checks if function passed the node is true.
* * when `object`, checks that all keys in test are in node, and that they have (strictly) equal values.
* * when `array`, checks if any one of the subtests pass.
* @returns {Check}
* An assertion.
*/
const convert =
// Note: overloads in JSDoc canβt yet use different `@template`s.
/**
* @type {(
* (<Condition extends string>(test: Condition) => (node: unknown, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & {type: Condition}) &
* (<Condition extends Props>(test: Condition) => (node: unknown, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & Condition) &
* (<Condition extends TestFunction>(test: Condition) => (node: unknown, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node & Predicate<Condition, Node>) &
* ((test?: null | undefined) => (node?: unknown, index?: number | null | undefined, parent?: Parent | null | undefined, context?: unknown) => node is Node) &
* ((test?: Test) => Check)
* )}
*/
(
/**
* @param {Test} [test]
* @returns {Check}
*/
function (test) {
if (test === null || test === undefined) {
return ok
}
if (typeof test === 'function') {
return castFactory(test)
}
if (typeof test === 'object') {
return Array.isArray(test) ? anyFactory(test) : propsFactory(test)
}
if (typeof test === 'string') {
return typeFactory(test)
}
throw new Error('Expected function, string, or object as test')
}
);
/**
* @param {Array<Props | TestFunction | string>} tests
* @returns {Check}
*/
function anyFactory(tests) {
/** @type {Array<Check>} */
const checks = [];
let index = -1;
while (++index < tests.length) {
checks[index] = convert(tests[index]);
}
return castFactory(any)
/**
* @this {unknown}
* @type {TestFunction}
*/
function any(...parameters) {
let index = -1;
while (++index < checks.length) {
if (checks[index].apply(this, parameters)) return true
}
return false
}
}
/**
* Turn an object into a test for a node with a certain fields.
*
* @param {Props} check
* @returns {Check}
*/
function propsFactory(check) {
const checkAsRecord = /** @type {Record<string, unknown>} */ (check);
return castFactory(all)
/**
* @param {Node} node
* @returns {boolean}
*/
function all(node) {
const nodeAsRecord = /** @type {Record<string, unknown>} */ (
/** @type {unknown} */ (node)
);
/** @type {string} */
let key;
for (key in check) {
if (nodeAsRecord[key] !== checkAsRecord[key]) return false
}
return true
}
}
/**
* Turn a string into a test for a node with a certain type.
*
* @param {string} check
* @returns {Check}
*/
function typeFactory(check) {
return castFactory(type)
/**
* @param {Node} node
*/
function type(node) {
return node && node.type === check
}
}
/**
* Turn a custom test into a test for a node that passes that test.
*
* @param {TestFunction} testFunction
* @returns {Check}
*/
function castFactory(testFunction) {
return check
/**
* @this {unknown}
* @type {Check}
*/
function check(value, index, parent) {
return Boolean(
looksLikeANode(value) &&
testFunction.call(
this,
value,
typeof index === 'number' ? index : undefined,
parent || undefined
)
)
}
}
function ok() {
return true
}
/**
* @param {unknown} value
* @returns {value is Node}
*/
function looksLikeANode(value) {
return value !== null && typeof value === 'object' && 'type' in value
}
/**
* @param {string} d
* @returns {string}
*/
function color(d) {
return d
}
/**
* @typedef {import('unist').Node} UnistNode
* @typedef {import('unist').Parent} UnistParent
*/
/** @type {Readonly<ActionTuple>} */
const empty = [];
/**
* Continue traversing as normal.
*/
const CONTINUE = true;
/**
* Stop traversing immediately.
*/
const EXIT = false;
/**
* Do not traverse this nodeβs children.
*/
const SKIP = 'skip';
/**
* Visit nodes, with ancestral information.
*
* This algorithm performs *depth-first* *tree traversal* in *preorder*
* (**NLR**) or if `reverse` is given, in *reverse preorder* (**NRL**).
*
* You can choose for which nodes `visitor` is called by passing a `test`.
* For complex tests, you should test yourself in `visitor`, as it will be
* faster and will have improved type information.
*
* Walking the tree is an intensive task.
* Make use of the return values of the visitor when possible.
* Instead of walking a tree multiple times, walk it once, use `unist-util-is`
* to check if a node matches, and then perform different operations.
*
* You can change the tree.
* See `Visitor` for more info.
*
* @overload
* @param {Tree} tree
* @param {Check} check
* @param {BuildVisitor<Tree, Check>} visitor
* @param {boolean | null | undefined} [reverse]
* @returns {undefined}
*
* @overload
* @param {Tree} tree
* @param {BuildVisitor<Tree>} visitor
* @param {boolean | null | undefined} [reverse]
* @returns {undefined}
*
* @param {UnistNode} tree
* Tree to traverse.
* @param {Visitor | Test} test
* `unist-util-is`-compatible test
* @param {Visitor | boolean | null | undefined} [visitor]
* Handle each node.
* @param {boolean | null | undefined} [reverse]
* Traverse in reverse preorder (NRL) instead of the default preorder (NLR).
* @returns {undefined}
* Nothing.
*
* @template {UnistNode} Tree
* Node type.
* @template {Test} Check
* `unist-util-is`-compatible test.
*/
function visitParents(tree, test, visitor, reverse) {
/** @type {Test} */
let check;
if (typeof test === 'function' && typeof visitor !== 'function') {
reverse = visitor;
// @ts-expect-error no visitor given, so `visitor` is test.
visitor = test;
} else {
// @ts-expect-error visitor given, so `test` isnβt a visitor.
check = test;
}
const is = convert(check);
const step = reverse ? -1 : 1;
factory(tree, undefined, [])();
/**
* @param {UnistNode} node
* @param {number | undefined} index
* @param {Array<UnistParent>} parents
*/
function factory(node, index, parents) {
const value = /** @type {Record<string, unknown>} */ (
node && typeof node === 'object' ? node : {}
);
if (typeof value.type === 'string') {
const name =
// `hast`
typeof value.tagName === 'string'
? value.tagName
: // `xast`
typeof value.name === 'string'
? value.name
: undefined;
Object.defineProperty(visit, 'name', {
value:
'node (' + color(node.type + (name ? '<' + name + '>' : '')) + ')'
});
}
return visit
function visit() {
/** @type {Readonly<ActionTuple>} */
let result = empty;
/** @type {Readonly<ActionTuple>} */
let subresult;
/** @type {number} */
let offset;
/** @type {Array<UnistParent>} */
let grandparents;
if (!test || is(node, index, parents[parents.length - 1] || undefined)) {
// @ts-expect-error: `visitor` is now a visitor.
result = toResult(visitor(node, parents));
if (result[0] === EXIT) {
return result
}
}
if ('children' in node && node.children) {
const nodeAsParent = /** @type {UnistParent} */ (node);
if (nodeAsParent.children && result[0] !== SKIP) {
offset = (reverse ? nodeAsParent.children.length : -1) + step;
grandparents = parents.concat(nodeAsParent);
while (offset > -1 && offset < nodeAsParent.children.length) {
const child = nodeAsParent.children[offset];
subresult = factory(child, offset, grandparents)();
if (subresult[0] === EXIT) {
return subresult
}
offset =
typeof subresult[1] === 'number' ? subresult[1] : offset + step;
}
}
}
return result
}
}
}
/**
* Turn a return value into a clean result.
*
* @param {VisitorResult} value
* Valid return values from visitors.
* @returns {Readonly<ActionTuple>}
* Clean result.
*/
function toResult(value) {
if (Array.isArray(value)) {
return value
}
if (typeof value === 'number') {
return [CONTINUE, value]
}
return value === null || value === undefined ? empty : [value]
}
/**
* @typedef {import('unist').Node} UnistNode
* @typedef {import('unist').Parent} UnistParent
* @typedef {import('unist-util-visit-parents').VisitorResult} VisitorResult
*/
/**
* Visit nodes.
*
* This algorithm performs *depth-first* *tree traversal* in *preorder*
* (**NLR**) or if `reverse` is given, in *reverse preorder* (**NRL**).
*
* You can choose for which nodes `visitor` is called by passing a `test`.
* For complex tests, you should test yourself in `visitor`, as it will be
* faster and will have improved type information.
*
* Walking the tree is an intensive task.
* Make use of the return values of the visitor when possible.
* Instead of walking a tree multiple times, walk it once, use `unist-util-is`
* to check if a node matches, and then perform different operations.
*
* You can change the tree.
* See `Visitor` for more info.
*
* @overload
* @param {Tree} tree
* @param {Check} check
* @param {BuildVisitor<Tree, Check>} visitor
* @param {boolean | null | undefined} [reverse]
* @returns {undefined}
*
* @overload
* @param {Tree} tree
* @param {BuildVisitor<Tree>} visitor
* @param {boolean | null | undefined} [reverse]
* @returns {undefined}
*
* @param {UnistNode} tree
* Tree to traverse.
* @param {Visitor | Test} testOrVisitor
* `unist-util-is`-compatible test (optional, omit to pass a visitor).
* @param {Visitor | boolean | null | undefined} [visitorOrReverse]
* Handle each node (when test is omitted, pass `reverse`).
* @param {boolean | null | undefined} [maybeReverse=false]
* Traverse in reverse preorder (NRL) instead of the default preorder (NLR).
* @returns {undefined}
* Nothing.
*
* @template {UnistNode} Tree
* Node type.
* @template {Test} Check
* `unist-util-is`-compatible test.
*/
function visit(tree, testOrVisitor, visitorOrReverse, maybeReverse) {
/** @type {boolean | null | undefined} */
let reverse;
/** @type {Test} */
let test;
/** @type {Visitor} */
let visitor;
if (
typeof testOrVisitor === 'function' &&
typeof visitorOrReverse !== 'function'
) {
test = undefined;
visitor = testOrVisitor;
reverse = visitorOrReverse;
} else {
// @ts-expect-error: assume the overload with test was given.
test = testOrVisitor;
// @ts-expect-error: assume the overload with test was given.
visitor = visitorOrReverse;
reverse = maybeReverse;
}
visitParents(tree, test, overload, reverse);
/**
* @param {UnistNode} node
* @param {Array<UnistParent>} parents
*/
function overload(node, parents) {
const parent = parents[parents.length - 1];
const index = parent ? parent.children.indexOf(node) : undefined;
return visitor(node, index, parent)
}
}
/**
* @import {State} from 'mdast-util-to-markdown'
* @import {Heading} from 'mdast'
*/
/**
* @param {Heading} node
* @param {State} state
* @returns {boolean}
*/
function formatHeadingAsSetext(node, state) {
let literalWithBreak = false;
// Look for literals with a line break.
// Note that this also
visit(node, function (node) {
if (
('value' in node && /\r?\n|\r/.test(node.value)) ||
node.type === 'break'
) {
literalWithBreak = true;
return EXIT
}
});
return Boolean(
(!node.depth || node.depth < 3) &&
toString(node) &&
(state.options.setext || literalWithBreak)
)
}
/**
* @import {Info, State} from 'mdast-util-to-markdown'
* @import {Heading, Parents} from 'mdast'
*/
/**
* @param {Heading} node
* @param {Parents | undefined} _
* @param {State} state
* @param {Info} info
* @returns {string}
*/
function heading(node, _, state, info) {
const rank = Math.max(Math.min(6, node.depth || 1), 1);
const tracker = state.createTracker(info);
if (formatHeadingAsSetext(node, state)) {
const exit = state.enter('headingSetext');
const subexit = state.enter('phrasing');
const value = state.containerPhrasing(node, {
...tracker.current(),
before: '\n',
after: '\n'
});
subexit();
exit();
return (
value +
'\n' +
(rank === 1 ? '=' : '-').repeat(
// The whole sizeβ¦
value.length -
// Minus the position of the character after the last EOL (or
// 0 if there is none)β¦
(Math.max(value.lastIndexOf('\r'), value.lastIndexOf('\n')) + 1)
)
)
}
const sequence = '#'.repeat(rank);
const exit = state.enter('headingAtx');
const subexit = state.enter('phrasing');
// Note: for proper tracking, we should reset the output positions when there
// is no content returned, because then the space is not output.
// Practically, in that case, there is no content, so it doesnβt matter that
// weβve tracked one too many characters.
tracker.move(sequence + ' ');
let value = state.containerPhrasing(node, {
before: '# ',
after: '\n',
...tracker.current()
});
if (/^[\t ]/.test(value)) {
// To do: what effect has the character reference on tracking?
value = encodeCharacterReference(value.charCodeAt(0)) + value.slice(1);
}
value = value ? sequence + ' ' + value : sequence;
if (state.options.closeAtx) {
value += ' ' + sequence;
}
subexit();
exit();
return value
}
/**
* @import {Html} from 'mdast'
*/
html.peek = htmlPeek;
/**
* @param {Html} node
* @returns {string}
*/
function html(node) {
return node.value || ''
}
/**
* @returns {string}
*/
function htmlPeek() {
return '<'
}
/**
* @import {Info, State} from 'mdast-util-to-markdown'
* @import {Image, Parents} from 'mdast'
*/
image.peek = imagePeek;
/**
* @param {Image} node
* @param {Parents | undefined} _
* @param {State} state
* @param {Info} info
* @returns {string}
*/
function image(node, _, state, info) {
const quote = checkQuote(state);
const suffix = quote === '"' ? 'Quote' : 'Apostrophe';
const exit = state.enter('image');
let subexit = state.enter('label');
const tracker = state.createTracker(info);
let value = tracker.move('![');
value += tracker.move(
state.safe(node.alt, {before: value, after: ']', ...tracker.current()})
);
value += tracker.move('](');
subexit();
if (
// If thereβs no url but there is a titleβ¦
(!node.url && node.title) ||
// If there are control characters or whitespace.
/[\0- \u007F]/.test(node.url)
) {
subexit = state.enter('destinationLiteral');
value += tracker.move('<');
value += tracker.move(
state.safe(node.url, {before: value, after: '>', ...tracker.current()})
);
value += tracker.move('>');
} else {
// No whitespace, raw is prettier.
subexit = state.enter('destinationRaw');
value += tracker.move(
state.safe(node.url, {
before: value,
after: node.title ? ' ' : ')',
...tracker.current()
})
);
}
subexit();
if (node.title) {
subexit = state.enter(`title${suffix}`);
value += tracker.move(' ' + quote);
value += tracker.move(
state.safe(node.title, {
before: value,
after: quote,
...tracker.current()
})
);
value += tracker.move(quote);
subexit();
}
value += tracker.move(')');
exit();
return value
}
/**
* @returns {string}
*/
function imagePeek() {
return '!'
}
/**
* @import {Info, State} from 'mdast-util-to-markdown'
* @import {ImageReference, Parents} from 'mdast'
*/
imageReference.peek = imageReferencePeek;
/**
* @param {ImageReference} node
* @param {Parents | undefined} _
* @param {State} state
* @param {Info} info
* @returns {string}
*/
function imageReference(node, _, state, info) {
const type = node.referenceType;
const exit = state.enter('imageReference');
let subexit = state.enter('label');
const tracker = state.createTracker(info);
let value = tracker.move('![');
const alt = state.safe(node.alt, {
before: value,
after: ']',
...tracker.current()
});
value += tracker.move(alt + '][');
subexit();
// Hide the fact that weβre in phrasing, because escapes donβt work.
const stack = state.stack;
state.stack = [];
subexit = state.enter('reference');
// Note: for proper tracking, we should reset the output positions when we end
// up making a `shortcut` reference, because then there is no brace output.
// Practically, in that case, there is no content, so it doesnβt matter that
// weβve tracked one too many characters.
const reference = state.safe(state.associationId(node), {
before: value,
after: ']',
...tracker.current()
});
subexit();
state.stack = stack;
exit();
if (type === 'full' || !alt || alt !== reference) {
value += tracker.move(reference + ']');
} else if (type === 'shortcut') {
// Remove the unwanted `[`.
value = value.slice(0, -1);
} else {
value += tracker.move(']');
}
return value
}
/**
* @returns {string}
*/
function imageReferencePeek() {
return '!'
}
/**
* @import {State} from 'mdast-util-to-markdown'
* @import {InlineCode, Parents} from 'mdast'
*/
inlineCode.peek = inlineCodePeek;
/**
* @param {InlineCode} node
* @param {Parents | undefined} _
* @param {State} state
* @returns {string}
*/
function inlineCode(node, _, state) {
let value = node.value || '';
let sequence = '`';
let index = -1;
// If there is a single grave accent on its own in the code, use a fence of
// two.
// If there are two in a row, use one.
while (new RegExp('(^|[^`])' + sequence + '([^`]|$)').test(value)) {
sequence += '`';
}
// If this is not just spaces or eols (tabs donβt count), and either the
// first or last character are a space, eol, or tick, then pad with spaces.
if (
/[^ \r\n]/.test(value) &&
((/^[ \r\n]/.test(value) && /[ \r\n]$/.test(value)) || /^`|`$/.test(value))
) {
value = ' ' + value + ' ';
}
// We have a potential problem: certain characters after eols could result in
// blocks being seen.
// For example, if someone injected the string `'\n# b'`, then that would
// result in an ATX heading.
// We canβt escape characters in `inlineCode`, but because eols are
// transformed to spaces when going from markdown to HTML anyway, we can swap
// them out.
while (++index < state.unsafe.length) {
const pattern = state.unsafe[index];
const expression = state.compilePattern(pattern);
/** @type {RegExpExecArray | null} */
let match;
// Only look for `atBreak`s.
// Btw: note that `atBreak` patterns will always start the regex at LF or
// CR.
if (!pattern.atBreak) continue
while ((match = expression.exec(value))) {
let position = match.index;
// Support CRLF (patterns only look for one of the characters).
if (
value.charCodeAt(position) === 10 /* `\n` */ &&
value.charCodeAt(position - 1) === 13 /* `\r` */
) {
position--;
}
value = value.slice(0, position) + ' ' + value.slice(match.index + 1);
}
}
return sequence + value + sequence
}
/**
* @returns {string}
*/
function inlineCodePeek() {
return '`'
}
/**
* @import {State} from 'mdast-util-to-markdown'
* @import {Link} from 'mdast'
*/
/**
* @param {Link} node
* @param {State} state
* @returns {boolean}
*/
function formatLinkAsAutolink(node, state) {
const raw = toString(node);
return Boolean(
!state.options.resourceLink &&
// If thereβs a urlβ¦
node.url &&
// And thereβs a no titleβ¦
!node.title &&
// And the content of `node` is a single text nodeβ¦
node.children &&
node.children.length === 1 &&
node.children[0].type === 'text' &&
// And if the url is the same as the contentβ¦
(raw === node.url || 'mailto:' + raw === node.url) &&
// And that starts w/ a protocolβ¦
/^[a-z][a-z+.-]+:/i.test(node.url) &&
// And that doesnβt contain ASCII control codes (character escapes and
// references donβt work), space, or angle bracketsβ¦
!/[\0- <>\u007F]/.test(node.url)
)
}
/**
* @import {Info, State} from 'mdast-util-to-markdown'
* @import {Link, Parents} from 'mdast'
* @import {Exit} from '../types.js'
*/
link.peek = linkPeek;
/**
* @param {Link} node
* @param {Parents | undefined} _
* @param {State} state
* @param {Info} info
* @returns {string}
*/
function link(node, _, state, info) {
const quote = checkQuote(state);
const suffix = quote === '"' ? 'Quote' : 'Apostrophe';
const tracker = state.createTracker(info);
/** @type {Exit} */
let exit;
/** @type {Exit} */
let subexit;
if (formatLinkAsAutolink(node, state)) {
// Hide the fact that weβre in phrasing, because escapes donβt work.
const stack = state.stack;
state.stack = [];
exit = state.enter('autolink');
let value = tracker.move('<');
value += tracker.move(
state.containerPhrasing(node, {
before: value,
after: '>',
...tracker.current()
})
);
value += tracker.move('>');
exit();
state.stack = stack;
return value
}
exit = state.enter('link');
subexit = state.enter('label');
let value = tracker.move('[');
value += tracker.move(
state.containerPhrasing(node, {
before: value,
after: '](',
...tracker.current()
})
);
value += tracker.move('](');
subexit();
if (
// If thereβs no url but there is a titleβ¦
(!node.url && node.title) ||
// If there are control characters or whitespace.
/[\0- \u007F]/.test(node.url)
) {
subexit = state.enter('destinationLiteral');
value += tracker.move('<');
value += tracker.move(
state.safe(node.url, {before: value, after: '>', ...tracker.current()})
);
value += tracker.move('>');
} else {
// No whitespace, raw is prettier.
subexit = state.enter('destinationRaw');
value += tracker.move(
state.safe(node.url, {
before: value,
after: node.title ? ' ' : ')',
...tracker.current()
})
);
}
subexit();
if (node.title) {
subexit = state.enter(`title${suffix}`);
value += tracker.move(' ' + quote);
value += tracker.move(
state.safe(node.title, {
before: value,
after: quote,
...tracker.current()
})
);
value += tracker.move(quote);
subexit();
}
value += tracker.move(')');
exit();
return value
}
/**
* @param {Link} node
* @param {Parents | undefined} _
* @param {State} state
* @returns {string}
*/
function linkPeek(node, _, state) {
return formatLinkAsAutolink(node, state) ? '<' : '['
}
/**
* @import {Info, State} from 'mdast-util-to-markdown'
* @import {LinkReference, Parents} from 'mdast'
*/
linkReference.peek = linkReferencePeek;
/**
* @param {LinkReference} node
* @param {Parents | undefined} _
* @param {State} state
* @param {Info} info
* @returns {string}
*/
function linkReference(node, _, state, info) {
const type = node.referenceType;
const exit = state.enter('linkReference');
let subexit = state.enter('label');
const tracker = state.createTracker(info);
let value = tracker.move('[');
const text = state.containerPhrasing(node, {
before: value,
after: ']',
...tracker.current()
});
value += tracker.move(text + '][');
subexit();
// Hide the fact that weβre in phrasing, because escapes donβt work.
const stack = state.stack;
state.stack = [];
subexit = state.enter('reference');
// Note: for proper tracking, we should reset the output positions when we end
// up making a `shortcut` reference, because then there is no brace output.
// Practically, in that case, there is no content, so it doesnβt matter that
// weβve tracked one too many characters.
const reference = state.safe(state.associationId(node), {
before: value,
after: ']',
...tracker.current()
});
subexit();
state.stack = stack;
exit();
if (type === 'full' || !text || text !== reference) {
value += tracker.move(reference + ']');
} else if (type === 'shortcut') {
// Remove the unwanted `[`.
value = value.slice(0, -1);
} else {
value += tracker.move(']');
}
return value
}
/**
* @returns {string}
*/
function linkReferencePeek() {
return '['
}
/**
* @import {Options, State} from 'mdast-util-to-markdown'
*/
/**
* @param {State} state
* @returns {Exclude<Options['bullet'], null | undefined>}
*/
function checkBullet(state) {
const marker = state.options.bullet || '*';
if (marker !== '*' && marker !== '+' && marker !== '-') {
throw new Error(
'Cannot serialize items with `' +
marker +
'` for `options.bullet`, expected `*`, `+`, or `-`'
)
}
return marker
}
/**
* @import {Options, State} from 'mdast-util-to-markdown'
*/
/**
* @param {State} state
* @returns {Exclude<Options['bullet'], null | undefined>}
*/
function checkBulletOther(state) {
const bullet = checkBullet(state);
const bulletOther = state.options.bulletOther;
if (!bulletOther) {
return bullet === '*' ? '-' : '*'
}
if (bulletOther !== '*' && bulletOther !== '+' && bulletOther !== '-') {
throw new Error(
'Cannot serialize items with `' +
bulletOther +
'` for `options.bulletOther`, expected `*`, `+`, or `-`'
)
}
if (bulletOther === bullet) {
throw new Error(
'Expected `bullet` (`' +
bullet +
'`) and `bulletOther` (`' +
bulletOther +
'`) to be different'
)
}
return bulletOther
}
/**
* @import {Options, State} from 'mdast-util-to-markdown'
*/
/**
* @param {State} state
* @returns {Exclude<Options['bulletOrdered'], null | undefined>}
*/
function checkBulletOrdered(state) {
const marker = state.options.bulletOrdered || '.';
if (marker !== '.' && marker !== ')') {
throw new Error(
'Cannot serialize items with `' +
marker +
'` for `options.bulletOrdered`, expected `.` or `)`'
)
}
return marker
}
/**
* @import {Options, State} from 'mdast-util-to-markdown'
*/
/**
* @param {State} state
* @returns {Exclude<Options['rule'], null | undefined>}
*/
function checkRule(state) {
const marker = state.options.rule || '*';
if (marker !== '*' && marker !== '-' && marker !== '_') {
throw new Error(
'Cannot serialize rules with `' +
marker +
'` for `options.rule`, expected `*`, `-`, or `_`'
)
}
return marker
}
/**
* @import {Info, State} from 'mdast-util-to-markdown'
* @import {List, Parents} from 'mdast'
*/
/**
* @param {List} node
* @param {Parents | undefined} parent
* @param {State} state
* @param {Info} info
* @returns {string}
*/
function list(node, parent, state, info) {
const exit = state.enter('list');
const bulletCurrent = state.bulletCurrent;
/** @type {string} */
let bullet = node.ordered ? checkBulletOrdered(state) : checkBullet(state);
/** @type {string} */
const bulletOther = node.ordered
? bullet === '.'
? ')'
: '.'
: checkBulletOther(state);
let useDifferentMarker =
parent && state.bulletLastUsed ? bullet === state.bulletLastUsed : false;
if (!node.ordered) {
const firstListItem = node.children ? node.children[0] : undefined;
// If thereβs an empty first list item directly in two list items,
// we have to use a different bullet:
//
// ```markdown
// * - *
// ```
//
// β¦because otherwise it would become one big thematic break.
if (
// Bullet could be used as a thematic break marker:
(bullet === '*' || bullet === '-') &&
// Empty first list item:
firstListItem &&
(!firstListItem.children || !firstListItem.children[0]) &&
// Directly in two other list items:
state.stack[state.stack.length - 1] === 'list' &&
state.stack[state.stack.length - 2] === 'listItem' &&
state.stack[state.stack.length - 3] === 'list' &&
state.stack[state.stack.length - 4] === 'listItem' &&
// That are each the first child.
state.indexStack[state.indexStack.length - 1] === 0 &&
state.indexStack[state.indexStack.length - 2] === 0 &&
state.indexStack[state.indexStack.length - 3] === 0
) {
useDifferentMarker = true;
}
// If thereβs a thematic break at the start of the first list item,
// we have to use a different bullet:
//
// ```markdown
// * ---
// ```
//
// β¦because otherwise it would become one big thematic break.
if (checkRule(state) === bullet && firstListItem) {
let index = -1;
while (++index < node.children.length) {
const item = node.children[index];
if (
item &&
item.type === 'listItem' &&
item.children &&
item.children[0] &&
item.children[0].type === 'thematicBreak'
) {
useDifferentMarker = true;
break
}
}
}
}
if (useDifferentMarker) {
bullet = bulletOther;
}
state.bulletCurrent = bullet;
const value = state.containerFlow(node, info);
state.bulletLastUsed = bullet;
state.bulletCurrent = bulletCurrent;
exit();
return value
}
/**
* @import {Options, State} from 'mdast-util-to-markdown'
*/
/**
* @param {State} state
* @returns {Exclude<Options['listItemIndent'], null | undefined>}
*/
function checkListItemIndent(state) {
const style = state.options.listItemIndent || 'one';
if (style !== 'tab' && style !== 'one' && style !== 'mixed') {
throw new Error(
'Cannot serialize items with `' +
style +
'` for `options.listItemIndent`, expected `tab`, `one`, or `mixed`'
)
}
return style
}
/**
* @import {Info, Map, State} from 'mdast-util-to-markdown'
* @import {ListItem, Parents} from 'mdast'
*/
/**
* @param {ListItem} node
* @param {Parents | undefined} parent
* @param {State} state
* @param {Info} info
* @returns {string}
*/
function listItem(node, parent, state, info) {
const listItemIndent = checkListItemIndent(state);
let bullet = state.bulletCurrent || checkBullet(state);
// Add the marker value for ordered lists.
if (parent && parent.type === 'list' && parent.ordered) {
bullet =
(typeof parent.start === 'number' && parent.start > -1
? parent.start
: 1) +
(state.options.incrementListMarker === false
? 0
: parent.children.indexOf(node)) +
bullet;
}
let size = bullet.length + 1;
if (
listItemIndent === 'tab' ||
(listItemIndent === 'mixed' &&
((parent && parent.type === 'list' && parent.spread) || node.spread))
) {
size = Math.ceil(size / 4) * 4;
}
const tracker = state.createTracker(info);
tracker.move(bullet + ' '.repeat(size - bullet.length));
tracker.shift(size);
const exit = state.enter('listItem');
const value = state.indentLines(
state.containerFlow(node, tracker.current()),
map
);
exit();
return value
/** @type {Map} */
function map(line, index, blank) {
if (index) {
return (blank ? '' : ' '.repeat(size)) + line
}
return (blank ? bullet : bullet + ' '.repeat(size - bullet.length)) + line
}
}
/**
* @import {Info, State} from 'mdast-util-to-markdown'
* @import {Paragraph, Parents} from 'mdast'
*/
/**
* @param {Paragraph} node
* @param {Parents | undefined} _
* @param {State} state
* @param {Info} info
* @returns {string}
*/
function paragraph(node, _, state, info) {
const exit = state.enter('paragraph');
const subexit = state.enter('phrasing');
const value = state.containerPhrasing(node, info);
subexit();
exit();
return value
}
/**
* @typedef {import('mdast').Html} Html
* @typedef {import('mdast').PhrasingContent} PhrasingContent
*/
/**
* Check if the given value is *phrasing content*.
*
* > π **Note**: Excludes `html`, which can be both phrasing or flow.
*
* @param node
* Thing to check, typically `Node`.
* @returns
* Whether `value` is phrasing content.
*/
const phrasing =
/** @type {(node?: unknown) => node is Exclude<PhrasingContent, Html>} */
(
convert([
'break',
'delete',
'emphasis',
// To do: next major: removed since footnotes were added to GFM.
'footnote',
'footnoteReference',
'image',
'imageReference',
'inlineCode',
// Enabled by `mdast-util-math`:
'inlineMath',
'link',
'linkReference',
// Enabled by `mdast-util-mdx`:
'mdxJsxTextElement',
// Enabled by `mdast-util-mdx`:
'mdxTextExpression',
'strong',
'text',
// Enabled by `mdast-util-directive`:
'textDirective'
])
);
/**
* @import {Info, State} from 'mdast-util-to-markdown'
* @import {Parents, Root} from 'mdast'
*/
/**
* @param {Root} node
* @param {Parents | undefined} _
* @param {State} state
* @param {Info} info
* @returns {string}
*/
function root(node, _, state, info) {
// Note: `html` nodes are ambiguous.
const hasPhrasing = node.children.some(function (d) {
return phrasing(d)
});
const container = hasPhrasing ? state.containerPhrasing : state.containerFlow;
return container.call(state, node, info)
}
/**
* @import {Options, State} from 'mdast-util-to-markdown'
*/
/**
* @param {State} state
* @returns {Exclude<Options['strong'], null | undefined>}
*/
function checkStrong(state) {
const marker = state.options.strong || '*';
if (marker !== '*' && marker !== '_') {
throw new Error(
'Cannot serialize strong with `' +
marker +
'` for `options.strong`, expected `*`, or `_`'
)
}
return marker
}
/**
* @import {Info, State} from 'mdast-util-to-markdown'
* @import {Parents, Strong} from 'mdast'
*/
strong.peek = strongPeek;
/**
* @param {Strong} node
* @param {Parents | undefined} _
* @param {State} state
* @param {Info} info
* @returns {string}
*/
function strong(node, _, state, info) {
const marker = checkStrong(state);
const exit = state.enter('strong');
const tracker = state.createTracker(info);
const before = tracker.move(marker + marker);
let between = tracker.move(
state.containerPhrasing(node, {
after: marker,
before,
...tracker.current()
})
);
const betweenHead = between.charCodeAt(0);
const open = encodeInfo(
info.before.charCodeAt(info.before.length - 1),
betweenHead,
marker
);
if (open.inside) {
between = encodeCharacterReference(betweenHead) + between.slice(1);
}
const betweenTail = between.charCodeAt(between.length - 1);
const close = encodeInfo(info.after.charCodeAt(0), betweenTail, marker);
if (close.inside) {
between = between.slice(0, -1) + encodeCharacterReference(betweenTail);
}
const after = tracker.move(marker + marker);
exit();
state.attentionEncodeSurroundingInfo = {
after: close.outside,
before: open.outside
};
return before + between + after
}
/**
* @param {Strong} _
* @param {Parents | undefined} _1
* @param {State} state
* @returns {string}
*/
function strongPeek(_, _1, state) {
return state.options.strong || '*'
}
/**
* @import {Info, State} from 'mdast-util-to-markdown'
* @import {Parents, Text} from 'mdast'
*/
/**
* @param {Text} node
* @param {Parents | undefined} _
* @param {State} state
* @param {Info} info
* @returns {string}
*/
function text(node, _, state, info) {
return state.safe(node.value, info)
}
/**
* @import {Options, State} from 'mdast-util-to-markdown'
*/
/**
* @param {State} state
* @returns {Exclude<Options['ruleRepetition'], null | undefined>}
*/
function checkRuleRepetition(state) {
const repetition = state.options.ruleRepetition || 3;
if (repetition < 3) {
throw new Error(
'Cannot serialize rules with repetition `' +
repetition +
'` for `options.ruleRepetition`, expected `3` or more'
)
}
return repetition
}
/**
* @import {State} from 'mdast-util-to-markdown'
* @import {Parents, ThematicBreak} from 'mdast'
*/
/**
* @param {ThematicBreak} _
* @param {Parents | undefined} _1
* @param {State} state
* @returns {string}
*/
function thematicBreak(_, _1, state) {
const value = (
checkRule(state) + (state.options.ruleSpaces ? ' ' : '')
).repeat(checkRuleRepetition(state));
return state.options.ruleSpaces ? value.slice(0, -1) : value
}
/**
* Default (CommonMark) handlers.
*/
const handle = {
blockquote,
break: hardBreak,
code,
definition,
emphasis,
hardBreak,
heading,
html,
image,
imageReference,
inlineCode,
link,
linkReference,
list,
listItem,
paragraph,
root,
strong,
text,
thematicBreak
};
/**
* @import {Join} from 'mdast-util-to-markdown'
*/
/** @type {Array<Join>} */
const join = [joinDefaults];
/** @type {Join} */
function joinDefaults(left, right, parent, state) {
// Indented code after list or another indented code.
if (
right.type === 'code' &&
formatCodeAsIndented(right, state) &&
(left.type === 'list' ||
(left.type === right.type && formatCodeAsIndented(left, state)))
) {
return false
}
// Join children of a list or an item.
// In which case, `parent` has a `spread` field.
if ('spread' in parent && typeof parent.spread === 'boolean') {
if (
left.type === 'paragraph' &&
// Two paragraphs.
(left.type === right.type ||
right.type === 'definition' ||
// Paragraph followed by a setext heading.
(right.type === 'heading' && formatHeadingAsSetext(right, state)))
) {
return
}
return parent.spread ? 1 : 0
}
}
/**
* @import {ConstructName, Unsafe} from 'mdast-util-to-markdown'
*/
/**
* List of constructs that occur in phrasing (paragraphs, headings), but cannot
* contain things like attention (emphasis, strong), images, or links.
* So they sort of cancel each other out.
* Note: could use a better name.
*
* @type {Array<ConstructName>}
*/
const fullPhrasingSpans = [
'autolink',
'destinationLiteral',
'destinationRaw',
'reference',
'titleQuote',
'titleApostrophe'
];
/** @type {Array<Unsafe>} */
const unsafe = [
{character: '\t', after: '[\\r\\n]', inConstruct: 'phrasing'},
{character: '\t', before: '[\\r\\n]', inConstruct: 'phrasing'},
{
character: '\t',
inConstruct: ['codeFencedLangGraveAccent', 'codeFencedLangTilde']
},
{
character: '\r',
inConstruct: [
'codeFencedLangGraveAccent',
'codeFencedLangTilde',
'codeFencedMetaGraveAccent',
'codeFencedMetaTilde',
'destinationLiteral',
'headingAtx'
]
},
{
character: '\n',
inConstruct: [
'codeFencedLangGraveAccent',
'codeFencedLangTilde',
'codeFencedMetaGraveAccent',
'codeFencedMetaTilde',
'destinationLiteral',
'headingAtx'
]
},
{character: ' ', after: '[\\r\\n]', inConstruct: 'phrasing'},
{character: ' ', before: '[\\r\\n]', inConstruct: 'phrasing'},
{
character: ' ',
inConstruct: ['codeFencedLangGraveAccent', 'codeFencedLangTilde']
},
// An exclamation mark can start an image, if it is followed by a link or
// a link reference.
{
character: '!',
after: '\\[',
inConstruct: 'phrasing',
notInConstruct: fullPhrasingSpans
},
// A quote can break out of a title.
{character: '"', inConstruct: 'titleQuote'},
// A number sign could start an ATX heading if it starts a line.
{atBreak: true, character: '#'},
{character: '#', inConstruct: 'headingAtx', after: '(?:[\r\n]|$)'},
// Dollar sign and percentage are not used in markdown.
// An ampersand could start a character reference.
{character: '&', after: '[#A-Za-z]', inConstruct: 'phrasing'},
// An apostrophe can break out of a title.
{character: "'", inConstruct: 'titleApostrophe'},
// A left paren could break out of a destination raw.
{character: '(', inConstruct: 'destinationRaw'},
// A left paren followed by `]` could make something into a link or image.
{
before: '\\]',
character: '(',
inConstruct: 'phrasing',
notInConstruct: fullPhrasingSpans
},
// A right paren could start a list item or break out of a destination
// raw.
{atBreak: true, before: '\\d+', character: ')'},
{character: ')', inConstruct: 'destinationRaw'},
// An asterisk can start thematic breaks, list items, emphasis, strong.
{atBreak: true, character: '*', after: '(?:[ \t\r\n*])'},
{character: '*', inConstruct: 'phrasing', notInConstruct: fullPhrasingSpans},
// A plus sign could start a list item.
{atBreak: true, character: '+', after: '(?:[ \t\r\n])'},
// A dash can start thematic breaks, list items, and setext heading
// underlines.
{atBreak: true, character: '-', after: '(?:[ \t\r\n-])'},
// A dot could start a list item.
{atBreak: true, before: '\\d+', character: '.', after: '(?:[ \t\r\n]|$)'},
// Slash, colon, and semicolon are not used in markdown for constructs.
// A less than can start html (flow or text) or an autolink.
// HTML could start with an exclamation mark (declaration, cdata, comment),
// slash (closing tag), question mark (instruction), or a letter (tag).
// An autolink also starts with a letter.
// Finally, it could break out of a destination literal.
{atBreak: true, character: '<', after: '[!/?A-Za-z]'},
{
character: '<',
after: '[!/?A-Za-z]',
inConstruct: 'phrasing',
notInConstruct: fullPhrasingSpans
},
{character: '<', inConstruct: 'destinationLiteral'},
// An equals to can start setext heading underlines.
{atBreak: true, character: '='},
// A greater than can start block quotes and it can break out of a
// destination literal.
{atBreak: true, character: '>'},
{character: '>', inConstruct: 'destinationLiteral'},
// Question mark and at sign are not used in markdown for constructs.
// A left bracket can start definitions, references, labels,
{atBreak: true, character: '['},
{character: '[', inConstruct: 'phrasing', notInConstruct: fullPhrasingSpans},
{character: '[', inConstruct: ['label', 'reference']},
// A backslash can start an escape (when followed by punctuation) or a
// hard break (when followed by an eol).
// Note: typical escapes are handled in `safe`!
{character: '\\', after: '[\\r\\n]', inConstruct: 'phrasing'},
// A right bracket can exit labels.
{character: ']', inConstruct: ['label', 'reference']},
// Caret is not used in markdown for constructs.
// An underscore can start emphasis, strong, or a thematic break.
{atBreak: true, character: '_'},
{character: '_', inConstruct: 'phrasing', notInConstruct: fullPhrasingSpans},
// A grave accent can start code (fenced or text), or it can break out of
// a grave accent code fence.
{atBreak: true, character: '`'},
{
character: '`',
inConstruct: ['codeFencedLangGraveAccent', 'codeFencedMetaGraveAccent']
},
{character: '`', inConstruct: 'phrasing', notInConstruct: fullPhrasingSpans},
// Left brace, vertical bar, right brace are not used in markdown for
// constructs.
// A tilde can start code (fenced).
{atBreak: true, character: '~'}
];
/**
* @import {AssociationId} from '../types.js'
*/
/**
* Get an identifier from an association to match it to others.
*
* Associations are nodes that match to something else through an ID:
* <https://github.com/syntax-tree/mdast#association>.
*
* The `label` of an association is the string value: character escapes and
* references work, and casing is intact.
* The `identifier` is used to match one association to another:
* controversially, character escapes and references donβt work in this
* matching: `©` does not match `Β©`, and `\+` does not match `+`.
*
* But casing is ignored (and whitespace) is trimmed and collapsed: ` A\nb`
* matches `a b`.
* So, we do prefer the label when figuring out how weβre going to serialize:
* it has whitespace, casing, and we can ignore most useless character
* escapes and all character references.
*
* @type {AssociationId}
*/
function association(node) {
if (node.label || !node.identifier) {
return node.label || ''
}
return decodeString(node.identifier)
}
/**
* @import {CompilePattern} from '../types.js'
*/
/**
* @type {CompilePattern}
*/
function compilePattern(pattern) {
if (!pattern._compiled) {
const before =
(pattern.atBreak ? '[\\r\\n][\\t ]*' : '') +
(pattern.before ? '(?:' + pattern.before + ')' : '');
pattern._compiled = new RegExp(
(before ? '(' + before + ')' : '') +
(/[|\\{}()[\]^$+*?.-]/.test(pattern.character) ? '\\' : '') +
pattern.character +
(pattern.after ? '(?:' + pattern.after + ')' : ''),
'g'
);
}
return pattern._compiled
}
/**
* @import {Handle, Info, State} from 'mdast-util-to-markdown'
* @import {PhrasingParents} from '../types.js'
*/
/**
* Serialize the children of a parent that contains phrasing children.
*
* These children will be joined flush together.
*
* @param {PhrasingParents} parent
* Parent of flow nodes.
* @param {State} state
* Info passed around about the current state.
* @param {Info} info
* Info on where we are in the document we are generating.
* @returns {string}
* Serialized children, joined together.
*/
function containerPhrasing(parent, state, info) {
const indexStack = state.indexStack;
const children = parent.children || [];
/** @type {Array<string>} */
const results = [];
let index = -1;
let before = info.before;
/** @type {string | undefined} */
let encodeAfter;
indexStack.push(-1);
let tracker = state.createTracker(info);
while (++index < children.length) {
const child = children[index];
/** @type {string} */
let after;
indexStack[indexStack.length - 1] = index;
if (index + 1 < children.length) {
/** @type {Handle} */
// @ts-expect-error: hush, itβs actually a `zwitch`.
let handle = state.handle.handlers[children[index + 1].type];
/** @type {Handle} */
// @ts-expect-error: hush, itβs actually a `zwitch`.
if (handle && handle.peek) handle = handle.peek;
after = handle
? handle(children[index + 1], parent, state, {
before: '',
after: '',
...tracker.current()
}).charAt(0)
: '';
} else {
after = info.after;
}
// In some cases, html (text) can be found in phrasing right after an eol.
// When weβd serialize that, in most cases that would be seen as html
// (flow).
// As we canβt escape or so to prevent it from happening, we take a somewhat
// reasonable approach: replace that eol with a space.
// See: <https://github.com/syntax-tree/mdast-util-to-markdown/issues/15>
if (
results.length > 0 &&
(before === '\r' || before === '\n') &&
child.type === 'html'
) {
results[results.length - 1] = results[results.length - 1].replace(
/(\r?\n|\r)$/,
' '
);
before = ' ';
// To do: does this work to reset tracker?
tracker = state.createTracker(info);
tracker.move(results.join(''));
}
let value = state.handle(child, parent, state, {
...tracker.current(),
after,
before
});
// If we had to encode the first character after the previous node and itβs
// still the same character,
// encode it.
if (encodeAfter && encodeAfter === value.slice(0, 1)) {
value =
encodeCharacterReference(encodeAfter.charCodeAt(0)) + value.slice(1);
}
const encodingInfo = state.attentionEncodeSurroundingInfo;
state.attentionEncodeSurroundingInfo = undefined;
encodeAfter = undefined;
// If we have to encode the first character before the current node and
// itβs still the same character,
// encode it.
if (encodingInfo) {
if (
results.length > 0 &&
encodingInfo.before &&
before === results[results.length - 1].slice(-1)
) {
results[results.length - 1] =
results[results.length - 1].slice(0, -1) +
encodeCharacterReference(before.charCodeAt(0));
}
if (encodingInfo.after) encodeAfter = after;
}
tracker.move(value);
results.push(value);
before = value.slice(-1);
}
indexStack.pop();
return results.join('')
}
/**
* @import {State} from 'mdast-util-to-markdown'
* @import {FlowChildren, FlowParents, TrackFields} from '../types.js'
*/
/**
* @param {FlowParents} parent
* Parent of flow nodes.
* @param {State} state
* Info passed around about the current state.
* @param {TrackFields} info
* Info on where we are in the document we are generating.
* @returns {string}
* Serialized children, joined by (blank) lines.
*/
function containerFlow(parent, state, info) {
const indexStack = state.indexStack;
const children = parent.children || [];
const tracker = state.createTracker(info);
/** @type {Array<string>} */
const results = [];
let index = -1;
indexStack.push(-1);
while (++index < children.length) {
const child = children[index];
indexStack[indexStack.length - 1] = index;
results.push(
tracker.move(
state.handle(child, parent, state, {
before: '\n',
after: '\n',
...tracker.current()
})
)
);
if (child.type !== 'list') {
state.bulletLastUsed = undefined;
}
if (index < children.length - 1) {
results.push(
tracker.move(between(child, children[index + 1], parent, state))
);
}
}
indexStack.pop();
return results.join('')
}
/**
* @param {FlowChildren} left
* @param {FlowChildren} right
* @param {FlowParents} parent
* @param {State} state
* @returns {string}
*/
function between(left, right, parent, state) {
let index = state.join.length;
while (index--) {
const result = state.join[index](left, right, parent, state);
if (result === true || result === 1) {
break
}
if (typeof result === 'number') {
return '\n'.repeat(1 + result)
}
if (result === false) {
return '\n\n<!---->\n\n'
}
}
return '\n\n'
}
/**
* @import {IndentLines} from '../types.js'
*/
const eol = /\r?\n|\r/g;
/**
* @type {IndentLines}
*/
function indentLines(value, map) {
/** @type {Array<string>} */
const result = [];
let start = 0;
let line = 0;
/** @type {RegExpExecArray | null} */
let match;
while ((match = eol.exec(value))) {
one(value.slice(start, match.index));
result.push(match[0]);
start = match.index + match[0].length;
line++;
}
one(value.slice(start));
return result.join('')
/**
* @param {string} value
*/
function one(value) {
result.push(map(value, line, !value));
}
}
/**
* @import {SafeConfig, State} from 'mdast-util-to-markdown'
*/
/**
* Make a string safe for embedding in markdown constructs.
*
* In markdown, almost all punctuation characters can, in certain cases,
* result in something.
* Whether they do is highly subjective to where they happen and in what
* they happen.
*
* To solve this, `mdast-util-to-markdown` tracks:
*
* * Characters before and after something;
* * What βconstructsβ we are in.
*
* This information is then used by this function to escape or encode
* special characters.
*
* @param {State} state
* Info passed around about the current state.
* @param {string | null | undefined} input
* Raw value to make safe.
* @param {SafeConfig} config
* Configuration.
* @returns {string}
* Serialized markdown safe for embedding.
*/
function safe(state, input, config) {
const value = (config.before || '') + (input || '') + (config.after || '');
/** @type {Array<number>} */
const positions = [];
/** @type {Array<string>} */
const result = [];
/** @type {Record<number, {before: boolean, after: boolean}>} */
const infos = {};
let index = -1;
while (++index < state.unsafe.length) {
const pattern = state.unsafe[index];
if (!patternInScope(state.stack, pattern)) {
continue
}
const expression = state.compilePattern(pattern);
/** @type {RegExpExecArray | null} */
let match;
while ((match = expression.exec(value))) {
const before = 'before' in pattern || Boolean(pattern.atBreak);
const after = 'after' in pattern;
const position = match.index + (before ? match[1].length : 0);
if (positions.includes(position)) {
if (infos[position].before && !before) {
infos[position].before = false;
}
if (infos[position].after && !after) {
infos[position].after = false;
}
} else {
positions.push(position);
infos[position] = {before, after};
}
}
}
positions.sort(numerical);
let start = config.before ? config.before.length : 0;
const end = value.length - (config.after ? config.after.length : 0);
index = -1;
while (++index < positions.length) {
const position = positions[index];
// Character before or after matched:
if (position < start || position >= end) {
continue
}
// If this character is supposed to be escaped because it has a condition on
// the next character, and the next character is definitly being escaped,
// then skip this escape.
if (
(position + 1 < end &&
positions[index + 1] === position + 1 &&
infos[position].after &&
!infos[position + 1].before &&
!infos[position + 1].after) ||
(positions[index - 1] === position - 1 &&
infos[position].before &&
!infos[position - 1].before &&
!infos[position - 1].after)
) {
continue
}
if (start !== position) {
// If we have to use a character reference, an ampersand would be more
// correct, but as backslashes only care about punctuation, either will
// do the trick
result.push(escapeBackslashes(value.slice(start, position), '\\'));
}
start = position;
if (
/[!-/:-@[-`{-~]/.test(value.charAt(position)) &&
(!config.encode || !config.encode.includes(value.charAt(position)))
) {
// Character escape.
result.push('\\');
} else {
// Character reference.
result.push(encodeCharacterReference(value.charCodeAt(position)));
start++;
}
}
result.push(escapeBackslashes(value.slice(start, end), config.after));
return result.join('')
}
/**
* @param {number} a
* @param {number} b
* @returns {number}
*/
function numerical(a, b) {
return a - b
}
/**
* @param {string} value
* @param {string} after
* @returns {string}
*/
function escapeBackslashes(value, after) {
const expression = /\\(?=[!-/:-@[-`{-~])/g;
/** @type {Array<number>} */
const positions = [];
/** @type {Array<string>} */
const results = [];
const whole = value + after;
let index = -1;
let start = 0;
/** @type {RegExpExecArray | null} */
let match;
while ((match = expression.exec(whole))) {
positions.push(match.index);
}
while (++index < positions.length) {
if (start !== positions[index]) {
results.push(value.slice(start, positions[index]));
}
results.push('\\');
start = positions[index];
}
results.push(value.slice(start));
return results.join('')
}
/**
* @import {CreateTracker, TrackCurrent, TrackMove, TrackShift} from '../types.js'
*/
/**
* Track positional info in the output.
*
* @type {CreateTracker}
*/
function track(config) {
// Defaults are used to prevent crashes when older utilities somehow activate
// this code.
/* c8 ignore next 5 */
const options = config || {};
const now = options.now || {};
let lineShift = options.lineShift || 0;
let line = now.line || 1;
let column = now.column || 1;
return {move, current, shift}
/**
* Get the current tracked info.
*
* @type {TrackCurrent}
*/
function current() {
return {now: {line, column}, lineShift}
}
/**
* Define an increased line shift (the typical indent for lines).
*
* @type {TrackShift}
*/
function shift(value) {
lineShift += value;
}
/**
* Move past some generated markdown.
*
* @type {TrackMove}
*/
function move(input) {
// eslint-disable-next-line unicorn/prefer-default-parameters
const value = input || '';
const chunks = value.split(/\r?\n|\r/g);
const tail = chunks[chunks.length - 1];
line += chunks.length - 1;
column =
chunks.length === 1 ? column + tail.length : 1 + tail.length + lineShift;
return value
}
}
/**
* @import {Info, Join, Options, SafeConfig, State} from 'mdast-util-to-markdown'
* @import {Nodes} from 'mdast'
* @import {Enter, FlowParents, PhrasingParents, TrackFields} from './types.js'
*/
/**
* Turn an mdast syntax tree into markdown.
*
* @param {Nodes} tree
* Tree to serialize.
* @param {Options | null | undefined} [options]
* Configuration (optional).
* @returns {string}
* Serialized markdown representing `tree`.
*/
function toMarkdown(tree, options) {
const settings = options || {};
/** @type {State} */
const state = {
associationId: association,
containerPhrasing: containerPhrasingBound,
containerFlow: containerFlowBound,
createTracker: track,
compilePattern,
enter,
// @ts-expect-error: GFM / frontmatter are typed in `mdast` but not defined
// here.
handlers: {...handle},
// @ts-expect-error: add `handle` in a second.
handle: undefined,
indentLines,
indexStack: [],
join: [...join],
options: {},
safe: safeBound,
stack: [],
unsafe: [...unsafe]
};
configure(state, settings);
if (state.options.tightDefinitions) {
state.join.push(joinDefinition);
}
state.handle = zwitch('type', {
invalid,
unknown,
handlers: state.handlers
});
let result = state.handle(tree, undefined, state, {
before: '\n',
after: '\n',
now: {line: 1, column: 1},
lineShift: 0
});
if (
result &&
result.charCodeAt(result.length - 1) !== 10 &&
result.charCodeAt(result.length - 1) !== 13
) {
result += '\n';
}
return result
/** @type {Enter} */
function enter(name) {
state.stack.push(name);
return exit
/**
* @returns {undefined}
*/
function exit() {
state.stack.pop();
}
}
}
/**
* @param {unknown} value
* @returns {never}
*/
function invalid(value) {
throw new Error('Cannot handle value `' + value + '`, expected node')
}
/**
* @param {unknown} value
* @returns {never}
*/
function unknown(value) {
// Always a node.
const node = /** @type {Nodes} */ (value);
throw new Error('Cannot handle unknown node `' + node.type + '`')
}
/** @type {Join} */
function joinDefinition(left, right) {
// No blank line between adjacent definitions.
if (left.type === 'definition' && left.type === right.type) {
return 0
}
}
/**
* Serialize the children of a parent that contains phrasing children.
*
* These children will be joined flush together.
*
* @this {State}
* Info passed around about the current state.
* @param {PhrasingParents} parent
* Parent of flow nodes.
* @param {Info} info
* Info on where we are in the document we are generating.
* @returns {string}
* Serialized children, joined together.
*/
function containerPhrasingBound(parent, info) {
return containerPhrasing(parent, this, info)
}
/**
* Serialize the children of a parent that contains flow children.
*
* These children will typically be joined by blank lines.
* What they are joined by exactly is defined by `Join` functions.
*
* @this {State}
* Info passed around about the current state.
* @param {FlowParents} parent
* Parent of flow nodes.
* @param {TrackFields} info
* Info on where we are in the document we are generating.
* @returns {string}
* Serialized children, joined by (blank) lines.
*/
function containerFlowBound(parent, info) {
return containerFlow(parent, this, info)
}
/**
* Make a string safe for embedding in markdown constructs.
*
* In markdown, almost all punctuation characters can, in certain cases,
* result in something.
* Whether they do is highly subjective to where they happen and in what
* they happen.
*
* To solve this, `mdast-util-to-markdown` tracks:
*
* * Characters before and after something;
* * What βconstructsβ we are in.
*
* This information is then used by this function to escape or encode
* special characters.
*
* @this {State}
* Info passed around about the current state.
* @param {string | null | undefined} value
* Raw value to make safe.
* @param {SafeConfig} config
* Configuration.
* @returns {string}
* Serialized markdown safe for embedding.
*/
function safeBound(value, config) {
return safe(this, value, config)
}
/**
* @typedef {import('mdast').Root} Root
* @typedef {import('mdast-util-to-markdown').Options} ToMarkdownOptions
* @typedef {import('unified').Compiler<Root, string>} Compiler
* @typedef {import('unified').Processor<undefined, undefined, undefined, Root, string>} Processor
*/
/**
* Add support for serializing to markdown.
*
* @param {Readonly<Options> | null | undefined} [options]
* Configuration (optional).
* @returns {undefined}
* Nothing.
*/
function remarkStringify(options) {
/** @type {Processor} */
// @ts-expect-error: TS in JSDoc generates wrong types if `this` is typed regularly.
const self = this;
self.compiler = compiler;
/**
* @type {Compiler}
*/
function compiler(tree) {
return toMarkdown(tree, {
...self.data('settings'),
...options,
// Note: this option is not in the readme.
// The goal is for it to be set by plugins on `data` instead of being
// passed by users.
extensions: self.data('toMarkdownExtensions') || []
})
}
}
var FieldStatus = /* @__PURE__ */ ((FieldStatus2) => {
FieldStatus2["FILLED"] = "filled";
FieldStatus2["EMPTY"] = "empty";
FieldStatus2["LOGIC"] = "logic";
return FieldStatus2;
})(FieldStatus || {});
class FieldTracker {
fields = /* @__PURE__ */ new Map();
processedContent = "";
totalOccurrences = 0;
/**
* Creates a new FieldTracker instance
*/
constructor() {
}
/**
* Track a field that has been processed
*
* Registers a field with the tracking system, determining its status based on
* the provided options and storing relevant metadata for later use.
*
* @param {string} fieldName - The name/identifier of the field to track
* @param {Object} options - Options for field tracking
* @param {any} [options.value] - The processed value of the field
* @param {any} [options.originalValue] - The original unprocessed value
* @param {boolean} [options.hasLogic=false] - Whether the field contains logical operations
* @param {string} [options.mixinUsed] - Name of mixin used for processing
* @returns {void}
* @example
* ```typescript
* // Track a filled field
* tracker.trackField('client.name', {
* value: 'Acme Corporation',
* originalValue: '{{client.name}}'
* });
*
* // Track an empty field
* tracker.trackField('client.address', {
* value: '',
* originalValue: '{{client.address}}'
* });
*
* // Track a field with logic
* tracker.trackField('warranty.clause', {
* value: 'Standard warranty applies',
* originalValue: '{{#if warranty.enabled}}{{warranty.text}}{{/if}}',
* hasLogic: true,
* mixinUsed: 'warranty-mixin'
* });
* ```
*/
trackField(fieldName, options) {
const { value, originalValue, hasLogic = false, mixinUsed } = options;
let status;
if (hasLogic || mixinUsed && ["conditional", "helper", "loop"].includes(mixinUsed)) {
status = FieldStatus.LOGIC;
} else if (value === void 0 || value === null || value === "") {
status = FieldStatus.EMPTY;
} else {
status = FieldStatus.FILLED;
}
const field = {
name: fieldName,
status,
value,
originalValue,
hasLogic,
mixinUsed
};
this.fields.set(fieldName, field);
this.totalOccurrences++;
}
/**
* Apply field tracking to processed content by wrapping fields with appropriate CSS classes
*/
applyFieldTracking(content) {
let processedContent = content;
this.fields.forEach((field, fieldName) => {
const cssClass = this.getFieldCssClass(field.status);
const escapedFieldName = this.escapeRegex(fieldName).replace(/_/g, "\\\\_?");
const fieldPattern = new RegExp(`\\{\\{\\s*${escapedFieldName}\\s*\\}\\}`, "g");
processedContent = processedContent.replace(fieldPattern, (match, offset, string) => {
const beforeMatch = string.substring(0, offset);
const lastSpanOpen = beforeMatch.lastIndexOf("<span");
const lastSpanClose = beforeMatch.lastIndexOf("</span>");
if (lastSpanOpen > lastSpanClose && lastSpanOpen !== -1) {
return match;
}
const value = field.value !== void 0 && field.value !== null && field.value !== "" ? String(field.value) : match;
return `<span class="${cssClass}" data-field="${fieldName.replace(/"/g, """)}">${value}</span>`;
});
if (field.value !== void 0 && field.value !== null && field.value !== "") {
const fieldValue = String(field.value);
const shouldHighlight = field.status === "logic" || // Always highlight logic fields
fieldName.startsWith("crossref.");
if (shouldHighlight) {
const parts = processedContent.split(/(<[^>]*>)/);
for (let i = 0; i < parts.length; i++) {
if (i % 2 === 0 && parts[i] && parts[i].includes(fieldValue)) {
const textPart = parts[i];
const prevTag = i > 0 ? parts[i - 1] : "";
const nextTag = i < parts.length - 1 ? parts[i + 1] : "";
const isInsideHighlightSpan = prevTag.includes('class="highlight"') || prevTag.includes('class="imported-value"') || prevTag.includes('class="missing-value"');
const isClosedBySpan = nextTag === "</span>";
if (isInsideHighlightSpan && isClosedBySpan) {
continue;
}
const escapedValue = this.escapeRegex(fieldValue);
parts[i] = textPart.replace(new RegExp(escapedValue, "g"), (match) => {
return `<span class="${cssClass}" data-field="${fieldName.replace(/"/g, """)}">${match}</span>`;
});
}
}
processedContent = parts.join("");
}
}
});
this.processedContent = processedContent;
return processedContent;
}
/**
* Get CSS class for field based on its status
*/
getFieldCssClass(status) {
switch (status) {
case FieldStatus.FILLED:
return "legal-field imported-value";
case FieldStatus.EMPTY:
return "legal-field missing-value";
case FieldStatus.LOGIC:
return "legal-field highlight";
default:
return "";
}
}
/**
* Escape special regex characters in a string
*/
escapeRegex(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
/**
* Get all tracked fields
*/
getFields() {
return new Map(this.fields);
}
/**
* Get total number of field occurrences tracked
*/
getTotalOccurrences() {
return this.totalOccurrences;
}
/**
* Get fields by status
*/
getFieldsByStatus(status) {
return Array.from(this.fields.values()).filter((field) => field.status === status);
}
/**
* Generate a summary report of tracked fields
*/
generateReport() {
const fields = Array.from(this.fields.values());
return {
total: fields.length,
filled: fields.filter((f) => f.status === FieldStatus.FILLED).length,
empty: fields.filter((f) => f.status === FieldStatus.EMPTY).length,
logic: fields.filter((f) => f.status === FieldStatus.LOGIC).length,
fields
};
}
/**
* Clear all tracked fields
*/
clear() {
this.fields.clear();
this.processedContent = "";
this.totalOccurrences = 0;
}
}
const fieldTracker = new FieldTracker();
function getRomanNumeral(num, lowercase = false) {
if (num <= 0) return "";
const romanNumerals = [
{ value: 1e3, numeral: "M" },
{ value: 900, numeral: "CM" },
{ value: 500, numeral: "D" },
{ value: 400, numeral: "CD" },
{ value: 100, numeral: "C" },
{ value: 90, numeral: "XC" },
{ value: 50, numeral: "L" },
{ value: 40, numeral: "XL" },
{ value: 10, numeral: "X" },
{ value: 9, numeral: "IX" },
{ value: 5, numeral: "V" },
{ value: 4, numeral: "IV" },
{ value: 1, numeral: "I" }
];
let roman = "";
let remaining = num;
for (const { value, numeral } of romanNumerals) {
while (remaining >= value) {
roman += numeral;
remaining -= value;
}
}
return lowercase ? roman.toLowerCase() : roman;
}
function getAlphaLabel(num) {
if (num <= 0) return "";
let label = "";
let n = num;
while (n > 0) {
const remainder = (n - 1) % 26;
label = String.fromCharCode(97 + remainder) + label;
n = Math.floor((n - 1) / 26);
}
return label;
}
const DEFAULT_LEVEL_FORMATS = {
level1: "Article %n.",
level2: "Section %n.",
level3: "(%n)",
level4: "(%n)",
level5: "(%n%c)",
level6: "Annex %r -"
};
function updateSectionCounters(counters, level) {
switch (level) {
case 1:
counters.level1++;
counters.level2 = 0;
counters.level3 = 0;
counters.level4 = 0;
counters.level5 = 0;
counters.level6 = 0;
break;
case 2:
counters.level2++;
counters.level3 = 0;
counters.level4 = 0;
counters.level5 = 0;
counters.level6 = 0;
break;
case 3:
counters.level3++;
counters.level4 = 0;
counters.level5 = 0;
counters.level6 = 0;
break;
case 4:
counters.level4++;
counters.level5 = 0;
counters.level6 = 0;
break;
case 5:
counters.level5++;
counters.level6 = 0;
break;
case 6:
counters.level6++;
break;
}
}
function generateSectionNumber(level, counters, levelFormats) {
const format = levelFormats[`level${level}`] || `Level ${level}.`;
counters[`level${level}`];
const headerNumbers = [
counters.level1,
counters.level2,
counters.level3,
counters.level4,
counters.level5,
counters.level6
];
let formattedHeader = format;
if (level === 4) {
if (formattedHeader.includes("%n%c") && !formattedHeader.includes(".%s") && !formattedHeader.includes(".%t") && !formattedHeader.includes(".%f")) {
formattedHeader = formattedHeader.replace(/%n/g, headerNumbers[2].toString());
formattedHeader = formattedHeader.replace(/%c/g, getAlphaLabel(headerNumbers[3]));
return formattedHeader;
}
} else if (level === 5) {
if (formattedHeader.includes("%c%r") || formattedHeader.includes("%n%c%r")) {
formattedHeader = formattedHeader.replace(/%n/g, headerNumbers[2].toString());
formattedHeader = formattedHeader.replace(/%c/g, getAlphaLabel(headerNumbers[3]));
formattedHeader = formattedHeader.replace(/%r/g, getRomanNumeral(headerNumbers[4], true));
return formattedHeader;
}
}
const hasAcademicContext = levelFormats.level3 && levelFormats.level3.includes("%n.%s.%t") || levelFormats.level4 && levelFormats.level4.includes("%n.%s.%t.%f") || levelFormats.level5 && levelFormats.level5.includes("%n.%s.%t.%f.%i");
const isAcademicHierarchical = (formattedHeader.includes(".%s.%t") || formattedHeader.includes(".%t.%f") || formattedHeader.includes(".%f.%i") || formattedHeader.includes(".%s") && hasAcademicContext) && !formattedHeader.includes("%r.") && !formattedHeader.includes("%R.") && !formattedHeader.includes("%c.");
const isHierarchicalAlpha = formattedHeader.includes("%c.%n");
const isHierarchicalRoman = formattedHeader.includes("%r.%n") || formattedHeader.includes("%R.%n");
if (isAcademicHierarchical) {
formattedHeader = formattedHeader.replace(/%n/g, headerNumbers[0].toString());
} else {
formattedHeader = formattedHeader.replace(/%n/g, headerNumbers[level - 1].toString());
}
if (isAcademicHierarchical) {
formattedHeader = formattedHeader.replace(/%s/g, headerNumbers[1].toString());
} else {
formattedHeader = formattedHeader.replace(/%s/g, headerNumbers[0].toString());
}
formattedHeader = formattedHeader.replace(/%t/g, headerNumbers[2].toString());
formattedHeader = formattedHeader.replace(/%f/g, headerNumbers[3].toString());
formattedHeader = formattedHeader.replace(/%i/g, headerNumbers[4].toString());
if (isHierarchicalAlpha && level > 1) {
formattedHeader = formattedHeader.replace(/%c/g, getAlphaLabel(headerNumbers[0]));
} else {
formattedHeader = formattedHeader.replace(/%c/g, getAlphaLabel(headerNumbers[level - 1]));
}
if (isHierarchicalRoman && level > 1) {
formattedHeader = formattedHeader.replace(/%r/g, getRomanNumeral(headerNumbers[0], true));
} else {
formattedHeader = formattedHeader.replace(
/%r/g,
getRomanNumeral(headerNumbers[level - 1], true)
);
}
const isHierarchicalUppercaseRoman = formattedHeader.includes("%R.%n");
if (isHierarchicalUppercaseRoman && level > 1) {
formattedHeader = formattedHeader.replace(/%R/g, getRomanNumeral(headerNumbers[0], false));
} else {
formattedHeader = formattedHeader.replace(
/%R/g,
getRomanNumeral(headerNumbers[level - 1], false)
);
}
return formattedHeader;
}
function extractCrossReferencesFromAST(root, metadata, debug = false) {
const crossReferences = [];
const sectionCounters = {
level1: 0,
level2: 0,
level3: 0,
level4: 0,
level5: 0,
level6: 0
};
const levelFormats = {
level1: metadata["level1"] || metadata["level-one"] || DEFAULT_LEVEL_FORMATS.level1,
level2: metadata["level2"] || metadata["level-two"] || DEFAULT_LEVEL_FORMATS.level2,
level3: metadata["level3"] || metadata["level-three"] || DEFAULT_LEVEL_FORMATS.level3,
level4: metadata["level4"] || metadata["level-four"] || DEFAULT_LEVEL_FORMATS.level4,
level5: metadata["level5"] || metadata["level-five"] || DEFAULT_LEVEL_FORMATS.level5,
level6: metadata["level6"] || metadata["level-six"] || DEFAULT_LEVEL_FORMATS.level6
};
visit(root, "heading", (node) => {
const headingText = toString(node);
const crossRefMatch = headingText.match(/^(?:l+\.\s+)?(.+?)\s+\|([^|]+)\|$/);
if (crossRefMatch) {
const [, headerContent, key] = crossRefMatch;
const level = node.depth;
updateSectionCounters(sectionCounters, level);
const sectionNumber = generateSectionNumber(level, sectionCounters, levelFormats);
const cleanHeaderText = headerContent.replace(/^\*\*|\*\*$/g, "").trim();
const sectionText = `${sectionNumber} ${cleanHeaderText}`;
crossReferences.push({
key: key.trim(),
level,
sectionNumber,
sectionText: sectionText.trim(),
headerText: cleanHeaderText,
position: node.position ? {
line: node.position.start.line,
column: node.position.start.column
} : void 0
});
}
});
return crossReferences;
}
function cleanHeaderDefinitionsInAST(root, crossReferences) {
const definitionMap = /* @__PURE__ */ new Map();
crossReferences.forEach((ref) => definitionMap.set(ref.key, ref));
visit(root, "heading", (node) => {
const headingText = toString(node);
const definitionMatch = headingText.match(/^(.+?)\s+\|([^|]+)\|$/);
if (definitionMatch) {
const [, headerContent, key] = definitionMatch;
const trimmedKey = key.trim();
if (definitionMap.has(trimmedKey)) {
visit(node, "text", (textNode) => {
const originalValue = textNode.value;
const modifiedValue = originalValue.replace(/\s+\|([^|]+)\|$/, (match, matchedKey) => {
const trimmedMatchedKey = matchedKey.trim();
if (trimmedMatchedKey === trimmedKey && definitionMap.has(trimmedMatchedKey)) {
return "";
}
return match;
});
if (modifiedValue !== originalValue) {
textNode.value = modifiedValue.trim();
}
});
}
}
});
}
function replaceCrossReferencesInAST(root, crossReferences, metadata, enableFieldTracking = false) {
const referenceMap = /* @__PURE__ */ new Map();
for (const ref of crossReferences) {
referenceMap.set(ref.key, ref.sectionNumber);
}
visit(root, "text", (node, index, parent) => {
if (parent && parent.type === "heading") {
const headingText = toString(parent);
if (headingText.match(/\|[^|]+\|$/)) {
return;
}
}
const originalValue = node.value;
let modifiedValue = originalValue;
let hasReplacements = false;
modifiedValue = modifiedValue.replace(/\|([^|]+)\|/g, (match, key) => {
const trimmedKey = key.trim();
const sectionNumber = referenceMap.get(trimmedKey);
if (sectionNumber) {
fieldTracker.trackField(`crossref.${trimmedKey}`, {
value: sectionNumber,
originalValue: match,
hasLogic: true
});
hasReplacements = true;
return formatCrossRefValue(sectionNumber, trimmedKey, enableFieldTracking, true);
}
const metadataValue = getNestedValue$2(metadata, trimmedKey);
if (metadataValue !== void 0) {
const resolvedValue = formatMetadataValue(metadataValue, trimmedKey, metadata);
fieldTracker.trackField(`crossref.${trimmedKey}`, {
value: resolvedValue,
originalValue: match,
hasLogic: false
});
hasReplacements = true;
return formatCrossRefValue(resolvedValue, trimmedKey, enableFieldTracking, false);
}
fieldTracker.trackField(`crossref.${trimmedKey}`, {
value: "",
originalValue: match,
hasLogic: false
});
return enableFieldTracking ? formatCrossRefValue("", trimmedKey, enableFieldTracking, false) : match;
});
if (hasReplacements) {
if (enableFieldTracking && modifiedValue.includes("<span")) {
if (parent && typeof index === "number") {
const htmlNode = {
type: "html",
value: modifiedValue
};
parent.children[index] = htmlNode;
}
} else {
node.value = modifiedValue;
}
}
});
visit(root, "html", (node) => {
if (!node.value || !node.value.includes("|")) {
return;
}
let modifiedValue = node.value;
let hasReplacements = false;
modifiedValue = modifiedValue.replace(/\|([^|]+)\|/g, (match, key) => {
const trimmedKey = key.trim();
const sectionNumber = referenceMap.get(trimmedKey);
if (sectionNumber) {
fieldTracker.trackField(`crossref.${trimmedKey}`, {
value: sectionNumber,
originalValue: match,
hasLogic: true
});
hasReplacements = true;
if (enableFieldTracking) {
const cssClass = getCrossRefCssClass(true, true);
return `<span class="${cssClass}" data-field="crossref.${trimmedKey.replace(/"/g, """)}">${sectionNumber}</span>`;
} else {
return sectionNumber;
}
}
const metadataValue = getNestedValue$2(metadata, trimmedKey);
if (metadataValue !== void 0) {
const resolvedValue = formatMetadataValue(metadataValue, trimmedKey, metadata);
fieldTracker.trackField(`crossref.${trimmedKey}`, {
value: resolvedValue,
originalValue: match,
hasLogic: false
});
hasReplacements = true;
if (enableFieldTracking) {
const cssClass = getCrossRefCssClass(true, false);
return `<span class="${cssClass}" data-field="crossref.${trimmedKey.replace(/"/g, """)}">${resolvedValue}</span>`;
} else {
return resolvedValue;
}
}
fieldTracker.trackField(`crossref.${trimmedKey}`, {
value: "",
originalValue: match,
hasLogic: false
});
if (enableFieldTracking) {
const cssClass = getCrossRefCssClass(false, false);
return `<span class="${cssClass}" data-field="crossref.${trimmedKey.replace(/"/g, """)}">${match}</span>`;
} else {
return match;
}
});
if (hasReplacements) {
node.value = modifiedValue;
}
});
}
function getCrossRefCssClass(hasValue, hasLogic) {
if (!hasValue) {
return "legal-field missing-value";
}
if (hasLogic) {
return "legal-field highlight";
}
return "legal-field imported-value";
}
function formatCrossRefValue(value, fieldName, enableFieldTracking = false, hasLogic = false) {
if (!enableFieldTracking) {
return value;
}
const hasValue = value !== "";
const cssClass = getCrossRefCssClass(hasValue, hasLogic);
return `<span class="${cssClass}" data-field="crossref.${fieldName.replace(/"/g, """)}">${value}</span>`;
}
function getNestedValue$2(obj, path) {
const keys = path.split(".");
let value = obj;
for (const key of keys) {
if (value === void 0 || value === null) {
return void 0;
}
value = value[key];
}
return value;
}
function formatMetadataValue(value, key, metadata) {
if (value === void 0) {
return "";
}
if (value === null) {
return "null";
}
if (value instanceof Date) {
return value.toISOString().split("T")[0];
}
if (typeof value === "number" && key.includes("amount")) {
const currency = metadata.payment_currency || "USD";
return new Intl.NumberFormat("en-US", {
style: "currency",
currency
}).format(value);
}
return String(value);
}
const remarkCrossReferences = (options) => {
const { metadata, debug = false, enableFieldTracking = false } = options;
return (tree) => {
if (debug) {
console.log("π Processing cross-references with remark plugin");
}
const crossReferences = extractCrossReferencesFromAST(tree, metadata, debug);
if (debug) {
console.log(
`Found ${crossReferences.length} cross-reference definitions:`,
crossReferences.map((ref) => `${ref.key} -> ${ref.sectionNumber}`)
);
}
metadata["_cross_references"] = crossReferences.map((ref) => ({
key: ref.key,
sectionNumber: ref.sectionNumber,
sectionText: ref.sectionText
}));
if (crossReferences.length === 0) {
metadata["_cross_references"] = [];
}
cleanHeaderDefinitionsInAST(tree, crossReferences);
if (debug) {
console.log("π Starting cross-reference replacement in content...");
}
replaceCrossReferencesInAST(tree, crossReferences, metadata, enableFieldTracking);
if (debug) {
console.log("β
Cross-reference processing completed");
}
};
};
function addYears(date, years) {
if (!date) {
throw new Error("Date is required for addYears");
}
const d = typeof date === "string" ? new Date(date) : new Date(date);
if (isNaN(d.getTime())) {
throw new Error(`Invalid date: ${date}`);
}
d.setFullYear(d.getFullYear() + years);
return d;
}
function addDays(date, days) {
if (!date) {
throw new Error("Date is required for addDays");
}
const d = typeof date === "string" ? new Date(date) : new Date(date);
if (isNaN(d.getTime())) {
throw new Error(`Invalid date: ${date}`);
}
d.setDate(d.getDate() + days);
return d;
}
function addMonths(date, months) {
if (!date) {
throw new Error("Date is required for addMonths");
}
const d = typeof date === "string" ? new Date(date) : new Date(date);
if (isNaN(d.getTime())) {
throw new Error(`Invalid date: ${date}`);
}
d.setMonth(d.getMonth() + months);
return d;
}
function formatDate(date, format = "YYYY-MM-DD") {
const d = typeof date === "string" ? new Date(date) : date;
if (isNaN(d.getTime())) {
throw new Error(`Invalid date: ${date}`);
}
const year = d.getFullYear();
const month = d.getMonth();
const day = d.getDate();
const dayOfWeek = d.getDay();
const monthNames = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
];
const monthNamesShort = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
];
const monthNamesSpanish = [
"enero",
"febrero",
"marzo",
"abril",
"mayo",
"junio",
"julio",
"agosto",
"septiembre",
"octubre",
"noviembre",
"diciembre"
];
const dayNames = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
const dayNamesShort = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
const replacements = {
YYYY: String(year),
YY: String(year).slice(-2),
MMMM: monthNames[month],
MMMM_ES: monthNamesSpanish[month],
MMM: monthNamesShort[month],
MM: String(month + 1).padStart(2, "0"),
M: String(month + 1),
DD: String(day).padStart(2, "0"),
D: String(day),
Do: addOrdinalSuffix(day),
dddd: dayNames[dayOfWeek],
ddd: dayNamesShort[dayOfWeek]
};
let result = format;
const sortedTokens = Object.keys(replacements).sort((a, b) => b.length - a.length);
for (const token of sortedTokens) {
const replacement = replacements[token];
const isShortToken = token.length <= 2;
const regex = isShortToken ? new RegExp(`\\b${token}\\b`, "g") : new RegExp(token, "g");
result = result.replace(regex, replacement);
}
return result;
}
function addOrdinalSuffix(num) {
const suffix = ["th", "st", "nd", "rd"];
const value = num % 100;
if (value >= 11 && value <= 13) {
return num + suffix[0];
}
const lastDigit = value % 10;
return num + (suffix[lastDigit] || suffix[0]);
}
const DateFormats = {
/** Legal format: "16th day of July, 2025" */
LEGAL: "Do day of MMMM, YYYY",
/** Formal format: "Wednesday, July 16th, 2025" */
FORMAL: "dddd, MMMM Do, YYYY",
/** Spanish format: "16 de julio de 2025" */
SPANISH: "D de MMMM_ES de YYYY",
/** US format: "07/16/2025" */
US: "MM/DD/YYYY",
/** European format: "16/07/2025" */
EU: "DD/MM/YYYY",
/** ISO format: "2025-07-16" */
ISO: "YYYY-MM-DD",
/** Long format: "July 16, 2025" */
LONG: "MMMM D, YYYY",
/** Short format: "Jul 16, 2025" */
SHORT: "MMM D, YYYY",
/** Year only: "2025" */
YEAR: "YYYY",
/** Month and year: "July 2025" */
MONTH_YEAR: "MMMM YYYY"
};
function formatInteger(value, separator = ",") {
const num = typeof value === "string" ? parseFloat(value) : value;
if (isNaN(num)) return String(value);
return Math.floor(num).toString().replace(/\B(?=(\d{3})+(?!\d))/g, separator);
}
function formatPercent(value, decimals = 2, symbol = true) {
const num = typeof value === "string" ? parseFloat(value) : value;
if (isNaN(num)) return String(value);
const percentage = num * 100;
const formatted = percentage.toFixed(decimals);
return symbol ? `${formatted}%` : formatted;
}
function formatCurrency(value, currency = "EUR", decimals = 2) {
const num = typeof value === "string" ? parseFloat(value) : value;
if (isNaN(num)) return String(value);
const symbols = {
EUR: "β¬",
USD: "$",
GBP: "Β£"
};
const formatted = num.toFixed(decimals).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
const symbol = symbols[currency] || currency;
if (currency === "EUR") {
return `${formatted} ${symbol}`;
} else {
return `${symbol}${formatted}`;
}
}
function formatEuro(value, decimals = 2) {
return formatCurrency(value, "EUR", decimals);
}
function formatDollar(value, decimals = 2) {
return formatCurrency(value, "USD", decimals);
}
function formatPound(value, decimals = 2) {
return formatCurrency(value, "GBP", decimals);
}
function numberToWords(num) {
const n = typeof num === "string" ? parseFloat(num) : num;
if (isNaN(n)) return String(num);
if (n === 0) return "zero";
const ones = ["", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine"];
const tens = [
"",
"",
"twenty",
"thirty",
"forty",
"fifty",
"sixty",
"seventy",
"eighty",
"ninety"
];
const teens = [
"ten",
"eleven",
"twelve",
"thirteen",
"fourteen",
"fifteen",
"sixteen",
"seventeen",
"eighteen",
"nineteen"
];
const convertMillions = (num2) => {
if (num2 >= 1e6) {
return convertMillions(Math.floor(num2 / 1e6)) + " million " + convertThousands(num2 % 1e6);
} else {
return convertThousands(num2);
}
};
const convertThousands = (num2) => {
if (num2 >= 1e3) {
return convertHundreds(Math.floor(num2 / 1e3)) + " thousand " + convertHundreds(num2 % 1e3);
} else {
return convertHundreds(num2);
}
};
const convertHundreds = (num2) => {
let str = "";
if (num2 > 99) {
str += ones[Math.floor(num2 / 100)] + " hundred ";
num2 %= 100;
}
if (num2 > 19) {
str += tens[Math.floor(num2 / 10)] + " ";
num2 %= 10;
} else if (num2 > 9) {
str += teens[num2 - 10] + " ";
return str.trim();
}
if (num2 > 0) {
str += ones[num2] + " ";
}
return str.trim();
};
if (n < 0) {
return "negative " + numberToWords(Math.abs(n));
}
const integerPart = Math.floor(n);
const decimalPart = Math.round((n - integerPart) * 100);
let result = convertMillions(integerPart);
if (decimalPart > 0) {
result += " and " + convertHundreds(decimalPart) + " cents";
}
return result.trim();
}
function round(value, decimals = 0) {
const num = typeof value === "string" ? parseFloat(value) : value;
if (isNaN(num)) return 0;
const factor = Math.pow(10, decimals);
return Math.round(num * factor) / factor;
}
function capitalize(str) {
if (!str) return "";
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
}
function capitalizeWords(str) {
if (!str) return "";
return str.split(" ").map((word) => capitalize(word)).join(" ");
}
function upper(str) {
return str ? str.toUpperCase() : "";
}
function lower(str) {
return str ? str.toLowerCase() : "";
}
function titleCase(str) {
if (!str) return "";
const smallWords = /* @__PURE__ */ new Set([
"a",
"an",
"and",
"as",
"at",
"but",
"by",
"for",
"if",
"in",
"nor",
"of",
"on",
"or",
"so",
"the",
"to",
"up",
"yet"
]);
return str.split(" ").map((word, index) => {
const lowerWord = word.toLowerCase();
if (index === 0 || index === str.split(" ").length - 1) {
return capitalize(word);
}
if (smallWords.has(lowerWord)) {
return lowerWord;
}
return capitalize(word);
}).join(" ");
}
function kebabCase(str) {
if (!str) return "";
return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
}
function snakeCase(str) {
if (!str) return "";
return str.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[\s-]+/g, "_").toLowerCase();
}
function camelCase(str) {
if (!str) return "";
return str.split(/[\s-_]+/).map((word, index) => {
if (index === 0) {
return word.toLowerCase();
}
return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
}).join("");
}
function pascalCase(str) {
if (!str) return "";
const camel = camelCase(str);
return camel.charAt(0).toUpperCase() + camel.slice(1);
}
function truncate(str, length, suffix = "...") {
if (!str) return "";
if (str.length <= length) return str;
return str.slice(0, length - suffix.length) + suffix;
}
function clean(str) {
if (!str) return "";
return str.replace(/\s+/g, " ").trim();
}
function pluralize(word, count, plural) {
if (count === 1) return word;
if (plural) return plural;
const rules = [
[/s$/i, "s"],
[/([^aeiou])y$/i, "$1ies"],
[/(x|z|s|sh|ch)$/i, "$1es"],
[/$/i, "s"]
];
for (const [pattern, replacement] of rules) {
if (pattern.test(word)) {
return word.replace(pattern, replacement);
}
}
return word + "s";
}
function padStart$1(str, length, char = " ") {
if (!str) return char.repeat(length);
return str.padStart(length, char);
}
function padEnd(str, length, char = " ") {
if (!str) return char.repeat(length);
return str.padEnd(length, char);
}
function contains(str, substring, caseSensitive = false) {
if (!str || !substring) return false;
if (caseSensitive) {
return str.includes(substring);
}
return str.toLowerCase().includes(substring.toLowerCase());
}
function replaceAll(str, search, replace) {
if (!str) return "";
return str.split(search).join(replace);
}
function initials(name) {
if (!name) return "";
return name.split(" ").map((word) => word.charAt(0).toUpperCase()).join("");
}
const extensionHelpers = {
// Advanced date helpers
addYears,
addMonths,
addDays,
formatDate,
DateFormats,
// Number helpers
formatInteger,
formatPercent,
formatCurrency,
formatEuro,
formatDollar,
formatPound,
numberToWords,
round,
// String helpers
capitalize,
capitalizeWords,
upper,
lower,
titleCase,
kebabCase,
snakeCase,
camelCase,
pascalCase,
truncate,
clean,
pluralize,
padStart: padStart$1,
padEnd,
contains,
replaceAll,
initials
};
function detectBracketValues(metadata, prefix = "") {
const bracketFields = /* @__PURE__ */ new Set();
function traverse(obj, currentPath) {
if (typeof obj === "string") {
if (obj.match(/^\[.*\]$/)) {
bracketFields.add(currentPath);
}
} else if (obj && typeof obj === "object" && !Array.isArray(obj)) {
for (const [key, value] of Object.entries(obj)) {
const newPath = currentPath ? `${currentPath}.${key}` : key;
traverse(value, newPath);
}
} else if (Array.isArray(obj)) {
obj.forEach((item, index) => {
const newPath = `${currentPath}[${index}]`;
traverse(item, newPath);
});
}
}
traverse(metadata, prefix);
return bracketFields;
}
const DEFAULT_FIELD_PATTERN = /\{\{\s*([^}]+)\s*\}\}/g;
const TODAY_PATTERN = /@today(?:\[([^\]]+)\])?/g;
function isInsideLoopOrConditional(text, position) {
const blockPattern = /\{\{#([\w_]+)\}\}[\s\S]*?\{\{\/\1\}\}/g;
let match;
while ((match = blockPattern.exec(text)) !== null) {
const blockStart = match.index;
const blockEnd = match.index + match[0].length;
if (position > blockStart && position < blockEnd) {
return true;
}
}
const ifBlockPattern = /\{\{#if\s+[^}]+\}\}[\s\S]*?\{\{\/if\}\}/g;
while ((match = ifBlockPattern.exec(text)) !== null) {
const blockStart = match.index;
const blockEnd = match.index + match[0].length;
if (position > blockStart && position < blockEnd) {
return true;
}
}
return false;
}
function extractTemplateFields(text, patterns) {
const fields = [];
const regexPatterns = patterns.length > 0 ? patterns.map((p) => new RegExp(p, "g")) : [DEFAULT_FIELD_PATTERN];
for (const regex of regexPatterns) {
let match;
regex.lastIndex = 0;
while ((match = regex.exec(text)) !== null) {
const [fullMatch, fieldExpression] = match;
const trimmedExpression = fieldExpression.trim();
if (trimmedExpression.startsWith("#") || trimmedExpression.startsWith("/") || trimmedExpression === "else") {
continue;
}
if (isInsideLoopOrConditional(text, match.index)) {
continue;
}
fields.push({
pattern: fullMatch,
fieldName: trimmedExpression,
expression: trimmedExpression,
startIndex: match.index,
endIndex: match.index + fullMatch.length
});
}
}
let todayMatch;
TODAY_PATTERN.lastIndex = 0;
while ((todayMatch = TODAY_PATTERN.exec(text)) !== null) {
const [fullMatch, formatSpecifier] = todayMatch;
const matchStart = todayMatch.index;
const matchEnd = todayMatch.index + fullMatch.length;
let isInsideTemplateField = false;
const templateFieldPattern = /\{\{[^}]*\}\}/g;
let templateMatch;
templateFieldPattern.lastIndex = 0;
while ((templateMatch = templateFieldPattern.exec(text)) !== null) {
const templateStart = templateMatch.index;
const templateEnd = templateMatch.index + templateMatch[0].length;
if (matchStart >= templateStart && matchEnd <= templateEnd) {
isInsideTemplateField = true;
break;
}
}
if (!isInsideTemplateField) {
const fieldName = formatSpecifier ? `@today[${formatSpecifier}]` : "@today";
fields.push({
pattern: fullMatch,
fieldName,
expression: fieldName,
startIndex: todayMatch.index,
endIndex: todayMatch.index + fullMatch.length
});
}
}
return fields.sort((a, b) => b.startIndex - a.startIndex);
}
function resolveFieldValue(fieldName, metadata) {
const bracketFields = detectBracketValues(metadata);
const isBracketValue = bracketFields.has(fieldName);
if (fieldName === "@today" || fieldName.startsWith("@today[")) {
const today = metadata["@today"] ? new Date(metadata["@today"]) : /* @__PURE__ */ new Date();
let formattedDate;
if (fieldName === "@today") {
formattedDate = today.toISOString().split("T")[0];
} else {
const formatMatch = fieldName.match(/@today\[([^\]]+)\]/);
const format = formatMatch ? formatMatch[1] : "";
switch (format.toLowerCase()) {
case "iso":
formattedDate = today.toISOString().split("T")[0];
break;
case "long":
formattedDate = today.toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric"
});
break;
case "european":
formattedDate = today.toLocaleDateString("en-GB");
break;
case "legal":
formattedDate = today.toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric"
});
break;
case "medium":
formattedDate = today.toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "numeric"
});
break;
default:
formattedDate = today.toISOString().split("T")[0];
break;
}
}
return {
value: formattedDate,
hasLogic: true,
mixinType: "helper"
};
}
const helperMatch = fieldName.match(/^(\w+)\((.*)?\)$/);
if (helperMatch) {
const [, helperName, argsString] = helperMatch;
const helper = extensionHelpers[helperName];
if (helper && typeof helper === "function") {
try {
const args = parseHelperArguments$1(argsString || "", metadata);
const result = helper(...args);
return {
value: result,
hasLogic: true,
mixinType: "helper"
};
} catch (error) {
console.warn(`Error calling helper '${helperName}':`, error);
return {
value: void 0,
hasLogic: true,
mixinType: "helper"
};
}
} else {
return {
value: void 0,
hasLogic: true,
mixinType: "helper"
};
}
}
const conditionalMatch = fieldName.match(/(.+?)\s*\?\s*(.+?)\s*:\s*(.+)/);
if (conditionalMatch) {
const [, condition, trueValue, falseValue] = conditionalMatch;
const conditionResult = resolveNestedValue(metadata, condition.trim());
const result = conditionResult ? trueValue.trim() : falseValue.trim();
const cleanResult = result.replace(/^["']|["']$/g, "");
return {
value: cleanResult,
hasLogic: true,
mixinType: "conditional"
};
}
const value = resolveNestedValue(metadata, fieldName);
if (isBracketValue) {
return {
value: void 0,
hasLogic: false,
mixinType: "variable"
};
}
return {
value,
hasLogic: false,
mixinType: "variable"
};
}
function resolveNestedValue(metadata, path) {
const keys = path.split(".");
let current = metadata;
for (const key of keys) {
if (current === null || current === void 0) {
return void 0;
}
const arrayMatch = key.match(/^(.+?)\[(\d+)\]$/);
if (arrayMatch) {
const [, arrayName, index] = arrayMatch;
current = current[arrayName];
if (Array.isArray(current)) {
current = current[parseInt(index, 10)];
} else {
return void 0;
}
} else {
current = current[key];
}
}
return current;
}
function isEmptyValue(value) {
return value === void 0 || value === null || value === "" || typeof value === "string" && value.trim() === "";
}
function getFieldCssClass(status) {
switch (status) {
case "filled":
return "legal-field imported-value";
case "empty":
return "legal-field missing-value";
case "logic":
return "legal-field highlight";
default:
return "legal-field imported-value";
}
}
function formatFieldValue(value, fieldName, enableFieldTracking = false, hasLogic = false, isEmptyField = false) {
const formattedValue = (() => {
if (isEmptyValue(value)) {
return `{{${fieldName}}}`;
}
if (typeof value === "boolean") {
return value.toString();
}
if (typeof value === "number") {
return value.toString();
}
if (value instanceof Date) {
return value.toISOString().split("T")[0];
}
return String(value);
})();
if (enableFieldTracking) {
const status = hasLogic ? "logic" : isEmptyField ? "empty" : "filled";
const cssClass = getFieldCssClass(status);
return `<span class="${cssClass}" data-field="${fieldName.replace(/"/g, """)}">${formattedValue}</span>`;
}
return formattedValue;
}
function hasExistingFieldSpans(text) {
return text.includes('class="legal-field') && text.includes('data-field="');
}
function isInsideFieldTrackingSpan(node, parent) {
if (!parent || parent.type !== "paragraph" || !parent.children) {
return false;
}
const nodeIndex = parent.children.indexOf(node);
if (nodeIndex === -1) {
return false;
}
let hasOpeningSpan = false;
for (let i = nodeIndex - 1; i >= 0; i--) {
const prevNode = parent.children[i];
if (prevNode.type === "html" && prevNode.value.includes('class="legal-field') && prevNode.value.includes('data-field="')) {
hasOpeningSpan = true;
break;
}
}
let hasClosingSpan = false;
for (let i = nodeIndex + 1; i < parent.children.length; i++) {
const nextNode = parent.children[i];
if (nextNode.type === "html" && nextNode.value.includes("</span>")) {
hasClosingSpan = true;
break;
}
}
return hasOpeningSpan && hasClosingSpan;
}
function smartSplitArguments(str) {
const parts = [];
let current = "";
let inQuotes = false;
let quoteChar = "";
let parenDepth = 0;
for (let i = 0; i < str.length; i++) {
const char = str[i];
if ((char === '"' || char === "'") && !inQuotes) {
inQuotes = true;
quoteChar = char;
current += char;
} else if (char === quoteChar && inQuotes) {
inQuotes = false;
quoteChar = "";
current += char;
} else if (char === "(" && !inQuotes) {
parenDepth++;
current += char;
} else if (char === ")" && !inQuotes) {
parenDepth--;
current += char;
} else if (char === "," && !inQuotes && parenDepth === 0) {
parts.push(current);
current = "";
} else {
current += char;
}
}
if (current) {
parts.push(current);
}
return parts;
}
function parseHelperArguments$1(argsString, metadata) {
if (!argsString.trim()) {
return [];
}
const args = [];
const parts = smartSplitArguments(argsString);
for (const part of parts) {
const trimmed = part.trim();
if (!trimmed) {
continue;
}
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
args.push(trimmed.slice(1, -1));
continue;
}
if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
args.push(parseFloat(trimmed));
continue;
}
if (trimmed === "true") {
args.push(true);
continue;
}
if (trimmed === "false") {
args.push(false);
continue;
}
if (trimmed === "null") {
args.push(null);
continue;
}
if (trimmed === "undefined") {
args.push(void 0);
continue;
}
const nestedHelperMatch = trimmed.match(/^(\w+)\((.*)\)$/);
if (nestedHelperMatch) {
const [, helperName, nestedArgsString] = nestedHelperMatch;
const helper = extensionHelpers[helperName];
if (helper && typeof helper === "function") {
try {
const nestedArgs = parseHelperArguments$1(nestedArgsString, metadata);
const nestedResult = helper(...nestedArgs);
args.push(nestedResult);
continue;
} catch (error) {
console.warn(`Error calling nested helper '${helperName}':`, error);
}
}
}
if (trimmed === "@today" || trimmed.startsWith("@today[")) {
const todayValue = metadata["@today"] ? new Date(metadata["@today"]) : /* @__PURE__ */ new Date();
args.push(todayValue);
continue;
}
const value = resolveNestedValue(metadata, trimmed);
args.push(value);
}
return args;
}
function processTemplateFieldsInAST(root, metadata, fieldPatterns, enableFieldTracking = false, debug = false) {
const fieldMappings = metadata["_field_mappings"] || /* @__PURE__ */ new Map();
visit(root, (node, index, parent) => {
if ((node.type === "text" || node.type === "html") && "value" in node && typeof node.value === "string") {
const originalValue = node.value;
if (node.type === "html" && hasExistingFieldSpans(originalValue)) {
if (debug) {
console.log(
`βοΈ Skipping HTML node with existing field spans: "${originalValue.substring(0, 100)}..."`
);
}
return;
}
if (node.type === "text" && isInsideFieldTrackingSpan(node, parent)) {
if (debug) {
console.log(`βοΈ Skipping text node inside existing field spans: "${originalValue}"`);
}
return;
}
const templateFields = extractTemplateFields(originalValue, fieldPatterns);
if (templateFields.length === 0) {
return;
}
if (debug) {
console.log(
`π Found ${templateFields.length} template fields in ${node.type}: "${originalValue}"`
);
}
let processedText = originalValue;
for (const field of templateFields) {
const { value, hasLogic, mixinType } = resolveFieldValue(field.fieldName, metadata);
const isEmptyField = isEmptyValue(value);
const formattedValue = formatFieldValue(
value,
field.fieldName,
enableFieldTracking,
hasLogic,
isEmptyField
);
const originalPattern = fieldMappings.get(field.pattern) || field.pattern;
fieldTracker.trackField(field.fieldName, {
value,
// Pass the original value, not the formatted one
originalValue: originalPattern,
hasLogic,
mixinUsed: mixinType
});
processedText = processedText.substring(0, field.startIndex) + formattedValue + processedText.substring(field.endIndex);
if (debug) {
console.log(
`π Replaced ${field.pattern} with "${formattedValue}" (original: ${originalPattern})`
);
}
}
node.value = processedText;
if (enableFieldTracking && node.type === "text" && processedText.includes('<span class="legal-field')) {
node.type = "html";
}
}
});
}
const remarkTemplateFields = (options) => {
const { metadata, debug = false, fieldPatterns = [], enableFieldTracking = false } = options;
return (tree) => {
if (debug) {
console.log("π Processing template fields with remark plugin");
console.log("π Metadata:", metadata);
console.log("π Field patterns:", fieldPatterns);
if (enableFieldTracking) {
console.log("π― Field tracking highlighting enabled");
}
}
processTemplateFieldsInAST(tree, metadata, fieldPatterns, enableFieldTracking, debug);
if (debug) {
console.log("β
Template field processing completed");
}
};
};
const remarkHeaders = (options) => {
const { metadata = {}, noReset = false, noIndent = false, debug = false } = options;
return (tree) => {
if (debug) {
console.log("[remarkHeaders] Processing headers with options:", options);
console.log("[remarkHeaders] Metadata:", metadata);
}
const config = extractHeaderConfig(metadata);
const state = initializeHeaderState();
let headingCount = 0;
visit(tree, "heading", (node) => {
if (node.data?.isLegalHeader) {
headingCount++;
}
});
if (debug) {
console.log(`[remarkHeaders] Found ${headingCount} legal headings in document`);
}
visit(tree, "heading", (node, index, parent) => {
if (node.data?.isLegalHeader) {
if (debug) {
console.log(
`[remarkHeaders] Processing legal heading at depth ${node.depth}:`,
extractTextContent(node)
);
}
processHeader(node, config, state, { noReset, noIndent, debug });
}
});
visit(tree, "heading", (node, index, parent) => {
if (node.__needsHtmlReplacement && parent && typeof index === "number") {
if (debug) {
console.log("[remarkHeaders] Replacing heading with HTML node to preserve indentation");
}
const htmlNode = {
type: "html",
value: node.__htmlContent
};
parent.children[index] = htmlNode;
}
});
if (debug) {
console.log("[remarkHeaders] Final header state:", state);
}
};
};
function extractHeaderConfig(metadata) {
const getFirstDefined = (...keys) => {
for (const key of keys) {
if (key in metadata) {
return metadata[key];
}
}
return null;
};
return {
levelOne: getFirstDefined("level-1", "level-one", "level_one"),
levelTwo: getFirstDefined("level-2", "level-two", "level_two"),
levelThree: getFirstDefined("level-3", "level-three", "level_three"),
levelFour: getFirstDefined("level-4", "level-four", "level_four"),
levelFive: getFirstDefined("level-5", "level-five", "level_five"),
levelSix: getFirstDefined("level-6", "level-six", "level_six"),
levelSeven: getFirstDefined("level-7", "level-seven", "level_seven"),
levelEight: getFirstDefined("level-8", "level-eight", "level_eight"),
levelNine: getFirstDefined("level-9", "level-nine", "level_nine"),
customFormats: /* @__PURE__ */ new Map()
};
}
function initializeHeaderState() {
return {
levelOne: 0,
levelTwo: 0,
levelThree: 0,
levelFour: 0,
levelFive: 0,
levelSix: 0,
levelSeven: 0,
levelEight: 0,
levelNine: 0,
customLevels: /* @__PURE__ */ new Map()
};
}
function processHeader(node, config, state, options) {
const { noReset, noIndent, debug } = options;
const level = node.depth;
const format = getHeaderFormat(level, config);
updateHeaderState(level, state, noReset);
const number = getHeaderNumber(level, state);
const headerText = formatHeaderText(node, format, number, state, { noIndent, debug });
if (headerText !== null) {
const hasIndentation = headerText.startsWith(" ");
if (hasIndentation) {
node.__needsHtmlReplacement = true;
node.__htmlContent = `${"#".repeat(level)} ${headerText}`;
}
updateHeaderNode(node, headerText);
}
if (debug) {
console.log(`[remarkHeaders] Processed level ${level} header:`, headerText);
}
}
function getHeaderFormat(level, config) {
let format = null;
switch (level) {
case 1:
format = config.levelOne;
break;
case 2:
format = config.levelTwo;
break;
case 3:
format = config.levelThree;
break;
case 4:
format = config.levelFour;
break;
case 5:
format = config.levelFive;
break;
case 6:
format = config.levelSix;
break;
case 7:
format = config.levelSeven;
break;
case 8:
format = config.levelEight;
break;
case 9:
format = config.levelNine;
break;
default:
format = null;
}
if (format === null || format === void 0) {
return `{{undefined-level-${level}}}`;
}
return format;
}
function updateHeaderState(level, state, noReset) {
switch (level) {
case 1:
state.levelOne++;
if (!noReset) {
state.levelTwo = 0;
state.levelThree = 0;
state.levelFour = 0;
state.levelFive = 0;
state.levelSix = 0;
state.levelSeven = 0;
state.levelEight = 0;
state.levelNine = 0;
}
break;
case 2:
state.levelTwo++;
if (!noReset) {
state.levelThree = 0;
state.levelFour = 0;
state.levelFive = 0;
state.levelSix = 0;
state.levelSeven = 0;
state.levelEight = 0;
state.levelNine = 0;
}
break;
case 3:
state.levelThree++;
if (!noReset) {
state.levelFour = 0;
state.levelFive = 0;
state.levelSix = 0;
state.levelSeven = 0;
state.levelEight = 0;
state.levelNine = 0;
}
break;
case 4:
state.levelFour++;
if (!noReset) {
state.levelFive = 0;
state.levelSix = 0;
state.levelSeven = 0;
state.levelEight = 0;
state.levelNine = 0;
}
break;
case 5:
state.levelFive++;
if (!noReset) {
state.levelSix = 0;
state.levelSeven = 0;
state.levelEight = 0;
state.levelNine = 0;
}
break;
case 6:
state.levelSix++;
if (!noReset) {
state.levelSeven = 0;
state.levelEight = 0;
state.levelNine = 0;
}
break;
case 7:
state.levelSeven++;
if (!noReset) {
state.levelEight = 0;
state.levelNine = 0;
}
break;
case 8:
state.levelEight++;
if (!noReset) {
state.levelNine = 0;
}
break;
case 9:
state.levelNine++;
break;
}
}
function getHeaderNumber(level, state) {
switch (level) {
case 1:
return state.levelOne;
case 2:
return state.levelTwo;
case 3:
return state.levelThree;
case 4:
return state.levelFour;
case 5:
return state.levelFive;
case 6:
return state.levelSix;
case 7:
return state.levelSeven;
case 8:
return state.levelEight;
case 9:
return state.levelNine;
default:
return 0;
}
}
function getLevelValue(level, state) {
return getHeaderNumber(level, state);
}
function formatHeaderText(node, format, number, state, options) {
const { noIndent, debug } = options;
const level = node.depth;
const currentText = extractTextContent(node);
if (!currentText) {
if (debug) {
console.log("[remarkHeaders] No text content found in header");
}
return null;
}
if (hasExistingNumbering(currentText)) {
if (debug) {
console.log("[remarkHeaders] Header already has numbering, skipping");
}
return null;
}
const numberedText = applyNumberingFormat(format, number, node.depth, state);
const indentation = noIndent ? "" : " ".repeat(Math.max(0, level - 1));
return `${indentation}${numberedText} ${currentText}`;
}
function extractTextContent(node) {
const result = node.children.map((child) => {
if (child.type === "text") {
return child.value;
} else if (child.type === "html") {
return child.value || "";
} else if (child.type === "strong" || child.type === "emphasis") {
const innerText = child.children.map((grandchild) => grandchild.type === "text" ? grandchild.value : "").join("");
if (child.type === "strong") {
return `**${innerText}**`;
} else if (child.type === "emphasis") {
return `*${innerText}*`;
}
return innerText;
} else if (child.type === "link") {
const linkText = child.children.map((grandchild) => grandchild.type === "text" ? grandchild.value : "").join("");
return linkText;
} else if (child.type === "inlineCode") {
return child.value || "";
}
return "";
}).join("").trim();
return result;
}
function hasExistingNumbering(text, format) {
const numberingPatterns = [
/^Article\s+\d+\.?\s*/i,
/^Section\s+\d+\.?\s*/i,
/^Chapter\s+\d+\.?\s*/i,
/^\(\d+\)\s*/,
/^\d+\.\s*/,
/^\d+\.\d+\.?\s*/,
/^[a-z]\.\s*/i,
/^\([a-z]\)\s*/i,
/^[ivxlcdm]+\.\s*/i,
/^\([ivxlcdm]+\)\s*/i
];
return numberingPatterns.some((pattern) => pattern.test(text));
}
function applyNumberingFormat(format, number, level, state) {
let result = format;
const leadingZeroPattern = /%0(\d+)n/g;
result = result.replace(leadingZeroPattern, (match, digits) => {
return number.toString().padStart(parseInt(digits), "0");
});
for (let i = 1; i <= 9; i++) {
const leadingZeroLevelPattern = new RegExp(`%0(\\d+)l${i}`, "g");
result = result.replace(leadingZeroLevelPattern, (match, digits) => {
const levelValue = getLevelValue(i, state);
return levelValue.toString().padStart(parseInt(digits), "0");
});
}
result = result.replace(/%n/g, number.toString());
result = result.replace(/%l1/g, state.levelOne.toString());
result = result.replace(/%l2/g, state.levelTwo.toString());
result = result.replace(/%l3/g, state.levelThree.toString());
result = result.replace(/%l4/g, state.levelFour.toString());
result = result.replace(/%l5/g, state.levelFive.toString());
result = result.replace(/%l6/g, state.levelSix.toString());
result = result.replace(/%l7/g, state.levelSeven.toString());
result = result.replace(/%l8/g, state.levelEight.toString());
result = result.replace(/%l9/g, state.levelNine.toString());
if (format.includes("%A")) {
const alphaNumber = level === 4 && format.includes("%n%A") ? state.levelFour : number;
const alphaLabel = String.fromCharCode(64 + alphaNumber);
result = result.replace(/%A/g, alphaLabel);
}
if (format.includes("%a")) {
const alphaNumber = level === 4 && format.includes("%n%a") ? state.levelFour : number;
const alphaLabel = String.fromCharCode(96 + alphaNumber);
result = result.replace(/%a/g, alphaLabel);
}
if (format.includes("%c")) {
const alphaNumber = level === 4 && format.includes("%n%c") ? state.levelFour : number;
const alphaLabel = String.fromCharCode(96 + alphaNumber);
result = result.replace(/%c/g, alphaLabel);
}
if (format.includes("%r")) {
const romanNumber = level === 5 && (format.includes("%c%r") || format.includes("%n%c%r")) ? state.levelFive : number;
const romanNumeral = toRomanNumeral(romanNumber).toLowerCase();
result = result.replace(/%r/g, romanNumeral);
}
if (format.includes("%R")) {
const romanNumeral = toRomanNumeral(number);
result = result.replace(/%R/g, romanNumeral);
}
if (format.includes("%o")) {
result = result.replace(/%o/g, number.toString());
}
return result;
}
function toRomanNumeral(num) {
const romanNumerals = [
[1e3, "M"],
[900, "CM"],
[500, "D"],
[400, "CD"],
[100, "C"],
[90, "XC"],
[50, "L"],
[40, "XL"],
[10, "X"],
[9, "IX"],
[5, "V"],
[4, "IV"],
[1, "I"]
];
let result = "";
for (const [value, symbol] of romanNumerals) {
while (num >= value) {
result += symbol;
num -= value;
}
}
return result;
}
function containsFieldTrackingSpans(text) {
return text.includes('<span class="legal-field') || text.includes('<span class="imported-value') || text.includes('<span class="missing-value') || text.includes('<span class="highlight');
}
function updateHeaderNode(node, newText) {
const hasFieldTracking = containsFieldTrackingSpans(newText);
const hasLeadingSpaces = newText.startsWith(" ");
const hasMarkdownFormatting = newText.includes("*") || newText.includes("_");
if (hasFieldTracking || hasLeadingSpaces || hasMarkdownFormatting) {
node.children = [
{
type: "html",
value: newText
}
];
} else {
node.children = [
{
type: "text",
value: newText
}
];
}
}
const remarkClauses = (options) => {
const { metadata = {}, debug = false, enableFieldTracking = false } = options;
return (tree) => {
if (debug) {
console.log("[remarkClauses] Processing clauses with metadata:", Object.keys(metadata));
}
visit(tree, (node) => {
if (node.type === "text") {
processTextNode$2(node, metadata, debug, enableFieldTracking);
} else if (node.type === "html" && "value" in node) {
processHtmlNode(node, metadata, debug, enableFieldTracking);
} else if (node.type === "paragraph") {
processParagraphNode$2(node, metadata, debug, enableFieldTracking);
}
});
};
};
function processTextNode$2(node, metadata, debug, enableFieldTracking) {
const originalText = node.value;
const conditionalBlocks = extractConditionalBlocks(originalText);
if (debug && originalText.includes("{{#")) {
console.log(`[remarkClauses] DEBUG - Text node content: "${originalText}"`);
console.log(`[remarkClauses] DEBUG - Found ${conditionalBlocks.length} conditional blocks`);
}
if (conditionalBlocks.length === 0) {
return;
}
if (debug) {
console.log(
`[remarkClauses] Found ${conditionalBlocks.length} conditional blocks in text node`
);
}
let processedText = originalText;
for (let i = conditionalBlocks.length - 1; i >= 0; i--) {
const block = conditionalBlocks[i];
const result = evaluateConditionalBlock(block, metadata, debug, enableFieldTracking);
processedText = processedText.substring(0, block.start) + result + processedText.substring(block.end);
}
node.value = processedText;
if (conditionalBlocks.length > 0 && processedText.includes('<span class="legal-field')) {
node.type = "html";
}
}
function processHtmlNode(node, metadata, debug, enableFieldTracking) {
const originalHtml = node.value;
const conditionalBlocks = extractConditionalBlocks(originalHtml);
if (conditionalBlocks.length === 0) {
return;
}
if (debug) {
console.log(
`[remarkClauses] Found ${conditionalBlocks.length} conditional blocks in HTML node`
);
}
let processedHtml = originalHtml;
for (let i = conditionalBlocks.length - 1; i >= 0; i--) {
const block = conditionalBlocks[i];
const result = evaluateConditionalBlock(block, metadata, debug, enableFieldTracking);
processedHtml = processedHtml.substring(0, block.start) + result + processedHtml.substring(block.end);
}
node.value = processedHtml;
}
function processParagraphNode$2(node, metadata, debug, enableFieldTracking) {
node.children.forEach((child) => {
if (child.type === "text") {
processTextNode$2(child, metadata, debug, enableFieldTracking);
}
});
}
function extractConditionalBlocks(text) {
const blocks = [];
const conditionalRegex = /\{\{#(if(?:\s+[^}]*)?)\}\}((?:(?!\{\{#if|\{\{\/if\}\}).)*?)(?:\{\{else\}\}((?:(?!\{\{\/if\}\}).)*?))?\{\{\/if\}\}/gs;
let match;
while ((match = conditionalRegex.exec(text)) !== null) {
const [fullMatch, condition, content, elseContent] = match;
blocks.push({
condition: condition.trim(),
content: content || "",
elseContent: elseContent || void 0,
start: match.index,
end: match.index + fullMatch.length
});
}
const simpleConditionalRegex = /\{\{#([\w._]+)\}\}((?:(?!\{\{\/\1\}\}).)*?)\{\{\/\1\}\}/gs;
while ((match = simpleConditionalRegex.exec(text)) !== null) {
const [fullMatch, variable, content] = match;
blocks.push({
condition: variable.trim(),
content: content || "",
elseContent: void 0,
start: match.index,
end: match.index + fullMatch.length
});
}
const bracketConditionalRegex = /\[([^[\]]*(?:\[[^\]]*\][^[\]]*)*?)\]\{([^{}]*?)\}/g;
while ((match = bracketConditionalRegex.exec(text)) !== null) {
const [fullMatch, content, condition] = match;
blocks.push({
condition: condition.trim(),
content: content || "",
elseContent: void 0,
start: match.index,
end: match.index + fullMatch.length
});
}
const originalBracketRegex = /\[\{\{([^{}]*?)\}\}([^[\]]*(?:\[[^\]]*\][^[\]]*)*?)\]/g;
while ((match = originalBracketRegex.exec(text)) !== null) {
const [fullMatch, condition, content] = match;
if (!condition.trim()) {
continue;
}
blocks.push({
condition: condition.trim(),
content: content || "",
elseContent: void 0,
start: match.index,
end: match.index + fullMatch.length
});
}
return blocks.sort((a, b) => a.start - b.start);
}
function evaluateConditionalBlock(block, metadata, debug, enableFieldTracking) {
const { condition, content, elseContent } = block;
try {
if (condition.startsWith("if ") || condition === "if") {
const actualCondition = condition === "if" ? "" : condition.substring(3).trim();
const result2 = evaluateCondition$1(actualCondition, metadata);
if (debug) {
console.log(`[remarkClauses] Conditional "if ${actualCondition}" evaluated to:`, result2);
}
if (result2) {
return content;
} else if (elseContent !== void 0) {
return elseContent;
} else {
return "";
}
}
const value = getNestedValue$1(metadata, condition);
if (Array.isArray(value)) {
if (debug) {
console.log(
`[remarkClauses] Array condition "${condition}" with ${value.length} items - processing as loop`
);
}
return processArrayLoop(condition, content, value, metadata, debug, enableFieldTracking);
}
const result = evaluateCondition$1(condition, metadata);
if (debug) {
console.log(`[remarkClauses] Condition "${condition}" evaluated to:`, result);
}
if (result) {
return content;
} else if (elseContent !== void 0) {
return elseContent;
} else {
return "";
}
} catch (error) {
if (debug) {
console.warn(`[remarkClauses] Error evaluating condition "${condition}":`, error);
}
return content;
}
}
function evaluateCondition$1(condition, metadata) {
if (!condition.trim()) {
throw new Error("Empty condition provided");
}
const sanitizedCondition = sanitizeCondition(condition);
if (!sanitizedCondition) {
return false;
}
return evaluateSimpleCondition(sanitizedCondition, metadata);
}
function sanitizeCondition(condition) {
if (/<[^>]*>/.test(condition)) {
console.warn("[remarkClauses] Unsafe condition detected, skipping:", condition);
return null;
}
const dangerousKeywords = ["script", "eval", "function", "constructor", "prototype"];
const lowerCondition = condition.toLowerCase();
for (const keyword of dangerousKeywords) {
if (lowerCondition.includes(keyword)) {
console.warn("[remarkClauses] Unsafe condition detected, skipping:", condition);
return null;
}
}
const safePattern = /^[a-zA-Z0-9_.\s==!=<>!&|()'"@]+$/;
if (!safePattern.test(condition)) {
console.warn("[remarkClauses] Unsafe condition detected, skipping:", condition);
return null;
}
return condition;
}
function evaluateSimpleCondition(condition, metadata) {
if (condition.includes("&&") || condition.includes("||")) {
return evaluateBooleanExpression(condition, metadata);
}
if (condition.includes("==") || condition.includes("!=") || condition.includes(">") || condition.includes("<")) {
return evaluateComparisonExpression(condition, metadata);
}
if (condition.includes(".")) {
return evaluateNestedVariable(condition, metadata);
}
const value = metadata[condition.trim()];
return isTruthy(value);
}
function evaluateBooleanExpression(condition, metadata) {
const orParts = condition.split("||");
for (const orPart of orParts) {
const andParts = orPart.split("&&");
let allAndTrue = true;
for (const andPart of andParts) {
if (!evaluateSimpleCondition(andPart.trim(), metadata)) {
allAndTrue = false;
break;
}
}
if (allAndTrue) {
return true;
}
}
return false;
}
function evaluateComparisonExpression(condition, metadata) {
let operator = "";
let leftSide = "";
let rightSide = "";
if (condition.includes("==")) {
[leftSide, rightSide] = condition.split("==");
operator = "==";
} else if (condition.includes("!=")) {
[leftSide, rightSide] = condition.split("!=");
operator = "!=";
} else if (condition.includes(">=")) {
[leftSide, rightSide] = condition.split(">=");
operator = ">=";
} else if (condition.includes("<=")) {
[leftSide, rightSide] = condition.split("<=");
operator = "<=";
} else if (condition.includes(">")) {
[leftSide, rightSide] = condition.split(">");
operator = ">";
} else if (condition.includes("<")) {
[leftSide, rightSide] = condition.split("<");
operator = "<";
}
if (!operator || !leftSide || !rightSide) {
return false;
}
const leftValue = getVariableValue(leftSide.trim(), metadata);
const rightValue = parseValue(rightSide.trim(), metadata);
switch (operator) {
case "==":
return leftValue == rightValue;
case "!=":
return leftValue != rightValue;
case ">":
return Number(leftValue) > Number(rightValue);
case "<":
return Number(leftValue) < Number(rightValue);
case ">=":
return Number(leftValue) >= Number(rightValue);
case "<=":
return Number(leftValue) <= Number(rightValue);
default:
return false;
}
}
function evaluateNestedVariable(condition, metadata) {
const value = getNestedValue$1(metadata, condition.trim());
return isTruthy(value);
}
function getVariableValue(variable, metadata) {
if (variable.includes(".")) {
return getNestedValue$1(metadata, variable);
}
return metadata[variable];
}
function getNestedValue$1(obj, path) {
return path.split(".").reduce((current, key) => {
return current && current[key] !== void 0 ? current[key] : void 0;
}, obj);
}
function parseValue(value, metadata) {
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
return value.slice(1, -1);
}
if (/^\d+(\.\d+)?$/.test(value)) {
return Number(value);
}
if (value === "true") return true;
if (value === "false") return false;
return getVariableValue(value, metadata);
}
function isTruthy(value) {
if (value === null || value === void 0) return false;
if (typeof value === "boolean") return value;
if (typeof value === "number") return value !== 0;
if (typeof value === "string") return value.length > 0;
if (Array.isArray(value)) return value.length > 0;
if (typeof value === "object") return Object.keys(value).length > 0;
return Boolean(value);
}
function processArrayLoop(variable, content, items, metadata, debug, enableFieldTracking) {
const results = [];
for (let i = 0; i < items.length; i++) {
const item = items[i];
const enhancedMetadata = { ...metadata };
if (item && typeof item === "object" && !Array.isArray(item)) {
Object.assign(enhancedMetadata, item);
}
enhancedMetadata["@index"] = i;
enhancedMetadata["@first"] = i === 0;
enhancedMetadata["@last"] = i === items.length - 1;
enhancedMetadata["@total"] = items.length;
let processedContent = content;
const nestedBlocks = extractConditionalBlocks(processedContent);
if (nestedBlocks.length > 0) {
for (let j = nestedBlocks.length - 1; j >= 0; j--) {
const nestedBlock = nestedBlocks[j];
const nestedResult = evaluateConditionalBlock(
nestedBlock,
enhancedMetadata,
debug,
enableFieldTracking
);
processedContent = processedContent.substring(0, nestedBlock.start) + nestedResult + processedContent.substring(nestedBlock.end);
}
}
processedContent = processedContent.replace(/\{\{([^}]+)\}\}/g, (match, field) => {
const trimmedField = field.trim();
if (trimmedField.startsWith("#") || trimmedField.startsWith("/") || trimmedField === "else") {
return match;
}
const value = getNestedValue$1(enhancedMetadata, trimmedField);
if (debug) {
console.log(`[remarkClauses] Loop field "${trimmedField}" resolved to:`, value);
}
if (enableFieldTracking) {
const isEmptyValue = value === void 0 || value === null || value === "" || typeof value === "string" && value.trim() === "";
const cssClass = isEmptyValue ? "legal-field missing-value" : "legal-field imported-value";
const formattedValue = value !== void 0 ? String(value) : match;
fieldTracker.trackField(trimmedField, {
value,
originalValue: match,
hasLogic: false,
mixinUsed: "loop"
});
return `<span class="${cssClass}" data-field="${trimmedField.replace(/"/g, """)}">${formattedValue}</span>`;
}
return value !== void 0 ? String(value) : match;
});
results.push(processedContent);
}
return results.join("");
}
const existsSync = () => false;
const readFileSync = () => "";
const writeFileSync = () => {
};
const mkdirSync = () => {
};
var pathBrowserify;
var hasRequiredPathBrowserify;
function requirePathBrowserify () {
if (hasRequiredPathBrowserify) return pathBrowserify;
hasRequiredPathBrowserify = 1;
var define_process_default = { env: { NODE_ENV: "production", DEBUG: false } };
function assertPath(path) {
if (typeof path !== "string") {
throw new TypeError("Path must be a string. Received " + JSON.stringify(path));
}
}
function normalizeStringPosix(path, allowAboveRoot) {
var res = "";
var lastSegmentLength = 0;
var lastSlash = -1;
var dots = 0;
var code;
for (var i = 0; i <= path.length; ++i) {
if (i < path.length)
code = path.charCodeAt(i);
else if (code === 47)
break;
else
code = 47;
if (code === 47) {
if (lastSlash === i - 1 || dots === 1) ; else if (lastSlash !== i - 1 && dots === 2) {
if (res.length < 2 || lastSegmentLength !== 2 || res.charCodeAt(res.length - 1) !== 46 || res.charCodeAt(res.length - 2) !== 46) {
if (res.length > 2) {
var lastSlashIndex = res.lastIndexOf("/");
if (lastSlashIndex !== res.length - 1) {
if (lastSlashIndex === -1) {
res = "";
lastSegmentLength = 0;
} else {
res = res.slice(0, lastSlashIndex);
lastSegmentLength = res.length - 1 - res.lastIndexOf("/");
}
lastSlash = i;
dots = 0;
continue;
}
} else if (res.length === 2 || res.length === 1) {
res = "";
lastSegmentLength = 0;
lastSlash = i;
dots = 0;
continue;
}
}
if (allowAboveRoot) {
if (res.length > 0)
res += "/..";
else
res = "..";
lastSegmentLength = 2;
}
} else {
if (res.length > 0)
res += "/" + path.slice(lastSlash + 1, i);
else
res = path.slice(lastSlash + 1, i);
lastSegmentLength = i - lastSlash - 1;
}
lastSlash = i;
dots = 0;
} else if (code === 46 && dots !== -1) {
++dots;
} else {
dots = -1;
}
}
return res;
}
function _format(sep, pathObject) {
var dir = pathObject.dir || pathObject.root;
var base = pathObject.base || (pathObject.name || "") + (pathObject.ext || "");
if (!dir) {
return base;
}
if (dir === pathObject.root) {
return dir + base;
}
return dir + sep + base;
}
var posix = {
// path.resolve([from ...], to)
resolve: function resolve() {
var resolvedPath = "";
var resolvedAbsolute = false;
var cwd;
for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
var path;
if (i >= 0)
path = arguments[i];
else {
if (cwd === void 0)
cwd = define_process_default.cwd();
path = cwd;
}
assertPath(path);
if (path.length === 0) {
continue;
}
resolvedPath = path + "/" + resolvedPath;
resolvedAbsolute = path.charCodeAt(0) === 47;
}
resolvedPath = normalizeStringPosix(resolvedPath, !resolvedAbsolute);
if (resolvedAbsolute) {
if (resolvedPath.length > 0)
return "/" + resolvedPath;
else
return "/";
} else if (resolvedPath.length > 0) {
return resolvedPath;
} else {
return ".";
}
},
normalize: function normalize(path) {
assertPath(path);
if (path.length === 0) return ".";
var isAbsolute2 = path.charCodeAt(0) === 47;
var trailingSeparator = path.charCodeAt(path.length - 1) === 47;
path = normalizeStringPosix(path, !isAbsolute2);
if (path.length === 0 && !isAbsolute2) path = ".";
if (path.length > 0 && trailingSeparator) path += "/";
if (isAbsolute2) return "/" + path;
return path;
},
isAbsolute: function isAbsolute(path) {
assertPath(path);
return path.length > 0 && path.charCodeAt(0) === 47;
},
join: function join() {
if (arguments.length === 0)
return ".";
var joined;
for (var i = 0; i < arguments.length; ++i) {
var arg = arguments[i];
assertPath(arg);
if (arg.length > 0) {
if (joined === void 0)
joined = arg;
else
joined += "/" + arg;
}
}
if (joined === void 0)
return ".";
return posix.normalize(joined);
},
relative: function relative(from, to) {
assertPath(from);
assertPath(to);
if (from === to) return "";
from = posix.resolve(from);
to = posix.resolve(to);
if (from === to) return "";
var fromStart = 1;
for (; fromStart < from.length; ++fromStart) {
if (from.charCodeAt(fromStart) !== 47)
break;
}
var fromEnd = from.length;
var fromLen = fromEnd - fromStart;
var toStart = 1;
for (; toStart < to.length; ++toStart) {
if (to.charCodeAt(toStart) !== 47)
break;
}
var toEnd = to.length;
var toLen = toEnd - toStart;
var length = fromLen < toLen ? fromLen : toLen;
var lastCommonSep = -1;
var i = 0;
for (; i <= length; ++i) {
if (i === length) {
if (toLen > length) {
if (to.charCodeAt(toStart + i) === 47) {
return to.slice(toStart + i + 1);
} else if (i === 0) {
return to.slice(toStart + i);
}
} else if (fromLen > length) {
if (from.charCodeAt(fromStart + i) === 47) {
lastCommonSep = i;
} else if (i === 0) {
lastCommonSep = 0;
}
}
break;
}
var fromCode = from.charCodeAt(fromStart + i);
var toCode = to.charCodeAt(toStart + i);
if (fromCode !== toCode)
break;
else if (fromCode === 47)
lastCommonSep = i;
}
var out = "";
for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) {
if (i === fromEnd || from.charCodeAt(i) === 47) {
if (out.length === 0)
out += "..";
else
out += "/..";
}
}
if (out.length > 0)
return out + to.slice(toStart + lastCommonSep);
else {
toStart += lastCommonSep;
if (to.charCodeAt(toStart) === 47)
++toStart;
return to.slice(toStart);
}
},
_makeLong: function _makeLong(path) {
return path;
},
dirname: function dirname(path) {
assertPath(path);
if (path.length === 0) return ".";
var code = path.charCodeAt(0);
var hasRoot = code === 47;
var end = -1;
var matchedSlash = true;
for (var i = path.length - 1; i >= 1; --i) {
code = path.charCodeAt(i);
if (code === 47) {
if (!matchedSlash) {
end = i;
break;
}
} else {
matchedSlash = false;
}
}
if (end === -1) return hasRoot ? "/" : ".";
if (hasRoot && end === 1) return "//";
return path.slice(0, end);
},
basename: function basename(path, ext) {
if (ext !== void 0 && typeof ext !== "string") throw new TypeError('"ext" argument must be a string');
assertPath(path);
var start = 0;
var end = -1;
var matchedSlash = true;
var i;
if (ext !== void 0 && ext.length > 0 && ext.length <= path.length) {
if (ext.length === path.length && ext === path) return "";
var extIdx = ext.length - 1;
var firstNonSlashEnd = -1;
for (i = path.length - 1; i >= 0; --i) {
var code = path.charCodeAt(i);
if (code === 47) {
if (!matchedSlash) {
start = i + 1;
break;
}
} else {
if (firstNonSlashEnd === -1) {
matchedSlash = false;
firstNonSlashEnd = i + 1;
}
if (extIdx >= 0) {
if (code === ext.charCodeAt(extIdx)) {
if (--extIdx === -1) {
end = i;
}
} else {
extIdx = -1;
end = firstNonSlashEnd;
}
}
}
}
if (start === end) end = firstNonSlashEnd;
else if (end === -1) end = path.length;
return path.slice(start, end);
} else {
for (i = path.length - 1; i >= 0; --i) {
if (path.charCodeAt(i) === 47) {
if (!matchedSlash) {
start = i + 1;
break;
}
} else if (end === -1) {
matchedSlash = false;
end = i + 1;
}
}
if (end === -1) return "";
return path.slice(start, end);
}
},
extname: function extname(path) {
assertPath(path);
var startDot = -1;
var startPart = 0;
var end = -1;
var matchedSlash = true;
var preDotState = 0;
for (var i = path.length - 1; i >= 0; --i) {
var code = path.charCodeAt(i);
if (code === 47) {
if (!matchedSlash) {
startPart = i + 1;
break;
}
continue;
}
if (end === -1) {
matchedSlash = false;
end = i + 1;
}
if (code === 46) {
if (startDot === -1)
startDot = i;
else if (preDotState !== 1)
preDotState = 1;
} else if (startDot !== -1) {
preDotState = -1;
}
}
if (startDot === -1 || end === -1 || // We saw a non-dot character immediately before the dot
preDotState === 0 || // The (right-most) trimmed path component is exactly '..'
preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) {
return "";
}
return path.slice(startDot, end);
},
format: function format(pathObject) {
if (pathObject === null || typeof pathObject !== "object") {
throw new TypeError('The "pathObject" argument must be of type Object. Received type ' + typeof pathObject);
}
return _format("/", pathObject);
},
parse: function parse(path) {
assertPath(path);
var ret = { root: "", dir: "", base: "", ext: "", name: "" };
if (path.length === 0) return ret;
var code = path.charCodeAt(0);
var isAbsolute2 = code === 47;
var start;
if (isAbsolute2) {
ret.root = "/";
start = 1;
} else {
start = 0;
}
var startDot = -1;
var startPart = 0;
var end = -1;
var matchedSlash = true;
var i = path.length - 1;
var preDotState = 0;
for (; i >= start; --i) {
code = path.charCodeAt(i);
if (code === 47) {
if (!matchedSlash) {
startPart = i + 1;
break;
}
continue;
}
if (end === -1) {
matchedSlash = false;
end = i + 1;
}
if (code === 46) {
if (startDot === -1) startDot = i;
else if (preDotState !== 1) preDotState = 1;
} else if (startDot !== -1) {
preDotState = -1;
}
}
if (startDot === -1 || end === -1 || // We saw a non-dot character immediately before the dot
preDotState === 0 || // The (right-most) trimmed path component is exactly '..'
preDotState === 1 && startDot === end - 1 && startDot === startPart + 1) {
if (end !== -1) {
if (startPart === 0 && isAbsolute2) ret.base = ret.name = path.slice(1, end);
else ret.base = ret.name = path.slice(startPart, end);
}
} else {
if (startPart === 0 && isAbsolute2) {
ret.name = path.slice(1, startDot);
ret.base = path.slice(1, end);
} else {
ret.name = path.slice(startPart, startDot);
ret.base = path.slice(startPart, end);
}
ret.ext = path.slice(startDot, end);
}
if (startPart > 0) ret.dir = path.slice(0, startPart - 1);
else if (isAbsolute2) ret.dir = "/";
return ret;
},
sep: "/",
delimiter: ":",
win32: null,
posix: null
};
posix.posix = posix;
pathBrowserify = posix;
return pathBrowserify;
}
var pathBrowserifyExports = requirePathBrowserify();
const remarkMixins = (options) => {
const { metadata = {}, basePath = ".", debug = false, maxDepth = 5, customMixins = {} } = options;
return (tree) => {
if (debug) {
console.log("[remarkMixins] Processing mixins with options:", {
basePath,
maxDepth,
customMixinCount: Object.keys(customMixins).length
});
}
const context = {
depth: 0,
maxDepth,
basePath,
metadata,
debug,
customMixins,
fileCache: /* @__PURE__ */ new Map()
};
visit(tree, (node) => {
if (node.type === "text") {
processTextNode$1(node, context);
} else if (node.type === "paragraph") {
processParagraphNode$1(node, context);
}
});
};
};
function processTextNode$1(node, context) {
const originalText = node.value;
const mixinDirectives = extractMixinDirectives(originalText);
if (mixinDirectives.length === 0) {
return;
}
if (context.debug) {
console.log(`[remarkMixins] Found ${mixinDirectives.length} mixin directives in text node`);
}
let processedText = originalText;
for (let i = mixinDirectives.length - 1; i >= 0; i--) {
const directive = mixinDirectives[i];
const result = processMixinDirective(directive, context);
processedText = processedText.substring(0, directive.start) + result + processedText.substring(directive.end);
}
node.value = processedText;
}
function processParagraphNode$1(node, context) {
node.children.forEach((child) => {
if (child.type === "text") {
processTextNode$1(child, context);
}
});
}
function extractMixinDirectives(text) {
const directives = [];
const mixinRegex = /@include\s+([a-zA-Z_][a-zA-Z0-9_-]*)\s*(?:\((.*?)\))?/g;
let match;
while ((match = mixinRegex.exec(text)) !== null) {
const [fullMatch, mixinName, paramString] = match;
let parameters = {};
if (paramString) {
try {
parameters = parseParameters(paramString);
} catch (error) {
console.warn(`[remarkMixins] Failed to parse parameters for mixin "${mixinName}":`, error);
}
}
directives.push({
name: mixinName,
parameters,
start: match.index,
end: match.index + fullMatch.length,
fullMatch
});
}
return directives;
}
function parseParameters(paramString) {
const parameters = {};
const paramRegex = /([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*([^,]+)/g;
let match;
while ((match = paramRegex.exec(paramString)) !== null) {
const [, key, value] = match;
parameters[key.trim()] = parseParameterValue(value.trim());
}
return parameters;
}
function parseParameterValue(value) {
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
return value.slice(1, -1);
}
if (/^\d+(\.\d+)?$/.test(value)) {
return Number(value);
}
if (value === "true") return true;
if (value === "false") return false;
return value;
}
function processMixinDirective(directive, context) {
if (context.depth >= context.maxDepth) {
console.warn(
`[remarkMixins] Maximum recursion depth (${context.maxDepth}) reached for mixin "${directive.name}"`
);
return directive.fullMatch;
}
if (context.debug) {
console.log(
`[remarkMixins] Processing mixin "${directive.name}" with parameters:`,
directive.parameters
);
}
const mixinContent = loadMixinContent(directive.name, context);
if (!mixinContent) {
console.warn(`[remarkMixins] Mixin "${directive.name}" not found`);
return directive.fullMatch;
}
const mixinContext = {
...context.metadata,
...directive.parameters
};
const processedContent = processTemplateFields(mixinContent, mixinContext, context.debug);
const nestedContext = {
...context,
depth: context.depth + 1
};
return processNestedMixins(processedContent, nestedContext);
}
function loadMixinContent(mixinName, context) {
if (context.customMixins[mixinName]) {
return context.customMixins[mixinName];
}
const filePath = pathBrowserifyExports.join(context.basePath, `${mixinName}.md`);
if (context.fileCache.has(filePath)) {
return context.fileCache.get(filePath);
}
try {
if (existsSync(filePath)) ;
} catch (error) {
if (context.debug) {
console.warn(`[remarkMixins] Failed to load mixin file "${filePath}":`, error);
}
}
return null;
}
function processTemplateFields(content, metadata, debug) {
return content.replace(/\{\{([^}]+)\}\}/g, (match, fieldName) => {
const trimmedField = fieldName.trim();
const value = getNestedValue(metadata, trimmedField);
if (value !== void 0 && value !== null) {
if (debug) {
console.log(`[remarkMixins] Replacing template field "${trimmedField}" with value:`, value);
}
return String(value);
}
if (debug) {
console.log(`[remarkMixins] Template field "${trimmedField}" not found, keeping original`);
}
return match;
});
}
function getNestedValue(obj, path2) {
return path2.split(".").reduce((current, key) => {
return current && current[key] !== void 0 ? current[key] : void 0;
}, obj);
}
function processNestedMixins(content, context) {
const mixinDirectives = extractMixinDirectives(content);
if (mixinDirectives.length === 0) {
return content;
}
let processedContent = content;
for (let i = mixinDirectives.length - 1; i >= 0; i--) {
const directive = mixinDirectives[i];
const result = processMixinDirective(directive, context);
processedContent = processedContent.substring(0, directive.start) + result + processedContent.substring(directive.end);
}
return processedContent;
}
/*! js-yaml 4.1.0 https://github.com/nodeca/js-yaml @license MIT */
function isNothing(subject) {
return typeof subject === "undefined" || subject === null;
}
function isObject(subject) {
return typeof subject === "object" && subject !== null;
}
function toArray(sequence) {
if (Array.isArray(sequence)) return sequence;
else if (isNothing(sequence)) return [];
return [sequence];
}
function extend(target, source) {
var index, length, key, sourceKeys;
if (source) {
sourceKeys = Object.keys(source);
for (index = 0, length = sourceKeys.length; index < length; index += 1) {
key = sourceKeys[index];
target[key] = source[key];
}
}
return target;
}
function repeat(string, count) {
var result = "", cycle;
for (cycle = 0; cycle < count; cycle += 1) {
result += string;
}
return result;
}
function isNegativeZero(number) {
return number === 0 && Number.NEGATIVE_INFINITY === 1 / number;
}
var isNothing_1 = isNothing;
var isObject_1 = isObject;
var toArray_1 = toArray;
var repeat_1 = repeat;
var isNegativeZero_1 = isNegativeZero;
var extend_1 = extend;
var common = {
isNothing: isNothing_1,
isObject: isObject_1,
toArray: toArray_1,
repeat: repeat_1,
isNegativeZero: isNegativeZero_1,
extend: extend_1
};
function formatError(exception2, compact) {
var where = "", message = exception2.reason || "(unknown reason)";
if (!exception2.mark) return message;
if (exception2.mark.name) {
where += 'in "' + exception2.mark.name + '" ';
}
where += "(" + (exception2.mark.line + 1) + ":" + (exception2.mark.column + 1) + ")";
if (!compact && exception2.mark.snippet) {
where += "\n\n" + exception2.mark.snippet;
}
return message + " " + where;
}
function YAMLException$1(reason, mark) {
Error.call(this);
this.name = "YAMLException";
this.reason = reason;
this.mark = mark;
this.message = formatError(this, false);
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
} else {
this.stack = new Error().stack || "";
}
}
YAMLException$1.prototype = Object.create(Error.prototype);
YAMLException$1.prototype.constructor = YAMLException$1;
YAMLException$1.prototype.toString = function toString(compact) {
return this.name + ": " + formatError(this, compact);
};
var exception = YAMLException$1;
function getLine(buffer, lineStart, lineEnd, position, maxLineLength) {
var head = "";
var tail = "";
var maxHalfLength = Math.floor(maxLineLength / 2) - 1;
if (position - lineStart > maxHalfLength) {
head = " ... ";
lineStart = position - maxHalfLength + head.length;
}
if (lineEnd - position > maxHalfLength) {
tail = " ...";
lineEnd = position + maxHalfLength - tail.length;
}
return {
str: head + buffer.slice(lineStart, lineEnd).replace(/\t/g, "β") + tail,
pos: position - lineStart + head.length
// relative position
};
}
function padStart(string, max) {
return common.repeat(" ", max - string.length) + string;
}
function makeSnippet(mark, options) {
options = Object.create(options || null);
if (!mark.buffer) return null;
if (!options.maxLength) options.maxLength = 79;
if (typeof options.indent !== "number") options.indent = 1;
if (typeof options.linesBefore !== "number") options.linesBefore = 3;
if (typeof options.linesAfter !== "number") options.linesAfter = 2;
var re = /\r?\n|\r|\0/g;
var lineStarts = [0];
var lineEnds = [];
var match;
var foundLineNo = -1;
while (match = re.exec(mark.buffer)) {
lineEnds.push(match.index);
lineStarts.push(match.index + match[0].length);
if (mark.position <= match.index && foundLineNo < 0) {
foundLineNo = lineStarts.length - 2;
}
}
if (foundLineNo < 0) foundLineNo = lineStarts.length - 1;
var result = "", i, line;
var lineNoLength = Math.min(mark.line + options.linesAfter, lineEnds.length).toString().length;
var maxLineLength = options.maxLength - (options.indent + lineNoLength + 3);
for (i = 1; i <= options.linesBefore; i++) {
if (foundLineNo - i < 0) break;
line = getLine(
mark.buffer,
lineStarts[foundLineNo - i],
lineEnds[foundLineNo - i],
mark.position - (lineStarts[foundLineNo] - lineStarts[foundLineNo - i]),
maxLineLength
);
result = common.repeat(" ", options.indent) + padStart((mark.line - i + 1).toString(), lineNoLength) + " | " + line.str + "\n" + result;
}
line = getLine(mark.buffer, lineStarts[foundLineNo], lineEnds[foundLineNo], mark.position, maxLineLength);
result += common.repeat(" ", options.indent) + padStart((mark.line + 1).toString(), lineNoLength) + " | " + line.str + "\n";
result += common.repeat("-", options.indent + lineNoLength + 3 + line.pos) + "^\n";
for (i = 1; i <= options.linesAfter; i++) {
if (foundLineNo + i >= lineEnds.length) break;
line = getLine(
mark.buffer,
lineStarts[foundLineNo + i],
lineEnds[foundLineNo + i],
mark.position - (lineStarts[foundLineNo] - lineStarts[foundLineNo + i]),
maxLineLength
);
result += common.repeat(" ", options.indent) + padStart((mark.line + i + 1).toString(), lineNoLength) + " | " + line.str + "\n";
}
return result.replace(/\n$/, "");
}
var snippet = makeSnippet;
var TYPE_CONSTRUCTOR_OPTIONS = [
"kind",
"multi",
"resolve",
"construct",
"instanceOf",
"predicate",
"represent",
"representName",
"defaultStyle",
"styleAliases"
];
var YAML_NODE_KINDS = [
"scalar",
"sequence",
"mapping"
];
function compileStyleAliases(map2) {
var result = {};
if (map2 !== null) {
Object.keys(map2).forEach(function(style) {
map2[style].forEach(function(alias) {
result[String(alias)] = style;
});
});
}
return result;
}
function Type$1(tag, options) {
options = options || {};
Object.keys(options).forEach(function(name) {
if (TYPE_CONSTRUCTOR_OPTIONS.indexOf(name) === -1) {
throw new exception('Unknown option "' + name + '" is met in definition of "' + tag + '" YAML type.');
}
});
this.options = options;
this.tag = tag;
this.kind = options["kind"] || null;
this.resolve = options["resolve"] || function() {
return true;
};
this.construct = options["construct"] || function(data) {
return data;
};
this.instanceOf = options["instanceOf"] || null;
this.predicate = options["predicate"] || null;
this.represent = options["represent"] || null;
this.representName = options["representName"] || null;
this.defaultStyle = options["defaultStyle"] || null;
this.multi = options["multi"] || false;
this.styleAliases = compileStyleAliases(options["styleAliases"] || null);
if (YAML_NODE_KINDS.indexOf(this.kind) === -1) {
throw new exception('Unknown kind "' + this.kind + '" is specified for "' + tag + '" YAML type.');
}
}
var type = Type$1;
function compileList(schema2, name) {
var result = [];
schema2[name].forEach(function(currentType) {
var newIndex = result.length;
result.forEach(function(previousType, previousIndex) {
if (previousType.tag === currentType.tag && previousType.kind === currentType.kind && previousType.multi === currentType.multi) {
newIndex = previousIndex;
}
});
result[newIndex] = currentType;
});
return result;
}
function compileMap() {
var result = {
scalar: {},
sequence: {},
mapping: {},
fallback: {},
multi: {
scalar: [],
sequence: [],
mapping: [],
fallback: []
}
}, index, length;
function collectType(type2) {
if (type2.multi) {
result.multi[type2.kind].push(type2);
result.multi["fallback"].push(type2);
} else {
result[type2.kind][type2.tag] = result["fallback"][type2.tag] = type2;
}
}
for (index = 0, length = arguments.length; index < length; index += 1) {
arguments[index].forEach(collectType);
}
return result;
}
function Schema$1(definition) {
return this.extend(definition);
}
Schema$1.prototype.extend = function extend2(definition) {
var implicit = [];
var explicit = [];
if (definition instanceof type) {
explicit.push(definition);
} else if (Array.isArray(definition)) {
explicit = explicit.concat(definition);
} else if (definition && (Array.isArray(definition.implicit) || Array.isArray(definition.explicit))) {
if (definition.implicit) implicit = implicit.concat(definition.implicit);
if (definition.explicit) explicit = explicit.concat(definition.explicit);
} else {
throw new exception("Schema.extend argument should be a Type, [ Type ], or a schema definition ({ implicit: [...], explicit: [...] })");
}
implicit.forEach(function(type$1) {
if (!(type$1 instanceof type)) {
throw new exception("Specified list of YAML types (or a single Type object) contains a non-Type object.");
}
if (type$1.loadKind && type$1.loadKind !== "scalar") {
throw new exception("There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported.");
}
if (type$1.multi) {
throw new exception("There is a multi type in the implicit list of a schema. Multi tags can only be listed as explicit.");
}
});
explicit.forEach(function(type$1) {
if (!(type$1 instanceof type)) {
throw new exception("Specified list of YAML types (or a single Type object) contains a non-Type object.");
}
});
var result = Object.create(Schema$1.prototype);
result.implicit = (this.implicit || []).concat(implicit);
result.explicit = (this.explicit || []).concat(explicit);
result.compiledImplicit = compileList(result, "implicit");
result.compiledExplicit = compileList(result, "explicit");
result.compiledTypeMap = compileMap(result.compiledImplicit, result.compiledExplicit);
return result;
};
var schema = Schema$1;
var str = new type("tag:yaml.org,2002:str", {
kind: "scalar",
construct: function(data) {
return data !== null ? data : "";
}
});
var seq = new type("tag:yaml.org,2002:seq", {
kind: "sequence",
construct: function(data) {
return data !== null ? data : [];
}
});
var map = new type("tag:yaml.org,2002:map", {
kind: "mapping",
construct: function(data) {
return data !== null ? data : {};
}
});
var failsafe = new schema({
explicit: [
str,
seq,
map
]
});
function resolveYamlNull(data) {
if (data === null) return true;
var max = data.length;
return max === 1 && data === "~" || max === 4 && (data === "null" || data === "Null" || data === "NULL");
}
function constructYamlNull() {
return null;
}
function isNull(object) {
return object === null;
}
var _null = new type("tag:yaml.org,2002:null", {
kind: "scalar",
resolve: resolveYamlNull,
construct: constructYamlNull,
predicate: isNull,
represent: {
canonical: function() {
return "~";
},
lowercase: function() {
return "null";
},
uppercase: function() {
return "NULL";
},
camelcase: function() {
return "Null";
},
empty: function() {
return "";
}
},
defaultStyle: "lowercase"
});
function resolveYamlBoolean(data) {
if (data === null) return false;
var max = data.length;
return max === 4 && (data === "true" || data === "True" || data === "TRUE") || max === 5 && (data === "false" || data === "False" || data === "FALSE");
}
function constructYamlBoolean(data) {
return data === "true" || data === "True" || data === "TRUE";
}
function isBoolean(object) {
return Object.prototype.toString.call(object) === "[object Boolean]";
}
var bool = new type("tag:yaml.org,2002:bool", {
kind: "scalar",
resolve: resolveYamlBoolean,
construct: constructYamlBoolean,
predicate: isBoolean,
represent: {
lowercase: function(object) {
return object ? "true" : "false";
},
uppercase: function(object) {
return object ? "TRUE" : "FALSE";
},
camelcase: function(object) {
return object ? "True" : "False";
}
},
defaultStyle: "lowercase"
});
function isHexCode(c) {
return 48 <= c && c <= 57 || 65 <= c && c <= 70 || 97 <= c && c <= 102;
}
function isOctCode(c) {
return 48 <= c && c <= 55;
}
function isDecCode(c) {
return 48 <= c && c <= 57;
}
function resolveYamlInteger(data) {
if (data === null) return false;
var max = data.length, index = 0, hasDigits = false, ch;
if (!max) return false;
ch = data[index];
if (ch === "-" || ch === "+") {
ch = data[++index];
}
if (ch === "0") {
if (index + 1 === max) return true;
ch = data[++index];
if (ch === "b") {
index++;
for (; index < max; index++) {
ch = data[index];
if (ch === "_") continue;
if (ch !== "0" && ch !== "1") return false;
hasDigits = true;
}
return hasDigits && ch !== "_";
}
if (ch === "x") {
index++;
for (; index < max; index++) {
ch = data[index];
if (ch === "_") continue;
if (!isHexCode(data.charCodeAt(index))) return false;
hasDigits = true;
}
return hasDigits && ch !== "_";
}
if (ch === "o") {
index++;
for (; index < max; index++) {
ch = data[index];
if (ch === "_") continue;
if (!isOctCode(data.charCodeAt(index))) return false;
hasDigits = true;
}
return hasDigits && ch !== "_";
}
}
if (ch === "_") return false;
for (; index < max; index++) {
ch = data[index];
if (ch === "_") continue;
if (!isDecCode(data.charCodeAt(index))) {
return false;
}
hasDigits = true;
}
if (!hasDigits || ch === "_") return false;
return true;
}
function constructYamlInteger(data) {
var value = data, sign = 1, ch;
if (value.indexOf("_") !== -1) {
value = value.replace(/_/g, "");
}
ch = value[0];
if (ch === "-" || ch === "+") {
if (ch === "-") sign = -1;
value = value.slice(1);
ch = value[0];
}
if (value === "0") return 0;
if (ch === "0") {
if (value[1] === "b") return sign * parseInt(value.slice(2), 2);
if (value[1] === "x") return sign * parseInt(value.slice(2), 16);
if (value[1] === "o") return sign * parseInt(value.slice(2), 8);
}
return sign * parseInt(value, 10);
}
function isInteger(object) {
return Object.prototype.toString.call(object) === "[object Number]" && (object % 1 === 0 && !common.isNegativeZero(object));
}
var int = new type("tag:yaml.org,2002:int", {
kind: "scalar",
resolve: resolveYamlInteger,
construct: constructYamlInteger,
predicate: isInteger,
represent: {
binary: function(obj) {
return obj >= 0 ? "0b" + obj.toString(2) : "-0b" + obj.toString(2).slice(1);
},
octal: function(obj) {
return obj >= 0 ? "0o" + obj.toString(8) : "-0o" + obj.toString(8).slice(1);
},
decimal: function(obj) {
return obj.toString(10);
},
/* eslint-disable max-len */
hexadecimal: function(obj) {
return obj >= 0 ? "0x" + obj.toString(16).toUpperCase() : "-0x" + obj.toString(16).toUpperCase().slice(1);
}
},
defaultStyle: "decimal",
styleAliases: {
binary: [2, "bin"],
octal: [8, "oct"],
decimal: [10, "dec"],
hexadecimal: [16, "hex"]
}
});
var YAML_FLOAT_PATTERN = new RegExp(
// 2.5e4, 2.5 and integers
"^(?:[-+]?(?:[0-9][0-9_]*)(?:\\.[0-9_]*)?(?:[eE][-+]?[0-9]+)?|\\.[0-9_]+(?:[eE][-+]?[0-9]+)?|[-+]?\\.(?:inf|Inf|INF)|\\.(?:nan|NaN|NAN))$"
);
function resolveYamlFloat(data) {
if (data === null) return false;
if (!YAML_FLOAT_PATTERN.test(data) || // Quick hack to not allow integers end with `_`
// Probably should update regexp & check speed
data[data.length - 1] === "_") {
return false;
}
return true;
}
function constructYamlFloat(data) {
var value, sign;
value = data.replace(/_/g, "").toLowerCase();
sign = value[0] === "-" ? -1 : 1;
if ("+-".indexOf(value[0]) >= 0) {
value = value.slice(1);
}
if (value === ".inf") {
return sign === 1 ? Number.POSITIVE_INFINITY : Number.NEGATIVE_INFINITY;
} else if (value === ".nan") {
return NaN;
}
return sign * parseFloat(value, 10);
}
var SCIENTIFIC_WITHOUT_DOT = /^[-+]?[0-9]+e/;
function representYamlFloat(object, style) {
var res;
if (isNaN(object)) {
switch (style) {
case "lowercase":
return ".nan";
case "uppercase":
return ".NAN";
case "camelcase":
return ".NaN";
}
} else if (Number.POSITIVE_INFINITY === object) {
switch (style) {
case "lowercase":
return ".inf";
case "uppercase":
return ".INF";
case "camelcase":
return ".Inf";
}
} else if (Number.NEGATIVE_INFINITY === object) {
switch (style) {
case "lowercase":
return "-.inf";
case "uppercase":
return "-.INF";
case "camelcase":
return "-.Inf";
}
} else if (common.isNegativeZero(object)) {
return "-0.0";
}
res = object.toString(10);
return SCIENTIFIC_WITHOUT_DOT.test(res) ? res.replace("e", ".e") : res;
}
function isFloat(object) {
return Object.prototype.toString.call(object) === "[object Number]" && (object % 1 !== 0 || common.isNegativeZero(object));
}
var float = new type("tag:yaml.org,2002:float", {
kind: "scalar",
resolve: resolveYamlFloat,
construct: constructYamlFloat,
predicate: isFloat,
represent: representYamlFloat,
defaultStyle: "lowercase"
});
var json = failsafe.extend({
implicit: [
_null,
bool,
int,
float
]
});
var core = json;
var YAML_DATE_REGEXP = new RegExp(
"^([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])$"
);
var YAML_TIMESTAMP_REGEXP = new RegExp(
"^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:[Tt]|[ \\t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \\t]*(Z|([-+])([0-9][0-9]?)(?::([0-9][0-9]))?))?$"
);
function resolveYamlTimestamp(data) {
if (data === null) return false;
if (YAML_DATE_REGEXP.exec(data) !== null) return true;
if (YAML_TIMESTAMP_REGEXP.exec(data) !== null) return true;
return false;
}
function constructYamlTimestamp(data) {
var match, year, month, day, hour, minute, second, fraction = 0, delta = null, tz_hour, tz_minute, date;
match = YAML_DATE_REGEXP.exec(data);
if (match === null) match = YAML_TIMESTAMP_REGEXP.exec(data);
if (match === null) throw new Error("Date resolve error");
year = +match[1];
month = +match[2] - 1;
day = +match[3];
if (!match[4]) {
return new Date(Date.UTC(year, month, day));
}
hour = +match[4];
minute = +match[5];
second = +match[6];
if (match[7]) {
fraction = match[7].slice(0, 3);
while (fraction.length < 3) {
fraction += "0";
}
fraction = +fraction;
}
if (match[9]) {
tz_hour = +match[10];
tz_minute = +(match[11] || 0);
delta = (tz_hour * 60 + tz_minute) * 6e4;
if (match[9] === "-") delta = -delta;
}
date = new Date(Date.UTC(year, month, day, hour, minute, second, fraction));
if (delta) date.setTime(date.getTime() - delta);
return date;
}
function representYamlTimestamp(object) {
return object.toISOString();
}
var timestamp = new type("tag:yaml.org,2002:timestamp", {
kind: "scalar",
resolve: resolveYamlTimestamp,
construct: constructYamlTimestamp,
instanceOf: Date,
represent: representYamlTimestamp
});
function resolveYamlMerge(data) {
return data === "<<" || data === null;
}
var merge = new type("tag:yaml.org,2002:merge", {
kind: "scalar",
resolve: resolveYamlMerge
});
var BASE64_MAP = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=\n\r";
function resolveYamlBinary(data) {
if (data === null) return false;
var code, idx, bitlen = 0, max = data.length, map2 = BASE64_MAP;
for (idx = 0; idx < max; idx++) {
code = map2.indexOf(data.charAt(idx));
if (code > 64) continue;
if (code < 0) return false;
bitlen += 6;
}
return bitlen % 8 === 0;
}
function constructYamlBinary(data) {
var idx, tailbits, input = data.replace(/[\r\n=]/g, ""), max = input.length, map2 = BASE64_MAP, bits = 0, result = [];
for (idx = 0; idx < max; idx++) {
if (idx % 4 === 0 && idx) {
result.push(bits >> 16 & 255);
result.push(bits >> 8 & 255);
result.push(bits & 255);
}
bits = bits << 6 | map2.indexOf(input.charAt(idx));
}
tailbits = max % 4 * 6;
if (tailbits === 0) {
result.push(bits >> 16 & 255);
result.push(bits >> 8 & 255);
result.push(bits & 255);
} else if (tailbits === 18) {
result.push(bits >> 10 & 255);
result.push(bits >> 2 & 255);
} else if (tailbits === 12) {
result.push(bits >> 4 & 255);
}
return new Uint8Array(result);
}
function representYamlBinary(object) {
var result = "", bits = 0, idx, tail, max = object.length, map2 = BASE64_MAP;
for (idx = 0; idx < max; idx++) {
if (idx % 3 === 0 && idx) {
result += map2[bits >> 18 & 63];
result += map2[bits >> 12 & 63];
result += map2[bits >> 6 & 63];
result += map2[bits & 63];
}
bits = (bits << 8) + object[idx];
}
tail = max % 3;
if (tail === 0) {
result += map2[bits >> 18 & 63];
result += map2[bits >> 12 & 63];
result += map2[bits >> 6 & 63];
result += map2[bits & 63];
} else if (tail === 2) {
result += map2[bits >> 10 & 63];
result += map2[bits >> 4 & 63];
result += map2[bits << 2 & 63];
result += map2[64];
} else if (tail === 1) {
result += map2[bits >> 2 & 63];
result += map2[bits << 4 & 63];
result += map2[64];
result += map2[64];
}
return result;
}
function isBinary(obj) {
return Object.prototype.toString.call(obj) === "[object Uint8Array]";
}
var binary = new type("tag:yaml.org,2002:binary", {
kind: "scalar",
resolve: resolveYamlBinary,
construct: constructYamlBinary,
predicate: isBinary,
represent: representYamlBinary
});
var _hasOwnProperty$3 = Object.prototype.hasOwnProperty;
var _toString$2 = Object.prototype.toString;
function resolveYamlOmap(data) {
if (data === null) return true;
var objectKeys = [], index, length, pair, pairKey, pairHasKey, object = data;
for (index = 0, length = object.length; index < length; index += 1) {
pair = object[index];
pairHasKey = false;
if (_toString$2.call(pair) !== "[object Object]") return false;
for (pairKey in pair) {
if (_hasOwnProperty$3.call(pair, pairKey)) {
if (!pairHasKey) pairHasKey = true;
else return false;
}
}
if (!pairHasKey) return false;
if (objectKeys.indexOf(pairKey) === -1) objectKeys.push(pairKey);
else return false;
}
return true;
}
function constructYamlOmap(data) {
return data !== null ? data : [];
}
var omap = new type("tag:yaml.org,2002:omap", {
kind: "sequence",
resolve: resolveYamlOmap,
construct: constructYamlOmap
});
var _toString$1 = Object.prototype.toString;
function resolveYamlPairs(data) {
if (data === null) return true;
var index, length, pair, keys, result, object = data;
result = new Array(object.length);
for (index = 0, length = object.length; index < length; index += 1) {
pair = object[index];
if (_toString$1.call(pair) !== "[object Object]") return false;
keys = Object.keys(pair);
if (keys.length !== 1) return false;
result[index] = [keys[0], pair[keys[0]]];
}
return true;
}
function constructYamlPairs(data) {
if (data === null) return [];
var index, length, pair, keys, result, object = data;
result = new Array(object.length);
for (index = 0, length = object.length; index < length; index += 1) {
pair = object[index];
keys = Object.keys(pair);
result[index] = [keys[0], pair[keys[0]]];
}
return result;
}
var pairs = new type("tag:yaml.org,2002:pairs", {
kind: "sequence",
resolve: resolveYamlPairs,
construct: constructYamlPairs
});
var _hasOwnProperty$2 = Object.prototype.hasOwnProperty;
function resolveYamlSet(data) {
if (data === null) return true;
var key, object = data;
for (key in object) {
if (_hasOwnProperty$2.call(object, key)) {
if (object[key] !== null) return false;
}
}
return true;
}
function constructYamlSet(data) {
return data !== null ? data : {};
}
var set = new type("tag:yaml.org,2002:set", {
kind: "mapping",
resolve: resolveYamlSet,
construct: constructYamlSet
});
var _default = core.extend({
implicit: [
timestamp,
merge
],
explicit: [
binary,
omap,
pairs,
set
]
});
var _hasOwnProperty$1 = Object.prototype.hasOwnProperty;
var CONTEXT_FLOW_IN = 1;
var CONTEXT_FLOW_OUT = 2;
var CONTEXT_BLOCK_IN = 3;
var CONTEXT_BLOCK_OUT = 4;
var CHOMPING_CLIP = 1;
var CHOMPING_STRIP = 2;
var CHOMPING_KEEP = 3;
var PATTERN_NON_PRINTABLE = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x84\x86-\x9F\uFFFE\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/;
var PATTERN_NON_ASCII_LINE_BREAKS = /[\x85\u2028\u2029]/;
var PATTERN_FLOW_INDICATORS = /[,\[\]\{\}]/;
var PATTERN_TAG_HANDLE = /^(?:!|!!|![a-z\-]+!)$/i;
var PATTERN_TAG_URI = /^(?:!|[^,\[\]\{\}])(?:%[0-9a-f]{2}|[0-9a-z\-#;\/\?:@&=\+\$,_\.!~\*'\(\)\[\]])*$/i;
function _class(obj) {
return Object.prototype.toString.call(obj);
}
function is_EOL(c) {
return c === 10 || c === 13;
}
function is_WHITE_SPACE(c) {
return c === 9 || c === 32;
}
function is_WS_OR_EOL(c) {
return c === 9 || c === 32 || c === 10 || c === 13;
}
function is_FLOW_INDICATOR(c) {
return c === 44 || c === 91 || c === 93 || c === 123 || c === 125;
}
function fromHexCode(c) {
var lc;
if (48 <= c && c <= 57) {
return c - 48;
}
lc = c | 32;
if (97 <= lc && lc <= 102) {
return lc - 97 + 10;
}
return -1;
}
function escapedHexLen(c) {
if (c === 120) {
return 2;
}
if (c === 117) {
return 4;
}
if (c === 85) {
return 8;
}
return 0;
}
function fromDecimalCode(c) {
if (48 <= c && c <= 57) {
return c - 48;
}
return -1;
}
function simpleEscapeSequence(c) {
return c === 48 ? "\0" : c === 97 ? "\x07" : c === 98 ? "\b" : c === 116 ? " " : c === 9 ? " " : c === 110 ? "\n" : c === 118 ? "\v" : c === 102 ? "\f" : c === 114 ? "\r" : c === 101 ? "\x1B" : c === 32 ? " " : c === 34 ? '"' : c === 47 ? "/" : c === 92 ? "\\" : c === 78 ? "Β
" : c === 95 ? "Β " : c === 76 ? "\u2028" : c === 80 ? "\u2029" : "";
}
function charFromCodepoint(c) {
if (c <= 65535) {
return String.fromCharCode(c);
}
return String.fromCharCode(
(c - 65536 >> 10) + 55296,
(c - 65536 & 1023) + 56320
);
}
var simpleEscapeCheck = new Array(256);
var simpleEscapeMap = new Array(256);
for (var i = 0; i < 256; i++) {
simpleEscapeCheck[i] = simpleEscapeSequence(i) ? 1 : 0;
simpleEscapeMap[i] = simpleEscapeSequence(i);
}
function State$1(input, options) {
this.input = input;
this.filename = options["filename"] || null;
this.schema = options["schema"] || _default;
this.onWarning = options["onWarning"] || null;
this.legacy = options["legacy"] || false;
this.json = options["json"] || false;
this.listener = options["listener"] || null;
this.implicitTypes = this.schema.compiledImplicit;
this.typeMap = this.schema.compiledTypeMap;
this.length = input.length;
this.position = 0;
this.line = 0;
this.lineStart = 0;
this.lineIndent = 0;
this.firstTabInLine = -1;
this.documents = [];
}
function generateError(state, message) {
var mark = {
name: state.filename,
buffer: state.input.slice(0, -1),
// omit trailing \0
position: state.position,
line: state.line,
column: state.position - state.lineStart
};
mark.snippet = snippet(mark);
return new exception(message, mark);
}
function throwError(state, message) {
throw generateError(state, message);
}
function throwWarning(state, message) {
if (state.onWarning) {
state.onWarning.call(null, generateError(state, message));
}
}
var directiveHandlers = {
YAML: function handleYamlDirective(state, name, args) {
var match, major, minor;
if (state.version !== null) {
throwError(state, "duplication of %YAML directive");
}
if (args.length !== 1) {
throwError(state, "YAML directive accepts exactly one argument");
}
match = /^([0-9]+)\.([0-9]+)$/.exec(args[0]);
if (match === null) {
throwError(state, "ill-formed argument of the YAML directive");
}
major = parseInt(match[1], 10);
minor = parseInt(match[2], 10);
if (major !== 1) {
throwError(state, "unacceptable YAML version of the document");
}
state.version = args[0];
state.checkLineBreaks = minor < 2;
if (minor !== 1 && minor !== 2) {
throwWarning(state, "unsupported YAML version of the document");
}
},
TAG: function handleTagDirective(state, name, args) {
var handle, prefix;
if (args.length !== 2) {
throwError(state, "TAG directive accepts exactly two arguments");
}
handle = args[0];
prefix = args[1];
if (!PATTERN_TAG_HANDLE.test(handle)) {
throwError(state, "ill-formed tag handle (first argument) of the TAG directive");
}
if (_hasOwnProperty$1.call(state.tagMap, handle)) {
throwError(state, 'there is a previously declared suffix for "' + handle + '" tag handle');
}
if (!PATTERN_TAG_URI.test(prefix)) {
throwError(state, "ill-formed tag prefix (second argument) of the TAG directive");
}
try {
prefix = decodeURIComponent(prefix);
} catch (err) {
throwError(state, "tag prefix is malformed: " + prefix);
}
state.tagMap[handle] = prefix;
}
};
function captureSegment(state, start, end, checkJson) {
var _position, _length, _character, _result;
if (start < end) {
_result = state.input.slice(start, end);
if (checkJson) {
for (_position = 0, _length = _result.length; _position < _length; _position += 1) {
_character = _result.charCodeAt(_position);
if (!(_character === 9 || 32 <= _character && _character <= 1114111)) {
throwError(state, "expected valid JSON character");
}
}
} else if (PATTERN_NON_PRINTABLE.test(_result)) {
throwError(state, "the stream contains non-printable characters");
}
state.result += _result;
}
}
function mergeMappings(state, destination, source, overridableKeys) {
var sourceKeys, key, index, quantity;
if (!common.isObject(source)) {
throwError(state, "cannot merge mappings; the provided source object is unacceptable");
}
sourceKeys = Object.keys(source);
for (index = 0, quantity = sourceKeys.length; index < quantity; index += 1) {
key = sourceKeys[index];
if (!_hasOwnProperty$1.call(destination, key)) {
destination[key] = source[key];
overridableKeys[key] = true;
}
}
}
function storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode, startLine, startLineStart, startPos) {
var index, quantity;
if (Array.isArray(keyNode)) {
keyNode = Array.prototype.slice.call(keyNode);
for (index = 0, quantity = keyNode.length; index < quantity; index += 1) {
if (Array.isArray(keyNode[index])) {
throwError(state, "nested arrays are not supported inside keys");
}
if (typeof keyNode === "object" && _class(keyNode[index]) === "[object Object]") {
keyNode[index] = "[object Object]";
}
}
}
if (typeof keyNode === "object" && _class(keyNode) === "[object Object]") {
keyNode = "[object Object]";
}
keyNode = String(keyNode);
if (_result === null) {
_result = {};
}
if (keyTag === "tag:yaml.org,2002:merge") {
if (Array.isArray(valueNode)) {
for (index = 0, quantity = valueNode.length; index < quantity; index += 1) {
mergeMappings(state, _result, valueNode[index], overridableKeys);
}
} else {
mergeMappings(state, _result, valueNode, overridableKeys);
}
} else {
if (!state.json && !_hasOwnProperty$1.call(overridableKeys, keyNode) && _hasOwnProperty$1.call(_result, keyNode)) {
state.line = startLine || state.line;
state.lineStart = startLineStart || state.lineStart;
state.position = startPos || state.position;
throwError(state, "duplicated mapping key");
}
if (keyNode === "__proto__") {
Object.defineProperty(_result, keyNode, {
configurable: true,
enumerable: true,
writable: true,
value: valueNode
});
} else {
_result[keyNode] = valueNode;
}
delete overridableKeys[keyNode];
}
return _result;
}
function readLineBreak(state) {
var ch;
ch = state.input.charCodeAt(state.position);
if (ch === 10) {
state.position++;
} else if (ch === 13) {
state.position++;
if (state.input.charCodeAt(state.position) === 10) {
state.position++;
}
} else {
throwError(state, "a line break is expected");
}
state.line += 1;
state.lineStart = state.position;
state.firstTabInLine = -1;
}
function skipSeparationSpace(state, allowComments, checkIndent) {
var lineBreaks = 0, ch = state.input.charCodeAt(state.position);
while (ch !== 0) {
while (is_WHITE_SPACE(ch)) {
if (ch === 9 && state.firstTabInLine === -1) {
state.firstTabInLine = state.position;
}
ch = state.input.charCodeAt(++state.position);
}
if (allowComments && ch === 35) {
do {
ch = state.input.charCodeAt(++state.position);
} while (ch !== 10 && ch !== 13 && ch !== 0);
}
if (is_EOL(ch)) {
readLineBreak(state);
ch = state.input.charCodeAt(state.position);
lineBreaks++;
state.lineIndent = 0;
while (ch === 32) {
state.lineIndent++;
ch = state.input.charCodeAt(++state.position);
}
} else {
break;
}
}
if (checkIndent !== -1 && lineBreaks !== 0 && state.lineIndent < checkIndent) {
throwWarning(state, "deficient indentation");
}
return lineBreaks;
}
function testDocumentSeparator(state) {
var _position = state.position, ch;
ch = state.input.charCodeAt(_position);
if ((ch === 45 || ch === 46) && ch === state.input.charCodeAt(_position + 1) && ch === state.input.charCodeAt(_position + 2)) {
_position += 3;
ch = state.input.charCodeAt(_position);
if (ch === 0 || is_WS_OR_EOL(ch)) {
return true;
}
}
return false;
}
function writeFoldedLines(state, count) {
if (count === 1) {
state.result += " ";
} else if (count > 1) {
state.result += common.repeat("\n", count - 1);
}
}
function readPlainScalar(state, nodeIndent, withinFlowCollection) {
var preceding, following, captureStart, captureEnd, hasPendingContent, _line, _lineStart, _lineIndent, _kind = state.kind, _result = state.result, ch;
ch = state.input.charCodeAt(state.position);
if (is_WS_OR_EOL(ch) || is_FLOW_INDICATOR(ch) || ch === 35 || ch === 38 || ch === 42 || ch === 33 || ch === 124 || ch === 62 || ch === 39 || ch === 34 || ch === 37 || ch === 64 || ch === 96) {
return false;
}
if (ch === 63 || ch === 45) {
following = state.input.charCodeAt(state.position + 1);
if (is_WS_OR_EOL(following) || withinFlowCollection && is_FLOW_INDICATOR(following)) {
return false;
}
}
state.kind = "scalar";
state.result = "";
captureStart = captureEnd = state.position;
hasPendingContent = false;
while (ch !== 0) {
if (ch === 58) {
following = state.input.charCodeAt(state.position + 1);
if (is_WS_OR_EOL(following) || withinFlowCollection && is_FLOW_INDICATOR(following)) {
break;
}
} else if (ch === 35) {
preceding = state.input.charCodeAt(state.position - 1);
if (is_WS_OR_EOL(preceding)) {
break;
}
} else if (state.position === state.lineStart && testDocumentSeparator(state) || withinFlowCollection && is_FLOW_INDICATOR(ch)) {
break;
} else if (is_EOL(ch)) {
_line = state.line;
_lineStart = state.lineStart;
_lineIndent = state.lineIndent;
skipSeparationSpace(state, false, -1);
if (state.lineIndent >= nodeIndent) {
hasPendingContent = true;
ch = state.input.charCodeAt(state.position);
continue;
} else {
state.position = captureEnd;
state.line = _line;
state.lineStart = _lineStart;
state.lineIndent = _lineIndent;
break;
}
}
if (hasPendingContent) {
captureSegment(state, captureStart, captureEnd, false);
writeFoldedLines(state, state.line - _line);
captureStart = captureEnd = state.position;
hasPendingContent = false;
}
if (!is_WHITE_SPACE(ch)) {
captureEnd = state.position + 1;
}
ch = state.input.charCodeAt(++state.position);
}
captureSegment(state, captureStart, captureEnd, false);
if (state.result) {
return true;
}
state.kind = _kind;
state.result = _result;
return false;
}
function readSingleQuotedScalar(state, nodeIndent) {
var ch, captureStart, captureEnd;
ch = state.input.charCodeAt(state.position);
if (ch !== 39) {
return false;
}
state.kind = "scalar";
state.result = "";
state.position++;
captureStart = captureEnd = state.position;
while ((ch = state.input.charCodeAt(state.position)) !== 0) {
if (ch === 39) {
captureSegment(state, captureStart, state.position, true);
ch = state.input.charCodeAt(++state.position);
if (ch === 39) {
captureStart = state.position;
state.position++;
captureEnd = state.position;
} else {
return true;
}
} else if (is_EOL(ch)) {
captureSegment(state, captureStart, captureEnd, true);
writeFoldedLines(state, skipSeparationSpace(state, false, nodeIndent));
captureStart = captureEnd = state.position;
} else if (state.position === state.lineStart && testDocumentSeparator(state)) {
throwError(state, "unexpected end of the document within a single quoted scalar");
} else {
state.position++;
captureEnd = state.position;
}
}
throwError(state, "unexpected end of the stream within a single quoted scalar");
}
function readDoubleQuotedScalar(state, nodeIndent) {
var captureStart, captureEnd, hexLength, hexResult, tmp, ch;
ch = state.input.charCodeAt(state.position);
if (ch !== 34) {
return false;
}
state.kind = "scalar";
state.result = "";
state.position++;
captureStart = captureEnd = state.position;
while ((ch = state.input.charCodeAt(state.position)) !== 0) {
if (ch === 34) {
captureSegment(state, captureStart, state.position, true);
state.position++;
return true;
} else if (ch === 92) {
captureSegment(state, captureStart, state.position, true);
ch = state.input.charCodeAt(++state.position);
if (is_EOL(ch)) {
skipSeparationSpace(state, false, nodeIndent);
} else if (ch < 256 && simpleEscapeCheck[ch]) {
state.result += simpleEscapeMap[ch];
state.position++;
} else if ((tmp = escapedHexLen(ch)) > 0) {
hexLength = tmp;
hexResult = 0;
for (; hexLength > 0; hexLength--) {
ch = state.input.charCodeAt(++state.position);
if ((tmp = fromHexCode(ch)) >= 0) {
hexResult = (hexResult << 4) + tmp;
} else {
throwError(state, "expected hexadecimal character");
}
}
state.result += charFromCodepoint(hexResult);
state.position++;
} else {
throwError(state, "unknown escape sequence");
}
captureStart = captureEnd = state.position;
} else if (is_EOL(ch)) {
captureSegment(state, captureStart, captureEnd, true);
writeFoldedLines(state, skipSeparationSpace(state, false, nodeIndent));
captureStart = captureEnd = state.position;
} else if (state.position === state.lineStart && testDocumentSeparator(state)) {
throwError(state, "unexpected end of the document within a double quoted scalar");
} else {
state.position++;
captureEnd = state.position;
}
}
throwError(state, "unexpected end of the stream within a double quoted scalar");
}
function readFlowCollection(state, nodeIndent) {
var readNext = true, _line, _lineStart, _pos, _tag = state.tag, _result, _anchor = state.anchor, following, terminator, isPair, isExplicitPair, isMapping, overridableKeys = /* @__PURE__ */ Object.create(null), keyNode, keyTag, valueNode, ch;
ch = state.input.charCodeAt(state.position);
if (ch === 91) {
terminator = 93;
isMapping = false;
_result = [];
} else if (ch === 123) {
terminator = 125;
isMapping = true;
_result = {};
} else {
return false;
}
if (state.anchor !== null) {
state.anchorMap[state.anchor] = _result;
}
ch = state.input.charCodeAt(++state.position);
while (ch !== 0) {
skipSeparationSpace(state, true, nodeIndent);
ch = state.input.charCodeAt(state.position);
if (ch === terminator) {
state.position++;
state.tag = _tag;
state.anchor = _anchor;
state.kind = isMapping ? "mapping" : "sequence";
state.result = _result;
return true;
} else if (!readNext) {
throwError(state, "missed comma between flow collection entries");
} else if (ch === 44) {
throwError(state, "expected the node content, but found ','");
}
keyTag = keyNode = valueNode = null;
isPair = isExplicitPair = false;
if (ch === 63) {
following = state.input.charCodeAt(state.position + 1);
if (is_WS_OR_EOL(following)) {
isPair = isExplicitPair = true;
state.position++;
skipSeparationSpace(state, true, nodeIndent);
}
}
_line = state.line;
_lineStart = state.lineStart;
_pos = state.position;
composeNode(state, nodeIndent, CONTEXT_FLOW_IN, false, true);
keyTag = state.tag;
keyNode = state.result;
skipSeparationSpace(state, true, nodeIndent);
ch = state.input.charCodeAt(state.position);
if ((isExplicitPair || state.line === _line) && ch === 58) {
isPair = true;
ch = state.input.charCodeAt(++state.position);
skipSeparationSpace(state, true, nodeIndent);
composeNode(state, nodeIndent, CONTEXT_FLOW_IN, false, true);
valueNode = state.result;
}
if (isMapping) {
storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode, _line, _lineStart, _pos);
} else if (isPair) {
_result.push(storeMappingPair(state, null, overridableKeys, keyTag, keyNode, valueNode, _line, _lineStart, _pos));
} else {
_result.push(keyNode);
}
skipSeparationSpace(state, true, nodeIndent);
ch = state.input.charCodeAt(state.position);
if (ch === 44) {
readNext = true;
ch = state.input.charCodeAt(++state.position);
} else {
readNext = false;
}
}
throwError(state, "unexpected end of the stream within a flow collection");
}
function readBlockScalar(state, nodeIndent) {
var captureStart, folding, chomping = CHOMPING_CLIP, didReadContent = false, detectedIndent = false, textIndent = nodeIndent, emptyLines = 0, atMoreIndented = false, tmp, ch;
ch = state.input.charCodeAt(state.position);
if (ch === 124) {
folding = false;
} else if (ch === 62) {
folding = true;
} else {
return false;
}
state.kind = "scalar";
state.result = "";
while (ch !== 0) {
ch = state.input.charCodeAt(++state.position);
if (ch === 43 || ch === 45) {
if (CHOMPING_CLIP === chomping) {
chomping = ch === 43 ? CHOMPING_KEEP : CHOMPING_STRIP;
} else {
throwError(state, "repeat of a chomping mode identifier");
}
} else if ((tmp = fromDecimalCode(ch)) >= 0) {
if (tmp === 0) {
throwError(state, "bad explicit indentation width of a block scalar; it cannot be less than one");
} else if (!detectedIndent) {
textIndent = nodeIndent + tmp - 1;
detectedIndent = true;
} else {
throwError(state, "repeat of an indentation width identifier");
}
} else {
break;
}
}
if (is_WHITE_SPACE(ch)) {
do {
ch = state.input.charCodeAt(++state.position);
} while (is_WHITE_SPACE(ch));
if (ch === 35) {
do {
ch = state.input.charCodeAt(++state.position);
} while (!is_EOL(ch) && ch !== 0);
}
}
while (ch !== 0) {
readLineBreak(state);
state.lineIndent = 0;
ch = state.input.charCodeAt(state.position);
while ((!detectedIndent || state.lineIndent < textIndent) && ch === 32) {
state.lineIndent++;
ch = state.input.charCodeAt(++state.position);
}
if (!detectedIndent && state.lineIndent > textIndent) {
textIndent = state.lineIndent;
}
if (is_EOL(ch)) {
emptyLines++;
continue;
}
if (state.lineIndent < textIndent) {
if (chomping === CHOMPING_KEEP) {
state.result += common.repeat("\n", didReadContent ? 1 + emptyLines : emptyLines);
} else if (chomping === CHOMPING_CLIP) {
if (didReadContent) {
state.result += "\n";
}
}
break;
}
if (folding) {
if (is_WHITE_SPACE(ch)) {
atMoreIndented = true;
state.result += common.repeat("\n", didReadContent ? 1 + emptyLines : emptyLines);
} else if (atMoreIndented) {
atMoreIndented = false;
state.result += common.repeat("\n", emptyLines + 1);
} else if (emptyLines === 0) {
if (didReadContent) {
state.result += " ";
}
} else {
state.result += common.repeat("\n", emptyLines);
}
} else {
state.result += common.repeat("\n", didReadContent ? 1 + emptyLines : emptyLines);
}
didReadContent = true;
detectedIndent = true;
emptyLines = 0;
captureStart = state.position;
while (!is_EOL(ch) && ch !== 0) {
ch = state.input.charCodeAt(++state.position);
}
captureSegment(state, captureStart, state.position, false);
}
return true;
}
function readBlockSequence(state, nodeIndent) {
var _line, _tag = state.tag, _anchor = state.anchor, _result = [], following, detected = false, ch;
if (state.firstTabInLine !== -1) return false;
if (state.anchor !== null) {
state.anchorMap[state.anchor] = _result;
}
ch = state.input.charCodeAt(state.position);
while (ch !== 0) {
if (state.firstTabInLine !== -1) {
state.position = state.firstTabInLine;
throwError(state, "tab characters must not be used in indentation");
}
if (ch !== 45) {
break;
}
following = state.input.charCodeAt(state.position + 1);
if (!is_WS_OR_EOL(following)) {
break;
}
detected = true;
state.position++;
if (skipSeparationSpace(state, true, -1)) {
if (state.lineIndent <= nodeIndent) {
_result.push(null);
ch = state.input.charCodeAt(state.position);
continue;
}
}
_line = state.line;
composeNode(state, nodeIndent, CONTEXT_BLOCK_IN, false, true);
_result.push(state.result);
skipSeparationSpace(state, true, -1);
ch = state.input.charCodeAt(state.position);
if ((state.line === _line || state.lineIndent > nodeIndent) && ch !== 0) {
throwError(state, "bad indentation of a sequence entry");
} else if (state.lineIndent < nodeIndent) {
break;
}
}
if (detected) {
state.tag = _tag;
state.anchor = _anchor;
state.kind = "sequence";
state.result = _result;
return true;
}
return false;
}
function readBlockMapping(state, nodeIndent, flowIndent) {
var following, allowCompact, _line, _keyLine, _keyLineStart, _keyPos, _tag = state.tag, _anchor = state.anchor, _result = {}, overridableKeys = /* @__PURE__ */ Object.create(null), keyTag = null, keyNode = null, valueNode = null, atExplicitKey = false, detected = false, ch;
if (state.firstTabInLine !== -1) return false;
if (state.anchor !== null) {
state.anchorMap[state.anchor] = _result;
}
ch = state.input.charCodeAt(state.position);
while (ch !== 0) {
if (!atExplicitKey && state.firstTabInLine !== -1) {
state.position = state.firstTabInLine;
throwError(state, "tab characters must not be used in indentation");
}
following = state.input.charCodeAt(state.position + 1);
_line = state.line;
if ((ch === 63 || ch === 58) && is_WS_OR_EOL(following)) {
if (ch === 63) {
if (atExplicitKey) {
storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null, _keyLine, _keyLineStart, _keyPos);
keyTag = keyNode = valueNode = null;
}
detected = true;
atExplicitKey = true;
allowCompact = true;
} else if (atExplicitKey) {
atExplicitKey = false;
allowCompact = true;
} else {
throwError(state, "incomplete explicit mapping pair; a key node is missed; or followed by a non-tabulated empty line");
}
state.position += 1;
ch = following;
} else {
_keyLine = state.line;
_keyLineStart = state.lineStart;
_keyPos = state.position;
if (!composeNode(state, flowIndent, CONTEXT_FLOW_OUT, false, true)) {
break;
}
if (state.line === _line) {
ch = state.input.charCodeAt(state.position);
while (is_WHITE_SPACE(ch)) {
ch = state.input.charCodeAt(++state.position);
}
if (ch === 58) {
ch = state.input.charCodeAt(++state.position);
if (!is_WS_OR_EOL(ch)) {
throwError(state, "a whitespace character is expected after the key-value separator within a block mapping");
}
if (atExplicitKey) {
storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null, _keyLine, _keyLineStart, _keyPos);
keyTag = keyNode = valueNode = null;
}
detected = true;
atExplicitKey = false;
allowCompact = false;
keyTag = state.tag;
keyNode = state.result;
} else if (detected) {
throwError(state, "can not read an implicit mapping pair; a colon is missed");
} else {
state.tag = _tag;
state.anchor = _anchor;
return true;
}
} else if (detected) {
throwError(state, "can not read a block mapping entry; a multiline key may not be an implicit key");
} else {
state.tag = _tag;
state.anchor = _anchor;
return true;
}
}
if (state.line === _line || state.lineIndent > nodeIndent) {
if (atExplicitKey) {
_keyLine = state.line;
_keyLineStart = state.lineStart;
_keyPos = state.position;
}
if (composeNode(state, nodeIndent, CONTEXT_BLOCK_OUT, true, allowCompact)) {
if (atExplicitKey) {
keyNode = state.result;
} else {
valueNode = state.result;
}
}
if (!atExplicitKey) {
storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, valueNode, _keyLine, _keyLineStart, _keyPos);
keyTag = keyNode = valueNode = null;
}
skipSeparationSpace(state, true, -1);
ch = state.input.charCodeAt(state.position);
}
if ((state.line === _line || state.lineIndent > nodeIndent) && ch !== 0) {
throwError(state, "bad indentation of a mapping entry");
} else if (state.lineIndent < nodeIndent) {
break;
}
}
if (atExplicitKey) {
storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null, _keyLine, _keyLineStart, _keyPos);
}
if (detected) {
state.tag = _tag;
state.anchor = _anchor;
state.kind = "mapping";
state.result = _result;
}
return detected;
}
function readTagProperty(state) {
var _position, isVerbatim = false, isNamed = false, tagHandle, tagName, ch;
ch = state.input.charCodeAt(state.position);
if (ch !== 33) return false;
if (state.tag !== null) {
throwError(state, "duplication of a tag property");
}
ch = state.input.charCodeAt(++state.position);
if (ch === 60) {
isVerbatim = true;
ch = state.input.charCodeAt(++state.position);
} else if (ch === 33) {
isNamed = true;
tagHandle = "!!";
ch = state.input.charCodeAt(++state.position);
} else {
tagHandle = "!";
}
_position = state.position;
if (isVerbatim) {
do {
ch = state.input.charCodeAt(++state.position);
} while (ch !== 0 && ch !== 62);
if (state.position < state.length) {
tagName = state.input.slice(_position, state.position);
ch = state.input.charCodeAt(++state.position);
} else {
throwError(state, "unexpected end of the stream within a verbatim tag");
}
} else {
while (ch !== 0 && !is_WS_OR_EOL(ch)) {
if (ch === 33) {
if (!isNamed) {
tagHandle = state.input.slice(_position - 1, state.position + 1);
if (!PATTERN_TAG_HANDLE.test(tagHandle)) {
throwError(state, "named tag handle cannot contain such characters");
}
isNamed = true;
_position = state.position + 1;
} else {
throwError(state, "tag suffix cannot contain exclamation marks");
}
}
ch = state.input.charCodeAt(++state.position);
}
tagName = state.input.slice(_position, state.position);
if (PATTERN_FLOW_INDICATORS.test(tagName)) {
throwError(state, "tag suffix cannot contain flow indicator characters");
}
}
if (tagName && !PATTERN_TAG_URI.test(tagName)) {
throwError(state, "tag name cannot contain such characters: " + tagName);
}
try {
tagName = decodeURIComponent(tagName);
} catch (err) {
throwError(state, "tag name is malformed: " + tagName);
}
if (isVerbatim) {
state.tag = tagName;
} else if (_hasOwnProperty$1.call(state.tagMap, tagHandle)) {
state.tag = state.tagMap[tagHandle] + tagName;
} else if (tagHandle === "!") {
state.tag = "!" + tagName;
} else if (tagHandle === "!!") {
state.tag = "tag:yaml.org,2002:" + tagName;
} else {
throwError(state, 'undeclared tag handle "' + tagHandle + '"');
}
return true;
}
function readAnchorProperty(state) {
var _position, ch;
ch = state.input.charCodeAt(state.position);
if (ch !== 38) return false;
if (state.anchor !== null) {
throwError(state, "duplication of an anchor property");
}
ch = state.input.charCodeAt(++state.position);
_position = state.position;
while (ch !== 0 && !is_WS_OR_EOL(ch) && !is_FLOW_INDICATOR(ch)) {
ch = state.input.charCodeAt(++state.position);
}
if (state.position === _position) {
throwError(state, "name of an anchor node must contain at least one character");
}
state.anchor = state.input.slice(_position, state.position);
return true;
}
function readAlias(state) {
var _position, alias, ch;
ch = state.input.charCodeAt(state.position);
if (ch !== 42) return false;
ch = state.input.charCodeAt(++state.position);
_position = state.position;
while (ch !== 0 && !is_WS_OR_EOL(ch) && !is_FLOW_INDICATOR(ch)) {
ch = state.input.charCodeAt(++state.position);
}
if (state.position === _position) {
throwError(state, "name of an alias node must contain at least one character");
}
alias = state.input.slice(_position, state.position);
if (!_hasOwnProperty$1.call(state.anchorMap, alias)) {
throwError(state, 'unidentified alias "' + alias + '"');
}
state.result = state.anchorMap[alias];
skipSeparationSpace(state, true, -1);
return true;
}
function composeNode(state, parentIndent, nodeContext, allowToSeek, allowCompact) {
var allowBlockStyles, allowBlockScalars, allowBlockCollections, indentStatus = 1, atNewLine = false, hasContent = false, typeIndex, typeQuantity, typeList, type2, flowIndent, blockIndent;
if (state.listener !== null) {
state.listener("open", state);
}
state.tag = null;
state.anchor = null;
state.kind = null;
state.result = null;
allowBlockStyles = allowBlockScalars = allowBlockCollections = CONTEXT_BLOCK_OUT === nodeContext || CONTEXT_BLOCK_IN === nodeContext;
if (allowToSeek) {
if (skipSeparationSpace(state, true, -1)) {
atNewLine = true;
if (state.lineIndent > parentIndent) {
indentStatus = 1;
} else if (state.lineIndent === parentIndent) {
indentStatus = 0;
} else if (state.lineIndent < parentIndent) {
indentStatus = -1;
}
}
}
if (indentStatus === 1) {
while (readTagProperty(state) || readAnchorProperty(state)) {
if (skipSeparationSpace(state, true, -1)) {
atNewLine = true;
allowBlockCollections = allowBlockStyles;
if (state.lineIndent > parentIndent) {
indentStatus = 1;
} else if (state.lineIndent === parentIndent) {
indentStatus = 0;
} else if (state.lineIndent < parentIndent) {
indentStatus = -1;
}
} else {
allowBlockCollections = false;
}
}
}
if (allowBlockCollections) {
allowBlockCollections = atNewLine || allowCompact;
}
if (indentStatus === 1 || CONTEXT_BLOCK_OUT === nodeContext) {
if (CONTEXT_FLOW_IN === nodeContext || CONTEXT_FLOW_OUT === nodeContext) {
flowIndent = parentIndent;
} else {
flowIndent = parentIndent + 1;
}
blockIndent = state.position - state.lineStart;
if (indentStatus === 1) {
if (allowBlockCollections && (readBlockSequence(state, blockIndent) || readBlockMapping(state, blockIndent, flowIndent)) || readFlowCollection(state, flowIndent)) {
hasContent = true;
} else {
if (allowBlockScalars && readBlockScalar(state, flowIndent) || readSingleQuotedScalar(state, flowIndent) || readDoubleQuotedScalar(state, flowIndent)) {
hasContent = true;
} else if (readAlias(state)) {
hasContent = true;
if (state.tag !== null || state.anchor !== null) {
throwError(state, "alias node should not have any properties");
}
} else if (readPlainScalar(state, flowIndent, CONTEXT_FLOW_IN === nodeContext)) {
hasContent = true;
if (state.tag === null) {
state.tag = "?";
}
}
if (state.anchor !== null) {
state.anchorMap[state.anchor] = state.result;
}
}
} else if (indentStatus === 0) {
hasContent = allowBlockCollections && readBlockSequence(state, blockIndent);
}
}
if (state.tag === null) {
if (state.anchor !== null) {
state.anchorMap[state.anchor] = state.result;
}
} else if (state.tag === "?") {
if (state.result !== null && state.kind !== "scalar") {
throwError(state, 'unacceptable node kind for !<?> tag; it should be "scalar", not "' + state.kind + '"');
}
for (typeIndex = 0, typeQuantity = state.implicitTypes.length; typeIndex < typeQuantity; typeIndex += 1) {
type2 = state.implicitTypes[typeIndex];
if (type2.resolve(state.result)) {
state.result = type2.construct(state.result);
state.tag = type2.tag;
if (state.anchor !== null) {
state.anchorMap[state.anchor] = state.result;
}
break;
}
}
} else if (state.tag !== "!") {
if (_hasOwnProperty$1.call(state.typeMap[state.kind || "fallback"], state.tag)) {
type2 = state.typeMap[state.kind || "fallback"][state.tag];
} else {
type2 = null;
typeList = state.typeMap.multi[state.kind || "fallback"];
for (typeIndex = 0, typeQuantity = typeList.length; typeIndex < typeQuantity; typeIndex += 1) {
if (state.tag.slice(0, typeList[typeIndex].tag.length) === typeList[typeIndex].tag) {
type2 = typeList[typeIndex];
break;
}
}
}
if (!type2) {
throwError(state, "unknown tag !<" + state.tag + ">");
}
if (state.result !== null && type2.kind !== state.kind) {
throwError(state, "unacceptable node kind for !<" + state.tag + '> tag; it should be "' + type2.kind + '", not "' + state.kind + '"');
}
if (!type2.resolve(state.result, state.tag)) {
throwError(state, "cannot resolve a node with !<" + state.tag + "> explicit tag");
} else {
state.result = type2.construct(state.result, state.tag);
if (state.anchor !== null) {
state.anchorMap[state.anchor] = state.result;
}
}
}
if (state.listener !== null) {
state.listener("close", state);
}
return state.tag !== null || state.anchor !== null || hasContent;
}
function readDocument(state) {
var documentStart = state.position, _position, directiveName, directiveArgs, hasDirectives = false, ch;
state.version = null;
state.checkLineBreaks = state.legacy;
state.tagMap = /* @__PURE__ */ Object.create(null);
state.anchorMap = /* @__PURE__ */ Object.create(null);
while ((ch = state.input.charCodeAt(state.position)) !== 0) {
skipSeparationSpace(state, true, -1);
ch = state.input.charCodeAt(state.position);
if (state.lineIndent > 0 || ch !== 37) {
break;
}
hasDirectives = true;
ch = state.input.charCodeAt(++state.position);
_position = state.position;
while (ch !== 0 && !is_WS_OR_EOL(ch)) {
ch = state.input.charCodeAt(++state.position);
}
directiveName = state.input.slice(_position, state.position);
directiveArgs = [];
if (directiveName.length < 1) {
throwError(state, "directive name must not be less than one character in length");
}
while (ch !== 0) {
while (is_WHITE_SPACE(ch)) {
ch = state.input.charCodeAt(++state.position);
}
if (ch === 35) {
do {
ch = state.input.charCodeAt(++state.position);
} while (ch !== 0 && !is_EOL(ch));
break;
}
if (is_EOL(ch)) break;
_position = state.position;
while (ch !== 0 && !is_WS_OR_EOL(ch)) {
ch = state.input.charCodeAt(++state.position);
}
directiveArgs.push(state.input.slice(_position, state.position));
}
if (ch !== 0) readLineBreak(state);
if (_hasOwnProperty$1.call(directiveHandlers, directiveName)) {
directiveHandlers[directiveName](state, directiveName, directiveArgs);
} else {
throwWarning(state, 'unknown document directive "' + directiveName + '"');
}
}
skipSeparationSpace(state, true, -1);
if (state.lineIndent === 0 && state.input.charCodeAt(state.position) === 45 && state.input.charCodeAt(state.position + 1) === 45 && state.input.charCodeAt(state.position + 2) === 45) {
state.position += 3;
skipSeparationSpace(state, true, -1);
} else if (hasDirectives) {
throwError(state, "directives end mark is expected");
}
composeNode(state, state.lineIndent - 1, CONTEXT_BLOCK_OUT, false, true);
skipSeparationSpace(state, true, -1);
if (state.checkLineBreaks && PATTERN_NON_ASCII_LINE_BREAKS.test(state.input.slice(documentStart, state.position))) {
throwWarning(state, "non-ASCII line breaks are interpreted as content");
}
state.documents.push(state.result);
if (state.position === state.lineStart && testDocumentSeparator(state)) {
if (state.input.charCodeAt(state.position) === 46) {
state.position += 3;
skipSeparationSpace(state, true, -1);
}
return;
}
if (state.position < state.length - 1) {
throwError(state, "end of the stream or a document separator is expected");
} else {
return;
}
}
function loadDocuments(input, options) {
input = String(input);
options = options || {};
if (input.length !== 0) {
if (input.charCodeAt(input.length - 1) !== 10 && input.charCodeAt(input.length - 1) !== 13) {
input += "\n";
}
if (input.charCodeAt(0) === 65279) {
input = input.slice(1);
}
}
var state = new State$1(input, options);
var nullpos = input.indexOf("\0");
if (nullpos !== -1) {
state.position = nullpos;
throwError(state, "null byte is not allowed in input");
}
state.input += "\0";
while (state.input.charCodeAt(state.position) === 32) {
state.lineIndent += 1;
state.position += 1;
}
while (state.position < state.length - 1) {
readDocument(state);
}
return state.documents;
}
function load$1(input, options) {
var documents = loadDocuments(input, options);
if (documents.length === 0) {
return void 0;
} else if (documents.length === 1) {
return documents[0];
}
throw new exception("expected a single document in the stream, but found more");
}
var load_1 = load$1;
var loader = {
load: load_1
};
var _toString = Object.prototype.toString;
var _hasOwnProperty = Object.prototype.hasOwnProperty;
var CHAR_BOM = 65279;
var CHAR_TAB = 9;
var CHAR_LINE_FEED = 10;
var CHAR_CARRIAGE_RETURN = 13;
var CHAR_SPACE = 32;
var CHAR_EXCLAMATION = 33;
var CHAR_DOUBLE_QUOTE = 34;
var CHAR_SHARP = 35;
var CHAR_PERCENT = 37;
var CHAR_AMPERSAND = 38;
var CHAR_SINGLE_QUOTE = 39;
var CHAR_ASTERISK = 42;
var CHAR_COMMA = 44;
var CHAR_MINUS = 45;
var CHAR_COLON = 58;
var CHAR_EQUALS = 61;
var CHAR_GREATER_THAN = 62;
var CHAR_QUESTION = 63;
var CHAR_COMMERCIAL_AT = 64;
var CHAR_LEFT_SQUARE_BRACKET = 91;
var CHAR_RIGHT_SQUARE_BRACKET = 93;
var CHAR_GRAVE_ACCENT = 96;
var CHAR_LEFT_CURLY_BRACKET = 123;
var CHAR_VERTICAL_LINE = 124;
var CHAR_RIGHT_CURLY_BRACKET = 125;
var ESCAPE_SEQUENCES = {};
ESCAPE_SEQUENCES[0] = "\\0";
ESCAPE_SEQUENCES[7] = "\\a";
ESCAPE_SEQUENCES[8] = "\\b";
ESCAPE_SEQUENCES[9] = "\\t";
ESCAPE_SEQUENCES[10] = "\\n";
ESCAPE_SEQUENCES[11] = "\\v";
ESCAPE_SEQUENCES[12] = "\\f";
ESCAPE_SEQUENCES[13] = "\\r";
ESCAPE_SEQUENCES[27] = "\\e";
ESCAPE_SEQUENCES[34] = '\\"';
ESCAPE_SEQUENCES[92] = "\\\\";
ESCAPE_SEQUENCES[133] = "\\N";
ESCAPE_SEQUENCES[160] = "\\_";
ESCAPE_SEQUENCES[8232] = "\\L";
ESCAPE_SEQUENCES[8233] = "\\P";
var DEPRECATED_BOOLEANS_SYNTAX = [
"y",
"Y",
"yes",
"Yes",
"YES",
"on",
"On",
"ON",
"n",
"N",
"no",
"No",
"NO",
"off",
"Off",
"OFF"
];
var DEPRECATED_BASE60_SYNTAX = /^[-+]?[0-9_]+(?::[0-9_]+)+(?:\.[0-9_]*)?$/;
function compileStyleMap(schema2, map2) {
var result, keys, index, length, tag, style, type2;
if (map2 === null) return {};
result = {};
keys = Object.keys(map2);
for (index = 0, length = keys.length; index < length; index += 1) {
tag = keys[index];
style = String(map2[tag]);
if (tag.slice(0, 2) === "!!") {
tag = "tag:yaml.org,2002:" + tag.slice(2);
}
type2 = schema2.compiledTypeMap["fallback"][tag];
if (type2 && _hasOwnProperty.call(type2.styleAliases, style)) {
style = type2.styleAliases[style];
}
result[tag] = style;
}
return result;
}
function encodeHex(character) {
var string, handle, length;
string = character.toString(16).toUpperCase();
if (character <= 255) {
handle = "x";
length = 2;
} else if (character <= 65535) {
handle = "u";
length = 4;
} else if (character <= 4294967295) {
handle = "U";
length = 8;
} else {
throw new exception("code point within a string may not be greater than 0xFFFFFFFF");
}
return "\\" + handle + common.repeat("0", length - string.length) + string;
}
var QUOTING_TYPE_SINGLE = 1, QUOTING_TYPE_DOUBLE = 2;
function State(options) {
this.schema = options["schema"] || _default;
this.indent = Math.max(1, options["indent"] || 2);
this.noArrayIndent = options["noArrayIndent"] || false;
this.skipInvalid = options["skipInvalid"] || false;
this.flowLevel = common.isNothing(options["flowLevel"]) ? -1 : options["flowLevel"];
this.styleMap = compileStyleMap(this.schema, options["styles"] || null);
this.sortKeys = options["sortKeys"] || false;
this.lineWidth = options["lineWidth"] || 80;
this.noRefs = options["noRefs"] || false;
this.noCompatMode = options["noCompatMode"] || false;
this.condenseFlow = options["condenseFlow"] || false;
this.quotingType = options["quotingType"] === '"' ? QUOTING_TYPE_DOUBLE : QUOTING_TYPE_SINGLE;
this.forceQuotes = options["forceQuotes"] || false;
this.replacer = typeof options["replacer"] === "function" ? options["replacer"] : null;
this.implicitTypes = this.schema.compiledImplicit;
this.explicitTypes = this.schema.compiledExplicit;
this.tag = null;
this.result = "";
this.duplicates = [];
this.usedDuplicates = null;
}
function indentString(string, spaces) {
var ind = common.repeat(" ", spaces), position = 0, next = -1, result = "", line, length = string.length;
while (position < length) {
next = string.indexOf("\n", position);
if (next === -1) {
line = string.slice(position);
position = length;
} else {
line = string.slice(position, next + 1);
position = next + 1;
}
if (line.length && line !== "\n") result += ind;
result += line;
}
return result;
}
function generateNextLine(state, level) {
return "\n" + common.repeat(" ", state.indent * level);
}
function testImplicitResolving(state, str2) {
var index, length, type2;
for (index = 0, length = state.implicitTypes.length; index < length; index += 1) {
type2 = state.implicitTypes[index];
if (type2.resolve(str2)) {
return true;
}
}
return false;
}
function isWhitespace(c) {
return c === CHAR_SPACE || c === CHAR_TAB;
}
function isPrintable(c) {
return 32 <= c && c <= 126 || 161 <= c && c <= 55295 && c !== 8232 && c !== 8233 || 57344 <= c && c <= 65533 && c !== CHAR_BOM || 65536 <= c && c <= 1114111;
}
function isNsCharOrWhitespace(c) {
return isPrintable(c) && c !== CHAR_BOM && c !== CHAR_CARRIAGE_RETURN && c !== CHAR_LINE_FEED;
}
function isPlainSafe(c, prev, inblock) {
var cIsNsCharOrWhitespace = isNsCharOrWhitespace(c);
var cIsNsChar = cIsNsCharOrWhitespace && !isWhitespace(c);
return (
// ns-plain-safe
(inblock ? (
// c = flow-in
cIsNsCharOrWhitespace
) : cIsNsCharOrWhitespace && c !== CHAR_COMMA && c !== CHAR_LEFT_SQUARE_BRACKET && c !== CHAR_RIGHT_SQUARE_BRACKET && c !== CHAR_LEFT_CURLY_BRACKET && c !== CHAR_RIGHT_CURLY_BRACKET) && c !== CHAR_SHARP && !(prev === CHAR_COLON && !cIsNsChar) || isNsCharOrWhitespace(prev) && !isWhitespace(prev) && c === CHAR_SHARP || prev === CHAR_COLON && cIsNsChar
);
}
function isPlainSafeFirst(c) {
return isPrintable(c) && c !== CHAR_BOM && !isWhitespace(c) && c !== CHAR_MINUS && c !== CHAR_QUESTION && c !== CHAR_COLON && c !== CHAR_COMMA && c !== CHAR_LEFT_SQUARE_BRACKET && c !== CHAR_RIGHT_SQUARE_BRACKET && c !== CHAR_LEFT_CURLY_BRACKET && c !== CHAR_RIGHT_CURLY_BRACKET && c !== CHAR_SHARP && c !== CHAR_AMPERSAND && c !== CHAR_ASTERISK && c !== CHAR_EXCLAMATION && c !== CHAR_VERTICAL_LINE && c !== CHAR_EQUALS && c !== CHAR_GREATER_THAN && c !== CHAR_SINGLE_QUOTE && c !== CHAR_DOUBLE_QUOTE && c !== CHAR_PERCENT && c !== CHAR_COMMERCIAL_AT && c !== CHAR_GRAVE_ACCENT;
}
function isPlainSafeLast(c) {
return !isWhitespace(c) && c !== CHAR_COLON;
}
function codePointAt(string, pos) {
var first = string.charCodeAt(pos), second;
if (first >= 55296 && first <= 56319 && pos + 1 < string.length) {
second = string.charCodeAt(pos + 1);
if (second >= 56320 && second <= 57343) {
return (first - 55296) * 1024 + second - 56320 + 65536;
}
}
return first;
}
function needIndentIndicator(string) {
var leadingSpaceRe = /^\n* /;
return leadingSpaceRe.test(string);
}
var STYLE_PLAIN = 1, STYLE_SINGLE = 2, STYLE_LITERAL = 3, STYLE_FOLDED = 4, STYLE_DOUBLE = 5;
function chooseScalarStyle(string, singleLineOnly, indentPerLevel, lineWidth, testAmbiguousType, quotingType, forceQuotes, inblock) {
var i;
var char = 0;
var prevChar = null;
var hasLineBreak = false;
var hasFoldableLine = false;
var shouldTrackWidth = lineWidth !== -1;
var previousLineBreak = -1;
var plain = isPlainSafeFirst(codePointAt(string, 0)) && isPlainSafeLast(codePointAt(string, string.length - 1));
if (singleLineOnly || forceQuotes) {
for (i = 0; i < string.length; char >= 65536 ? i += 2 : i++) {
char = codePointAt(string, i);
if (!isPrintable(char)) {
return STYLE_DOUBLE;
}
plain = plain && isPlainSafe(char, prevChar, inblock);
prevChar = char;
}
} else {
for (i = 0; i < string.length; char >= 65536 ? i += 2 : i++) {
char = codePointAt(string, i);
if (char === CHAR_LINE_FEED) {
hasLineBreak = true;
if (shouldTrackWidth) {
hasFoldableLine = hasFoldableLine || // Foldable line = too long, and not more-indented.
i - previousLineBreak - 1 > lineWidth && string[previousLineBreak + 1] !== " ";
previousLineBreak = i;
}
} else if (!isPrintable(char)) {
return STYLE_DOUBLE;
}
plain = plain && isPlainSafe(char, prevChar, inblock);
prevChar = char;
}
hasFoldableLine = hasFoldableLine || shouldTrackWidth && (i - previousLineBreak - 1 > lineWidth && string[previousLineBreak + 1] !== " ");
}
if (!hasLineBreak && !hasFoldableLine) {
if (plain && !forceQuotes && !testAmbiguousType(string)) {
return STYLE_PLAIN;
}
return quotingType === QUOTING_TYPE_DOUBLE ? STYLE_DOUBLE : STYLE_SINGLE;
}
if (indentPerLevel > 9 && needIndentIndicator(string)) {
return STYLE_DOUBLE;
}
if (!forceQuotes) {
return hasFoldableLine ? STYLE_FOLDED : STYLE_LITERAL;
}
return quotingType === QUOTING_TYPE_DOUBLE ? STYLE_DOUBLE : STYLE_SINGLE;
}
function writeScalar(state, string, level, iskey, inblock) {
state.dump = function() {
if (string.length === 0) {
return state.quotingType === QUOTING_TYPE_DOUBLE ? '""' : "''";
}
if (!state.noCompatMode) {
if (DEPRECATED_BOOLEANS_SYNTAX.indexOf(string) !== -1 || DEPRECATED_BASE60_SYNTAX.test(string)) {
return state.quotingType === QUOTING_TYPE_DOUBLE ? '"' + string + '"' : "'" + string + "'";
}
}
var indent = state.indent * Math.max(1, level);
var lineWidth = state.lineWidth === -1 ? -1 : Math.max(Math.min(state.lineWidth, 40), state.lineWidth - indent);
var singleLineOnly = iskey || state.flowLevel > -1 && level >= state.flowLevel;
function testAmbiguity(string2) {
return testImplicitResolving(state, string2);
}
switch (chooseScalarStyle(
string,
singleLineOnly,
state.indent,
lineWidth,
testAmbiguity,
state.quotingType,
state.forceQuotes && !iskey,
inblock
)) {
case STYLE_PLAIN:
return string;
case STYLE_SINGLE:
return "'" + string.replace(/'/g, "''") + "'";
case STYLE_LITERAL:
return "|" + blockHeader(string, state.indent) + dropEndingNewline(indentString(string, indent));
case STYLE_FOLDED:
return ">" + blockHeader(string, state.indent) + dropEndingNewline(indentString(foldString(string, lineWidth), indent));
case STYLE_DOUBLE:
return '"' + escapeString(string) + '"';
default:
throw new exception("impossible error: invalid scalar style");
}
}();
}
function blockHeader(string, indentPerLevel) {
var indentIndicator = needIndentIndicator(string) ? String(indentPerLevel) : "";
var clip = string[string.length - 1] === "\n";
var keep = clip && (string[string.length - 2] === "\n" || string === "\n");
var chomp = keep ? "+" : clip ? "" : "-";
return indentIndicator + chomp + "\n";
}
function dropEndingNewline(string) {
return string[string.length - 1] === "\n" ? string.slice(0, -1) : string;
}
function foldString(string, width) {
var lineRe = /(\n+)([^\n]*)/g;
var result = function() {
var nextLF = string.indexOf("\n");
nextLF = nextLF !== -1 ? nextLF : string.length;
lineRe.lastIndex = nextLF;
return foldLine(string.slice(0, nextLF), width);
}();
var prevMoreIndented = string[0] === "\n" || string[0] === " ";
var moreIndented;
var match;
while (match = lineRe.exec(string)) {
var prefix = match[1], line = match[2];
moreIndented = line[0] === " ";
result += prefix + (!prevMoreIndented && !moreIndented && line !== "" ? "\n" : "") + foldLine(line, width);
prevMoreIndented = moreIndented;
}
return result;
}
function foldLine(line, width) {
if (line === "" || line[0] === " ") return line;
var breakRe = / [^ ]/g;
var match;
var start = 0, end, curr = 0, next = 0;
var result = "";
while (match = breakRe.exec(line)) {
next = match.index;
if (next - start > width) {
end = curr > start ? curr : next;
result += "\n" + line.slice(start, end);
start = end + 1;
}
curr = next;
}
result += "\n";
if (line.length - start > width && curr > start) {
result += line.slice(start, curr) + "\n" + line.slice(curr + 1);
} else {
result += line.slice(start);
}
return result.slice(1);
}
function escapeString(string) {
var result = "";
var char = 0;
var escapeSeq;
for (var i = 0; i < string.length; char >= 65536 ? i += 2 : i++) {
char = codePointAt(string, i);
escapeSeq = ESCAPE_SEQUENCES[char];
if (!escapeSeq && isPrintable(char)) {
result += string[i];
if (char >= 65536) result += string[i + 1];
} else {
result += escapeSeq || encodeHex(char);
}
}
return result;
}
function writeFlowSequence(state, level, object) {
var _result = "", _tag = state.tag, index, length, value;
for (index = 0, length = object.length; index < length; index += 1) {
value = object[index];
if (state.replacer) {
value = state.replacer.call(object, String(index), value);
}
if (writeNode(state, level, value, false, false) || typeof value === "undefined" && writeNode(state, level, null, false, false)) {
if (_result !== "") _result += "," + (!state.condenseFlow ? " " : "");
_result += state.dump;
}
}
state.tag = _tag;
state.dump = "[" + _result + "]";
}
function writeBlockSequence(state, level, object, compact) {
var _result = "", _tag = state.tag, index, length, value;
for (index = 0, length = object.length; index < length; index += 1) {
value = object[index];
if (state.replacer) {
value = state.replacer.call(object, String(index), value);
}
if (writeNode(state, level + 1, value, true, true, false, true) || typeof value === "undefined" && writeNode(state, level + 1, null, true, true, false, true)) {
if (!compact || _result !== "") {
_result += generateNextLine(state, level);
}
if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) {
_result += "-";
} else {
_result += "- ";
}
_result += state.dump;
}
}
state.tag = _tag;
state.dump = _result || "[]";
}
function writeFlowMapping(state, level, object) {
var _result = "", _tag = state.tag, objectKeyList = Object.keys(object), index, length, objectKey, objectValue, pairBuffer;
for (index = 0, length = objectKeyList.length; index < length; index += 1) {
pairBuffer = "";
if (_result !== "") pairBuffer += ", ";
if (state.condenseFlow) pairBuffer += '"';
objectKey = objectKeyList[index];
objectValue = object[objectKey];
if (state.replacer) {
objectValue = state.replacer.call(object, objectKey, objectValue);
}
if (!writeNode(state, level, objectKey, false, false)) {
continue;
}
if (state.dump.length > 1024) pairBuffer += "? ";
pairBuffer += state.dump + (state.condenseFlow ? '"' : "") + ":" + (state.condenseFlow ? "" : " ");
if (!writeNode(state, level, objectValue, false, false)) {
continue;
}
pairBuffer += state.dump;
_result += pairBuffer;
}
state.tag = _tag;
state.dump = "{" + _result + "}";
}
function writeBlockMapping(state, level, object, compact) {
var _result = "", _tag = state.tag, objectKeyList = Object.keys(object), index, length, objectKey, objectValue, explicitPair, pairBuffer;
if (state.sortKeys === true) {
objectKeyList.sort();
} else if (typeof state.sortKeys === "function") {
objectKeyList.sort(state.sortKeys);
} else if (state.sortKeys) {
throw new exception("sortKeys must be a boolean or a function");
}
for (index = 0, length = objectKeyList.length; index < length; index += 1) {
pairBuffer = "";
if (!compact || _result !== "") {
pairBuffer += generateNextLine(state, level);
}
objectKey = objectKeyList[index];
objectValue = object[objectKey];
if (state.replacer) {
objectValue = state.replacer.call(object, objectKey, objectValue);
}
if (!writeNode(state, level + 1, objectKey, true, true, true)) {
continue;
}
explicitPair = state.tag !== null && state.tag !== "?" || state.dump && state.dump.length > 1024;
if (explicitPair) {
if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) {
pairBuffer += "?";
} else {
pairBuffer += "? ";
}
}
pairBuffer += state.dump;
if (explicitPair) {
pairBuffer += generateNextLine(state, level);
}
if (!writeNode(state, level + 1, objectValue, true, explicitPair)) {
continue;
}
if (state.dump && CHAR_LINE_FEED === state.dump.charCodeAt(0)) {
pairBuffer += ":";
} else {
pairBuffer += ": ";
}
pairBuffer += state.dump;
_result += pairBuffer;
}
state.tag = _tag;
state.dump = _result || "{}";
}
function detectType(state, object, explicit) {
var _result, typeList, index, length, type2, style;
typeList = explicit ? state.explicitTypes : state.implicitTypes;
for (index = 0, length = typeList.length; index < length; index += 1) {
type2 = typeList[index];
if ((type2.instanceOf || type2.predicate) && (!type2.instanceOf || typeof object === "object" && object instanceof type2.instanceOf) && (!type2.predicate || type2.predicate(object))) {
if (explicit) {
if (type2.multi && type2.representName) {
state.tag = type2.representName(object);
} else {
state.tag = type2.tag;
}
} else {
state.tag = "?";
}
if (type2.represent) {
style = state.styleMap[type2.tag] || type2.defaultStyle;
if (_toString.call(type2.represent) === "[object Function]") {
_result = type2.represent(object, style);
} else if (_hasOwnProperty.call(type2.represent, style)) {
_result = type2.represent[style](object, style);
} else {
throw new exception("!<" + type2.tag + '> tag resolver accepts not "' + style + '" style');
}
state.dump = _result;
}
return true;
}
}
return false;
}
function writeNode(state, level, object, block, compact, iskey, isblockseq) {
state.tag = null;
state.dump = object;
if (!detectType(state, object, false)) {
detectType(state, object, true);
}
var type2 = _toString.call(state.dump);
var inblock = block;
var tagStr;
if (block) {
block = state.flowLevel < 0 || state.flowLevel > level;
}
var objectOrArray = type2 === "[object Object]" || type2 === "[object Array]", duplicateIndex, duplicate;
if (objectOrArray) {
duplicateIndex = state.duplicates.indexOf(object);
duplicate = duplicateIndex !== -1;
}
if (state.tag !== null && state.tag !== "?" || duplicate || state.indent !== 2 && level > 0) {
compact = false;
}
if (duplicate && state.usedDuplicates[duplicateIndex]) {
state.dump = "*ref_" + duplicateIndex;
} else {
if (objectOrArray && duplicate && !state.usedDuplicates[duplicateIndex]) {
state.usedDuplicates[duplicateIndex] = true;
}
if (type2 === "[object Object]") {
if (block && Object.keys(state.dump).length !== 0) {
writeBlockMapping(state, level, state.dump, compact);
if (duplicate) {
state.dump = "&ref_" + duplicateIndex + state.dump;
}
} else {
writeFlowMapping(state, level, state.dump);
if (duplicate) {
state.dump = "&ref_" + duplicateIndex + " " + state.dump;
}
}
} else if (type2 === "[object Array]") {
if (block && state.dump.length !== 0) {
if (state.noArrayIndent && !isblockseq && level > 0) {
writeBlockSequence(state, level - 1, state.dump, compact);
} else {
writeBlockSequence(state, level, state.dump, compact);
}
if (duplicate) {
state.dump = "&ref_" + duplicateIndex + state.dump;
}
} else {
writeFlowSequence(state, level, state.dump);
if (duplicate) {
state.dump = "&ref_" + duplicateIndex + " " + state.dump;
}
}
} else if (type2 === "[object String]") {
if (state.tag !== "?") {
writeScalar(state, state.dump, level, iskey, inblock);
}
} else if (type2 === "[object Undefined]") {
return false;
} else {
if (state.skipInvalid) return false;
throw new exception("unacceptable kind of an object to dump " + type2);
}
if (state.tag !== null && state.tag !== "?") {
tagStr = encodeURI(
state.tag[0] === "!" ? state.tag.slice(1) : state.tag
).replace(/!/g, "%21");
if (state.tag[0] === "!") {
tagStr = "!" + tagStr;
} else if (tagStr.slice(0, 18) === "tag:yaml.org,2002:") {
tagStr = "!!" + tagStr.slice(18);
} else {
tagStr = "!<" + tagStr + ">";
}
state.dump = tagStr + " " + state.dump;
}
}
return true;
}
function getDuplicateReferences(object, state) {
var objects = [], duplicatesIndexes = [], index, length;
inspectNode(object, objects, duplicatesIndexes);
for (index = 0, length = duplicatesIndexes.length; index < length; index += 1) {
state.duplicates.push(objects[duplicatesIndexes[index]]);
}
state.usedDuplicates = new Array(length);
}
function inspectNode(object, objects, duplicatesIndexes) {
var objectKeyList, index, length;
if (object !== null && typeof object === "object") {
index = objects.indexOf(object);
if (index !== -1) {
if (duplicatesIndexes.indexOf(index) === -1) {
duplicatesIndexes.push(index);
}
} else {
objects.push(object);
if (Array.isArray(object)) {
for (index = 0, length = object.length; index < length; index += 1) {
inspectNode(object[index], objects, duplicatesIndexes);
}
} else {
objectKeyList = Object.keys(object);
for (index = 0, length = objectKeyList.length; index < length; index += 1) {
inspectNode(object[objectKeyList[index]], objects, duplicatesIndexes);
}
}
}
}
}
function dump$1(input, options) {
options = options || {};
var state = new State(options);
if (!state.noRefs) getDuplicateReferences(input, state);
var value = input;
if (state.replacer) {
value = state.replacer.call({ "": value }, "", value);
}
if (writeNode(state, 0, value, true, true)) return state.dump + "\n";
return "";
}
var dump_1 = dump$1;
var dumper = {
dump: dump_1
};
var load = loader.load;
var dump = dumper.dump;
function parseYamlFrontMatter(content, throwOnError = false) {
const defaultResult = {
content,
metadata: {}
};
if (!content.startsWith("---")) {
return defaultResult;
}
const endDelimiterIndex = content.indexOf("---", 3);
if (endDelimiterIndex === -1) {
return defaultResult;
}
let yamlContent = content.substring(3, endDelimiterIndex).trim();
const remainingContent = content.substring(endDelimiterIndex + 3).trim();
yamlContent = processDateReferencesInYaml(yamlContent);
try {
const metadata = load(yamlContent);
if (!metadata || typeof metadata !== "object") {
return {
content: remainingContent,
metadata: {}
};
}
return {
content: remainingContent,
metadata
};
} catch (error) {
if (throwOnError) {
throw new Error(
`Invalid YAML Front Matter: ${error instanceof Error ? error.message : "Unknown error"}`
);
} else {
return {
content: remainingContent,
metadata: {}
};
}
}
}
function processDateReferencesInYaml(yamlContent) {
const todayPattern = /@today(?:\[([^\]]+)\])?/g;
return yamlContent.replace(todayPattern, (match, formatOverride) => {
try {
const date = /* @__PURE__ */ new Date();
const format = formatOverride || "YYYY-MM-DD";
const formattedDate = formatDateForYaml(date, format);
return `"${formattedDate}"`;
} catch (error) {
console.warn(`Error processing YAML date reference ${match}:`, error);
return `"${match}"`;
}
});
}
function formatDateForYaml(date, format) {
try {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
const dayNumber = date.getDate();
const monthNames = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
];
switch (format.toLowerCase()) {
case "iso":
case "yyyy-mm-dd":
return `${year}-${month}-${day}`;
case "us":
case "mm/dd/yyyy":
return `${month}/${day}/${year}`;
case "eu":
case "dd/mm/yyyy":
return `${day}/${month}/${year}`;
case "legal":
return `${monthNames[date.getMonth()]} ${dayNumber}, ${year}`;
case "long":
return date.toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric"
});
case "medium":
return date.toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "numeric"
});
case "short":
return date.toLocaleDateString("en-US", {
year: "2-digit",
month: "short",
day: "numeric"
});
default:
return `${year}-${month}-${day}`;
}
} catch (error) {
return date.toISOString().split("T")[0];
}
}
function flattenObject(obj, prefix = "", visited = /* @__PURE__ */ new WeakSet(), startTime = Date.now(), timeoutMs = 5e3) {
if (Date.now() - startTime > timeoutMs) {
const message = `Object flattening timed out after ${timeoutMs}ms. This may indicate a complex nested structure or circular references.`;
throw new Error(message);
}
if (obj === null || obj === void 0) {
return {};
}
const flattened = {};
if (typeof obj !== "object" || Array.isArray(obj)) {
if (prefix) {
flattened[prefix] = obj;
}
return flattened;
}
if (visited.has(obj)) {
console.warn(`Circular reference detected at path '${prefix}'. Replacing with placeholder.`);
if (prefix) {
flattened[prefix] = "[Circular Reference]";
}
return flattened;
}
visited.add(obj);
for (const [key, value] of Object.entries(obj)) {
const newKey = prefix ? `${prefix}.${key}` : key;
if (value === null || value === void 0) {
flattened[newKey] = value;
} else if (Array.isArray(value)) {
flattened[newKey] = value;
} else if (typeof value === "object") {
Object.assign(flattened, flattenObject(value, newKey, visited, startTime, timeoutMs));
} else {
flattened[newKey] = value;
}
}
visited.delete(obj);
return flattened;
}
function unflattenObject(flattened) {
if (!flattened || typeof flattened !== "object") {
return flattened;
}
const result = {};
for (const [key, value] of Object.entries(flattened)) {
const parts = key.split(".");
let current = result;
for (let i = 0; i < parts.length - 1; i++) {
const part = parts[i];
if (!(part in current)) {
current[part] = {};
} else if (typeof current[part] !== "object" || Array.isArray(current[part])) {
current[part] = {};
}
current = current[part];
}
const finalKey = parts[parts.length - 1];
current[finalKey] = value;
}
return result;
}
const RESERVED_FIELDS = [
// Document structure configuration
"level-one",
"level-two",
"level-three",
"level-four",
"level-five",
"level-six",
"level-indent",
"no-reset",
"no-indent",
// Metadata export configuration
"meta-yaml-output",
"meta-json-output",
"meta-output-path",
"meta-include-original",
// Date and localization configuration
"date-format",
"dateFormat",
"timezone",
"tz",
"locale",
"lang",
// Force commands - CRITICAL for security
"force_commands",
"force-commands",
"forceCommands",
"commands",
// Import configuration (current and future)
"import-tracing",
"import-tracing-format",
"disable-frontmatter-merge",
// Pipeline configuration
"pipeline-config",
"pipeline-steps",
"processing-options",
// Field tracking configuration
"enable-field-tracking",
"field-tracking-mode",
// Cross-references metadata (internal)
"_cross_references"
];
function filterReservedFields(metadata, options = {}) {
if (!metadata || typeof metadata !== "object") {
return metadata;
}
const { logFiltered = false, additionalReserved = [], strictMode = false } = options;
const filtered = {};
[...RESERVED_FIELDS, ...additionalReserved];
for (const [key, value] of Object.entries(metadata)) {
if (isReservedField(key, { additionalReserved, strictMode })) {
if (logFiltered) {
console.warn(`Reserved field '${key}' ignored from import`);
}
} else {
filtered[key] = value;
}
}
return filtered;
}
function isReservedField(fieldName, options = {}) {
if (!fieldName || typeof fieldName !== "string") {
return false;
}
const { additionalReserved = [], strictMode = false } = options;
const allReservedFields = [...RESERVED_FIELDS, ...additionalReserved];
if (strictMode) {
return allReservedFields.includes(fieldName);
} else {
const lowerFieldName = fieldName.toLowerCase();
return allReservedFields.some((reserved) => reserved.toLowerCase() === lowerFieldName);
}
}
class MergeValidationError extends Error {
constructor(message, field, currentType, importedType) {
super(message);
this.field = field;
this.currentType = currentType;
this.importedType = importedType;
this.name = "MergeValidationError";
}
}
function mergeFlattened(current, imported, options = {}) {
const {
filterReserved = true,
validateTypes = true,
logOperations = false,
conflictStrategy = "source-wins",
includeStats = false,
timeoutMs = 1e4
} = options;
const startTime = Date.now();
const stats = {
currentProperties: 0,
importedProperties: 0,
propertiesAdded: 0,
conflictsResolved: 0,
reservedFieldsFiltered: 0,
addedFields: [],
conflictedFields: [],
filteredFields: []
};
if (!current || typeof current !== "object") {
current = {};
}
if (!imported || typeof imported !== "object") {
imported = {};
}
let filteredImported = imported;
if (filterReserved) {
const originalKeys = Object.keys(imported);
filteredImported = filterReservedFields(imported, { logFiltered: logOperations });
const filteredKeys = Object.keys(filteredImported);
stats.reservedFieldsFiltered = originalKeys.length - filteredKeys.length;
stats.filteredFields = originalKeys.filter((key) => !filteredKeys.includes(key));
}
const currentFlat = flattenObject(current, "", /* @__PURE__ */ new WeakSet(), startTime, timeoutMs);
const importedFlat = flattenObject(filteredImported, "", /* @__PURE__ */ new WeakSet(), Date.now(), timeoutMs);
stats.currentProperties = Object.keys(currentFlat).length;
stats.importedProperties = Object.keys(importedFlat).length;
if (logOperations) {
console.log(
`Merging frontmatter: ${stats.currentProperties} current + ${stats.importedProperties} imported properties`
);
}
const mergedFlat = { ...currentFlat };
for (const [key, importedValue] of Object.entries(importedFlat)) {
if (Date.now() - startTime > timeoutMs) {
throw new Error(
`Frontmatter merge timed out after ${timeoutMs}ms. This may indicate complex nested structures or circular references.`
);
}
if (Object.prototype.hasOwnProperty.call(currentFlat, key)) {
const currentValue = currentFlat[key];
if (validateTypes) {
try {
validateMergeCompatibility(currentValue, importedValue, key);
} catch (error) {
if (logOperations) {
console.warn(
`Type conflict for '${key}': ${error instanceof Error ? error.message : String(error)}`
);
}
stats.conflictsResolved++;
stats.conflictedFields.push(key);
continue;
}
}
stats.conflictsResolved++;
stats.conflictedFields.push(key);
if (logOperations) {
console.log(`Conflict for '${key}': current value kept`);
}
} else {
const nestedConflictResult = validateTypes ? checkNestedConflicts(key, importedValue, currentFlat, logOperations) : { hasConflict: false, conflictedField: "" };
if (nestedConflictResult.hasConflict) {
stats.conflictsResolved++;
stats.conflictedFields.push(nestedConflictResult.conflictedField);
}
if (!nestedConflictResult.hasConflict) {
mergedFlat[key] = importedValue;
stats.propertiesAdded++;
stats.addedFields.push(key);
if (logOperations) {
console.log(`Added '${key}' from import`);
}
}
}
}
const mergedMetadata = unflattenObject(mergedFlat);
if (includeStats) {
return {
metadata: mergedMetadata,
stats
};
}
return mergedMetadata;
}
function validateMergeCompatibility(current, imported, key) {
const currentType = getValueType(current);
const importedType = getValueType(imported);
if (current === null || current === void 0 || imported === null || imported === void 0) {
return;
}
if (currentType !== importedType) {
const compatibleCombinations = [
["string", "number"],
// Can convert numbers to strings
["number", "string"],
// Can parse strings as numbers
["boolean", "string"],
// Can convert boolean to string
["string", "boolean"]
// Can parse strings as boolean
];
const isCompatible = compatibleCombinations.some(
([type1, type2]) => currentType === type1 && importedType === type2 || currentType === type2 && importedType === type1
);
if (!isCompatible) {
throw new MergeValidationError(
`Type conflict for '${key}': current=${currentType}, imported=${importedType}`,
key,
currentType,
importedType
);
}
}
}
function getValueType(value) {
if (value === null) return "null";
if (value === void 0) return "undefined";
if (Array.isArray(value)) return "array";
return typeof value;
}
function mergeSequentially(initial, imports, options = {}) {
const startTime = Date.now();
const timeoutMs = options.timeoutMs || 15e3;
let currentMetadata = initial;
const cumulativeStats = {
currentProperties: 0,
importedProperties: 0,
propertiesAdded: 0,
conflictsResolved: 0,
reservedFieldsFiltered: 0,
addedFields: [],
conflictedFields: [],
filteredFields: []
};
for (let i = 0; i < imports.length; i++) {
if (Date.now() - startTime > timeoutMs) {
const message = `Sequential merge timed out after ${timeoutMs}ms while processing import ${i + 1}/${imports.length}.`;
throw new Error(message);
}
const importedData = imports[i];
const remainingTime = Math.max(1e3, timeoutMs - (Date.now() - startTime));
const result = mergeFlattened(currentMetadata, importedData, {
...options,
includeStats: true,
timeoutMs: remainingTime
});
currentMetadata = result.metadata;
if (result.stats) {
cumulativeStats.importedProperties += result.stats.importedProperties;
cumulativeStats.propertiesAdded += result.stats.propertiesAdded;
cumulativeStats.conflictsResolved += result.stats.conflictsResolved;
cumulativeStats.reservedFieldsFiltered += result.stats.reservedFieldsFiltered;
cumulativeStats.addedFields.push(...result.stats.addedFields);
cumulativeStats.conflictedFields.push(...result.stats.conflictedFields);
cumulativeStats.filteredFields.push(...result.stats.filteredFields);
}
if (options.logOperations) {
console.log(
`Merged import ${i + 1}/${imports.length}: +${result.stats?.propertiesAdded} properties`
);
}
}
cumulativeStats.currentProperties = Object.keys(flattenObject(currentMetadata)).length;
return {
metadata: currentMetadata,
stats: cumulativeStats
};
}
function checkNestedConflicts(key, importedValue, currentFlat, logOperations) {
for (const currentKey of Object.keys(currentFlat)) {
if (currentKey.startsWith(key + ".")) {
try {
validateMergeCompatibility({}, importedValue, key);
} catch (error) {
if (logOperations) {
console.warn(
`Type conflict for '${key}': ${error instanceof Error ? error.message : String(error)}`
);
}
return { hasConflict: true, conflictedField: key };
}
}
}
const baseKey = key.split(".")[0];
const hasBaseKey = Object.prototype.hasOwnProperty.call(currentFlat, baseKey);
if (hasBaseKey && baseKey !== key) {
const currentValue = currentFlat[baseKey];
try {
validateMergeCompatibility(currentValue, {}, baseKey);
} catch (error) {
if (logOperations) {
const message = error instanceof Error ? error.message : String(error);
console.warn(`Type conflict for '${baseKey}': ${message}`);
}
return { hasConflict: true, conflictedField: baseKey };
}
}
return { hasConflict: false, conflictedField: "" };
}
const remarkImports = (options) => {
const {
basePath = ".",
mergeMetadata = true,
debug = false,
maxDepth = 10,
timeoutMs = 3e4,
filterReserved = true,
validateTypes = true,
logImportOperations = false,
onMetadataMerged,
importStack = []
} = options;
return (tree) => {
const startTime = Date.now();
if (debug) {
console.log("[remarkImports] Processing imports with options:", {
basePath,
mergeMetadata,
maxDepth,
timeoutMs,
filterReserved,
validateTypes,
currentDepth: importStack.length
});
}
const context = {
depth: importStack.length,
maxDepth,
basePath,
mergeMetadata,
debug,
startTime,
timeoutMs,
filterReserved,
validateTypes,
logImportOperations,
onMetadataMerged,
importStack: [...importStack],
contentCache: /* @__PURE__ */ new Map(),
importedMetadataList: [],
importedFiles: []
};
visit(tree, (node) => {
if (node.type === "text") {
processTextNode(node, context);
} else if (node.type === "paragraph") {
processParagraphNode(node, context);
}
});
if (context.mergeMetadata && context.importedMetadataList.length > 0) {
const mergedResult = performSequentialMerge(context);
tree._importedMetadata = mergedResult.metadata;
tree._importStats = mergedResult.stats;
}
};
};
function processTextNode(node, context) {
const originalText = node.value;
const importDirectives = extractImportDirectives(originalText);
if (importDirectives.length === 0) {
return;
}
if (context.debug) {
console.log(`[remarkImports] Found ${importDirectives.length} import directives in text node`);
}
let processedText = originalText;
for (let i = importDirectives.length - 1; i >= 0; i--) {
const directive = importDirectives[i];
const result = processImportDirective(directive, context);
processedText = processedText.substring(0, directive.start) + result + processedText.substring(directive.end);
}
node.value = processedText;
}
function processParagraphNode(node, context) {
node.children.forEach((child) => {
if (child.type === "text") {
processTextNode(child, context);
}
});
}
function extractImportDirectives(text) {
const directives = [];
const importRegex = /@import\s+([^\s#]+)(?:#([^\s]+))?/g;
let match;
while ((match = importRegex.exec(text)) !== null) {
const [fullMatch, filePath, section] = match;
directives.push({
filePath: filePath.trim(),
section: section?.trim(),
start: match.index,
end: match.index + fullMatch.length,
fullMatch
});
}
return directives;
}
function processImportDirective(directive, context) {
if (context.depth >= context.maxDepth) {
console.warn(
`[remarkImports] Maximum import depth (${context.maxDepth}) reached for file "${directive.filePath}"`
);
return directive.fullMatch;
}
const absolutePath = pathBrowserifyExports.resolve(context.basePath, directive.filePath);
const normalizedPath = pathBrowserifyExports.normalize(absolutePath);
if (context.importStack.includes(normalizedPath)) {
console.warn(`[remarkImports] Circular import detected: ${normalizedPath}`);
return directive.fullMatch;
}
if (context.debug) {
console.log(
`[remarkImports] Processing import "${directive.filePath}" (resolved: ${normalizedPath})`
);
}
const fileContent = loadFileContent(normalizedPath, context);
if (!fileContent) {
console.warn(`[remarkImports] Import file not found: ${directive.filePath}`);
return directive.fullMatch;
}
let contentToImport = fileContent;
if (context.mergeMetadata) {
const { content, metadata } = parseYamlFrontMatter(fileContent, false);
contentToImport = content;
if (Object.keys(metadata).length > 0) {
context.importedMetadataList.push({
metadata,
source: normalizedPath
});
if (!context.importedFiles.includes(normalizedPath)) {
context.importedFiles.push(normalizedPath);
}
if (context.debug) {
console.log(
`[remarkImports] Collected metadata from ${directive.filePath}:`,
Object.keys(metadata)
);
}
}
}
if (directive.section) {
contentToImport = extractSection(contentToImport, directive.section, context.debug);
}
const nestedContext = {
...context,
depth: context.depth + 1,
importStack: [...context.importStack, normalizedPath],
basePath: pathBrowserifyExports.dirname(normalizedPath)
// Update base path for relative imports
};
return processNestedImports(contentToImport, nestedContext);
}
function performSequentialMerge(context) {
if (context.importedMetadataList.length === 0) {
return {
metadata: {},
stats: void 0
};
}
if (Date.now() - context.startTime > context.timeoutMs) {
throw new Error(
`Import processing timed out after ${context.timeoutMs}ms. This may indicate complex nested imports or slow file operations.`
);
}
const mergeOptions = {
filterReserved: context.filterReserved,
validateTypes: context.validateTypes,
logOperations: context.logImportOperations,
includeStats: true,
timeoutMs: Math.max(1e3, context.timeoutMs - (Date.now() - context.startTime))
};
const metadataList = context.importedMetadataList.map((item) => item.metadata);
if (context.debug) {
console.log(
`[remarkImports] Performing sequential merge of ${metadataList.length} metadata objects`
);
}
try {
const result = mergeSequentially({}, metadataList, mergeOptions);
if (context.onMetadataMerged && Object.keys(result.metadata).length > 0) {
context.onMetadataMerged(result.metadata, "merged-imports");
}
return result;
} catch (error) {
if (context.debug) {
console.warn("[remarkImports] Sequential merge failed:", error);
}
throw error;
}
}
function loadFileContent(filePath, context) {
if (context.contentCache.has(filePath)) {
return context.contentCache.get(filePath);
}
try {
if (existsSync(filePath)) ;
} catch (error) {
if (context.debug) {
console.warn(`[remarkImports] Failed to load import file "${filePath}":`, error);
}
}
return null;
}
function extractSection(content, sectionName, debug) {
const lines = content.split("\n");
let sectionStart = -1;
let sectionEnd = lines.length;
let sectionLevel = 0;
for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim();
const headerMatch = line.match(/^(#{1,6})\s+(.+)$/);
if (headerMatch) {
const [, hashes, title] = headerMatch;
const level = hashes.length;
if (title.toLowerCase() === sectionName.toLowerCase()) {
sectionStart = i + 1;
sectionLevel = level;
if (debug) {
console.log(`[remarkImports] Found section '${sectionName}' at line ${i}`);
}
break;
}
}
}
if (sectionStart === -1) {
if (debug) {
console.warn(`[remarkImports] Section '${sectionName}' not found`);
}
return content;
}
for (let i = sectionStart; i < lines.length; i++) {
const line = lines[i].trim();
const headerMatch = line.match(/^(#{1,6})\s+(.+)$/);
if (headerMatch) {
const [, hashes] = headerMatch;
const level = hashes.length;
if (level <= sectionLevel) {
sectionEnd = i;
break;
}
}
}
return lines.slice(sectionStart, sectionEnd).join("\n").trim();
}
function processNestedImports(content, context) {
const importDirectives = extractImportDirectives(content);
if (importDirectives.length === 0) {
return content;
}
let processedContent = content;
for (let i = importDirectives.length - 1; i >= 0; i--) {
const directive = importDirectives[i];
const result = processImportDirective(directive, context);
processedContent = processedContent.substring(0, directive.start) + result + processedContent.substring(directive.end);
}
return processedContent;
}
const LEGAL_HEADER_PATTERN = /^(l{1,9})\.\s+(.+)$/;
function getHeadingLevel(pattern) {
return pattern.length;
}
function isLegalHeader(node) {
if (node.children.length === 1 && node.children[0].type === "text") {
const textNode = node.children[0];
const match = textNode.value.match(LEGAL_HEADER_PATTERN);
if (match) {
const [, levelPattern, headerText] = match;
return {
level: getHeadingLevel(levelPattern),
text: headerText.trim()
};
}
}
if (node.children.length > 0 && node.children[0].type === "text") {
const firstChild = node.children[0];
const match = firstChild.value.match(/^(l{1,5})\.\s*/);
if (match) {
const [fullMatch, levelPattern] = match;
const remainingText = firstChild.value.slice(fullMatch.length);
const otherText = node.children.slice(1).map((child) => {
if (child.type === "text") return child.value;
if (child.type === "html") return child.value || "";
return "";
}).join("");
return {
level: getHeadingLevel(levelPattern),
text: (remainingText + otherText).trim()
};
}
}
return null;
}
function convertToHeading(node, level, text) {
const heading = {
type: "heading",
depth: level,
children: parseMarkdownInlineFormatting(text)
};
heading.data = { isLegalHeader: true };
if (node.children.length > 1 || node.children[0].type === "text" && node.children[0].value.includes("<span")) {
const firstChild = node.children[0];
if (firstChild.type === "text") {
const match = firstChild.value.match(/^(l{1,5})\.\s*/);
if (match) {
const [fullMatch] = match;
firstChild.value = firstChild.value.slice(fullMatch.length);
heading.children = node.children.filter((child) => {
if (child.type === "text" && child.value.trim() === "") {
return false;
}
return true;
});
}
}
}
return heading;
}
const remarkLegalHeadersParser = (options = {}) => {
const { debug = false } = options;
return (tree) => {
if (debug) {
console.log("π [remarkLegalHeadersParser] Parsing legal header syntax");
}
let convertedCount = 0;
const nodesToReplace = [];
visit(tree, "paragraph", (node, index, parent) => {
if (parent && typeof index === "number") {
if (node.children.length === 1 && node.children[0].type === "text") {
const textNode = node.children[0];
const lines = textNode.value.split("\n");
const hasLegalHeaders = lines.some((line) => LEGAL_HEADER_PATTERN.test(line));
if (hasLegalHeaders) {
const newNodes = [];
const nonHeaderLines = [];
for (const line of lines) {
const match = line.match(LEGAL_HEADER_PATTERN);
if (match) {
if (nonHeaderLines.length > 0) {
newNodes.push({
type: "paragraph",
children: [
{
type: "text",
value: nonHeaderLines.join("\n")
}
]
});
nonHeaderLines.length = 0;
}
const [, levelPattern, headerText] = match;
const level = getHeadingLevel(levelPattern);
if (debug) {
console.log(
`π [remarkLegalHeadersParser] Converting "${line.substring(0, 50)}..." to level ${level} heading`
);
}
const headingNode = {
type: "heading",
depth: level,
children: parseMarkdownInlineFormatting(headerText.trim())
};
headingNode.data = { isLegalHeader: true };
newNodes.push(headingNode);
convertedCount++;
} else {
nonHeaderLines.push(line);
}
}
if (nonHeaderLines.length > 0) {
newNodes.push({
type: "paragraph",
children: [
{
type: "text",
value: nonHeaderLines.join("\n")
}
]
});
}
if (newNodes.length > 0) {
nodesToReplace.push({ parent, index, newNodes });
}
}
} else if (node.children.length > 1) {
let fullText = "";
const childrenMap = /* @__PURE__ */ new Map();
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i];
const start = fullText.length;
if (child.type === "text") {
fullText += child.value;
} else if (child.type === "emphasis") {
const emphasisText = child.children.map((c) => c.value || "").join("");
fullText += `_${emphasisText}_`;
} else if (child.type === "strong") {
const strongText = child.children.map((c) => c.value || "").join("");
fullText += `__${strongText}__`;
} else if (child.type === "link") {
const linkText = child.children.map((c) => c.value || "").join("");
const url = child.url || "";
fullText += `[${linkText}](${url})`;
} else if (child.type === "inlineCode") {
fullText += `\`${child.value || ""}\``;
} else {
fullText += child.value || "";
}
const end = fullText.length;
childrenMap.set(i, { start, end, child });
}
const lines = fullText.split("\n");
const hasLegalHeaders = lines.some((line) => LEGAL_HEADER_PATTERN.test(line));
if (hasLegalHeaders) {
const newNodes = [];
const nonHeaderLines = [];
for (const line of lines) {
const match = line.match(LEGAL_HEADER_PATTERN);
if (match) {
if (nonHeaderLines.length > 0) {
newNodes.push({
type: "paragraph",
children: [
{
type: "text",
value: nonHeaderLines.join("\n")
}
]
});
nonHeaderLines.length = 0;
}
const [, levelPattern, headerText] = match;
const level = getHeadingLevel(levelPattern);
if (debug) {
console.log(
`π [remarkLegalHeadersParser] Converting complex "${line.substring(0, 50)}..." to level ${level} heading`
);
}
const headingNode = {
type: "heading",
depth: level,
children: parseMarkdownInlineFormatting(headerText.trim())
};
headingNode.data = { isLegalHeader: true };
newNodes.push(headingNode);
convertedCount++;
} else {
nonHeaderLines.push(line);
}
}
if (nonHeaderLines.length > 0) {
newNodes.push({
type: "paragraph",
children: [
{
type: "text",
value: nonHeaderLines.join("\n")
}
]
});
}
if (newNodes.length > 0) {
nodesToReplace.push({ parent, index, newNodes });
}
}
}
} else {
if (debug) {
const firstChildText = node.children[0]?.type === "text" ? node.children[0].value : "<non-text>";
console.log(`π [remarkLegalHeadersParser] Single-line paragraph: "${firstChildText}"`);
}
const headerInfo = isLegalHeader(node);
if (headerInfo && parent && typeof index === "number") {
if (debug) {
const firstChildText = node.children[0].type === "text" ? node.children[0].value : "";
console.log(
`π [remarkLegalHeadersParser] Converting "${firstChildText.substring(0, 50)}..." to level ${headerInfo.level} heading`
);
}
const heading = convertToHeading(node, headerInfo.level, headerInfo.text);
nodesToReplace.push({ parent, index, newNodes: [heading] });
convertedCount++;
}
}
});
for (let i = nodesToReplace.length - 1; i >= 0; i--) {
const { parent, index, newNodes } = nodesToReplace[i];
parent.children.splice(index, 1, ...newNodes);
}
if (debug) {
console.log(`β
[remarkLegalHeadersParser] Converted ${convertedCount} legal headers`);
}
};
};
function parseMarkdownInlineFormatting(text) {
const children = [];
const patterns = [
{ regex: /\[([^\]]+)\]\([^)]+\)/g, type: "link" },
// [text](url) - extract just the text
{ regex: /`([^`]+)`/g, type: "inlineCode" },
// `code`
{ regex: /\*\*(.+?)\*\*/g, type: "strong" },
// **bold**
{ regex: /__(.+?)__/g, type: "strong" },
// __bold__
{ regex: /\*(.+?)\*/g, type: "emphasis" },
// *italic*
{ regex: /_(.+?)_/g, type: "emphasis" }
// _italic_
];
const allMatches = [];
for (const pattern of patterns) {
let match;
while ((match = pattern.regex.exec(text)) !== null) {
allMatches.push({
start: match.index,
end: match.index + match[0].length,
type: pattern.type,
content: match[1]
});
}
}
allMatches.sort((a, b) => a.start - b.start);
const validMatches = [];
let lastEnd = 0;
for (const match of allMatches) {
if (match.start >= lastEnd) {
validMatches.push(match);
lastEnd = match.end;
}
}
let pos = 0;
for (const match of validMatches) {
if (pos < match.start) {
const beforeText = text.slice(pos, match.start);
if (beforeText) {
children.push({
type: "text",
value: beforeText
});
}
}
if (match.type === "inlineCode") {
children.push({
type: "inlineCode",
value: match.content
});
} else if (match.type === "link") {
children.push({
type: "text",
value: match.content
});
} else {
children.push({
type: match.type,
children: [
{
type: "text",
value: match.content
}
]
});
}
pos = match.end;
}
if (pos < text.length) {
const remainingText = text.slice(pos);
if (remainingText) {
children.push({
type: "text",
value: remainingText
});
}
}
if (children.length === 0) {
return [
{
type: "text",
value: text
}
];
}
return children;
}
const EXTENDED_DATE_PATTERN = /@today((?:[+-]\d+[dmy]?(?:\[[^\]]+\])?)|(?:[+-]\d+[dmy]?)|(?:\[[^\]]+\]))?/g;
function parseDateToken(token) {
if (!token) return { arithmetic: null, format: null, isValid: true };
if (!/^[+-]?\d*[dmy]?(\[[^\]]+\])?$/.test(token)) {
return { arithmetic: null, format: null, isValid: false };
}
const formatMatch = token.match(/\[([^\]]+)\]/);
const format = formatMatch ? formatMatch[1] : null;
const arithmeticPart = token.replace(/\[([^\]]+)\]/, "");
let arithmetic = null;
if (arithmeticPart) {
const arithmeticMatch = arithmeticPart.match(/^([+-])(\d+)([dmy]?)$/);
if (arithmeticMatch) {
const [, sign, amount, suffix] = arithmeticMatch;
const numAmount = parseInt(amount) * (sign === "-" ? -1 : 1);
switch (suffix) {
case "d":
case "":
arithmetic = { type: "days", amount: numAmount };
break;
case "m":
arithmetic = { type: "months", amount: numAmount };
break;
case "y":
arithmetic = { type: "years", amount: numAmount };
break;
default:
arithmetic = { type: "days", amount: numAmount };
}
} else if (arithmeticPart !== "") {
return { arithmetic: null, format: null, isValid: false };
}
}
return { arithmetic, format, isValid: true };
}
function applyDateArithmetic(baseDate, arithmetic) {
if (!arithmetic) return baseDate;
switch (arithmetic.type) {
case "days":
return addDays(baseDate, arithmetic.amount);
case "months":
return addMonths(baseDate, arithmetic.amount);
case "years":
return addYears(baseDate, arithmetic.amount);
default:
return baseDate;
}
}
function formatDateBasic(date, format) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
const dayNumber = date.getDate();
const monthNames = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
];
switch (format.toLowerCase()) {
case "iso":
case "yyyy-mm-dd":
return `${year}-${month}-${day}`;
case "us":
case "mm/dd/yyyy":
return `${month}/${day}/${year}`;
case "eu":
case "dd/mm/yyyy":
return `${day}/${month}/${year}`;
case "legal":
return `${monthNames[date.getMonth()]} ${dayNumber}, ${year}`;
default:
return `${year}-${month}-${day}`;
}
}
function getDateFieldCssClass(hasArithmetic) {
return hasArithmetic ? "legal-field highlight" : "legal-field imported-value";
}
function formatDateValue(value, originalToken, enableFieldTracking = false, hasArithmetic = false) {
if (!enableFieldTracking) {
return value;
}
const cssClass = getDateFieldCssClass(hasArithmetic);
const fieldName = `date.${originalToken.replace(/[@[\]]/g, "")}`;
return `<span class="${cssClass}" data-field="${fieldName.replace(/"/g, """)}">${value}</span>`;
}
function processDateReferencesInAST(root, metadata, enableFieldTracking = false) {
visit(root, "text", (node, index, parent) => {
const originalValue = node.value;
let modifiedValue = originalValue;
let hasChanges = false;
modifiedValue = modifiedValue.replace(EXTENDED_DATE_PATTERN, (match, token) => {
try {
const { arithmetic, format: tokenFormat, isValid } = parseDateToken(token);
if (!isValid) {
return match;
}
let date = /* @__PURE__ */ new Date();
const hasArithmetic = !!arithmetic;
if (arithmetic) {
date = applyDateArithmetic(date, arithmetic);
}
const format = tokenFormat || metadata["date-format"] || "YYYY-MM-DD";
const formattedDate = formatDateBasic(date, format);
fieldTracker.trackField(`date.${match.replace(/[@[\]]/g, "")}`, {
value: formattedDate,
originalValue: match,
hasLogic: hasArithmetic
});
hasChanges = true;
return formatDateValue(formattedDate, match, enableFieldTracking, hasArithmetic);
} catch (error) {
console.warn(`Error processing date reference ${match}:`, error);
return match;
}
});
if (hasChanges) {
if (enableFieldTracking && modifiedValue.includes("<span")) {
if (parent && typeof index === "number") {
const htmlNode = {
type: "html",
value: modifiedValue
};
parent.children[index] = htmlNode;
}
} else {
node.value = modifiedValue;
}
}
});
visit(root, "html", (node) => {
if (!node.value || !node.value.includes("@today")) {
return;
}
let modifiedValue = node.value;
let hasChanges = false;
modifiedValue = modifiedValue.replace(
EXTENDED_DATE_PATTERN,
(match, formatOverride) => {
try {
const date = /* @__PURE__ */ new Date();
const format = formatOverride || metadata["date-format"] || "YYYY-MM-DD";
const formattedDate = formatDateBasic(date, format);
fieldTracker.trackField(`date.${match.replace(/[@[\]]/g, "")}`, {
value: formattedDate,
originalValue: match,
hasLogic: false
});
hasChanges = true;
if (enableFieldTracking) {
const cssClass = getDateFieldCssClass(false);
const fieldName = `date.${match.replace(/[@[\]]/g, "")}`;
return `<span class="${cssClass}" data-field="${fieldName.replace(/"/g, """)}">${formattedDate}</span>`;
} else {
return formattedDate;
}
} catch (error) {
console.warn(`Error processing date reference ${match}:`, error);
return match;
}
}
);
if (hasChanges) {
node.value = modifiedValue;
}
});
}
const remarkDates = (options) => {
const { metadata, debug = false, enableFieldTracking = false } = options;
return (tree) => {
if (debug) {
console.log("π
Processing date references with remark plugin");
}
processDateReferencesInAST(tree, metadata, enableFieldTracking);
if (debug) {
console.log("β
Date reference processing completed");
}
};
};
function escapeHtmlAttribute(value) {
return value.replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">");
}
function processTemplateLoops(content, metadata, context, enableFieldTracking = true) {
const loopBlocks = findLoopBlocks(content);
if (loopBlocks.length === 0) {
return content;
}
let processedContent = content;
for (let i = loopBlocks.length - 1; i >= 0; i--) {
const loopBlock = loopBlocks[i];
const expandedContent = expandLoopBlock(loopBlock, metadata, context, enableFieldTracking);
processedContent = processedContent.slice(0, loopBlock.start) + expandedContent + processedContent.slice(loopBlock.end);
}
return processedContent;
}
function findLoopBlocks(content) {
const loopBlocks = [];
const loopPattern = /\{\{#([\w.]+)\}\}([\s\S]*?)\{\{\/\1\}\}/g;
const conditionalPattern = /\{\{#if\s*([^}]+)\}\}([\s\S]*?)\{\{\/if\}\}/g;
let match;
while ((match = loopPattern.exec(content)) !== null) {
const [fullMatch, variable, loopContent] = match;
loopBlocks.push({
variable,
content: loopContent,
start: match.index,
end: match.index + fullMatch.length,
fullMatch
});
}
while ((match = conditionalPattern.exec(content)) !== null) {
const [fullMatch, condition, conditionalContent] = match;
loopBlocks.push({
variable: `if ${condition}`,
// Mark as conditional
content: conditionalContent,
start: match.index,
end: match.index + fullMatch.length,
fullMatch
});
}
return loopBlocks;
}
function expandLoopBlock(loopBlock, metadata, parentContext, enableFieldTracking = true) {
const { variable, content } = loopBlock;
if (variable.startsWith("if ")) {
const condition = variable.substring(3).trim();
const conditionValue = evaluateCondition(condition, metadata);
fieldTracker.trackField(`if ${condition}`, {
value: conditionValue,
hasLogic: true,
mixinUsed: "conditional"
});
const elseIndex = content.indexOf("{{else}}");
if (elseIndex !== -1) {
const ifContent = content.substring(0, elseIndex);
const elseContent = content.substring(elseIndex + 8);
const selectedContent = conditionValue ? ifContent : elseContent;
return processTemplateContent(selectedContent, metadata, parentContext, enableFieldTracking);
} else {
if (conditionValue) {
return processTemplateContent(content, metadata, parentContext, enableFieldTracking);
} else {
return "";
}
}
}
const loopValue = resolveLoopVariable(variable, metadata, parentContext);
fieldTracker.trackField(variable, {
value: loopValue,
hasLogic: true,
mixinUsed: "loop"
});
if (Array.isArray(loopValue)) {
return expandArrayLoop(
variable,
content,
loopValue,
metadata,
parentContext,
enableFieldTracking
);
} else if (loopValue) {
return expandConditionalBlock(
variable,
content,
loopValue,
metadata,
parentContext,
enableFieldTracking
);
} else {
return "";
}
}
function expandArrayLoop(variable, content, items, metadata, parentContext, enableFieldTracking = true) {
const expandedParts = [];
for (let i = 0; i < items.length; i++) {
const item = items[i];
const loopContext = {
variable,
item,
index: i,
total: items.length,
parent: parentContext
};
const enhancedMetadata = createEnhancedMetadata(metadata, item, loopContext);
let processedContent = content;
processedContent = processTemplateLoops(
processedContent,
enhancedMetadata,
loopContext,
enableFieldTracking
);
processedContent = processItemMixins(
processedContent,
enhancedMetadata,
loopContext,
enableFieldTracking
);
processedContent = convertMarkdownListToHtml(processedContent);
expandedParts.push(processedContent);
}
return expandedParts.join("\n");
}
function expandConditionalBlock(variable, content, value, metadata, parentContext, enableFieldTracking = true) {
const enhancedMetadata = {
...metadata,
[variable]: value
};
let processedContent = processTemplateLoops(
content,
enhancedMetadata,
parentContext,
enableFieldTracking
);
processedContent = processItemMixins(
processedContent,
enhancedMetadata,
parentContext,
enableFieldTracking
);
return processedContent;
}
function resolveLoopVariable(variable, metadata, parentContext) {
const resolvedFromMetadata = resolvePath(metadata, variable);
if (resolvedFromMetadata !== void 0) {
return resolvedFromMetadata;
}
if (parentContext && parentContext.item) {
const resolvedFromContext = resolvePath(parentContext.item, variable);
if (resolvedFromContext !== void 0) {
return resolvedFromContext;
}
}
let ctx = parentContext;
while (ctx && ctx.parent) {
ctx = ctx.parent;
if (ctx.item) {
const resolvedFromNestedContext = resolvePath(ctx.item, variable);
if (resolvedFromNestedContext !== void 0) {
return resolvedFromNestedContext;
}
}
}
return void 0;
}
function createEnhancedMetadata(metadata, item, context) {
const enhanced = { ...metadata };
if (item && typeof item === "object" && !Array.isArray(item)) {
Object.assign(enhanced, item);
}
enhanced["."] = item;
enhanced["@index"] = context.index;
enhanced["@total"] = context.total;
enhanced["@first"] = context.index === 0;
enhanced["@last"] = context.index === context.total - 1;
return enhanced;
}
function evaluateHelperExpression(expression, metadata) {
try {
const match = expression.match(/^(\w+)\((.*)\)$/);
if (!match) return void 0;
const [, helperName, argsString] = match;
const helper = extensionHelpers[helperName];
if (!helper || typeof helper !== "function") {
return void 0;
}
const args = argsString ? parseHelperArguments(argsString, metadata) : [];
return helper(...args);
} catch (error) {
console.error(`Error evaluating helper expression: ${expression}`, error);
return void 0;
}
}
function parseHelperArguments(argsString, metadata) {
const args = [];
let currentArg = "";
let parenDepth = 0;
let inQuotes = false;
let quoteChar = "";
for (let i = 0; i < argsString.length; i++) {
const char = argsString[i];
if ((char === '"' || char === "'") && !inQuotes) {
inQuotes = true;
quoteChar = char;
currentArg += char;
} else if (char === quoteChar && inQuotes) {
inQuotes = false;
quoteChar = "";
currentArg += char;
} else if (char === "(" && !inQuotes) {
parenDepth++;
currentArg += char;
} else if (char === ")" && !inQuotes) {
parenDepth--;
currentArg += char;
} else if (char === "," && parenDepth === 0 && !inQuotes) {
args.push(resolveHelperArgument(currentArg.trim(), metadata));
currentArg = "";
} else {
currentArg += char;
}
}
if (currentArg.trim()) {
args.push(resolveHelperArgument(currentArg.trim(), metadata));
}
return args;
}
function resolveHelperArgument(arg, metadata) {
if (arg.startsWith('"') && arg.endsWith('"') || arg.startsWith("'") && arg.endsWith("'")) {
return arg.slice(1, -1);
}
if (arg === "true") return true;
if (arg === "false") return false;
if (/^\d+$/.test(arg)) {
return parseInt(arg, 10);
}
if (/^\d+\.\d+$/.test(arg)) {
return parseFloat(arg);
}
if (arg === "@today") {
return metadata["@today"] ? new Date(metadata["@today"]) : /* @__PURE__ */ new Date();
}
if (arg.includes("(") && arg.includes(")")) {
const result = evaluateHelperExpression(arg, metadata);
if (result !== void 0) {
return result;
}
}
return resolvePath(metadata, arg);
}
function resolvePath(obj, path) {
const keys = path.split(".");
let current = obj;
for (const key of keys) {
if (current && typeof current === "object" && key in current) {
current = current[key];
} else {
return void 0;
}
}
return current;
}
function processItemMixins(content, metadata, context, enableFieldTracking = true) {
const mixinPattern = /\{\{([^}]+)\}\}/g;
return content.replace(mixinPattern, (match, variable) => {
const trimmedVar = variable.trim();
if (trimmedVar.includes("?") && trimmedVar.includes(":")) {
const result = processConditionalExpression(trimmedVar, metadata, enableFieldTracking);
if (enableFieldTracking) {
return `<span class="highlight">${result}</span>`;
}
return result;
}
if (trimmedVar.includes("(") && trimmedVar.includes(")")) {
const result = evaluateHelperExpression(trimmedVar, metadata);
if (result !== void 0) {
fieldTracker.trackField(trimmedVar, {
value: result,
hasLogic: true,
mixinUsed: "helper"
});
if (enableFieldTracking) {
const escapedField = escapeHtmlAttribute(trimmedVar);
const stringResult = String(result);
return `<span class="highlight"><span class="imported-value" data-field="${escapedField}">${stringResult}</span></span>`;
}
return String(result);
}
}
const value = resolveVariablePath(trimmedVar, metadata);
if (value === void 0 || value === null) {
if (enableFieldTracking) {
return `<span class="missing-value" data-field="${escapeHtmlAttribute(trimmedVar)}">[[${trimmedVar}]]</span>`;
}
return `{{${trimmedVar}}}`;
}
if (enableFieldTracking) {
return `<span class="imported-value" data-field="${escapeHtmlAttribute(trimmedVar)}">${String(value)}</span>`;
}
return String(value);
});
}
function processConditionalExpression(expression, metadata, enableFieldTracking = true) {
const questionIndex = findOperatorIndex(expression, "?");
const colonIndex = findOperatorIndex(expression, ":", questionIndex);
if (questionIndex === -1 || colonIndex === -1) {
return expression;
}
const condition = expression.substring(0, questionIndex).trim();
const truePart = expression.substring(questionIndex + 1, colonIndex).trim();
const falsePart = expression.substring(colonIndex + 1).trim();
const conditionValue = resolveVariablePath(condition, metadata);
const selectedPart = conditionValue ? truePart : falsePart;
if (selectedPart.startsWith('"') && selectedPart.endsWith('"') || // eslint-disable-next-line quotes
selectedPart.startsWith("'") && selectedPart.endsWith("'")) {
const unquotedValue = selectedPart.slice(1, -1);
if (enableFieldTracking) {
return `<span class="imported-value" data-field="${escapeHtmlAttribute(expression)}">${unquotedValue}</span>`;
}
return unquotedValue;
}
const processedValue = processExpression(selectedPart, metadata);
if (enableFieldTracking) {
return `<span class="imported-value" data-field="${escapeHtmlAttribute(expression)}">${processedValue}</span>`;
}
return processedValue;
}
function findOperatorIndex(expression, operator, startIndex = 0) {
let inQuotes = false;
let quoteChar = "";
for (let i = startIndex; i < expression.length; i++) {
const char = expression[i];
if (!inQuotes && (char === '"' || char === "'")) {
inQuotes = true;
quoteChar = char;
} else if (inQuotes && char === quoteChar) {
inQuotes = false;
quoteChar = "";
} else if (!inQuotes && char === operator) {
return i;
}
}
return -1;
}
function processExpression(expression, metadata) {
if (expression.includes("+") && (expression.includes('"') || expression.includes("'"))) {
return evaluateStringConcatenation(expression, metadata);
}
if (expression.includes("*") || expression.includes("/") || expression.includes("+") || expression.includes("-")) {
return evaluateMathematicalExpression(expression, metadata);
}
const value = resolveVariablePath(expression, metadata);
return value !== void 0 ? String(value) : expression;
}
function evaluateStringConcatenation(expression, metadata) {
try {
let processedExpression = expression.trim();
const variablePattern = /\b[a-zA-Z_][a-zA-Z0-9_.]*\b/g;
const variables = /* @__PURE__ */ new Set();
let match;
let inQuotes = false;
let quoteChar = "";
for (let i = 0; i < expression.length; i++) {
const char = expression[i];
if (!inQuotes && (char === '"' || char === "'")) {
inQuotes = true;
quoteChar = char;
} else if (inQuotes && char === quoteChar) {
inQuotes = false;
quoteChar = "";
}
}
while ((match = variablePattern.exec(expression)) !== null) {
const beforeMatch = expression.substring(0, match.index);
const openQuotes = (beforeMatch.match(/"/g) || []).length;
const openSingleQuotes = (beforeMatch.match(/'/g) || []).length;
if (openQuotes % 2 === 0 && openSingleQuotes % 2 === 0) {
variables.add(match[0]);
}
}
for (const variable of variables) {
const value = resolveVariablePath(variable, metadata);
if (value !== void 0) {
const regex = new RegExp(`\\b${variable.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "g");
if (typeof value === "string") {
processedExpression = processedExpression.replace(regex, `"${value}"`);
} else {
processedExpression = processedExpression.replace(regex, String(value));
}
} else {
return expression;
}
}
const result = new Function(`"use strict"; return (${processedExpression})`)();
return String(result);
} catch (error) {
return expression;
}
}
function evaluateMathematicalExpression(expression, metadata) {
try {
let processedExpression = expression;
const variablePattern = /[a-zA-Z_][a-zA-Z0-9_.]*(?![a-zA-Z0-9_.])/g;
const variables = /* @__PURE__ */ new Set();
let match;
while ((match = variablePattern.exec(expression)) !== null) {
variables.add(match[0]);
}
for (const variable of variables) {
const value = resolveVariablePath(variable, metadata);
if (value !== void 0 && !isNaN(Number(value))) {
const regex = new RegExp(`\\b${variable.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "g");
processedExpression = processedExpression.replace(regex, String(value));
} else {
return expression;
}
}
if (!/^[\d\s+\-*/().]+$/.test(processedExpression)) {
return expression;
}
const result = new Function(`"use strict"; return (${processedExpression})`)();
return typeof result === "number" && !isNaN(result) ? String(result) : expression;
} catch (error) {
return expression;
}
}
function resolveVariablePath(path, metadata, context) {
if (path === ".") {
return metadata["."];
}
const parts = path.split(".");
let current = metadata;
for (const part of parts) {
if (current && typeof current === "object" && Object.prototype.hasOwnProperty.call(current, part)) {
current = current[part];
} else {
return void 0;
}
}
return current;
}
function convertMarkdownListToHtml(content) {
const trimmedContent = content.trim();
if (trimmedContent.startsWith("- ")) {
const listItemContent = trimmedContent.substring(2).trim();
return `<li>${listItemContent}</li>`;
}
return content;
}
function evaluateCondition(condition, metadata) {
const value = resolveVariablePath(condition, metadata);
if (value === null || value === void 0) return false;
if (typeof value === "boolean") return value;
if (typeof value === "number") return value !== 0;
if (typeof value === "string") return value.length > 0;
if (Array.isArray(value)) return value.length > 0;
if (typeof value === "object") return Object.keys(value).length > 0;
return Boolean(value);
}
function processTemplateContent(content, metadata, context, enableFieldTracking = true) {
let processedContent = processTemplateLoops(content, metadata, context, enableFieldTracking);
processedContent = processedContent.replace(/\{\{([^#/][^}]*)\}\}/g, (match, variable) => {
const trimmedVar = variable.trim();
const value = resolveVariablePath(trimmedVar, metadata);
if (value !== void 0) {
if (enableFieldTracking) {
const isEmptyValue = value === null || value === "" || typeof value === "string" && value.trim() === "";
const cssClass = isEmptyValue ? "missing-value" : "imported-value";
fieldTracker.trackField(trimmedVar, {
value,
originalValue: match,
hasLogic: false,
mixinUsed: "conditional"
});
return `<span class="${cssClass}" data-field="${escapeHtmlAttribute(trimmedVar)}">${String(value)}</span>`;
}
return String(value);
}
return match;
});
return processedContent;
}
var define_process_default = { env: { NODE_ENV: "production", DEBUG: false } };
function exportMetadata(metadata, format, outputPath) {
const exportedFiles = [];
const yamlOutput = metadata["meta-yaml-output"];
const jsonOutput = metadata["meta-json-output"];
const customOutputPath = metadata["meta-output-path"];
let outputDir;
if (customOutputPath) {
outputDir = customOutputPath;
} else if (outputPath) {
outputDir = pathBrowserifyExports.extname(outputPath) ? pathBrowserifyExports.dirname(outputPath) : outputPath;
} else {
outputDir = define_process_default.cwd();
}
const exportedMetadata = filterMetadataForExport(metadata);
if (yamlOutput || format === "yaml") {
let yamlPath;
if (yamlOutput) {
yamlPath = pathBrowserifyExports.resolve(outputDir, yamlOutput);
} else if (outputPath && pathBrowserifyExports.extname(outputPath)) {
yamlPath = outputPath;
} else {
yamlPath = pathBrowserifyExports.resolve(outputDir, "metadata.yaml");
}
try {
const yamlDir = pathBrowserifyExports.dirname(yamlPath);
if (!existsSync(yamlDir)) {
mkdirSync(yamlDir, { recursive: true });
}
const yamlContent = dump(exportedMetadata);
writeFileSync(yamlPath, yamlContent, "utf8");
exportedFiles.push(yamlPath);
console.log(`Exported YAML metadata to: ${yamlPath}`);
} catch (error) {
console.error("Error exporting YAML metadata:", error);
}
}
if (jsonOutput || format === "json") {
const jsonPath = jsonOutput ? pathBrowserifyExports.resolve(outputDir, jsonOutput) : pathBrowserifyExports.resolve(outputDir, "metadata.json");
try {
const jsonDir = pathBrowserifyExports.dirname(jsonPath);
if (!existsSync(jsonDir)) {
mkdirSync(jsonDir, { recursive: true });
}
const jsonContent = JSON.stringify(exportedMetadata, null, 2);
writeFileSync(jsonPath, jsonContent, "utf8");
exportedFiles.push(jsonPath);
console.log(`Exported JSON metadata to: ${jsonPath}`);
} catch (error) {
console.error("Error exporting JSON metadata:", error);
}
}
return { exportedFiles };
}
function filterMetadataForExport(metadata) {
const result = JSON.parse(JSON.stringify(metadata));
delete result["meta-yaml-output"];
delete result["meta-json-output"];
delete result["meta-output-path"];
delete result["meta-include-original"];
if (metadata["meta-include-original"] === false && metadata["meta"]) {
return { meta: result.meta };
}
return result;
}
function createLegalMarkdownProcessor(metadata, options) {
const processor = unified().use(remarkParse);
if (!options.noHeaders) {
processor.use(remarkLegalHeadersParser, {
debug: options.debug
});
}
if (!options.noImports) {
processor.use(remarkImports, {
basePath: options.basePath || ".",
mergeMetadata: true,
debug: options.debug,
maxDepth: 10,
timeoutMs: 3e4,
filterReserved: true,
validateTypes: true,
logImportOperations: options.debug
});
}
if (!options.noMixins) {
processor.use(remarkMixins, {
metadata,
basePath: options.basePath || ".",
debug: options.debug,
maxDepth: 5
});
}
if (!options.noClauses) {
processor.use(remarkClauses, {
metadata,
debug: options.debug,
enableFieldTracking: options.enableFieldTracking
});
}
processor.use(remarkDates, {
metadata,
debug: options.debug,
enableFieldTracking: options.enableFieldTracking
});
processor.use(remarkTemplateFields, {
metadata,
fieldPatterns: options.fieldPatterns,
enableFieldTracking: options.enableFieldTracking,
debug: options.debug
});
if (!options.disableCrossReferences && !options.noReferences) {
processor.use(remarkCrossReferences, {
metadata,
enableFieldTracking: options.enableFieldTracking,
debug: options.debug
});
}
if (!options.noHeaders) {
processor.use(remarkHeaders, {
metadata,
noReset: options.noReset,
noIndent: options.noIndent,
debug: options.debug
});
}
processor.use(remarkStringify, {
emphasis: "*",
// Use * for _italic_
strong: "*",
// Use * for __bold__ (single asterisk means **double**)
bullet: "-",
// Use - for bullets
fence: "`",
// Use ` for code blocks
fences: true,
// Use fenced code blocks
incrementListMarker: true
// Increment list markers (1. 2. 3.)
});
return processor;
}
async function processLegalMarkdownWithRemark(content, options = {}) {
const startTime = Date.now();
const warnings = [];
const pluginsUsed = ["remarkParse", "remarkStringify"];
try {
fieldTracker.clear();
if (options.debug) {
console.log("π Starting Legal Markdown processing with remark pipeline");
}
if (options.debug) {
console.log("[legal-markdown-processor] Raw content length:", content.length);
console.log(
"[legal-markdown-processor] Raw content first 500 chars:",
content.substring(0, 500)
);
}
const { content: contentWithoutYaml, metadata: yamlMetadata } = parseYamlFrontMatter(
content,
options.throwOnYamlError
);
if (options.debug) {
console.log("[legal-markdown-processor] YAML parsed:", Object.keys(yamlMetadata));
console.log("[legal-markdown-processor] Sample metadata:", yamlMetadata);
}
const keyProcessingDisabled = options.noHeaders && options.noReferences && options.noMixins;
if (options.debug) {
console.log("[legal-markdown-processor] Processing flags:", {
noHeaders: options.noHeaders,
noReferences: options.noReferences,
noMixins: options.noMixins,
enableFieldTracking: options.enableFieldTracking,
keyProcessingDisabled
});
}
if (keyProcessingDisabled && !options.enableFieldTracking) {
if (options.debug) {
console.log(
"[legal-markdown-processor] Key processing disabled, returning original content"
);
}
return {
content: contentWithoutYaml,
metadata: { ...yamlMetadata, ...options.additionalMetadata },
stats: {
processingTime: Date.now() - startTime,
pluginsUsed: [],
crossReferencesFound: 0,
fieldsTracked: 0
},
warnings: []
};
}
if (options.yamlOnly) {
return {
content: contentWithoutYaml,
metadata: yamlMetadata,
stats: {
processingTime: Date.now() - startTime,
pluginsUsed: [],
crossReferencesFound: 0,
fieldsTracked: 0
},
warnings: []
};
}
const combinedMetadata = {
...yamlMetadata,
...options.additionalMetadata
};
if (options.debug) {
console.log(
"[legal-markdown-processor] Combined metadata keys:",
Object.keys(combinedMetadata)
);
console.log("[legal-markdown-processor] Combined metadata sample:", combinedMetadata);
console.log(
"[legal-markdown-processor] Services structure:",
JSON.stringify(combinedMetadata.services, null, 2)
);
console.log(
"[legal-markdown-processor] Milestones structure:",
JSON.stringify(combinedMetadata.milestones, null, 2)
);
}
let preprocessedContent = contentWithoutYaml;
const fieldMappings = /* @__PURE__ */ new Map();
if (options.debug) {
console.log("[legal-markdown-processor] About to check for pre-processing...");
}
if (options.fieldPatterns && options.fieldPatterns.length > 0) {
for (const pattern of options.fieldPatterns) {
if (pattern !== "{{(.+?)}}") {
const regex = new RegExp(pattern, "g");
preprocessedContent = preprocessedContent.replace(regex, (match, fieldName) => {
const normalizedPattern = `{{${fieldName}}}`;
fieldMappings.set(normalizedPattern, match);
return normalizedPattern;
});
}
}
}
if (!options.noClauses) {
if (options.debug) {
console.log("[legal-markdown-processor] Pre-processing template loops...");
}
preprocessedContent = processTemplateLoops(
preprocessedContent,
combinedMetadata,
void 0,
// context
options.enableFieldTracking || false
);
if (options.debug) {
console.log(
"[legal-markdown-processor] Content after processTemplateLoops (first 800 chars):"
);
console.log(preprocessedContent.substring(0, 800));
}
if (options.debug) {
console.log("[legal-markdown-processor] Template loops processed");
if (preprocessedContent.includes("{{#")) {
console.log(
"[legal-markdown-processor] WARNING: Content still contains {{# patterns after loop processing!"
);
}
}
}
combinedMetadata["_field_mappings"] = fieldMappings;
const processingOptions = {
...options,
fieldPatterns: options.fieldPatterns && options.fieldPatterns.length > 0 ? ["{{(.+?)}}", ...options.fieldPatterns] : void 0
};
const processor = createLegalMarkdownProcessor(combinedMetadata, processingOptions);
pluginsUsed.push("remarkTemplateFields");
if (!options.disableCrossReferences && !options.noReferences) {
pluginsUsed.push("remarkCrossReferences");
}
if (!options.disableFieldTracking && options.enableFieldTracking) {
pluginsUsed.push("remarkFieldTracking");
}
if (options.debug) {
console.log(`π Using plugins: ${pluginsUsed.join(", ")}`);
}
const result = await processor.process(preprocessedContent);
const processedContent = String(result);
const processedTree = result.history[0];
const importedMetadata = processedTree?._importedMetadata || {};
const finalMetadata = {
...importedMetadata,
...combinedMetadata
};
if (options.debug && Object.keys(importedMetadata).length > 0) {
console.log(
`π¦ Merged ${Object.keys(importedMetadata).length} imported metadata fields:`,
Object.keys(importedMetadata)
);
}
const crossReferencesFound = combinedMetadata["_cross_references"]?.length || 0;
const fieldsTracked = options.enableFieldTracking ? fieldTracker.getTotalOccurrences() : 0;
let fieldReport;
if (options.enableFieldTracking) {
const fields = fieldTracker.getFields();
fieldReport = {
totalFields: fieldsTracked,
uniqueFields: fields.size,
fields
};
}
let exportedFiles = [];
if (options.exportMetadata) {
try {
const exportResult = exportMetadata(
finalMetadata,
options.exportFormat,
options.exportPath
);
exportedFiles = exportResult.exportedFiles;
if (options.debug) {
console.log(`π Exported metadata files: ${exportedFiles.join(", ")}`);
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
warnings.push(`Failed to export metadata: ${errorMessage}`);
if (options.debug) {
console.warn("β οΈ Metadata export failed:", error);
}
}
}
if (options.debug) {
console.log(`β
Processing completed in ${Date.now() - startTime}ms`);
console.log(`π Cross-references found: ${crossReferencesFound}`);
console.log(`π Fields tracked: ${fieldsTracked}`);
}
return {
content: processedContent,
metadata: finalMetadata,
exportedFiles: exportedFiles.length > 0 ? exportedFiles : void 0,
fieldReport,
stats: {
processingTime: Date.now() - startTime,
pluginsUsed,
crossReferencesFound,
fieldsTracked
},
warnings
};
} catch (error) {
if (options.debug) {
console.error("β Legal Markdown processing failed:", error);
}
const enhancedError = new Error(
`Legal Markdown processing failed: ${error instanceof Error ? error.message : String(error)}`
);
enhancedError.originalError = error;
throw enhancedError;
}
}
async function processLegalMarkdown(content, options = {}) {
return processLegalMarkdownWithRemark(content, options);
}
function processLegalMarkdownSync() {
throw new Error(
"Synchronous processing is no longer supported in the browser version. Please use processLegalMarkdown() which returns a Promise:\n\nconst result = await processLegalMarkdown(content, options);\n\nThis change enables full feature parity with the CLI version."
);
}
const LegalMarkdown = {
processLegalMarkdown,
processLegalMarkdownSync,
// Also export with explicit names for clarity
processLegalMarkdownWithRemark,
process: processLegalMarkdown,
// Version info
version: "2.16.3",
isModernBundle: true
};
if (typeof window !== "undefined" && window.DEBUG_LEGAL_MARKDOWN) {
console.log("[Legal Markdown] Modern browser bundle loaded", {
version: LegalMarkdown.version,
features: [
"remark AST processing",
"legal headers (l., ll., lll.)",
"template fields ({{variable}})",
"template loops ({{#array}}...{{/array}})",
"conditional blocks ({{#if}}...{{/if}})",
"bracket conditionals ([text]{condition})",
"date helpers (@today)",
"cross-references",
"mixins",
"field tracking"
]
});
}
export { LegalMarkdown as default, processLegalMarkdown, processLegalMarkdownSync };
//# sourceMappingURL=legal-markdown-browser.js.map