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