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