@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/zod.js
CHANGED
|
@@ -166,6 +166,20 @@ var ConfigError = class extends Error {
|
|
|
166
166
|
};
|
|
167
167
|
|
|
168
168
|
// src/internal/lexer/lexer.ts
|
|
169
|
+
function isHoconWhitespace(ch) {
|
|
170
|
+
const cp = ch.codePointAt(0) ?? -1;
|
|
171
|
+
if (cp === 9 || cp === 10 || cp === 11 || cp === 12 || cp === 13) return true;
|
|
172
|
+
if (cp >= 28 && cp <= 31) return true;
|
|
173
|
+
if (cp === 32 || cp === 160 || cp === 65279) return true;
|
|
174
|
+
if (cp === 5760) return true;
|
|
175
|
+
if (cp >= 8192 && cp <= 8202) return true;
|
|
176
|
+
if (cp === 8232 || cp === 8233 || cp === 8239 || cp === 8287) return true;
|
|
177
|
+
if (cp === 12288) return true;
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
function isHoconNewline(ch) {
|
|
181
|
+
return ch === "\n";
|
|
182
|
+
}
|
|
169
183
|
var SINGLE_CHAR_TOKENS = {
|
|
170
184
|
"{": "lbrace",
|
|
171
185
|
"}": "rbrace",
|
|
@@ -188,18 +202,18 @@ var Lexer = class {
|
|
|
188
202
|
while (this.pos < this.input.length) {
|
|
189
203
|
const sl = this.line, sc = this.col;
|
|
190
204
|
const ch = this.peek();
|
|
191
|
-
if (ch
|
|
192
|
-
this.advance();
|
|
193
|
-
this.hadSpace = true;
|
|
194
|
-
continue;
|
|
195
|
-
}
|
|
196
|
-
if (ch === "\n") {
|
|
205
|
+
if (isHoconNewline(ch)) {
|
|
197
206
|
this.advance();
|
|
198
207
|
if (this.tokens[this.tokens.length - 1]?.kind !== "newline") {
|
|
199
208
|
this.push("newline", "\n", sl, sc);
|
|
200
209
|
}
|
|
201
210
|
continue;
|
|
202
211
|
}
|
|
212
|
+
if (isHoconWhitespace(ch)) {
|
|
213
|
+
this.advance();
|
|
214
|
+
this.hadSpace = true;
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
203
217
|
if (ch === "/" && this.peek(1) === "/") {
|
|
204
218
|
while (this.pos < this.input.length && this.peek() !== "\n") this.advance();
|
|
205
219
|
this.hadSpace = true;
|
|
@@ -229,16 +243,16 @@ var Lexer = class {
|
|
|
229
243
|
if (ch === "$" && this.peek(1) === "{") {
|
|
230
244
|
this.advance();
|
|
231
245
|
this.advance();
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
this.
|
|
246
|
+
const payload = this.parseSubstBody(sl, sc);
|
|
247
|
+
const value = payload.segments.map((s) => {
|
|
248
|
+
const t = s.text;
|
|
249
|
+
if (t === "" || t.includes(".") || t.includes(" ") || t.includes(" ") || t.includes('"') || t.includes("\\") || t !== t.trim()) {
|
|
250
|
+
const escaped = t.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
251
|
+
return `"${escaped}"`;
|
|
252
|
+
}
|
|
253
|
+
return t;
|
|
254
|
+
}).join(".");
|
|
255
|
+
this.pushSubst(payload, value, sl, sc);
|
|
242
256
|
continue;
|
|
243
257
|
}
|
|
244
258
|
if (ch === '"' && this.peek(1) === '"' && this.peek(2) === '"') {
|
|
@@ -273,62 +287,19 @@ var Lexer = class {
|
|
|
273
287
|
}
|
|
274
288
|
if (ch === '"') {
|
|
275
289
|
this.advance();
|
|
276
|
-
|
|
277
|
-
while (this.pos < this.input.length && this.peek() !== '"') {
|
|
278
|
-
if (this.peek() === "\n") throw new ParseError("unterminated string", sl, sc);
|
|
279
|
-
if (this.peek() === "\\") {
|
|
280
|
-
this.advance();
|
|
281
|
-
const esc = this.advance();
|
|
282
|
-
switch (esc) {
|
|
283
|
-
case "n":
|
|
284
|
-
value += "\n";
|
|
285
|
-
break;
|
|
286
|
-
case "t":
|
|
287
|
-
value += " ";
|
|
288
|
-
break;
|
|
289
|
-
case "r":
|
|
290
|
-
value += "\r";
|
|
291
|
-
break;
|
|
292
|
-
case '"':
|
|
293
|
-
value += '"';
|
|
294
|
-
break;
|
|
295
|
-
case "\\":
|
|
296
|
-
value += "\\";
|
|
297
|
-
break;
|
|
298
|
-
case "/":
|
|
299
|
-
value += "/";
|
|
300
|
-
break;
|
|
301
|
-
case "b":
|
|
302
|
-
value += "\b";
|
|
303
|
-
break;
|
|
304
|
-
case "f":
|
|
305
|
-
value += "\f";
|
|
306
|
-
break;
|
|
307
|
-
case "u": {
|
|
308
|
-
if (this.pos + 4 > this.input.length) {
|
|
309
|
-
throw new ParseError("Invalid unicode escape: not enough characters", this.line, this.col);
|
|
310
|
-
}
|
|
311
|
-
const hex = this.input.slice(this.pos, this.pos + 4);
|
|
312
|
-
if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
|
|
313
|
-
throw new ParseError(`Invalid unicode escape: \\u${hex}`, this.line, this.col);
|
|
314
|
-
}
|
|
315
|
-
value += String.fromCharCode(parseInt(hex, 16));
|
|
316
|
-
for (let i = 0; i < 4; i++) this.advance();
|
|
317
|
-
break;
|
|
318
|
-
}
|
|
319
|
-
default:
|
|
320
|
-
throw new ParseError(`Unknown escape sequence: \\${esc}`, this.line, this.col);
|
|
321
|
-
}
|
|
322
|
-
} else {
|
|
323
|
-
value += this.advance();
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
if (this.peek() !== '"') throw new ParseError("unterminated string", sl, sc);
|
|
327
|
-
this.advance();
|
|
290
|
+
const value = this.readQuotedStringBody(sl, sc);
|
|
328
291
|
this.push("string", value, sl, sc, true);
|
|
329
292
|
continue;
|
|
330
293
|
}
|
|
331
294
|
if (isUnquotedStart(ch)) {
|
|
295
|
+
if (ch === "-" && !isDecimalDigit(this.peek(1))) {
|
|
296
|
+
const after = this.peek(1) === "" ? "EOF" : JSON.stringify(this.peek(1));
|
|
297
|
+
throw new ParseError(
|
|
298
|
+
`unquoted string cannot begin with '-' unless followed by a digit (got '-' then ${after}, HOCON.md L270-276)`,
|
|
299
|
+
sl,
|
|
300
|
+
sc
|
|
301
|
+
);
|
|
302
|
+
}
|
|
332
303
|
let value = "";
|
|
333
304
|
while (this.pos < this.input.length && isUnquotedContinue(this.peek(), () => this.peek(1))) {
|
|
334
305
|
value += this.advance();
|
|
@@ -358,22 +329,201 @@ var Lexer = class {
|
|
|
358
329
|
this.tokens.push({ kind, value, line: l, col: c, isQuoted, precedingSpace: this.hadSpace });
|
|
359
330
|
this.hadSpace = false;
|
|
360
331
|
}
|
|
332
|
+
pushSubst(payload, value, l, c) {
|
|
333
|
+
this.tokens.push({ kind: "subst", value, line: l, col: c, isQuoted: false, precedingSpace: this.hadSpace, subst: payload });
|
|
334
|
+
this.hadSpace = false;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Read body of a quoted string. Opening `"` must already be consumed.
|
|
338
|
+
* Returns decoded string. Throws ParseError on unterminated/invalid-escape.
|
|
339
|
+
* openLine/openCol are the position of the opening `"` for error reporting.
|
|
340
|
+
*/
|
|
341
|
+
readQuotedStringBody(openLine, openCol) {
|
|
342
|
+
let value = "";
|
|
343
|
+
while (this.pos < this.input.length && this.peek() !== '"') {
|
|
344
|
+
if (this.peek() === "\n") throw new ParseError("unterminated string", openLine, openCol);
|
|
345
|
+
if (this.peek() === "\\") {
|
|
346
|
+
const escCol = this.col;
|
|
347
|
+
this.advance();
|
|
348
|
+
if (this.pos >= this.input.length) {
|
|
349
|
+
throw new ParseError("unterminated string", openLine, openCol);
|
|
350
|
+
}
|
|
351
|
+
const esc = this.advance();
|
|
352
|
+
switch (esc) {
|
|
353
|
+
case "n":
|
|
354
|
+
value += "\n";
|
|
355
|
+
break;
|
|
356
|
+
case "t":
|
|
357
|
+
value += " ";
|
|
358
|
+
break;
|
|
359
|
+
case "r":
|
|
360
|
+
value += "\r";
|
|
361
|
+
break;
|
|
362
|
+
case '"':
|
|
363
|
+
value += '"';
|
|
364
|
+
break;
|
|
365
|
+
case "\\":
|
|
366
|
+
value += "\\";
|
|
367
|
+
break;
|
|
368
|
+
case "/":
|
|
369
|
+
value += "/";
|
|
370
|
+
break;
|
|
371
|
+
case "b":
|
|
372
|
+
value += "\b";
|
|
373
|
+
break;
|
|
374
|
+
case "f":
|
|
375
|
+
value += "\f";
|
|
376
|
+
break;
|
|
377
|
+
case "u": {
|
|
378
|
+
if (this.pos + 4 > this.input.length) {
|
|
379
|
+
throw new ParseError("invalid unicode escape", openLine, escCol);
|
|
380
|
+
}
|
|
381
|
+
const hex = this.input.slice(this.pos, this.pos + 4);
|
|
382
|
+
if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
|
|
383
|
+
throw new ParseError("invalid unicode escape", openLine, escCol);
|
|
384
|
+
}
|
|
385
|
+
const code = parseInt(hex, 16);
|
|
386
|
+
value += String.fromCharCode(code);
|
|
387
|
+
for (let i = 0; i < 4; i++) this.advance();
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
default:
|
|
391
|
+
throw new ParseError(`unknown escape sequence: \\${esc}`, openLine, escCol);
|
|
392
|
+
}
|
|
393
|
+
} else {
|
|
394
|
+
value += this.advance();
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (this.pos >= this.input.length || this.peek() !== '"') {
|
|
398
|
+
throw new ParseError("unterminated string", openLine, openCol);
|
|
399
|
+
}
|
|
400
|
+
this.advance();
|
|
401
|
+
return value;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Parse the body of a `${...}` substitution (called after `${` has been consumed).
|
|
405
|
+
* Implements Appendix A state machine from the cross-language spec.
|
|
406
|
+
*/
|
|
407
|
+
parseSubstBody(startLine, startCol) {
|
|
408
|
+
let optional = false;
|
|
409
|
+
if (this.peek() === "?") {
|
|
410
|
+
this.advance();
|
|
411
|
+
optional = true;
|
|
412
|
+
}
|
|
413
|
+
let curText = "";
|
|
414
|
+
let curStarted = false;
|
|
415
|
+
let curLine = 0;
|
|
416
|
+
let curCol = 0;
|
|
417
|
+
let pendingWs = "";
|
|
418
|
+
const segments = [];
|
|
419
|
+
let lastDot = null;
|
|
420
|
+
while (true) {
|
|
421
|
+
if (this.pos >= this.input.length) {
|
|
422
|
+
throw new ParseError("unterminated substitution", startLine, startCol);
|
|
423
|
+
}
|
|
424
|
+
const ch = this.peek();
|
|
425
|
+
if (ch === "}") {
|
|
426
|
+
this.advance();
|
|
427
|
+
pendingWs = "";
|
|
428
|
+
break;
|
|
429
|
+
} else if (ch === '"') {
|
|
430
|
+
const qLine = startLine;
|
|
431
|
+
const qCol = this.col;
|
|
432
|
+
if (curStarted) {
|
|
433
|
+
curText += pendingWs;
|
|
434
|
+
}
|
|
435
|
+
pendingWs = "";
|
|
436
|
+
this.advance();
|
|
437
|
+
const decoded = this.readQuotedStringBody(qLine, qCol);
|
|
438
|
+
curText += decoded;
|
|
439
|
+
if (!curStarted) {
|
|
440
|
+
curLine = qLine;
|
|
441
|
+
curCol = qCol;
|
|
442
|
+
curStarted = true;
|
|
443
|
+
}
|
|
444
|
+
} else if (isUnquotedSubstChar(ch)) {
|
|
445
|
+
if (ch === "-" && !curStarted && !isDecimalDigit(this.peek(1))) {
|
|
446
|
+
const after = this.peek(1) === "" ? "EOF" : JSON.stringify(this.peek(1));
|
|
447
|
+
throw new ParseError(
|
|
448
|
+
`unquoted path segment cannot begin with '-' unless followed by a digit (got '-' then ${after}, HOCON.md L270-276)`,
|
|
449
|
+
startLine,
|
|
450
|
+
this.col
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
const uCol = this.col;
|
|
454
|
+
if (curStarted) {
|
|
455
|
+
curText += pendingWs;
|
|
456
|
+
}
|
|
457
|
+
pendingWs = "";
|
|
458
|
+
if (!curStarted) {
|
|
459
|
+
curLine = startLine;
|
|
460
|
+
curCol = uCol;
|
|
461
|
+
curStarted = true;
|
|
462
|
+
}
|
|
463
|
+
while (this.pos < this.input.length && isUnquotedSubstChar(this.peek())) {
|
|
464
|
+
curText += this.advance();
|
|
465
|
+
}
|
|
466
|
+
} else if (ch === ".") {
|
|
467
|
+
const dotCol = this.col;
|
|
468
|
+
pendingWs = "";
|
|
469
|
+
if (!curStarted) {
|
|
470
|
+
throw new ParseError("empty segment in path", startLine, dotCol);
|
|
471
|
+
}
|
|
472
|
+
segments.push({ text: curText, line: curLine, col: curCol });
|
|
473
|
+
curText = "";
|
|
474
|
+
curStarted = false;
|
|
475
|
+
curLine = 0;
|
|
476
|
+
curCol = 0;
|
|
477
|
+
lastDot = [startLine, dotCol];
|
|
478
|
+
this.advance();
|
|
479
|
+
} else if (isHoconNewline(ch)) {
|
|
480
|
+
throw new ParseError("unterminated substitution", startLine, startCol);
|
|
481
|
+
} else if (isHoconWhitespace(ch)) {
|
|
482
|
+
pendingWs += ch;
|
|
483
|
+
this.advance();
|
|
484
|
+
} else {
|
|
485
|
+
throw new ParseError(`unexpected character in substitution path: ${JSON.stringify(ch)}`, startLine, this.col);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
if (curStarted) {
|
|
489
|
+
segments.push({ text: curText, line: curLine, col: curCol });
|
|
490
|
+
} else if (segments.length === 0) {
|
|
491
|
+
throw new ParseError("empty substitution path", startLine, startCol);
|
|
492
|
+
} else {
|
|
493
|
+
const [errLine, errCol] = lastDot ?? [startLine, startCol];
|
|
494
|
+
throw new ParseError("empty segment in path", errLine, errCol);
|
|
495
|
+
}
|
|
496
|
+
return { segments, optional };
|
|
497
|
+
}
|
|
361
498
|
};
|
|
362
499
|
function tokenize(input) {
|
|
363
500
|
return new Lexer(input).tokenize();
|
|
364
501
|
}
|
|
502
|
+
function isUnquotedSubstChar(ch) {
|
|
503
|
+
if (ch === "" || isHoconWhitespace(ch)) return false;
|
|
504
|
+
if ('"\\'.includes(ch)) return false;
|
|
505
|
+
if ("{}[]".includes(ch)) return false;
|
|
506
|
+
if (":=,+#`^?!@*&$.".includes(ch)) return false;
|
|
507
|
+
return true;
|
|
508
|
+
}
|
|
509
|
+
function isDecimalDigit(ch) {
|
|
510
|
+
return ch >= "0" && ch <= "9";
|
|
511
|
+
}
|
|
365
512
|
function isUnquotedStart(ch) {
|
|
366
|
-
|
|
513
|
+
if (ch === "" || isHoconWhitespace(ch)) return false;
|
|
514
|
+
if ('{}[],:=+#"$?!@*&^\\'.includes(ch)) return false;
|
|
515
|
+
return true;
|
|
367
516
|
}
|
|
368
517
|
function isUnquotedContinue(ch, nextFn) {
|
|
369
|
-
if (ch === "" ||
|
|
518
|
+
if (ch === "" || isHoconWhitespace(ch)) return false;
|
|
519
|
+
if ('{}[],:=#"$?!@*&^\\'.includes(ch)) return false;
|
|
370
520
|
if (ch === "+" && nextFn() === "=") return false;
|
|
371
521
|
if (ch === "/" && nextFn() === "/") return false;
|
|
372
522
|
return true;
|
|
373
523
|
}
|
|
374
524
|
|
|
375
525
|
// src/internal/parser/parser.ts
|
|
376
|
-
var EOF_TOKEN = { kind: "eof", value: "", line: 0, col: 0, isQuoted: false, precedingSpace: false };
|
|
526
|
+
var EOF_TOKEN = { kind: "eof", value: "", line: 0, col: 0, isQuoted: false, precedingSpace: false, subst: void 0 };
|
|
377
527
|
var Parser = class {
|
|
378
528
|
constructor(tokens) {
|
|
379
529
|
this.tokens = tokens;
|
|
@@ -477,10 +627,21 @@ var Parser = class {
|
|
|
477
627
|
trailingDot = false;
|
|
478
628
|
} else if (t.kind === "unquoted") {
|
|
479
629
|
this.advance();
|
|
480
|
-
const
|
|
630
|
+
const raw = t.value.startsWith(".") && segments.length > 0 && !t.precedingSpace ? t.value.slice(1) : t.value;
|
|
631
|
+
const parts = raw.split(".");
|
|
481
632
|
const filtered = parts.filter((s) => s.length > 0);
|
|
633
|
+
for (const seg of filtered) {
|
|
634
|
+
if (seg[0] === "-" && !(seg.length >= 2 && seg[1] >= "0" && seg[1] <= "9")) {
|
|
635
|
+
const after = seg.length >= 2 ? JSON.stringify(seg[1]) : "EOF";
|
|
636
|
+
throw new ParseError(
|
|
637
|
+
`unquoted key segment cannot begin with '-' unless followed by a digit (got '-' then ${after} in ${JSON.stringify(seg)}, HOCON.md L270-276)`,
|
|
638
|
+
t.line,
|
|
639
|
+
t.col
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
482
643
|
segments.push(...filtered);
|
|
483
|
-
trailingDot =
|
|
644
|
+
trailingDot = raw.endsWith(".");
|
|
484
645
|
} else {
|
|
485
646
|
if (segments.length === 0) throw new ParseError(`expected key, got ${t.kind}`, t.line, t.col);
|
|
486
647
|
break;
|
|
@@ -492,6 +653,10 @@ var Parser = class {
|
|
|
492
653
|
trailingDot = true;
|
|
493
654
|
continue;
|
|
494
655
|
}
|
|
656
|
+
if (next.kind === "unquoted" && next.value.startsWith(".") && !next.precedingSpace) {
|
|
657
|
+
trailingDot = true;
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
495
660
|
break;
|
|
496
661
|
}
|
|
497
662
|
return segments;
|
|
@@ -567,9 +732,13 @@ var Parser = class {
|
|
|
567
732
|
} else if (t.kind === "lbracket") {
|
|
568
733
|
this.advance();
|
|
569
734
|
node = this.parseArray();
|
|
570
|
-
} else if (t.kind === "subst"
|
|
735
|
+
} else if (t.kind === "subst") {
|
|
571
736
|
this.advance();
|
|
572
|
-
|
|
737
|
+
if (!t.subst) {
|
|
738
|
+
throw new ParseError(`internal: subst token missing payload`, t.line, t.col);
|
|
739
|
+
}
|
|
740
|
+
const payload = t.subst;
|
|
741
|
+
node = { kind: "subst", segments: payload.segments, optional: payload.optional, pos: { line: t.line, col: t.col } };
|
|
573
742
|
} else if (t.kind === "string" || t.kind === "triple_string") {
|
|
574
743
|
this.advance();
|
|
575
744
|
node = { kind: "scalar", raw: t.value, valueType: "string", pos: { line: t.line, col: t.col } };
|
|
@@ -639,64 +808,20 @@ function makeResObj() {
|
|
|
639
808
|
}
|
|
640
809
|
|
|
641
810
|
// src/internal/resolver/utils.ts
|
|
642
|
-
function parseSubstPath(raw) {
|
|
643
|
-
const segments = [];
|
|
644
|
-
let i = 0;
|
|
645
|
-
while (i < raw.length) {
|
|
646
|
-
while (i < raw.length && (raw[i] === " " || raw[i] === " ")) i++;
|
|
647
|
-
if (i >= raw.length) break;
|
|
648
|
-
if (raw[i] === '"') {
|
|
649
|
-
i++;
|
|
650
|
-
let seg = "";
|
|
651
|
-
while (i < raw.length && raw[i] !== '"') {
|
|
652
|
-
if (raw[i] === "\\" && i + 1 < raw.length && (raw[i + 1] === '"' || raw[i + 1] === "\\")) {
|
|
653
|
-
seg += raw[i + 1];
|
|
654
|
-
i += 2;
|
|
655
|
-
} else {
|
|
656
|
-
seg += raw[i];
|
|
657
|
-
i++;
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
if (i < raw.length) i++;
|
|
661
|
-
segments.push(seg);
|
|
662
|
-
while (i < raw.length && (raw[i] === " " || raw[i] === " ")) i++;
|
|
663
|
-
if (i < raw.length && raw[i] === ".") i++;
|
|
664
|
-
} else if (raw[i] === ".") {
|
|
665
|
-
segments.push("");
|
|
666
|
-
i++;
|
|
667
|
-
} else {
|
|
668
|
-
let seg = "";
|
|
669
|
-
while (i < raw.length && raw[i] !== ".") {
|
|
670
|
-
seg += raw[i];
|
|
671
|
-
i++;
|
|
672
|
-
}
|
|
673
|
-
segments.push(seg.trim());
|
|
674
|
-
if (i < raw.length && raw[i] === ".") i++;
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
return segments;
|
|
678
|
-
}
|
|
679
811
|
function segmentsToKey(segments) {
|
|
680
812
|
return segments.map((s) => {
|
|
681
|
-
|
|
682
|
-
|
|
813
|
+
const t = s.text;
|
|
814
|
+
if (t === "" || /[^a-zA-Z0-9\-_]/.test(t)) {
|
|
815
|
+
return `"${t.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
683
816
|
}
|
|
684
|
-
return
|
|
817
|
+
return t;
|
|
685
818
|
}).join(".");
|
|
686
819
|
}
|
|
687
820
|
function lookupPath(root, segments) {
|
|
688
821
|
const [head, ...tail] = segments;
|
|
689
|
-
if (head === void 0
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
if (val2 === void 0) return void 0;
|
|
693
|
-
if (tail.length === 0) return val2;
|
|
694
|
-
if (isResObj(val2)) return lookupPath(val2, tail);
|
|
695
|
-
return void 0;
|
|
696
|
-
}
|
|
697
|
-
return void 0;
|
|
698
|
-
}
|
|
699
|
-
const val = root.fields.get(head);
|
|
822
|
+
if (head === void 0) return void 0;
|
|
823
|
+
const key = head.text;
|
|
824
|
+
const val = root.fields.get(key);
|
|
700
825
|
if (val === void 0) return void 0;
|
|
701
826
|
if (tail.length === 0) return val;
|
|
702
827
|
if (isResObj(val)) return lookupPath(val, tail);
|
|
@@ -705,7 +830,7 @@ function lookupPath(root, segments) {
|
|
|
705
830
|
function lookupResObj(root, segments) {
|
|
706
831
|
let cur = root;
|
|
707
832
|
for (const seg of segments) {
|
|
708
|
-
const val = cur.fields.get(seg);
|
|
833
|
+
const val = cur.fields.get(seg.text);
|
|
709
834
|
if (val === void 0 || !isResObj(val)) return void 0;
|
|
710
835
|
cur = val;
|
|
711
836
|
}
|
|
@@ -982,7 +1107,7 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
982
1107
|
return;
|
|
983
1108
|
}
|
|
984
1109
|
const [head, ...tail] = field.key;
|
|
985
|
-
if (
|
|
1110
|
+
if (head === void 0) return;
|
|
986
1111
|
if (tail.length > 0) {
|
|
987
1112
|
const syntheticAst = {
|
|
988
1113
|
kind: "object",
|
|
@@ -1024,7 +1149,7 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1024
1149
|
return;
|
|
1025
1150
|
}
|
|
1026
1151
|
const [head, ...tail] = field.key;
|
|
1027
|
-
if (
|
|
1152
|
+
if (head === void 0) return;
|
|
1028
1153
|
if (tail.length > 0) {
|
|
1029
1154
|
const syntheticAst = {
|
|
1030
1155
|
kind: "object",
|
|
@@ -1070,7 +1195,7 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1070
1195
|
return inner;
|
|
1071
1196
|
}
|
|
1072
1197
|
case "subst":
|
|
1073
|
-
return { _kind: "subst-placeholder", segments:
|
|
1198
|
+
return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
|
|
1074
1199
|
case "concat":
|
|
1075
1200
|
return { _kind: "concat-placeholder", nodes: ast.nodes.map((n) => this.astToResolverValue(n, pathPrefix)) };
|
|
1076
1201
|
case "include":
|
|
@@ -1094,7 +1219,7 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1094
1219
|
case "object":
|
|
1095
1220
|
return await this.buildAsync(ast, pathPrefix);
|
|
1096
1221
|
case "subst":
|
|
1097
|
-
return { _kind: "subst-placeholder", segments:
|
|
1222
|
+
return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
|
|
1098
1223
|
case "concat": {
|
|
1099
1224
|
const nodes = [];
|
|
1100
1225
|
for (const n of ast.nodes) {
|
|
@@ -1109,7 +1234,8 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1109
1234
|
// ---- Relativize substitution paths for nested includes ----
|
|
1110
1235
|
relativizeSubstPaths(val, prefixSegments) {
|
|
1111
1236
|
if (isSubst(val)) {
|
|
1112
|
-
|
|
1237
|
+
const prefixAsSegments = prefixSegments.map((text) => ({ text, line: 0, col: 0 }));
|
|
1238
|
+
val.segments = [...prefixAsSegments, ...val.segments];
|
|
1113
1239
|
val.prefixLen += prefixSegments.length;
|
|
1114
1240
|
return;
|
|
1115
1241
|
}
|
|
@@ -1145,6 +1271,23 @@ var StructureBuilder = class _StructureBuilder {
|
|
|
1145
1271
|
}
|
|
1146
1272
|
};
|
|
1147
1273
|
|
|
1274
|
+
// src/value/numeric-array.ts
|
|
1275
|
+
function numericObjectToArray(value) {
|
|
1276
|
+
if (value.kind !== "object") return null;
|
|
1277
|
+
if (value.fields.size === 0) return null;
|
|
1278
|
+
const canonicalInt = /^(0|[1-9][0-9]*)$/;
|
|
1279
|
+
const eligible = [];
|
|
1280
|
+
for (const [key, val] of value.fields) {
|
|
1281
|
+
if (!canonicalInt.test(key)) continue;
|
|
1282
|
+
const n = Number(key);
|
|
1283
|
+
if (!Number.isInteger(n) || n > 2147483647) continue;
|
|
1284
|
+
eligible.push({ n, v: val });
|
|
1285
|
+
}
|
|
1286
|
+
if (eligible.length === 0) return null;
|
|
1287
|
+
eligible.sort((a, b) => a.n - b.n);
|
|
1288
|
+
return eligible.map((e) => e.v);
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1148
1291
|
// src/internal/resolver/substitution-resolver.ts
|
|
1149
1292
|
var SubstitutionResolver = class {
|
|
1150
1293
|
constructor(root, opts) {
|
|
@@ -1205,7 +1348,7 @@ var SubstitutionResolver = class {
|
|
|
1205
1348
|
return hv;
|
|
1206
1349
|
}
|
|
1207
1350
|
segmentsEqual(a, b) {
|
|
1208
|
-
return a.length === b.length && a.every((seg, i) => seg === b[i]);
|
|
1351
|
+
return a.length === b.length && a.every((seg, i) => seg.text === b[i]?.text);
|
|
1209
1352
|
}
|
|
1210
1353
|
resolveSubst(s, scope) {
|
|
1211
1354
|
const key = segmentsToKey(s.segments);
|
|
@@ -1213,14 +1356,14 @@ var SubstitutionResolver = class {
|
|
|
1213
1356
|
if (this.resolving.has(key)) {
|
|
1214
1357
|
let prior;
|
|
1215
1358
|
if (s.prefixLen > 0) {
|
|
1216
|
-
const leafSeg = s.segments[s.segments.length - 1] ?? "";
|
|
1359
|
+
const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
|
|
1217
1360
|
const parentScope = lookupResObj(
|
|
1218
1361
|
this.root,
|
|
1219
1362
|
s.segments.slice(0, s.segments.length - 1)
|
|
1220
1363
|
);
|
|
1221
1364
|
prior = parentScope?.priorValues.get(leafSeg);
|
|
1222
1365
|
} else {
|
|
1223
|
-
const rootSeg = s.segments[0] ?? "";
|
|
1366
|
+
const rootSeg = s.segments[0]?.text ?? "";
|
|
1224
1367
|
prior = scope.priorValues.get(rootSeg) ?? this.root.priorValues.get(rootSeg);
|
|
1225
1368
|
}
|
|
1226
1369
|
if (prior !== void 0) {
|
|
@@ -1251,14 +1394,14 @@ var SubstitutionResolver = class {
|
|
|
1251
1394
|
if (isSelfRef) {
|
|
1252
1395
|
let prior;
|
|
1253
1396
|
if (s.prefixLen > 0) {
|
|
1254
|
-
const leafSeg = s.segments[s.segments.length - 1] ?? "";
|
|
1397
|
+
const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
|
|
1255
1398
|
const parentScope = lookupResObj(
|
|
1256
1399
|
this.root,
|
|
1257
1400
|
s.segments.slice(0, s.segments.length - 1)
|
|
1258
1401
|
);
|
|
1259
1402
|
prior = parentScope?.priorValues.get(leafSeg);
|
|
1260
1403
|
} else {
|
|
1261
|
-
const rootSeg = s.segments[0] ?? "";
|
|
1404
|
+
const rootSeg = s.segments[0]?.text ?? "";
|
|
1262
1405
|
prior = scope.priorValues.get(rootSeg) ?? this.root.priorValues.get(rootSeg);
|
|
1263
1406
|
}
|
|
1264
1407
|
if (prior !== void 0) {
|
|
@@ -1271,7 +1414,7 @@ var SubstitutionResolver = class {
|
|
|
1271
1414
|
let result = this.resolveVal(found, scope);
|
|
1272
1415
|
const effectiveLen = s.segments.length - s.prefixLen;
|
|
1273
1416
|
if (effectiveLen === 1 && result !== void 0 && result.kind === "object") {
|
|
1274
|
-
const leafSeg = s.segments[s.segments.length - 1] ?? "";
|
|
1417
|
+
const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
|
|
1275
1418
|
let prior;
|
|
1276
1419
|
if (s.prefixLen > 0) {
|
|
1277
1420
|
const parentScope = lookupResObj(
|
|
@@ -1295,8 +1438,8 @@ var SubstitutionResolver = class {
|
|
|
1295
1438
|
if (result !== void 0) this.cache.set(key, result);
|
|
1296
1439
|
return result;
|
|
1297
1440
|
}
|
|
1298
|
-
const envKey = s.segments.join(".");
|
|
1299
|
-
const envVal = this.opts.env[envKey] ?? (s.prefixLen > 0 ? this.opts.env[s.segments.slice(s.prefixLen).join(".")] : void 0);
|
|
1441
|
+
const envKey = s.segments.map((seg) => seg.text).join(".");
|
|
1442
|
+
const envVal = this.opts.env[envKey] ?? (s.prefixLen > 0 ? this.opts.env[s.segments.slice(s.prefixLen).map((seg) => seg.text).join(".")] : void 0);
|
|
1300
1443
|
if (envVal !== void 0) {
|
|
1301
1444
|
const result = { kind: "scalar", raw: envVal, valueType: "string" };
|
|
1302
1445
|
this.cache.set(key, result);
|
|
@@ -1318,32 +1461,48 @@ var SubstitutionResolver = class {
|
|
|
1318
1461
|
if (resolved.length === 0) return { kind: "scalar", raw: "null", valueType: "null" };
|
|
1319
1462
|
if (resolved.length === 1) return resolved[0];
|
|
1320
1463
|
const nonSep = resolved.filter((v) => !separatorValues.has(v));
|
|
1321
|
-
if (nonSep.length
|
|
1322
|
-
const
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
}
|
|
1464
|
+
if (nonSep.length === 0) {
|
|
1465
|
+
const str = resolved.map((v) => v.kind === "scalar" ? v.raw : JSON.stringify(v)).join("");
|
|
1466
|
+
return { kind: "scalar", raw: str, valueType: "string" };
|
|
1467
|
+
}
|
|
1468
|
+
const joinPair = (left, right) => {
|
|
1469
|
+
if (left.kind === "object" && right.kind === "object") {
|
|
1470
|
+
return deepMergeHoconValues(left, right);
|
|
1471
|
+
}
|
|
1472
|
+
if (left.kind === "array" && right.kind === "object") {
|
|
1473
|
+
const converted = numericObjectToArray(right);
|
|
1474
|
+
if (converted !== null) {
|
|
1475
|
+
return { kind: "array", items: [...left.items, ...converted] };
|
|
1333
1476
|
}
|
|
1477
|
+
return { kind: "array", items: [...left.items, right] };
|
|
1334
1478
|
}
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1479
|
+
if (left.kind === "object" && right.kind === "array") {
|
|
1480
|
+
const converted = numericObjectToArray(left);
|
|
1481
|
+
if (converted !== null) {
|
|
1482
|
+
return { kind: "array", items: [...converted, ...right.items] };
|
|
1483
|
+
}
|
|
1484
|
+
return { kind: "array", items: [left, ...right.items] };
|
|
1485
|
+
}
|
|
1486
|
+
if (left.kind === "array" && right.kind === "array") {
|
|
1487
|
+
return { kind: "array", items: [...left.items, ...right.items] };
|
|
1488
|
+
}
|
|
1489
|
+
if (left.kind === "array") {
|
|
1490
|
+
return { kind: "array", items: [...left.items, right] };
|
|
1342
1491
|
}
|
|
1343
|
-
|
|
1492
|
+
if (right.kind === "array") {
|
|
1493
|
+
return { kind: "array", items: [left, ...right.items] };
|
|
1494
|
+
}
|
|
1495
|
+
const leftStr = left.kind === "scalar" ? left.raw : JSON.stringify(left);
|
|
1496
|
+
const rightStr = right.kind === "scalar" ? right.raw : JSON.stringify(right);
|
|
1497
|
+
return { kind: "scalar", raw: leftStr + rightStr, valueType: "string" };
|
|
1498
|
+
};
|
|
1499
|
+
const [head, ...tail] = nonSep;
|
|
1500
|
+
const folded = tail.reduce(joinPair, head);
|
|
1501
|
+
if (folded.kind === "scalar") {
|
|
1502
|
+
const str = resolved.map((v) => v.kind === "scalar" ? v.raw : JSON.stringify(v)).join("");
|
|
1503
|
+
return { kind: "scalar", raw: str, valueType: "string" };
|
|
1344
1504
|
}
|
|
1345
|
-
|
|
1346
|
-
return { kind: "scalar", raw: str, valueType: "string" };
|
|
1505
|
+
return folded;
|
|
1347
1506
|
}
|
|
1348
1507
|
resolveAppend(a, scope) {
|
|
1349
1508
|
const existing = this.resolveVal(a.existing, scope) ?? { kind: "array", items: [] };
|
|
@@ -1413,6 +1572,10 @@ var Config = class _Config {
|
|
|
1413
1572
|
getList(path2) {
|
|
1414
1573
|
const v = this.lookupNode(path2);
|
|
1415
1574
|
if (v === void 0) throw new ConfigError(`path not found: ${path2}`, path2);
|
|
1575
|
+
if (v.kind === "object") {
|
|
1576
|
+
const converted = numericObjectToArray(v);
|
|
1577
|
+
if (converted !== null) return converted.map(hoconToJs);
|
|
1578
|
+
}
|
|
1416
1579
|
if (v.kind !== "array") throw new ConfigError(`expected array at ${path2}`, path2);
|
|
1417
1580
|
return v.items.map(hoconToJs);
|
|
1418
1581
|
}
|