UNPKG

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
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