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