conditional-tag
Version:
Clean, easily readable conditional statements in template literals/strings for Node.js and browsers. Provides `if` / `elseif` / `else` and `switch` / `case` / `default` syntax options.
1,460 lines (1,248 loc) • 124 kB
JavaScript
const condTrue = Symbol('condTrue');
const condFalse = Symbol('condFalse');
function _if(condition) {
return {
func: 'if',
cond: (condition) ? condTrue : condFalse
};
}
function _elseif(condition) {
return {
func: 'elseif',
cond: (condition) ? condTrue : condFalse
};
}
const _else = Symbol('else');
const _endif = Symbol('endif');
/**
* Base parser class with common functionality.
*
* @since 1.2.0
*/
class Parser {
status = [];
depth = -1;
getStatus() {
return this.status[this.depth];
}
setStatus(statusObjOrName, value) {
if (typeof statusObjOrName === 'string' && typeof value !== undefined) {
this.status[this.depth][statusObjOrName] = value;
}
else {
this.status[this.depth] = statusObjOrName;
}
return this.status[this.depth];
}
pushStatus(statusObj) {
this.depth++;
this.status.push(statusObj);
return this.status.at(-1);
}
popStatus() {
this.depth--;
return this.status.pop();
}
/* c8 ignore next 7 */
canHandle() {
throw new Error('canHandle() not implemented.');
}
handle() {
throw new Error('handle() not implemented.');
}
}
class ConditionalTagSyntaxError extends Error {}
function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
var browser = {exports: {}};
/**
* Helpers.
*/
var ms;
var hasRequiredMs;
function requireMs () {
if (hasRequiredMs) return ms;
hasRequiredMs = 1;
var s = 1000;
var m = s * 60;
var h = m * 60;
var d = h * 24;
var w = d * 7;
var y = d * 365.25;
/**
* Parse or format the given `val`.
*
* Options:
*
* - `long` verbose formatting [false]
*
* @param {String|Number} val
* @param {Object} [options]
* @throws {Error} throw an error if val is not a non-empty string or a number
* @return {String|Number}
* @api public
*/
ms = function (val, options) {
options = options || {};
var type = typeof val;
if (type === 'string' && val.length > 0) {
return parse(val);
} else if (type === 'number' && isFinite(val)) {
return options.long ? fmtLong(val) : fmtShort(val);
}
throw new Error(
'val is not a non-empty string or a valid number. val=' +
JSON.stringify(val)
);
};
/**
* Parse the given `str` and return milliseconds.
*
* @param {String} str
* @return {Number}
* @api private
*/
function parse(str) {
str = String(str);
if (str.length > 100) {
return;
}
var match = /^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(
str
);
if (!match) {
return;
}
var n = parseFloat(match[1]);
var type = (match[2] || 'ms').toLowerCase();
switch (type) {
case 'years':
case 'year':
case 'yrs':
case 'yr':
case 'y':
return n * y;
case 'weeks':
case 'week':
case 'w':
return n * w;
case 'days':
case 'day':
case 'd':
return n * d;
case 'hours':
case 'hour':
case 'hrs':
case 'hr':
case 'h':
return n * h;
case 'minutes':
case 'minute':
case 'mins':
case 'min':
case 'm':
return n * m;
case 'seconds':
case 'second':
case 'secs':
case 'sec':
case 's':
return n * s;
case 'milliseconds':
case 'millisecond':
case 'msecs':
case 'msec':
case 'ms':
return n;
default:
return undefined;
}
}
/**
* Short format for `ms`.
*
* @param {Number} ms
* @return {String}
* @api private
*/
function fmtShort(ms) {
var msAbs = Math.abs(ms);
if (msAbs >= d) {
return Math.round(ms / d) + 'd';
}
if (msAbs >= h) {
return Math.round(ms / h) + 'h';
}
if (msAbs >= m) {
return Math.round(ms / m) + 'm';
}
if (msAbs >= s) {
return Math.round(ms / s) + 's';
}
return ms + 'ms';
}
/**
* Long format for `ms`.
*
* @param {Number} ms
* @return {String}
* @api private
*/
function fmtLong(ms) {
var msAbs = Math.abs(ms);
if (msAbs >= d) {
return plural(ms, msAbs, d, 'day');
}
if (msAbs >= h) {
return plural(ms, msAbs, h, 'hour');
}
if (msAbs >= m) {
return plural(ms, msAbs, m, 'minute');
}
if (msAbs >= s) {
return plural(ms, msAbs, s, 'second');
}
return ms + ' ms';
}
/**
* Pluralization helper.
*/
function plural(ms, msAbs, n, name) {
var isPlural = msAbs >= n * 1.5;
return Math.round(ms / n) + ' ' + name + (isPlural ? 's' : '');
}
return ms;
}
var common;
var hasRequiredCommon;
function requireCommon () {
if (hasRequiredCommon) return common;
hasRequiredCommon = 1;
/**
* This is the common logic for both the Node.js and web browser
* implementations of `debug()`.
*/
function setup(env) {
createDebug.debug = createDebug;
createDebug.default = createDebug;
createDebug.coerce = coerce;
createDebug.disable = disable;
createDebug.enable = enable;
createDebug.enabled = enabled;
createDebug.humanize = requireMs();
createDebug.destroy = destroy;
Object.keys(env).forEach(key => {
createDebug[key] = env[key];
});
/**
* The currently active debug mode names, and names to skip.
*/
createDebug.names = [];
createDebug.skips = [];
/**
* Map of special "%n" handling functions, for the debug "format" argument.
*
* Valid key names are a single, lower or upper-case letter, i.e. "n" and "N".
*/
createDebug.formatters = {};
/**
* Selects a color for a debug namespace
* @param {String} namespace The namespace string for the debug instance to be colored
* @return {Number|String} An ANSI color code for the given namespace
* @api private
*/
function selectColor(namespace) {
let hash = 0;
for (let i = 0; i < namespace.length; i++) {
hash = ((hash << 5) - hash) + namespace.charCodeAt(i);
hash |= 0; // Convert to 32bit integer
}
return createDebug.colors[Math.abs(hash) % createDebug.colors.length];
}
createDebug.selectColor = selectColor;
/**
* Create a debugger with the given `namespace`.
*
* @param {String} namespace
* @return {Function}
* @api public
*/
function createDebug(namespace) {
let prevTime;
let enableOverride = null;
let namespacesCache;
let enabledCache;
function debug(...args) {
// Disabled?
if (!debug.enabled) {
return;
}
const self = debug;
// Set `diff` timestamp
const curr = Number(new Date());
const ms = curr - (prevTime || curr);
self.diff = ms;
self.prev = prevTime;
self.curr = curr;
prevTime = curr;
args[0] = createDebug.coerce(args[0]);
if (typeof args[0] !== 'string') {
// Anything else let's inspect with %O
args.unshift('%O');
}
// Apply any `formatters` transformations
let index = 0;
args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => {
// If we encounter an escaped % then don't increase the array index
if (match === '%%') {
return '%';
}
index++;
const formatter = createDebug.formatters[format];
if (typeof formatter === 'function') {
const val = args[index];
match = formatter.call(self, val);
// Now we need to remove `args[index]` since it's inlined in the `format`
args.splice(index, 1);
index--;
}
return match;
});
// Apply env-specific formatting (colors, etc.)
createDebug.formatArgs.call(self, args);
const logFn = self.log || createDebug.log;
logFn.apply(self, args);
}
debug.namespace = namespace;
debug.useColors = createDebug.useColors();
debug.color = createDebug.selectColor(namespace);
debug.extend = extend;
debug.destroy = createDebug.destroy; // XXX Temporary. Will be removed in the next major release.
Object.defineProperty(debug, 'enabled', {
enumerable: true,
configurable: false,
get: () => {
if (enableOverride !== null) {
return enableOverride;
}
if (namespacesCache !== createDebug.namespaces) {
namespacesCache = createDebug.namespaces;
enabledCache = createDebug.enabled(namespace);
}
return enabledCache;
},
set: v => {
enableOverride = v;
}
});
// Env-specific initialization logic for debug instances
if (typeof createDebug.init === 'function') {
createDebug.init(debug);
}
return debug;
}
function extend(namespace, delimiter) {
const newDebug = createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace);
newDebug.log = this.log;
return newDebug;
}
/**
* Enables a debug mode by namespaces. This can include modes
* separated by a colon and wildcards.
*
* @param {String} namespaces
* @api public
*/
function enable(namespaces) {
createDebug.save(namespaces);
createDebug.namespaces = namespaces;
createDebug.names = [];
createDebug.skips = [];
const split = (typeof namespaces === 'string' ? namespaces : '')
.trim()
.replace(' ', ',')
.split(',')
.filter(Boolean);
for (const ns of split) {
if (ns[0] === '-') {
createDebug.skips.push(ns.slice(1));
} else {
createDebug.names.push(ns);
}
}
}
/**
* Checks if the given string matches a namespace template, honoring
* asterisks as wildcards.
*
* @param {String} search
* @param {String} template
* @return {Boolean}
*/
function matchesTemplate(search, template) {
let searchIndex = 0;
let templateIndex = 0;
let starIndex = -1;
let matchIndex = 0;
while (searchIndex < search.length) {
if (templateIndex < template.length && (template[templateIndex] === search[searchIndex] || template[templateIndex] === '*')) {
// Match character or proceed with wildcard
if (template[templateIndex] === '*') {
starIndex = templateIndex;
matchIndex = searchIndex;
templateIndex++; // Skip the '*'
} else {
searchIndex++;
templateIndex++;
}
} else if (starIndex !== -1) { // eslint-disable-line no-negated-condition
// Backtrack to the last '*' and try to match more characters
templateIndex = starIndex + 1;
matchIndex++;
searchIndex = matchIndex;
} else {
return false; // No match
}
}
// Handle trailing '*' in template
while (templateIndex < template.length && template[templateIndex] === '*') {
templateIndex++;
}
return templateIndex === template.length;
}
/**
* Disable debug output.
*
* @return {String} namespaces
* @api public
*/
function disable() {
const namespaces = [
...createDebug.names,
...createDebug.skips.map(namespace => '-' + namespace)
].join(',');
createDebug.enable('');
return namespaces;
}
/**
* Returns true if the given mode name is enabled, false otherwise.
*
* @param {String} name
* @return {Boolean}
* @api public
*/
function enabled(name) {
for (const skip of createDebug.skips) {
if (matchesTemplate(name, skip)) {
return false;
}
}
for (const ns of createDebug.names) {
if (matchesTemplate(name, ns)) {
return true;
}
}
return false;
}
/**
* Coerce `val`.
*
* @param {Mixed} val
* @return {Mixed}
* @api private
*/
function coerce(val) {
if (val instanceof Error) {
return val.stack || val.message;
}
return val;
}
/**
* XXX DO NOT USE. This is a temporary stub function.
* XXX It WILL be removed in the next major release.
*/
function destroy() {
console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.');
}
createDebug.enable(createDebug.load());
return createDebug;
}
common = setup;
return common;
}
/* eslint-env browser */
var hasRequiredBrowser;
function requireBrowser () {
if (hasRequiredBrowser) return browser.exports;
hasRequiredBrowser = 1;
(function (module, exports) {
/**
* This is the web browser implementation of `debug()`.
*/
exports.formatArgs = formatArgs;
exports.save = save;
exports.load = load;
exports.useColors = useColors;
exports.storage = localstorage();
exports.destroy = (() => {
let warned = false;
return () => {
if (!warned) {
warned = true;
console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.');
}
};
})();
/**
* Colors.
*/
exports.colors = [
'#0000CC',
'#0000FF',
'#0033CC',
'#0033FF',
'#0066CC',
'#0066FF',
'#0099CC',
'#0099FF',
'#00CC00',
'#00CC33',
'#00CC66',
'#00CC99',
'#00CCCC',
'#00CCFF',
'#3300CC',
'#3300FF',
'#3333CC',
'#3333FF',
'#3366CC',
'#3366FF',
'#3399CC',
'#3399FF',
'#33CC00',
'#33CC33',
'#33CC66',
'#33CC99',
'#33CCCC',
'#33CCFF',
'#6600CC',
'#6600FF',
'#6633CC',
'#6633FF',
'#66CC00',
'#66CC33',
'#9900CC',
'#9900FF',
'#9933CC',
'#9933FF',
'#99CC00',
'#99CC33',
'#CC0000',
'#CC0033',
'#CC0066',
'#CC0099',
'#CC00CC',
'#CC00FF',
'#CC3300',
'#CC3333',
'#CC3366',
'#CC3399',
'#CC33CC',
'#CC33FF',
'#CC6600',
'#CC6633',
'#CC9900',
'#CC9933',
'#CCCC00',
'#CCCC33',
'#FF0000',
'#FF0033',
'#FF0066',
'#FF0099',
'#FF00CC',
'#FF00FF',
'#FF3300',
'#FF3333',
'#FF3366',
'#FF3399',
'#FF33CC',
'#FF33FF',
'#FF6600',
'#FF6633',
'#FF9900',
'#FF9933',
'#FFCC00',
'#FFCC33'
];
/**
* Currently only WebKit-based Web Inspectors, Firefox >= v31,
* and the Firebug extension (any Firefox version) are known
* to support "%c" CSS customizations.
*
* TODO: add a `localStorage` variable to explicitly enable/disable colors
*/
// eslint-disable-next-line complexity
function useColors() {
// NB: In an Electron preload script, document will be defined but not fully
// initialized. Since we know we're in Chrome, we'll just detect this case
// explicitly
if (typeof window !== 'undefined' && window.process && (window.process.type === 'renderer' || window.process.__nwjs)) {
return true;
}
// Internet Explorer and Edge do not support colors.
if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) {
return false;
}
let m;
// Is webkit? http://stackoverflow.com/a/16459606/376773
// document is undefined in react-native: https://github.com/facebook/react-native/pull/1632
// eslint-disable-next-line no-return-assign
return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) ||
// Is firebug? http://stackoverflow.com/a/398120/376773
(typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) ||
// Is firefox >= v31?
// https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
(typeof navigator !== 'undefined' && navigator.userAgent && (m = navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)) && parseInt(m[1], 10) >= 31) ||
// Double check webkit in userAgent just in case we are in a worker
(typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/));
}
/**
* Colorize log arguments if enabled.
*
* @api public
*/
function formatArgs(args) {
args[0] = (this.useColors ? '%c' : '') +
this.namespace +
(this.useColors ? ' %c' : ' ') +
args[0] +
(this.useColors ? '%c ' : ' ') +
'+' + module.exports.humanize(this.diff);
if (!this.useColors) {
return;
}
const c = 'color: ' + this.color;
args.splice(1, 0, c, 'color: inherit');
// The final "%c" is somewhat tricky, because there could be other
// arguments passed either before or after the %c, so we need to
// figure out the correct index to insert the CSS into
let index = 0;
let lastC = 0;
args[0].replace(/%[a-zA-Z%]/g, match => {
if (match === '%%') {
return;
}
index++;
if (match === '%c') {
// We only are interested in the *last* %c
// (the user may have provided their own)
lastC = index;
}
});
args.splice(lastC, 0, c);
}
/**
* Invokes `console.debug()` when available.
* No-op when `console.debug` is not a "function".
* If `console.debug` is not available, falls back
* to `console.log`.
*
* @api public
*/
exports.log = console.debug || console.log || (() => {});
/**
* Save `namespaces`.
*
* @param {String} namespaces
* @api private
*/
function save(namespaces) {
try {
if (namespaces) {
exports.storage.setItem('debug', namespaces);
} else {
exports.storage.removeItem('debug');
}
} catch (error) {
// Swallow
// XXX (@Qix-) should we be logging these?
}
}
/**
* Load `namespaces`.
*
* @return {String} returns the previously persisted debug modes
* @api private
*/
function load() {
let r;
try {
r = exports.storage.getItem('debug');
} catch (error) {
// Swallow
// XXX (@Qix-) should we be logging these?
}
// If debug isn't set in LS, and we're in Electron, try to load $DEBUG
if (!r && typeof process !== 'undefined' && 'env' in process) {
r = process.env.DEBUG;
}
return r;
}
/**
* Localstorage attempts to return the localstorage.
*
* This is necessary because safari throws
* when a user disables cookies/localstorage
* and you attempt to access it.
*
* @return {LocalStorage}
* @api private
*/
function localstorage() {
try {
// TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context
// The Browser also has localStorage in the global context.
return localStorage;
} catch (error) {
// Swallow
// XXX (@Qix-) should we be logging these?
}
}
module.exports = requireCommon()(exports);
const {formatters} = module.exports;
/**
* Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.
*/
formatters.j = function (v) {
try {
return JSON.stringify(v);
} catch (error) {
return '[UnexpectedJSONParseError]: ' + error.message;
}
};
} (browser, browser.exports));
return browser.exports;
}
var browserExports = requireBrowser();
var makeDebug = /*@__PURE__*/getDefaultExportFromCjs(browserExports);
const debug$4 = makeDebug('condtag:if');
/**
* Parser class for if syntax expressions.
*
* @since 1.0
*/
class IfSyntaxParser extends Parser {
static steps = {
IGNORED: 0,
IF: 1,
ELSEIF: 2,
ELSE: 3
};
#isIf(expr) {
return (expr?.func === 'if' && (this.#isCondTrue(expr?.cond) || this.#isCondFalse(expr?.cond)));
}
#isElseIf(expr) {
return (expr?.func === 'elseif' && (this.#isCondTrue(expr?.cond) || this.#isCondFalse(expr?.cond)));
}
#isElse(expr) {
return (expr === _else);
}
#isEndIf(expr) {
return (expr === _endif);
}
#isCondTrue(expr) {
return (expr === condTrue);
}
#isCondFalse(expr) {
return (expr === condFalse);
}
/**
* Checks if the template literal expression is an _if syntax expression.
*
* @param {object|string} expr - The template literal expression.
* @returns {boolean} True if it is. False otherwise.
* @since 1.0
*/
canHandle(expr) {
return (
this.#isIf(expr) ||
this.#isElseIf(expr) ||
this.#isElse(expr) ||
this.#isEndIf(expr)
);
}
/**
* Handles the _if syntax expression.
*
* @param {object|string} expr - The _if syntax expression.
* @param {object} filterStatus - The status of the filter of the calling tag function.
* @returns {boolean} Always false.
* @since 1.0
*/
handle(expr, filterStatus) {
let s = this.getStatus();
// Check if the last conditional-tag was an _always tag and restore
// original filter status.
// @TODO Move to tag function to avoid repetition?
if('beforeAlways' in filterStatus) {
debug$4(`beforeAlways (${filterStatus.beforeAlways}) encountered. Setting filter status.`);
filterStatus.filterOut = filterStatus.beforeAlways;
delete filterStatus.beforeAlways;
}
// Nested _if in unrendered block: Whole if-block at this depth gets ignored.
if (s !== undefined && s.step === IfSyntaxParser.steps.IGNORED && !this.#isEndIf(expr)) {
debug$4('<if-syntax> ignored due to unrendered block.');
}
else if (this.#isIf(expr)) {
if (filterStatus.filterOut === true) {
this.pushStatus({
anyCondMet: null,
step: IfSyntaxParser.steps.IGNORED,
parentFilter: filterStatus.filterOut
});
debug$4('<if> found in unrendered block. Ignoring this if-block.');
return false;
}
s = this.pushStatus({
anyCondMet: this.#isCondTrue(expr.cond),
step: IfSyntaxParser.steps.IF,
parentFilter: filterStatus.filterOut
});
filterStatus.filterOut = !s.anyCondMet;
debug$4(`<if> found. New depth: ${this.depth}. Conditions ${s.anyCondMet ? '' : 'NOT '}met.`);
}
else if (this.#isElseIf(expr)) {
if (s === undefined) {
throw new ConditionalTagSyntaxError('_elseif() must be inside if-block.');
}
if (s.step > IfSyntaxParser.steps.ELSEIF) {
throw new ConditionalTagSyntaxError('_elseif() must not occur after _else.');
}
s.step = IfSyntaxParser.steps.ELSEIF;
if (!s.anyCondMet) {
this.setStatus('anyCondMet', this.#isCondTrue(expr.cond));
filterStatus.filterOut = !s.anyCondMet;
debug$4(`<elseif> found. Depth: ${this.depth}. Conditions ${s.anyCondMet ? '' : 'NOT '}met.`);
}
else {
filterStatus.filterOut = true;
debug$4(`<elseif> found. Depth: ${this.depth}. Conditions ignored.`);
}
}
else if (this.#isElse(expr)) {
if (s === undefined) {
throw new ConditionalTagSyntaxError('_else must be inside if-block.');
}
if (s.step > IfSyntaxParser.steps.ELSEIF) {
throw new ConditionalTagSyntaxError('Only one _else permitted per if-block.');
}
s.step = IfSyntaxParser.steps.ELSE;
// No previous conditions have been met.
if (!s.anyCondMet) {
this.setStatus('anyCondMet', true);
filterStatus.filterOut = false;
debug$4(`<else> found. Depth: ${this.depth}. Conditions met.`);
}
// A previous condition has been met, so we filter else out.
else {
filterStatus.filterOut = true;
debug$4(`<else> found. Depth: ${this.depth}. Conditions ignored.`);
}
}
// Reset everything.
else if (this.#isEndIf(expr)) {
if (s === undefined) {
throw new ConditionalTagSyntaxError('_endif must be inside if-block.');
}
const oldS = this.popStatus();
filterStatus.filterOut = oldS.parentFilter;
debug$4(`<endif> found. Depth now: ${this.depth}.`);
}
return false;
}
}
const funcSwitch = Symbol('switch');
const funcCase = Symbol('case');
const funcSwitchCase = Symbol('switchCase');
function _switch(switchVar) {
return {
func: funcSwitch,
switchVar,
_case: chainedCase(switchVar)
};
}
function _case(...caseVars) {
return {
func: funcCase,
caseVars
};
}
const _default = Symbol('default');
const _endswitch = Symbol('endswitch');
function chainedCase(switchVar) {
return function(...caseVars) {
return {
func: funcSwitchCase,
switchVar,
caseVars
}
}
}
const debug$3 = makeDebug('condtag:switch');
/**
* Parser class for switch syntax expressions.
*
* @since 1.0
*/
class SwitchSyntaxParser extends Parser {
#isSwitch(expr) {
return (expr?.func === funcSwitch);
}
#isCase(expr) {
return (expr?.func === funcCase);
}
#isSwitchCase(expr) {
return (expr?.func === funcSwitchCase);
}
#isDefault(expr) {
return (expr === _default);
}
#isEndSwitch(expr) {
return (expr === _endswitch);
}
/**
* Checks if the template literal expression is a _switch syntax expression.
*
* @param {object|string} expr - The template literal expression.
* @returns {boolean} True if it is. False otherwise.
* @since 1.0
*/
canHandle(expr) {
return (
this.#isSwitch(expr) ||
this.#isCase(expr) ||
this.#isSwitchCase(expr) ||
this.#isDefault(expr) ||
this.#isEndSwitch(expr)
);
}
/**
* Handles the _switch syntax expression.
*
* @param {object|string} expr - The _switch syntax expression.
* @param {object} filterStatus - The status of the filter of the calling tag function.
* @returns {boolean} Always false.
* @since 1.0
*/
handle(expr, filterStatus) {
let s = this.getStatus();
// Check if the last conditional-tag was an _always tag and restore
// original filter status.
// @TODO Move to tag function to avoid repetition?
if('beforeAlways' in filterStatus) {
debug$3(`beforeAlways (${filterStatus.beforeAlways}) encountered. Setting filter status.`);
filterStatus.filterOut = filterStatus.beforeAlways;
delete filterStatus.beforeAlways;
}
// Nested _switch in unrendered block: Whole switch-block at this depth gets ignored.
if (s !== undefined && s.ignored === true && !this.#isEndSwitch(expr)) {
debug$3('<switch-syntax> ignored due to unrendered block.');
return false;
}
if (this.#isSwitch(expr) || this.#isSwitchCase(expr)) {
// No _case has been handled yet.
if (s !== undefined && !s?.case) {
throw new ConditionalTagSyntaxError('_switch() can only be nested inside other _case () blocks.');
}
if (filterStatus.filterOut === true) {
this.pushStatus({
ignored: true,
parentFilter: filterStatus.filterOut
});
debug$3('<switch> found in unrendered block. Ignoring this switch-block.');
return false;
}
s = this.pushStatus({
switchVar: expr.switchVar,
parentFilter: filterStatus.filterOut
});
if (this.#isSwitchCase(expr)) {
debug$3(`<switch.case> found. New depth: ${this.depth}. Setting switchVar.`);
expr.func = funcCase;
}
else {
debug$3(`<switch> found. New depth: ${this.depth}. Setting switchVar.`);
}
}
if (this.#isCase(expr)) {
if (s === undefined) {
throw new ConditionalTagSyntaxError('_case() must be inside switch-block.');
}
// At least one _case has been handled at this depth.
s.case = true;
if (expr.caseVars.some(caseVar => (caseVar === s.switchVar))) {
this.setStatus('anyCondMet', true);
filterStatus.filterOut = false;
debug$3(`<case> found. Depth: ${this.depth}. Condition met.`);
}
else {
filterStatus.filterOut = true;
debug$3(`<case> found. Depth: ${this.depth}. Condition NOT met.`);
}
}
else if (this.#isDefault(expr)) {
if (s === undefined) {
throw new ConditionalTagSyntaxError('_default must be inside switch-block.');
}
if (!s?.case) {
throw new ConditionalTagSyntaxError('_default must be preceded by at least one _case.');
}
filterStatus.filterOut = s.anyCondMet;
debug$3(`<default> Depth: ${this.depth}. found. Conditions ${s.anyCondMet === true ? 'ignored' : 'met'}.`);
}
else if (this.#isEndSwitch(expr)) {
if (s === undefined) {
throw new ConditionalTagSyntaxError('_endswitch must be inside switch-block');
}
// filterStatus.filterOut = false;
// this.#reset();
const oldS = this.popStatus();
filterStatus.filterOut = oldS.parentFilter;
debug$3(`<endswitch> found. Depth now: ${this.depth}.`);
}
return false;
}
}
const _always = Symbol('always');
const debug$2 = makeDebug('condtag:always');
/**
* Parser class for the _always expression.
*
* @since 1.0
*/
class AlwaysSyntaxParser {
canHandle(expr) {
return (expr === _always);
}
handle(expr, status) {
status.beforeAlways = status.filterOut;
status.filterOut = false;
debug$2(`<always> found. %o`, status);
return false;
}
}
/**
* Checks if func is an arrow function.
* This is a simple test to discern the most common cases.
* I'm sure it produces false positives if one puts their mind to it.
*
* @param {function} func - The function to test.
* @returns {boolean} true if func is an arrow function. false otherwise.
*/
function isArrowFunction(func) {
return (typeof func === 'function'
&& typeof func.prototype === 'undefined'
&& func.name === '');
}
const debug$1 = makeDebug('condtag:function');
/**
* Parser class for arrow function expressions.
* This mechanism is used to prevent unneccesary function
* calls in unrendered blocks.
*
* @since 1.1
*/
class FunctionSyntaxParser {
canHandle(expr) {
return isArrowFunction(expr);
}
handle(expr, status) {
if (!status.filterOut) {
debug$1('<function> found. Evaluating.');
// expr.toString = function() {
// return this();
// }
expr[Symbol.toPrimitive] = function() {
debug$1('toPrimitive()');
return this();
};
}
else {
debug$1('<function> found. Skipping.');
}
}
}
const debug = makeDebug('condtag:tag');
const
beforeRgx = /(^|\r?\n)[^\S\r\n]*$/,
afterRgx = /^[^\S\r\n]*(\r?\n|$)/,
nlRgx = /^[\r\n]+$/,
isNl = (...arr) => arr.every(str => nlRgx.test(str));
/**
* Interleaves strings and expressions of a template literal into one array [str[0], val[0], str[1], val[1], …].
* In template literals, an expression is always surrounded by strings, even if they are empty.
* Therefore, strings.length is always expressions.length + 1.
*
* @param {Array} strings - The strings.
* @param {Array} expressions - The expressions.
* @returns {Array} The interleaved array of both strings and expressions.
* @todo Benchmarks. Maybe there's a faster/more efficient way?
* @since 1.1
*/
function interleave(strings, expressions) {
return strings.flatMap((str, idx) => idx < expressions.length ? [str, expressions[idx]] : str);
}
/**
* Result of the `parse()` function.
*
* @typedef {Object} ParsedResult
* @property {Array} output - Array of parsed and filtered strings.
* @property {Array} handled - Array of indices where condition-tag expressions were encountered in the output array.
*/
/**
* Parses an array of interleaved template literal strings and expressions.
* Checks for conditional-tag expressions and filters out items where conditions are not met.
*
* @param {Array} items - The array of interleaved items.
* @returns {ParsedResult} The result.
* @since 1.1
*/
function parse(items) {
const
ifParser = new IfSyntaxParser(),
switchParser = new SwitchSyntaxParser(),
alwaysParser = new AlwaysSyntaxParser(),
functionParser = new FunctionSyntaxParser(),
status = {
filterOut: false
},
handled = new Set();
let index = 0;
const output = items.filter((item, idx) => {
debug('Looking at item %d.', idx);
if (typeof item !== 'string') {
if (ifParser.canHandle(item)) {
handled.add(index);
return ifParser.handle(item, status);
}
else if (switchParser.canHandle(item)) {
handled.add(index);
return switchParser.handle(item, status);
}
else if (alwaysParser.canHandle(item)) {
handled.add(index);
return alwaysParser.handle(item, status);
}
else if (functionParser.canHandle(item)) {
functionParser.handle(item, status);
}
/* c8 ignore next 3*/
else {
debug('Expression found.');
}
}
/* c8 ignore next 3*/
else {
debug('String found.');
}
// Filter out.
if (status.filterOut) {
debug('› Filterting.');
return false;
}
// Don't filter out.
debug('› Not filterting.');
index++;
return true;
});
return { output, handled }
}
/**
* Trims lines of a multi-line template literal in the case that a condition-tag expression
* was surrounded by only whitespace characters on that line.
*
* @param {Array} strings - Array of parsed strings.
* @param {Array} handled - Array of indices where condition-tag expressions were encountered in the strings array.
* @since 1.1
*/
function trim(strings, handled) {
// Potentially trim whitespace lines.
handled.forEach(afterIdx => {
const
beforeIdx = afterIdx - 1,
beforeMatch = beforeRgx.exec(strings[beforeIdx]),
afterMatch = afterRgx.exec(strings[afterIdx]);
// Only trim if conditions are met on both sides of the conditional-tag expression.
// If newlines occur on both sides, collapse them into one newline.
if (beforeMatch !== null && afterMatch !== null) {
strings[beforeIdx] = strings[beforeIdx].replace(beforeRgx, isNl(beforeMatch[1], afterMatch[1]) ? beforeMatch[1] : '');
strings[afterIdx] = strings[afterIdx].replace(afterRgx, '');
}
});
}
/**
* Tag function for templates literals/strings. Enables conditional logic
* within these strings.
*
* @param {Array} strings - The strings of the template literal.
* @param {Array} expressions - The expressions of the template literal.
* @returns {string} The rendered string.
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates}
* @since 1.0
*/
function _(strings, ...expressions) {
const items = interleave(strings, expressions);
debug('Parsing %O', items);
try {
const { output, handled } = parse(items);
trim(output, handled);
return output.join('');
}
catch(err) {
// Trying to be a bit more helpful.
if (err instanceof TypeError) {
throw new Error(`${err.message} (Maybe there's an async function in an expression? Use tag function _async with await.)`, { cause: err });
}
throw err;
}
}
/**
* Asynchronous tag function for templates literals/strings. Enables conditional logic
* within these strings.
*
* @param {Array} strings - The strings of the template literal.
* @param {Array} expressions - The expressions of the template literal.
* @returns {string} The rendered string.
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates}
* @since 1.1
*/
async function _async(strings, ...expressions) {
const items = interleave(strings, expressions);
debug('Parsing (async) %O', items);
const { output, handled } = parse(items);
const resolved = await Promise.all(output.map(item => {
if (isArrowFunction(item)) {
// In case of async functions, this will be a Promise.
return item[Symbol.toPrimitive]();
}
else {
return item;
}
}));
trim(resolved, handled);
return resolved.join('');
}
// For convenience:
_.if = _if;
_.async = _async;
_.elseif = _elseif;
_.else = _else;
_.endif = _endif;
_.switch = _switch;
_.case = _case;
_.default = _default;
_.endswitch = _endswitch;
_.always = _always;
export { ConditionalTagSyntaxError, _, _always, _async, _case, _default, _else, _elseif, _endif, _endswitch, _if, _switch };
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZGl0aW9uYWwtdGFnLmRlYnVnLmpzIiwic291cmNlcyI6WyIuLi9saWIvc3ludGF4LWlmLmpzIiwiLi4vbGliL3BhcnNlci5qcyIsIi4uL2xpYi9lcnJvci5qcyIsIi4uL25vZGVfbW9kdWxlcy9tcy9pbmRleC5qcyIsIi4uL25vZGVfbW9kdWxlcy9kZWJ1Zy9zcmMvY29tbW9uLmpzIiwiLi4vbm9kZV9tb2R1bGVzL2RlYnVnL3NyYy9icm93c2VyLmpzIiwiLi4vbGliL3BhcnNlci1pZi5qcyIsIi4uL2xpYi9zeW50YXgtc3dpdGNoLmpzIiwiLi4vbGliL3BhcnNlci1zd2l0Y2guanMiLCIuLi9saWIvc3ludGF4LWFsd2F5cy5qcyIsIi4uL2xpYi9wYXJzZXItYWx3YXlzLmpzIiwiLi4vbGliL2lzLWFycm93LWZ1bmMuanMiLCIuLi9saWIvcGFyc2VyLWZ1bmN0aW9uLmpzIiwiLi4vbGliL3RhZy5qcyJdLCJzb3VyY2VzQ29udGVudCI6WyJcbmNvbnN0IGNvbmRUcnVlID0gU3ltYm9sKCdjb25kVHJ1ZScpO1xuY29uc3QgY29uZEZhbHNlID0gU3ltYm9sKCdjb25kRmFsc2UnKTtcblxuZnVuY3Rpb24gX2lmKGNvbmRpdGlvbikge1xuXHRyZXR1cm4ge1xuXHRcdGZ1bmM6ICdpZicsXG5cdFx0Y29uZDogKGNvbmRpdGlvbikgPyBjb25kVHJ1ZSA6IGNvbmRGYWxzZVxuXHR9O1xufVxuXG5mdW5jdGlvbiBfZWxzZWlmKGNvbmRpdGlvbikge1xuXHRyZXR1cm4ge1xuXHRcdGZ1bmM6ICdlbHNlaWYnLFxuXHRcdGNvbmQ6IChjb25kaXRpb24pID8gY29uZFRydWUgOiBjb25kRmFsc2Vcblx0fTtcbn1cblxuY29uc3QgX2Vsc2UgPSBTeW1ib2woJ2Vsc2UnKTtcbmNvbnN0IF9lbmRpZiA9IFN5bWJvbCgnZW5kaWYnKTtcblxuZXhwb3J0IHtcblx0X2lmLFxuXHRfZWxzZWlmLFxuXHRfZWxzZSxcblx0X2VuZGlmLFxuXHRjb25kVHJ1ZSxcblx0Y29uZEZhbHNlXG59O1xuIiwiXG5cbi8qKlxuICogQmFzZSBwYXJzZXIgY2xhc3Mgd2l0aCBjb21tb24gZnVuY3Rpb25hbGl0eS5cbiAqXG4gKiBAc2luY2UgMS4yLjBcbiAqL1xuZXhwb3J0IGRlZmF1bHQgY2xhc3MgUGFyc2VyIHtcblx0c3RhdHVzID0gW107XG5cdGRlcHRoID0gLTE7XG5cblx0Z2V0U3RhdHVzKCkge1xuXHRcdHJldHVybiB0aGlzLnN0YXR1c1t0aGlzLmRlcHRoXTtcblx0fVxuXG5cdHNldFN0YXR1cyhzdGF0dXNPYmpPck5hbWUsIHZhbHVlKSB7XG5cdFx0aWYgKHR5cGVvZiBzdGF0dXNPYmpPck5hbWUgPT09ICdzdHJpbmcnICYmIHR5cGVvZiB2YWx1ZSAhPT0gdW5kZWZpbmVkKSB7XG5cdFx0XHR0aGlzLnN0YXR1c1t0aGlzLmRlcHRoXVtzdGF0dXNPYmpPck5hbWVdID0gdmFsdWU7XG5cdFx0fVxuXHRcdGVsc2Uge1xuXHRcdFx0dGhpcy5zdGF0dXNbdGhpcy5kZXB0aF0gPSBzdGF0dXNPYmpPck5hbWU7XHRcblx0XHR9XG5cblx0XHRyZXR1cm4gdGhpcy5zdGF0dXNbdGhpcy5kZXB0aF07XG5cdH1cblxuXHRwdXNoU3RhdHVzKHN0YXR1c09iaikge1xuXHRcdHRoaXMuZGVwdGgrKztcblx0XHR0aGlzLnN0YXR1cy5wdXNoKHN0YXR1c09iaik7XG5cblx0XHRyZXR1cm4gdGhpcy5zdGF0dXMuYXQoLTEpO1xuXHR9XG5cblx0cG9wU3RhdHVzKCkge1xuXHRcdHRoaXMuZGVwdGgtLTtcblxuXHRcdHJldHVybiB0aGlzLnN0YXR1cy5wb3AoKTtcblx0fVxuXG5cdC8qIGM4IGlnbm9yZSBuZXh0IDcgKi9cblx0Y2FuSGFuZGxlKCkge1xuXHRcdHRocm93IG5ldyBFcnJvcignY2FuSGFuZGxlKCkgbm90IGltcGxlbWVudGVkLicpO1xuXHR9XG5cblx0aGFuZGxlKCkge1xuXHRcdHRocm93IG5ldyBFcnJvcignaGFuZGxlKCkgbm90IGltcGxlbWVudGVkLicpO1xuXHR9XG59XG4iLCJcbmV4cG9ydCBjbGFzcyBDb25kaXRpb25hbFRhZ1N5bnRheEVycm9yIGV4dGVuZHMgRXJyb3Ige307XG4iLCIvKipcbiAqIEhlbHBlcnMuXG4gKi9cblxudmFyIHMgPSAxMDAwO1xudmFyIG0gPSBzICogNjA7XG52YXIgaCA9IG0gKiA2MDtcbnZhciBkID0gaCAqIDI0O1xudmFyIHcgPSBkICogNztcbnZhciB5ID0gZCAqIDM2NS4yNTtcblxuLyoqXG4gKiBQYXJzZSBvciBmb3JtYXQgdGhlIGdpdmVuIGB2YWxgLlxuICpcbiAqIE9wdGlvbnM6XG4gKlxuICogIC0gYGxvbmdgIHZlcmJvc2UgZm9ybWF0dGluZyBbZmFsc2VdXG4gKlxuICogQHBhcmFtIHtTdHJpbmd8TnVtYmVyfSB2YWxcbiAqIEBwYXJhbSB7T2JqZWN0fSBbb3B0aW9uc11cbiAqIEB0aHJvd3Mge0Vycm9yfSB0aHJvdyBhbiBlcnJvciBpZiB2YWwgaXMgbm90IGEgbm9uLWVtcHR5IHN0cmluZyBvciBhIG51bWJlclxuICogQHJldHVybiB7U3RyaW5nfE51bWJlcn1cbiAqIEBhcGkgcHVibGljXG4gKi9cblxubW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiAodmFsLCBvcHRpb25zKSB7XG4gIG9wdGlvbnMgPSBvcHRpb25zIHx8IHt9O1xuICB2YXIgdHlwZSA9IHR5cGVvZiB2YWw7XG4gIGlmICh0eXBlID09PSAnc3RyaW5nJyAmJiB2YWwubGVuZ3RoID4gMCkge1xuICAgIHJldHVybiBwYXJzZSh2YWwpO1xuICB9IGVsc2UgaWYgKHR5cGUgPT09ICdudW1iZXInICYmIGlzRmluaXRlKHZhbCkpIHtcbiAgICByZXR1cm4gb3B0aW9ucy5sb25nID8gZm10TG9uZyh2YWwpIDogZm10U2hvcnQodmFsKTtcbiAgfVxuICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgJ3ZhbCBpcyBub3QgYSBub24tZW1wdHkgc3RyaW5nIG9yIGEgdmFsaWQgbnVtYmVyLiB2YWw9JyArXG4gICAgICBKU09OLnN0cmluZ2lmeSh2YWwpXG4gICk7XG59O1xuXG4vKipcbiAqIFBhcnNlIHRoZSBnaXZlbiBgc3RyYCBhbmQgcmV0dXJuIG1pbGxpc2Vjb25kcy5cbiAqXG4gKiBAcGFyYW0ge1N0cmluZ30gc3RyXG4gKiBAcmV0dXJuIHtOdW1iZXJ9XG4gKiBAYXBpIHByaXZhdGVcbiAqL1xuXG5mdW5jdGlvbiBwYXJzZShzdHIpIHtcbiAgc3RyID0gU3RyaW5nKHN0cik7XG4gIGlmIChzdHIubGVuZ3RoID4gMTAwKSB7XG4gICAgcmV0dXJuO1xuICB9XG4gIHZhciBtYXRjaCA9IC9eKC0/KD86XFxkKyk/XFwuP1xcZCspICoobWlsbGlzZWNvbmRzP3xtc2Vjcz98bXN8c2Vjb25kcz98c2Vjcz98c3xtaW51dGVzP3xtaW5zP3xtfGhvdXJzP3xocnM/fGh8ZGF5cz98ZHx3ZWVrcz98d3x5ZWFycz98eXJzP3x5KT8kL2kuZXhlYyhcbiAgICBzdHJcbiAgKTtcbiAgaWYgKCFtYXRjaCkge1xuICAgIHJldHVybjtcbiAgfVxuICB2YXIgbiA9IHBhcnNlRmxvYXQobWF0Y2hbMV0pO1xuICB2YXIgdHlwZSA9IChtYXRjaFsyXSB8fCAnbXMnKS50b0xvd2VyQ2FzZSgpO1xuICBzd2l0Y2ggKHR5cGUpIHtcbiAgICBjYXNlICd5ZWFycyc6XG4gICAgY2FzZSAneWVhcic6XG4gICAgY2FzZSAneXJzJzpcbiAgICBjYXNlICd5cic6XG4gICAgY2FzZSAneSc6XG4gICAgICByZXR1cm4gbiAqIHk7XG4gICAgY2FzZSAnd2Vla3MnOlxuICAgIGNhc2UgJ3dlZWsnOlxuICAgIGNhc2UgJ3cnOlxuICAgICAgcmV0dXJuIG4gKiB3O1xuICAgIGNhc2UgJ2RheXMnOlxuICAgIGNhc2UgJ2RheSc6XG4gICAgY2FzZSAnZCc6XG4gICAgICByZXR1cm4gbiAqIGQ7XG4gICAgY2FzZSAnaG91cnMnOlxuICAgIGNhc2UgJ2hvdXInOlxuICAgIGNhc2UgJ2hycyc6XG4gICAgY2FzZSAnaHInOlxuICAgIGNhc2UgJ2gnOlxuICAgICAgcmV0dXJuIG4gKiBoO1xuICAgIGNhc2UgJ21pbnV0ZXMnOlxuICAgIGNhc2UgJ21pbnV0ZSc6XG4gICAgY2FzZSAnbWlucyc6XG4gICAgY2FzZSAnbWluJzpcbiAgICBjYXNlICdtJzpcbiAgICAgIHJldHVybiBuICogbTtcbiAgICBjYXNlICdzZWNvbmRzJzpcbiAgICBjYXNlICdzZWNvbmQnOlxuICAgIGNhc2UgJ3NlY3MnOlxuICAgIGNhc2UgJ3NlYyc6XG4gICAgY2FzZSAncyc6XG4gICAgICByZXR1cm4gbiAqIHM7XG4gICAgY2FzZSAnbWlsbGlzZWNvbmRzJzpcbiAgICBjYXNlICdtaWxsaXNlY29uZCc6XG4gICAgY2FzZSAnbXNlY3MnOlxuICAgIGNhc2UgJ21zZWMnOlxuICAgIGNhc2UgJ21zJzpcbiAgICAgIHJldHVybiBuO1xuICAgIGRlZmF1bHQ6XG4gICAgICByZXR1cm4gdW5kZWZpbmVkO1xuICB9XG59XG5cbi8qKlxuICogU2hvcnQgZm9ybWF0IGZvciBgbXNgLlxuICpcbiAqIEBwYXJhbSB7TnVtYmVyfSBtc1xuICogQHJldHVybiB7U3RyaW5nfVxuICogQGFwaSBwcml2YXRlXG4gKi9cblxuZnVuY3Rpb24gZm10U2hvcnQobXMpIHtcbiAgdmFyIG1zQWJzID0gTWF0aC5hYnMobXMpO1xuICBpZiAobXNBYnMgPj0gZCkge1xuICAgIHJldHVybiBNYXRoLnJvdW5kKG1zIC8gZCkgKyAnZCc7XG4gIH1cbiAgaWYgKG1zQWJzID49IGgpIHtcbiAgICByZXR1cm4gTWF0aC5yb3VuZChtcyAvIGgpICsgJ2gnO1xuICB9XG4gIGlmIChtc0FicyA+PSBtKSB7XG4gICAgcmV0dXJuIE1hdGgucm91bmQobXMgLyBtKSArICdtJztcbiAgfVxuICBpZiAobXNBYnMgPj0gcykge1xuICAgIHJldHVybiBNYXRoLnJvdW5kKG1zIC8gcykgKyAncyc7XG4gIH1cbiAgcmV0dXJuIG1zICsgJ21zJztcbn1cblxuLyoqXG4gKiBMb25nIGZvcm1hdCBmb3IgYG1zYC5cbiAqXG4gKiBAcGFyYW0ge051bWJlcn0gbXNcbiAqIEByZXR1cm4ge1N0cmluZ31cbiAqIEBhcGkgcHJpdmF0ZVxuICovXG5cbmZ1bmN0aW9uIGZtdExvbmcobXMpIHtcbiAgdmFyIG1zQWJzID0gTWF0aC5hYnMobXMpO1xuICBpZiAobXNBYnMgPj0gZCkge1xuICAgIHJldHVybiBwbHVyYWwobXMsIG1zQWJzLCBkLCAnZGF5Jyk7XG4gIH1cbiAgaWYgKG1zQWJzID49IGgpIHtcbiAgICByZXR1cm4gcGx1cmFsKG1zLCBtc0FicywgaCwgJ2hvdXInKTtcbiAgfVxuICBpZiAobXNBYnMgPj0gbSkge1xuICAgIHJldHVybiBwbHVyYWwobXMsIG1zQWJzLCBtLCAnbWludXRlJyk7XG4gIH1cbiAgaWYgKG1zQWJzID49IHMpIHtcbiAgICByZXR1cm4gcGx1cmFsKG1zLCBtc0FicywgcywgJ3NlY29uZCcpO1xuICB9XG4gIHJldHVybiBtcyArICcgbXMnO1xufVxuXG4vKipcbiAqIFBsdXJhbGl6YXRpb24gaGVscGVyLlxuICovXG5cbmZ1bmN0aW9uIHBsdXJhbChtcywgbXNBYnMsIG4sIG5hbWUpIHtcbiAgdmFyIGlzUGx1cmFsID0gbXNBYnMgPj0gbiAqIDEuNTtcbiAgcmV0dXJuIE1hdGgucm91bmQobXMgLyBuKSArICcgJyArIG5hbWUgKyAoaXNQbHVyYWwgPyAncycgOiAnJyk7XG59XG4iLCJcbi8qKlxuICogVGhpcyBpcyB0aGUgY29tbW9uIGxvZ2ljIGZvciBib3RoIHRoZSBOb2RlLmpzIGFuZCB3ZWIgYnJvd3NlclxuICogaW1wbGVtZW50YXRpb25zIG9mIGBkZWJ1ZygpYC5cbiAqL1xuXG5mdW5jdGlvbiBzZXR1cChlbnYpIHtcblx0Y3JlYXRlRGVidWcuZGVidWcgPSBjcmVhdGVEZWJ1Zztcblx0Y3JlYXRlRGVidWcuZGVmYXVsdCA9IGNyZWF0ZURlYnVnO1xuXHRjcmVhdGVEZWJ1Zy5jb2VyY2UgPSBjb2VyY2U7XG5cdGNyZWF0ZURlYnVnLmRpc2FibGUgPSBkaXNhYmxlO1xuXHRjcmVhdGVEZWJ1Zy5lbmFibGUgPSBlbmFibGU7XG5cdGNyZWF0ZURlYnVnLmVuYWJsZWQgPSBlbmFibGVkO1xuXHRjcmVhdGVEZWJ1Zy5odW1hbml6ZSA9IHJlcXVpcmUoJ21zJyk7XG5cdGNyZWF0ZURlYnVnLmRlc3Ryb3kgPSBkZXN0cm95O1xuXG5cdE9iamVjdC5rZXlzKGVudikuZm9yRWFjaChrZXkgPT4ge1xuXHRcdGNyZWF0ZURlYnVnW2tleV0gPSBlbnZba2V5XTtcblx0fSk7XG5cblx0LyoqXG5cdCogVGhlIGN1cnJlbnRseSBhY3RpdmUgZGVidWcgbW9kZSBuYW1lcywgYW5kIG5hbWVzIHRvIHNraXAuXG5cdCovXG5cblx0Y3JlYXRlRGVidWcubmFtZXMgPSBbXTtcblx0Y3JlYXRlRGVidWcuc2tpcHMgPSBbXTtcblxuXHQvKipcblx0KiBNYXAgb2Ygc3BlY2lhbCBcIiVuXCIgaGFuZGxpbmcgZnVuY3Rpb25zLCBmb3IgdGhlIGRlYnVnIFwiZm9ybWF0XCIgYXJndW1lbnQuXG5cdCpcblx0KiBWYWxpZCBrZXkgbmFtZXMgYXJlIGEgc2luZ2xlLCBsb3dlciBvciB1cHBlci1jYXNlIGxldHRlciwgaS5lLiBcIm5cIiBhbmQgXCJOXCIuXG5cdCovXG5cdGNyZWF0ZURlYnVnLmZvcm1hdHRlcnMgPSB7fTtcblxuXHQvKipcblx0KiBTZWxlY3RzIGEgY29sb3IgZm9yIGEgZGVidWcgbmFtZXNwYWNlXG5cdCogQHBhcmFtIHtTdHJpbmd9IG5hbWVzcGFjZSBUaGUgbmFtZXNwYWNlIHN0cmluZyBmb3IgdGhlIGRlYnVnIGluc3RhbmNlIHRvIGJlIGNvbG9yZWRcblx0KiBAcmV0dXJuIHtOdW1iZXJ8U3RyaW5nfSBBbiBBTlNJIGNvbG9yIGNvZGUgZm9yIHRoZSBnaXZlbiBuYW1lc3BhY2Vcblx0KiBAYXBpIHByaXZhdGVcblx0Ki9cblx0ZnVuY3Rpb24gc2VsZWN0Q29sb3IobmFtZXNwYWNlKSB7XG5cdFx0bGV0IGhhc2ggPSAwO1xuXG5cdFx0Zm9yIChsZXQgaSA9IDA7IGkgPCBuYW1lc3BhY2UubGVuZ3RoOyBpKyspIHtcblx0XHRcdGhhc2ggPSAoKGhhc2ggPDwgNSkgLSBoYXNoKSArIG5hbWVzcGFjZS5jaGFyQ29kZUF0KGkpO1xuXHRcdFx0aGFzaCB8PSAwOyAvLyBDb252ZXJ0IHRvIDMyYml0IGludGVnZXJcblx0XHR9XG5cblx0XHRyZXR1cm4gY3JlYXRlRGVidWcuY29sb3JzW01hdGguYWJzKGhhc2gpICUgY3JlYXRlRGVidWcuY29sb3JzLmxlbmd0aF07XG5cdH1cblx0Y3JlYXRlRGVidWcuc2VsZWN0Q29sb3IgPSBzZWxlY3RDb2xvcjtcblxuXHQvKipcblx0KiBDcmVhdGUgYSBkZWJ1Z2dlciB3aXRoIHRoZSBnaXZlbiBgbmFtZXNwYWNlYC5cblx0KlxuXHQqIEBwYXJhbSB7U3RyaW5nfSBuYW1lc3BhY2Vcblx0KiBAcmV0dXJuIHtGdW5jdGlvbn1cblx0KiBAYXBpIHB1YmxpY1xuXHQqL1xuXHRmdW5jdGlvbiBjcmVhdGVEZWJ1ZyhuYW1lc3BhY2UpIHtcblx0XHRsZXQgcHJldlRpbWU7XG5cdFx0bGV0IGVuYWJsZU92ZXJyaWRlID0gbnVsbDtcblx0XHRsZXQgbmFtZXNwYWNlc0NhY2hlO1xuXHRcdGxldCBlbmFibGVkQ2FjaGU7XG5cblx0XHRmdW5jdGlvbiBkZWJ1ZyguLi5hcmdzKSB7XG5cdFx0XHQvLyBEaXNhYmxlZD9cblx0XHRcdGlmICghZGVidWcuZW5hYmxlZCkge1xuXHRcdFx0XHRyZXR1cm47XG5cdFx0XHR9XG5cblx0XHRcdGNvbnN0IHNlbGYgPSBkZWJ1ZztcblxuXHRcdFx0Ly8gU2V0IGBkaWZmYCB0aW1lc3RhbXBcblx0XHRcdGNvbnN0IGN1cnIgPSBOdW1iZXIobmV3IERhdGUoKSk7XG5cdFx0XHRjb25zdCBtcyA9IGN1cnIgLSAocHJldlRpbWUgfHwgY3Vycik7XG5cdFx0XHRzZWxmLmRpZmYgPSBtcztcblx0XHRcdHNlbGYucHJldiA9IHByZXZUaW1lO1xuXHRcdFx0c2VsZi5jdXJyID0gY3Vycjtcblx0XHRcdHByZXZUaW1lID0gY3VycjtcblxuXHRcdFx0YXJnc1swXSA9IGNyZWF0ZURlYnVnLmNvZXJjZShhcmdzWzBdKTtcblxuXHRcdFx0aWYgKHR5cGVvZiBhcmdzWzBdICE9PSAnc3RyaW5nJykge1xuXHRcdFx0XHQvLyBBbnl0aGluZyBlbHNlIGxldCdzIGluc3BlY3Qgd2l0aCAlT1xuXHRcdFx0XHRhcmdzLnVuc2hpZnQoJyVPJyk7XG5cdFx0XHR9XG5cblx0XHRcdC8vIEFwcGx5IGFueSBgZm9ybWF0dGVyc2AgdHJhbnNmb3JtYXRpb25zXG5cdFx0XHRsZXQgaW5kZXggPSAwO1xuXHRcdFx0YXJnc1swXSA9IGFyZ3NbMF0ucmVwbGFjZSgvJShbYS16QS1aJV0pL2csIChtYXRjaCwgZm9ybWF0KSA9PiB7XG5cdFx0XHRcdC8vIElmIHdlIGVuY291bnRlciBhbiBlc2NhcGVkICUgdGhlbiBkb24ndCBpbmNyZWFzZSB0aGUgYXJyYXkgaW5kZXhcblx0XHRcdFx0aWYgKG1hdGNoID09PSAnJSUnKSB7XG5cdFx0XHRcdFx0cmV0dXJuICclJztcblx0XHRcdFx0fVxuXHRcdFx0XHRpbmRleCsrO1xuXHRcdFx0XHRjb25zdCBmb3JtYXR0ZXIgPSBjcmVhdGVEZWJ1Zy5mb3JtYXR0ZXJzW2Zvcm1hdF07XG5cdFx0XHRcdGlmICh0eXBlb2YgZm9ybWF0dGVyID09PSAnZnVuY3Rpb24nKSB7XG5cdFx0XHRcdFx0Y29uc3QgdmFsID0gYXJnc1tpbmRleF07XG5cdFx0XHRcdFx0bWF0Y2ggPSBmb3JtYXR0ZXIuY2FsbChzZWxmLCB2YWwpO1xuXG5cdFx0XHRcdFx0Ly8gTm93IHdlIG5lZWQgdG8gcmVtb3ZlIGBhcmdzW2luZGV4XWAgc2luY2UgaXQncyBpbmxpbmVkIGluIHRoZSBgZm9ybWF0YFxuXHRcdFx0XHRcdGFyZ3Muc3BsaWNlKGluZGV4LCAxKTtcblx0XHRcdFx0XHRpbmRleC0tO1xuXHRcdFx0XHR9XG5cdFx0XHRcdHJldHVybiBtYXRjaDtcblx0XHRcdH0pO1xuXG5cdFx0XHQvLyBBcHBseSBlbnYtc3BlY2lmaWMgZm9ybWF0dGluZyAoY29sb3JzLCBldGMuKVxuXHRcdFx0Y3JlYXRlRGVidWcuZm9ybWF0QXJncy5jYWxsKHNlbGYsIGFyZ3MpO1xuXG5cdFx0XHRjb25zdCBsb2dGbiA9IHNlbGYubG9nIHx8IGNyZWF0ZURlYnVnLmxvZztcblx0XHRcdGxvZ0ZuLmFwcGx5KHNlbGYsIGFyZ3MpO1xuXHRcdH1cblxuXHRcdGRlYnVnLm5hbWVzcGFjZSA9IG5hbWVzcGFjZTtcblx0XHRkZWJ1Zy51c2VDb2xvcnMgPSBjcmVhdGVEZWJ1Zy51c2VDb2xvcnMoKTtcblx0XHRkZWJ1Zy5jb2xvciA9IGNyZWF0ZURlYnVnLnNlbGVjdENvbG9yKG5hbWVzcGFjZSk7XG5cdFx0ZGVidWcuZXh0ZW5kID0gZXh0ZW5kO1xuXHRcdGRlYnVnLmRlc3Ryb3kgPSBjcmVhdGVEZWJ1Zy5kZXN0cm95OyAvLyBYWFggVGVtcG9yYXJ5LiBXaWxsIGJlIHJlbW92ZWQgaW4gdGhlIG5leHQgbWFqb3IgcmVsZWFzZS5cblxuXHRcdE9iamVjdC5kZWZpbmVQcm9wZXJ0eShkZWJ1ZywgJ2VuYWJsZWQnLCB7XG5cdFx0XHRlbnVtZXJhYmxlOiB0cnVlLFxuXHRcdFx0Y29uZmlndXJhYmxlOiBmYWxzZSxcblx0XHRcdGdldDogKCkgPT4ge1xuXHRcdFx0XHRpZiAoZW5hYmxlT3ZlcnJpZGUgIT09IG51bGwpIHtcblx0XHRcdFx0XHRyZXR1cm4gZW5hYmxlT3ZlcnJpZGU7XG5cdFx0XHRcdH1cblx0XHRcdFx0aWYgKG5hbWVzcGFjZXNDYWNoZSAhPT0gY3JlYXRlRGVidWcubmFtZXNwYWNlcykge1xuXHRcdFx0XHRcdG5hbWVzcGFjZXNDYWNoZSA9IGNyZWF0ZURlYnVnLm5hbWVzcGFjZXM7XG5cdFx0XHRcdFx0ZW5hYmxlZENhY2hlID0gY3JlYXRlRGVidWcuZW5hYmxlZChuYW1lc3BhY2UpO1xuXHRcdFx0XHR9XG5cblx0XHRcdFx0cmV0dXJuIGVuYWJsZWRDYWNoZTtcblx0XHRcdH0sXG5cdFx0XHRzZXQ6IHYgPT4ge1xuXHRcdFx0XHRlbmFibGVPdmVycmlkZSA9IHY7XG5cdFx0XHR9XG5cdFx0fSk7XG5cblx0XHQvLyBFbnYtc3BlY2lmaWMgaW5pdGlhbGl6YXRpb24gbG9naWMgZm9yIGRlYnVnIGluc3RhbmNlc1xuXHRcdGlmICh0eXBlb2YgY3JlYXRlRGVidWcuaW5pdCA9PT0gJ2Z1bmN0aW9uJykge1xuXHRcdFx0Y3JlYXRlRGVidWcuaW5pdChkZWJ1Zyk7XG5cdFx0fVxuXG5cdFx0cmV0dXJuIGRlYnVnO1xuXHR9XG5cblx0ZnVuY3Rpb24gZXh0ZW5kKG5hbWVzcGFjZSwgZGVsaW1pdGVyKSB7XG5cdFx0Y29uc3QgbmV3RGVidWcgPSBjcmVhdGVEZWJ1Zyh0aGlzLm5hbWVzcGFjZSArICh0eXBlb2YgZGVsaW1pdGVyID09PSAndW5kZWZpbmVkJyA/ICc6JyA6IGRlbGltaXRlcikgKyBuYW1lc3BhY2UpO1xuXHRcdG5ld0RlYnVnLmxvZyA9IHRoaXMubG9nO1xuXHRcdHJldHVybiBuZXdEZWJ1Zztcblx0fVxuXG5cdC8qKlxuXHQqIEVuYWJsZXMgYSBkZWJ1ZyBtb2RlIGJ5IG5hbWVzcGFjZXMuIFRoaXMgY2FuIGluY2x1ZGUgbW9kZXNcblx0KiBzZXBhcmF0ZWQgYnkgYSBjb2xvbiBhbmQgd2lsZGNhcmRzLlxuXHQqXG5cdCogQHBhcmFtIHtTdHJpbmd9IG5hbWVzcGFjZXNcblx0KiBAYXBpIHB1YmxpY1xuXHQqL1xuXHRmdW5jdGlvbiBlbmFibGUobmFtZXNwYWNlcykge1xuXHRcdGNyZWF0ZURlYnVnLnNhdmUobmFtZXNwYWNlcyk7XG5cdFx0Y3JlYXRlRGVidWcubmFtZXNwYWNlcyA9IG5hbWVzcGFjZXM7XG5cblx0XHRjcmVhdGVEZWJ1Zy5uYW1lcyA9IFtdO1xuXHRcdGNyZWF0ZURlYnVnLnNraXBzID0gW107XG5cblx0XHRjb25zdCBzcGxpdCA9ICh0eXBlb2YgbmFtZXNwYWNlcyA9PT0gJ3N0cmluZycgPyBuYW1lc3BhY2VzIDogJycpXG5cdFx0XHQudHJpbSgpXG5cdFx0XHQucmVwbGFjZSgnICcsICcsJylcblx0XHRcdC5zcGxpdCgnLCcpXG5cdFx0XHQuZmlsdGVyKEJvb2xlYW4pO1xuXG5cdFx0Zm9yIChjb25zdCBucyBvZiBzcGxpdCkge1xuXHRcdFx0aWYgKG5zWzBdID09PSAnLScpIHtcblx0XHRcdFx0Y3JlYXRlRGVidWcuc2tpcHMucHVzaChucy5zbGljZSgxKSk7XG5cdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHRjcmVhdGVEZWJ1Zy5uYW1lcy5wdXNoKG5zKTtcblx0XHRcdH1cblx0XHR9XG5cdH1cblxuXHQvKipcblx0ICogQ2hlY2tzIGlmIHRoZSBnaXZlbiBzdHJpbmcgbWF0Y2hlcyBhIG5hbWVzcGFjZSB0ZW1wbGF0ZSwgaG9ub3Jpbmdcblx0ICogYXN0ZXJpc2tzIGFzIHdpbGRjYXJkcy5cblx0ICpcblx0ICogQHBhcmFtIHtTdHJpbmd9IHNlYXJjaFxuXHQgKiBAcGFyYW0ge1N0cmluZ30gdGVtcGxhdGVcblx0ICogQHJldHVybiB7Qm9vbGVhbn1cblx0ICovXG5cdGZ1bmN0aW9uIG1hdGNoZXNUZW1wbGF0ZShzZWFyY2gsIHRlbXBsYXRlKSB7XG5cdFx0bGV0IHNlYXJjaEluZGV4ID0gMDtcblx0XHRsZXQgdGVtcGxhdGVJbmRleCA9IDA7XG5cdFx0bGV0IHN0YXJJbmRleCA9IC0xO1xuXHRcdGxldCBtYXRjaEluZGV4ID0gMDtcblxuXHRcdHdoaWxlIChzZWFyY2hJbmRleCA8IHNlYXJjaC5sZW5ndGgpIHtcblx0XHRcdGlmICh0ZW1wbGF0ZUluZGV4IDwgdGVtcGxhdGUubGVuZ3RoICYmICh0ZW1wbGF0ZVt0ZW1wbGF0ZUluZGV4XSA9PT0gc2VhcmNoW3NlYXJjaEluZGV4XSB8fCB0ZW1wbGF0ZVt0ZW1wbGF0ZUluZGV4XSA9PT0gJyonKSkge1xuXHRcdFx0XHQvLyBNYXRjaCBjaGFyYWN0ZXIgb3IgcHJvY2VlZCB3aXRoIHdpbGRjYXJkXG5cdFx0XHRcdGlmICh0ZW1wbGF0ZVt0ZW1wbGF0ZUluZGV4XSA9PT0gJyonKSB7XG5cdFx0XHRcdFx0c3RhckluZGV4ID0gdGVtcGxhdGVJbmRleDtcblx0XHRcdFx0XHRtYXRjaEluZGV4ID0gc2VhcmNoSW5kZXg7XG5cdFx0XHRcdFx0dGVtcGxhdGVJbmRleCsrOyAvLyBTa2lwIHRoZSAnKidcblx0XHRcdFx0fSBlbHNlIHtcblx0XHRcdFx0XHRzZWFyY2hJbmRleCsrO1xuXHRcdFx0XHRcdHRlbXBsYXRlSW5kZXgrKztcblx0XHRcdFx0fVxuXHRcdFx0fSBlbHNlIGlmIChzdGFySW5kZXggIT09IC0xKSB7IC8vIGVzbGludC1kaXNhYmxlLWxpbmUgbm8tbmVnYXRlZC1jb25kaXRpb25cblx0XHRcdFx0Ly8gQmFja3RyYWNrIHRvIHRoZSBsYXN0ICcqJyBhbmQgdHJ5IHRvIG1hdGNoIG1vcmUgY2hhcmFjdGVyc1xuXHRcdFx0XHR0ZW1wbGF0ZUluZGV4ID0gc3RhckluZGV4ICsgMTtcblx0XHRcdFx0bWF0Y2hJbmRleCsrO1xuXHRcdFx0XHRzZWFyY2hJbmRleCA9IG1hdGNoSW5kZXg7XG5cdFx0XHR9IGVsc2Uge1xuXHRcdFx0XHRyZXR1cm4gZmFsc2U7IC8vIE5vIG1hdGNoXG5cdFx0XHR9XG5cdFx0fVxuXG5cdFx0Ly8gSGFuZGxlIHRyYWlsaW5nICcqJyBpbiB0ZW1wbGF0ZVxuXHRcdHdoaWxlICh0ZW1wbGF0ZUluZGV4IDwgdGVtcGxhdGUubGVuZ3RoICYmIHRlbXBsYXRlW3RlbXBsY