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