@o3co/ts.hocon 1.0.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 +1196 -795
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1196 -795
- package/dist/{parse-C4VOYuEk.d.cts → parse-nNbyqnOh.d.cts} +4 -2
- package/dist/{parse-C4VOYuEk.d.ts → parse-nNbyqnOh.d.ts} +4 -2
- package/dist/zod.cjs +1195 -657
- package/dist/zod.d.cts +1 -1
- package/dist/zod.d.ts +1 -1
- package/dist/zod.js +1195 -657
- package/package.json +4 -4
package/dist/zod.cjs
CHANGED
|
@@ -38,6 +38,7 @@ __export(zod_exports, {
|
|
|
38
38
|
module.exports = __toCommonJS(zod_exports);
|
|
39
39
|
|
|
40
40
|
// src/coerce.ts
|
|
41
|
+
var DECIMAL_NUMBER_RE = /^-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/;
|
|
41
42
|
var TRUTHY = /* @__PURE__ */ new Set(["true", "yes", "on"]);
|
|
42
43
|
var FALSY = /* @__PURE__ */ new Set(["false", "no", "off"]);
|
|
43
44
|
function coerceBoolean(value) {
|
|
@@ -47,7 +48,7 @@ function coerceBoolean(value) {
|
|
|
47
48
|
return void 0;
|
|
48
49
|
}
|
|
49
50
|
function coerceNumber(value) {
|
|
50
|
-
if (
|
|
51
|
+
if (!DECIMAL_NUMBER_RE.test(value)) return void 0;
|
|
51
52
|
return Number(value);
|
|
52
53
|
}
|
|
53
54
|
var DURATION_UNITS = {
|
|
@@ -204,281 +205,494 @@ var ConfigError = class extends Error {
|
|
|
204
205
|
};
|
|
205
206
|
|
|
206
207
|
// src/internal/lexer/lexer.ts
|
|
207
|
-
function
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
208
|
+
function isHoconWhitespace(ch) {
|
|
209
|
+
const cp = ch.codePointAt(0) ?? -1;
|
|
210
|
+
if (cp === 9 || cp === 10 || cp === 11 || cp === 12 || cp === 13) return true;
|
|
211
|
+
if (cp >= 28 && cp <= 31) return true;
|
|
212
|
+
if (cp === 32 || cp === 160 || cp === 65279) return true;
|
|
213
|
+
if (cp === 5760) return true;
|
|
214
|
+
if (cp >= 8192 && cp <= 8202) return true;
|
|
215
|
+
if (cp === 8232 || cp === 8233 || cp === 8239 || cp === 8287) return true;
|
|
216
|
+
if (cp === 12288) return true;
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
function isHoconNewline(ch) {
|
|
220
|
+
return ch === "\n";
|
|
221
|
+
}
|
|
222
|
+
var SINGLE_CHAR_TOKENS = {
|
|
223
|
+
"{": "lbrace",
|
|
224
|
+
"}": "rbrace",
|
|
225
|
+
"[": "lbracket",
|
|
226
|
+
"]": "rbracket",
|
|
227
|
+
",": "comma",
|
|
228
|
+
":": "colon"
|
|
229
|
+
};
|
|
230
|
+
var Lexer = class {
|
|
231
|
+
constructor(input) {
|
|
232
|
+
this.input = input;
|
|
233
|
+
this.pos = 0;
|
|
234
|
+
this.line = 1;
|
|
235
|
+
this.col = 1;
|
|
236
|
+
this.hadSpace = false;
|
|
237
|
+
this.tokens = [];
|
|
238
|
+
if (input.charCodeAt(0) === 65279) this.input = input.slice(1);
|
|
239
|
+
}
|
|
240
|
+
tokenize() {
|
|
241
|
+
while (this.pos < this.input.length) {
|
|
242
|
+
const sl = this.line, sc = this.col;
|
|
243
|
+
const ch = this.peek();
|
|
244
|
+
if (isHoconNewline(ch)) {
|
|
245
|
+
this.advance();
|
|
246
|
+
if (this.tokens[this.tokens.length - 1]?.kind !== "newline") {
|
|
247
|
+
this.push("newline", "\n", sl, sc);
|
|
248
|
+
}
|
|
249
|
+
continue;
|
|
243
250
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
hadSpace = true;
|
|
249
|
-
continue;
|
|
250
|
-
}
|
|
251
|
-
if (ch === "#") {
|
|
252
|
-
while (pos < input.length && peek() !== "\n") advance();
|
|
253
|
-
hadSpace = true;
|
|
254
|
-
continue;
|
|
255
|
-
}
|
|
256
|
-
const single = { "{": "lbrace", "}": "rbrace", "[": "lbracket", "]": "rbracket", ",": "comma", ":": "colon" };
|
|
257
|
-
if (ch in single) {
|
|
258
|
-
advance();
|
|
259
|
-
push(single[ch], ch, sl, sc);
|
|
260
|
-
continue;
|
|
261
|
-
}
|
|
262
|
-
if (ch === "=") {
|
|
263
|
-
advance();
|
|
264
|
-
push("equals", "=", sl, sc);
|
|
265
|
-
continue;
|
|
266
|
-
}
|
|
267
|
-
if (ch === "+" && peek(1) === "=") {
|
|
268
|
-
advance();
|
|
269
|
-
advance();
|
|
270
|
-
push("plus_equals", "+=", sl, sc);
|
|
271
|
-
continue;
|
|
272
|
-
}
|
|
273
|
-
if (ch === "$" && peek(1) === "{") {
|
|
274
|
-
advance();
|
|
275
|
-
advance();
|
|
276
|
-
const optional = peek() === "?";
|
|
277
|
-
if (optional) advance();
|
|
278
|
-
let path2 = "";
|
|
279
|
-
while (pos < input.length && peek() !== "}") {
|
|
280
|
-
if (peek() === "\n") throw new ParseError("unterminated substitution", sl, sc);
|
|
281
|
-
path2 += advance();
|
|
251
|
+
if (isHoconWhitespace(ch)) {
|
|
252
|
+
this.advance();
|
|
253
|
+
this.hadSpace = true;
|
|
254
|
+
continue;
|
|
282
255
|
}
|
|
283
|
-
if (
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
256
|
+
if (ch === "/" && this.peek(1) === "/") {
|
|
257
|
+
while (this.pos < this.input.length && this.peek() !== "\n") this.advance();
|
|
258
|
+
this.hadSpace = true;
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (ch === "#") {
|
|
262
|
+
while (this.pos < this.input.length && this.peek() !== "\n") this.advance();
|
|
263
|
+
this.hadSpace = true;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
if (ch in SINGLE_CHAR_TOKENS) {
|
|
267
|
+
this.advance();
|
|
268
|
+
this.push(SINGLE_CHAR_TOKENS[ch], ch, sl, sc);
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (ch === "=") {
|
|
272
|
+
this.advance();
|
|
273
|
+
this.push("equals", "=", sl, sc);
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (ch === "+" && this.peek(1) === "=") {
|
|
277
|
+
this.advance();
|
|
278
|
+
this.advance();
|
|
279
|
+
this.push("plus_equals", "+=", sl, sc);
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
if (ch === "$" && this.peek(1) === "{") {
|
|
283
|
+
this.advance();
|
|
284
|
+
this.advance();
|
|
285
|
+
const payload = this.parseSubstBody(sl, sc);
|
|
286
|
+
const value = payload.segments.map((s) => {
|
|
287
|
+
const t = s.text;
|
|
288
|
+
if (t === "" || t.includes(".") || t.includes(" ") || t.includes(" ") || t.includes('"') || t.includes("\\") || t !== t.trim()) {
|
|
289
|
+
const escaped = t.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
290
|
+
return `"${escaped}"`;
|
|
300
291
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
292
|
+
return t;
|
|
293
|
+
}).join(".");
|
|
294
|
+
this.pushSubst(payload, value, sl, sc);
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
if (ch === '"' && this.peek(1) === '"' && this.peek(2) === '"') {
|
|
298
|
+
this.advance();
|
|
299
|
+
this.advance();
|
|
300
|
+
this.advance();
|
|
301
|
+
let value = "";
|
|
302
|
+
let closed = false;
|
|
303
|
+
while (this.pos < this.input.length) {
|
|
304
|
+
if (this.peek() === '"') {
|
|
305
|
+
let quoteCount = 0;
|
|
306
|
+
while (this.pos < this.input.length && this.peek() === '"') {
|
|
307
|
+
quoteCount++;
|
|
308
|
+
this.advance();
|
|
309
|
+
}
|
|
310
|
+
if (quoteCount >= 3) {
|
|
311
|
+
for (let i = 0; i < quoteCount - 3; i++) value += '"';
|
|
312
|
+
closed = true;
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
for (let i = 0; i < quoteCount; i++) value += '"';
|
|
316
|
+
continue;
|
|
305
317
|
}
|
|
306
|
-
|
|
307
|
-
|
|
318
|
+
value += this.advance();
|
|
319
|
+
}
|
|
320
|
+
if (!closed) {
|
|
321
|
+
throw new ParseError("unterminated triple-quoted string", sl, sc);
|
|
308
322
|
}
|
|
309
|
-
value
|
|
323
|
+
if (value.startsWith("\n")) value = value.slice(1);
|
|
324
|
+
this.push("triple_string", value, sl, sc, true);
|
|
325
|
+
continue;
|
|
310
326
|
}
|
|
311
|
-
if (
|
|
312
|
-
|
|
327
|
+
if (ch === '"') {
|
|
328
|
+
this.advance();
|
|
329
|
+
const value = this.readQuotedStringBody(sl, sc);
|
|
330
|
+
this.push("string", value, sl, sc, true);
|
|
331
|
+
continue;
|
|
313
332
|
}
|
|
314
|
-
if (
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
333
|
+
if (isUnquotedStart(ch)) {
|
|
334
|
+
if (ch === "-" && !isDecimalDigit(this.peek(1))) {
|
|
335
|
+
const after = this.peek(1) === "" ? "EOF" : JSON.stringify(this.peek(1));
|
|
336
|
+
throw new ParseError(
|
|
337
|
+
`unquoted string cannot begin with '-' unless followed by a digit (got '-' then ${after}, HOCON.md L270-276)`,
|
|
338
|
+
sl,
|
|
339
|
+
sc
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
let value = "";
|
|
343
|
+
while (this.pos < this.input.length && isUnquotedContinue(this.peek(), () => this.peek(1))) {
|
|
344
|
+
value += this.advance();
|
|
345
|
+
}
|
|
346
|
+
this.push("unquoted", value.trimEnd(), sl, sc);
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
throw new ParseError(`unexpected character: ${JSON.stringify(ch)}`, sl, sc);
|
|
350
|
+
}
|
|
351
|
+
this.tokens.push({ kind: "eof", value: "", line: this.line, col: this.col, isQuoted: false, precedingSpace: false });
|
|
352
|
+
return this.tokens;
|
|
353
|
+
}
|
|
354
|
+
peek(offset = 0) {
|
|
355
|
+
return this.input[this.pos + offset] ?? "";
|
|
356
|
+
}
|
|
357
|
+
advance() {
|
|
358
|
+
const ch = this.input[this.pos++] ?? "";
|
|
359
|
+
if (ch === "\n") {
|
|
360
|
+
this.line++;
|
|
361
|
+
this.col = 1;
|
|
362
|
+
} else {
|
|
363
|
+
this.col++;
|
|
364
|
+
}
|
|
365
|
+
return ch;
|
|
366
|
+
}
|
|
367
|
+
push(kind, value, l, c, isQuoted = false) {
|
|
368
|
+
this.tokens.push({ kind, value, line: l, col: c, isQuoted, precedingSpace: this.hadSpace });
|
|
369
|
+
this.hadSpace = false;
|
|
370
|
+
}
|
|
371
|
+
pushSubst(payload, value, l, c) {
|
|
372
|
+
this.tokens.push({ kind: "subst", value, line: l, col: c, isQuoted: false, precedingSpace: this.hadSpace, subst: payload });
|
|
373
|
+
this.hadSpace = false;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Read body of a quoted string. Opening `"` must already be consumed.
|
|
377
|
+
* Returns decoded string. Throws ParseError on unterminated/invalid-escape.
|
|
378
|
+
* openLine/openCol are the position of the opening `"` for error reporting.
|
|
379
|
+
*/
|
|
380
|
+
readQuotedStringBody(openLine, openCol) {
|
|
381
|
+
let value = "";
|
|
382
|
+
while (this.pos < this.input.length && this.peek() !== '"') {
|
|
383
|
+
if (this.peek() === "\n") throw new ParseError("unterminated string", openLine, openCol);
|
|
384
|
+
if (this.peek() === "\\") {
|
|
385
|
+
const escCol = this.col;
|
|
386
|
+
this.advance();
|
|
387
|
+
if (this.pos >= this.input.length) {
|
|
388
|
+
throw new ParseError("unterminated string", openLine, openCol);
|
|
389
|
+
}
|
|
390
|
+
const esc = this.advance();
|
|
391
|
+
switch (esc) {
|
|
392
|
+
case "n":
|
|
393
|
+
value += "\n";
|
|
394
|
+
break;
|
|
395
|
+
case "t":
|
|
396
|
+
value += " ";
|
|
397
|
+
break;
|
|
398
|
+
case "r":
|
|
399
|
+
value += "\r";
|
|
400
|
+
break;
|
|
401
|
+
case '"':
|
|
402
|
+
value += '"';
|
|
403
|
+
break;
|
|
404
|
+
case "\\":
|
|
405
|
+
value += "\\";
|
|
406
|
+
break;
|
|
407
|
+
case "/":
|
|
408
|
+
value += "/";
|
|
409
|
+
break;
|
|
410
|
+
case "b":
|
|
411
|
+
value += "\b";
|
|
412
|
+
break;
|
|
413
|
+
case "f":
|
|
414
|
+
value += "\f";
|
|
415
|
+
break;
|
|
416
|
+
case "u": {
|
|
417
|
+
if (this.pos + 4 > this.input.length) {
|
|
418
|
+
throw new ParseError("invalid unicode escape", openLine, escCol);
|
|
419
|
+
}
|
|
420
|
+
const hex = this.input.slice(this.pos, this.pos + 4);
|
|
421
|
+
if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
|
|
422
|
+
throw new ParseError("invalid unicode escape", openLine, escCol);
|
|
362
423
|
}
|
|
363
|
-
|
|
364
|
-
|
|
424
|
+
const code = parseInt(hex, 16);
|
|
425
|
+
value += String.fromCharCode(code);
|
|
426
|
+
for (let i = 0; i < 4; i++) this.advance();
|
|
427
|
+
break;
|
|
365
428
|
}
|
|
366
|
-
|
|
367
|
-
|
|
429
|
+
default:
|
|
430
|
+
throw new ParseError(`unknown escape sequence: \\${esc}`, openLine, escCol);
|
|
368
431
|
}
|
|
432
|
+
} else {
|
|
433
|
+
value += this.advance();
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
if (this.pos >= this.input.length || this.peek() !== '"') {
|
|
437
|
+
throw new ParseError("unterminated string", openLine, openCol);
|
|
438
|
+
}
|
|
439
|
+
this.advance();
|
|
440
|
+
return value;
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Parse the body of a `${...}` substitution (called after `${` has been consumed).
|
|
444
|
+
* Implements Appendix A state machine from the cross-language spec.
|
|
445
|
+
*/
|
|
446
|
+
parseSubstBody(startLine, startCol) {
|
|
447
|
+
let optional = false;
|
|
448
|
+
if (this.peek() === "?") {
|
|
449
|
+
this.advance();
|
|
450
|
+
optional = true;
|
|
451
|
+
}
|
|
452
|
+
let curText = "";
|
|
453
|
+
let curStarted = false;
|
|
454
|
+
let curLine = 0;
|
|
455
|
+
let curCol = 0;
|
|
456
|
+
let pendingWs = "";
|
|
457
|
+
const segments = [];
|
|
458
|
+
let lastDot = null;
|
|
459
|
+
while (true) {
|
|
460
|
+
if (this.pos >= this.input.length) {
|
|
461
|
+
throw new ParseError("unterminated substitution", startLine, startCol);
|
|
369
462
|
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
463
|
+
const ch = this.peek();
|
|
464
|
+
if (ch === "}") {
|
|
465
|
+
this.advance();
|
|
466
|
+
pendingWs = "";
|
|
467
|
+
break;
|
|
468
|
+
} else if (ch === '"') {
|
|
469
|
+
const qLine = startLine;
|
|
470
|
+
const qCol = this.col;
|
|
471
|
+
if (curStarted) {
|
|
472
|
+
curText += pendingWs;
|
|
473
|
+
}
|
|
474
|
+
pendingWs = "";
|
|
475
|
+
this.advance();
|
|
476
|
+
const decoded = this.readQuotedStringBody(qLine, qCol);
|
|
477
|
+
curText += decoded;
|
|
478
|
+
if (!curStarted) {
|
|
479
|
+
curLine = qLine;
|
|
480
|
+
curCol = qCol;
|
|
481
|
+
curStarted = true;
|
|
482
|
+
}
|
|
483
|
+
} else if (isUnquotedSubstChar(ch)) {
|
|
484
|
+
if (ch === "-" && !curStarted && !isDecimalDigit(this.peek(1))) {
|
|
485
|
+
const after = this.peek(1) === "" ? "EOF" : JSON.stringify(this.peek(1));
|
|
486
|
+
throw new ParseError(
|
|
487
|
+
`unquoted path segment cannot begin with '-' unless followed by a digit (got '-' then ${after}, HOCON.md L270-276)`,
|
|
488
|
+
startLine,
|
|
489
|
+
this.col
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
const uCol = this.col;
|
|
493
|
+
if (curStarted) {
|
|
494
|
+
curText += pendingWs;
|
|
495
|
+
}
|
|
496
|
+
pendingWs = "";
|
|
497
|
+
if (!curStarted) {
|
|
498
|
+
curLine = startLine;
|
|
499
|
+
curCol = uCol;
|
|
500
|
+
curStarted = true;
|
|
501
|
+
}
|
|
502
|
+
while (this.pos < this.input.length && isUnquotedSubstChar(this.peek())) {
|
|
503
|
+
curText += this.advance();
|
|
504
|
+
}
|
|
505
|
+
} else if (ch === ".") {
|
|
506
|
+
const dotCol = this.col;
|
|
507
|
+
pendingWs = "";
|
|
508
|
+
if (!curStarted) {
|
|
509
|
+
throw new ParseError("empty segment in path", startLine, dotCol);
|
|
510
|
+
}
|
|
511
|
+
segments.push({ text: curText, line: curLine, col: curCol });
|
|
512
|
+
curText = "";
|
|
513
|
+
curStarted = false;
|
|
514
|
+
curLine = 0;
|
|
515
|
+
curCol = 0;
|
|
516
|
+
lastDot = [startLine, dotCol];
|
|
517
|
+
this.advance();
|
|
518
|
+
} else if (isHoconNewline(ch)) {
|
|
519
|
+
throw new ParseError("unterminated substitution", startLine, startCol);
|
|
520
|
+
} else if (isHoconWhitespace(ch)) {
|
|
521
|
+
pendingWs += ch;
|
|
522
|
+
this.advance();
|
|
523
|
+
} else {
|
|
524
|
+
throw new ParseError(`unexpected character in substitution path: ${JSON.stringify(ch)}`, startLine, this.col);
|
|
379
525
|
}
|
|
380
|
-
push("unquoted", value.trimEnd(), sl, sc);
|
|
381
|
-
continue;
|
|
382
526
|
}
|
|
383
|
-
|
|
527
|
+
if (curStarted) {
|
|
528
|
+
segments.push({ text: curText, line: curLine, col: curCol });
|
|
529
|
+
} else if (segments.length === 0) {
|
|
530
|
+
throw new ParseError("empty substitution path", startLine, startCol);
|
|
531
|
+
} else {
|
|
532
|
+
const [errLine, errCol] = lastDot ?? [startLine, startCol];
|
|
533
|
+
throw new ParseError("empty segment in path", errLine, errCol);
|
|
534
|
+
}
|
|
535
|
+
return { segments, optional };
|
|
384
536
|
}
|
|
385
|
-
|
|
386
|
-
|
|
537
|
+
};
|
|
538
|
+
function tokenize(input) {
|
|
539
|
+
return new Lexer(input).tokenize();
|
|
540
|
+
}
|
|
541
|
+
function isUnquotedSubstChar(ch) {
|
|
542
|
+
if (ch === "" || isHoconWhitespace(ch)) return false;
|
|
543
|
+
if ('"\\'.includes(ch)) return false;
|
|
544
|
+
if ("{}[]".includes(ch)) return false;
|
|
545
|
+
if (":=,+#`^?!@*&$.".includes(ch)) return false;
|
|
546
|
+
return true;
|
|
547
|
+
}
|
|
548
|
+
function isDecimalDigit(ch) {
|
|
549
|
+
return ch >= "0" && ch <= "9";
|
|
387
550
|
}
|
|
388
551
|
function isUnquotedStart(ch) {
|
|
389
|
-
|
|
552
|
+
if (ch === "" || isHoconWhitespace(ch)) return false;
|
|
553
|
+
if ('{}[],:=+#"$?!@*&^\\'.includes(ch)) return false;
|
|
554
|
+
return true;
|
|
390
555
|
}
|
|
391
556
|
function isUnquotedContinue(ch, nextFn) {
|
|
392
|
-
if (ch === "" ||
|
|
557
|
+
if (ch === "" || isHoconWhitespace(ch)) return false;
|
|
558
|
+
if ('{}[],:=#"$?!@*&^\\'.includes(ch)) return false;
|
|
393
559
|
if (ch === "+" && nextFn() === "=") return false;
|
|
394
560
|
if (ch === "/" && nextFn() === "/") return false;
|
|
395
561
|
return true;
|
|
396
562
|
}
|
|
397
563
|
|
|
398
564
|
// src/internal/parser/parser.ts
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
565
|
+
var EOF_TOKEN = { kind: "eof", value: "", line: 0, col: 0, isQuoted: false, precedingSpace: false, subst: void 0 };
|
|
566
|
+
var Parser = class {
|
|
567
|
+
constructor(tokens) {
|
|
568
|
+
this.tokens = tokens;
|
|
569
|
+
this.pos = 0;
|
|
570
|
+
}
|
|
571
|
+
parse() {
|
|
572
|
+
this.skip("newline");
|
|
573
|
+
const t = this.peek();
|
|
574
|
+
if (t.kind === "lbrace") {
|
|
575
|
+
this.advance();
|
|
576
|
+
const first = this.parseObject(true);
|
|
577
|
+
const allFields = first.kind === "object" ? [...first.fields] : [];
|
|
578
|
+
while (true) {
|
|
579
|
+
this.skip("newline");
|
|
580
|
+
if (this.peek().kind === "eof") break;
|
|
581
|
+
if (this.peek().kind === "lbrace") {
|
|
582
|
+
this.advance();
|
|
583
|
+
const extra = this.parseObject(true);
|
|
584
|
+
if (extra.kind === "object") allFields.push(...extra.fields);
|
|
585
|
+
} else {
|
|
586
|
+
const rest = this.parseObject(false);
|
|
587
|
+
if (rest.kind === "object") allFields.push(...rest.fields);
|
|
588
|
+
break;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
this.skip("newline");
|
|
592
|
+
const remaining = this.peek();
|
|
593
|
+
if (remaining.kind !== "eof") {
|
|
594
|
+
throw new ParseError(`Unexpected token '${remaining.value}' after closing brace`, remaining.line, remaining.col);
|
|
595
|
+
}
|
|
596
|
+
return { kind: "object", fields: allFields, pos: first.pos };
|
|
597
|
+
}
|
|
598
|
+
return this.parseObject(false);
|
|
599
|
+
}
|
|
600
|
+
peek(offset = 0) {
|
|
601
|
+
return this.tokens[this.pos + offset] ?? EOF_TOKEN;
|
|
403
602
|
}
|
|
404
|
-
|
|
405
|
-
const
|
|
406
|
-
if (pos < tokens.length) pos++;
|
|
407
|
-
return
|
|
603
|
+
advance() {
|
|
604
|
+
const t = this.tokens[this.pos];
|
|
605
|
+
if (this.pos < this.tokens.length) this.pos++;
|
|
606
|
+
return t ?? EOF_TOKEN;
|
|
408
607
|
}
|
|
409
|
-
|
|
410
|
-
while (kinds.includes(peek().kind)) advance();
|
|
608
|
+
skip(...kinds) {
|
|
609
|
+
while (kinds.includes(this.peek().kind)) this.advance();
|
|
411
610
|
}
|
|
412
|
-
|
|
413
|
-
const
|
|
414
|
-
return { line:
|
|
611
|
+
currentPos() {
|
|
612
|
+
const t = this.peek();
|
|
613
|
+
return { line: t.line, col: t.col };
|
|
415
614
|
}
|
|
416
|
-
|
|
417
|
-
const p = currentPos();
|
|
615
|
+
parseObject(expectClosingBrace) {
|
|
616
|
+
const p = this.currentPos();
|
|
418
617
|
const fields = [];
|
|
419
618
|
while (true) {
|
|
420
|
-
skip("newline");
|
|
421
|
-
const
|
|
422
|
-
if (
|
|
423
|
-
if (
|
|
424
|
-
advance();
|
|
425
|
-
fields.push(parseInclude());
|
|
426
|
-
skip("newline");
|
|
427
|
-
if (peek().kind === "comma") advance();
|
|
428
|
-
skip("newline");
|
|
619
|
+
this.skip("newline");
|
|
620
|
+
const t = this.peek();
|
|
621
|
+
if (t.kind === "eof" || t.kind === "rbrace") break;
|
|
622
|
+
if (t.kind === "unquoted" && t.value === "include") {
|
|
623
|
+
this.advance();
|
|
624
|
+
fields.push(this.parseInclude());
|
|
625
|
+
this.skip("newline");
|
|
626
|
+
if (this.peek().kind === "comma") this.advance();
|
|
627
|
+
this.skip("newline");
|
|
429
628
|
continue;
|
|
430
629
|
}
|
|
431
|
-
const keyPos = currentPos();
|
|
432
|
-
const key = parseKey();
|
|
433
|
-
skip("newline");
|
|
630
|
+
const keyPos = this.currentPos();
|
|
631
|
+
const key = this.parseKey();
|
|
632
|
+
this.skip("newline");
|
|
434
633
|
let append = false;
|
|
435
|
-
const sep = peek();
|
|
436
|
-
if (sep.kind === "equals") advance();
|
|
634
|
+
const sep = this.peek();
|
|
635
|
+
if (sep.kind === "equals") this.advance();
|
|
437
636
|
else if (sep.kind === "plus_equals") {
|
|
438
|
-
advance();
|
|
637
|
+
this.advance();
|
|
439
638
|
append = true;
|
|
440
|
-
} else if (sep.kind === "colon") advance();
|
|
639
|
+
} else if (sep.kind === "colon") this.advance();
|
|
441
640
|
else if (sep.kind === "lbrace") {
|
|
442
641
|
} else if (sep.kind !== "newline" && sep.kind !== "eof") {
|
|
443
642
|
throw new ParseError(`unexpected token after key: ${sep.kind}`, sep.line, sep.col);
|
|
444
643
|
}
|
|
445
|
-
skip("newline");
|
|
446
|
-
const value = parseValue();
|
|
644
|
+
this.skip("newline");
|
|
645
|
+
const value = this.parseValue();
|
|
447
646
|
fields.push({ key, value, append, pos: keyPos });
|
|
448
|
-
skip("newline");
|
|
449
|
-
if (peek().kind === "comma") advance();
|
|
450
|
-
skip("newline");
|
|
647
|
+
this.skip("newline");
|
|
648
|
+
if (this.peek().kind === "comma") this.advance();
|
|
649
|
+
this.skip("newline");
|
|
451
650
|
}
|
|
452
651
|
if (expectClosingBrace) {
|
|
453
|
-
const
|
|
454
|
-
if (
|
|
455
|
-
advance();
|
|
652
|
+
const t = this.peek();
|
|
653
|
+
if (t.kind !== "rbrace") throw new ParseError("expected }", t.line, t.col);
|
|
654
|
+
this.advance();
|
|
456
655
|
}
|
|
457
656
|
return { kind: "object", fields, pos: p };
|
|
458
657
|
}
|
|
459
|
-
|
|
658
|
+
parseKey() {
|
|
460
659
|
const segments = [];
|
|
461
660
|
let trailingDot = false;
|
|
462
661
|
while (true) {
|
|
463
|
-
const
|
|
464
|
-
if (
|
|
465
|
-
advance();
|
|
466
|
-
segments.push(
|
|
662
|
+
const t = this.peek();
|
|
663
|
+
if (t.kind === "string") {
|
|
664
|
+
this.advance();
|
|
665
|
+
segments.push(t.value);
|
|
467
666
|
trailingDot = false;
|
|
468
|
-
} else if (
|
|
469
|
-
advance();
|
|
470
|
-
const
|
|
667
|
+
} else if (t.kind === "unquoted") {
|
|
668
|
+
this.advance();
|
|
669
|
+
const raw = t.value.startsWith(".") && segments.length > 0 && !t.precedingSpace ? t.value.slice(1) : t.value;
|
|
670
|
+
const parts = raw.split(".");
|
|
471
671
|
const filtered = parts.filter((s) => s.length > 0);
|
|
672
|
+
for (const seg of filtered) {
|
|
673
|
+
if (seg[0] === "-" && !(seg.length >= 2 && seg[1] >= "0" && seg[1] <= "9")) {
|
|
674
|
+
const after = seg.length >= 2 ? JSON.stringify(seg[1]) : "EOF";
|
|
675
|
+
throw new ParseError(
|
|
676
|
+
`unquoted key segment cannot begin with '-' unless followed by a digit (got '-' then ${after} in ${JSON.stringify(seg)}, HOCON.md L270-276)`,
|
|
677
|
+
t.line,
|
|
678
|
+
t.col
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
472
682
|
segments.push(...filtered);
|
|
473
|
-
trailingDot =
|
|
683
|
+
trailingDot = raw.endsWith(".");
|
|
474
684
|
} else {
|
|
475
|
-
if (segments.length === 0) throw new ParseError(`expected key, got ${
|
|
685
|
+
if (segments.length === 0) throw new ParseError(`expected key, got ${t.kind}`, t.line, t.col);
|
|
476
686
|
break;
|
|
477
687
|
}
|
|
478
688
|
if (trailingDot) continue;
|
|
479
|
-
const next = peek();
|
|
689
|
+
const next = this.peek();
|
|
480
690
|
if (next.kind === "unquoted" && next.value === "." && !next.precedingSpace) {
|
|
481
|
-
advance();
|
|
691
|
+
this.advance();
|
|
692
|
+
trailingDot = true;
|
|
693
|
+
continue;
|
|
694
|
+
}
|
|
695
|
+
if (next.kind === "unquoted" && next.value.startsWith(".") && !next.precedingSpace) {
|
|
482
696
|
trailingDot = true;
|
|
483
697
|
continue;
|
|
484
698
|
}
|
|
@@ -486,147 +700,230 @@ function parseTokens(tokens) {
|
|
|
486
700
|
}
|
|
487
701
|
return segments;
|
|
488
702
|
}
|
|
489
|
-
|
|
490
|
-
const p = currentPos();
|
|
491
|
-
skip("newline");
|
|
492
|
-
const
|
|
703
|
+
parseInclude() {
|
|
704
|
+
const p = this.currentPos();
|
|
705
|
+
this.skip("newline");
|
|
706
|
+
const t = this.peek();
|
|
493
707
|
let path2;
|
|
494
708
|
let required = false;
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
709
|
+
let isFile = false;
|
|
710
|
+
if (t.kind === "unquoted" && (t.value === "required(" || t.value === "required" || t.value.startsWith("required("))) {
|
|
711
|
+
if (t.value === "required") {
|
|
712
|
+
const next2 = this.peek(1);
|
|
498
713
|
if (next2.kind !== "unquoted" || !next2.value.startsWith("(")) {
|
|
499
|
-
throw new ParseError("include required must be followed by (",
|
|
714
|
+
throw new ParseError("include required must be followed by (", t.line, t.col);
|
|
500
715
|
}
|
|
501
716
|
}
|
|
502
717
|
required = true;
|
|
503
|
-
advance();
|
|
504
|
-
const innerPrefix =
|
|
718
|
+
this.advance();
|
|
719
|
+
const innerPrefix = t.value.startsWith("required(") ? t.value.slice("required(".length) : "";
|
|
505
720
|
if (innerPrefix.startsWith("url") || innerPrefix.startsWith("classpath")) {
|
|
506
|
-
throw new ParseError("include url(...) and classpath(...) are not supported",
|
|
721
|
+
throw new ParseError("include url(...) and classpath(...) are not supported", t.line, t.col);
|
|
722
|
+
}
|
|
723
|
+
if (innerPrefix.startsWith("file")) {
|
|
724
|
+
isFile = true;
|
|
507
725
|
}
|
|
508
|
-
const next = peek();
|
|
726
|
+
const next = this.peek();
|
|
509
727
|
if (next.kind === "unquoted" && (next.value === "url" || next.value.startsWith("url(") || next.value === "classpath" || next.value.startsWith("classpath("))) {
|
|
510
728
|
throw new ParseError("include url(...) and classpath(...) are not supported", next.line, next.col);
|
|
511
729
|
}
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
while (peek().kind !== "
|
|
516
|
-
|
|
517
|
-
path2 = advance().value;
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
throw new ParseError("include
|
|
526
|
-
|
|
527
|
-
|
|
730
|
+
if (next.kind === "unquoted" && (next.value === "file(" || next.value === "file" || next.value.startsWith("file("))) {
|
|
731
|
+
isFile = true;
|
|
732
|
+
}
|
|
733
|
+
while (this.peek().kind !== "string" && this.peek().kind !== "eof") this.advance();
|
|
734
|
+
if (this.peek().kind === "eof") throw new ParseError("expected include path", t.line, t.col);
|
|
735
|
+
path2 = this.advance().value;
|
|
736
|
+
while (this.peek().kind !== "newline" && this.peek().kind !== "rbrace" && this.peek().kind !== "eof" && this.peek().kind !== "comma") this.advance();
|
|
737
|
+
} else if (t.kind === "string") {
|
|
738
|
+
path2 = this.advance().value;
|
|
739
|
+
} else if (t.kind === "unquoted" && (t.value === "file(" || t.value === "file")) {
|
|
740
|
+
isFile = true;
|
|
741
|
+
this.advance();
|
|
742
|
+
while (this.peek().kind !== "string" && this.peek().kind !== "eof") this.advance();
|
|
743
|
+
if (this.peek().kind === "eof") throw new ParseError("expected include path", t.line, t.col);
|
|
744
|
+
path2 = this.advance().value;
|
|
745
|
+
while (this.peek().kind !== "newline" && this.peek().kind !== "rbrace" && this.peek().kind !== "eof" && this.peek().kind !== "comma") this.advance();
|
|
746
|
+
} else if (t.kind === "unquoted" && (t.value === "url" || t.value.startsWith("url("))) {
|
|
747
|
+
throw new ParseError("include url(...) is not supported", t.line, t.col);
|
|
748
|
+
} else if (t.kind === "unquoted" && (t.value === "classpath" || t.value.startsWith("classpath("))) {
|
|
749
|
+
throw new ParseError("include classpath(...) is not supported", t.line, t.col);
|
|
528
750
|
} else {
|
|
529
|
-
throw new ParseError(`expected include path, got ${
|
|
751
|
+
throw new ParseError(`expected include path, got ${t.kind}`, t.line, t.col);
|
|
530
752
|
}
|
|
531
753
|
return {
|
|
532
754
|
key: [],
|
|
533
|
-
value: { kind: "include", path: path2, required, pos: p },
|
|
755
|
+
value: { kind: "include", path: path2, required, ...isFile ? { isFile: true } : {}, pos: p },
|
|
534
756
|
append: false,
|
|
535
757
|
pos: p
|
|
536
758
|
};
|
|
537
759
|
}
|
|
538
|
-
|
|
539
|
-
const p = currentPos();
|
|
760
|
+
parseValue() {
|
|
761
|
+
const p = this.currentPos();
|
|
540
762
|
const parts = [];
|
|
541
763
|
while (true) {
|
|
542
|
-
const
|
|
543
|
-
if (
|
|
544
|
-
const hadSpace =
|
|
764
|
+
const t = this.peek();
|
|
765
|
+
if (t.kind === "eof" || t.kind === "newline" || t.kind === "rbrace" || t.kind === "rbracket" || t.kind === "comma") break;
|
|
766
|
+
const hadSpace = t.precedingSpace && parts.length > 0;
|
|
545
767
|
let node;
|
|
546
|
-
if (
|
|
547
|
-
advance();
|
|
548
|
-
node = parseObject(true);
|
|
549
|
-
} else if (
|
|
550
|
-
advance();
|
|
551
|
-
node = parseArray();
|
|
552
|
-
} else if (
|
|
553
|
-
advance();
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
768
|
+
if (t.kind === "lbrace") {
|
|
769
|
+
this.advance();
|
|
770
|
+
node = this.parseObject(true);
|
|
771
|
+
} else if (t.kind === "lbracket") {
|
|
772
|
+
this.advance();
|
|
773
|
+
node = this.parseArray();
|
|
774
|
+
} else if (t.kind === "subst") {
|
|
775
|
+
this.advance();
|
|
776
|
+
if (!t.subst) {
|
|
777
|
+
throw new ParseError(`internal: subst token missing payload`, t.line, t.col);
|
|
778
|
+
}
|
|
779
|
+
const payload = t.subst;
|
|
780
|
+
node = { kind: "subst", segments: payload.segments, optional: payload.optional, pos: { line: t.line, col: t.col } };
|
|
781
|
+
} else if (t.kind === "string" || t.kind === "triple_string") {
|
|
782
|
+
this.advance();
|
|
783
|
+
node = { kind: "scalar", raw: t.value, valueType: "string", pos: { line: t.line, col: t.col } };
|
|
784
|
+
} else if (t.kind === "unquoted") {
|
|
785
|
+
this.advance();
|
|
786
|
+
node = { kind: "scalar", raw: t.value, valueType: this.scalarValueType(t.value), pos: { line: t.line, col: t.col } };
|
|
787
|
+
} else if ((t.kind === "colon" || t.kind === "equals") && parts.length > 0) {
|
|
788
|
+
this.advance();
|
|
789
|
+
node = { kind: "scalar", raw: t.value, valueType: "string", pos: { line: t.line, col: t.col } };
|
|
564
790
|
} else {
|
|
565
791
|
break;
|
|
566
792
|
}
|
|
567
793
|
if (hadSpace) {
|
|
568
|
-
parts.push({ kind: "scalar",
|
|
794
|
+
parts.push({ kind: "scalar", raw: " ", valueType: "string", pos: { line: t.line, col: t.col }, _separator: true });
|
|
569
795
|
}
|
|
570
796
|
parts.push(node);
|
|
571
797
|
}
|
|
572
|
-
if (parts.length === 0) throw new ParseError("expected value", peek().line, peek().col);
|
|
798
|
+
if (parts.length === 0) throw new ParseError("expected value", this.peek().line, this.peek().col);
|
|
573
799
|
if (parts.length === 1) return parts[0];
|
|
574
800
|
return { kind: "concat", nodes: parts, pos: p };
|
|
575
801
|
}
|
|
576
|
-
|
|
577
|
-
const p = currentPos();
|
|
802
|
+
parseArray() {
|
|
803
|
+
const p = this.currentPos();
|
|
578
804
|
const items = [];
|
|
579
805
|
while (true) {
|
|
580
|
-
skip("newline");
|
|
581
|
-
if (peek().kind === "rbracket" || peek().kind === "eof") break;
|
|
582
|
-
items.push(parseValue());
|
|
583
|
-
skip("newline");
|
|
584
|
-
if (peek().kind === "comma") advance();
|
|
585
|
-
skip("newline");
|
|
586
|
-
}
|
|
587
|
-
const
|
|
588
|
-
if (
|
|
589
|
-
advance();
|
|
806
|
+
this.skip("newline");
|
|
807
|
+
if (this.peek().kind === "rbracket" || this.peek().kind === "eof") break;
|
|
808
|
+
items.push(this.parseValue());
|
|
809
|
+
this.skip("newline");
|
|
810
|
+
if (this.peek().kind === "comma") this.advance();
|
|
811
|
+
this.skip("newline");
|
|
812
|
+
}
|
|
813
|
+
const t = this.peek();
|
|
814
|
+
if (t.kind !== "rbracket") throw new ParseError("expected ]", t.line, t.col);
|
|
815
|
+
this.advance();
|
|
590
816
|
return { kind: "array", items, pos: p };
|
|
591
817
|
}
|
|
592
|
-
|
|
593
|
-
if (raw === "true") return
|
|
594
|
-
if (raw === "
|
|
595
|
-
if (raw ===
|
|
596
|
-
|
|
597
|
-
if (!isNaN(n) && raw.trim() !== "") return n;
|
|
598
|
-
return raw;
|
|
599
|
-
}
|
|
600
|
-
skip("newline");
|
|
601
|
-
const t = peek();
|
|
602
|
-
if (t.kind === "lbrace") {
|
|
603
|
-
advance();
|
|
604
|
-
const first = parseObject(true);
|
|
605
|
-
const allFields = first.kind === "object" ? [...first.fields] : [];
|
|
606
|
-
while (true) {
|
|
607
|
-
skip("newline");
|
|
608
|
-
if (peek().kind === "eof") break;
|
|
609
|
-
if (peek().kind === "lbrace") {
|
|
610
|
-
advance();
|
|
611
|
-
const extra = parseObject(true);
|
|
612
|
-
if (extra.kind === "object") allFields.push(...extra.fields);
|
|
613
|
-
} else {
|
|
614
|
-
const rest = parseObject(false);
|
|
615
|
-
if (rest.kind === "object") allFields.push(...rest.fields);
|
|
616
|
-
break;
|
|
617
|
-
}
|
|
818
|
+
scalarValueType(raw) {
|
|
819
|
+
if (raw === "true" || raw === "false") return "boolean";
|
|
820
|
+
if (raw === "null") return "null";
|
|
821
|
+
if (raw.length > 0 && (raw.charCodeAt(0) >= 48 && raw.charCodeAt(0) <= 57 || raw.charCodeAt(0) === 45)) {
|
|
822
|
+
if (DECIMAL_NUMBER_RE.test(raw)) return "number";
|
|
618
823
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
824
|
+
return "string";
|
|
825
|
+
}
|
|
826
|
+
};
|
|
827
|
+
function parseTokens(tokens) {
|
|
828
|
+
return new Parser(tokens).parse();
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// src/internal/resolver/types.ts
|
|
832
|
+
var separatorValues = /* @__PURE__ */ new WeakSet();
|
|
833
|
+
function isSubst(v) {
|
|
834
|
+
return v._kind === "subst-placeholder";
|
|
835
|
+
}
|
|
836
|
+
function isConcat(v) {
|
|
837
|
+
return v._kind === "concat-placeholder";
|
|
838
|
+
}
|
|
839
|
+
function isAppend(v) {
|
|
840
|
+
return v._kind === "append-placeholder";
|
|
841
|
+
}
|
|
842
|
+
function isResObj(v) {
|
|
843
|
+
return v._kind === "res-obj";
|
|
844
|
+
}
|
|
845
|
+
function makeResObj() {
|
|
846
|
+
return { _kind: "res-obj", fields: /* @__PURE__ */ new Map(), priorValues: /* @__PURE__ */ new Map() };
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// src/internal/resolver/utils.ts
|
|
850
|
+
function segmentsToKey(segments) {
|
|
851
|
+
return segments.map((s) => {
|
|
852
|
+
const t = s.text;
|
|
853
|
+
if (t === "" || /[^a-zA-Z0-9\-_]/.test(t)) {
|
|
854
|
+
return `"${t.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
855
|
+
}
|
|
856
|
+
return t;
|
|
857
|
+
}).join(".");
|
|
858
|
+
}
|
|
859
|
+
function lookupPath(root, segments) {
|
|
860
|
+
const [head, ...tail] = segments;
|
|
861
|
+
if (head === void 0) return void 0;
|
|
862
|
+
const key = head.text;
|
|
863
|
+
const val = root.fields.get(key);
|
|
864
|
+
if (val === void 0) return void 0;
|
|
865
|
+
if (tail.length === 0) return val;
|
|
866
|
+
if (isResObj(val)) return lookupPath(val, tail);
|
|
867
|
+
return void 0;
|
|
868
|
+
}
|
|
869
|
+
function lookupResObj(root, segments) {
|
|
870
|
+
let cur = root;
|
|
871
|
+
for (const seg of segments) {
|
|
872
|
+
const val = cur.fields.get(seg.text);
|
|
873
|
+
if (val === void 0 || !isResObj(val)) return void 0;
|
|
874
|
+
cur = val;
|
|
875
|
+
}
|
|
876
|
+
return cur;
|
|
877
|
+
}
|
|
878
|
+
function deepMergeHoconValues(base, overlay) {
|
|
879
|
+
const merged = new Map(base.fields);
|
|
880
|
+
for (const [k, v] of overlay.fields) {
|
|
881
|
+
const existing = merged.get(k);
|
|
882
|
+
if (existing?.kind === "object" && v.kind === "object") {
|
|
883
|
+
merged.set(k, deepMergeHoconValues(existing, v));
|
|
884
|
+
} else {
|
|
885
|
+
merged.set(k, v);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
return { kind: "object", fields: merged };
|
|
889
|
+
}
|
|
890
|
+
function deepMergeResObjInto(dst, src) {
|
|
891
|
+
for (const [k, srcVal] of src.fields) {
|
|
892
|
+
const dstVal = dst.fields.get(k);
|
|
893
|
+
if (dstVal !== void 0 && isResObj(dstVal) && isResObj(srcVal)) {
|
|
894
|
+
deepMergeResObjInto(dstVal, srcVal);
|
|
895
|
+
} else {
|
|
896
|
+
if (dstVal !== void 0) dst.priorValues.set(k, dstVal);
|
|
897
|
+
dst.fields.set(k, srcVal);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
for (const [k, srcPrior] of src.priorValues) {
|
|
901
|
+
if (!dst.priorValues.has(k)) {
|
|
902
|
+
dst.priorValues.set(k, srcPrior);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
function hoconValueToResObj(hv) {
|
|
907
|
+
const obj = makeResObj();
|
|
908
|
+
if (hv.kind !== "object") return obj;
|
|
909
|
+
for (const [key, val] of hv.fields) {
|
|
910
|
+
if (val.kind === "object") {
|
|
911
|
+
obj.fields.set(key, hoconValueToResObj(val));
|
|
912
|
+
} else {
|
|
913
|
+
obj.fields.set(key, val);
|
|
623
914
|
}
|
|
624
|
-
return { kind: "object", fields: allFields, pos: first.pos };
|
|
625
915
|
}
|
|
626
|
-
return
|
|
916
|
+
return obj;
|
|
917
|
+
}
|
|
918
|
+
function isFileNotFoundError(e) {
|
|
919
|
+
if (!(e instanceof Error)) return false;
|
|
920
|
+
const code = e.code;
|
|
921
|
+
if (code === "ENOENT" || code === "MODULE_NOT_FOUND") return true;
|
|
922
|
+
const msg = e.message.toLowerCase();
|
|
923
|
+
return msg.includes("not found") || msg.includes("no such file") || msg.includes("enoent");
|
|
627
924
|
}
|
|
628
925
|
|
|
629
|
-
// src/internal/resolver/
|
|
926
|
+
// src/internal/resolver/include-loader.ts
|
|
630
927
|
var nodePath = __toESM(require("path"), 1);
|
|
631
928
|
|
|
632
929
|
// src/internal/properties/properties.ts
|
|
@@ -673,7 +970,7 @@ function recordToHoconValue(obj) {
|
|
|
673
970
|
const fields = /* @__PURE__ */ new Map();
|
|
674
971
|
for (const [key, val] of Object.entries(obj)) {
|
|
675
972
|
if (typeof val === "string") {
|
|
676
|
-
fields.set(key, { kind: "scalar",
|
|
973
|
+
fields.set(key, { kind: "scalar", raw: val, valueType: "string" });
|
|
677
974
|
} else if (val !== null && typeof val === "object") {
|
|
678
975
|
fields.set(key, recordToHoconValue(val));
|
|
679
976
|
}
|
|
@@ -681,356 +978,584 @@ function recordToHoconValue(obj) {
|
|
|
681
978
|
return { kind: "object", fields };
|
|
682
979
|
}
|
|
683
980
|
|
|
684
|
-
// src/internal/resolver/
|
|
685
|
-
var
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
981
|
+
// src/internal/resolver/include-loader.ts
|
|
982
|
+
var IncludeLoader = class {
|
|
983
|
+
constructor(opts) {
|
|
984
|
+
this.opts = opts;
|
|
985
|
+
}
|
|
986
|
+
/**
|
|
987
|
+
* Resolve an include path to an absolute path.
|
|
988
|
+
* - file() includes resolve relative to CWD (or as absolute paths),
|
|
989
|
+
* NOT relative to the including file's directory.
|
|
990
|
+
* - Bare includes resolve relative to the including file's directory (baseDir).
|
|
991
|
+
*/
|
|
992
|
+
resolveIncludePath(includePath, baseDir, isFile) {
|
|
993
|
+
return isFile ? nodePath.resolve(includePath) : baseDir ? nodePath.resolve(baseDir, includePath) : nodePath.resolve(includePath);
|
|
994
|
+
}
|
|
995
|
+
load(includePath, required, isFile) {
|
|
996
|
+
const { baseDir, includeStack = [] } = this.opts;
|
|
997
|
+
const absPath = this.resolveIncludePath(includePath, baseDir, !!isFile);
|
|
998
|
+
if (includeStack.includes(absPath)) {
|
|
999
|
+
throw new ResolveError(`circular include: ${absPath}`, absPath, 0, 0);
|
|
1000
|
+
}
|
|
1001
|
+
if (includeStack.length >= 50) {
|
|
1002
|
+
throw new ResolveError(`include depth limit exceeded (max 50)`, includePath, 0, 0);
|
|
1003
|
+
}
|
|
1004
|
+
const hasExplicitExt = absPath.endsWith(".conf") || absPath.endsWith(".json") || absPath.endsWith(".properties");
|
|
1005
|
+
if (hasExplicitExt) {
|
|
1006
|
+
const result = this.loadSingle(absPath);
|
|
1007
|
+
if (result !== void 0) return result;
|
|
1008
|
+
if (required) {
|
|
1009
|
+
throw new ResolveError(`required include file not found: ${includePath}`, includePath, 0, 0);
|
|
1010
|
+
}
|
|
1011
|
+
return makeResObj();
|
|
1012
|
+
}
|
|
1013
|
+
const merged = makeResObj();
|
|
1014
|
+
let foundAny = false;
|
|
1015
|
+
const probeExts = [".properties", ".json", ".conf"];
|
|
1016
|
+
for (const ext of probeExts) {
|
|
1017
|
+
const obj = this.loadSingle(`${absPath}${ext}`);
|
|
1018
|
+
if (obj !== void 0) {
|
|
1019
|
+
deepMergeResObjInto(merged, obj);
|
|
1020
|
+
foundAny = true;
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
if (!foundAny && required) {
|
|
1024
|
+
throw new ResolveError(`required include file not found: ${includePath}`, includePath, 0, 0);
|
|
1025
|
+
}
|
|
1026
|
+
return merged;
|
|
710
1027
|
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
1028
|
+
async loadAsync(includePath, required, isFile) {
|
|
1029
|
+
const { baseDir, includeStack = [] } = this.opts;
|
|
1030
|
+
const absPath = this.resolveIncludePath(includePath, baseDir, !!isFile);
|
|
1031
|
+
if (includeStack.includes(absPath)) {
|
|
1032
|
+
throw new ResolveError(`circular include: ${absPath}`, absPath, 0, 0);
|
|
1033
|
+
}
|
|
1034
|
+
if (includeStack.length >= 50) {
|
|
1035
|
+
throw new ResolveError(`include depth limit exceeded (max 50)`, includePath, 0, 0);
|
|
1036
|
+
}
|
|
1037
|
+
const hasExplicitExt = absPath.endsWith(".conf") || absPath.endsWith(".json") || absPath.endsWith(".properties");
|
|
1038
|
+
if (hasExplicitExt) {
|
|
1039
|
+
const result = await this.loadSingleAsync(absPath);
|
|
1040
|
+
if (result !== void 0) return result;
|
|
1041
|
+
if (required) {
|
|
1042
|
+
throw new ResolveError(`required include file not found: ${includePath}`, includePath, 0, 0);
|
|
1043
|
+
}
|
|
1044
|
+
return makeResObj();
|
|
1045
|
+
}
|
|
1046
|
+
const merged = makeResObj();
|
|
1047
|
+
let foundAny = false;
|
|
1048
|
+
const probeExts = [".properties", ".json", ".conf"];
|
|
1049
|
+
for (const ext of probeExts) {
|
|
1050
|
+
const obj = await this.loadSingleAsync(`${absPath}${ext}`);
|
|
1051
|
+
if (obj !== void 0) {
|
|
1052
|
+
deepMergeResObjInto(merged, obj);
|
|
1053
|
+
foundAny = true;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
if (!foundAny && required) {
|
|
1057
|
+
throw new ResolveError(`required include file not found: ${includePath}`, includePath, 0, 0);
|
|
1058
|
+
}
|
|
1059
|
+
return merged;
|
|
714
1060
|
}
|
|
715
|
-
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
obj.priorValues.set(head, existing2);
|
|
737
|
-
obj.fields.set(head, {
|
|
738
|
-
_kind: "append-placeholder",
|
|
739
|
-
existing: existing2,
|
|
740
|
-
elem: astToResolverValue(field.value, opts)
|
|
1061
|
+
loadSingle(candidate) {
|
|
1062
|
+
const { readFileSync: readFileSync2, includeStack = [], env } = this.opts;
|
|
1063
|
+
if (includeStack.includes(candidate)) {
|
|
1064
|
+
throw new ResolveError(`circular include: ${candidate}`, candidate, 0, 0);
|
|
1065
|
+
}
|
|
1066
|
+
let content;
|
|
1067
|
+
try {
|
|
1068
|
+
content = readFileSync2(candidate);
|
|
1069
|
+
} catch (e) {
|
|
1070
|
+
if (isFileNotFoundError(e)) return void 0;
|
|
1071
|
+
throw e;
|
|
1072
|
+
}
|
|
1073
|
+
if (candidate.endsWith(".properties")) {
|
|
1074
|
+
return hoconValueToResObj(propertiesToHoconValue(content));
|
|
1075
|
+
}
|
|
1076
|
+
const ast = parseTokens(tokenize(content));
|
|
1077
|
+
return this.onBuildResObj(ast, {
|
|
1078
|
+
env,
|
|
1079
|
+
baseDir: nodePath.dirname(candidate),
|
|
1080
|
+
readFileSync: readFileSync2,
|
|
1081
|
+
includeStack: [...includeStack, candidate]
|
|
741
1082
|
});
|
|
742
|
-
return;
|
|
743
|
-
}
|
|
744
|
-
const existing = obj.fields.get(head);
|
|
745
|
-
const newVal = astToResolverValue(field.value, opts);
|
|
746
|
-
if (existing !== void 0) {
|
|
747
|
-
obj.priorValues.set(head, existing);
|
|
748
1083
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
}
|
|
755
|
-
function astToResolverValue(ast, opts) {
|
|
756
|
-
switch (ast.kind) {
|
|
757
|
-
case "scalar": {
|
|
758
|
-
const sv = { kind: "scalar", value: ast.value };
|
|
759
|
-
if (ast._separator) separatorValues.add(sv);
|
|
760
|
-
return sv;
|
|
1084
|
+
async loadSingleAsync(candidate) {
|
|
1085
|
+
const { readFile, readFileSync: readFileSync2, includeStack = [], env } = this.opts;
|
|
1086
|
+
const read = readFile ? async (p) => readFile(p) : async (p) => readFileSync2(p);
|
|
1087
|
+
if (includeStack.includes(candidate)) {
|
|
1088
|
+
throw new ResolveError(`circular include: ${candidate}`, candidate, 0, 0);
|
|
761
1089
|
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
return
|
|
1090
|
+
let content;
|
|
1091
|
+
try {
|
|
1092
|
+
content = await read(candidate);
|
|
1093
|
+
} catch (e) {
|
|
1094
|
+
if (isFileNotFoundError(e)) return void 0;
|
|
1095
|
+
throw e;
|
|
1096
|
+
}
|
|
1097
|
+
if (candidate.endsWith(".properties")) {
|
|
1098
|
+
return hoconValueToResObj(propertiesToHoconValue(content));
|
|
767
1099
|
}
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
1100
|
+
const ast = parseTokens(tokenize(content));
|
|
1101
|
+
return this.onBuildResObjAsync(ast, {
|
|
1102
|
+
env,
|
|
1103
|
+
baseDir: nodePath.dirname(candidate),
|
|
1104
|
+
readFileSync: readFileSync2,
|
|
1105
|
+
readFile,
|
|
1106
|
+
includeStack: [...includeStack, candidate]
|
|
1107
|
+
});
|
|
774
1108
|
}
|
|
775
|
-
}
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
// src/internal/resolver/structure-builder.ts
|
|
1112
|
+
var StructureBuilder = class _StructureBuilder {
|
|
1113
|
+
constructor(opts) {
|
|
1114
|
+
this.opts = opts;
|
|
1115
|
+
this.loader = new IncludeLoader(opts);
|
|
1116
|
+
this.loader.onBuildResObj = (a, o) => new _StructureBuilder(o).build(a);
|
|
1117
|
+
this.loader.onBuildResObjAsync = async (a, o) => new _StructureBuilder(o).buildAsync(a);
|
|
1118
|
+
}
|
|
1119
|
+
build(ast, pathPrefix = []) {
|
|
1120
|
+
if (ast.kind !== "object") {
|
|
1121
|
+
throw new ResolveError("root AST must be an object", "", ast.pos.line, ast.pos.col);
|
|
1122
|
+
}
|
|
1123
|
+
const obj = makeResObj();
|
|
1124
|
+
for (const field of ast.fields) {
|
|
1125
|
+
this.applyField(obj, field, pathPrefix);
|
|
784
1126
|
}
|
|
1127
|
+
return obj;
|
|
785
1128
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
const
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
} else {
|
|
794
|
-
if (dstVal !== void 0) dst.priorValues.set(k, dstVal);
|
|
795
|
-
dst.fields.set(k, srcVal);
|
|
1129
|
+
async buildAsync(ast, pathPrefix = []) {
|
|
1130
|
+
if (ast.kind !== "object") {
|
|
1131
|
+
throw new ResolveError("root AST must be an object", "", ast.pos.line, ast.pos.col);
|
|
1132
|
+
}
|
|
1133
|
+
const obj = makeResObj();
|
|
1134
|
+
for (const field of ast.fields) {
|
|
1135
|
+
await this.applyFieldAsync(obj, field, pathPrefix);
|
|
796
1136
|
}
|
|
1137
|
+
return obj;
|
|
797
1138
|
}
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
if (resolved !== void 0) {
|
|
804
|
-
result.set(key, resolved);
|
|
805
|
-
} else {
|
|
806
|
-
const prior = obj.priorValues.get(key);
|
|
807
|
-
if (prior !== void 0) {
|
|
808
|
-
const priorResolved = resolveVal(prior, obj, root, resolving, resolvedCache, opts);
|
|
809
|
-
if (priorResolved !== void 0) result.set(key, priorResolved);
|
|
1139
|
+
applyField(obj, field, pathPrefix) {
|
|
1140
|
+
if (field.key.length === 0 && field.value.kind === "include") {
|
|
1141
|
+
const included = this.loader.load(field.value.path, field.value.required, field.value.isFile);
|
|
1142
|
+
if (pathPrefix.length > 0) {
|
|
1143
|
+
this.relativizeResObj(included, pathPrefix);
|
|
810
1144
|
}
|
|
1145
|
+
deepMergeResObjInto(obj, included);
|
|
1146
|
+
return;
|
|
811
1147
|
}
|
|
1148
|
+
const [head, ...tail] = field.key;
|
|
1149
|
+
if (head === void 0) return;
|
|
1150
|
+
if (tail.length > 0) {
|
|
1151
|
+
const syntheticAst = {
|
|
1152
|
+
kind: "object",
|
|
1153
|
+
fields: [{ key: tail, value: field.value, append: field.append, pos: field.pos }],
|
|
1154
|
+
pos: field.pos
|
|
1155
|
+
};
|
|
1156
|
+
this.applyField(obj, { key: [head], value: syntheticAst, append: false, pos: field.pos }, pathPrefix);
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
const childPrefix = [...pathPrefix, head];
|
|
1160
|
+
if (field.append) {
|
|
1161
|
+
const existing2 = obj.fields.get(head) ?? { kind: "array", items: [] };
|
|
1162
|
+
obj.priorValues.set(head, existing2);
|
|
1163
|
+
obj.fields.set(head, {
|
|
1164
|
+
_kind: "append-placeholder",
|
|
1165
|
+
existing: existing2,
|
|
1166
|
+
elem: this.astToResolverValue(field.value, childPrefix)
|
|
1167
|
+
});
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
const existing = obj.fields.get(head);
|
|
1171
|
+
const newVal = this.astToResolverValue(field.value, childPrefix);
|
|
1172
|
+
if (existing !== void 0) {
|
|
1173
|
+
obj.priorValues.set(head, existing);
|
|
1174
|
+
}
|
|
1175
|
+
if (existing !== void 0 && isResObj(existing) && isResObj(newVal)) {
|
|
1176
|
+
deepMergeResObjInto(existing, newVal);
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
obj.fields.set(head, newVal);
|
|
812
1180
|
}
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
if (isAppend(v)) return resolveAppend(v, scope, root, resolving, resolvedCache, opts);
|
|
819
|
-
if (isResObj(v)) return resolveResObj(v, root, resolving, resolvedCache, opts);
|
|
820
|
-
const hv = v;
|
|
821
|
-
if (hv.kind === "array") {
|
|
822
|
-
return {
|
|
823
|
-
kind: "array",
|
|
824
|
-
items: hv.items.map(
|
|
825
|
-
(item) => resolveVal(item, scope, root, resolving, resolvedCache, opts) ?? { kind: "scalar", value: null }
|
|
826
|
-
)
|
|
827
|
-
};
|
|
828
|
-
}
|
|
829
|
-
return hv;
|
|
830
|
-
}
|
|
831
|
-
function resolveSubst(s, scope, root, resolving, resolvedCache, opts) {
|
|
832
|
-
if (resolvedCache.has(s.path)) return resolvedCache.get(s.path);
|
|
833
|
-
if (resolving.has(s.path)) {
|
|
834
|
-
const rootSeg = parseSubstPath(s.path)[0] ?? "";
|
|
835
|
-
const prior = scope.priorValues.get(rootSeg) ?? root.priorValues.get(rootSeg);
|
|
836
|
-
if (prior !== void 0) {
|
|
837
|
-
return resolveVal(prior, scope, root, new Set(resolving), resolvedCache, opts);
|
|
838
|
-
}
|
|
839
|
-
if (s.optional) return void 0;
|
|
840
|
-
throw new ResolveError(`circular substitution: ${s.path}`, s.path, s.line, s.col);
|
|
841
|
-
}
|
|
842
|
-
resolving.add(s.path);
|
|
843
|
-
try {
|
|
844
|
-
const found = lookupPath(root, parseSubstPath(s.path));
|
|
845
|
-
if (found !== void 0) {
|
|
846
|
-
if (isSubst(found) || isConcat(found)) {
|
|
847
|
-
const rootSeg = parseSubstPath(s.path)[0] ?? "";
|
|
848
|
-
const prior = scope.priorValues.get(rootSeg) ?? root.priorValues.get(rootSeg);
|
|
849
|
-
if (prior !== void 0) {
|
|
850
|
-
const result2 = resolveVal(prior, scope, root, resolving, resolvedCache, opts);
|
|
851
|
-
if (result2 !== void 0) resolvedCache.set(s.path, result2);
|
|
852
|
-
return result2;
|
|
853
|
-
}
|
|
1181
|
+
async applyFieldAsync(obj, field, pathPrefix) {
|
|
1182
|
+
if (field.key.length === 0 && field.value.kind === "include") {
|
|
1183
|
+
const included = await this.loader.loadAsync(field.value.path, field.value.required, field.value.isFile);
|
|
1184
|
+
if (pathPrefix.length > 0) {
|
|
1185
|
+
this.relativizeResObj(included, pathPrefix);
|
|
854
1186
|
}
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
return result;
|
|
1187
|
+
deepMergeResObjInto(obj, included);
|
|
1188
|
+
return;
|
|
858
1189
|
}
|
|
859
|
-
const
|
|
860
|
-
if (
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
1190
|
+
const [head, ...tail] = field.key;
|
|
1191
|
+
if (head === void 0) return;
|
|
1192
|
+
if (tail.length > 0) {
|
|
1193
|
+
const syntheticAst = {
|
|
1194
|
+
kind: "object",
|
|
1195
|
+
fields: [{ key: tail, value: field.value, append: field.append, pos: field.pos }],
|
|
1196
|
+
pos: field.pos
|
|
1197
|
+
};
|
|
1198
|
+
await this.applyFieldAsync(obj, { key: [head], value: syntheticAst, append: false, pos: field.pos }, pathPrefix);
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
const childPrefix = [...pathPrefix, head];
|
|
1202
|
+
if (field.append) {
|
|
1203
|
+
const existing2 = obj.fields.get(head) ?? { kind: "array", items: [] };
|
|
1204
|
+
obj.priorValues.set(head, existing2);
|
|
1205
|
+
obj.fields.set(head, {
|
|
1206
|
+
_kind: "append-placeholder",
|
|
1207
|
+
existing: existing2,
|
|
1208
|
+
elem: await this.astToResolverValueAsync(field.value, childPrefix)
|
|
1209
|
+
});
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
1212
|
+
const existing = obj.fields.get(head);
|
|
1213
|
+
const newVal = await this.astToResolverValueAsync(field.value, childPrefix);
|
|
1214
|
+
if (existing !== void 0) {
|
|
1215
|
+
obj.priorValues.set(head, existing);
|
|
1216
|
+
}
|
|
1217
|
+
if (existing !== void 0 && isResObj(existing) && isResObj(newVal)) {
|
|
1218
|
+
deepMergeResObjInto(existing, newVal);
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
obj.fields.set(head, newVal);
|
|
1222
|
+
}
|
|
1223
|
+
astToResolverValue(ast, pathPrefix) {
|
|
1224
|
+
switch (ast.kind) {
|
|
1225
|
+
case "scalar": {
|
|
1226
|
+
const sv = { kind: "scalar", raw: ast.raw, valueType: ast.valueType };
|
|
1227
|
+
if (ast._separator) separatorValues.add(sv);
|
|
1228
|
+
return sv;
|
|
1229
|
+
}
|
|
1230
|
+
case "array":
|
|
1231
|
+
return { kind: "array", items: ast.items.map((i) => this.astToResolverValue(i, pathPrefix)) };
|
|
1232
|
+
case "object": {
|
|
1233
|
+
const inner = this.build(ast, pathPrefix);
|
|
1234
|
+
return inner;
|
|
1235
|
+
}
|
|
1236
|
+
case "subst":
|
|
1237
|
+
return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
|
|
1238
|
+
case "concat":
|
|
1239
|
+
return { _kind: "concat-placeholder", nodes: ast.nodes.map((n) => this.astToResolverValue(n, pathPrefix)) };
|
|
1240
|
+
case "include":
|
|
1241
|
+
return { kind: "scalar", raw: "null", valueType: "null" };
|
|
864
1242
|
}
|
|
865
|
-
if (s.optional) return void 0;
|
|
866
|
-
throw new ResolveError(`could not resolve substitution: \${${s.path}}`, s.path, s.line, s.col);
|
|
867
|
-
} finally {
|
|
868
|
-
resolving.delete(s.path);
|
|
869
1243
|
}
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
1244
|
+
async astToResolverValueAsync(ast, pathPrefix) {
|
|
1245
|
+
switch (ast.kind) {
|
|
1246
|
+
case "scalar": {
|
|
1247
|
+
const sv = { kind: "scalar", raw: ast.raw, valueType: ast.valueType };
|
|
1248
|
+
if (ast._separator) separatorValues.add(sv);
|
|
1249
|
+
return sv;
|
|
1250
|
+
}
|
|
1251
|
+
case "array": {
|
|
1252
|
+
const items = [];
|
|
1253
|
+
for (const i of ast.items) {
|
|
1254
|
+
items.push(await this.astToResolverValueAsync(i, pathPrefix));
|
|
1255
|
+
}
|
|
1256
|
+
return { kind: "array", items };
|
|
1257
|
+
}
|
|
1258
|
+
case "object":
|
|
1259
|
+
return await this.buildAsync(ast, pathPrefix);
|
|
1260
|
+
case "subst":
|
|
1261
|
+
return { _kind: "subst-placeholder", segments: ast.segments, optional: ast.optional, line: ast.pos.line, col: ast.pos.col, prefixLen: 0 };
|
|
1262
|
+
case "concat": {
|
|
1263
|
+
const nodes = [];
|
|
1264
|
+
for (const n of ast.nodes) {
|
|
1265
|
+
nodes.push(await this.astToResolverValueAsync(n, pathPrefix));
|
|
887
1266
|
}
|
|
1267
|
+
return { _kind: "concat-placeholder", nodes };
|
|
888
1268
|
}
|
|
1269
|
+
case "include":
|
|
1270
|
+
return { kind: "scalar", raw: "null", valueType: "null" };
|
|
889
1271
|
}
|
|
890
|
-
return { kind: "object", fields: merged };
|
|
891
1272
|
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
1273
|
+
// ---- Relativize substitution paths for nested includes ----
|
|
1274
|
+
relativizeSubstPaths(val, prefixSegments) {
|
|
1275
|
+
if (isSubst(val)) {
|
|
1276
|
+
const prefixAsSegments = prefixSegments.map((text) => ({ text, line: 0, col: 0 }));
|
|
1277
|
+
val.segments = [...prefixAsSegments, ...val.segments];
|
|
1278
|
+
val.prefixLen += prefixSegments.length;
|
|
1279
|
+
return;
|
|
897
1280
|
}
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
1281
|
+
if (isConcat(val)) {
|
|
1282
|
+
for (const node of val.nodes) {
|
|
1283
|
+
this.relativizeSubstPaths(node, prefixSegments);
|
|
1284
|
+
}
|
|
1285
|
+
return;
|
|
1286
|
+
}
|
|
1287
|
+
if (isAppend(val)) {
|
|
1288
|
+
this.relativizeSubstPaths(val.existing, prefixSegments);
|
|
1289
|
+
this.relativizeSubstPaths(val.elem, prefixSegments);
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
if (isResObj(val)) {
|
|
1293
|
+
this.relativizeResObj(val, prefixSegments);
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
const hv = val;
|
|
1297
|
+
if (hv.kind === "array") {
|
|
1298
|
+
for (const item of hv.items) {
|
|
1299
|
+
this.relativizeSubstPaths(item, prefixSegments);
|
|
1300
|
+
}
|
|
911
1301
|
}
|
|
912
1302
|
}
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
if (elem !== void 0) items.push(elem);
|
|
920
|
-
return { kind: "array", items };
|
|
921
|
-
}
|
|
922
|
-
function lookupPath(root, segments) {
|
|
923
|
-
const [head, ...tail] = segments;
|
|
924
|
-
if (head === void 0 || head === "") {
|
|
925
|
-
if (segments.length > 0) {
|
|
926
|
-
const val2 = root.fields.get("");
|
|
927
|
-
if (val2 === void 0) return void 0;
|
|
928
|
-
if (tail.length === 0) return val2;
|
|
929
|
-
if (isResObj(val2)) return lookupPath(val2, tail);
|
|
930
|
-
return void 0;
|
|
1303
|
+
relativizeResObj(obj, prefixSegments) {
|
|
1304
|
+
for (const val of obj.fields.values()) {
|
|
1305
|
+
this.relativizeSubstPaths(val, prefixSegments);
|
|
1306
|
+
}
|
|
1307
|
+
for (const val of obj.priorValues.values()) {
|
|
1308
|
+
this.relativizeSubstPaths(val, prefixSegments);
|
|
931
1309
|
}
|
|
932
|
-
return void 0;
|
|
933
1310
|
}
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
return
|
|
1311
|
+
};
|
|
1312
|
+
|
|
1313
|
+
// src/value/numeric-array.ts
|
|
1314
|
+
function numericObjectToArray(value) {
|
|
1315
|
+
if (value.kind !== "object") return null;
|
|
1316
|
+
if (value.fields.size === 0) return null;
|
|
1317
|
+
const canonicalInt = /^(0|[1-9][0-9]*)$/;
|
|
1318
|
+
const eligible = [];
|
|
1319
|
+
for (const [key, val] of value.fields) {
|
|
1320
|
+
if (!canonicalInt.test(key)) continue;
|
|
1321
|
+
const n = Number(key);
|
|
1322
|
+
if (!Number.isInteger(n) || n > 2147483647) continue;
|
|
1323
|
+
eligible.push({ n, v: val });
|
|
1324
|
+
}
|
|
1325
|
+
if (eligible.length === 0) return null;
|
|
1326
|
+
eligible.sort((a, b) => a.n - b.n);
|
|
1327
|
+
return eligible.map((e) => e.v);
|
|
939
1328
|
}
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
1329
|
+
|
|
1330
|
+
// src/internal/resolver/substitution-resolver.ts
|
|
1331
|
+
var SubstitutionResolver = class {
|
|
1332
|
+
constructor(root, opts) {
|
|
1333
|
+
this.root = root;
|
|
1334
|
+
this.opts = opts;
|
|
1335
|
+
this.resolving = /* @__PURE__ */ new Set();
|
|
1336
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
1337
|
+
}
|
|
1338
|
+
resolve() {
|
|
1339
|
+
return this.resolveResObj(this.root);
|
|
1340
|
+
}
|
|
1341
|
+
resolveResObj(obj) {
|
|
1342
|
+
const result = /* @__PURE__ */ new Map();
|
|
1343
|
+
for (const [key, val] of obj.fields) {
|
|
1344
|
+
const resolved = this.resolveVal(val, obj);
|
|
1345
|
+
if (resolved !== void 0) {
|
|
1346
|
+
if (resolved.kind === "object") {
|
|
1347
|
+
const prior = obj.priorValues.get(key);
|
|
1348
|
+
if (prior !== void 0) {
|
|
1349
|
+
const priorResolved = this.resolveVal(prior, obj);
|
|
1350
|
+
if (priorResolved !== void 0 && priorResolved.kind === "object") {
|
|
1351
|
+
result.set(
|
|
1352
|
+
key,
|
|
1353
|
+
deepMergeHoconValues(
|
|
1354
|
+
priorResolved,
|
|
1355
|
+
resolved
|
|
1356
|
+
)
|
|
1357
|
+
);
|
|
1358
|
+
continue;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
result.set(key, resolved);
|
|
1363
|
+
} else {
|
|
1364
|
+
const prior = obj.priorValues.get(key);
|
|
1365
|
+
if (prior !== void 0) {
|
|
1366
|
+
const priorResolved = this.resolveVal(prior, obj);
|
|
1367
|
+
if (priorResolved !== void 0) result.set(key, priorResolved);
|
|
1368
|
+
}
|
|
952
1369
|
}
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1370
|
+
}
|
|
1371
|
+
return { kind: "object", fields: result };
|
|
1372
|
+
}
|
|
1373
|
+
resolveVal(v, scope) {
|
|
1374
|
+
if (isSubst(v)) return this.resolveSubst(v, scope);
|
|
1375
|
+
if (isConcat(v)) return this.resolveConcat(v.nodes, scope);
|
|
1376
|
+
if (isAppend(v)) return this.resolveAppend(v, scope);
|
|
1377
|
+
if (isResObj(v)) return this.resolveResObj(v);
|
|
1378
|
+
const hv = v;
|
|
1379
|
+
if (hv.kind === "array") {
|
|
1380
|
+
return {
|
|
1381
|
+
kind: "array",
|
|
1382
|
+
items: hv.items.map(
|
|
1383
|
+
(item) => this.resolveVal(item, scope) ?? { kind: "scalar", raw: "null", valueType: "null" }
|
|
1384
|
+
)
|
|
1385
|
+
};
|
|
1386
|
+
}
|
|
1387
|
+
return hv;
|
|
1388
|
+
}
|
|
1389
|
+
segmentsEqual(a, b) {
|
|
1390
|
+
return a.length === b.length && a.every((seg, i) => seg.text === b[i]?.text);
|
|
1391
|
+
}
|
|
1392
|
+
resolveSubst(s, scope) {
|
|
1393
|
+
const key = segmentsToKey(s.segments);
|
|
1394
|
+
if (this.cache.has(key)) return this.cache.get(key);
|
|
1395
|
+
if (this.resolving.has(key)) {
|
|
1396
|
+
let prior;
|
|
1397
|
+
if (s.prefixLen > 0) {
|
|
1398
|
+
const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
|
|
1399
|
+
const parentScope = lookupResObj(
|
|
1400
|
+
this.root,
|
|
1401
|
+
s.segments.slice(0, s.segments.length - 1)
|
|
1402
|
+
);
|
|
1403
|
+
prior = parentScope?.priorValues.get(leafSeg);
|
|
1404
|
+
} else {
|
|
1405
|
+
const rootSeg = s.segments[0]?.text ?? "";
|
|
1406
|
+
prior = scope.priorValues.get(rootSeg) ?? this.root.priorValues.get(rootSeg);
|
|
1407
|
+
}
|
|
1408
|
+
if (prior !== void 0) {
|
|
1409
|
+
const saved = this.resolving;
|
|
1410
|
+
this.resolving = new Set(saved);
|
|
1411
|
+
try {
|
|
1412
|
+
return this.resolveVal(prior, scope);
|
|
1413
|
+
} finally {
|
|
1414
|
+
this.resolving = saved;
|
|
1415
|
+
}
|
|
965
1416
|
}
|
|
966
|
-
|
|
967
|
-
|
|
1417
|
+
if (s.optional) return void 0;
|
|
1418
|
+
throw new ResolveError(
|
|
1419
|
+
`circular substitution: ${key}`,
|
|
1420
|
+
key,
|
|
1421
|
+
s.line,
|
|
1422
|
+
s.col
|
|
1423
|
+
);
|
|
968
1424
|
}
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1425
|
+
this.resolving.add(key);
|
|
1426
|
+
try {
|
|
1427
|
+
const found = lookupPath(this.root, s.segments);
|
|
1428
|
+
if (found !== void 0) {
|
|
1429
|
+
if (isSubst(found) || isConcat(found)) {
|
|
1430
|
+
const isSelfRef = isSubst(found) ? this.segmentsEqual(found.segments, s.segments) : isConcat(found) && found.nodes.some(
|
|
1431
|
+
(n) => isSubst(n) && this.segmentsEqual(n.segments, s.segments)
|
|
1432
|
+
);
|
|
1433
|
+
if (isSelfRef) {
|
|
1434
|
+
let prior;
|
|
1435
|
+
if (s.prefixLen > 0) {
|
|
1436
|
+
const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
|
|
1437
|
+
const parentScope = lookupResObj(
|
|
1438
|
+
this.root,
|
|
1439
|
+
s.segments.slice(0, s.segments.length - 1)
|
|
1440
|
+
);
|
|
1441
|
+
prior = parentScope?.priorValues.get(leafSeg);
|
|
1442
|
+
} else {
|
|
1443
|
+
const rootSeg = s.segments[0]?.text ?? "";
|
|
1444
|
+
prior = scope.priorValues.get(rootSeg) ?? this.root.priorValues.get(rootSeg);
|
|
1445
|
+
}
|
|
1446
|
+
if (prior !== void 0) {
|
|
1447
|
+
const result2 = this.resolveVal(prior, scope);
|
|
1448
|
+
if (result2 !== void 0) this.cache.set(key, result2);
|
|
1449
|
+
return result2;
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
let result = this.resolveVal(found, scope);
|
|
1454
|
+
const effectiveLen = s.segments.length - s.prefixLen;
|
|
1455
|
+
if (effectiveLen === 1 && result !== void 0 && result.kind === "object") {
|
|
1456
|
+
const leafSeg = s.segments[s.segments.length - 1]?.text ?? "";
|
|
1457
|
+
let prior;
|
|
1458
|
+
if (s.prefixLen > 0) {
|
|
1459
|
+
const parentScope = lookupResObj(
|
|
1460
|
+
this.root,
|
|
1461
|
+
s.segments.slice(0, s.segments.length - 1)
|
|
1462
|
+
);
|
|
1463
|
+
prior = parentScope?.priorValues.get(leafSeg);
|
|
1464
|
+
} else {
|
|
1465
|
+
prior = scope.priorValues.get(leafSeg) ?? this.root.priorValues.get(leafSeg);
|
|
1466
|
+
}
|
|
1467
|
+
if (prior !== void 0) {
|
|
1468
|
+
const priorResolved = this.resolveVal(prior, scope);
|
|
1469
|
+
if (priorResolved !== void 0 && priorResolved.kind === "object") {
|
|
1470
|
+
result = deepMergeHoconValues(
|
|
1471
|
+
priorResolved,
|
|
1472
|
+
result
|
|
1473
|
+
);
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
if (result !== void 0) this.cache.set(key, result);
|
|
1478
|
+
return result;
|
|
1479
|
+
}
|
|
1480
|
+
const envKey = s.segments.map((seg) => seg.text).join(".");
|
|
1481
|
+
const envVal = this.opts.env[envKey] ?? (s.prefixLen > 0 ? this.opts.env[s.segments.slice(s.prefixLen).map((seg) => seg.text).join(".")] : void 0);
|
|
1482
|
+
if (envVal !== void 0) {
|
|
1483
|
+
const result = { kind: "scalar", raw: envVal, valueType: "string" };
|
|
1484
|
+
this.cache.set(key, result);
|
|
1485
|
+
return result;
|
|
1486
|
+
}
|
|
1487
|
+
if (s.optional) return void 0;
|
|
1488
|
+
throw new ResolveError(
|
|
1489
|
+
`could not resolve substitution: \${${key}}`,
|
|
1490
|
+
key,
|
|
1491
|
+
s.line,
|
|
1492
|
+
s.col
|
|
1493
|
+
);
|
|
1494
|
+
} finally {
|
|
1495
|
+
this.resolving.delete(key);
|
|
1017
1496
|
}
|
|
1018
|
-
return makeResObj();
|
|
1019
1497
|
}
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
const
|
|
1025
|
-
if (
|
|
1026
|
-
|
|
1027
|
-
|
|
1498
|
+
resolveConcat(nodes, scope) {
|
|
1499
|
+
const resolved = nodes.map((n) => this.resolveVal(n, scope)).filter((v) => v !== void 0);
|
|
1500
|
+
if (resolved.length === 0) return { kind: "scalar", raw: "null", valueType: "null" };
|
|
1501
|
+
if (resolved.length === 1) return resolved[0];
|
|
1502
|
+
const nonSep = resolved.filter((v) => !separatorValues.has(v));
|
|
1503
|
+
if (nonSep.length === 0) {
|
|
1504
|
+
const str = resolved.map((v) => v.kind === "scalar" ? v.raw : JSON.stringify(v)).join("");
|
|
1505
|
+
return { kind: "scalar", raw: str, valueType: "string" };
|
|
1506
|
+
}
|
|
1507
|
+
const joinPair = (left, right) => {
|
|
1508
|
+
if (left.kind === "object" && right.kind === "object") {
|
|
1509
|
+
return deepMergeHoconValues(left, right);
|
|
1510
|
+
}
|
|
1511
|
+
if (left.kind === "array" && right.kind === "object") {
|
|
1512
|
+
const converted = numericObjectToArray(right);
|
|
1513
|
+
if (converted !== null) {
|
|
1514
|
+
return { kind: "array", items: [...left.items, ...converted] };
|
|
1515
|
+
}
|
|
1516
|
+
return { kind: "array", items: [...left.items, right] };
|
|
1517
|
+
}
|
|
1518
|
+
if (left.kind === "object" && right.kind === "array") {
|
|
1519
|
+
const converted = numericObjectToArray(left);
|
|
1520
|
+
if (converted !== null) {
|
|
1521
|
+
return { kind: "array", items: [...converted, ...right.items] };
|
|
1522
|
+
}
|
|
1523
|
+
return { kind: "array", items: [left, ...right.items] };
|
|
1524
|
+
}
|
|
1525
|
+
if (left.kind === "array" && right.kind === "array") {
|
|
1526
|
+
return { kind: "array", items: [...left.items, ...right.items] };
|
|
1527
|
+
}
|
|
1528
|
+
if (left.kind === "array") {
|
|
1529
|
+
return { kind: "array", items: [...left.items, right] };
|
|
1530
|
+
}
|
|
1531
|
+
if (right.kind === "array") {
|
|
1532
|
+
return { kind: "array", items: [left, ...right.items] };
|
|
1533
|
+
}
|
|
1534
|
+
const leftStr = left.kind === "scalar" ? left.raw : JSON.stringify(left);
|
|
1535
|
+
const rightStr = right.kind === "scalar" ? right.raw : JSON.stringify(right);
|
|
1536
|
+
return { kind: "scalar", raw: leftStr + rightStr, valueType: "string" };
|
|
1537
|
+
};
|
|
1538
|
+
const [head, ...tail] = nonSep;
|
|
1539
|
+
const folded = tail.reduce(joinPair, head);
|
|
1540
|
+
if (folded.kind === "scalar") {
|
|
1541
|
+
const str = resolved.map((v) => v.kind === "scalar" ? v.raw : JSON.stringify(v)).join("");
|
|
1542
|
+
return { kind: "scalar", raw: str, valueType: "string" };
|
|
1028
1543
|
}
|
|
1544
|
+
return folded;
|
|
1029
1545
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1546
|
+
resolveAppend(a, scope) {
|
|
1547
|
+
const existing = this.resolveVal(a.existing, scope) ?? { kind: "array", items: [] };
|
|
1548
|
+
const elem = this.resolveVal(a.elem, scope);
|
|
1549
|
+
const items = existing.kind === "array" ? [...existing.items] : [existing];
|
|
1550
|
+
if (elem !== void 0) items.push(elem);
|
|
1551
|
+
return { kind: "array", items };
|
|
1032
1552
|
}
|
|
1033
|
-
|
|
1553
|
+
};
|
|
1554
|
+
|
|
1555
|
+
// src/internal/resolver/resolver.ts
|
|
1556
|
+
function resolve2(ast, opts) {
|
|
1557
|
+
const root = new StructureBuilder(opts).build(ast);
|
|
1558
|
+
return new SubstitutionResolver(root, opts).resolve();
|
|
1034
1559
|
}
|
|
1035
1560
|
|
|
1036
1561
|
// src/config.ts
|
|
@@ -1045,39 +1570,36 @@ var Config = class _Config {
|
|
|
1045
1570
|
}
|
|
1046
1571
|
getString(path2) {
|
|
1047
1572
|
const v = this.requireScalar(path2);
|
|
1048
|
-
|
|
1049
|
-
return v;
|
|
1573
|
+
return v.raw;
|
|
1050
1574
|
}
|
|
1051
1575
|
getNumber(path2) {
|
|
1052
1576
|
const v = this.requireScalar(path2);
|
|
1053
|
-
|
|
1054
|
-
if (
|
|
1055
|
-
|
|
1056
|
-
if (coerced !== void 0) return coerced;
|
|
1057
|
-
}
|
|
1058
|
-
throw new ConfigError(`expected number at ${path2}, got ${typeof v}`, path2);
|
|
1577
|
+
const coerced = coerceNumber(v.raw);
|
|
1578
|
+
if (coerced !== void 0) return coerced;
|
|
1579
|
+
throw new ConfigError(`expected number at ${path2}, got ${v.valueType}`, path2);
|
|
1059
1580
|
}
|
|
1060
1581
|
getBoolean(path2) {
|
|
1061
1582
|
const v = this.requireScalar(path2);
|
|
1062
|
-
|
|
1063
|
-
if (
|
|
1064
|
-
|
|
1065
|
-
if (coerced !== void 0) return coerced;
|
|
1066
|
-
}
|
|
1067
|
-
throw new ConfigError(`expected boolean at ${path2}, got ${typeof v}`, path2);
|
|
1583
|
+
const coerced = coerceBoolean(v.raw);
|
|
1584
|
+
if (coerced !== void 0) return coerced;
|
|
1585
|
+
throw new ConfigError(`expected boolean at ${path2}, got ${v.valueType}`, path2);
|
|
1068
1586
|
}
|
|
1069
1587
|
getDuration(path2, unit) {
|
|
1070
1588
|
const v = this.requireScalar(path2);
|
|
1071
|
-
if (
|
|
1072
|
-
|
|
1073
|
-
|
|
1589
|
+
if (v.valueType !== "string" && v.valueType !== "number") {
|
|
1590
|
+
throw new ConfigError(`expected duration at ${path2}, got ${v.valueType}`, path2);
|
|
1591
|
+
}
|
|
1592
|
+
const result = parseDuration(v.raw, unit);
|
|
1593
|
+
if (Number.isNaN(result)) throw new ConfigError(`invalid duration at ${path2}: ${JSON.stringify(v.raw)}`, path2);
|
|
1074
1594
|
return result;
|
|
1075
1595
|
}
|
|
1076
1596
|
getBytes(path2, unit) {
|
|
1077
1597
|
const v = this.requireScalar(path2);
|
|
1078
|
-
if (
|
|
1079
|
-
|
|
1080
|
-
|
|
1598
|
+
if (v.valueType !== "string" && v.valueType !== "number") {
|
|
1599
|
+
throw new ConfigError(`expected byte size at ${path2}, got ${v.valueType}`, path2);
|
|
1600
|
+
}
|
|
1601
|
+
const result = parseBytes(v.raw, unit);
|
|
1602
|
+
if (Number.isNaN(result)) throw new ConfigError(`invalid byte size at ${path2}: ${JSON.stringify(v.raw)}`, path2);
|
|
1081
1603
|
return result;
|
|
1082
1604
|
}
|
|
1083
1605
|
getConfig(path2) {
|
|
@@ -1089,6 +1611,10 @@ var Config = class _Config {
|
|
|
1089
1611
|
getList(path2) {
|
|
1090
1612
|
const v = this.lookupNode(path2);
|
|
1091
1613
|
if (v === void 0) throw new ConfigError(`path not found: ${path2}`, path2);
|
|
1614
|
+
if (v.kind === "object") {
|
|
1615
|
+
const converted = numericObjectToArray(v);
|
|
1616
|
+
if (converted !== null) return converted.map(hoconToJs);
|
|
1617
|
+
}
|
|
1092
1618
|
if (v.kind !== "array") throw new ConfigError(`expected array at ${path2}`, path2);
|
|
1093
1619
|
return v.items.map(hoconToJs);
|
|
1094
1620
|
}
|
|
@@ -1120,7 +1646,7 @@ var Config = class _Config {
|
|
|
1120
1646
|
const v = this.lookupNode(path2);
|
|
1121
1647
|
if (v === void 0) throw new ConfigError(`path not found: ${path2}`, path2);
|
|
1122
1648
|
if (v.kind !== "scalar") throw new ConfigError(`expected scalar at ${path2}, got ${v.kind}`, path2);
|
|
1123
|
-
return v
|
|
1649
|
+
return v;
|
|
1124
1650
|
}
|
|
1125
1651
|
};
|
|
1126
1652
|
function splitConfigPath(path2) {
|
|
@@ -1162,10 +1688,22 @@ function splitConfigPath(path2) {
|
|
|
1162
1688
|
}
|
|
1163
1689
|
return segments;
|
|
1164
1690
|
}
|
|
1691
|
+
function scalarToJs(raw, valueType) {
|
|
1692
|
+
switch (valueType) {
|
|
1693
|
+
case "null":
|
|
1694
|
+
return null;
|
|
1695
|
+
case "boolean":
|
|
1696
|
+
return raw === "true";
|
|
1697
|
+
case "number":
|
|
1698
|
+
return Number(raw);
|
|
1699
|
+
case "string":
|
|
1700
|
+
return raw;
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1165
1703
|
function hoconToJs(v) {
|
|
1166
1704
|
switch (v.kind) {
|
|
1167
1705
|
case "scalar":
|
|
1168
|
-
return v.
|
|
1706
|
+
return scalarToJs(v.raw, v.valueType);
|
|
1169
1707
|
case "array":
|
|
1170
1708
|
return v.items.map(hoconToJs);
|
|
1171
1709
|
case "object": {
|