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