UNPKG

expand-value

Version:

Get deeply nested values from an object, like dot-prop and get-value, but with support for advanced features like bracket-notation and more.

677 lines (668 loc) 19 kB
var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; // src/utils.ts import isNumber from "is-number"; import { Segmenter } from "intl-segmenter"; var { defineProperty } = Reflect; var isObject = /* @__PURE__ */ __name((val) => val && typeof val === "object" && !Array.isArray(val), "isObject"); var unquote = /* @__PURE__ */ __name((str) => { if (!str) return ""; return str.replace(/^['"`]|['"`]$/g, ""); }, "unquote"); var define = /* @__PURE__ */ __name((node, key, value) => { defineProperty(node, key, { configurable: true, enumerable: false, writable: true, value }); }, "define"); var size = /* @__PURE__ */ __name((value) => { if (value == null) return 0; if (isNumber(value)) return String(value).length; if (isObject(value)) return Object.keys(value).length; if (typeof value.length === "number") return value.length; if (typeof value.size === "number") return value.size; return null; }, "size"); var isValidObject = /* @__PURE__ */ __name((val) => { return isObject(val) || Array.isArray(val) || typeof val === "function"; }, "isValidObject"); var isSafeKey = /* @__PURE__ */ __name((key) => { return key !== "__proto__" && key !== "constructor" && key !== "prototype"; }, "isSafeKey"); var isValid = /* @__PURE__ */ __name((key, data, options) => { if (!isSafeKey(key)) { return false; } if (typeof options.isValid === "function") { return options.isValid(key, data); } return true; }, "isValid"); var findSafeBreakPoint = /* @__PURE__ */ __name((input) => { for (let i = input.length - 1; i >= 0; i--) { if (/\s/.test(input[i]) || /^[\x20-\x7E]$/.test(input[i])) { return i + 1; } } return input.length; }, "findSafeBreakPoint"); var getSegments = /* @__PURE__ */ __name((input, language = "en", granularity) => { const segmenter = new Segmenter(language, { granularity, localeMatcher: "best fit" }); return Array.from(segmenter.segment(input)).map((segment) => segment.segment); }, "getSegments"); var getGraphemes = /* @__PURE__ */ __name((input, language = "en", maxChunkLength = 500) => { const graphemes = []; let position = 0; while (position < input.length) { const remainingText = input.slice(position); const chunkSize = Math.min(maxChunkLength, remainingText.length); const potentialChunk = remainingText.slice(0, chunkSize); const breakPoint = findSafeBreakPoint(potentialChunk); const chunk = potentialChunk.slice(0, breakPoint); const chunkSegments = getSegments(chunk, language, "grapheme"); graphemes.push(...chunkSegments); position += breakPoint; } return graphemes; }, "getGraphemes"); // src/helpers.ts var helpers_exports = {}; __export(helpers_exports, { first: () => first, last: () => last, length: () => length, size: () => size2 }); var first = /* @__PURE__ */ __name((value) => { if (!value) return; if (value instanceof Set || value instanceof Map) { value = [...value]; } if (isObject(value)) { return typeof value.first === "function" ? value.first() : value.first; } if (Array.isArray(value)) { return value[0]; } if (typeof value === "string") { const graphemes = getGraphemes(value.slice(0, 20)); return graphemes[0]; } return value[0]; }, "first"); var last = /* @__PURE__ */ __name((value) => { if (!value) return; if (value instanceof Set || value instanceof Map) { value = [...value]; } if (isObject(value)) { return typeof value.last === "function" ? value.last() : value.last; } if (Array.isArray(value)) { return value[value.length - 1]; } if (typeof value === "string") { const graphemes = getGraphemes(value.slice(-20)); return graphemes[graphemes.length - 1]; } return value[value.length - 1]; }, "last"); var length = /* @__PURE__ */ __name((value) => { if (typeof value?.length === "number") { return value.length; } if (typeof value?.size === "number") { return value.size; } return size2(value); }, "length"); var size2 = /* @__PURE__ */ __name((value) => { if (value === null) { return 1; } if (typeof value?.size === "number") { return value.size; } if (typeof value?.length === "number") { return value.length; } return size(value); }, "size"); // src/compile.ts var compile = /* @__PURE__ */ __name((ast, data = {}, options = {}) => { const orig = { ...data }; let context = orig; let prev = context; const fns = options.helpers ? { ...helpers_exports, ...options.helpers } : helpers_exports; const resolve = /* @__PURE__ */ __name((node) => { if (node.skip || node.type === "separator") { return; } if (context === void 0) { return; } if (node.type === "paren") { const args = []; for (let i = 1; i < node.nodes.length - 1; i++) { const child = node.nodes[i]; switch (child.type) { case "integer": args.push(Number(child.value)); break; case "quoted": args.push(unquote(child.value)); break; case "symbol": args.push(Symbol.for(child.value)); break; case "ident": args.push(expand(context, child.value)); break; default: { break; } } } context = `(${args.join("..")})`; return; } if (node.nodes) { node.nodes.forEach((child) => resolve(child)); return; } if (node.type === "symbol") { prev = context; for (const symbol2 of Object.getOwnPropertySymbols(context)) { if (symbol2 === node.symbol || symbol2.toString() === node.symbol.toString()) { context = context[symbol2]; return; } } const symbol = node.symbol || Symbol.for(node.value); context = context[symbol]; return; } if (node.type === "ident") { if (!isSafeKey(node.value)) { context = void 0; return; } let value = node.value; if (node.parent?.type === "bracket") { let temp = orig; value = expand(temp, value); if (isObject(value)) { const sibs = node.siblings.filter((n) => ["ident", "quoted", "symbol"].includes(n.type)); let index = sibs.indexOf(node) + 1; let next = sibs[index]; while (isObject(value) && isObject(next) && temp) { const key = next.value; value = expand(value, key); next.skip = true; temp = expand(temp, value); next = sibs[++index]; } } } prev = context; if (context?.[value] !== void 0) { context = context[value]; if (typeof context === "function") { context = context.call(prev); } return; } const helper = fns[value]; if (typeof helper === "function") { context = helper(context); } if (context === void 0 && options.strict === true) { throw new Error(`Variable is undefined: "${node.value}"`); } return; } if (node.type === "integer" || node.type === "number") { if (node.parent.type === "bracket") { const index = node.parent.nodes.indexOf(node); const next = node.parent.nodes[index + 1]; const after = node.parent.nodes[index + 2]; if (next?.type === "range" && (after?.type === "integer" || after?.type === "number")) { next.skip = true; after.skip = true; const start = Number(node.value); const end = Number(after.value); const range = Array.from({ length: end - start + 1 }, (_, i) => start + i); context = range.map((i) => context[i]); return; } } prev = context; context = context[Number(node.value)]; return; } if (node.type === "quoted") { prev = context; context = context[node.match[2]]; } }, "resolve"); resolve(ast); if (typeof context === "function") { context.context = prev; } if (ast.nodes?.length > 0 && context === orig) { return void 0; } return context; }, "compile"); // src/nodes/Location.ts var Position = class { static { __name(this, "Position"); } index; line; col; constructor(loc) { this.index = loc.index; this.line = loc.line; this.col = loc.col; } }; var Location = class { static { __name(this, "Location"); } start; end; constructor(start, end) { this.start = start; this.end = end; } slice(input) { return input.slice(...this.range); } get range() { return [this.start.index, this.end.index]; } get lines() { return [this.start.line, this.end.line]; } }; var location = /* @__PURE__ */ __name((loc) => { const start = new Position(loc); return (node) => { node.loc = new Location(start, new Position(loc)); return node; }; }, "location"); location.Position = Position; location.Location = Location; location.location = location; // src/nodes/Node.ts var Node = class { static { __name(this, "Node"); } type; value; output; symbol; parent; constructor(node) { this.type = node.type; this.value = node.value || ""; if (node.output != null && node.output !== "") { this.output = node.output; } if (node.symbol) { this.symbol = node.symbol; } define(this, "alt", node.alt); define(this, "match", node.match); define(this, "loc", node.loc); } get siblings() { return this.parent?.nodes || []; } }; // src/nodes/Block.ts var Block = class extends Node { static { __name(this, "Block"); } nodes; constructor(node) { super(node); this.nodes = node.nodes || []; } append(input) { this.parent && this.parent.append(input); this.output = this.output || ""; this.output += input; } push(node) { define(node, "parent", this); this.nodes.push(node); } }; // src/nodes/Token.ts var { defineProperty: defineProperty2 } = Reflect; var Token = class { static { __name(this, "Token"); } type; value; loc; match; constructor(token) { this.type = token.type; this.value = token.value; defineProperty2(this, "loc", { value: token.loc, writable: true }); defineProperty2(this, "match", { value: token.match }); } }; // src/parse.ts var QUOTED_STRING = /^(['"`])((?:\\.|(?!\1)[\s\S])*?)(\1)/; var IDENT_DOT = /^([a-zA-Z_][-a-zA-Z0-9_.]*(?<!\.))/; var IDENT = /^([a-zA-Z_$][a-zA-Z0-9_$-]*(?<!-))/; var NUMBER = /^(-?[0-9]+(?:\.[0-9]+)?|-?Infinity|NaN)/; var RANGE = /^\.\.(?=[0-9a-z])/i; var SYMBOL = /^Symbol\((.*?)\)/; var parse = /* @__PURE__ */ __name((input = "", options = {}) => { if (!isObject(options)) options = {}; const loc = { index: 0, line: 1, col: 0, row: 1 }; let pos = location(loc); const str = String(input); const ast = new Block({ type: "root" }); ast.output = ""; const stack = [ast]; const stash = []; const tokens = []; let remaining = str; let block = ast; let token; let match; let prev; let separator = /^\./; let IDENTITY = IDENT; if (typeof options.separator === "string") { separator = new RegExp(`^\\${options.separator.replace(/^[\\^]+/, "")}`); IDENTITY = IDENT_DOT; } const eos = /* @__PURE__ */ __name(() => remaining === void 0 || remaining === "", "eos"); const scan = /* @__PURE__ */ __name((regex, type = "text") => { if (match = regex.exec(remaining)) { consume(match[0]); return new Token({ type, value: match[0], match }); } }, "scan"); const updateLocation = /* @__PURE__ */ __name((value, len) => { const i = value.lastIndexOf("\n"); loc.index += len; loc.col = ~i ? len - i : loc.col + len; loc.row += Math.max(0, value.split("\n").length - 1); }, "updateLocation"); const consume = /* @__PURE__ */ __name((value, len = value.length) => { updateLocation(value, len); remaining = remaining.slice(len); return value; }, "consume"); const drop = /* @__PURE__ */ __name(() => { if (stash.length) { block.push(stash.shift()); } }, "drop"); const shouldPush = /* @__PURE__ */ __name((node) => { return node.type !== "newline" || options.newlines !== false; }, "shouldPush"); const push = /* @__PURE__ */ __name((node) => { pos(node); if (prev?.type === "ident" && node.type === "ident") { block.append(node.output || node.match[0]); prev.value += node.value; return; } if (!shouldPush(node)) return; block.push(node); if (node.nodes) { stack.push(node); block = node; } else { block.append(node.output || node.match[0]); tokens.push(node); } pos = location(loc); prev = node; if (block.type === "root") { drop(); } }, "push"); const pop = /* @__PURE__ */ __name(() => { const parent = stack.pop(); block = stack[stack.length - 1]; return parent; }, "pop"); const advance = /* @__PURE__ */ __name(() => { if (token = scan(/^\\+/, "escaped")) { if (token.value.length % 2 === 1) { token.value += consume(remaining[0]); } if (token.value === "\\.") { token.type = "ident"; token.value = "."; token.output = "\\."; } push(new Node(token)); return; } if (token = scan(SYMBOL, "symbol")) { token.value = token.match[1]; token.symbol = Symbol.for(token.value); push(new Node(token)); return; } if (token = scan(/^\[/, "left_bracket")) { token.index = tokens.length; push(new Block({ type: "bracket" })); push(new Node(token)); return; } if (token = scan(/^\]/, "right_bracket")) { push(new Node(token)); if (block.type === "bracket") { pop(); } return; } if (token = scan(/^\(/, "left_paren")) { token.index = tokens.length; push(new Block({ type: "paren" })); push(new Node(token)); return; } if (token = scan(/^\)/, "right_paren")) { push(new Node(token)); if (block.type === "paren") { pop(); } return; } if (block.type === "bracket" && (token = scan(RANGE, "range"))) { push(new Node(token)); return; } if (!options.separator && (token = scan(separator, "separator"))) { push(new Node(token)); return; } if (token = scan(/^[0-9]+/, "integer")) { push(new Node(token)); return; } if (token = scan(IDENTITY, "ident")) { push(new Node(token)); return; } if (options.separator && options.separator !== ".") { if (token = scan(separator, "separator")) { push(new Node(token)); return; } } if (token = scan(QUOTED_STRING, "quoted")) { push(new Node(token)); return; } if (token = scan(NUMBER, "number")) { if (token.value === "-0") { token.alt = "0"; } push(new Node(token)); return; } push(new Node(scan(/^(.)/, "text"))); }, "advance"); while (!eos()) advance(); return { ast, tokens, output: ast.output }; }, "parse"); // src/expand.ts var METHOD_REGEX = /(\[[^[\]]+?\]|\.(?:blank|empty|first|last|length|nil|size)(\.|$))/; var expand = /* @__PURE__ */ __name((data, path, options = {}) => { if (!isObject(options)) { options = { default: options }; } const fallback = options.default !== void 0 ? options.default : options.fallback; const helpers = options.helpers; if (data && typeof path === "string") { if (path.startsWith("[") && path.endsWith("]") && !path.slice(1).includes("[")) { const prop = path.slice(1, -1); if (data[prop] !== void 0 && isValid(prop, data, options)) { return data[prop]; } } if (data[path] !== void 0 && isValid(path, data, options)) { return data[path]; } } if ((typeof path === "symbol" || typeof path === "number") && isValid(path, data, options)) { return data[path]; } if (typeof path !== "string" && !Array.isArray(path)) { return data; } if (!isValidObject(data)) { return fallback; } if (path in data && isValid(path, data, options)) { return data[path]; } if ((Array.isArray(path) || !METHOD_REGEX.test(path)) && !options.separator) { const segs = Array.isArray(path) ? path : path.split(/(?<!\\)\.(?!$)/); let ctx = data; let prev = ctx; let i = 0; if (segs.length === 0) { return fallback; } for (; i < segs.length; i++) { if (ctx === void 0) { return fallback; } let key = String(segs[i]).replace(/\\(.)/g, "$1"); const helper = helpers?.[key]; if (!isValidObject(ctx)) { if (!helper) { return fallback; } ctx = helper(ctx); continue; } if (options.onResolve) { options.onResolve(ctx, key); } if (Array.isArray(ctx) || typeof ctx === "string") { const index = Number(key); if (!Number.isNaN(index)) { key = index; } } else if (key === "-0") { key = -0; } else if (key === "NaN") { key = NaN; } else if (key === "Infinity" || key === "-Infinity") { key = Number(key); } if (!isValid(key, ctx, options)) { return fallback; } let val = ctx[key]; if (val === void 0 && helper) { val = helper(ctx); } if (val !== void 0) { if (!isValid(key, ctx, options)) { return fallback; } prev = ctx; ctx = val; continue; } let temp = ctx; let next = segs[i + 1]; let found = false; while (next) { i++; key += `.${next}`; if (!isValid(key, ctx, options)) { return fallback; } temp = ctx[key]; next = segs[i + 1]; if (temp !== void 0) { prev = ctx; ctx = temp; found = true; break; } } if (!found) { prev = ctx; ctx = fallback; break; } } if (i < segs.length) { if (options.strict === true && fallback === void 0) { throw new Error(`Variable is undefined: "${segs[i - 1]}"`); } return fallback; } if (typeof ctx === "function" && isObject(prev)) { ctx.context = prev; } return ctx; } const { ast } = parse(path, options); const output = compile(ast, data, options); if (output === void 0) { return fallback; } return output; }, "expand"); expand.parse = parse; expand.compile = compile; var expand_default = expand; export { compile, expand_default as default, expand, parse }; //# sourceMappingURL=index.mjs.map