@o3co/ts.hocon 0.1.0 → 0.1.2
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 +49 -0
- package/README.md +49 -0
- package/dist/config-D4EyvNd8.d.cts +31 -0
- package/dist/config-D4EyvNd8.d.ts +31 -0
- package/dist/index.cjs +891 -0
- package/dist/index.d.cts +39 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +847 -0
- package/dist/zod.cjs +39 -0
- package/dist/zod.d.cts +7 -0
- package/dist/zod.d.ts +7 -0
- package/dist/zod.js +13 -0
- package/package.json +13 -10
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -29
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -23
- package/.github/pull_request_template.md +0 -17
- package/.github/workflows/lint.yml +0 -33
- package/.github/workflows/release.yml +0 -41
- package/.github/workflows/test.yml +0 -53
- package/CHANGELOG.md +0 -22
- package/CONTRIBUTING.md +0 -62
- package/SECURITY.md +0 -20
- package/docs/superpowers/plans/2026-03-20-ts-hocon.md +0 -2412
- package/docs/superpowers/specs/2026-03-20-ts-hocon-design.md +0 -368
- package/src/config.ts +0 -108
- package/src/errors.ts +0 -34
- package/src/index.ts +0 -5
- package/src/internal/lexer/lexer.ts +0 -168
- package/src/internal/lexer/token.ts +0 -20
- package/src/internal/parser/ast.ts +0 -17
- package/src/internal/parser/parser.ts +0 -229
- package/src/internal/resolver/resolver.ts +0 -428
- package/src/parse.ts +0 -79
- package/src/value.ts +0 -5
- package/src/zod.ts +0 -12
- package/tests/config.test.ts +0 -100
- package/tests/errors.test.ts +0 -43
- package/tests/lexer.test.ts +0 -141
- package/tests/lightbend/lightbend.test.ts +0 -39
- package/tests/lightbend/testdata/a_1.conf +0 -1
- package/tests/lightbend/testdata/b_2.conf +0 -1
- package/tests/lightbend/testdata/beanconfig/beanconfig01.conf +0 -131
- package/tests/lightbend/testdata/bom.conf +0 -2
- package/tests/lightbend/testdata/cycle.conf +0 -1
- package/tests/lightbend/testdata/env-variables.conf +0 -16
- package/tests/lightbend/testdata/equiv01/comments.conf +0 -45
- package/tests/lightbend/testdata/equiv01/equals.conf +0 -41
- package/tests/lightbend/testdata/equiv01/no-commas.conf +0 -55
- package/tests/lightbend/testdata/equiv01/no-root-braces.conf +0 -40
- package/tests/lightbend/testdata/equiv01/no-whitespace.json +0 -1
- package/tests/lightbend/testdata/equiv01/omit-colons.conf +0 -44
- package/tests/lightbend/testdata/equiv01/original.json +0 -41
- package/tests/lightbend/testdata/equiv01/path-keys.conf +0 -29
- package/tests/lightbend/testdata/equiv01/properties-style.conf +0 -37
- package/tests/lightbend/testdata/equiv01/substitutions.conf +0 -41
- package/tests/lightbend/testdata/equiv01/unquoted.conf +0 -41
- package/tests/lightbend/testdata/equiv02/original.json +0 -17
- package/tests/lightbend/testdata/equiv02/path-keys-weird-whitespace.conf +0 -32
- package/tests/lightbend/testdata/equiv02/path-keys.conf +0 -9
- package/tests/lightbend/testdata/equiv03/includes.conf +0 -7
- package/tests/lightbend/testdata/equiv03/letters/a.conf +0 -6
- package/tests/lightbend/testdata/equiv03/letters/b.json +0 -3
- package/tests/lightbend/testdata/equiv03/letters/c.conf +0 -2
- package/tests/lightbend/testdata/equiv03/letters/c.properties +0 -1
- package/tests/lightbend/testdata/equiv03/letters/numbers/1.conf +0 -1
- package/tests/lightbend/testdata/equiv03/letters/numbers/2.properties +0 -1
- package/tests/lightbend/testdata/equiv03/original.json +0 -15
- package/tests/lightbend/testdata/equiv03/root/foo.conf +0 -1
- package/tests/lightbend/testdata/equiv04/missing-substitutions.conf +0 -4
- package/tests/lightbend/testdata/equiv04/original.json +0 -3
- package/tests/lightbend/testdata/equiv05/original.json +0 -16
- package/tests/lightbend/testdata/equiv05/triple-quotes.conf +0 -20
- package/tests/lightbend/testdata/file-include.conf +0 -5
- package/tests/lightbend/testdata/include-from-list.conf +0 -5
- package/tests/lightbend/testdata/onclasspath.conf +0 -2
- package/tests/lightbend/testdata/subdir/bar-file.conf +0 -1
- package/tests/lightbend/testdata/subdir/bar.conf +0 -1
- package/tests/lightbend/testdata/subdir/baz.conf +0 -1
- package/tests/lightbend/testdata/subdir/foo.conf +0 -5
- package/tests/lightbend/testdata/test01.conf +0 -96
- package/tests/lightbend/testdata/test01.json +0 -4
- package/tests/lightbend/testdata/test01.properties +0 -5
- package/tests/lightbend/testdata/test02.conf +0 -13
- package/tests/lightbend/testdata/test03-included.conf +0 -9
- package/tests/lightbend/testdata/test03.conf +0 -36
- package/tests/lightbend/testdata/test04.conf +0 -305
- package/tests/lightbend/testdata/test05.conf +0 -154
- package/tests/lightbend/testdata/test06.conf +0 -14
- package/tests/lightbend/testdata/test07.conf +0 -4
- package/tests/lightbend/testdata/test08.conf +0 -6
- package/tests/lightbend/testdata/test09.conf +0 -17
- package/tests/lightbend/testdata/test10.conf +0 -11
- package/tests/lightbend/testdata/test11.conf +0 -7
- package/tests/lightbend/testdata/test12.conf +0 -43
- package/tests/lightbend/testdata/test13-application-override-substitutions.conf +0 -1
- package/tests/lightbend/testdata/test13-reference-bad-substitutions.conf +0 -1
- package/tests/lightbend/testdata/test13-reference-with-substitutions.conf +0 -2
- package/tests/lightbend/testdata/validate-invalid.conf +0 -30
- package/tests/lightbend/testdata/validate-reference.conf +0 -32
- package/tests/parse.test.ts +0 -44
- package/tests/parser.test.ts +0 -154
- package/tests/resolver.test.ts +0 -159
- package/tests/zod.test.ts +0 -57
- package/tsconfig.json +0 -16
- package/tsup.config.ts +0 -12
- package/vitest.config.ts +0 -7
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,891 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var src_exports = {};
|
|
32
|
+
__export(src_exports, {
|
|
33
|
+
Config: () => Config,
|
|
34
|
+
ConfigError: () => ConfigError,
|
|
35
|
+
ParseError: () => ParseError,
|
|
36
|
+
ResolveError: () => ResolveError,
|
|
37
|
+
parse: () => parse,
|
|
38
|
+
parseAsync: () => parseAsync,
|
|
39
|
+
parseFile: () => parseFile,
|
|
40
|
+
parseFileAsync: () => parseFileAsync
|
|
41
|
+
});
|
|
42
|
+
module.exports = __toCommonJS(src_exports);
|
|
43
|
+
|
|
44
|
+
// src/errors.ts
|
|
45
|
+
var ParseError = class extends Error {
|
|
46
|
+
constructor(message, line, col, file) {
|
|
47
|
+
super(message);
|
|
48
|
+
this.line = line;
|
|
49
|
+
this.col = col;
|
|
50
|
+
this.file = file;
|
|
51
|
+
this.name = "ParseError";
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var ResolveError = class extends Error {
|
|
55
|
+
constructor(message, path2, line, col, file) {
|
|
56
|
+
super(message);
|
|
57
|
+
this.path = path2;
|
|
58
|
+
this.line = line;
|
|
59
|
+
this.col = col;
|
|
60
|
+
this.file = file;
|
|
61
|
+
this.name = "ResolveError";
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
var ConfigError = class extends Error {
|
|
65
|
+
constructor(message, path2) {
|
|
66
|
+
super(message);
|
|
67
|
+
this.path = path2;
|
|
68
|
+
this.name = "ConfigError";
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// src/config.ts
|
|
73
|
+
var Config = class _Config {
|
|
74
|
+
constructor(root) {
|
|
75
|
+
this.root = root;
|
|
76
|
+
}
|
|
77
|
+
get(path2) {
|
|
78
|
+
const v = this.lookupNode(path2);
|
|
79
|
+
if (v === void 0) return void 0;
|
|
80
|
+
return hoconToJs(v);
|
|
81
|
+
}
|
|
82
|
+
getString(path2) {
|
|
83
|
+
const v = this.requireScalar(path2);
|
|
84
|
+
if (typeof v !== "string") throw new ConfigError(`expected string at ${path2}, got ${typeof v}`, path2);
|
|
85
|
+
return v;
|
|
86
|
+
}
|
|
87
|
+
getNumber(path2) {
|
|
88
|
+
const v = this.requireScalar(path2);
|
|
89
|
+
if (typeof v === "number") return v;
|
|
90
|
+
if (typeof v === "string") {
|
|
91
|
+
const n = Number(v);
|
|
92
|
+
if (!Number.isNaN(n)) return n;
|
|
93
|
+
}
|
|
94
|
+
throw new ConfigError(`expected number at ${path2}, got ${typeof v}`, path2);
|
|
95
|
+
}
|
|
96
|
+
getBoolean(path2) {
|
|
97
|
+
const v = this.requireScalar(path2);
|
|
98
|
+
if (typeof v === "boolean") return v;
|
|
99
|
+
if (typeof v === "string") {
|
|
100
|
+
if (v === "true") return true;
|
|
101
|
+
if (v === "false") return false;
|
|
102
|
+
}
|
|
103
|
+
throw new ConfigError(`expected boolean at ${path2}, got ${typeof v}`, path2);
|
|
104
|
+
}
|
|
105
|
+
getConfig(path2) {
|
|
106
|
+
const v = this.lookupNode(path2);
|
|
107
|
+
if (v === void 0) throw new ConfigError(`path not found: ${path2}`, path2);
|
|
108
|
+
if (v.kind !== "object") throw new ConfigError(`expected object at ${path2}`, path2);
|
|
109
|
+
return new _Config(v);
|
|
110
|
+
}
|
|
111
|
+
getList(path2) {
|
|
112
|
+
const v = this.lookupNode(path2);
|
|
113
|
+
if (v === void 0) throw new ConfigError(`path not found: ${path2}`, path2);
|
|
114
|
+
if (v.kind !== "array") throw new ConfigError(`expected array at ${path2}`, path2);
|
|
115
|
+
return v.items.map(hoconToJs);
|
|
116
|
+
}
|
|
117
|
+
has(path2) {
|
|
118
|
+
return this.lookupNode(path2) !== void 0;
|
|
119
|
+
}
|
|
120
|
+
keys() {
|
|
121
|
+
return [...this.root.fields.keys()];
|
|
122
|
+
}
|
|
123
|
+
withFallback(fallback) {
|
|
124
|
+
const merged = deepMergeHocon(this.root, fallback.root);
|
|
125
|
+
return new _Config(merged);
|
|
126
|
+
}
|
|
127
|
+
toObject() {
|
|
128
|
+
return hoconToJs(this.root);
|
|
129
|
+
}
|
|
130
|
+
lookupNode(path2) {
|
|
131
|
+
const segments = path2.split(".");
|
|
132
|
+
let current = this.root;
|
|
133
|
+
for (const seg of segments) {
|
|
134
|
+
if (current.kind !== "object") return void 0;
|
|
135
|
+
const next = current.fields.get(seg);
|
|
136
|
+
if (next === void 0) return void 0;
|
|
137
|
+
current = next;
|
|
138
|
+
}
|
|
139
|
+
return current;
|
|
140
|
+
}
|
|
141
|
+
requireScalar(path2) {
|
|
142
|
+
const v = this.lookupNode(path2);
|
|
143
|
+
if (v === void 0) throw new ConfigError(`path not found: ${path2}`, path2);
|
|
144
|
+
if (v.kind !== "scalar") throw new ConfigError(`expected scalar at ${path2}, got ${v.kind}`, path2);
|
|
145
|
+
return v.value;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
function hoconToJs(v) {
|
|
149
|
+
switch (v.kind) {
|
|
150
|
+
case "scalar":
|
|
151
|
+
return v.value;
|
|
152
|
+
case "array":
|
|
153
|
+
return v.items.map(hoconToJs);
|
|
154
|
+
case "object": {
|
|
155
|
+
const obj = {};
|
|
156
|
+
for (const [k, val] of v.fields) obj[k] = hoconToJs(val);
|
|
157
|
+
return obj;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function deepMergeHocon(receiver, fallback) {
|
|
162
|
+
const merged = new Map(fallback.fields);
|
|
163
|
+
for (const [k, v] of receiver.fields) {
|
|
164
|
+
const fb = merged.get(k);
|
|
165
|
+
if (fb?.kind === "object" && v.kind === "object") {
|
|
166
|
+
merged.set(k, deepMergeHocon(v, fb));
|
|
167
|
+
} else {
|
|
168
|
+
merged.set(k, v);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return { kind: "object", fields: merged };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/internal/lexer/lexer.ts
|
|
175
|
+
function tokenize(input) {
|
|
176
|
+
if (input.charCodeAt(0) === 65279) input = input.slice(1);
|
|
177
|
+
const tokens = [];
|
|
178
|
+
let pos = 0;
|
|
179
|
+
let line = 1;
|
|
180
|
+
let col = 1;
|
|
181
|
+
let hadSpace = false;
|
|
182
|
+
function peek(offset = 0) {
|
|
183
|
+
return input[pos + offset] ?? "";
|
|
184
|
+
}
|
|
185
|
+
function advance() {
|
|
186
|
+
const ch = input[pos++] ?? "";
|
|
187
|
+
if (ch === "\n") {
|
|
188
|
+
line++;
|
|
189
|
+
col = 1;
|
|
190
|
+
} else {
|
|
191
|
+
col++;
|
|
192
|
+
}
|
|
193
|
+
return ch;
|
|
194
|
+
}
|
|
195
|
+
function push(kind, value, l, c, isQuoted = false) {
|
|
196
|
+
tokens.push({ kind, value, line: l, col: c, isQuoted, precedingSpace: hadSpace });
|
|
197
|
+
hadSpace = false;
|
|
198
|
+
}
|
|
199
|
+
while (pos < input.length) {
|
|
200
|
+
const sl = line, sc = col;
|
|
201
|
+
const ch = peek();
|
|
202
|
+
if (ch === " " || ch === " " || ch === "\r") {
|
|
203
|
+
advance();
|
|
204
|
+
hadSpace = true;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (ch === "\n") {
|
|
208
|
+
advance();
|
|
209
|
+
if (tokens[tokens.length - 1]?.kind !== "newline") {
|
|
210
|
+
push("newline", "\n", sl, sc);
|
|
211
|
+
}
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (ch === "/" && peek(1) === "/") {
|
|
215
|
+
while (pos < input.length && peek() !== "\n") advance();
|
|
216
|
+
hadSpace = true;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
if (ch === "#") {
|
|
220
|
+
while (pos < input.length && peek() !== "\n") advance();
|
|
221
|
+
hadSpace = true;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
const single = { "{": "lbrace", "}": "rbrace", "[": "lbracket", "]": "rbracket", ",": "comma", ":": "colon" };
|
|
225
|
+
if (ch in single) {
|
|
226
|
+
advance();
|
|
227
|
+
push(single[ch], ch, sl, sc);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (ch === "=") {
|
|
231
|
+
advance();
|
|
232
|
+
push("equals", "=", sl, sc);
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (ch === "+" && peek(1) === "=") {
|
|
236
|
+
advance();
|
|
237
|
+
advance();
|
|
238
|
+
push("plus_equals", "+=", sl, sc);
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (ch === "$" && peek(1) === "{") {
|
|
242
|
+
advance();
|
|
243
|
+
advance();
|
|
244
|
+
const optional = peek() === "?";
|
|
245
|
+
if (optional) advance();
|
|
246
|
+
let path2 = "";
|
|
247
|
+
while (pos < input.length && peek() !== "}") {
|
|
248
|
+
if (peek() === "\n") throw new ParseError("unterminated substitution", sl, sc);
|
|
249
|
+
path2 += advance();
|
|
250
|
+
}
|
|
251
|
+
if (peek() !== "}") throw new ParseError("unterminated substitution", sl, sc);
|
|
252
|
+
advance();
|
|
253
|
+
push(optional ? "opt_subst" : "subst", path2.trim(), sl, sc);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (ch === '"' && peek(1) === '"' && peek(2) === '"') {
|
|
257
|
+
advance();
|
|
258
|
+
advance();
|
|
259
|
+
advance();
|
|
260
|
+
let value = "";
|
|
261
|
+
while (pos < input.length) {
|
|
262
|
+
if (peek() === '"') {
|
|
263
|
+
let quoteCount = 0;
|
|
264
|
+
while (pos < input.length && peek() === '"') {
|
|
265
|
+
quoteCount++;
|
|
266
|
+
advance();
|
|
267
|
+
}
|
|
268
|
+
if (quoteCount >= 3) {
|
|
269
|
+
for (let i = 0; i < quoteCount - 3; i++) value += '"';
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
for (let i = 0; i < quoteCount; i++) value += '"';
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
value += advance();
|
|
276
|
+
}
|
|
277
|
+
if (value.startsWith("\n")) value = value.slice(1);
|
|
278
|
+
push("triple_string", value, sl, sc, true);
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
if (ch === '"') {
|
|
282
|
+
advance();
|
|
283
|
+
let value = "";
|
|
284
|
+
while (pos < input.length && peek() !== '"') {
|
|
285
|
+
if (peek() === "\n") throw new ParseError("unterminated string", sl, sc);
|
|
286
|
+
if (peek() === "\\") {
|
|
287
|
+
advance();
|
|
288
|
+
const esc = advance();
|
|
289
|
+
switch (esc) {
|
|
290
|
+
case "n":
|
|
291
|
+
value += "\n";
|
|
292
|
+
break;
|
|
293
|
+
case "t":
|
|
294
|
+
value += " ";
|
|
295
|
+
break;
|
|
296
|
+
case "r":
|
|
297
|
+
value += "\r";
|
|
298
|
+
break;
|
|
299
|
+
case '"':
|
|
300
|
+
value += '"';
|
|
301
|
+
break;
|
|
302
|
+
case "\\":
|
|
303
|
+
value += "\\";
|
|
304
|
+
break;
|
|
305
|
+
case "/":
|
|
306
|
+
value += "/";
|
|
307
|
+
break;
|
|
308
|
+
case "b":
|
|
309
|
+
value += "\b";
|
|
310
|
+
break;
|
|
311
|
+
case "f":
|
|
312
|
+
value += "\f";
|
|
313
|
+
break;
|
|
314
|
+
case "u": {
|
|
315
|
+
const hex = input.slice(pos, pos + 4);
|
|
316
|
+
value += String.fromCharCode(parseInt(hex, 16));
|
|
317
|
+
for (let i = 0; i < 4; i++) advance();
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
default:
|
|
321
|
+
value += esc;
|
|
322
|
+
}
|
|
323
|
+
} else {
|
|
324
|
+
value += advance();
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (peek() !== '"') throw new ParseError("unterminated string", sl, sc);
|
|
328
|
+
advance();
|
|
329
|
+
push("string", value, sl, sc, true);
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
if (isUnquotedStart(ch)) {
|
|
333
|
+
let value = "";
|
|
334
|
+
while (pos < input.length && isUnquotedContinue(peek(), () => peek(1))) {
|
|
335
|
+
value += advance();
|
|
336
|
+
}
|
|
337
|
+
push("unquoted", value.trimEnd(), sl, sc);
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
throw new ParseError(`unexpected character: ${JSON.stringify(ch)}`, sl, sc);
|
|
341
|
+
}
|
|
342
|
+
tokens.push({ kind: "eof", value: "", line, col, isQuoted: false, precedingSpace: false });
|
|
343
|
+
return tokens;
|
|
344
|
+
}
|
|
345
|
+
function isUnquotedStart(ch) {
|
|
346
|
+
return ch !== "" && !'{}[],:=+#\n\r "$'.includes(ch);
|
|
347
|
+
}
|
|
348
|
+
function isUnquotedContinue(ch, nextFn) {
|
|
349
|
+
if (ch === "" || '{}[],:=\n\r #"$'.includes(ch) || ch === " ") return false;
|
|
350
|
+
if (ch === "+" && nextFn() === "=") return false;
|
|
351
|
+
if (ch === "/" && nextFn() === "/") return false;
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// src/internal/parser/parser.ts
|
|
356
|
+
function parseTokens(tokens) {
|
|
357
|
+
let pos = 0;
|
|
358
|
+
function peek() {
|
|
359
|
+
return tokens[pos] ?? { kind: "eof", value: "", line: 0, col: 0, isQuoted: false, precedingSpace: false };
|
|
360
|
+
}
|
|
361
|
+
function advance() {
|
|
362
|
+
const t2 = tokens[pos];
|
|
363
|
+
if (pos < tokens.length) pos++;
|
|
364
|
+
return t2 ?? { kind: "eof", value: "", line: 0, col: 0, isQuoted: false, precedingSpace: false };
|
|
365
|
+
}
|
|
366
|
+
function skip(...kinds) {
|
|
367
|
+
while (kinds.includes(peek().kind)) advance();
|
|
368
|
+
}
|
|
369
|
+
function currentPos() {
|
|
370
|
+
const t2 = peek();
|
|
371
|
+
return { line: t2.line, col: t2.col };
|
|
372
|
+
}
|
|
373
|
+
function parseObject(expectClosingBrace) {
|
|
374
|
+
const p = currentPos();
|
|
375
|
+
const fields = [];
|
|
376
|
+
while (true) {
|
|
377
|
+
skip("newline");
|
|
378
|
+
const t2 = peek();
|
|
379
|
+
if (t2.kind === "eof" || t2.kind === "rbrace") break;
|
|
380
|
+
if (t2.kind === "unquoted" && t2.value === "include") {
|
|
381
|
+
advance();
|
|
382
|
+
fields.push(parseInclude());
|
|
383
|
+
skip("newline");
|
|
384
|
+
if (peek().kind === "comma") advance();
|
|
385
|
+
skip("newline");
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
const keyPos = currentPos();
|
|
389
|
+
const key = parseKey();
|
|
390
|
+
skip("newline");
|
|
391
|
+
let append = false;
|
|
392
|
+
const sep = peek();
|
|
393
|
+
if (sep.kind === "equals") advance();
|
|
394
|
+
else if (sep.kind === "plus_equals") {
|
|
395
|
+
advance();
|
|
396
|
+
append = true;
|
|
397
|
+
} else if (sep.kind === "colon") advance();
|
|
398
|
+
else if (sep.kind === "lbrace") {
|
|
399
|
+
} else if (sep.kind !== "newline" && sep.kind !== "eof") {
|
|
400
|
+
throw new ParseError(`unexpected token after key: ${sep.kind}`, sep.line, sep.col);
|
|
401
|
+
}
|
|
402
|
+
skip("newline");
|
|
403
|
+
const value = parseValue();
|
|
404
|
+
fields.push({ key, value, append, pos: keyPos });
|
|
405
|
+
skip("newline");
|
|
406
|
+
if (peek().kind === "comma") advance();
|
|
407
|
+
skip("newline");
|
|
408
|
+
}
|
|
409
|
+
if (expectClosingBrace) {
|
|
410
|
+
const t2 = peek();
|
|
411
|
+
if (t2.kind !== "rbrace") throw new ParseError("expected }", t2.line, t2.col);
|
|
412
|
+
advance();
|
|
413
|
+
}
|
|
414
|
+
return { kind: "object", fields, pos: p };
|
|
415
|
+
}
|
|
416
|
+
function parseKey() {
|
|
417
|
+
const segments = [];
|
|
418
|
+
let trailingDot = false;
|
|
419
|
+
while (true) {
|
|
420
|
+
const t2 = peek();
|
|
421
|
+
if (t2.kind === "string") {
|
|
422
|
+
advance();
|
|
423
|
+
segments.push(t2.value);
|
|
424
|
+
trailingDot = false;
|
|
425
|
+
} else if (t2.kind === "unquoted") {
|
|
426
|
+
advance();
|
|
427
|
+
const parts = t2.value.split(".");
|
|
428
|
+
const filtered = parts.filter((s) => s.length > 0);
|
|
429
|
+
segments.push(...filtered);
|
|
430
|
+
trailingDot = t2.value.endsWith(".");
|
|
431
|
+
} else {
|
|
432
|
+
if (segments.length === 0) throw new ParseError(`expected key, got ${t2.kind}`, t2.line, t2.col);
|
|
433
|
+
break;
|
|
434
|
+
}
|
|
435
|
+
if (trailingDot) continue;
|
|
436
|
+
const next = peek();
|
|
437
|
+
if (next.kind === "unquoted" && next.value === "." && !next.precedingSpace) {
|
|
438
|
+
advance();
|
|
439
|
+
trailingDot = true;
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
break;
|
|
443
|
+
}
|
|
444
|
+
return segments;
|
|
445
|
+
}
|
|
446
|
+
function parseInclude() {
|
|
447
|
+
const p = currentPos();
|
|
448
|
+
skip("newline");
|
|
449
|
+
const t2 = peek();
|
|
450
|
+
let path2;
|
|
451
|
+
if (t2.kind === "string") {
|
|
452
|
+
path2 = advance().value;
|
|
453
|
+
} else if (t2.kind === "unquoted" && (t2.value === "file(" || t2.value === "file")) {
|
|
454
|
+
advance();
|
|
455
|
+
while (peek().kind !== "string" && peek().kind !== "eof") advance();
|
|
456
|
+
if (peek().kind === "eof") throw new ParseError("expected include path", t2.line, t2.col);
|
|
457
|
+
path2 = advance().value;
|
|
458
|
+
while (peek().kind !== "newline" && peek().kind !== "rbrace" && peek().kind !== "eof") advance();
|
|
459
|
+
} else {
|
|
460
|
+
throw new ParseError(`expected include path, got ${t2.kind}`, t2.line, t2.col);
|
|
461
|
+
}
|
|
462
|
+
return {
|
|
463
|
+
key: [],
|
|
464
|
+
value: { kind: "include", path: path2, pos: p },
|
|
465
|
+
append: false,
|
|
466
|
+
pos: p
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
function parseValue() {
|
|
470
|
+
const p = currentPos();
|
|
471
|
+
const parts = [];
|
|
472
|
+
while (true) {
|
|
473
|
+
const t2 = peek();
|
|
474
|
+
if (t2.kind === "eof" || t2.kind === "newline" || t2.kind === "rbrace" || t2.kind === "rbracket" || t2.kind === "comma") break;
|
|
475
|
+
const hadSpace = t2.precedingSpace && parts.length > 0;
|
|
476
|
+
let node;
|
|
477
|
+
if (t2.kind === "lbrace") {
|
|
478
|
+
advance();
|
|
479
|
+
node = parseObject(true);
|
|
480
|
+
} else if (t2.kind === "lbracket") {
|
|
481
|
+
advance();
|
|
482
|
+
node = parseArray();
|
|
483
|
+
} else if (t2.kind === "subst" || t2.kind === "opt_subst") {
|
|
484
|
+
advance();
|
|
485
|
+
node = { kind: "subst", path: t2.value, optional: t2.kind === "opt_subst", pos: { line: t2.line, col: t2.col } };
|
|
486
|
+
} else if (t2.kind === "string" || t2.kind === "triple_string") {
|
|
487
|
+
advance();
|
|
488
|
+
node = { kind: "scalar", value: t2.value, pos: { line: t2.line, col: t2.col } };
|
|
489
|
+
} else if (t2.kind === "unquoted") {
|
|
490
|
+
advance();
|
|
491
|
+
node = { kind: "scalar", value: parseScalarValue(t2.value), pos: { line: t2.line, col: t2.col } };
|
|
492
|
+
} else if ((t2.kind === "colon" || t2.kind === "equals") && parts.length > 0) {
|
|
493
|
+
advance();
|
|
494
|
+
node = { kind: "scalar", value: t2.value, pos: { line: t2.line, col: t2.col } };
|
|
495
|
+
} else {
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
498
|
+
if (hadSpace) {
|
|
499
|
+
parts.push({ kind: "scalar", value: " ", pos: { line: t2.line, col: t2.col } });
|
|
500
|
+
}
|
|
501
|
+
parts.push(node);
|
|
502
|
+
}
|
|
503
|
+
if (parts.length === 0) throw new ParseError("expected value", peek().line, peek().col);
|
|
504
|
+
if (parts.length === 1) return parts[0];
|
|
505
|
+
return { kind: "concat", nodes: parts, pos: p };
|
|
506
|
+
}
|
|
507
|
+
function parseArray() {
|
|
508
|
+
const p = currentPos();
|
|
509
|
+
const items = [];
|
|
510
|
+
while (true) {
|
|
511
|
+
skip("newline");
|
|
512
|
+
if (peek().kind === "rbracket" || peek().kind === "eof") break;
|
|
513
|
+
items.push(parseValue());
|
|
514
|
+
skip("newline");
|
|
515
|
+
if (peek().kind === "comma") advance();
|
|
516
|
+
skip("newline");
|
|
517
|
+
}
|
|
518
|
+
const t2 = peek();
|
|
519
|
+
if (t2.kind !== "rbracket") throw new ParseError("expected ]", t2.line, t2.col);
|
|
520
|
+
advance();
|
|
521
|
+
return { kind: "array", items, pos: p };
|
|
522
|
+
}
|
|
523
|
+
function parseScalarValue(raw) {
|
|
524
|
+
if (raw === "true") return true;
|
|
525
|
+
if (raw === "false") return false;
|
|
526
|
+
if (raw === "null") return null;
|
|
527
|
+
const n = Number(raw);
|
|
528
|
+
if (!isNaN(n) && raw.trim() !== "") return n;
|
|
529
|
+
return raw;
|
|
530
|
+
}
|
|
531
|
+
skip("newline");
|
|
532
|
+
const t = peek();
|
|
533
|
+
if (t.kind === "lbrace") {
|
|
534
|
+
advance();
|
|
535
|
+
return parseObject(true);
|
|
536
|
+
}
|
|
537
|
+
return parseObject(false);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// src/internal/resolver/resolver.ts
|
|
541
|
+
var nodePath = __toESM(require("path"), 1);
|
|
542
|
+
function isSubst(v) {
|
|
543
|
+
return v._kind === "subst-placeholder";
|
|
544
|
+
}
|
|
545
|
+
function isConcat(v) {
|
|
546
|
+
return v._kind === "concat-placeholder";
|
|
547
|
+
}
|
|
548
|
+
function isAppend(v) {
|
|
549
|
+
return v._kind === "append-placeholder";
|
|
550
|
+
}
|
|
551
|
+
function isResObj(v) {
|
|
552
|
+
return v._kind === "res-obj";
|
|
553
|
+
}
|
|
554
|
+
function makeResObj() {
|
|
555
|
+
return { _kind: "res-obj", fields: /* @__PURE__ */ new Map(), priorValues: /* @__PURE__ */ new Map() };
|
|
556
|
+
}
|
|
557
|
+
function resolve2(ast, opts) {
|
|
558
|
+
const root = buildResObj(ast, opts);
|
|
559
|
+
const resolving = /* @__PURE__ */ new Set();
|
|
560
|
+
const resolvedCache = /* @__PURE__ */ new Map();
|
|
561
|
+
return resolveResObj(root, root, resolving, resolvedCache, opts);
|
|
562
|
+
}
|
|
563
|
+
function buildResObj(ast, opts) {
|
|
564
|
+
if (ast.kind !== "object") {
|
|
565
|
+
throw new ResolveError("root AST must be an object", "", ast.pos.line, ast.pos.col);
|
|
566
|
+
}
|
|
567
|
+
const obj = makeResObj();
|
|
568
|
+
for (const field of ast.fields) {
|
|
569
|
+
applyField(obj, field, opts);
|
|
570
|
+
}
|
|
571
|
+
return obj;
|
|
572
|
+
}
|
|
573
|
+
function applyField(obj, field, opts) {
|
|
574
|
+
if (field.key.length === 0 && field.value.kind === "include") {
|
|
575
|
+
const included = loadInclude(field.value.path, opts);
|
|
576
|
+
deepMergeResObjInto(obj, included);
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
const [head, ...tail] = field.key;
|
|
580
|
+
if (!head) return;
|
|
581
|
+
if (tail.length > 0) {
|
|
582
|
+
const syntheticAst = {
|
|
583
|
+
kind: "object",
|
|
584
|
+
fields: [{ key: tail, value: field.value, append: field.append, pos: field.pos }],
|
|
585
|
+
pos: field.pos
|
|
586
|
+
};
|
|
587
|
+
applyField(obj, { key: [head], value: syntheticAst, append: false, pos: field.pos }, opts);
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
if (field.append) {
|
|
591
|
+
const existing2 = obj.fields.get(head) ?? { kind: "array", items: [] };
|
|
592
|
+
obj.priorValues.set(head, existing2);
|
|
593
|
+
obj.fields.set(head, {
|
|
594
|
+
_kind: "append-placeholder",
|
|
595
|
+
existing: existing2,
|
|
596
|
+
elem: astToResolverValue(field.value, opts)
|
|
597
|
+
});
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
const existing = obj.fields.get(head);
|
|
601
|
+
const newVal = astToResolverValue(field.value, opts);
|
|
602
|
+
if (existing !== void 0) {
|
|
603
|
+
obj.priorValues.set(head, existing);
|
|
604
|
+
}
|
|
605
|
+
if (existing !== void 0 && isResObj(existing) && isResObj(newVal)) {
|
|
606
|
+
deepMergeResObjInto(existing, newVal);
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
obj.fields.set(head, newVal);
|
|
610
|
+
}
|
|
611
|
+
function astToResolverValue(ast, opts) {
|
|
612
|
+
switch (ast.kind) {
|
|
613
|
+
case "scalar":
|
|
614
|
+
return { kind: "scalar", value: ast.value };
|
|
615
|
+
case "array":
|
|
616
|
+
return { kind: "array", items: ast.items.map((i) => astToResolverValue(i, opts)) };
|
|
617
|
+
case "object": {
|
|
618
|
+
const inner = buildResObj(ast, opts);
|
|
619
|
+
return inner;
|
|
620
|
+
}
|
|
621
|
+
case "subst":
|
|
622
|
+
return { _kind: "subst-placeholder", path: ast.path, optional: ast.optional, line: ast.pos.line, col: ast.pos.col };
|
|
623
|
+
case "concat":
|
|
624
|
+
return { _kind: "concat-placeholder", nodes: ast.nodes.map((n) => astToResolverValue(n, opts)) };
|
|
625
|
+
case "include":
|
|
626
|
+
return { kind: "scalar", value: null };
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
function deepMergeResObjInto(dst, src) {
|
|
630
|
+
for (const [k, srcVal] of src.fields) {
|
|
631
|
+
const dstVal = dst.fields.get(k);
|
|
632
|
+
if (dstVal !== void 0 && isResObj(dstVal) && isResObj(srcVal)) {
|
|
633
|
+
deepMergeResObjInto(dstVal, srcVal);
|
|
634
|
+
} else {
|
|
635
|
+
if (dstVal !== void 0) dst.priorValues.set(k, dstVal);
|
|
636
|
+
dst.fields.set(k, srcVal);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
function resolveResObj(obj, root, resolving, resolvedCache, opts) {
|
|
641
|
+
const result = /* @__PURE__ */ new Map();
|
|
642
|
+
for (const [key, val] of obj.fields) {
|
|
643
|
+
const resolved = resolveVal(val, obj, root, resolving, resolvedCache, opts);
|
|
644
|
+
if (resolved !== void 0) {
|
|
645
|
+
result.set(key, resolved);
|
|
646
|
+
} else {
|
|
647
|
+
const prior = obj.priorValues.get(key);
|
|
648
|
+
if (prior !== void 0) {
|
|
649
|
+
const priorResolved = resolveVal(prior, obj, root, resolving, resolvedCache, opts);
|
|
650
|
+
if (priorResolved !== void 0) result.set(key, priorResolved);
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
return { kind: "object", fields: result };
|
|
655
|
+
}
|
|
656
|
+
function resolveVal(v, scope, root, resolving, resolvedCache, opts) {
|
|
657
|
+
if (isSubst(v)) return resolveSubst(v, scope, root, resolving, resolvedCache, opts);
|
|
658
|
+
if (isConcat(v)) return resolveConcat(v.nodes, scope, root, resolving, resolvedCache, opts);
|
|
659
|
+
if (isAppend(v)) return resolveAppend(v, scope, root, resolving, resolvedCache, opts);
|
|
660
|
+
if (isResObj(v)) return resolveResObj(v, root, resolving, resolvedCache, opts);
|
|
661
|
+
const hv = v;
|
|
662
|
+
if (hv.kind === "array") {
|
|
663
|
+
return {
|
|
664
|
+
kind: "array",
|
|
665
|
+
items: hv.items.map(
|
|
666
|
+
(item) => resolveVal(item, scope, root, resolving, resolvedCache, opts) ?? { kind: "scalar", value: null }
|
|
667
|
+
)
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
return hv;
|
|
671
|
+
}
|
|
672
|
+
function resolveSubst(s, scope, root, resolving, resolvedCache, opts) {
|
|
673
|
+
if (resolvedCache.has(s.path)) return resolvedCache.get(s.path);
|
|
674
|
+
if (resolving.has(s.path)) {
|
|
675
|
+
const rootSeg = parseSubstPath(s.path)[0] ?? "";
|
|
676
|
+
const prior = scope.priorValues.get(rootSeg) ?? root.priorValues.get(rootSeg);
|
|
677
|
+
if (prior !== void 0) {
|
|
678
|
+
return resolveVal(prior, scope, root, new Set(resolving), resolvedCache, opts);
|
|
679
|
+
}
|
|
680
|
+
if (s.optional) return void 0;
|
|
681
|
+
throw new ResolveError(`circular substitution: ${s.path}`, s.path, s.line, s.col);
|
|
682
|
+
}
|
|
683
|
+
resolving.add(s.path);
|
|
684
|
+
try {
|
|
685
|
+
const found = lookupPath(root, parseSubstPath(s.path));
|
|
686
|
+
if (found !== void 0) {
|
|
687
|
+
if (isSubst(found) || isConcat(found)) {
|
|
688
|
+
const rootSeg = parseSubstPath(s.path)[0] ?? "";
|
|
689
|
+
const prior = scope.priorValues.get(rootSeg) ?? root.priorValues.get(rootSeg);
|
|
690
|
+
if (prior !== void 0) {
|
|
691
|
+
const result2 = resolveVal(prior, scope, root, resolving, resolvedCache, opts);
|
|
692
|
+
if (result2 !== void 0) resolvedCache.set(s.path, result2);
|
|
693
|
+
return result2;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
const result = resolveVal(found, scope, root, resolving, resolvedCache, opts);
|
|
697
|
+
if (result !== void 0) resolvedCache.set(s.path, result);
|
|
698
|
+
return result;
|
|
699
|
+
}
|
|
700
|
+
const envVal = opts.env[s.path];
|
|
701
|
+
if (envVal !== void 0) {
|
|
702
|
+
const result = { kind: "scalar", value: envVal };
|
|
703
|
+
resolvedCache.set(s.path, result);
|
|
704
|
+
return result;
|
|
705
|
+
}
|
|
706
|
+
if (s.optional) return void 0;
|
|
707
|
+
throw new ResolveError(`could not resolve substitution: \${${s.path}}`, s.path, s.line, s.col);
|
|
708
|
+
} finally {
|
|
709
|
+
resolving.delete(s.path);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
function resolveConcat(nodes, scope, root, resolving, resolvedCache, opts) {
|
|
713
|
+
const resolved = nodes.map((n) => resolveVal(n, scope, root, resolving, resolvedCache, opts)).filter((v) => v !== void 0);
|
|
714
|
+
if (resolved.length === 0) return { kind: "scalar", value: null };
|
|
715
|
+
if (resolved.length === 1) return resolved[0];
|
|
716
|
+
if (resolved.every((v) => v.kind === "object")) {
|
|
717
|
+
const merged = /* @__PURE__ */ new Map();
|
|
718
|
+
for (const v of resolved) {
|
|
719
|
+
if (v.kind === "object") {
|
|
720
|
+
for (const [k, val] of v.fields) {
|
|
721
|
+
merged.set(k, val);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
return { kind: "object", fields: merged };
|
|
726
|
+
}
|
|
727
|
+
if (resolved.some((v) => v.kind === "array")) {
|
|
728
|
+
const items = [];
|
|
729
|
+
for (const v of resolved) {
|
|
730
|
+
if (v.kind === "array") items.push(...v.items);
|
|
731
|
+
else items.push(v);
|
|
732
|
+
}
|
|
733
|
+
return { kind: "array", items };
|
|
734
|
+
}
|
|
735
|
+
const str = resolved.map((v) => v.kind === "scalar" ? String(v.value) : JSON.stringify(v)).join("");
|
|
736
|
+
return { kind: "scalar", value: str };
|
|
737
|
+
}
|
|
738
|
+
function resolveAppend(a, scope, root, resolving, resolvedCache, opts) {
|
|
739
|
+
const existing = resolveVal(a.existing, scope, root, resolving, resolvedCache, opts) ?? { kind: "array", items: [] };
|
|
740
|
+
const elem = resolveVal(a.elem, scope, root, resolving, resolvedCache, opts);
|
|
741
|
+
const items = existing.kind === "array" ? [...existing.items] : [existing];
|
|
742
|
+
if (elem !== void 0) items.push(elem);
|
|
743
|
+
return { kind: "array", items };
|
|
744
|
+
}
|
|
745
|
+
function lookupPath(root, segments) {
|
|
746
|
+
const [head, ...tail] = segments;
|
|
747
|
+
if (head === void 0 || head === "") {
|
|
748
|
+
if (segments.length > 0) {
|
|
749
|
+
const val2 = root.fields.get("");
|
|
750
|
+
if (val2 === void 0) return void 0;
|
|
751
|
+
if (tail.length === 0) return val2;
|
|
752
|
+
if (isResObj(val2)) return lookupPath(val2, tail);
|
|
753
|
+
return void 0;
|
|
754
|
+
}
|
|
755
|
+
return void 0;
|
|
756
|
+
}
|
|
757
|
+
const val = root.fields.get(head);
|
|
758
|
+
if (val === void 0) return void 0;
|
|
759
|
+
if (tail.length === 0) return val;
|
|
760
|
+
if (isResObj(val)) return lookupPath(val, tail);
|
|
761
|
+
return void 0;
|
|
762
|
+
}
|
|
763
|
+
function parseSubstPath(raw) {
|
|
764
|
+
const segments = [];
|
|
765
|
+
let i = 0;
|
|
766
|
+
while (i < raw.length) {
|
|
767
|
+
while (i < raw.length && (raw[i] === " " || raw[i] === " ")) i++;
|
|
768
|
+
if (i >= raw.length) break;
|
|
769
|
+
if (raw[i] === '"') {
|
|
770
|
+
i++;
|
|
771
|
+
let seg = "";
|
|
772
|
+
while (i < raw.length && raw[i] !== '"') {
|
|
773
|
+
seg += raw[i];
|
|
774
|
+
i++;
|
|
775
|
+
}
|
|
776
|
+
if (i < raw.length) i++;
|
|
777
|
+
segments.push(seg);
|
|
778
|
+
while (i < raw.length && (raw[i] === " " || raw[i] === " ")) i++;
|
|
779
|
+
if (i < raw.length && raw[i] === ".") i++;
|
|
780
|
+
} else if (raw[i] === ".") {
|
|
781
|
+
segments.push("");
|
|
782
|
+
i++;
|
|
783
|
+
} else {
|
|
784
|
+
let seg = "";
|
|
785
|
+
while (i < raw.length && raw[i] !== ".") {
|
|
786
|
+
seg += raw[i];
|
|
787
|
+
i++;
|
|
788
|
+
}
|
|
789
|
+
segments.push(seg.trim());
|
|
790
|
+
if (i < raw.length && raw[i] === ".") i++;
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
return segments;
|
|
794
|
+
}
|
|
795
|
+
function loadInclude(includePath, opts) {
|
|
796
|
+
const { baseDir, readFileSync: readFileSync2, includeStack = [], env } = opts;
|
|
797
|
+
const absPath = baseDir ? nodePath.resolve(baseDir, includePath) : nodePath.resolve(includePath);
|
|
798
|
+
if (includeStack.includes(absPath)) {
|
|
799
|
+
throw new ResolveError(`circular include: ${absPath}`, absPath, 0, 0);
|
|
800
|
+
}
|
|
801
|
+
const candidates = [absPath];
|
|
802
|
+
if (!absPath.endsWith(".conf") && !absPath.endsWith(".json") && !absPath.endsWith(".properties")) {
|
|
803
|
+
candidates.push(absPath + ".conf", absPath + ".json");
|
|
804
|
+
}
|
|
805
|
+
for (const candidate of candidates) {
|
|
806
|
+
let content;
|
|
807
|
+
try {
|
|
808
|
+
content = readFileSync2(candidate);
|
|
809
|
+
} catch {
|
|
810
|
+
continue;
|
|
811
|
+
}
|
|
812
|
+
if (includeStack.includes(candidate)) {
|
|
813
|
+
throw new ResolveError(`circular include: ${candidate}`, candidate, 0, 0);
|
|
814
|
+
}
|
|
815
|
+
const ast = parseTokens(tokenize(content));
|
|
816
|
+
return buildResObj(ast, {
|
|
817
|
+
env,
|
|
818
|
+
baseDir: nodePath.dirname(candidate),
|
|
819
|
+
readFileSync: readFileSync2,
|
|
820
|
+
includeStack: [...includeStack, candidate]
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
return makeResObj();
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// src/parse.ts
|
|
827
|
+
var fs = __toESM(require("fs"), 1);
|
|
828
|
+
var path = __toESM(require("path"), 1);
|
|
829
|
+
function getEnv(opts) {
|
|
830
|
+
if (opts.env !== void 0) return opts.env;
|
|
831
|
+
if (typeof process !== "undefined" && process.env) return process.env;
|
|
832
|
+
return {};
|
|
833
|
+
}
|
|
834
|
+
function defaultReadFileSync(filePath) {
|
|
835
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
836
|
+
}
|
|
837
|
+
async function defaultReadFile(filePath) {
|
|
838
|
+
return fs.promises.readFile(filePath, "utf-8");
|
|
839
|
+
}
|
|
840
|
+
function parse(input, opts = {}) {
|
|
841
|
+
const tokens = tokenize(input);
|
|
842
|
+
const ast = parseTokens(tokens);
|
|
843
|
+
const resolveOpts = {
|
|
844
|
+
env: getEnv(opts),
|
|
845
|
+
baseDir: opts.baseDir,
|
|
846
|
+
readFileSync: opts.readFileSync ?? defaultReadFileSync
|
|
847
|
+
};
|
|
848
|
+
const value = resolve2(ast, resolveOpts);
|
|
849
|
+
if (value.kind !== "object") throw new Error("resolved value is not an object");
|
|
850
|
+
return new Config(value);
|
|
851
|
+
}
|
|
852
|
+
async function parseAsync(input, opts = {}) {
|
|
853
|
+
return Promise.resolve(parse(input, opts));
|
|
854
|
+
}
|
|
855
|
+
function parseFile(filePath, opts = {}) {
|
|
856
|
+
if (typeof process === "undefined" && !opts.readFileSync) {
|
|
857
|
+
throw new Error("parseFile is not supported in browser environments. Provide opts.readFileSync or use parse() instead.");
|
|
858
|
+
}
|
|
859
|
+
const resolvedPath = path.resolve(filePath);
|
|
860
|
+
const readFileSync2 = opts.readFileSync ?? defaultReadFileSync;
|
|
861
|
+
const input = readFileSync2(resolvedPath);
|
|
862
|
+
return parse(input, {
|
|
863
|
+
...opts,
|
|
864
|
+
baseDir: opts.baseDir ?? path.dirname(resolvedPath),
|
|
865
|
+
readFileSync: readFileSync2
|
|
866
|
+
});
|
|
867
|
+
}
|
|
868
|
+
async function parseFileAsync(filePath, opts = {}) {
|
|
869
|
+
if (typeof process === "undefined" && !opts.readFile) {
|
|
870
|
+
throw new Error("parseFileAsync is not supported in browser environments. Provide opts.readFile or use parseAsync() instead.");
|
|
871
|
+
}
|
|
872
|
+
const resolvedPath = path.resolve(filePath);
|
|
873
|
+
const readFile = opts.readFile ?? defaultReadFile;
|
|
874
|
+
const input = await readFile(resolvedPath);
|
|
875
|
+
return parse(input, {
|
|
876
|
+
...opts,
|
|
877
|
+
baseDir: opts.baseDir ?? path.dirname(resolvedPath),
|
|
878
|
+
readFileSync: opts.readFileSync ?? defaultReadFileSync
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
882
|
+
0 && (module.exports = {
|
|
883
|
+
Config,
|
|
884
|
+
ConfigError,
|
|
885
|
+
ParseError,
|
|
886
|
+
ResolveError,
|
|
887
|
+
parse,
|
|
888
|
+
parseAsync,
|
|
889
|
+
parseFile,
|
|
890
|
+
parseFileAsync
|
|
891
|
+
});
|