@ztimson/utils 0.28.2 → 0.28.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -428,6 +428,8 @@ ${opts.message || this.desc}`;
428
428
  return function(a, b) {
429
429
  const aVal = dotNotation(a, prop);
430
430
  const bVal = dotNotation(b, prop);
431
+ if (aVal === void 0) return 1;
432
+ if (bVal === void 0) return -1;
431
433
  if (typeof aVal == "number" && typeof bVal == "number")
432
434
  return (reverse ? -1 : 1) * (aVal - bVal);
433
435
  if (aVal > bVal) return reverse ? -1 : 1;
@@ -2354,8 +2356,11 @@ ${opts.message || this.desc}`;
2354
2356
  return result;
2355
2357
  }
2356
2358
  async function renderTemplate(template, data, fetch2) {
2357
- let content = template, found;
2358
- const now = /* @__PURE__ */ new Date(), d = {
2359
+ if (!fetch2) fetch2 = (file) => {
2360
+ throw new TemplateError(`Unable to fetch template: ${file}`);
2361
+ };
2362
+ const now = /* @__PURE__ */ new Date();
2363
+ const d = {
2359
2364
  date: {
2360
2365
  day: now.getDate(),
2361
2366
  month: now.toLocaleString("default", { month: "long" }),
@@ -2365,65 +2370,144 @@ ${opts.message || this.desc}`;
2365
2370
  },
2366
2371
  ...data || {}
2367
2372
  };
2368
- if (!fetch2) fetch2 = (file) => {
2369
- throw new TemplateError(`Unable to fetch template: ${file}`);
2370
- };
2371
2373
  const evaluate = (code, data2, fatal = true) => {
2372
2374
  try {
2373
2375
  return Function("data", `with(data) { return ${code}; }`)(data2);
2374
- } catch {
2375
- if (fatal) throw new TemplateError(`Failed to evaluate: ${code}`);
2376
- else return false;
2376
+ } catch (err) {
2377
+ if (fatal) throw new TemplateError(`Failed to evaluate: ${code}
2378
+ ${err.message || err.toString()}`);
2379
+ return false;
2377
2380
  }
2378
2381
  };
2379
- while (!!(found = /\{\{\s*?\?\s*?(.+?)\s*?}}([\s\S]*?)\{\{\s*?\/\?\s*?}}/g.exec(content))) {
2380
- const nested = matchAll(found[0], /\{\{\s*?\?.+?}}/g).slice(-1)?.[0]?.index;
2381
- if (nested != 0) found = /\{\{\s*?\?\s*?(.+?)\s*?}}([\s\S]*?)\{\{\s*?\/\?\s*?}}/g.exec(content.slice(found.index + nested));
2382
- const parts = found[2].split(/\{\{\s*?!\?\s*?/);
2383
- let result = evaluate(found[1], d, false) ? parts[0] : "";
2384
- if (!result) {
2385
- for (let i = 1; i < parts.length; i++) {
2386
- const [cond, body] = parts[i].split(/}}/);
2387
- if (!cond.trim()) {
2388
- result = body || "";
2389
- break;
2390
- }
2391
- if (evaluate(cond, d, false)) {
2392
- result = body || "";
2393
- break;
2382
+ async function process(content, ctx = d) {
2383
+ let result = content;
2384
+ const extendsMatch = result.match(/\{\{\s*>\s*(.+?):(.+?)\s*}}([\s\S]*?)\{\{\s*\/>\s*}}/);
2385
+ if (extendsMatch) {
2386
+ const parentTemplate = await fetch2(extendsMatch[1].trim());
2387
+ if (!parentTemplate) throw new TemplateError(`Unknown extended template: ${extendsMatch[1].trim()}`);
2388
+ const slotName = extendsMatch[2].trim();
2389
+ const slotContent = await process(extendsMatch[3], ctx);
2390
+ return process(parentTemplate, { ...ctx, [slotName]: slotContent });
2391
+ }
2392
+ let changed = true;
2393
+ while (changed) {
2394
+ changed = false;
2395
+ const before = result;
2396
+ const importMatch = result.match(/\{\{\s*<\s*(.+?)\s*}}/);
2397
+ if (importMatch) {
2398
+ const t = await fetch2(importMatch[1].trim());
2399
+ if (!t) throw new TemplateError(`Unknown imported template: ${importMatch[1].trim()}`);
2400
+ const rendered = await process(t, ctx);
2401
+ result = result.replace(importMatch[0], rendered);
2402
+ changed = true;
2403
+ continue;
2404
+ }
2405
+ const forMatch = findInnermostFor(result);
2406
+ if (forMatch) {
2407
+ const { full, vars, array, body, start } = forMatch;
2408
+ const [element, index = "index"] = vars.split(",").map((v) => v.trim());
2409
+ const arr = dotNotation(ctx, array);
2410
+ if (!arr || typeof arr != "object") throw new TemplateError(`Cannot iterate: ${array}`);
2411
+ let output = [];
2412
+ for (let i = 0; i < arr.length; i++)
2413
+ output.push(await process(body, { ...ctx, [element]: arr[i], [index]: i }));
2414
+ result = result.slice(0, start) + output.join("\n") + result.slice(start + full.length);
2415
+ changed = true;
2416
+ continue;
2417
+ }
2418
+ const ifMatch = findInnermostIf(result);
2419
+ if (ifMatch) {
2420
+ const { full, condition, body, start } = ifMatch;
2421
+ const branches = parseIfBranches(body);
2422
+ let output = "";
2423
+ if (evaluate(condition, ctx, false)) {
2424
+ output = branches.if;
2425
+ } else {
2426
+ for (const branch of branches.elseIf) {
2427
+ if (evaluate(branch.condition, ctx, false)) {
2428
+ output = branch.body;
2429
+ break;
2430
+ }
2431
+ }
2432
+ if (!output && branches.else) output = branches.else;
2394
2433
  }
2434
+ result = result.slice(0, start) + output + result.slice(start + full.length);
2435
+ changed = true;
2436
+ continue;
2437
+ }
2438
+ if (before === result) changed = false;
2439
+ }
2440
+ return processVariables(result, ctx);
2441
+ }
2442
+ function processVariables(content, data2) {
2443
+ return content.replace(/\{\{\s*([^<>\*\?!/}\s][^{}]*?)\s*}}/g, (match, code) => {
2444
+ return evaluate(code.trim(), data2) ?? "";
2445
+ });
2446
+ }
2447
+ function findInnermostIf(content) {
2448
+ const regex = /\{\{\s*\?\s*(.+?)\s*}}/g;
2449
+ let match, lastMatch = null;
2450
+ while ((match = regex.exec(content)) !== null) {
2451
+ const start = match.index;
2452
+ const condition = match[1];
2453
+ const bodyStart = match.index + match[0].length;
2454
+ const end = findMatchingClose(content, bodyStart, /\{\{\s*\?\s*/, /\{\{\s*\/\?\s*}}/);
2455
+ if (end === -1) throw new TemplateError(`Unmatched if-statement at position ${start}`);
2456
+ const closeTag = content.slice(end).match(/\{\{\s*\/\?\s*}}/);
2457
+ const full = content.slice(start, end + closeTag[0].length);
2458
+ const body = content.slice(bodyStart, end);
2459
+ lastMatch = { full, condition, body, start };
2460
+ }
2461
+ return lastMatch;
2462
+ }
2463
+ function findInnermostFor(content) {
2464
+ const regex = /\{\{\s*\*\s*(.+?)\s+in\s+(.+?)\s*}}/g;
2465
+ let match, lastMatch = null;
2466
+ while ((match = regex.exec(content)) !== null) {
2467
+ const start = match.index;
2468
+ const vars = match[1].replaceAll(/[()\s]/g, "");
2469
+ const array = match[2];
2470
+ const bodyStart = match.index + match[0].length;
2471
+ const end = findMatchingClose(content, bodyStart, /\{\{\s*\*\s*/, /\{\{\s*\/\*\s*}}/);
2472
+ if (end === -1) throw new TemplateError(`Unmatched for-loop at position ${start}`);
2473
+ const closeTag = content.slice(end).match(/\{\{\s*\/\*\s*}}/);
2474
+ const full = content.slice(start, end + closeTag[0].length);
2475
+ const body = content.slice(bodyStart, end);
2476
+ lastMatch = { full, vars, array, body, start };
2477
+ }
2478
+ return lastMatch;
2479
+ }
2480
+ function findMatchingClose(content, startIndex, openTag, closeTag) {
2481
+ let depth = 1, pos = startIndex;
2482
+ while (depth > 0 && pos < content.length) {
2483
+ const remaining = content.slice(pos);
2484
+ const nextOpen = remaining.search(openTag);
2485
+ const nextClose = remaining.search(closeTag);
2486
+ if (nextClose === -1) return -1;
2487
+ if (nextOpen !== -1 && nextOpen < nextClose) {
2488
+ depth++;
2489
+ pos += nextOpen + 1;
2490
+ } else {
2491
+ depth--;
2492
+ if (depth === 0) return pos + nextClose;
2493
+ pos += nextClose + 1;
2395
2494
  }
2396
2495
  }
2397
- content = content.replace(found[0], result);
2398
- }
2399
- while (!!(found = /\{\{\s*?<\s*?(.+?)\s*?}}/g.exec(content))) {
2400
- const t = await fetch2(found[1].trim());
2401
- if (!t) throw new TemplateError(`Unknown imported template: ${found[1].trim()}`);
2402
- content = content.replace(found[0], await renderTemplate(t, d, fetch2));
2403
- }
2404
- while (!!(found = /\{\{\s*?\*\s*?(.+?)\s+in\s+(.+?)\s*?}}([\s\S]*?)\{\{\s*?\/\*\s*?}}/g.exec(content))) {
2405
- const split = found[1].replaceAll(/[()\s]/g, "").split(",");
2406
- const element = split[0];
2407
- const index = split[1] || "index";
2408
- const array = dotNotation(d, found[2]);
2409
- if (!array || typeof array != "object") throw new TemplateError(`Cannot iterate: ${found[2]}`);
2410
- let compiled = [];
2411
- for (let i = 0; i < array.length; i++)
2412
- compiled.push(await renderTemplate(found[3], { ...d, [element]: array[i], [index]: i }, fetch2));
2413
- content = content.replace(found[0], compiled.join("\n"));
2414
- }
2415
- while (!!(found = /\{\{\s*([^<>\*\?!/}\s][^}]*?)\s*}}/g.exec(content))) {
2416
- content = content.replace(found[0], evaluate(found[1].trim(), d) ?? "");
2417
- }
2418
- while (!!(found = /\{\{\s*?>\s*?(.+?):(.+?)\s*?}}([\s\S]*?)\{\{\s*?\/>\s*?}}/g.exec(content))) {
2419
- const t = await fetch2(found[1].trim());
2420
- if (!t) throw new TemplateError(`Unknown extended templated: ${found[1].trim()}`);
2421
- content = content.replace(found[0], await renderTemplate(t, {
2422
- ...d,
2423
- [found[2].trim()]: found[3]
2424
- }, fetch2));
2425
- }
2426
- return content;
2496
+ return -1;
2497
+ }
2498
+ function parseIfBranches(body) {
2499
+ const parts = body.split(/\{\{\s*!\?\s*/);
2500
+ const result = { if: parts[0], elseIf: [], else: "" };
2501
+ for (let i = 1; i < parts.length; i++) {
2502
+ const closeBrace = parts[i].indexOf("}}");
2503
+ const condition = parts[i].slice(0, closeBrace).trim();
2504
+ const branchBody = parts[i].slice(closeBrace + 2);
2505
+ if (!condition) result.else = branchBody;
2506
+ else result.elseIf.push({ condition, body: branchBody });
2507
+ }
2508
+ return result;
2509
+ }
2510
+ return process(template);
2427
2511
  }
2428
2512
  var dist = {};
2429
2513
  var persist = {};