@o3co/ts.hocon 1.1.0 → 1.2.0
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/README.ja.md +10 -4
- package/README.md +18 -4
- package/dist/index.cjs +325 -162
- package/dist/index.js +325 -162
- package/dist/zod.cjs +325 -162
- package/dist/zod.js +325 -162
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -165,6 +165,23 @@ var ConfigError = class extends Error {
|
|
|
165
165
|
}
|
|
166
166
|
};
|
|
167
167
|
|
|
168
|
+
// src/value/numeric-array.ts
|
|
169
|
+
function numericObjectToArray(value) {
|
|
170
|
+
if (value.kind !== "object") return null;
|
|
171
|
+
if (value.fields.size === 0) return null;
|
|
172
|
+
const canonicalInt = /^(0|[1-9][0-9]*)$/;
|
|
173
|
+
const eligible = [];
|
|
174
|
+
for (const [key, val] of value.fields) {
|
|
175
|
+
if (!canonicalInt.test(key)) continue;
|
|
176
|
+
const n = Number(key);
|
|
177
|
+
if (!Number.isInteger(n) || n > 2147483647) continue;
|
|
178
|
+
eligible.push({ n, v: val });
|
|
179
|
+
}
|
|
180
|
+
if (eligible.length === 0) return null;
|
|
181
|
+
eligible.sort((a, b) => a.n - b.n);
|
|
182
|
+
return eligible.map((e) => e.v);
|
|
183
|
+
}
|
|
184
|
+
|
|
168
185
|
// src/config.ts
|
|
169
186
|
var Config = class _Config {
|
|
170
187
|
constructor(root) {
|
|
@@ -218,6 +235,10 @@ var Config = class _Config {
|
|
|
218
235
|
getList(path2) {
|
|
219
236
|
const v = this.lookupNode(path2);
|
|
220
237
|
if (v === void 0) throw new ConfigError(`path not found: ${path2}`, path2);
|
|
238
|
+
if (v.kind === "object") {
|
|
239
|
+
const converted = numericObjectToArray(v);
|
|
240
|
+
if (converted !== null) return converted.map(hoconToJs);
|
|
241
|
+
}
|
|
221
242
|
if (v.kind !== "array") throw new ConfigError(`expected array at ${path2}`, path2);
|
|
222
243
|
return v.items.map(hoconToJs);
|
|
223
244
|
}
|
|
@@ -330,6 +351,20 @@ function deepMergeHocon(receiver, fallback) {
|
|
|
330
351
|
}
|
|
331
352
|
|
|
332
353
|
// src/internal/lexer/lexer.ts
|
|
354
|
+
function isHoconWhitespace(ch) {
|
|
355
|
+
const cp = ch.codePointAt(0) ?? -1;
|
|
356
|
+
if (cp === 9 || cp === 10 || cp === 11 || cp === 12 || cp === 13) return true;
|
|
357
|
+
if (cp >= 28 && cp <= 31) return true;
|
|
358
|
+
if (cp === 32 || cp === 160 || cp === 65279) return true;
|
|
359
|
+
if (cp === 5760) return true;
|
|
360
|
+
if (cp >= 8192 && cp <= 8202) return true;
|
|
361
|
+
if (cp === 8232 || cp === 8233 || cp === 8239 || cp === 8287) return true;
|
|
362
|
+
if (cp === 12288) return true;
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
function isHoconNewline(ch) {
|
|
366
|
+
return ch === "\n";
|
|
367
|
+
}
|
|
333
368
|
var SINGLE_CHAR_TOKENS = {
|
|
334
369
|
"{": "lbrace",
|
|
335
370
|
"}": "rbrace",
|
|
@@ -352,18 +387,18 @@ var Lexer = class {
|
|
|
352
387
|
while (this.pos < this.input.length) {
|
|
353
388
|
const sl = this.line, sc = this.col;
|
|
354
389
|
const ch = this.peek();
|
|
355
|
-
if (ch
|
|
356
|
-
this.advance();
|
|
357
|
-
this.hadSpace = true;
|
|
358
|
-
continue;
|
|
359
|
-
}
|
|
360
|
-
if (ch === "\n") {
|
|
390
|
+
if (isHoconNewline(ch)) {
|
|
361
391
|
this.advance();
|
|
362
392
|
if (this.tokens[this.tokens.length - 1]?.kind !== "newline") {
|
|
363
393
|
this.push("newline", "\n", sl, sc);
|
|
364
394
|
}
|
|
365
395
|
continue;
|
|
366
396
|
}
|
|
397
|
+
if (isHoconWhitespace(ch)) {
|
|
398
|
+
this.advance();
|
|
399
|
+
this.hadSpace = true;
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
367
402
|
if (ch === "/" && this.peek(1) === "/") {
|
|
368
403
|
while (this.pos < this.input.length && this.peek() !== "\n") this.advance();
|
|
369
404
|
this.hadSpace = true;
|
|
@@ -393,16 +428,16 @@ var Lexer = class {
|
|
|
393
428
|
if (ch === "$" && this.peek(1) === "{") {
|
|
394
429
|
this.advance();
|
|
395
430
|
this.advance();
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
this.
|
|
431
|
+
const payload = this.parseSubstBody(sl, sc);
|
|
432
|
+
const value = payload.segments.map((s) => {
|
|
433
|
+
const t = s.text;
|
|
434
|
+
if (t === "" || t.includes(".") || t.includes(" ") || t.includes(" ") || t.includes('"') || t.includes("\\") || t !== t.trim()) {
|
|
435
|
+
const escaped = t.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
436
|
+
return `"${escaped}"`;
|
|
437
|
+
}
|
|
438
|
+
return t;
|
|
439
|
+
}).join(".");
|
|
440
|
+
this.pushSubst(payload, value, sl, sc);
|
|
406
441
|
continue;
|
|
407
442
|
}
|
|
408
443
|
if (ch === '"' && this.peek(1) === '"' && this.peek(2) === '"') {
|
|
@@ -437,62 +472,19 @@ var Lexer = class {
|
|
|
437
472
|
}
|
|
438
473
|
if (ch === '"') {
|
|
439
474
|
this.advance();
|
|
440
|
-
|
|
441
|
-
while (this.pos < this.input.length && this.peek() !== '"') {
|
|
442
|
-
if (this.peek() === "\n") throw new ParseError("unterminated string", sl, sc);
|
|
443
|
-
if (this.peek() === "\\") {
|
|
444
|
-
this.advance();
|
|
445
|
-
const esc = this.advance();
|
|
446
|
-
switch (esc) {
|
|
447
|
-
case "n":
|
|
448
|
-
value += "\n";
|
|
449
|
-
break;
|
|
450
|
-
case "t":
|
|
451
|
-
value += " ";
|
|
452
|
-
break;
|
|
453
|
-
case "r":
|
|
454
|
-
value += "\r";
|
|
455
|
-
break;
|
|
456
|
-
case '"':
|
|
457
|
-
value += '"';
|
|
458
|
-
break;
|
|
459
|
-
case "\\":
|
|
460
|
-
value += "\\";
|
|
461
|
-
break;
|
|
462
|
-
case "/":
|
|
463
|
-
value += "/";
|
|
464
|
-
break;
|
|
465
|
-
case "b":
|
|
466
|
-
value += "\b";
|
|
467
|
-
break;
|
|
468
|
-
case "f":
|
|
469
|
-
value += "\f";
|
|
470
|
-
break;
|
|
471
|
-
case "u": {
|
|
472
|
-
if (this.pos + 4 > this.input.length) {
|
|
473
|
-
throw new ParseError("Invalid unicode escape: not enough characters", this.line, this.col);
|
|
474
|
-
}
|
|
475
|
-
const hex = this.input.slice(this.pos, this.pos + 4);
|
|
476
|
-
if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
|
|
477
|
-
throw new ParseError(`Invalid unicode escape: \\u${hex}`, this.line, this.col);
|
|
478
|
-
}
|
|
479
|
-
value += String.fromCharCode(parseInt(hex, 16));
|
|
480
|
-
for (let i = 0; i < 4; i++) this.advance();
|
|
481
|
-
break;
|
|
482
|
-
}
|
|
483
|
-
default:
|
|
484
|
-
throw new ParseError(`Unknown escape sequence: \\${esc}`, this.line, this.col);
|
|
485
|
-
}
|
|
486
|
-
} else {
|
|
487
|
-
value += this.advance();
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
if (this.peek() !== '"') throw new ParseError("unterminated string", sl, sc);
|
|
491
|
-
this.advance();
|
|
475
|
+
const value = this.readQuotedStringBody(sl, sc);
|
|
492
476
|
this.push("string", value, sl, sc, true);
|
|
493
477
|
continue;
|
|
494
478
|
}
|
|
495
479
|
if (isUnquotedStart(ch)) {
|
|
480
|
+
if (ch === "-" && !isDecimalDigit(this.peek(1))) {
|
|
481
|
+
const after = this.peek(1) === "" ? "EOF" : JSON.stringify(this.peek(1));
|
|
482
|
+
throw new ParseError(
|
|
483
|
+
`unquoted string cannot begin with '-' unless followed by a digit (got '-' then ${after}, HOCON.md L270-276)`,
|
|
484
|
+
sl,
|
|
485
|
+
sc
|
|
486
|
+
);
|
|
487
|
+
}
|
|
496
488
|
let value = "";
|
|
497
489
|
while (this.pos < this.input.length && isUnquotedContinue(this.peek(), () => this.peek(1))) {
|
|
498
490
|
value += this.advance();
|
|
@@ -522,22 +514,201 @@ var Lexer = class {
|
|
|
522
514
|
this.tokens.push({ kind, value, line: l, col: c, isQuoted, precedingSpace: this.hadSpace });
|
|
523
515
|
this.hadSpace = false;
|
|
524
516
|
}
|
|
517
|
+
pushSubst(payload, value, l, c) {
|
|
518
|
+
this.tokens.push({ kind: "subst", value, line: l, col: c, isQuoted: false, precedingSpace: this.hadSpace, subst: payload });
|
|
519
|
+
this.hadSpace = false;
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Read body of a quoted string. Opening `"` must already be consumed.
|
|
523
|
+
* Returns decoded string. Throws ParseError on unterminated/invalid-escape.
|
|
524
|
+
* openLine/openCol are the position of the opening `"` for error reporting.
|
|
525
|
+
*/
|
|
526
|
+
readQuotedStringBody(openLine, openCol) {
|
|
527
|
+
let value = "";
|
|
528
|
+
while (this.pos < this.input.length && this.peek() !== '"') {
|
|
529
|
+
if (this.peek() === "\n") throw new ParseError("unterminated string", openLine, openCol);
|
|
530
|
+
if (this.peek() === "\\") {
|
|
531
|
+
const escCol = this.col;
|
|
532
|
+
this.advance();
|
|
533
|
+
if (this.pos >= this.input.length) {
|
|
534
|
+
throw new ParseError("unterminated string", openLine, openCol);
|
|
535
|
+
}
|
|
536
|
+
const esc = this.advance();
|
|
537
|
+
switch (esc) {
|
|
538
|
+
case "n":
|
|
539
|
+
value += "\n";
|
|
540
|
+
break;
|
|
541
|
+
case "t":
|
|
542
|
+
value += " ";
|
|
543
|
+
break;
|
|
544
|
+
case "r":
|
|
545
|
+
value += "\r";
|
|
546
|
+
break;
|
|
547
|
+
case '"':
|
|
548
|
+
value += '"';
|
|
549
|
+
break;
|
|
550
|
+
case "\\":
|
|
551
|
+
value += "\\";
|
|
552
|
+
break;
|
|
553
|
+
case "/":
|
|
554
|
+
value += "/";
|
|
555
|
+
break;
|
|
556
|
+
case "b":
|
|
557
|
+
value += "\b";
|
|
558
|
+
break;
|
|
559
|
+
case "f":
|
|
560
|
+
value += "\f";
|
|
561
|
+
break;
|
|
562
|
+
case "u": {
|
|
563
|
+
if (this.pos + 4 > this.input.length) {
|
|
564
|
+
throw new ParseError("invalid unicode escape", openLine, escCol);
|
|
565
|
+
}
|
|
566
|
+
const hex = this.input.slice(this.pos, this.pos + 4);
|
|
567
|
+
if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
|
|
568
|
+
throw new ParseError("invalid unicode escape", openLine, escCol);
|
|
569
|
+
}
|
|
570
|
+
const code = parseInt(hex, 16);
|
|
571
|
+
value += String.fromCharCode(code);
|
|
572
|
+
for (let i = 0; i < 4; i++) this.advance();
|
|
573
|
+
break;
|
|
574
|
+
}
|
|
575
|
+
default:
|
|
576
|
+
throw new ParseError(`unknown escape sequence: \\${esc}`, openLine, escCol);
|
|
577
|
+
}
|
|
578
|
+
} else {
|
|
579
|
+
value += this.advance();
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
if (this.pos >= this.input.length || this.peek() !== '"') {
|
|
583
|
+
throw new ParseError("unterminated string", openLine, openCol);
|
|
584
|
+
}
|
|
585
|
+
this.advance();
|
|
586
|
+
return value;
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Parse the body of a `${...}` substitution (called after `${` has been consumed).
|
|
590
|
+
* Implements Appendix A state machine from the cross-language spec.
|
|
591
|
+
*/
|
|
592
|
+
parseSubstBody(startLine, startCol) {
|
|
593
|
+
let optional = false;
|
|
594
|
+
if (this.peek() === "?") {
|
|
595
|
+
this.advance();
|
|
596
|
+
optional = true;
|
|
597
|
+
}
|
|
598
|
+
let curText = "";
|
|
599
|
+
let curStarted = false;
|
|
600
|
+
let curLine = 0;
|
|
601
|
+
let curCol = 0;
|
|
602
|
+
let pendingWs = "";
|
|
603
|
+
const segments = [];
|
|
604
|
+
let lastDot = null;
|
|
605
|
+
while (true) {
|
|
606
|
+
if (this.pos >= this.input.length) {
|
|
607
|
+
throw new ParseError("unterminated substitution", startLine, startCol);
|
|
608
|
+
}
|
|
609
|
+
const ch = this.peek();
|
|
610
|
+
if (ch === "}") {
|
|
611
|
+
this.advance();
|
|
612
|
+
pendingWs = "";
|
|
613
|
+
break;
|
|
614
|
+
} else if (ch === '"') {
|
|
615
|
+
const qLine = startLine;
|
|
616
|
+
const qCol = this.col;
|
|
617
|
+
if (curStarted) {
|
|
618
|
+
curText += pendingWs;
|
|
619
|
+
}
|
|
620
|
+
pendingWs = "";
|
|
621
|
+
this.advance();
|
|
622
|
+
const decoded = this.readQuotedStringBody(qLine, qCol);
|
|
623
|
+
curText += decoded;
|
|
624
|
+
if (!curStarted) {
|
|
625
|
+
curLine = qLine;
|
|
626
|
+
curCol = qCol;
|
|
627
|
+
curStarted = true;
|
|
628
|
+
}
|
|
629
|
+
} else if (isUnquotedSubstChar(ch)) {
|
|
630
|
+
if (ch === "-" && !curStarted && !isDecimalDigit(this.peek(1))) {
|
|
631
|
+
const after = this.peek(1) === "" ? "EOF" : JSON.stringify(this.peek(1));
|
|
632
|
+
throw new ParseError(
|
|
633
|
+
`unquoted path segment cannot begin with '-' unless followed by a digit (got '-' then ${after}, HOCON.md L270-276)`,
|
|
634
|
+
startLine,
|
|
635
|
+
this.col
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
const uCol = this.col;
|
|
639
|
+
if (curStarted) {
|
|
640
|
+
curText += pendingWs;
|
|
641
|
+
}
|
|
642
|
+
pendingWs = "";
|
|
643
|
+
if (!curStarted) {
|
|
644
|
+
curLine = startLine;
|
|
645
|
+
curCol = uCol;
|
|
646
|
+
curStarted = true;
|
|
647
|
+
}
|
|
648
|
+
while (this.pos < this.input.length && isUnquotedSubstChar(this.peek())) {
|
|
649
|
+
curText += this.advance();
|
|
650
|
+
}
|
|
651
|
+
} else if (ch === ".") {
|
|
652
|
+
const dotCol = this.col;
|
|
653
|
+
pendingWs = "";
|
|
654
|
+
if (!curStarted) {
|
|
655
|
+
throw new ParseError("empty segment in path", startLine, dotCol);
|
|
656
|
+
}
|
|
657
|
+
segments.push({ text: curText, line: curLine, col: curCol });
|
|
658
|
+
curText = "";
|
|
659
|
+
curStarted = false;
|
|
660
|
+
curLine = 0;
|
|
661
|
+
curCol = 0;
|
|
662
|
+
lastDot = [startLine, dotCol];
|
|
663
|
+
this.advance();
|
|
664
|
+
} else if (isHoconNewline(ch)) {
|
|
665
|
+
throw new ParseError("unterminated substitution", startLine, startCol);
|
|
666
|
+
} else if (isHoconWhitespace(ch)) {
|
|
667
|
+
pendingWs += ch;
|
|
668
|
+
this.advance();
|
|
669
|
+
} else {
|
|
670
|
+
throw new ParseError(`unexpected character in substitution path: ${JSON.stringify(ch)}`, startLine, this.col);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
if (curStarted) {
|
|
674
|
+
segments.push({ text: curText, line: curLine, col: curCol });
|
|
675
|
+
} else if (segments.length === 0) {
|
|
676
|
+
throw new ParseError("empty substitution path", startLine, startCol);
|
|
677
|
+
} else {
|
|
678
|
+
const [errLine, errCol] = lastDot ?? [startLine, startCol];
|
|
679
|
+
throw new ParseError("empty segment in path", errLine, errCol);
|
|
680
|
+
}
|
|
681
|
+
return { segments, optional };
|
|
682
|
+
}
|
|
525
683
|
};
|
|
526
684
|
function tokenize(input) {
|
|
527
685
|
return new Lexer(input).tokenize();
|
|
528
686
|
}
|
|
687
|
+
function isUnquotedSubstChar(ch) {
|
|
688
|
+
if (ch === "" || isHoconWhitespace(ch)) return false;
|
|
689
|
+
if ('"\\'.includes(ch)) return false;
|
|
690
|
+
if ("{}[]".includes(ch)) return false;
|
|
691
|
+
if (":=,+#`^?!@*&$.".includes(ch)) return false;
|
|
692
|
+
return true;
|
|
693
|
+
}
|
|
694
|
+
function isDecimalDigit(ch) {
|
|
695
|
+
return ch >= "0" && ch <= "9";
|
|
696
|
+
}
|
|
529
697
|
function isUnquotedStart(ch) {
|
|
530
|
-
|
|
698
|
+
if (ch === "" || isHoconWhitespace(ch)) return false;
|
|
699
|
+
if ('{}[],:=+#"$?!@*&^\\'.includes(ch)) return false;
|
|
700
|
+
return true;
|
|
531
701
|
}
|
|
532
702
|
function isUnquotedContinue(ch, nextFn) {
|
|
533
|
-
if (ch === "" ||
|
|
703
|
+
if (ch === "" || isHoconWhitespace(ch)) return false;
|
|
704
|
+
if ('{}[],:=#"$?!@*&^\\'.includes(ch)) return false;
|
|
534
705
|
if (ch === "+" && nextFn() === "=") return false;
|
|
535
706
|
if (ch === "/" && nextFn() === "/") return false;
|
|
536
707
|
return true;
|
|
537
708
|
}
|
|
538
709
|
|
|
539
710
|
// src/internal/parser/parser.ts
|
|
540
|
-
var EOF_TOKEN = { kind: "eof", value: "", line: 0, col: 0, isQuoted: false, precedingSpace: false };
|
|
711
|
+
var EOF_TOKEN = { kind: "eof", value: "", line: 0, col: 0, isQuoted: false, precedingSpace: false, subst: void 0 };
|
|
541
712
|
var Parser = class {
|
|
542
713
|
constructor(tokens) {
|
|
543
714
|
this.tokens = tokens;
|
|
@@ -641,10 +812,21 @@ var Parser = class {
|
|
|
641
812
|
trailingDot = false;
|
|
642
813
|
} else if (t.kind === "unquoted") {
|
|
643
814
|
this.advance();
|
|
644
|
-
const
|
|
815
|
+
const raw = t.value.startsWith(".") && segments.length > 0 && !t.precedingSpace ? t.value.slice(1) : t.value;
|
|
816
|
+
const parts = raw.split(".");
|
|
645
817
|
const filtered = parts.filter((s) => s.length > 0);
|
|
818
|
+
for (const seg of filtered) {
|
|
819
|
+
if (seg[0] === "-" && !(seg.length >= 2 && seg[1] >= "0" && seg[1] <= "9")) {
|
|
820
|
+
const after = seg.length >= 2 ? JSON.stringify(seg[1]) : "EOF";
|
|
821
|
+
throw new ParseError(
|
|
822
|
+
`unquoted key segment cannot begin with '-' unless followed by a digit (got '-' then ${after} in ${JSON.stringify(seg)}, HOCON.md L270-276)`,
|
|
823
|
+
t.line,
|
|
824
|
+
t.col
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
}
|
|
646
828
|
segments.push(...filtered);
|
|
647
|
-
trailingDot =
|
|
829
|
+
trailingDot = raw.endsWith(".");
|
|
648
830
|
} else {
|
|
649
831
|
if (segments.length === 0) throw new ParseError(`expected key, got ${t.kind}`, t.line, t.col);
|
|
650
832
|
break;
|
|
@@ -656,6 +838,10 @@ var Parser = class {
|
|
|
656
838
|
trailingDot = true;
|
|
657
839
|
continue;
|
|
658
840
|
}
|
|
841
|
+
if (next.kind === "unquoted" && next.value.startsWith(".") && !next.precedingSpace) {
|
|
842
|
+
trailingDot = true;
|
|
843
|
+
continue;
|
|
844
|
+
}
|
|
659
845
|
break;
|
|
660
846
|
}
|
|
661
847
|
return segments;
|
|
@@ -731,9 +917,13 @@ var Parser = class {
|
|
|
731
917
|
} else if (t.kind === "lbracket") {
|
|
732
918
|
this.advance();
|
|
733
919
|
node = this.parseArray();
|
|
734
|
-
} else if (t.kind === "subst"
|
|
920
|
+
} else if (t.kind === "subst") {
|
|
735
921
|
this.advance();
|
|
736
|
-
|
|
922
|
+
if (!t.subst) {
|
|
923
|
+
throw new ParseError(`internal: subst token missing payload`, t.line, t.col);
|
|
924
|
+
}
|
|
925
|
+
const payload = t.subst;
|
|
926
|
+
node = { kind: "subst", segments: payload.segments, optional: payload.optional, pos: { line: t.line, col: t.col } };
|
|
737
927
|
} else if (t.kind === "string" || t.kind === "triple_string") {
|
|
738
928
|
this.advance();
|
|
739
929
|
node = { kind: "scalar", raw: t.value, valueType: "string", pos: { line: t.line, col: t.col } };
|
|
@@ -803,64 +993,20 @@ function makeResObj() {
|
|
|
803
993
|
}
|
|
804
994
|
|
|
805
995
|
// src/internal/resolver/utils.ts
|
|
806
|
-
function parseSubstPath(raw) {
|
|
807
|
-
const segments = [];
|
|
808
|
-
let i = 0;
|
|
809
|
-
while (i < raw.length) {
|
|
810
|
-
while (i < raw.length && (raw[i] === " " || raw[i] === " ")) i++;
|
|
811
|
-
if (i >= raw.length) break;
|
|
812
|
-
if (raw[i] === '"') {
|
|
813
|
-
i++;
|
|
814
|
-
let seg = "";
|
|
815
|
-
while (i < raw.length && raw[i] !== '"') {
|
|
816
|
-
if (raw[i] === "\\" && i + 1 < raw.length && (raw[i + 1] === '"' || raw[i + 1] === "\\")) {
|
|
817
|
-
seg += raw[i + 1];
|
|
818
|
-
i += 2;
|
|
819
|
-
} else {
|
|
820
|
-
seg += raw[i];
|
|
821
|
-
i++;
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
if (i < raw.length) i++;
|
|
825
|
-
segments.push(seg);
|
|
826
|
-
while (i < raw.length && (raw[i] === " " || raw[i] === " ")) i++;
|
|
827
|
-
if (i < raw.length && raw[i] === ".") i++;
|
|
828
|
-
} else if (raw[i] === ".") {
|
|
829
|
-
segments.push("");
|
|
830
|
-
i++;
|
|
831
|
-
} else {
|
|
832
|
-
let seg = "";
|
|
833
|
-
while (i < raw.length && raw[i] !== ".") {
|
|
834
|
-
seg += raw[i];
|
|
835
|
-
i++;
|
|
836
|
-
}
|
|
837
|
-
segments.push(seg.trim());
|
|
838
|
-
if (i < raw.length && raw[i] === ".") i++;
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
return segments;
|
|
842
|
-
}
|
|
843
996
|
function segmentsToKey(segments) {
|
|
844
997
|
return segments.map((s) => {
|
|
845
|
-
|
|
846
|
-
|
|
998
|
+
const t = s.text;
|
|
999
|
+
if (t === "" || /[^a-zA-Z0-9\-_]/.test(t)) {
|
|
1000
|
+
return `"${t.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
847
1001
|
}
|
|
848
|
-
return
|
|
1002
|
+
return t;
|
|
849
1003
|
}).join(".");
|
|
850
1004
|
}
|
|
851
1005
|
function lookupPath(root, segments) {
|
|
852
1006
|
const [head, ...tail] = segments;
|
|
853
|
-
if (head === void 0
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
if (val2 === void 0) return void 0;
|
|
857
|
-
if (tail.length === 0) return val2;
|
|
858
|
-
if (isResObj(val2)) return lookupPath(val2, tail);
|
|
859
|
-
return void 0;
|
|
860
|
-
}
|
|
861
|
-
return void 0;
|
|
862
|
-
}
|
|
863
|
-
const val = root.fields.get(head);
|
|
1007
|
+
if (head === void 0) return void 0;
|
|
1008
|
+
const key = head.text;
|
|
1009
|
+
const val = root.fields.get(key);
|
|
864
1010
|
if (val === void 0) return void 0;
|
|
865
1011
|
if (tail.length === 0) return val;
|
|
866
1012
|
if (isResObj(val)) return lookupPath(val, tail);
|
|
@@ -869,7 +1015,7 @@ function lookupPath(root, segments) {
|
|
|
869
1015
|
function lookupResObj(root, segments) {
|
|
870
1016
|
let cur = root;
|
|
871
1017
|
for (const seg of segments) {
|
|
872
|
-
const val = cur.fields.get(seg);
|
|
1018
|
+
const val = cur.fields.get(seg.text);
|
|
873
1019
|
if (val === void 0 || !isResObj(val)) return void 0;
|
|
874
1020
|
cur = val;
|
|
875
1021
|
}
|
|
@@ -1146,7 +1292,7 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1146
1292
|
return;
|
|
1147
1293
|
}
|
|
1148
1294
|
const [head, ...tail] = field.key;
|
|
1149
|
-
if (
|
|
1295
|
+
if (head === void 0) return;
|
|
1150
1296
|
if (tail.length > 0) {
|
|
1151
1297
|
const syntheticAst = {
|
|
1152
1298
|
kind: "object",
|
|
@@ -1188,7 +1334,7 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1188
1334
|
return;
|
|
1189
1335
|
}
|
|
1190
1336
|
const [head, ...tail] = field.key;
|
|
1191
|
-
if (
|
|
1337
|
+
if (head === void 0) return;
|
|
1192
1338
|
if (tail.length > 0) {
|
|
1193
1339
|
const syntheticAst = {
|
|
1194
1340
|
kind: "object",
|
|
@@ -1234,7 +1380,7 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1234
1380
|
return inner;
|
|
1235
1381
|
}
|
|
1236
1382
|
case "subst":
|
|
1237
|
-
return { _kind: "subst-placeholder", segments:
|
|
1383
|
+
return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
|
|
1238
1384
|
case "concat":
|
|
1239
1385
|
return { _kind: "concat-placeholder", nodes: ast.nodes.map((n) => this.astToResolverValue(n, pathPrefix)) };
|
|
1240
1386
|
case "include":
|
|
@@ -1258,7 +1404,7 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1258
1404
|
case "object":
|
|
1259
1405
|
return await this.buildAsync(ast, pathPrefix);
|
|
1260
1406
|
case "subst":
|
|
1261
|
-
return { _kind: "subst-placeholder", segments:
|
|
1407
|
+
return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
|
|
1262
1408
|
case "concat": {
|
|
1263
1409
|
const nodes = [];
|
|
1264
1410
|
for (const n of ast.nodes) {
|
|
@@ -1273,7 +1419,8 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1273
1419
|
// ---- Relativize substitution paths for nested includes ----
|
|
1274
1420
|
relativizeSubstPaths(val, prefixSegments) {
|
|
1275
1421
|
if (isSubst(val)) {
|
|
1276
|
-
|
|
1422
|
+
const prefixAsSegments = prefixSegments.map((text) => ({ text, line: 0, col: 0 }));
|
|
1423
|
+
val.segments = [...prefixAsSegments, ...val.segments];
|
|
1277
1424
|
val.prefixLen += prefixSegments.length;
|
|
1278
1425
|
return;
|
|
1279
1426
|
}
|
|
@@ -1369,7 +1516,7 @@ var SubstitutionResolver = class {
|
|
|
1369
1516
|
return hv;
|
|
1370
1517
|
}
|
|
1371
1518
|
segmentsEqual(a, b) {
|
|
1372
|
-
return a.length === b.length && a.every((seg, i) => seg === b[i]);
|
|
1519
|
+
return a.length === b.length && a.every((seg, i) => seg.text === b[i]?.text);
|
|
1373
1520
|
}
|
|
1374
1521
|
resolveSubst(s, scope) {
|
|
1375
1522
|
const key = segmentsToKey(s.segments);
|
|
@@ -1377,14 +1524,14 @@ var SubstitutionResolver = class {
|
|
|
1377
1524
|
if (this.resolving.has(key)) {
|
|
1378
1525
|
let prior;
|
|
1379
1526
|
if (s.prefixLen > 0) {
|
|
1380
|
-
const leafSeg = s.segments[s.segments.length - 1] ?? "";
|
|
1527
|
+
const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
|
|
1381
1528
|
const parentScope = lookupResObj(
|
|
1382
1529
|
this.root,
|
|
1383
1530
|
s.segments.slice(0, s.segments.length - 1)
|
|
1384
1531
|
);
|
|
1385
1532
|
prior = parentScope?.priorValues.get(leafSeg);
|
|
1386
1533
|
} else {
|
|
1387
|
-
const rootSeg = s.segments[0] ?? "";
|
|
1534
|
+
const rootSeg = s.segments[0]?.text ?? "";
|
|
1388
1535
|
prior = scope.priorValues.get(rootSeg) ?? this.root.priorValues.get(rootSeg);
|
|
1389
1536
|
}
|
|
1390
1537
|
if (prior !== void 0) {
|
|
@@ -1415,14 +1562,14 @@ var SubstitutionResolver = class {
|
|
|
1415
1562
|
if (isSelfRef) {
|
|
1416
1563
|
let prior;
|
|
1417
1564
|
if (s.prefixLen > 0) {
|
|
1418
|
-
const leafSeg = s.segments[s.segments.length - 1] ?? "";
|
|
1565
|
+
const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
|
|
1419
1566
|
const parentScope = lookupResObj(
|
|
1420
1567
|
this.root,
|
|
1421
1568
|
s.segments.slice(0, s.segments.length - 1)
|
|
1422
1569
|
);
|
|
1423
1570
|
prior = parentScope?.priorValues.get(leafSeg);
|
|
1424
1571
|
} else {
|
|
1425
|
-
const rootSeg = s.segments[0] ?? "";
|
|
1572
|
+
const rootSeg = s.segments[0]?.text ?? "";
|
|
1426
1573
|
prior = scope.priorValues.get(rootSeg) ?? this.root.priorValues.get(rootSeg);
|
|
1427
1574
|
}
|
|
1428
1575
|
if (prior !== void 0) {
|
|
@@ -1435,7 +1582,7 @@ var SubstitutionResolver = class {
|
|
|
1435
1582
|
let result = this.resolveVal(found, scope);
|
|
1436
1583
|
const effectiveLen = s.segments.length - s.prefixLen;
|
|
1437
1584
|
if (effectiveLen === 1 && result !== void 0 && result.kind === "object") {
|
|
1438
|
-
const leafSeg = s.segments[s.segments.length - 1] ?? "";
|
|
1585
|
+
const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
|
|
1439
1586
|
let prior;
|
|
1440
1587
|
if (s.prefixLen > 0) {
|
|
1441
1588
|
const parentScope = lookupResObj(
|
|
@@ -1459,8 +1606,8 @@ var SubstitutionResolver = class {
|
|
|
1459
1606
|
if (result !== void 0) this.cache.set(key, result);
|
|
1460
1607
|
return result;
|
|
1461
1608
|
}
|
|
1462
|
-
const envKey = s.segments.join(".");
|
|
1463
|
-
const envVal = this.opts.env[envKey] ?? (s.prefixLen > 0 ? this.opts.env[s.segments.slice(s.prefixLen).join(".")] : void 0);
|
|
1609
|
+
const envKey = s.segments.map((seg) => seg.text).join(".");
|
|
1610
|
+
const envVal = this.opts.env[envKey] ?? (s.prefixLen > 0 ? this.opts.env[s.segments.slice(s.prefixLen).map((seg) => seg.text).join(".")] : void 0);
|
|
1464
1611
|
if (envVal !== void 0) {
|
|
1465
1612
|
const result = { kind: "scalar", raw: envVal, valueType: "string" };
|
|
1466
1613
|
this.cache.set(key, result);
|
|
@@ -1482,32 +1629,48 @@ var SubstitutionResolver = class {
|
|
|
1482
1629
|
if (resolved.length === 0) return { kind: "scalar", raw: "null", valueType: "null" };
|
|
1483
1630
|
if (resolved.length === 1) return resolved[0];
|
|
1484
1631
|
const nonSep = resolved.filter((v) => !separatorValues.has(v));
|
|
1485
|
-
if (nonSep.length
|
|
1486
|
-
const
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
}
|
|
1632
|
+
if (nonSep.length === 0) {
|
|
1633
|
+
const str = resolved.map((v) => v.kind === "scalar" ? v.raw : JSON.stringify(v)).join("");
|
|
1634
|
+
return { kind: "scalar", raw: str, valueType: "string" };
|
|
1635
|
+
}
|
|
1636
|
+
const joinPair = (left, right) => {
|
|
1637
|
+
if (left.kind === "object" && right.kind === "object") {
|
|
1638
|
+
return deepMergeHoconValues(left, right);
|
|
1639
|
+
}
|
|
1640
|
+
if (left.kind === "array" && right.kind === "object") {
|
|
1641
|
+
const converted = numericObjectToArray(right);
|
|
1642
|
+
if (converted !== null) {
|
|
1643
|
+
return { kind: "array", items: [...left.items, ...converted] };
|
|
1497
1644
|
}
|
|
1645
|
+
return { kind: "array", items: [...left.items, right] };
|
|
1498
1646
|
}
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1647
|
+
if (left.kind === "object" && right.kind === "array") {
|
|
1648
|
+
const converted = numericObjectToArray(left);
|
|
1649
|
+
if (converted !== null) {
|
|
1650
|
+
return { kind: "array", items: [...converted, ...right.items] };
|
|
1651
|
+
}
|
|
1652
|
+
return { kind: "array", items: [left, ...right.items] };
|
|
1653
|
+
}
|
|
1654
|
+
if (left.kind === "array" && right.kind === "array") {
|
|
1655
|
+
return { kind: "array", items: [...left.items, ...right.items] };
|
|
1506
1656
|
}
|
|
1507
|
-
|
|
1657
|
+
if (left.kind === "array") {
|
|
1658
|
+
return { kind: "array", items: [...left.items, right] };
|
|
1659
|
+
}
|
|
1660
|
+
if (right.kind === "array") {
|
|
1661
|
+
return { kind: "array", items: [left, ...right.items] };
|
|
1662
|
+
}
|
|
1663
|
+
const leftStr = left.kind === "scalar" ? left.raw : JSON.stringify(left);
|
|
1664
|
+
const rightStr = right.kind === "scalar" ? right.raw : JSON.stringify(right);
|
|
1665
|
+
return { kind: "scalar", raw: leftStr + rightStr, valueType: "string" };
|
|
1666
|
+
};
|
|
1667
|
+
const [head, ...tail] = nonSep;
|
|
1668
|
+
const folded = tail.reduce(joinPair, head);
|
|
1669
|
+
if (folded.kind === "scalar") {
|
|
1670
|
+
const str = resolved.map((v) => v.kind === "scalar" ? v.raw : JSON.stringify(v)).join("");
|
|
1671
|
+
return { kind: "scalar", raw: str, valueType: "string" };
|
|
1508
1672
|
}
|
|
1509
|
-
|
|
1510
|
-
return { kind: "scalar", raw: str, valueType: "string" };
|
|
1673
|
+
return folded;
|
|
1511
1674
|
}
|
|
1512
1675
|
resolveAppend(a, scope) {
|
|
1513
1676
|
const existing = this.resolveVal(a.existing, scope) ?? { kind: "array", items: [] };
|