@o3co/ts.hocon 0.1.4 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +6 -6
- package/README.md +51 -6
- package/dist/index.cjs +544 -39
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +544 -39
- package/dist/{parse-C1NDMzD5.d.cts → parse-C4VOYuEk.d.cts} +8 -5
- package/dist/{parse-C1NDMzD5.d.ts → parse-C4VOYuEk.d.ts} +8 -5
- package/dist/zod.cjs +398 -38
- package/dist/zod.d.cts +1 -1
- package/dist/zod.d.ts +1 -1
- package/dist/zod.js +398 -38
- package/package.json +19 -2
package/dist/index.cjs
CHANGED
|
@@ -54,6 +54,130 @@ function coerceNumber(value) {
|
|
|
54
54
|
if (!/^-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?$/.test(value)) return void 0;
|
|
55
55
|
return Number(value);
|
|
56
56
|
}
|
|
57
|
+
var DURATION_UNITS = {
|
|
58
|
+
ns: 1e-6,
|
|
59
|
+
nanosecond: 1e-6,
|
|
60
|
+
nanoseconds: 1e-6,
|
|
61
|
+
us: 1e-3,
|
|
62
|
+
microsecond: 1e-3,
|
|
63
|
+
microseconds: 1e-3,
|
|
64
|
+
ms: 1,
|
|
65
|
+
millisecond: 1,
|
|
66
|
+
milliseconds: 1,
|
|
67
|
+
s: 1e3,
|
|
68
|
+
second: 1e3,
|
|
69
|
+
seconds: 1e3,
|
|
70
|
+
m: 6e4,
|
|
71
|
+
minute: 6e4,
|
|
72
|
+
minutes: 6e4,
|
|
73
|
+
h: 36e5,
|
|
74
|
+
hour: 36e5,
|
|
75
|
+
hours: 36e5,
|
|
76
|
+
d: 864e5,
|
|
77
|
+
day: 864e5,
|
|
78
|
+
days: 864e5
|
|
79
|
+
};
|
|
80
|
+
var OUTPUT_DURATION_UNITS = {
|
|
81
|
+
ns: 1e-6,
|
|
82
|
+
us: 1e-3,
|
|
83
|
+
ms: 1,
|
|
84
|
+
s: 1e3,
|
|
85
|
+
m: 6e4,
|
|
86
|
+
h: 36e5,
|
|
87
|
+
d: 864e5
|
|
88
|
+
};
|
|
89
|
+
function parseDuration(value, outputUnit = "ms") {
|
|
90
|
+
const trimmed = value.trim();
|
|
91
|
+
let i = 0;
|
|
92
|
+
while (i < trimmed.length) {
|
|
93
|
+
const ch = trimmed[i];
|
|
94
|
+
if (ch !== "-" && ch !== "." && (ch < "0" || ch > "9")) break;
|
|
95
|
+
i++;
|
|
96
|
+
}
|
|
97
|
+
if (i === 0) return NaN;
|
|
98
|
+
const num = Number(trimmed.slice(0, i));
|
|
99
|
+
if (Number.isNaN(num)) return NaN;
|
|
100
|
+
const unit = trimmed.slice(i).trim().toLowerCase();
|
|
101
|
+
const mult = DURATION_UNITS[unit];
|
|
102
|
+
if (mult === void 0) return NaN;
|
|
103
|
+
const ms = num * mult;
|
|
104
|
+
const divisor = OUTPUT_DURATION_UNITS[outputUnit];
|
|
105
|
+
if (divisor === void 0) return NaN;
|
|
106
|
+
return ms / divisor;
|
|
107
|
+
}
|
|
108
|
+
var BYTE_UNITS = {
|
|
109
|
+
B: 1,
|
|
110
|
+
byte: 1,
|
|
111
|
+
bytes: 1,
|
|
112
|
+
KB: 1e3,
|
|
113
|
+
kilobyte: 1e3,
|
|
114
|
+
kilobytes: 1e3,
|
|
115
|
+
KiB: 1024,
|
|
116
|
+
kibibyte: 1024,
|
|
117
|
+
kibibytes: 1024,
|
|
118
|
+
MB: 1e6,
|
|
119
|
+
megabyte: 1e6,
|
|
120
|
+
megabytes: 1e6,
|
|
121
|
+
MiB: 1048576,
|
|
122
|
+
mebibyte: 1048576,
|
|
123
|
+
mebibytes: 1048576,
|
|
124
|
+
GB: 1e9,
|
|
125
|
+
gigabyte: 1e9,
|
|
126
|
+
gigabytes: 1e9,
|
|
127
|
+
GiB: 1073741824,
|
|
128
|
+
gibibyte: 1073741824,
|
|
129
|
+
gibibytes: 1073741824,
|
|
130
|
+
TB: 1e12,
|
|
131
|
+
terabyte: 1e12,
|
|
132
|
+
terabytes: 1e12,
|
|
133
|
+
TiB: 1099511627776,
|
|
134
|
+
tebibyte: 1099511627776,
|
|
135
|
+
tebibytes: 1099511627776,
|
|
136
|
+
// lowercase short-form aliases
|
|
137
|
+
b: 1,
|
|
138
|
+
kb: 1e3,
|
|
139
|
+
kib: 1024,
|
|
140
|
+
mb: 1e6,
|
|
141
|
+
mib: 1048576,
|
|
142
|
+
gb: 1e9,
|
|
143
|
+
gib: 1073741824,
|
|
144
|
+
tb: 1e12,
|
|
145
|
+
tib: 1099511627776
|
|
146
|
+
};
|
|
147
|
+
var OUTPUT_BYTE_UNITS = {
|
|
148
|
+
B: 1,
|
|
149
|
+
KB: 1e3,
|
|
150
|
+
KiB: 1024,
|
|
151
|
+
MB: 1e6,
|
|
152
|
+
MiB: 1048576,
|
|
153
|
+
GB: 1e9,
|
|
154
|
+
GiB: 1073741824,
|
|
155
|
+
TB: 1e12,
|
|
156
|
+
TiB: 1099511627776
|
|
157
|
+
};
|
|
158
|
+
function parseBytes(value, outputUnit = "B") {
|
|
159
|
+
const trimmed = value.trim();
|
|
160
|
+
let i = 0;
|
|
161
|
+
while (i < trimmed.length) {
|
|
162
|
+
const ch = trimmed[i];
|
|
163
|
+
if (ch !== "-" && ch !== "." && (ch < "0" || ch > "9")) break;
|
|
164
|
+
i++;
|
|
165
|
+
}
|
|
166
|
+
if (i === 0) return NaN;
|
|
167
|
+
const num = Number(trimmed.slice(0, i));
|
|
168
|
+
if (Number.isNaN(num)) return NaN;
|
|
169
|
+
const unit = trimmed.slice(i).trim();
|
|
170
|
+
let mult = BYTE_UNITS[unit];
|
|
171
|
+
if (mult === void 0) {
|
|
172
|
+
mult = BYTE_UNITS[unit.toLowerCase()];
|
|
173
|
+
}
|
|
174
|
+
if (mult === void 0) return NaN;
|
|
175
|
+
const bytes = num * mult;
|
|
176
|
+
const divisor = OUTPUT_BYTE_UNITS[outputUnit];
|
|
177
|
+
if (divisor === void 0) return NaN;
|
|
178
|
+
const result = bytes / divisor;
|
|
179
|
+
return outputUnit === "B" ? Math.round(result) : result;
|
|
180
|
+
}
|
|
57
181
|
|
|
58
182
|
// src/errors.ts
|
|
59
183
|
var ParseError = class extends Error {
|
|
@@ -116,6 +240,20 @@ var Config = class _Config {
|
|
|
116
240
|
}
|
|
117
241
|
throw new ConfigError(`expected boolean at ${path2}, got ${typeof v}`, path2);
|
|
118
242
|
}
|
|
243
|
+
getDuration(path2, unit) {
|
|
244
|
+
const v = this.requireScalar(path2);
|
|
245
|
+
if (typeof v !== "string") throw new ConfigError(`expected duration string at ${path2}, got ${typeof v}`, path2);
|
|
246
|
+
const result = parseDuration(v, unit);
|
|
247
|
+
if (Number.isNaN(result)) throw new ConfigError(`invalid duration at ${path2}: ${JSON.stringify(v)}`, path2);
|
|
248
|
+
return result;
|
|
249
|
+
}
|
|
250
|
+
getBytes(path2, unit) {
|
|
251
|
+
const v = this.requireScalar(path2);
|
|
252
|
+
if (typeof v !== "string") throw new ConfigError(`expected byte size string at ${path2}, got ${typeof v}`, path2);
|
|
253
|
+
const result = parseBytes(v, unit);
|
|
254
|
+
if (Number.isNaN(result)) throw new ConfigError(`invalid byte size at ${path2}: ${JSON.stringify(v)}`, path2);
|
|
255
|
+
return result;
|
|
256
|
+
}
|
|
119
257
|
getConfig(path2) {
|
|
120
258
|
const v = this.lookupNode(path2);
|
|
121
259
|
if (v === void 0) throw new ConfigError(`path not found: ${path2}`, path2);
|
|
@@ -142,7 +280,7 @@ var Config = class _Config {
|
|
|
142
280
|
return hoconToJs(this.root);
|
|
143
281
|
}
|
|
144
282
|
lookupNode(path2) {
|
|
145
|
-
const segments = path2
|
|
283
|
+
const segments = splitConfigPath(path2);
|
|
146
284
|
let current = this.root;
|
|
147
285
|
for (const seg of segments) {
|
|
148
286
|
if (current.kind !== "object") return void 0;
|
|
@@ -159,6 +297,45 @@ var Config = class _Config {
|
|
|
159
297
|
return v.value;
|
|
160
298
|
}
|
|
161
299
|
};
|
|
300
|
+
function splitConfigPath(path2) {
|
|
301
|
+
const segments = [];
|
|
302
|
+
let i = 0;
|
|
303
|
+
while (i < path2.length) {
|
|
304
|
+
if (path2[i] === '"') {
|
|
305
|
+
i++;
|
|
306
|
+
let segment = "";
|
|
307
|
+
let closed = false;
|
|
308
|
+
while (i < path2.length) {
|
|
309
|
+
const ch = path2[i];
|
|
310
|
+
if (ch === "\\" && i + 1 < path2.length) {
|
|
311
|
+
const next = path2[i + 1];
|
|
312
|
+
segment += next;
|
|
313
|
+
i += 2;
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
if (ch === '"') {
|
|
317
|
+
closed = true;
|
|
318
|
+
i++;
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
segment += ch;
|
|
322
|
+
i++;
|
|
323
|
+
}
|
|
324
|
+
if (!closed) throw new ConfigError(`unterminated quoted path segment: ${path2}`, path2);
|
|
325
|
+
segments.push(segment);
|
|
326
|
+
if (i < path2.length && path2[i] === ".") i++;
|
|
327
|
+
} else {
|
|
328
|
+
const dot = path2.indexOf(".", i);
|
|
329
|
+
if (dot === -1) {
|
|
330
|
+
segments.push(path2.slice(i));
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
segments.push(path2.slice(i, dot));
|
|
334
|
+
i = dot + 1;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return segments;
|
|
338
|
+
}
|
|
162
339
|
function hoconToJs(v) {
|
|
163
340
|
switch (v.kind) {
|
|
164
341
|
case "scalar":
|
|
@@ -272,6 +449,7 @@ function tokenize(input) {
|
|
|
272
449
|
advance();
|
|
273
450
|
advance();
|
|
274
451
|
let value = "";
|
|
452
|
+
let closed = false;
|
|
275
453
|
while (pos < input.length) {
|
|
276
454
|
if (peek() === '"') {
|
|
277
455
|
let quoteCount = 0;
|
|
@@ -281,6 +459,7 @@ function tokenize(input) {
|
|
|
281
459
|
}
|
|
282
460
|
if (quoteCount >= 3) {
|
|
283
461
|
for (let i = 0; i < quoteCount - 3; i++) value += '"';
|
|
462
|
+
closed = true;
|
|
284
463
|
break;
|
|
285
464
|
}
|
|
286
465
|
for (let i = 0; i < quoteCount; i++) value += '"';
|
|
@@ -288,6 +467,9 @@ function tokenize(input) {
|
|
|
288
467
|
}
|
|
289
468
|
value += advance();
|
|
290
469
|
}
|
|
470
|
+
if (!closed) {
|
|
471
|
+
throw new ParseError("unterminated triple-quoted string", sl, sc);
|
|
472
|
+
}
|
|
291
473
|
if (value.startsWith("\n")) value = value.slice(1);
|
|
292
474
|
push("triple_string", value, sl, sc, true);
|
|
293
475
|
continue;
|
|
@@ -326,13 +508,19 @@ function tokenize(input) {
|
|
|
326
508
|
value += "\f";
|
|
327
509
|
break;
|
|
328
510
|
case "u": {
|
|
511
|
+
if (pos + 4 > input.length) {
|
|
512
|
+
throw new ParseError("Invalid unicode escape: not enough characters", line, col);
|
|
513
|
+
}
|
|
329
514
|
const hex = input.slice(pos, pos + 4);
|
|
515
|
+
if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
|
|
516
|
+
throw new ParseError(`Invalid unicode escape: \\u${hex}`, line, col);
|
|
517
|
+
}
|
|
330
518
|
value += String.fromCharCode(parseInt(hex, 16));
|
|
331
519
|
for (let i = 0; i < 4; i++) advance();
|
|
332
520
|
break;
|
|
333
521
|
}
|
|
334
522
|
default:
|
|
335
|
-
|
|
523
|
+
throw new ParseError(`Unknown escape sequence: \\${esc}`, line, col);
|
|
336
524
|
}
|
|
337
525
|
} else {
|
|
338
526
|
value += advance();
|
|
@@ -357,10 +545,10 @@ function tokenize(input) {
|
|
|
357
545
|
return tokens;
|
|
358
546
|
}
|
|
359
547
|
function isUnquotedStart(ch) {
|
|
360
|
-
return ch !== "" && !'{}[],:=+#\n\r "
|
|
548
|
+
return ch !== "" && !'{}[],:=+#\n\r "$?!@*&^\\'.includes(ch);
|
|
361
549
|
}
|
|
362
550
|
function isUnquotedContinue(ch, nextFn) {
|
|
363
|
-
if (ch === "" || '{}[],:=\n\r #"
|
|
551
|
+
if (ch === "" || '{}[],:=\n\r #"$?!@*&^\\'.includes(ch) || ch === " ") return false;
|
|
364
552
|
if (ch === "+" && nextFn() === "=") return false;
|
|
365
553
|
if (ch === "/" && nextFn() === "/") return false;
|
|
366
554
|
return true;
|
|
@@ -462,20 +650,46 @@ function parseTokens(tokens) {
|
|
|
462
650
|
skip("newline");
|
|
463
651
|
const t2 = peek();
|
|
464
652
|
let path2;
|
|
465
|
-
|
|
653
|
+
let required = false;
|
|
654
|
+
if (t2.kind === "unquoted" && (t2.value === "required(" || t2.value === "required" || t2.value.startsWith("required("))) {
|
|
655
|
+
if (t2.value === "required") {
|
|
656
|
+
const next2 = tokens[pos + 1] ?? { kind: "eof", value: "" };
|
|
657
|
+
if (next2.kind !== "unquoted" || !next2.value.startsWith("(")) {
|
|
658
|
+
throw new ParseError("include required must be followed by (", t2.line, t2.col);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
required = true;
|
|
662
|
+
advance();
|
|
663
|
+
const innerPrefix = t2.value.startsWith("required(") ? t2.value.slice("required(".length) : "";
|
|
664
|
+
if (innerPrefix.startsWith("url") || innerPrefix.startsWith("classpath")) {
|
|
665
|
+
throw new ParseError("include url(...) and classpath(...) are not supported", t2.line, t2.col);
|
|
666
|
+
}
|
|
667
|
+
const next = peek();
|
|
668
|
+
if (next.kind === "unquoted" && (next.value === "url" || next.value.startsWith("url(") || next.value === "classpath" || next.value.startsWith("classpath("))) {
|
|
669
|
+
throw new ParseError("include url(...) and classpath(...) are not supported", next.line, next.col);
|
|
670
|
+
}
|
|
671
|
+
while (peek().kind !== "string" && peek().kind !== "eof") advance();
|
|
672
|
+
if (peek().kind === "eof") throw new ParseError("expected include path", t2.line, t2.col);
|
|
673
|
+
path2 = advance().value;
|
|
674
|
+
while (peek().kind !== "newline" && peek().kind !== "rbrace" && peek().kind !== "eof" && peek().kind !== "comma") advance();
|
|
675
|
+
} else if (t2.kind === "string") {
|
|
466
676
|
path2 = advance().value;
|
|
467
677
|
} else if (t2.kind === "unquoted" && (t2.value === "file(" || t2.value === "file")) {
|
|
468
678
|
advance();
|
|
469
679
|
while (peek().kind !== "string" && peek().kind !== "eof") advance();
|
|
470
680
|
if (peek().kind === "eof") throw new ParseError("expected include path", t2.line, t2.col);
|
|
471
681
|
path2 = advance().value;
|
|
472
|
-
while (peek().kind !== "newline" && peek().kind !== "rbrace" && peek().kind !== "eof") advance();
|
|
682
|
+
while (peek().kind !== "newline" && peek().kind !== "rbrace" && peek().kind !== "eof" && peek().kind !== "comma") advance();
|
|
683
|
+
} else if (t2.kind === "unquoted" && (t2.value === "url" || t2.value.startsWith("url("))) {
|
|
684
|
+
throw new ParseError("include url(...) is not supported", t2.line, t2.col);
|
|
685
|
+
} else if (t2.kind === "unquoted" && (t2.value === "classpath" || t2.value.startsWith("classpath("))) {
|
|
686
|
+
throw new ParseError("include classpath(...) is not supported", t2.line, t2.col);
|
|
473
687
|
} else {
|
|
474
688
|
throw new ParseError(`expected include path, got ${t2.kind}`, t2.line, t2.col);
|
|
475
689
|
}
|
|
476
690
|
return {
|
|
477
691
|
key: [],
|
|
478
|
-
value: { kind: "include", path: path2, pos: p },
|
|
692
|
+
value: { kind: "include", path: path2, required, pos: p },
|
|
479
693
|
append: false,
|
|
480
694
|
pos: p
|
|
481
695
|
};
|
|
@@ -510,7 +724,7 @@ function parseTokens(tokens) {
|
|
|
510
724
|
break;
|
|
511
725
|
}
|
|
512
726
|
if (hadSpace) {
|
|
513
|
-
parts.push({ kind: "scalar", value: " ", pos: { line: t2.line, col: t2.col } });
|
|
727
|
+
parts.push({ kind: "scalar", value: " ", pos: { line: t2.line, col: t2.col }, _separator: true });
|
|
514
728
|
}
|
|
515
729
|
parts.push(node);
|
|
516
730
|
}
|
|
@@ -546,13 +760,88 @@ function parseTokens(tokens) {
|
|
|
546
760
|
const t = peek();
|
|
547
761
|
if (t.kind === "lbrace") {
|
|
548
762
|
advance();
|
|
549
|
-
|
|
763
|
+
const first = parseObject(true);
|
|
764
|
+
const allFields = first.kind === "object" ? [...first.fields] : [];
|
|
765
|
+
while (true) {
|
|
766
|
+
skip("newline");
|
|
767
|
+
if (peek().kind === "eof") break;
|
|
768
|
+
if (peek().kind === "lbrace") {
|
|
769
|
+
advance();
|
|
770
|
+
const extra = parseObject(true);
|
|
771
|
+
if (extra.kind === "object") allFields.push(...extra.fields);
|
|
772
|
+
} else {
|
|
773
|
+
const rest = parseObject(false);
|
|
774
|
+
if (rest.kind === "object") allFields.push(...rest.fields);
|
|
775
|
+
break;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
skip("newline");
|
|
779
|
+
const remaining = peek();
|
|
780
|
+
if (remaining.kind !== "eof") {
|
|
781
|
+
throw new ParseError(`Unexpected token '${remaining.value}' after closing brace`, remaining.line, remaining.col);
|
|
782
|
+
}
|
|
783
|
+
return { kind: "object", fields: allFields, pos: first.pos };
|
|
550
784
|
}
|
|
551
785
|
return parseObject(false);
|
|
552
786
|
}
|
|
553
787
|
|
|
554
788
|
// src/internal/resolver/resolver.ts
|
|
555
789
|
var nodePath = __toESM(require("path"), 1);
|
|
790
|
+
|
|
791
|
+
// src/internal/properties/properties.ts
|
|
792
|
+
function parseProperties(input) {
|
|
793
|
+
const root = /* @__PURE__ */ Object.create(null);
|
|
794
|
+
for (const line of input.split("\n")) {
|
|
795
|
+
const trimmed = line.trim();
|
|
796
|
+
if (trimmed === "" || trimmed.startsWith("#") || trimmed.startsWith("!")) continue;
|
|
797
|
+
const sepIdx = findSeparator(trimmed);
|
|
798
|
+
if (sepIdx === -1) continue;
|
|
799
|
+
const key = trimmed.slice(0, sepIdx).trim();
|
|
800
|
+
const value = trimmed.slice(sepIdx + 1).trim();
|
|
801
|
+
if (key === "") continue;
|
|
802
|
+
setNested(root, key.split("."), value);
|
|
803
|
+
}
|
|
804
|
+
return root;
|
|
805
|
+
}
|
|
806
|
+
function findSeparator(line) {
|
|
807
|
+
for (let i = 0; i < line.length; i++) {
|
|
808
|
+
if (line[i] === "=" || line[i] === ":") return i;
|
|
809
|
+
}
|
|
810
|
+
return -1;
|
|
811
|
+
}
|
|
812
|
+
var DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
813
|
+
function setNested(obj, segments, value) {
|
|
814
|
+
let current = obj;
|
|
815
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
816
|
+
const seg = segments[i];
|
|
817
|
+
if (seg === void 0 || DANGEROUS_KEYS.has(seg)) return;
|
|
818
|
+
if (!(seg in current) || typeof current[seg] !== "object" || current[seg] === null) {
|
|
819
|
+
current[seg] = /* @__PURE__ */ Object.create(null);
|
|
820
|
+
}
|
|
821
|
+
current = current[seg];
|
|
822
|
+
}
|
|
823
|
+
const last = segments[segments.length - 1];
|
|
824
|
+
if (last === void 0 || DANGEROUS_KEYS.has(last)) return;
|
|
825
|
+
current[last] = value;
|
|
826
|
+
}
|
|
827
|
+
function propertiesToHoconValue(input) {
|
|
828
|
+
const parsed = parseProperties(input);
|
|
829
|
+
return recordToHoconValue(parsed);
|
|
830
|
+
}
|
|
831
|
+
function recordToHoconValue(obj) {
|
|
832
|
+
const fields = /* @__PURE__ */ new Map();
|
|
833
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
834
|
+
if (typeof val === "string") {
|
|
835
|
+
fields.set(key, { kind: "scalar", value: val });
|
|
836
|
+
} else if (val !== null && typeof val === "object") {
|
|
837
|
+
fields.set(key, recordToHoconValue(val));
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
return { kind: "object", fields };
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// src/internal/resolver/resolver.ts
|
|
844
|
+
var separatorValues = /* @__PURE__ */ new WeakSet();
|
|
556
845
|
function isSubst(v) {
|
|
557
846
|
return v._kind === "subst-placeholder";
|
|
558
847
|
}
|
|
@@ -574,6 +863,12 @@ function resolve2(ast, opts) {
|
|
|
574
863
|
const resolvedCache = /* @__PURE__ */ new Map();
|
|
575
864
|
return resolveResObj(root, root, resolving, resolvedCache, opts);
|
|
576
865
|
}
|
|
866
|
+
async function resolveAsync(ast, opts) {
|
|
867
|
+
const root = await buildResObjAsync(ast, opts);
|
|
868
|
+
const resolving = /* @__PURE__ */ new Set();
|
|
869
|
+
const resolvedCache = /* @__PURE__ */ new Map();
|
|
870
|
+
return resolveResObj(root, root, resolving, resolvedCache, opts);
|
|
871
|
+
}
|
|
577
872
|
function buildResObj(ast, opts) {
|
|
578
873
|
if (ast.kind !== "object") {
|
|
579
874
|
throw new ResolveError("root AST must be an object", "", ast.pos.line, ast.pos.col);
|
|
@@ -586,7 +881,7 @@ function buildResObj(ast, opts) {
|
|
|
586
881
|
}
|
|
587
882
|
function applyField(obj, field, opts) {
|
|
588
883
|
if (field.key.length === 0 && field.value.kind === "include") {
|
|
589
|
-
const included = loadInclude(field.value.path, opts);
|
|
884
|
+
const included = loadInclude(field.value.path, field.value.required, opts);
|
|
590
885
|
deepMergeResObjInto(obj, included);
|
|
591
886
|
return;
|
|
592
887
|
}
|
|
@@ -624,8 +919,11 @@ function applyField(obj, field, opts) {
|
|
|
624
919
|
}
|
|
625
920
|
function astToResolverValue(ast, opts) {
|
|
626
921
|
switch (ast.kind) {
|
|
627
|
-
case "scalar":
|
|
628
|
-
|
|
922
|
+
case "scalar": {
|
|
923
|
+
const sv = { kind: "scalar", value: ast.value };
|
|
924
|
+
if (ast._separator) separatorValues.add(sv);
|
|
925
|
+
return sv;
|
|
926
|
+
}
|
|
629
927
|
case "array":
|
|
630
928
|
return { kind: "array", items: ast.items.map((i) => astToResolverValue(i, opts)) };
|
|
631
929
|
case "object": {
|
|
@@ -640,6 +938,18 @@ function astToResolverValue(ast, opts) {
|
|
|
640
938
|
return { kind: "scalar", value: null };
|
|
641
939
|
}
|
|
642
940
|
}
|
|
941
|
+
function hoconValueToResObj(hv) {
|
|
942
|
+
const obj = makeResObj();
|
|
943
|
+
if (hv.kind !== "object") return obj;
|
|
944
|
+
for (const [key, val] of hv.fields) {
|
|
945
|
+
if (val.kind === "object") {
|
|
946
|
+
obj.fields.set(key, hoconValueToResObj(val));
|
|
947
|
+
} else {
|
|
948
|
+
obj.fields.set(key, val);
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
return obj;
|
|
952
|
+
}
|
|
643
953
|
function deepMergeResObjInto(dst, src) {
|
|
644
954
|
for (const [k, srcVal] of src.fields) {
|
|
645
955
|
const dstVal = dst.fields.get(k);
|
|
@@ -727,12 +1037,18 @@ function resolveConcat(nodes, scope, root, resolving, resolvedCache, opts) {
|
|
|
727
1037
|
const resolved = nodes.map((n) => resolveVal(n, scope, root, resolving, resolvedCache, opts)).filter((v) => v !== void 0);
|
|
728
1038
|
if (resolved.length === 0) return { kind: "scalar", value: null };
|
|
729
1039
|
if (resolved.length === 1) return resolved[0];
|
|
730
|
-
|
|
1040
|
+
const nonSep = resolved.filter((v) => !separatorValues.has(v));
|
|
1041
|
+
if (nonSep.length > 0 && nonSep.every((v) => v.kind === "object")) {
|
|
731
1042
|
const merged = /* @__PURE__ */ new Map();
|
|
732
|
-
for (const v of
|
|
1043
|
+
for (const v of nonSep) {
|
|
733
1044
|
if (v.kind === "object") {
|
|
734
1045
|
for (const [k, val] of v.fields) {
|
|
735
|
-
merged.
|
|
1046
|
+
const existing = merged.get(k);
|
|
1047
|
+
if (existing?.kind === "object" && val.kind === "object") {
|
|
1048
|
+
merged.set(k, deepMergeHoconValues(existing, val));
|
|
1049
|
+
} else {
|
|
1050
|
+
merged.set(k, val);
|
|
1051
|
+
}
|
|
736
1052
|
}
|
|
737
1053
|
}
|
|
738
1054
|
}
|
|
@@ -749,6 +1065,18 @@ function resolveConcat(nodes, scope, root, resolving, resolvedCache, opts) {
|
|
|
749
1065
|
const str = resolved.map((v) => v.kind === "scalar" ? String(v.value) : JSON.stringify(v)).join("");
|
|
750
1066
|
return { kind: "scalar", value: str };
|
|
751
1067
|
}
|
|
1068
|
+
function deepMergeHoconValues(base, overlay) {
|
|
1069
|
+
const merged = new Map(base.fields);
|
|
1070
|
+
for (const [k, v] of overlay.fields) {
|
|
1071
|
+
const existing = merged.get(k);
|
|
1072
|
+
if (existing?.kind === "object" && v.kind === "object") {
|
|
1073
|
+
merged.set(k, deepMergeHoconValues(existing, v));
|
|
1074
|
+
} else {
|
|
1075
|
+
merged.set(k, v);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
return { kind: "object", fields: merged };
|
|
1079
|
+
}
|
|
752
1080
|
function resolveAppend(a, scope, root, resolving, resolvedCache, opts) {
|
|
753
1081
|
const existing = resolveVal(a.existing, scope, root, resolving, resolvedCache, opts) ?? { kind: "array", items: [] };
|
|
754
1082
|
const elem = resolveVal(a.elem, scope, root, resolving, resolvedCache, opts);
|
|
@@ -806,35 +1134,203 @@ function parseSubstPath(raw) {
|
|
|
806
1134
|
}
|
|
807
1135
|
return segments;
|
|
808
1136
|
}
|
|
809
|
-
function
|
|
810
|
-
|
|
1137
|
+
function isFileNotFoundError(e) {
|
|
1138
|
+
if (!(e instanceof Error)) return false;
|
|
1139
|
+
const code = e.code;
|
|
1140
|
+
if (code === "ENOENT" || code === "MODULE_NOT_FOUND") return true;
|
|
1141
|
+
const msg = e.message.toLowerCase();
|
|
1142
|
+
return msg.includes("not found") || msg.includes("no such file") || msg.includes("enoent");
|
|
1143
|
+
}
|
|
1144
|
+
function loadSingleInclude(candidate, opts) {
|
|
1145
|
+
const { readFileSync: readFileSync2, includeStack = [], env } = opts;
|
|
1146
|
+
if (includeStack.includes(candidate)) {
|
|
1147
|
+
throw new ResolveError(`circular include: ${candidate}`, candidate, 0, 0);
|
|
1148
|
+
}
|
|
1149
|
+
let content;
|
|
1150
|
+
try {
|
|
1151
|
+
content = readFileSync2(candidate);
|
|
1152
|
+
} catch (e) {
|
|
1153
|
+
if (isFileNotFoundError(e)) return void 0;
|
|
1154
|
+
throw e;
|
|
1155
|
+
}
|
|
1156
|
+
if (candidate.endsWith(".properties")) {
|
|
1157
|
+
return hoconValueToResObj(propertiesToHoconValue(content));
|
|
1158
|
+
}
|
|
1159
|
+
const ast = parseTokens(tokenize(content));
|
|
1160
|
+
return buildResObj(ast, {
|
|
1161
|
+
env,
|
|
1162
|
+
baseDir: nodePath.dirname(candidate),
|
|
1163
|
+
readFileSync: readFileSync2,
|
|
1164
|
+
includeStack: [...includeStack, candidate]
|
|
1165
|
+
});
|
|
1166
|
+
}
|
|
1167
|
+
function loadInclude(includePath, required, opts) {
|
|
1168
|
+
const { baseDir, includeStack = [] } = opts;
|
|
811
1169
|
const absPath = baseDir ? nodePath.resolve(baseDir, includePath) : nodePath.resolve(includePath);
|
|
812
1170
|
if (includeStack.includes(absPath)) {
|
|
813
1171
|
throw new ResolveError(`circular include: ${absPath}`, absPath, 0, 0);
|
|
814
1172
|
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
candidates.push(absPath + ".conf", absPath + ".json");
|
|
1173
|
+
if (includeStack.length >= 50) {
|
|
1174
|
+
throw new ResolveError(`include depth limit exceeded (max 50)`, includePath, 0, 0);
|
|
818
1175
|
}
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
1176
|
+
const hasExplicitExt = absPath.endsWith(".conf") || absPath.endsWith(".json") || absPath.endsWith(".properties");
|
|
1177
|
+
if (hasExplicitExt) {
|
|
1178
|
+
const result = loadSingleInclude(absPath, opts);
|
|
1179
|
+
if (result !== void 0) return result;
|
|
1180
|
+
if (required) {
|
|
1181
|
+
throw new ResolveError(`required include file not found: ${includePath}`, includePath, 0, 0);
|
|
825
1182
|
}
|
|
826
|
-
|
|
827
|
-
|
|
1183
|
+
return makeResObj();
|
|
1184
|
+
}
|
|
1185
|
+
const merged = makeResObj();
|
|
1186
|
+
let foundAny = false;
|
|
1187
|
+
const probeExts = [".properties", ".json", ".conf"];
|
|
1188
|
+
for (const ext of probeExts) {
|
|
1189
|
+
const obj = loadSingleInclude(`${absPath}${ext}`, opts);
|
|
1190
|
+
if (obj !== void 0) {
|
|
1191
|
+
deepMergeResObjInto(merged, obj);
|
|
1192
|
+
foundAny = true;
|
|
828
1193
|
}
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
1194
|
+
}
|
|
1195
|
+
if (!foundAny && required) {
|
|
1196
|
+
throw new ResolveError(`required include file not found: ${includePath}`, includePath, 0, 0);
|
|
1197
|
+
}
|
|
1198
|
+
return merged;
|
|
1199
|
+
}
|
|
1200
|
+
async function buildResObjAsync(ast, opts) {
|
|
1201
|
+
if (ast.kind !== "object") {
|
|
1202
|
+
throw new ResolveError("root AST must be an object", "", ast.pos.line, ast.pos.col);
|
|
1203
|
+
}
|
|
1204
|
+
const obj = makeResObj();
|
|
1205
|
+
for (const field of ast.fields) {
|
|
1206
|
+
await applyFieldAsync(obj, field, opts);
|
|
1207
|
+
}
|
|
1208
|
+
return obj;
|
|
1209
|
+
}
|
|
1210
|
+
async function applyFieldAsync(obj, field, opts) {
|
|
1211
|
+
if (field.key.length === 0 && field.value.kind === "include") {
|
|
1212
|
+
const included = await loadIncludeAsync(field.value.path, field.value.required, opts);
|
|
1213
|
+
deepMergeResObjInto(obj, included);
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
const [head, ...tail] = field.key;
|
|
1217
|
+
if (!head) return;
|
|
1218
|
+
if (tail.length > 0) {
|
|
1219
|
+
const syntheticAst = {
|
|
1220
|
+
kind: "object",
|
|
1221
|
+
fields: [{ key: tail, value: field.value, append: field.append, pos: field.pos }],
|
|
1222
|
+
pos: field.pos
|
|
1223
|
+
};
|
|
1224
|
+
await applyFieldAsync(obj, { key: [head], value: syntheticAst, append: false, pos: field.pos }, opts);
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1227
|
+
if (field.append) {
|
|
1228
|
+
const existing2 = obj.fields.get(head) ?? { kind: "array", items: [] };
|
|
1229
|
+
obj.priorValues.set(head, existing2);
|
|
1230
|
+
obj.fields.set(head, {
|
|
1231
|
+
_kind: "append-placeholder",
|
|
1232
|
+
existing: existing2,
|
|
1233
|
+
elem: await astToResolverValueAsync(field.value, opts)
|
|
835
1234
|
});
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1237
|
+
const existing = obj.fields.get(head);
|
|
1238
|
+
const newVal = await astToResolverValueAsync(field.value, opts);
|
|
1239
|
+
if (existing !== void 0) {
|
|
1240
|
+
obj.priorValues.set(head, existing);
|
|
1241
|
+
}
|
|
1242
|
+
if (existing !== void 0 && isResObj(existing) && isResObj(newVal)) {
|
|
1243
|
+
deepMergeResObjInto(existing, newVal);
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
obj.fields.set(head, newVal);
|
|
1247
|
+
}
|
|
1248
|
+
async function astToResolverValueAsync(ast, opts) {
|
|
1249
|
+
switch (ast.kind) {
|
|
1250
|
+
case "scalar": {
|
|
1251
|
+
const sv = { kind: "scalar", value: ast.value };
|
|
1252
|
+
if (ast._separator) separatorValues.add(sv);
|
|
1253
|
+
return sv;
|
|
1254
|
+
}
|
|
1255
|
+
case "array": {
|
|
1256
|
+
const items = [];
|
|
1257
|
+
for (const i of ast.items) {
|
|
1258
|
+
items.push(await astToResolverValueAsync(i, opts));
|
|
1259
|
+
}
|
|
1260
|
+
return { kind: "array", items };
|
|
1261
|
+
}
|
|
1262
|
+
case "object":
|
|
1263
|
+
return await buildResObjAsync(ast, opts);
|
|
1264
|
+
case "subst":
|
|
1265
|
+
return { _kind: "subst-placeholder", path: ast.path, optional: ast.optional, line: ast.pos.line, col: ast.pos.col };
|
|
1266
|
+
case "concat": {
|
|
1267
|
+
const nodes = [];
|
|
1268
|
+
for (const n of ast.nodes) {
|
|
1269
|
+
nodes.push(await astToResolverValueAsync(n, opts));
|
|
1270
|
+
}
|
|
1271
|
+
return { _kind: "concat-placeholder", nodes };
|
|
1272
|
+
}
|
|
1273
|
+
case "include":
|
|
1274
|
+
return { kind: "scalar", value: null };
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
async function loadSingleIncludeAsync(candidate, opts) {
|
|
1278
|
+
const { readFile, readFileSync: readFileSync2, includeStack = [], env } = opts;
|
|
1279
|
+
const read = readFile ? async (p) => readFile(p) : async (p) => readFileSync2(p);
|
|
1280
|
+
if (includeStack.includes(candidate)) {
|
|
1281
|
+
throw new ResolveError(`circular include: ${candidate}`, candidate, 0, 0);
|
|
1282
|
+
}
|
|
1283
|
+
let content;
|
|
1284
|
+
try {
|
|
1285
|
+
content = await read(candidate);
|
|
1286
|
+
} catch (e) {
|
|
1287
|
+
if (isFileNotFoundError(e)) return void 0;
|
|
1288
|
+
throw e;
|
|
1289
|
+
}
|
|
1290
|
+
if (candidate.endsWith(".properties")) {
|
|
1291
|
+
return hoconValueToResObj(propertiesToHoconValue(content));
|
|
836
1292
|
}
|
|
837
|
-
|
|
1293
|
+
const ast = parseTokens(tokenize(content));
|
|
1294
|
+
return buildResObjAsync(ast, {
|
|
1295
|
+
env,
|
|
1296
|
+
baseDir: nodePath.dirname(candidate),
|
|
1297
|
+
readFileSync: readFileSync2,
|
|
1298
|
+
readFile,
|
|
1299
|
+
includeStack: [...includeStack, candidate]
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
async function loadIncludeAsync(includePath, required, opts) {
|
|
1303
|
+
const { baseDir, includeStack = [] } = opts;
|
|
1304
|
+
const absPath = baseDir ? nodePath.resolve(baseDir, includePath) : nodePath.resolve(includePath);
|
|
1305
|
+
if (includeStack.includes(absPath)) {
|
|
1306
|
+
throw new ResolveError(`circular include: ${absPath}`, absPath, 0, 0);
|
|
1307
|
+
}
|
|
1308
|
+
if (includeStack.length >= 50) {
|
|
1309
|
+
throw new ResolveError(`include depth limit exceeded (max 50)`, includePath, 0, 0);
|
|
1310
|
+
}
|
|
1311
|
+
const hasExplicitExt = absPath.endsWith(".conf") || absPath.endsWith(".json") || absPath.endsWith(".properties");
|
|
1312
|
+
if (hasExplicitExt) {
|
|
1313
|
+
const result = await loadSingleIncludeAsync(absPath, opts);
|
|
1314
|
+
if (result !== void 0) return result;
|
|
1315
|
+
if (required) {
|
|
1316
|
+
throw new ResolveError(`required include file not found: ${includePath}`, includePath, 0, 0);
|
|
1317
|
+
}
|
|
1318
|
+
return makeResObj();
|
|
1319
|
+
}
|
|
1320
|
+
const merged = makeResObj();
|
|
1321
|
+
let foundAny = false;
|
|
1322
|
+
const probeExts = [".properties", ".json", ".conf"];
|
|
1323
|
+
for (const ext of probeExts) {
|
|
1324
|
+
const obj = await loadSingleIncludeAsync(`${absPath}${ext}`, opts);
|
|
1325
|
+
if (obj !== void 0) {
|
|
1326
|
+
deepMergeResObjInto(merged, obj);
|
|
1327
|
+
foundAny = true;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
if (!foundAny && required) {
|
|
1331
|
+
throw new ResolveError(`required include file not found: ${includePath}`, includePath, 0, 0);
|
|
1332
|
+
}
|
|
1333
|
+
return merged;
|
|
838
1334
|
}
|
|
839
1335
|
|
|
840
1336
|
// src/parse.ts
|
|
@@ -851,20 +1347,28 @@ function defaultReadFileSync(filePath) {
|
|
|
851
1347
|
async function defaultReadFile(filePath) {
|
|
852
1348
|
return fs.promises.readFile(filePath, "utf-8");
|
|
853
1349
|
}
|
|
854
|
-
function
|
|
1350
|
+
function buildResolveContext(input, opts) {
|
|
855
1351
|
const tokens = tokenize(input);
|
|
856
1352
|
const ast = parseTokens(tokens);
|
|
857
1353
|
const resolveOpts = {
|
|
858
1354
|
env: getEnv(opts),
|
|
859
1355
|
baseDir: opts.baseDir,
|
|
860
|
-
readFileSync: opts.readFileSync ?? defaultReadFileSync
|
|
1356
|
+
readFileSync: opts.readFileSync ?? defaultReadFileSync,
|
|
1357
|
+
readFile: opts.readFile
|
|
861
1358
|
};
|
|
1359
|
+
return { ast, resolveOpts };
|
|
1360
|
+
}
|
|
1361
|
+
function parse(input, opts = {}) {
|
|
1362
|
+
const { ast, resolveOpts } = buildResolveContext(input, opts);
|
|
862
1363
|
const value = resolve2(ast, resolveOpts);
|
|
863
1364
|
if (value.kind !== "object") throw new Error("resolved value is not an object");
|
|
864
1365
|
return new Config(value);
|
|
865
1366
|
}
|
|
866
1367
|
async function parseAsync(input, opts = {}) {
|
|
867
|
-
|
|
1368
|
+
const { ast, resolveOpts } = buildResolveContext(input, opts);
|
|
1369
|
+
const value = await resolveAsync(ast, resolveOpts);
|
|
1370
|
+
if (value.kind !== "object") throw new Error("resolved value is not an object");
|
|
1371
|
+
return new Config(value);
|
|
868
1372
|
}
|
|
869
1373
|
function parseFile(filePath, opts = {}) {
|
|
870
1374
|
if (typeof process === "undefined" && !opts.readFileSync) {
|
|
@@ -886,9 +1390,10 @@ async function parseFileAsync(filePath, opts = {}) {
|
|
|
886
1390
|
const resolvedPath = path.resolve(filePath);
|
|
887
1391
|
const readFile = opts.readFile ?? defaultReadFile;
|
|
888
1392
|
const input = await readFile(resolvedPath);
|
|
889
|
-
return
|
|
1393
|
+
return parseAsync(input, {
|
|
890
1394
|
...opts,
|
|
891
1395
|
baseDir: opts.baseDir ?? path.dirname(resolvedPath),
|
|
1396
|
+
readFile,
|
|
892
1397
|
readFileSync: opts.readFileSync ?? defaultReadFileSync
|
|
893
1398
|
});
|
|
894
1399
|
}
|