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.
706 lines (696 loc) • 20.5 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
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 });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/compile.ts
var compile_exports = {};
__export(compile_exports, {
compile: () => compile,
default: () => compile_default
});
module.exports = __toCommonJS(compile_exports);
// src/utils.ts
var import_is_number = __toESM(require("is-number"));
var import_intl_segmenter = require("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 ((0, import_is_number.default)(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 import_intl_segmenter.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/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;
// 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");
var compile_default = compile;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
compile
});
//# sourceMappingURL=compile.js.map