lacspace-json 0.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/LICENSE +51 -0
- package/README.md +159 -0
- package/dist/cli.js +2245 -0
- package/dist/lib.cjs +1980 -0
- package/dist/lib.d.cts +151 -0
- package/dist/lib.d.ts +151 -0
- package/dist/lib.js +1921 -0
- package/package.json +47 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,2245 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { stdout, stderr, argv, exit, stdin } from "process";
|
|
5
|
+
import { readFileSync, readdirSync, statSync } from "fs";
|
|
6
|
+
import { basename, dirname, join as join2 } from "path";
|
|
7
|
+
|
|
8
|
+
// src/util.ts
|
|
9
|
+
var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
10
|
+
function isForbiddenKey(key) {
|
|
11
|
+
return FORBIDDEN_KEYS.has(key);
|
|
12
|
+
}
|
|
13
|
+
function safeSet(obj, key, value) {
|
|
14
|
+
if (isForbiddenKey(key)) return;
|
|
15
|
+
Object.defineProperty(obj, key, {
|
|
16
|
+
value,
|
|
17
|
+
writable: true,
|
|
18
|
+
enumerable: true,
|
|
19
|
+
configurable: true
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function isPlainObject(v) {
|
|
23
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
24
|
+
}
|
|
25
|
+
function typeOf(v) {
|
|
26
|
+
if (v === null || v === void 0) return "null";
|
|
27
|
+
if (Array.isArray(v)) return "array";
|
|
28
|
+
const t = typeof v;
|
|
29
|
+
if (t === "boolean") return "boolean";
|
|
30
|
+
if (t === "number") return "number";
|
|
31
|
+
if (t === "string") return "string";
|
|
32
|
+
return "object";
|
|
33
|
+
}
|
|
34
|
+
function deepEqual(a, b) {
|
|
35
|
+
if (a === b) return true;
|
|
36
|
+
if (a === null || b === null) return a === b;
|
|
37
|
+
const ta = typeof a;
|
|
38
|
+
const tb = typeof b;
|
|
39
|
+
if (ta !== tb) return false;
|
|
40
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
41
|
+
if (!Array.isArray(a) || !Array.isArray(b)) return false;
|
|
42
|
+
if (a.length !== b.length) return false;
|
|
43
|
+
for (let i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i])) return false;
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
if (ta === "object") {
|
|
47
|
+
const oa = a;
|
|
48
|
+
const ob = b;
|
|
49
|
+
const ka = Object.keys(oa);
|
|
50
|
+
const kb = Object.keys(ob);
|
|
51
|
+
if (ka.length !== kb.length) return false;
|
|
52
|
+
for (const k of ka) {
|
|
53
|
+
if (!Object.prototype.hasOwnProperty.call(ob, k)) return false;
|
|
54
|
+
if (!deepEqual(oa[k], ob[k])) return false;
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
function deepClone(v) {
|
|
61
|
+
if (v === null || typeof v !== "object") return v;
|
|
62
|
+
if (Array.isArray(v)) return v.map((x) => deepClone(x));
|
|
63
|
+
const out2 = {};
|
|
64
|
+
for (const k of Object.keys(v)) {
|
|
65
|
+
safeSet(out2, k, deepClone(v[k]));
|
|
66
|
+
}
|
|
67
|
+
return out2;
|
|
68
|
+
}
|
|
69
|
+
function compareValues(a, b) {
|
|
70
|
+
const rank = (v) => {
|
|
71
|
+
if (v === null || v === void 0) return 0;
|
|
72
|
+
if (v === false) return 1;
|
|
73
|
+
if (v === true) return 2;
|
|
74
|
+
if (typeof v === "number") return 3;
|
|
75
|
+
if (typeof v === "string") return 4;
|
|
76
|
+
if (Array.isArray(v)) return 5;
|
|
77
|
+
return 6;
|
|
78
|
+
};
|
|
79
|
+
const ra = rank(a);
|
|
80
|
+
const rb = rank(b);
|
|
81
|
+
if (ra !== rb) return ra - rb;
|
|
82
|
+
if (ra === 3) return a - b;
|
|
83
|
+
if (ra === 4) return a < b ? -1 : a > b ? 1 : 0;
|
|
84
|
+
if (ra === 5) {
|
|
85
|
+
const aa = a;
|
|
86
|
+
const bb = b;
|
|
87
|
+
const n = Math.min(aa.length, bb.length);
|
|
88
|
+
for (let i = 0; i < n; i++) {
|
|
89
|
+
const c2 = compareValues(aa[i], bb[i]);
|
|
90
|
+
if (c2 !== 0) return c2;
|
|
91
|
+
}
|
|
92
|
+
return aa.length - bb.length;
|
|
93
|
+
}
|
|
94
|
+
if (ra === 6) {
|
|
95
|
+
const ka = Object.keys(a).sort();
|
|
96
|
+
const kb = Object.keys(b).sort();
|
|
97
|
+
const n = Math.min(ka.length, kb.length);
|
|
98
|
+
for (let i = 0; i < n; i++) {
|
|
99
|
+
if (ka[i] !== kb[i]) return ka[i] < kb[i] ? -1 : 1;
|
|
100
|
+
}
|
|
101
|
+
if (ka.length !== kb.length) return ka.length - kb.length;
|
|
102
|
+
for (const k of ka) {
|
|
103
|
+
const c2 = compareValues(a[k], b[k]);
|
|
104
|
+
if (c2 !== 0) return c2;
|
|
105
|
+
}
|
|
106
|
+
return 0;
|
|
107
|
+
}
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
function sortKeysDeep(v) {
|
|
111
|
+
if (Array.isArray(v)) return v.map(sortKeysDeep);
|
|
112
|
+
if (isPlainObject(v)) {
|
|
113
|
+
const out2 = {};
|
|
114
|
+
for (const k of Object.keys(v).sort()) safeSet(out2, k, sortKeysDeep(v[k]));
|
|
115
|
+
return out2;
|
|
116
|
+
}
|
|
117
|
+
return v;
|
|
118
|
+
}
|
|
119
|
+
var JsonToolError = class extends Error {
|
|
120
|
+
path;
|
|
121
|
+
constructor(message, path) {
|
|
122
|
+
super(message);
|
|
123
|
+
this.name = "JsonToolError";
|
|
124
|
+
if (path !== void 0) this.path = path;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// src/query.ts
|
|
129
|
+
function tokenize(src) {
|
|
130
|
+
const toks = [];
|
|
131
|
+
let i = 0;
|
|
132
|
+
const n = src.length;
|
|
133
|
+
while (i < n) {
|
|
134
|
+
const ch = src[i];
|
|
135
|
+
if (ch === " " || ch === " " || ch === "\n" || ch === "\r") {
|
|
136
|
+
i++;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
if (ch === ".") {
|
|
140
|
+
toks.push({ t: "dot" });
|
|
141
|
+
i++;
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (ch === "[") {
|
|
145
|
+
toks.push({ t: "lbracket" });
|
|
146
|
+
i++;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (ch === "]") {
|
|
150
|
+
toks.push({ t: "rbracket" });
|
|
151
|
+
i++;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (ch === "(") {
|
|
155
|
+
toks.push({ t: "lparen" });
|
|
156
|
+
i++;
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (ch === ")") {
|
|
160
|
+
toks.push({ t: "rparen" });
|
|
161
|
+
i++;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (ch === "|") {
|
|
165
|
+
toks.push({ t: "pipe" });
|
|
166
|
+
i++;
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
if (ch === ",") {
|
|
170
|
+
toks.push({ t: "comma" });
|
|
171
|
+
i++;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (ch === '"' || ch === "'") {
|
|
175
|
+
const quote = ch;
|
|
176
|
+
let j = i + 1;
|
|
177
|
+
let s = "";
|
|
178
|
+
while (j < n && src[j] !== quote) {
|
|
179
|
+
if (src[j] === "\\" && j + 1 < n) {
|
|
180
|
+
const esc = src[j + 1];
|
|
181
|
+
s += esc === "n" ? "\n" : esc === "t" ? " " : esc;
|
|
182
|
+
j += 2;
|
|
183
|
+
} else {
|
|
184
|
+
s += src[j];
|
|
185
|
+
j++;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (j >= n) throw new JsonToolError(`Unterminated string in query near "${src.slice(i, i + 12)}"`);
|
|
189
|
+
toks.push({ t: "str", v: s });
|
|
190
|
+
i = j + 1;
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (ch === ">" || ch === "<" || ch === "=" || ch === "!") {
|
|
194
|
+
if (src[i + 1] === "=") {
|
|
195
|
+
toks.push({ t: "op", v: ch + "=" });
|
|
196
|
+
i += 2;
|
|
197
|
+
} else if (ch === "=" || ch === "!") throw new JsonToolError(`Use "==" / "!=" for comparison near "${src.slice(i)}"`);
|
|
198
|
+
else {
|
|
199
|
+
toks.push({ t: "op", v: ch });
|
|
200
|
+
i++;
|
|
201
|
+
}
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (/[0-9]/.test(ch) || ch === "-" && /[0-9]/.test(src[i + 1] ?? "")) {
|
|
205
|
+
let j = i + 1;
|
|
206
|
+
while (j < n && /[0-9.eE+-]/.test(src[j])) j++;
|
|
207
|
+
const raw = src.slice(i, j);
|
|
208
|
+
const num = Number(raw);
|
|
209
|
+
if (Number.isNaN(num)) throw new JsonToolError(`Invalid number "${raw}" in query`);
|
|
210
|
+
toks.push({ t: "num", v: num });
|
|
211
|
+
i = j;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
if (/[A-Za-z_]/.test(ch)) {
|
|
215
|
+
let j = i + 1;
|
|
216
|
+
while (j < n && /[A-Za-z0-9_]/.test(src[j])) j++;
|
|
217
|
+
toks.push({ t: "ident", v: src.slice(i, j) });
|
|
218
|
+
i = j;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
throw new JsonToolError(`Unexpected character "${ch}" in query near "${src.slice(i, i + 12)}"`);
|
|
222
|
+
}
|
|
223
|
+
return toks;
|
|
224
|
+
}
|
|
225
|
+
var FUNCS_NO_ARG = /* @__PURE__ */ new Set([
|
|
226
|
+
"keys",
|
|
227
|
+
"values",
|
|
228
|
+
"length",
|
|
229
|
+
"type",
|
|
230
|
+
"unique",
|
|
231
|
+
"reverse",
|
|
232
|
+
"first",
|
|
233
|
+
"last",
|
|
234
|
+
"min",
|
|
235
|
+
"max",
|
|
236
|
+
"sum",
|
|
237
|
+
"avg",
|
|
238
|
+
"add",
|
|
239
|
+
"flatten"
|
|
240
|
+
]);
|
|
241
|
+
var FUNCS_WITH_ARG = /* @__PURE__ */ new Set(["map", "sort_by", "group_by", "has", "select"]);
|
|
242
|
+
var Parser = class {
|
|
243
|
+
toks;
|
|
244
|
+
pos = 0;
|
|
245
|
+
constructor(toks) {
|
|
246
|
+
this.toks = toks;
|
|
247
|
+
}
|
|
248
|
+
peek() {
|
|
249
|
+
return this.toks[this.pos];
|
|
250
|
+
}
|
|
251
|
+
next() {
|
|
252
|
+
return this.toks[this.pos++];
|
|
253
|
+
}
|
|
254
|
+
expect(t) {
|
|
255
|
+
const tk = this.next();
|
|
256
|
+
if (!tk || tk.t !== t) throw new JsonToolError(`Query parse error: expected ${t}`);
|
|
257
|
+
return tk;
|
|
258
|
+
}
|
|
259
|
+
parsePipeline(stopAtParen = false) {
|
|
260
|
+
const stages = [];
|
|
261
|
+
stages.push(this.parseStage());
|
|
262
|
+
while (this.peek()?.t === "pipe") {
|
|
263
|
+
this.next();
|
|
264
|
+
stages.push(this.parseStage());
|
|
265
|
+
}
|
|
266
|
+
if (stopAtParen && this.peek() && this.peek().t !== "rparen") {
|
|
267
|
+
throw new JsonToolError("Query parse error: expected ')'");
|
|
268
|
+
}
|
|
269
|
+
return { stages };
|
|
270
|
+
}
|
|
271
|
+
parseStage() {
|
|
272
|
+
const tk = this.peek();
|
|
273
|
+
if (!tk) throw new JsonToolError("Query parse error: unexpected end of expression");
|
|
274
|
+
if (tk.t === "dot" || tk.t === "lbracket") return this.parsePath();
|
|
275
|
+
if (tk.t === "ident") return this.parseFunc();
|
|
276
|
+
throw new JsonToolError(`Query parse error: unexpected token near "${describeTok(tk)}"`);
|
|
277
|
+
}
|
|
278
|
+
parsePath() {
|
|
279
|
+
const steps = [];
|
|
280
|
+
while (true) {
|
|
281
|
+
const tk = this.peek();
|
|
282
|
+
if (tk?.t === "dot") {
|
|
283
|
+
this.next();
|
|
284
|
+
const after = this.peek();
|
|
285
|
+
if (after?.t === "ident") {
|
|
286
|
+
this.next();
|
|
287
|
+
steps.push({ kind: "key", name: after.v });
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
if (after?.t === "str") {
|
|
291
|
+
this.next();
|
|
292
|
+
steps.push({ kind: "key", name: after.v });
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
if (after?.t === "lbracket") {
|
|
296
|
+
this.parseBracket(steps);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
if (tk?.t === "lbracket") {
|
|
302
|
+
this.parseBracket(steps);
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
return { kind: "path", steps };
|
|
308
|
+
}
|
|
309
|
+
parseBracket(steps) {
|
|
310
|
+
this.expect("lbracket");
|
|
311
|
+
const inner = this.peek();
|
|
312
|
+
if (inner?.t === "rbracket") {
|
|
313
|
+
this.next();
|
|
314
|
+
steps.push({ kind: "iterate" });
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
if (inner?.t === "num") {
|
|
318
|
+
this.next();
|
|
319
|
+
this.expect("rbracket");
|
|
320
|
+
steps.push({ kind: "index", index: inner.v });
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
if (inner?.t === "str") {
|
|
324
|
+
this.next();
|
|
325
|
+
this.expect("rbracket");
|
|
326
|
+
steps.push({ kind: "key", name: inner.v });
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (inner?.t === "op" && inner.v === "<") {
|
|
330
|
+
}
|
|
331
|
+
throw new JsonToolError("Query parse error: expected number, string or empty in [ ]");
|
|
332
|
+
}
|
|
333
|
+
parseFunc() {
|
|
334
|
+
const nameTok = this.expect("ident");
|
|
335
|
+
const name = nameTok.v;
|
|
336
|
+
if (name === "select") {
|
|
337
|
+
this.expect("lparen");
|
|
338
|
+
const cond = this.parseCond();
|
|
339
|
+
this.expect("rparen");
|
|
340
|
+
return { kind: "select", cond };
|
|
341
|
+
}
|
|
342
|
+
if (FUNCS_WITH_ARG.has(name)) {
|
|
343
|
+
this.expect("lparen");
|
|
344
|
+
if (name === "has") {
|
|
345
|
+
const arg = this.next();
|
|
346
|
+
let key;
|
|
347
|
+
if (arg?.t === "str") key = arg.v;
|
|
348
|
+
else if (arg?.t === "num") key = arg.v;
|
|
349
|
+
else if (arg?.t === "ident") key = arg.v;
|
|
350
|
+
else throw new JsonToolError('has() needs a key, e.g. has("id")');
|
|
351
|
+
this.expect("rparen");
|
|
352
|
+
return { kind: "func", name, litKey: key };
|
|
353
|
+
}
|
|
354
|
+
const inner = this.parsePipeline(true);
|
|
355
|
+
this.expect("rparen");
|
|
356
|
+
return { kind: "func", name, arg: inner };
|
|
357
|
+
}
|
|
358
|
+
if (FUNCS_NO_ARG.has(name)) return { kind: "func", name };
|
|
359
|
+
throw new JsonToolError(`Unknown query function "${name}"`);
|
|
360
|
+
}
|
|
361
|
+
// --- conditions (inside select) ---
|
|
362
|
+
parseCond() {
|
|
363
|
+
return this.parseOr();
|
|
364
|
+
}
|
|
365
|
+
parseOr() {
|
|
366
|
+
let left = this.parseAnd();
|
|
367
|
+
while (this.peek()?.t === "ident" && this.peek().v === "or") {
|
|
368
|
+
this.next();
|
|
369
|
+
const right = this.parseAnd();
|
|
370
|
+
left = { kind: "or", l: left, r: right };
|
|
371
|
+
}
|
|
372
|
+
return left;
|
|
373
|
+
}
|
|
374
|
+
parseAnd() {
|
|
375
|
+
let left = this.parseCondAtom();
|
|
376
|
+
while (this.peek()?.t === "ident" && this.peek().v === "and") {
|
|
377
|
+
this.next();
|
|
378
|
+
const right = this.parseCondAtom();
|
|
379
|
+
left = { kind: "and", l: left, r: right };
|
|
380
|
+
}
|
|
381
|
+
return left;
|
|
382
|
+
}
|
|
383
|
+
parseCondAtom() {
|
|
384
|
+
const tk = this.peek();
|
|
385
|
+
if (tk?.t === "ident" && tk.v === "not") {
|
|
386
|
+
this.next();
|
|
387
|
+
return { kind: "not", c: this.parseCondAtom() };
|
|
388
|
+
}
|
|
389
|
+
if (tk?.t === "lparen") {
|
|
390
|
+
this.next();
|
|
391
|
+
const c2 = this.parseCond();
|
|
392
|
+
this.expect("rparen");
|
|
393
|
+
return c2;
|
|
394
|
+
}
|
|
395
|
+
const left = this.parseCondPipeline();
|
|
396
|
+
const opTok = this.peek();
|
|
397
|
+
if (opTok?.t === "op") {
|
|
398
|
+
this.next();
|
|
399
|
+
const right = this.parseLiteral();
|
|
400
|
+
return { kind: "cmp", left, op: opTok.v, right };
|
|
401
|
+
}
|
|
402
|
+
return { kind: "truthy", expr: left };
|
|
403
|
+
}
|
|
404
|
+
parseCondPipeline() {
|
|
405
|
+
const stages = [this.parseStage()];
|
|
406
|
+
while (this.peek()?.t === "pipe") {
|
|
407
|
+
this.next();
|
|
408
|
+
stages.push(this.parseStage());
|
|
409
|
+
}
|
|
410
|
+
return { stages };
|
|
411
|
+
}
|
|
412
|
+
parseLiteral() {
|
|
413
|
+
const tk = this.next();
|
|
414
|
+
if (!tk) throw new JsonToolError("Query parse error: expected a value after operator");
|
|
415
|
+
if (tk.t === "num") return { lit: true, value: tk.v };
|
|
416
|
+
if (tk.t === "str") return { lit: true, value: tk.v };
|
|
417
|
+
if (tk.t === "ident") {
|
|
418
|
+
if (tk.v === "true") return { lit: true, value: true };
|
|
419
|
+
if (tk.v === "false") return { lit: true, value: false };
|
|
420
|
+
if (tk.v === "null") return { lit: true, value: null };
|
|
421
|
+
return { lit: true, value: tk.v };
|
|
422
|
+
}
|
|
423
|
+
throw new JsonToolError("Query parse error: expected a literal (number, string, true/false/null)");
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
function describeTok(t) {
|
|
427
|
+
if (t.t === "ident" || t.t === "str") return t.v;
|
|
428
|
+
if (t.t === "num") return String(t.v);
|
|
429
|
+
if (t.t === "op") return t.v;
|
|
430
|
+
return t.t;
|
|
431
|
+
}
|
|
432
|
+
function getKey(v, key) {
|
|
433
|
+
if (v === null || v === void 0) return void 0;
|
|
434
|
+
if (Array.isArray(v)) return void 0;
|
|
435
|
+
if (typeof v !== "object") return void 0;
|
|
436
|
+
if (!Object.prototype.hasOwnProperty.call(v, key)) return void 0;
|
|
437
|
+
return v[key];
|
|
438
|
+
}
|
|
439
|
+
function evalPath(steps, inputs) {
|
|
440
|
+
let cur = inputs;
|
|
441
|
+
for (const step of steps) {
|
|
442
|
+
if (step.kind === "key") {
|
|
443
|
+
cur = cur.map((v) => getKey(v, step.name));
|
|
444
|
+
} else if (step.kind === "index") {
|
|
445
|
+
cur = cur.map((v) => {
|
|
446
|
+
if (!Array.isArray(v)) return void 0;
|
|
447
|
+
const idx = step.index < 0 ? v.length + step.index : step.index;
|
|
448
|
+
return v[idx];
|
|
449
|
+
});
|
|
450
|
+
} else {
|
|
451
|
+
const out2 = [];
|
|
452
|
+
for (const v of cur) {
|
|
453
|
+
if (Array.isArray(v)) out2.push(...v);
|
|
454
|
+
else if (v && typeof v === "object") out2.push(...Object.values(v));
|
|
455
|
+
}
|
|
456
|
+
cur = out2;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return cur;
|
|
460
|
+
}
|
|
461
|
+
function asNumbers(arr, fn) {
|
|
462
|
+
const out2 = [];
|
|
463
|
+
for (const v of arr) {
|
|
464
|
+
if (typeof v !== "number") throw new JsonToolError(`${fn}: expected an array of numbers, found ${typeOf(v)}`);
|
|
465
|
+
out2.push(v);
|
|
466
|
+
}
|
|
467
|
+
return out2;
|
|
468
|
+
}
|
|
469
|
+
function evalFunc(stage, inputs) {
|
|
470
|
+
const name = stage.name;
|
|
471
|
+
const litKey = stage.litKey;
|
|
472
|
+
const perValue = (fn) => inputs.map(fn);
|
|
473
|
+
switch (name) {
|
|
474
|
+
case "keys":
|
|
475
|
+
return perValue((v) => {
|
|
476
|
+
if (Array.isArray(v)) return v.map((_, i) => i);
|
|
477
|
+
if (v && typeof v === "object") return Object.keys(v).sort();
|
|
478
|
+
throw new JsonToolError(`keys: expected object or array, found ${typeOf(v)}`);
|
|
479
|
+
});
|
|
480
|
+
case "values":
|
|
481
|
+
return perValue((v) => {
|
|
482
|
+
if (Array.isArray(v)) return v;
|
|
483
|
+
if (v && typeof v === "object") return Object.values(v);
|
|
484
|
+
throw new JsonToolError(`values: expected object or array, found ${typeOf(v)}`);
|
|
485
|
+
});
|
|
486
|
+
case "length":
|
|
487
|
+
return perValue((v) => {
|
|
488
|
+
if (v === null || v === void 0) return 0;
|
|
489
|
+
if (typeof v === "string") return v.length;
|
|
490
|
+
if (Array.isArray(v)) return v.length;
|
|
491
|
+
if (typeof v === "number") return Math.abs(v);
|
|
492
|
+
if (typeof v === "object") return Object.keys(v).length;
|
|
493
|
+
throw new JsonToolError(`length: unsupported type ${typeOf(v)}`);
|
|
494
|
+
});
|
|
495
|
+
case "type":
|
|
496
|
+
return perValue((v) => typeOf(v));
|
|
497
|
+
case "has":
|
|
498
|
+
return perValue((v) => {
|
|
499
|
+
if (Array.isArray(v)) return typeof litKey === "number" && litKey >= 0 && litKey < v.length;
|
|
500
|
+
if (v && typeof v === "object") return Object.prototype.hasOwnProperty.call(v, String(litKey));
|
|
501
|
+
throw new JsonToolError(`has: expected object or array, found ${typeOf(v)}`);
|
|
502
|
+
});
|
|
503
|
+
case "unique":
|
|
504
|
+
return perValue((v) => {
|
|
505
|
+
if (!Array.isArray(v)) throw new JsonToolError(`unique: expected array, found ${typeOf(v)}`);
|
|
506
|
+
const sorted = [...v].sort(compareValues);
|
|
507
|
+
const out2 = [];
|
|
508
|
+
for (const x of sorted) if (out2.length === 0 || !deepEqual(out2[out2.length - 1], x)) out2.push(x);
|
|
509
|
+
return out2;
|
|
510
|
+
});
|
|
511
|
+
case "reverse":
|
|
512
|
+
return perValue((v) => {
|
|
513
|
+
if (Array.isArray(v)) return [...v].reverse();
|
|
514
|
+
if (typeof v === "string") return [...v].reverse().join("");
|
|
515
|
+
throw new JsonToolError(`reverse: expected array or string, found ${typeOf(v)}`);
|
|
516
|
+
});
|
|
517
|
+
case "flatten":
|
|
518
|
+
return perValue((v) => {
|
|
519
|
+
if (!Array.isArray(v)) throw new JsonToolError(`flatten: expected array, found ${typeOf(v)}`);
|
|
520
|
+
const out2 = [];
|
|
521
|
+
const walk2 = (a) => {
|
|
522
|
+
for (const x of a) Array.isArray(x) ? walk2(x) : out2.push(x);
|
|
523
|
+
};
|
|
524
|
+
walk2(v);
|
|
525
|
+
return out2;
|
|
526
|
+
});
|
|
527
|
+
case "first":
|
|
528
|
+
return perValue((v) => {
|
|
529
|
+
if (!Array.isArray(v)) throw new JsonToolError(`first: expected array`);
|
|
530
|
+
return v.length ? v[0] : null;
|
|
531
|
+
});
|
|
532
|
+
case "last":
|
|
533
|
+
return perValue((v) => {
|
|
534
|
+
if (!Array.isArray(v)) throw new JsonToolError(`last: expected array`);
|
|
535
|
+
return v.length ? v[v.length - 1] : null;
|
|
536
|
+
});
|
|
537
|
+
case "min":
|
|
538
|
+
return perValue((v) => {
|
|
539
|
+
if (!Array.isArray(v)) throw new JsonToolError(`min: expected array`);
|
|
540
|
+
if (!v.length) return null;
|
|
541
|
+
return [...v].sort(compareValues)[0];
|
|
542
|
+
});
|
|
543
|
+
case "max":
|
|
544
|
+
return perValue((v) => {
|
|
545
|
+
if (!Array.isArray(v)) throw new JsonToolError(`max: expected array`);
|
|
546
|
+
if (!v.length) return null;
|
|
547
|
+
return [...v].sort(compareValues)[v.length - 1];
|
|
548
|
+
});
|
|
549
|
+
case "sum":
|
|
550
|
+
return perValue((v) => {
|
|
551
|
+
if (!Array.isArray(v)) throw new JsonToolError(`sum: expected array`);
|
|
552
|
+
return asNumbers(v, "sum").reduce((a, b) => a + b, 0);
|
|
553
|
+
});
|
|
554
|
+
case "avg":
|
|
555
|
+
return perValue((v) => {
|
|
556
|
+
if (!Array.isArray(v)) throw new JsonToolError(`avg: expected array`);
|
|
557
|
+
const nums = asNumbers(v, "avg");
|
|
558
|
+
return nums.length ? nums.reduce((a, b) => a + b, 0) / nums.length : null;
|
|
559
|
+
});
|
|
560
|
+
case "add":
|
|
561
|
+
return perValue((v) => {
|
|
562
|
+
if (!Array.isArray(v)) throw new JsonToolError(`add: expected array`);
|
|
563
|
+
if (!v.length) return null;
|
|
564
|
+
if (v.every((x) => typeof x === "number")) return v.reduce((a, b) => a + b, 0);
|
|
565
|
+
if (v.every((x) => typeof x === "string")) return v.join("");
|
|
566
|
+
if (v.every((x) => Array.isArray(x))) return [].concat(...v);
|
|
567
|
+
throw new JsonToolError("add: array must be all numbers, all strings, or all arrays");
|
|
568
|
+
});
|
|
569
|
+
case "map": {
|
|
570
|
+
const inner = stage.arg;
|
|
571
|
+
return perValue((v) => {
|
|
572
|
+
if (!Array.isArray(v)) throw new JsonToolError(`map: expected array, found ${typeOf(v)}`);
|
|
573
|
+
return v.map((el) => firstOf(evalPipeline(inner, [el])));
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
case "sort_by": {
|
|
577
|
+
const inner = stage.arg;
|
|
578
|
+
return perValue((v) => {
|
|
579
|
+
if (!Array.isArray(v)) throw new JsonToolError(`sort_by: expected array, found ${typeOf(v)}`);
|
|
580
|
+
return [...v].sort((a, b) => compareValues(firstOf(evalPipeline(inner, [a])), firstOf(evalPipeline(inner, [b]))));
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
case "group_by": {
|
|
584
|
+
const inner = stage.arg;
|
|
585
|
+
return perValue((v) => {
|
|
586
|
+
if (!Array.isArray(v)) throw new JsonToolError(`group_by: expected array, found ${typeOf(v)}`);
|
|
587
|
+
const withKey = v.map((el) => ({ el, key: firstOf(evalPipeline(inner, [el])) }));
|
|
588
|
+
withKey.sort((a, b) => compareValues(a.key, b.key));
|
|
589
|
+
const groups = [];
|
|
590
|
+
let curKey = /* @__PURE__ */ Symbol();
|
|
591
|
+
for (const { el, key } of withKey) {
|
|
592
|
+
if (groups.length === 0 || !deepEqual(curKey, key)) {
|
|
593
|
+
groups.push([]);
|
|
594
|
+
curKey = key;
|
|
595
|
+
}
|
|
596
|
+
groups[groups.length - 1].push(el);
|
|
597
|
+
}
|
|
598
|
+
return groups;
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
default:
|
|
602
|
+
throw new JsonToolError(`Unknown query function "${name}"`);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
function truthy(v) {
|
|
606
|
+
return v !== null && v !== void 0 && v !== false;
|
|
607
|
+
}
|
|
608
|
+
function evalCond(cond, value) {
|
|
609
|
+
switch (cond.kind) {
|
|
610
|
+
case "or":
|
|
611
|
+
return evalCond(cond.l, value) || evalCond(cond.r, value);
|
|
612
|
+
case "and":
|
|
613
|
+
return evalCond(cond.l, value) && evalCond(cond.r, value);
|
|
614
|
+
case "not":
|
|
615
|
+
return !evalCond(cond.c, value);
|
|
616
|
+
case "truthy":
|
|
617
|
+
return truthy(firstOf(evalPipeline(cond.expr, [value])));
|
|
618
|
+
case "cmp": {
|
|
619
|
+
const left = firstOf(evalPipeline(cond.left, [value]));
|
|
620
|
+
const right = cond.right.value;
|
|
621
|
+
switch (cond.op) {
|
|
622
|
+
case "==":
|
|
623
|
+
return deepEqual(left, right);
|
|
624
|
+
case "!=":
|
|
625
|
+
return !deepEqual(left, right);
|
|
626
|
+
case ">":
|
|
627
|
+
return compareValues(left, right) > 0;
|
|
628
|
+
case "<":
|
|
629
|
+
return compareValues(left, right) < 0;
|
|
630
|
+
case ">=":
|
|
631
|
+
return compareValues(left, right) >= 0;
|
|
632
|
+
case "<=":
|
|
633
|
+
return compareValues(left, right) <= 0;
|
|
634
|
+
default:
|
|
635
|
+
throw new JsonToolError(`Unknown operator "${cond.op}"`);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
function evalPipeline(pipe, inputs) {
|
|
641
|
+
let cur = inputs;
|
|
642
|
+
for (const stage of pipe.stages) {
|
|
643
|
+
if (stage.kind === "path") cur = evalPath(stage.steps, cur);
|
|
644
|
+
else if (stage.kind === "select") cur = cur.filter((v) => evalCond(stage.cond, v));
|
|
645
|
+
else cur = evalFunc(stage, cur);
|
|
646
|
+
}
|
|
647
|
+
return cur;
|
|
648
|
+
}
|
|
649
|
+
function firstOf(arr) {
|
|
650
|
+
return arr.length ? arr[0] : null;
|
|
651
|
+
}
|
|
652
|
+
function compileQuery(expr) {
|
|
653
|
+
const toks = tokenize(expr);
|
|
654
|
+
if (toks.length === 0) return (d) => [d];
|
|
655
|
+
const ast = new Parser(toks).parsePipeline();
|
|
656
|
+
return (data) => evalPipeline(ast, [data]);
|
|
657
|
+
}
|
|
658
|
+
function query(data, expr) {
|
|
659
|
+
const results = compileQuery(expr)(data);
|
|
660
|
+
const clean = results.map((v) => v === void 0 ? null : v);
|
|
661
|
+
return clean.length === 1 ? clean[0] : clean;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// src/yaml.ts
|
|
665
|
+
function splitLines(src) {
|
|
666
|
+
const out2 = [];
|
|
667
|
+
const rawLines = src.split(/\r?\n/);
|
|
668
|
+
for (let i = 0; i < rawLines.length; i++) {
|
|
669
|
+
const raw = rawLines[i];
|
|
670
|
+
if (/^\s*$/.test(raw)) continue;
|
|
671
|
+
if (/^\s*#/.test(raw)) continue;
|
|
672
|
+
const trimmedRight = raw.replace(/\s+$/, "");
|
|
673
|
+
if (trimmedRight === "---" || trimmedRight === "...") continue;
|
|
674
|
+
const indent = raw.length - raw.replace(/^\s+/, "").length;
|
|
675
|
+
out2.push({ indent, content: raw.trim(), raw, n: i + 1 });
|
|
676
|
+
}
|
|
677
|
+
return out2;
|
|
678
|
+
}
|
|
679
|
+
function stripComment(s) {
|
|
680
|
+
let inS = false, inD = false;
|
|
681
|
+
for (let i = 0; i < s.length; i++) {
|
|
682
|
+
const ch = s[i];
|
|
683
|
+
if (ch === "'" && !inD) inS = !inS;
|
|
684
|
+
else if (ch === '"' && !inS) inD = !inD;
|
|
685
|
+
else if (ch === "#" && !inS && !inD && (i === 0 || s[i - 1] === " " || s[i - 1] === " ")) {
|
|
686
|
+
return s.slice(0, i).replace(/\s+$/, "");
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
return s;
|
|
690
|
+
}
|
|
691
|
+
function parseScalar(raw) {
|
|
692
|
+
const s = raw.trim();
|
|
693
|
+
if (s === "" || s === "~" || s === "null" || s === "Null" || s === "NULL") return null;
|
|
694
|
+
if (s === "true" || s === "True" || s === "TRUE") return true;
|
|
695
|
+
if (s === "false" || s === "False" || s === "FALSE") return false;
|
|
696
|
+
if (s.length >= 2 && s[0] === '"' && s[s.length - 1] === '"') {
|
|
697
|
+
return s.slice(1, -1).replace(/\\(["\\/ntr])/g, (_m, c2) => c2 === "n" ? "\n" : c2 === "t" ? " " : c2 === "r" ? "\r" : c2);
|
|
698
|
+
}
|
|
699
|
+
if (s.length >= 2 && s[0] === "'" && s[s.length - 1] === "'") {
|
|
700
|
+
return s.slice(1, -1).replace(/''/g, "'");
|
|
701
|
+
}
|
|
702
|
+
if (s[0] === "[" || s[0] === "{") return parseFlow(s);
|
|
703
|
+
if (/^[-+]?(0|[1-9][0-9]*)$/.test(s)) return parseInt(s, 10);
|
|
704
|
+
if (/^[-+]?(\d+\.?\d*|\.\d+)([eE][-+]?\d+)?$/.test(s) && /\d/.test(s)) {
|
|
705
|
+
const num = Number(s);
|
|
706
|
+
if (!Number.isNaN(num)) return num;
|
|
707
|
+
}
|
|
708
|
+
return s;
|
|
709
|
+
}
|
|
710
|
+
function parseFlow(s) {
|
|
711
|
+
let i = 0;
|
|
712
|
+
const parseValue2 = () => {
|
|
713
|
+
skipWs();
|
|
714
|
+
const ch = s[i];
|
|
715
|
+
if (ch === "[") return parseArr();
|
|
716
|
+
if (ch === "{") return parseObj();
|
|
717
|
+
return parseFlowScalar();
|
|
718
|
+
};
|
|
719
|
+
const skipWs = () => {
|
|
720
|
+
while (i < s.length && /\s/.test(s[i])) i++;
|
|
721
|
+
};
|
|
722
|
+
const parseArr = () => {
|
|
723
|
+
i++;
|
|
724
|
+
const arr = [];
|
|
725
|
+
skipWs();
|
|
726
|
+
if (s[i] === "]") {
|
|
727
|
+
i++;
|
|
728
|
+
return arr;
|
|
729
|
+
}
|
|
730
|
+
while (i < s.length) {
|
|
731
|
+
arr.push(parseValue2());
|
|
732
|
+
skipWs();
|
|
733
|
+
if (s[i] === ",") {
|
|
734
|
+
i++;
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
if (s[i] === "]") {
|
|
738
|
+
i++;
|
|
739
|
+
break;
|
|
740
|
+
}
|
|
741
|
+
throw new JsonToolError(`YAML: malformed flow sequence near "${s.slice(i)}"`);
|
|
742
|
+
}
|
|
743
|
+
return arr;
|
|
744
|
+
};
|
|
745
|
+
const parseObj = () => {
|
|
746
|
+
i++;
|
|
747
|
+
const obj = {};
|
|
748
|
+
skipWs();
|
|
749
|
+
if (s[i] === "}") {
|
|
750
|
+
i++;
|
|
751
|
+
return obj;
|
|
752
|
+
}
|
|
753
|
+
while (i < s.length) {
|
|
754
|
+
skipWs();
|
|
755
|
+
const key = parseFlowKey();
|
|
756
|
+
skipWs();
|
|
757
|
+
if (s[i] !== ":") throw new JsonToolError(`YAML: expected ':' in flow map near "${s.slice(i)}"`);
|
|
758
|
+
i++;
|
|
759
|
+
const val = parseValue2();
|
|
760
|
+
if (!isForbiddenKey(key)) safeSet(obj, key, val);
|
|
761
|
+
skipWs();
|
|
762
|
+
if (s[i] === ",") {
|
|
763
|
+
i++;
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
766
|
+
if (s[i] === "}") {
|
|
767
|
+
i++;
|
|
768
|
+
break;
|
|
769
|
+
}
|
|
770
|
+
throw new JsonToolError(`YAML: malformed flow map near "${s.slice(i)}"`);
|
|
771
|
+
}
|
|
772
|
+
return obj;
|
|
773
|
+
};
|
|
774
|
+
const parseFlowKey = () => {
|
|
775
|
+
skipWs();
|
|
776
|
+
if (s[i] === '"' || s[i] === "'") {
|
|
777
|
+
const v = parseFlowScalar();
|
|
778
|
+
return String(v);
|
|
779
|
+
}
|
|
780
|
+
let j = i;
|
|
781
|
+
while (j < s.length && s[j] !== ":" && s[j] !== "," && s[j] !== "}") j++;
|
|
782
|
+
const k = s.slice(i, j).trim();
|
|
783
|
+
i = j;
|
|
784
|
+
return k;
|
|
785
|
+
};
|
|
786
|
+
const parseFlowScalar = () => {
|
|
787
|
+
skipWs();
|
|
788
|
+
if (s[i] === '"' || s[i] === "'") {
|
|
789
|
+
const q = s[i];
|
|
790
|
+
let j2 = i + 1;
|
|
791
|
+
let str = "";
|
|
792
|
+
while (j2 < s.length && s[j2] !== q) {
|
|
793
|
+
if (q === '"' && s[j2] === "\\") {
|
|
794
|
+
str += s[j2 + 1] === "n" ? "\n" : s[j2 + 1];
|
|
795
|
+
j2 += 2;
|
|
796
|
+
} else {
|
|
797
|
+
str += s[j2];
|
|
798
|
+
j2++;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
i = j2 + 1;
|
|
802
|
+
return str;
|
|
803
|
+
}
|
|
804
|
+
let j = i;
|
|
805
|
+
while (j < s.length && !",]}".includes(s[j])) j++;
|
|
806
|
+
const tok = s.slice(i, j).trim();
|
|
807
|
+
i = j;
|
|
808
|
+
return parseScalar(tok);
|
|
809
|
+
};
|
|
810
|
+
const result = parseValue2();
|
|
811
|
+
return result;
|
|
812
|
+
}
|
|
813
|
+
function parseBlock(lines, start, minIndent) {
|
|
814
|
+
const first = lines[start];
|
|
815
|
+
const indent = first.indent;
|
|
816
|
+
if (/^-(\s|$)/.test(first.content)) {
|
|
817
|
+
const arr = [];
|
|
818
|
+
let i2 = start;
|
|
819
|
+
while (i2 < lines.length && lines[i2].indent === indent && /^-(\s|$)/.test(lines[i2].content)) {
|
|
820
|
+
const line = lines[i2];
|
|
821
|
+
const after = line.content.slice(1).trim();
|
|
822
|
+
if (after === "") {
|
|
823
|
+
const inner = parseBlock(lines, i2 + 1, indent + 1);
|
|
824
|
+
arr.push(inner.value);
|
|
825
|
+
i2 = inner.next;
|
|
826
|
+
} else if (/^[^:\s][^:]*:(\s|$)/.test(after) || isInlineMapStart(after)) {
|
|
827
|
+
const synthetic = [{ indent: indent + 2, content: after, raw: line.raw, n: line.n }];
|
|
828
|
+
let j = i2 + 1;
|
|
829
|
+
while (j < lines.length && lines[j].indent > indent) {
|
|
830
|
+
synthetic.push(lines[j]);
|
|
831
|
+
j++;
|
|
832
|
+
}
|
|
833
|
+
const inner = parseBlock(synthetic, 0, indent + 2);
|
|
834
|
+
arr.push(inner.value);
|
|
835
|
+
i2 = j;
|
|
836
|
+
} else {
|
|
837
|
+
arr.push(parseScalar(stripComment(after)));
|
|
838
|
+
i2++;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
return { value: arr, next: i2 };
|
|
842
|
+
}
|
|
843
|
+
const obj = {};
|
|
844
|
+
let i = start;
|
|
845
|
+
while (i < lines.length && lines[i].indent === indent) {
|
|
846
|
+
const line = lines[i];
|
|
847
|
+
if (/^-(\s|$)/.test(line.content)) break;
|
|
848
|
+
const { key, rest } = splitKey(line.content, line.n);
|
|
849
|
+
const restClean = stripComment(rest);
|
|
850
|
+
if (restClean === "") {
|
|
851
|
+
const child = lines[i + 1];
|
|
852
|
+
if (child && child.indent > indent) {
|
|
853
|
+
const inner = parseBlock(lines, i + 1, indent + 1);
|
|
854
|
+
if (!isForbiddenKey(key)) safeSet(obj, key, inner.value);
|
|
855
|
+
i = inner.next;
|
|
856
|
+
} else {
|
|
857
|
+
if (!isForbiddenKey(key)) safeSet(obj, key, null);
|
|
858
|
+
i++;
|
|
859
|
+
}
|
|
860
|
+
} else if (restClean === "|" || restClean === ">" || /^[|>][-+]?$/.test(restClean)) {
|
|
861
|
+
const { text, next } = readBlockScalar(lines, i + 1, indent, restClean);
|
|
862
|
+
if (!isForbiddenKey(key)) safeSet(obj, key, text);
|
|
863
|
+
i = next;
|
|
864
|
+
} else {
|
|
865
|
+
if (!isForbiddenKey(key)) safeSet(obj, key, parseScalar(restClean));
|
|
866
|
+
i++;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
void minIndent;
|
|
870
|
+
return { value: obj, next: i };
|
|
871
|
+
}
|
|
872
|
+
function isInlineMapStart(s) {
|
|
873
|
+
return /^[^:]+:\s/.test(s);
|
|
874
|
+
}
|
|
875
|
+
function splitKey(content, n) {
|
|
876
|
+
let inS = false, inD = false;
|
|
877
|
+
for (let i = 0; i < content.length; i++) {
|
|
878
|
+
const ch = content[i];
|
|
879
|
+
if (ch === "'" && !inD) inS = !inS;
|
|
880
|
+
else if (ch === '"' && !inS) inD = !inD;
|
|
881
|
+
else if (ch === ":" && !inS && !inD && (i + 1 >= content.length || content[i + 1] === " ")) {
|
|
882
|
+
let key = content.slice(0, i).trim();
|
|
883
|
+
if (key.length >= 2 && (key[0] === '"' && key.endsWith('"') || key[0] === "'" && key.endsWith("'"))) {
|
|
884
|
+
key = key.slice(1, -1);
|
|
885
|
+
}
|
|
886
|
+
return { key, rest: content.slice(i + 1).trim() };
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
throw new JsonToolError(`YAML: expected "key: value" mapping at line ${n}: "${content}"`);
|
|
890
|
+
}
|
|
891
|
+
function readBlockScalar(lines, start, parentIndent, marker) {
|
|
892
|
+
const folded = marker[0] === ">";
|
|
893
|
+
const chomp = marker.includes("-") ? "strip" : marker.includes("+") ? "keep" : "clip";
|
|
894
|
+
const body = [];
|
|
895
|
+
let i = start;
|
|
896
|
+
let blockIndent = -1;
|
|
897
|
+
while (i < lines.length && lines[i].indent > parentIndent) {
|
|
898
|
+
if (blockIndent < 0) blockIndent = lines[i].indent;
|
|
899
|
+
body.push(lines[i].raw.slice(blockIndent));
|
|
900
|
+
i++;
|
|
901
|
+
}
|
|
902
|
+
let text = folded ? body.join(" ") : body.join("\n");
|
|
903
|
+
if (chomp === "strip") text = text.replace(/\n+$/, "");
|
|
904
|
+
else if (chomp === "clip") text = text.replace(/\n+$/, "") + (body.length ? "\n" : "");
|
|
905
|
+
return { text, next: i };
|
|
906
|
+
}
|
|
907
|
+
function parseYaml(src) {
|
|
908
|
+
const lines = splitLines(src);
|
|
909
|
+
if (lines.length === 0) return null;
|
|
910
|
+
if (lines.length === 1 && !/^-(\s|$)/.test(lines[0].content) && !/:\s|:$/.test(lines[0].content)) {
|
|
911
|
+
return parseScalar(stripComment(lines[0].content));
|
|
912
|
+
}
|
|
913
|
+
const { value } = parseBlock(lines, 0, 0);
|
|
914
|
+
return value;
|
|
915
|
+
}
|
|
916
|
+
function needsQuote(s) {
|
|
917
|
+
if (s === "") return true;
|
|
918
|
+
if (/^(true|false|null|~|yes|no|on|off)$/i.test(s)) return true;
|
|
919
|
+
if (/^[-+]?(\d+\.?\d*|\.\d+)([eE][-+]?\d+)?$/.test(s)) return true;
|
|
920
|
+
if (/^[\s]|[\s]$/.test(s)) return true;
|
|
921
|
+
if (/[:#\[\]{}&*!|>'"%@`,]/.test(s)) return true;
|
|
922
|
+
if (/^[?-]/.test(s)) return true;
|
|
923
|
+
return false;
|
|
924
|
+
}
|
|
925
|
+
function writeScalar(v) {
|
|
926
|
+
if (v === null) return "null";
|
|
927
|
+
if (typeof v === "boolean") return v ? "true" : "false";
|
|
928
|
+
if (typeof v === "number") return Number.isFinite(v) ? String(v) : "null";
|
|
929
|
+
const s = String(v);
|
|
930
|
+
if (needsQuote(s)) return JSON.stringify(s);
|
|
931
|
+
return s;
|
|
932
|
+
}
|
|
933
|
+
function writeYamlNode(v, indent) {
|
|
934
|
+
const pad = " ".repeat(indent);
|
|
935
|
+
if (Array.isArray(v)) {
|
|
936
|
+
if (v.length === 0) return pad + "[]";
|
|
937
|
+
return v.map((item) => {
|
|
938
|
+
if (item !== null && typeof item === "object") {
|
|
939
|
+
const inner = writeYamlNode(item, indent + 1);
|
|
940
|
+
return pad + "-\n" + inner;
|
|
941
|
+
}
|
|
942
|
+
return pad + "- " + writeScalar(item);
|
|
943
|
+
}).join("\n");
|
|
944
|
+
}
|
|
945
|
+
if (v !== null && typeof v === "object") {
|
|
946
|
+
const keys = Object.keys(v);
|
|
947
|
+
if (keys.length === 0) return pad + "{}";
|
|
948
|
+
return keys.map((k) => {
|
|
949
|
+
const val = v[k];
|
|
950
|
+
const keyStr = needsQuote(k) ? JSON.stringify(k) : k;
|
|
951
|
+
if (val !== null && typeof val === "object" && (Array.isArray(val) ? val.length : Object.keys(val).length)) {
|
|
952
|
+
const nested = writeYamlNode(val, indent + 1);
|
|
953
|
+
return pad + keyStr + ":\n" + nested;
|
|
954
|
+
}
|
|
955
|
+
return pad + keyStr + ": " + writeScalar(val);
|
|
956
|
+
}).join("\n");
|
|
957
|
+
}
|
|
958
|
+
return pad + writeScalar(v);
|
|
959
|
+
}
|
|
960
|
+
function stringifyYaml(value) {
|
|
961
|
+
if (value === null || typeof value !== "object") return writeScalar(value) + "\n";
|
|
962
|
+
return writeYamlNode(value, 0) + "\n";
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// src/toml.ts
|
|
966
|
+
function parseValue(raw) {
|
|
967
|
+
const s = raw.trim();
|
|
968
|
+
if (s === "true") return true;
|
|
969
|
+
if (s === "false") return false;
|
|
970
|
+
if (s[0] === '"') return parseBasicString(s);
|
|
971
|
+
if (s[0] === "'") return parseLiteralString(s);
|
|
972
|
+
if (s[0] === "[") return parseArray(s);
|
|
973
|
+
if (s[0] === "{") return parseInline(s);
|
|
974
|
+
const numStr = s.replace(/_/g, "");
|
|
975
|
+
if (/^[-+]?(0|[1-9]\d*)$/.test(numStr)) return parseInt(numStr, 10);
|
|
976
|
+
if (/^[-+]?(\d+\.?\d*|\.\d+)([eE][-+]?\d+)?$/.test(numStr) && /\d/.test(numStr)) {
|
|
977
|
+
const n = Number(numStr);
|
|
978
|
+
if (!Number.isNaN(n)) return n;
|
|
979
|
+
}
|
|
980
|
+
if (/^0x[0-9a-fA-F]+$/.test(numStr)) return parseInt(numStr, 16);
|
|
981
|
+
return s;
|
|
982
|
+
}
|
|
983
|
+
function parseBasicString(s) {
|
|
984
|
+
if (s.length < 2 || s[s.length - 1] !== '"') throw new JsonToolError(`TOML: unterminated string ${s}`);
|
|
985
|
+
return s.slice(1, -1).replace(/\\(["\\/nrt]|u[0-9a-fA-F]{4})/g, (_m, c2) => {
|
|
986
|
+
if (c2[0] === "u") return String.fromCharCode(parseInt(c2.slice(1), 16));
|
|
987
|
+
return c2 === "n" ? "\n" : c2 === "t" ? " " : c2 === "r" ? "\r" : c2;
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
function parseLiteralString(s) {
|
|
991
|
+
if (s.length < 2 || s[s.length - 1] !== "'") throw new JsonToolError(`TOML: unterminated literal string ${s}`);
|
|
992
|
+
return s.slice(1, -1);
|
|
993
|
+
}
|
|
994
|
+
function splitTop(s, open, close) {
|
|
995
|
+
const inner = s.slice(1, -1);
|
|
996
|
+
const items = [];
|
|
997
|
+
let depth = 0, inD = false, inSq = false, cur = "";
|
|
998
|
+
for (let i = 0; i < inner.length; i++) {
|
|
999
|
+
const ch = inner[i];
|
|
1000
|
+
if (inD) {
|
|
1001
|
+
cur += ch;
|
|
1002
|
+
if (ch === '"' && inner[i - 1] !== "\\") inD = false;
|
|
1003
|
+
continue;
|
|
1004
|
+
}
|
|
1005
|
+
if (inSq) {
|
|
1006
|
+
cur += ch;
|
|
1007
|
+
if (ch === "'") inSq = false;
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
1010
|
+
if (ch === '"') {
|
|
1011
|
+
inD = true;
|
|
1012
|
+
cur += ch;
|
|
1013
|
+
continue;
|
|
1014
|
+
}
|
|
1015
|
+
if (ch === "'") {
|
|
1016
|
+
inSq = true;
|
|
1017
|
+
cur += ch;
|
|
1018
|
+
continue;
|
|
1019
|
+
}
|
|
1020
|
+
if (ch === "[" || ch === "{") depth++;
|
|
1021
|
+
if (ch === "]" || ch === "}") depth--;
|
|
1022
|
+
if (ch === "," && depth === 0) {
|
|
1023
|
+
items.push(cur);
|
|
1024
|
+
cur = "";
|
|
1025
|
+
continue;
|
|
1026
|
+
}
|
|
1027
|
+
cur += ch;
|
|
1028
|
+
}
|
|
1029
|
+
if (cur.trim() !== "") items.push(cur);
|
|
1030
|
+
void open;
|
|
1031
|
+
void close;
|
|
1032
|
+
return items;
|
|
1033
|
+
}
|
|
1034
|
+
function parseArray(s) {
|
|
1035
|
+
const items = splitTop(s, "[", "]");
|
|
1036
|
+
return items.map((it) => parseValue(it.trim()));
|
|
1037
|
+
}
|
|
1038
|
+
function parseInline(s) {
|
|
1039
|
+
const items = splitTop(s, "{", "}");
|
|
1040
|
+
const obj = {};
|
|
1041
|
+
for (const it of items) {
|
|
1042
|
+
const t = it.trim();
|
|
1043
|
+
if (t === "") continue;
|
|
1044
|
+
const eq = findEquals(t);
|
|
1045
|
+
const key = t.slice(0, eq).trim();
|
|
1046
|
+
const val = parseValue(t.slice(eq + 1).trim());
|
|
1047
|
+
assignDotted(obj, splitKeyPath(key), val);
|
|
1048
|
+
}
|
|
1049
|
+
return obj;
|
|
1050
|
+
}
|
|
1051
|
+
function findEquals(line) {
|
|
1052
|
+
let inD = false, inSq = false;
|
|
1053
|
+
for (let i = 0; i < line.length; i++) {
|
|
1054
|
+
const ch = line[i];
|
|
1055
|
+
if (inD) {
|
|
1056
|
+
if (ch === '"' && line[i - 1] !== "\\") inD = false;
|
|
1057
|
+
continue;
|
|
1058
|
+
}
|
|
1059
|
+
if (inSq) {
|
|
1060
|
+
if (ch === "'") inSq = false;
|
|
1061
|
+
continue;
|
|
1062
|
+
}
|
|
1063
|
+
if (ch === '"') {
|
|
1064
|
+
inD = true;
|
|
1065
|
+
continue;
|
|
1066
|
+
}
|
|
1067
|
+
if (ch === "'") {
|
|
1068
|
+
inSq = true;
|
|
1069
|
+
continue;
|
|
1070
|
+
}
|
|
1071
|
+
if (ch === "=") return i;
|
|
1072
|
+
}
|
|
1073
|
+
return -1;
|
|
1074
|
+
}
|
|
1075
|
+
function splitKeyPath(key) {
|
|
1076
|
+
const parts = [];
|
|
1077
|
+
let cur = "", inD = false, inSq = false;
|
|
1078
|
+
for (let i = 0; i < key.length; i++) {
|
|
1079
|
+
const ch = key[i];
|
|
1080
|
+
if (inD) {
|
|
1081
|
+
if (ch === '"') {
|
|
1082
|
+
inD = false;
|
|
1083
|
+
} else cur += ch;
|
|
1084
|
+
continue;
|
|
1085
|
+
}
|
|
1086
|
+
if (inSq) {
|
|
1087
|
+
if (ch === "'") {
|
|
1088
|
+
inSq = false;
|
|
1089
|
+
} else cur += ch;
|
|
1090
|
+
continue;
|
|
1091
|
+
}
|
|
1092
|
+
if (ch === '"') {
|
|
1093
|
+
inD = true;
|
|
1094
|
+
continue;
|
|
1095
|
+
}
|
|
1096
|
+
if (ch === "'") {
|
|
1097
|
+
inSq = true;
|
|
1098
|
+
continue;
|
|
1099
|
+
}
|
|
1100
|
+
if (ch === ".") {
|
|
1101
|
+
parts.push(cur.trim());
|
|
1102
|
+
cur = "";
|
|
1103
|
+
continue;
|
|
1104
|
+
}
|
|
1105
|
+
cur += ch;
|
|
1106
|
+
}
|
|
1107
|
+
parts.push(cur.trim());
|
|
1108
|
+
return parts.filter((p) => p !== "");
|
|
1109
|
+
}
|
|
1110
|
+
function assignDotted(root, path, value) {
|
|
1111
|
+
let cur = root;
|
|
1112
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
1113
|
+
const k = path[i];
|
|
1114
|
+
if (isForbiddenKey(k)) return;
|
|
1115
|
+
if (!isPlainObject(cur[k])) safeSet(cur, k, {});
|
|
1116
|
+
cur = cur[k];
|
|
1117
|
+
}
|
|
1118
|
+
const last = path[path.length - 1];
|
|
1119
|
+
if (!isForbiddenKey(last)) safeSet(cur, last, value);
|
|
1120
|
+
}
|
|
1121
|
+
function ensureTable(root, path) {
|
|
1122
|
+
let cur = root;
|
|
1123
|
+
for (const k of path) {
|
|
1124
|
+
if (isForbiddenKey(k)) throw new JsonToolError(`TOML: forbidden key "${k}"`);
|
|
1125
|
+
const existing = cur[k];
|
|
1126
|
+
if (Array.isArray(existing)) {
|
|
1127
|
+
const arr = existing;
|
|
1128
|
+
cur = arr[arr.length - 1];
|
|
1129
|
+
} else if (isPlainObject(existing)) {
|
|
1130
|
+
cur = existing;
|
|
1131
|
+
} else {
|
|
1132
|
+
safeSet(cur, k, {});
|
|
1133
|
+
cur = cur[k];
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
return cur;
|
|
1137
|
+
}
|
|
1138
|
+
function ensureArrayTable(root, path) {
|
|
1139
|
+
const parent = ensureTable(root, path.slice(0, -1));
|
|
1140
|
+
const last = path[path.length - 1];
|
|
1141
|
+
if (isForbiddenKey(last)) throw new JsonToolError(`TOML: forbidden key "${last}"`);
|
|
1142
|
+
if (!Array.isArray(parent[last])) safeSet(parent, last, []);
|
|
1143
|
+
const arr = parent[last];
|
|
1144
|
+
const tbl = {};
|
|
1145
|
+
arr.push(tbl);
|
|
1146
|
+
return tbl;
|
|
1147
|
+
}
|
|
1148
|
+
function parseToml(src) {
|
|
1149
|
+
const root = {};
|
|
1150
|
+
let cur = root;
|
|
1151
|
+
const lines = src.split(/\r?\n/);
|
|
1152
|
+
for (let li = 0; li < lines.length; li++) {
|
|
1153
|
+
let line = lines[li];
|
|
1154
|
+
line = stripComment2(line).trim();
|
|
1155
|
+
if (line === "") continue;
|
|
1156
|
+
if (line.startsWith("[[") && line.endsWith("]]")) {
|
|
1157
|
+
const path = splitKeyPath(line.slice(2, -2).trim());
|
|
1158
|
+
cur = ensureArrayTable(root, path);
|
|
1159
|
+
continue;
|
|
1160
|
+
}
|
|
1161
|
+
if (line.startsWith("[") && line.endsWith("]")) {
|
|
1162
|
+
const path = splitKeyPath(line.slice(1, -1).trim());
|
|
1163
|
+
cur = ensureTable(root, path);
|
|
1164
|
+
continue;
|
|
1165
|
+
}
|
|
1166
|
+
const eq = findEquals(line);
|
|
1167
|
+
if (eq < 0) throw new JsonToolError(`TOML: expected "key = value" at line ${li + 1}: "${line}"`);
|
|
1168
|
+
const key = line.slice(0, eq).trim();
|
|
1169
|
+
const value = parseValue(line.slice(eq + 1).trim());
|
|
1170
|
+
assignDotted(cur, splitKeyPath(key), value);
|
|
1171
|
+
}
|
|
1172
|
+
return root;
|
|
1173
|
+
}
|
|
1174
|
+
function stripComment2(line) {
|
|
1175
|
+
let inD = false, inSq = false;
|
|
1176
|
+
for (let i = 0; i < line.length; i++) {
|
|
1177
|
+
const ch = line[i];
|
|
1178
|
+
if (inD) {
|
|
1179
|
+
if (ch === '"' && line[i - 1] !== "\\") inD = false;
|
|
1180
|
+
continue;
|
|
1181
|
+
}
|
|
1182
|
+
if (inSq) {
|
|
1183
|
+
if (ch === "'") inSq = false;
|
|
1184
|
+
continue;
|
|
1185
|
+
}
|
|
1186
|
+
if (ch === '"') {
|
|
1187
|
+
inD = true;
|
|
1188
|
+
continue;
|
|
1189
|
+
}
|
|
1190
|
+
if (ch === "'") {
|
|
1191
|
+
inSq = true;
|
|
1192
|
+
continue;
|
|
1193
|
+
}
|
|
1194
|
+
if (ch === "#") return line.slice(0, i);
|
|
1195
|
+
}
|
|
1196
|
+
return line;
|
|
1197
|
+
}
|
|
1198
|
+
function bareKey(k) {
|
|
1199
|
+
return /^[A-Za-z0-9_-]+$/.test(k) ? k : JSON.stringify(k);
|
|
1200
|
+
}
|
|
1201
|
+
function writeInlineValue(v) {
|
|
1202
|
+
if (v === null) return '""';
|
|
1203
|
+
if (typeof v === "boolean") return v ? "true" : "false";
|
|
1204
|
+
if (typeof v === "number") return Number.isFinite(v) ? String(v) : '"NaN"';
|
|
1205
|
+
if (typeof v === "string") return JSON.stringify(v);
|
|
1206
|
+
if (Array.isArray(v)) return "[" + v.map(writeInlineValue).join(", ") + "]";
|
|
1207
|
+
const entries = Object.keys(v).map((k) => `${bareKey(k)} = ${writeInlineValue(v[k])}`);
|
|
1208
|
+
return "{ " + entries.join(", ") + " }";
|
|
1209
|
+
}
|
|
1210
|
+
function isTableArray(v) {
|
|
1211
|
+
return Array.isArray(v) && v.length > 0 && v.every((x) => isPlainObject(x));
|
|
1212
|
+
}
|
|
1213
|
+
function writeToml(obj, prefix, out2) {
|
|
1214
|
+
const scalars = [];
|
|
1215
|
+
const tables = [];
|
|
1216
|
+
const tableArrays = [];
|
|
1217
|
+
for (const k of Object.keys(obj)) {
|
|
1218
|
+
const v = obj[k];
|
|
1219
|
+
if (isPlainObject(v)) tables.push([k, v]);
|
|
1220
|
+
else if (isTableArray(v)) tableArrays.push([k, v]);
|
|
1221
|
+
else scalars.push(`${bareKey(k)} = ${writeInlineValue(v)}`);
|
|
1222
|
+
}
|
|
1223
|
+
if (scalars.length) out2.push(scalars.join("\n"));
|
|
1224
|
+
for (const [k, v] of tables) {
|
|
1225
|
+
const path = [...prefix, k];
|
|
1226
|
+
out2.push(`
|
|
1227
|
+
[${path.map(bareKey).join(".")}]`);
|
|
1228
|
+
writeToml(v, path, out2);
|
|
1229
|
+
}
|
|
1230
|
+
for (const [k, arr] of tableArrays) {
|
|
1231
|
+
const path = [...prefix, k];
|
|
1232
|
+
for (const item of arr) {
|
|
1233
|
+
out2.push(`
|
|
1234
|
+
[[${path.map(bareKey).join(".")}]]`);
|
|
1235
|
+
writeToml(item, path, out2);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
function stringifyToml(value) {
|
|
1240
|
+
if (!isPlainObject(value)) {
|
|
1241
|
+
throw new JsonToolError("TOML output requires a top-level object/table");
|
|
1242
|
+
}
|
|
1243
|
+
const out2 = [];
|
|
1244
|
+
writeToml(value, [], out2);
|
|
1245
|
+
return out2.join("\n").replace(/^\n+/, "").replace(/\n{3,}/g, "\n\n") + "\n";
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
// src/csv.ts
|
|
1249
|
+
function coerce(raw) {
|
|
1250
|
+
if (raw === "") return "";
|
|
1251
|
+
if (raw === "true") return true;
|
|
1252
|
+
if (raw === "false") return false;
|
|
1253
|
+
if (raw === "null") return null;
|
|
1254
|
+
if (/^[-+]?(0|[1-9]\d*)$/.test(raw)) {
|
|
1255
|
+
const n = parseInt(raw, 10);
|
|
1256
|
+
if (String(n) === raw.replace(/^\+/, "")) return n;
|
|
1257
|
+
}
|
|
1258
|
+
if (/^[-+]?(\d+\.\d+|\.\d+|\d+)([eE][-+]?\d+)?$/.test(raw)) {
|
|
1259
|
+
const n = Number(raw);
|
|
1260
|
+
if (!Number.isNaN(n)) return n;
|
|
1261
|
+
}
|
|
1262
|
+
return raw;
|
|
1263
|
+
}
|
|
1264
|
+
function parseCsv(src, opts = {}) {
|
|
1265
|
+
const delim = opts.delimiter ?? ",";
|
|
1266
|
+
const parseTypes = opts.parseTypes ?? true;
|
|
1267
|
+
const rows = parseCsvRows(src, delim);
|
|
1268
|
+
if (rows.length === 0) return [];
|
|
1269
|
+
const header = rows[0];
|
|
1270
|
+
const out2 = [];
|
|
1271
|
+
for (let r = 1; r < rows.length; r++) {
|
|
1272
|
+
const row = rows[r];
|
|
1273
|
+
if (row.length === 1 && row[0] === "") continue;
|
|
1274
|
+
const obj = {};
|
|
1275
|
+
for (let c2 = 0; c2 < header.length; c2++) {
|
|
1276
|
+
const key = header[c2] ?? `col${c2}`;
|
|
1277
|
+
if (isForbiddenKey(key)) continue;
|
|
1278
|
+
const cell2 = row[c2] ?? "";
|
|
1279
|
+
safeSet(obj, key, parseTypes ? coerce(cell2) : cell2);
|
|
1280
|
+
}
|
|
1281
|
+
out2.push(obj);
|
|
1282
|
+
}
|
|
1283
|
+
return out2;
|
|
1284
|
+
}
|
|
1285
|
+
function parseCsvRows(src, delim) {
|
|
1286
|
+
const rows = [];
|
|
1287
|
+
let field = "";
|
|
1288
|
+
let row = [];
|
|
1289
|
+
let inQuotes = false;
|
|
1290
|
+
let i = 0;
|
|
1291
|
+
const n = src.length;
|
|
1292
|
+
if (src.charCodeAt(0) === 65279) i = 1;
|
|
1293
|
+
while (i < n) {
|
|
1294
|
+
const ch = src[i];
|
|
1295
|
+
if (inQuotes) {
|
|
1296
|
+
if (ch === '"') {
|
|
1297
|
+
if (src[i + 1] === '"') {
|
|
1298
|
+
field += '"';
|
|
1299
|
+
i += 2;
|
|
1300
|
+
continue;
|
|
1301
|
+
}
|
|
1302
|
+
inQuotes = false;
|
|
1303
|
+
i++;
|
|
1304
|
+
continue;
|
|
1305
|
+
}
|
|
1306
|
+
field += ch;
|
|
1307
|
+
i++;
|
|
1308
|
+
continue;
|
|
1309
|
+
}
|
|
1310
|
+
if (ch === '"') {
|
|
1311
|
+
inQuotes = true;
|
|
1312
|
+
i++;
|
|
1313
|
+
continue;
|
|
1314
|
+
}
|
|
1315
|
+
if (ch === delim) {
|
|
1316
|
+
row.push(field);
|
|
1317
|
+
field = "";
|
|
1318
|
+
i++;
|
|
1319
|
+
continue;
|
|
1320
|
+
}
|
|
1321
|
+
if (ch === "\r") {
|
|
1322
|
+
i++;
|
|
1323
|
+
continue;
|
|
1324
|
+
}
|
|
1325
|
+
if (ch === "\n") {
|
|
1326
|
+
row.push(field);
|
|
1327
|
+
rows.push(row);
|
|
1328
|
+
row = [];
|
|
1329
|
+
field = "";
|
|
1330
|
+
i++;
|
|
1331
|
+
continue;
|
|
1332
|
+
}
|
|
1333
|
+
field += ch;
|
|
1334
|
+
i++;
|
|
1335
|
+
}
|
|
1336
|
+
if (field !== "" || row.length > 0) {
|
|
1337
|
+
row.push(field);
|
|
1338
|
+
rows.push(row);
|
|
1339
|
+
}
|
|
1340
|
+
return rows;
|
|
1341
|
+
}
|
|
1342
|
+
function needsQuote2(s, delim) {
|
|
1343
|
+
return s.includes(delim) || s.includes('"') || s.includes("\n") || s.includes("\r");
|
|
1344
|
+
}
|
|
1345
|
+
function cell(v, delim) {
|
|
1346
|
+
let s;
|
|
1347
|
+
if (v === null || v === void 0) s = "";
|
|
1348
|
+
else if (typeof v === "object") s = JSON.stringify(v);
|
|
1349
|
+
else s = String(v);
|
|
1350
|
+
if (needsQuote2(s, delim)) return '"' + s.replace(/"/g, '""') + '"';
|
|
1351
|
+
return s;
|
|
1352
|
+
}
|
|
1353
|
+
function stringifyCsv(value, opts = {}) {
|
|
1354
|
+
const delim = opts.delimiter ?? ",";
|
|
1355
|
+
if (!Array.isArray(value)) {
|
|
1356
|
+
throw new JsonToolError("CSV output requires an array of objects");
|
|
1357
|
+
}
|
|
1358
|
+
const rows = value;
|
|
1359
|
+
let columns = opts.columns;
|
|
1360
|
+
if (!columns) {
|
|
1361
|
+
const seen = [];
|
|
1362
|
+
const set = /* @__PURE__ */ new Set();
|
|
1363
|
+
for (const r of rows) {
|
|
1364
|
+
if (isPlainObject(r)) {
|
|
1365
|
+
for (const k of Object.keys(r)) if (!set.has(k)) {
|
|
1366
|
+
set.add(k);
|
|
1367
|
+
seen.push(k);
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
columns = seen;
|
|
1372
|
+
}
|
|
1373
|
+
const lines = [];
|
|
1374
|
+
lines.push(columns.map((c2) => cell(c2, delim)).join(delim));
|
|
1375
|
+
for (const r of rows) {
|
|
1376
|
+
if (isPlainObject(r)) {
|
|
1377
|
+
lines.push(columns.map((c2) => cell(r[c2] ?? null, delim)).join(delim));
|
|
1378
|
+
} else {
|
|
1379
|
+
lines.push(cell(r, delim));
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
return lines.join("\n") + "\n";
|
|
1383
|
+
}
|
|
1384
|
+
function parseNdjson(src) {
|
|
1385
|
+
const out2 = [];
|
|
1386
|
+
const lines = src.split(/\r?\n/);
|
|
1387
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1388
|
+
const line = lines[i].trim();
|
|
1389
|
+
if (line === "") continue;
|
|
1390
|
+
try {
|
|
1391
|
+
out2.push(JSON.parse(line));
|
|
1392
|
+
} catch (err) {
|
|
1393
|
+
throw new JsonToolError(`NDJSON: invalid JSON on line ${i + 1}: ${err.message}`);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
return out2;
|
|
1397
|
+
}
|
|
1398
|
+
function stringifyNdjson(value) {
|
|
1399
|
+
const arr = Array.isArray(value) ? value : [value];
|
|
1400
|
+
return arr.map((v) => JSON.stringify(v)).join("\n") + "\n";
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
// src/convert.ts
|
|
1404
|
+
var EXT_MAP = {
|
|
1405
|
+
json: "json",
|
|
1406
|
+
json5: "json",
|
|
1407
|
+
yaml: "yaml",
|
|
1408
|
+
yml: "yaml",
|
|
1409
|
+
toml: "toml",
|
|
1410
|
+
csv: "csv",
|
|
1411
|
+
tsv: "csv",
|
|
1412
|
+
ndjson: "ndjson",
|
|
1413
|
+
jsonl: "ndjson"
|
|
1414
|
+
};
|
|
1415
|
+
function formatFromExt(filename) {
|
|
1416
|
+
const m = /\.([A-Za-z0-9]+)$/.exec(filename);
|
|
1417
|
+
if (!m) return void 0;
|
|
1418
|
+
return EXT_MAP[m[1].toLowerCase()];
|
|
1419
|
+
}
|
|
1420
|
+
function detectFormat(src) {
|
|
1421
|
+
const trimmed = src.trim();
|
|
1422
|
+
if (trimmed === "") return "json";
|
|
1423
|
+
const lines = trimmed.split(/\r?\n/).filter((l) => l.trim() !== "");
|
|
1424
|
+
if (lines.length > 1 && lines.every((l) => {
|
|
1425
|
+
const t = l.trim();
|
|
1426
|
+
return t.startsWith("{") && t.endsWith("}") || t.startsWith("[") && t.endsWith("]");
|
|
1427
|
+
})) {
|
|
1428
|
+
if (lines.every((l) => {
|
|
1429
|
+
try {
|
|
1430
|
+
JSON.parse(l);
|
|
1431
|
+
return true;
|
|
1432
|
+
} catch {
|
|
1433
|
+
return false;
|
|
1434
|
+
}
|
|
1435
|
+
})) return "ndjson";
|
|
1436
|
+
}
|
|
1437
|
+
if (trimmed[0] === "{" || trimmed[0] === "[") {
|
|
1438
|
+
try {
|
|
1439
|
+
JSON.parse(trimmed);
|
|
1440
|
+
return "json";
|
|
1441
|
+
} catch {
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
if (/^\s*\[\[?[^\]]+\]\]?\s*$/m.test(trimmed) && /^\s*[A-Za-z0-9_."'-]+\s*=/m.test(trimmed)) return "toml";
|
|
1445
|
+
const first = lines[0].trim();
|
|
1446
|
+
if (first.includes(",") && !first.includes(": ") && !/^[\[{]/.test(first) && lines.length >= 1) {
|
|
1447
|
+
if (!/^\s*-\s/.test(first)) return "csv";
|
|
1448
|
+
}
|
|
1449
|
+
if (/^\s*[A-Za-z0-9_."'-]+\s*=\s*/m.test(trimmed) && !/:\s/.test(trimmed)) return "toml";
|
|
1450
|
+
return "yaml";
|
|
1451
|
+
}
|
|
1452
|
+
function parseFormat(src, format) {
|
|
1453
|
+
switch (format) {
|
|
1454
|
+
case "json": {
|
|
1455
|
+
try {
|
|
1456
|
+
return sanitizeJson(JSON.parse(src === "" ? "null" : src));
|
|
1457
|
+
} catch (err) {
|
|
1458
|
+
throw new JsonToolError(`Invalid JSON: ${err.message}`);
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
case "yaml":
|
|
1462
|
+
return parseYaml(src);
|
|
1463
|
+
case "toml":
|
|
1464
|
+
return parseToml(src);
|
|
1465
|
+
case "csv":
|
|
1466
|
+
return parseCsv(src);
|
|
1467
|
+
case "ndjson":
|
|
1468
|
+
return parseNdjson(src);
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
function stringifyFormat(value, format, opts = {}) {
|
|
1472
|
+
switch (format) {
|
|
1473
|
+
case "json": {
|
|
1474
|
+
const space = opts.minify ? void 0 : opts.indent ?? 2;
|
|
1475
|
+
return JSON.stringify(value, null, space) + (opts.minify ? "" : "\n");
|
|
1476
|
+
}
|
|
1477
|
+
case "yaml":
|
|
1478
|
+
return stringifyYaml(value);
|
|
1479
|
+
case "toml":
|
|
1480
|
+
return stringifyToml(value);
|
|
1481
|
+
case "csv":
|
|
1482
|
+
return stringifyCsv(value);
|
|
1483
|
+
case "ndjson":
|
|
1484
|
+
return stringifyNdjson(value);
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
function sanitizeJson(v) {
|
|
1488
|
+
if (v === null || typeof v !== "object") return v;
|
|
1489
|
+
if (Array.isArray(v)) return v.map(sanitizeJson);
|
|
1490
|
+
const out2 = {};
|
|
1491
|
+
for (const k of Object.keys(v)) {
|
|
1492
|
+
if (isForbiddenKey(k)) continue;
|
|
1493
|
+
safeSet(out2, k, sanitizeJson(v[k]));
|
|
1494
|
+
}
|
|
1495
|
+
return out2;
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
// src/schema.ts
|
|
1499
|
+
var FORMATS = {
|
|
1500
|
+
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
|
|
1501
|
+
uri: /^[A-Za-z][A-Za-z0-9+.-]*:\/\/\S+$/,
|
|
1502
|
+
url: /^https?:\/\/\S+$/i,
|
|
1503
|
+
uuid: /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/,
|
|
1504
|
+
date: /^\d{4}-\d{2}-\d{2}$/,
|
|
1505
|
+
"date-time": /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:?\d{2})?$/,
|
|
1506
|
+
time: /^\d{2}:\d{2}:\d{2}(\.\d+)?$/,
|
|
1507
|
+
ipv4: /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/,
|
|
1508
|
+
hostname: /^(?=.{1,253}$)([A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/
|
|
1509
|
+
};
|
|
1510
|
+
var Validator = class _Validator {
|
|
1511
|
+
root;
|
|
1512
|
+
errors = [];
|
|
1513
|
+
constructor(root) {
|
|
1514
|
+
this.root = root;
|
|
1515
|
+
}
|
|
1516
|
+
validate(data, schema, path) {
|
|
1517
|
+
if (schema === true || schema === void 0) return;
|
|
1518
|
+
if (schema === false) {
|
|
1519
|
+
this.err(path, "schema is `false` \u2014 no value is valid here");
|
|
1520
|
+
return;
|
|
1521
|
+
}
|
|
1522
|
+
if (typeof schema !== "object") return;
|
|
1523
|
+
if (typeof schema["$ref"] === "string") {
|
|
1524
|
+
const resolved = this.resolveRef(schema["$ref"]);
|
|
1525
|
+
if (resolved === void 0) this.err(path, `cannot resolve $ref "${schema["$ref"]}"`);
|
|
1526
|
+
else this.validate(data, resolved, path);
|
|
1527
|
+
return;
|
|
1528
|
+
}
|
|
1529
|
+
this.checkType(data, schema, path);
|
|
1530
|
+
this.checkEnumConst(data, schema, path);
|
|
1531
|
+
this.checkNumber(data, schema, path);
|
|
1532
|
+
this.checkString(data, schema, path);
|
|
1533
|
+
this.checkArray(data, schema, path);
|
|
1534
|
+
this.checkObject(data, schema, path);
|
|
1535
|
+
this.checkCombinators(data, schema, path);
|
|
1536
|
+
}
|
|
1537
|
+
err(path, message) {
|
|
1538
|
+
this.errors.push({ path, message });
|
|
1539
|
+
}
|
|
1540
|
+
resolveRef(ref) {
|
|
1541
|
+
if (!ref.startsWith("#/")) return void 0;
|
|
1542
|
+
const parts = ref.slice(2).split("/").map((p) => p.replace(/~1/g, "/").replace(/~0/g, "~"));
|
|
1543
|
+
let cur = this.root;
|
|
1544
|
+
for (const p of parts) {
|
|
1545
|
+
if (cur && typeof cur === "object") cur = cur[p];
|
|
1546
|
+
else return void 0;
|
|
1547
|
+
}
|
|
1548
|
+
return cur;
|
|
1549
|
+
}
|
|
1550
|
+
checkType(data, schema, path) {
|
|
1551
|
+
const t = schema["type"];
|
|
1552
|
+
if (t === void 0) return;
|
|
1553
|
+
const types = Array.isArray(t) ? t : [t];
|
|
1554
|
+
const actual = typeOf(data);
|
|
1555
|
+
const ok = types.some((want) => {
|
|
1556
|
+
if (want === "integer") return actual === "number" && Number.isInteger(data);
|
|
1557
|
+
return want === actual;
|
|
1558
|
+
});
|
|
1559
|
+
if (!ok) this.err(path, `expected type ${types.join(" | ")}, got ${actual}`);
|
|
1560
|
+
}
|
|
1561
|
+
checkEnumConst(data, schema, path) {
|
|
1562
|
+
if ("const" in schema && !deepEqual(data, schema["const"])) {
|
|
1563
|
+
this.err(path, `must equal const ${JSON.stringify(schema["const"])}`);
|
|
1564
|
+
}
|
|
1565
|
+
if (Array.isArray(schema["enum"])) {
|
|
1566
|
+
const list = schema["enum"];
|
|
1567
|
+
if (!list.some((e) => deepEqual(e, data))) {
|
|
1568
|
+
this.err(path, `must be one of ${JSON.stringify(list)}`);
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
checkNumber(data, schema, path) {
|
|
1573
|
+
if (typeof data !== "number") return;
|
|
1574
|
+
if (typeof schema["minimum"] === "number" && data < schema["minimum"]) this.err(path, `must be >= ${schema["minimum"]}`);
|
|
1575
|
+
if (typeof schema["maximum"] === "number" && data > schema["maximum"]) this.err(path, `must be <= ${schema["maximum"]}`);
|
|
1576
|
+
if (typeof schema["exclusiveMinimum"] === "number" && data <= schema["exclusiveMinimum"]) this.err(path, `must be > ${schema["exclusiveMinimum"]}`);
|
|
1577
|
+
if (typeof schema["exclusiveMaximum"] === "number" && data >= schema["exclusiveMaximum"]) this.err(path, `must be < ${schema["exclusiveMaximum"]}`);
|
|
1578
|
+
if (typeof schema["multipleOf"] === "number" && schema["multipleOf"] > 0) {
|
|
1579
|
+
const q = data / schema["multipleOf"];
|
|
1580
|
+
if (Math.abs(q - Math.round(q)) > 1e-9) this.err(path, `must be a multiple of ${schema["multipleOf"]}`);
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
checkString(data, schema, path) {
|
|
1584
|
+
if (typeof data !== "string") return;
|
|
1585
|
+
if (typeof schema["minLength"] === "number" && data.length < schema["minLength"]) this.err(path, `must be at least ${schema["minLength"]} characters`);
|
|
1586
|
+
if (typeof schema["maxLength"] === "number" && data.length > schema["maxLength"]) this.err(path, `must be at most ${schema["maxLength"]} characters`);
|
|
1587
|
+
if (typeof schema["pattern"] === "string") {
|
|
1588
|
+
let re = null;
|
|
1589
|
+
try {
|
|
1590
|
+
re = new RegExp(schema["pattern"]);
|
|
1591
|
+
} catch {
|
|
1592
|
+
}
|
|
1593
|
+
if (re && !re.test(data)) this.err(path, `must match pattern /${schema["pattern"]}/`);
|
|
1594
|
+
}
|
|
1595
|
+
if (typeof schema["format"] === "string") {
|
|
1596
|
+
const fmt = FORMATS[schema["format"]];
|
|
1597
|
+
if (fmt && !fmt.test(data)) this.err(path, `must be a valid ${schema["format"]}`);
|
|
1598
|
+
if (schema["format"] === "ipv4" && fmt && fmt.test(data)) {
|
|
1599
|
+
if (!data.split(".").every((o) => Number(o) <= 255)) this.err(path, `must be a valid ipv4`);
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
checkArray(data, schema, path) {
|
|
1604
|
+
if (!Array.isArray(data)) return;
|
|
1605
|
+
if (typeof schema["minItems"] === "number" && data.length < schema["minItems"]) this.err(path, `must have at least ${schema["minItems"]} items`);
|
|
1606
|
+
if (typeof schema["maxItems"] === "number" && data.length > schema["maxItems"]) this.err(path, `must have at most ${schema["maxItems"]} items`);
|
|
1607
|
+
if (schema["uniqueItems"] === true) {
|
|
1608
|
+
for (let i = 0; i < data.length; i++) {
|
|
1609
|
+
for (let j = i + 1; j < data.length; j++) {
|
|
1610
|
+
if (deepEqual(data[i], data[j])) {
|
|
1611
|
+
this.err(path, `items must be unique (indexes ${i} and ${j})`);
|
|
1612
|
+
break;
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
const items = schema["items"];
|
|
1618
|
+
if (Array.isArray(items)) {
|
|
1619
|
+
for (let i = 0; i < items.length && i < data.length; i++) {
|
|
1620
|
+
this.validate(data[i], items[i], `${path}[${i}]`);
|
|
1621
|
+
}
|
|
1622
|
+
const additional = schema["additionalItems"];
|
|
1623
|
+
if (additional !== void 0 && data.length > items.length) {
|
|
1624
|
+
for (let i = items.length; i < data.length; i++) {
|
|
1625
|
+
if (additional === false) this.err(`${path}[${i}]`, "additional items are not allowed");
|
|
1626
|
+
else this.validate(data[i], additional, `${path}[${i}]`);
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
} else if (items !== void 0) {
|
|
1630
|
+
for (let i = 0; i < data.length; i++) this.validate(data[i], items, `${path}[${i}]`);
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
checkObject(data, schema, path) {
|
|
1634
|
+
if (data === null || typeof data !== "object" || Array.isArray(data)) return;
|
|
1635
|
+
const obj = data;
|
|
1636
|
+
const keys = Object.keys(obj);
|
|
1637
|
+
if (Array.isArray(schema["required"])) {
|
|
1638
|
+
for (const req of schema["required"]) {
|
|
1639
|
+
if (!Object.prototype.hasOwnProperty.call(obj, req)) this.err(path, `missing required property "${req}"`);
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
if (typeof schema["minProperties"] === "number" && keys.length < schema["minProperties"]) this.err(path, `must have at least ${schema["minProperties"]} properties`);
|
|
1643
|
+
if (typeof schema["maxProperties"] === "number" && keys.length > schema["maxProperties"]) this.err(path, `must have at most ${schema["maxProperties"]} properties`);
|
|
1644
|
+
const props = schema["properties"] || {};
|
|
1645
|
+
const patternProps = schema["patternProperties"] || {};
|
|
1646
|
+
const additional = schema["additionalProperties"];
|
|
1647
|
+
for (const key of keys) {
|
|
1648
|
+
const childPath = `${path}.${key}`;
|
|
1649
|
+
let matched = false;
|
|
1650
|
+
if (Object.prototype.hasOwnProperty.call(props, key)) {
|
|
1651
|
+
matched = true;
|
|
1652
|
+
this.validate(obj[key], props[key], childPath);
|
|
1653
|
+
}
|
|
1654
|
+
for (const pat of Object.keys(patternProps)) {
|
|
1655
|
+
let re = null;
|
|
1656
|
+
try {
|
|
1657
|
+
re = new RegExp(pat);
|
|
1658
|
+
} catch {
|
|
1659
|
+
}
|
|
1660
|
+
if (re && re.test(key)) {
|
|
1661
|
+
matched = true;
|
|
1662
|
+
this.validate(obj[key], patternProps[pat], childPath);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
if (!matched && additional !== void 0) {
|
|
1666
|
+
if (additional === false) this.err(childPath, `additional property "${key}" is not allowed`);
|
|
1667
|
+
else if (additional !== true) this.validate(obj[key], additional, childPath);
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
checkCombinators(data, schema, path) {
|
|
1672
|
+
if (Array.isArray(schema["allOf"])) {
|
|
1673
|
+
for (const sub of schema["allOf"]) this.validate(data, sub, path);
|
|
1674
|
+
}
|
|
1675
|
+
if (Array.isArray(schema["anyOf"])) {
|
|
1676
|
+
const subs = schema["anyOf"];
|
|
1677
|
+
const ok = subs.some((sub) => this.subValid(data, sub, path));
|
|
1678
|
+
if (!ok) this.err(path, "must match at least one schema in anyOf");
|
|
1679
|
+
}
|
|
1680
|
+
if (Array.isArray(schema["oneOf"])) {
|
|
1681
|
+
const subs = schema["oneOf"];
|
|
1682
|
+
const count = subs.filter((sub) => this.subValid(data, sub, path)).length;
|
|
1683
|
+
if (count !== 1) this.err(path, `must match exactly one schema in oneOf (matched ${count})`);
|
|
1684
|
+
}
|
|
1685
|
+
if (schema["not"] !== void 0) {
|
|
1686
|
+
if (this.subValid(data, schema["not"], path)) this.err(path, "must not match the `not` schema");
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
subValid(data, schema, path) {
|
|
1690
|
+
const probe = new _Validator(this.root);
|
|
1691
|
+
probe.validate(data, schema, path);
|
|
1692
|
+
return probe.errors.length === 0;
|
|
1693
|
+
}
|
|
1694
|
+
result() {
|
|
1695
|
+
return { valid: this.errors.length === 0, errors: this.errors };
|
|
1696
|
+
}
|
|
1697
|
+
};
|
|
1698
|
+
function validateSchema(data, schema) {
|
|
1699
|
+
const v = new Validator(schema);
|
|
1700
|
+
v.validate(data, schema, "$");
|
|
1701
|
+
return v.result();
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
// src/diff.ts
|
|
1705
|
+
function join(base, key) {
|
|
1706
|
+
if (typeof key === "number") return `${base}[${key}]`;
|
|
1707
|
+
return base === "" ? key : `${base}.${key}`;
|
|
1708
|
+
}
|
|
1709
|
+
function walk(a, b, path, out2) {
|
|
1710
|
+
if (deepEqual(a, b)) return;
|
|
1711
|
+
const aMissing = a === void 0;
|
|
1712
|
+
const bMissing = b === void 0;
|
|
1713
|
+
if (aMissing && !bMissing) {
|
|
1714
|
+
out2.push({ kind: "added", path, after: b });
|
|
1715
|
+
return;
|
|
1716
|
+
}
|
|
1717
|
+
if (!aMissing && bMissing) {
|
|
1718
|
+
out2.push({ kind: "removed", path, before: a });
|
|
1719
|
+
return;
|
|
1720
|
+
}
|
|
1721
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
1722
|
+
const keys = /* @__PURE__ */ new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
1723
|
+
for (const k of [...keys].sort()) {
|
|
1724
|
+
walk(
|
|
1725
|
+
Object.prototype.hasOwnProperty.call(a, k) ? a[k] : void 0,
|
|
1726
|
+
Object.prototype.hasOwnProperty.call(b, k) ? b[k] : void 0,
|
|
1727
|
+
join(path, k),
|
|
1728
|
+
out2
|
|
1729
|
+
);
|
|
1730
|
+
}
|
|
1731
|
+
return;
|
|
1732
|
+
}
|
|
1733
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
1734
|
+
const max = Math.max(a.length, b.length);
|
|
1735
|
+
for (let i = 0; i < max; i++) {
|
|
1736
|
+
walk(i < a.length ? a[i] : void 0, i < b.length ? b[i] : void 0, join(path, i), out2);
|
|
1737
|
+
}
|
|
1738
|
+
return;
|
|
1739
|
+
}
|
|
1740
|
+
out2.push({ kind: "changed", path: path === "" ? "$" : path, before: a, after: b });
|
|
1741
|
+
}
|
|
1742
|
+
function diff(a, b) {
|
|
1743
|
+
const out2 = [];
|
|
1744
|
+
walk(a, b, "", out2);
|
|
1745
|
+
return out2;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
// src/merge.ts
|
|
1749
|
+
function mergeArrays(a, b, strat) {
|
|
1750
|
+
if (strat.mode === "replace") return deepClone(b);
|
|
1751
|
+
if (strat.mode === "concat") return [...deepClone(a), ...deepClone(b)];
|
|
1752
|
+
const key = strat.key;
|
|
1753
|
+
const out2 = deepClone(a);
|
|
1754
|
+
const indexByKey = /* @__PURE__ */ new Map();
|
|
1755
|
+
out2.forEach((item, i) => {
|
|
1756
|
+
if (isPlainObject(item) && item[key] !== void 0) indexByKey.set(JSON.stringify(item[key]), i);
|
|
1757
|
+
});
|
|
1758
|
+
for (const item of b) {
|
|
1759
|
+
if (isPlainObject(item) && item[key] !== void 0) {
|
|
1760
|
+
const id = JSON.stringify(item[key]);
|
|
1761
|
+
if (indexByKey.has(id)) {
|
|
1762
|
+
const idx = indexByKey.get(id);
|
|
1763
|
+
out2[idx] = mergeTwo(out2[idx], item, { array: strat });
|
|
1764
|
+
} else {
|
|
1765
|
+
indexByKey.set(id, out2.length);
|
|
1766
|
+
out2.push(deepClone(item));
|
|
1767
|
+
}
|
|
1768
|
+
} else {
|
|
1769
|
+
out2.push(deepClone(item));
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
return out2;
|
|
1773
|
+
}
|
|
1774
|
+
function mergeTwo(a, b, opts) {
|
|
1775
|
+
const strat = opts.array ?? { mode: "replace" };
|
|
1776
|
+
if (Array.isArray(a) && Array.isArray(b)) return mergeArrays(a, b, strat);
|
|
1777
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
1778
|
+
const out2 = {};
|
|
1779
|
+
for (const k of Object.keys(a)) {
|
|
1780
|
+
if (isForbiddenKey(k)) continue;
|
|
1781
|
+
safeSet(out2, k, deepClone(a[k]));
|
|
1782
|
+
}
|
|
1783
|
+
for (const k of Object.keys(b)) {
|
|
1784
|
+
if (isForbiddenKey(k)) continue;
|
|
1785
|
+
if (Object.prototype.hasOwnProperty.call(out2, k)) {
|
|
1786
|
+
safeSet(out2, k, mergeTwo(out2[k], b[k], opts));
|
|
1787
|
+
} else {
|
|
1788
|
+
safeSet(out2, k, deepClone(b[k]));
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
return out2;
|
|
1792
|
+
}
|
|
1793
|
+
return deepClone(b);
|
|
1794
|
+
}
|
|
1795
|
+
function merge(values, opts = {}) {
|
|
1796
|
+
if (values.length === 0) return null;
|
|
1797
|
+
let acc = deepClone(values[0]);
|
|
1798
|
+
for (let i = 1; i < values.length; i++) acc = mergeTwo(acc, values[i], opts);
|
|
1799
|
+
return acc;
|
|
1800
|
+
}
|
|
1801
|
+
function parseArrayStrategy(spec, byKey) {
|
|
1802
|
+
if (byKey) return { mode: "by-key", key: byKey };
|
|
1803
|
+
if (spec === "concat") return { mode: "concat" };
|
|
1804
|
+
if (spec === "replace") return { mode: "replace" };
|
|
1805
|
+
if (spec && spec.startsWith("by-key")) {
|
|
1806
|
+
const key = spec.split(/[:=]/)[1];
|
|
1807
|
+
if (key) return { mode: "by-key", key };
|
|
1808
|
+
}
|
|
1809
|
+
return { mode: "replace" };
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
// src/format.ts
|
|
1813
|
+
function formatJson(value, opts = {}) {
|
|
1814
|
+
const v = opts.sortKeys ? sortKeysDeep(value) : value;
|
|
1815
|
+
if (opts.minify) return JSON.stringify(v);
|
|
1816
|
+
const indent = opts.indent ?? 2;
|
|
1817
|
+
return JSON.stringify(v, null, indent);
|
|
1818
|
+
}
|
|
1819
|
+
function getPath(data, path) {
|
|
1820
|
+
const segments = parsePath(path);
|
|
1821
|
+
let cur = data;
|
|
1822
|
+
for (const seg of segments) {
|
|
1823
|
+
if (cur === null || cur === void 0) return void 0;
|
|
1824
|
+
if (typeof seg === "number") {
|
|
1825
|
+
if (!Array.isArray(cur)) return void 0;
|
|
1826
|
+
const idx = seg < 0 ? cur.length + seg : seg;
|
|
1827
|
+
cur = cur[idx];
|
|
1828
|
+
} else {
|
|
1829
|
+
if (Array.isArray(cur) || typeof cur !== "object") return void 0;
|
|
1830
|
+
if (!Object.prototype.hasOwnProperty.call(cur, seg)) return void 0;
|
|
1831
|
+
cur = cur[seg];
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
return cur;
|
|
1835
|
+
}
|
|
1836
|
+
function parsePath(path) {
|
|
1837
|
+
const out2 = [];
|
|
1838
|
+
let i = 0;
|
|
1839
|
+
const n = path.length;
|
|
1840
|
+
if (path[0] === ".") i = 1;
|
|
1841
|
+
let cur = "";
|
|
1842
|
+
const flush = () => {
|
|
1843
|
+
if (cur !== "") {
|
|
1844
|
+
out2.push(cur);
|
|
1845
|
+
cur = "";
|
|
1846
|
+
}
|
|
1847
|
+
};
|
|
1848
|
+
while (i < n) {
|
|
1849
|
+
const ch = path[i];
|
|
1850
|
+
if (ch === ".") {
|
|
1851
|
+
flush();
|
|
1852
|
+
i++;
|
|
1853
|
+
continue;
|
|
1854
|
+
}
|
|
1855
|
+
if (ch === "[") {
|
|
1856
|
+
flush();
|
|
1857
|
+
const close = path.indexOf("]", i);
|
|
1858
|
+
if (close < 0) throw new JsonToolError(`Unbalanced [ in path "${path}"`);
|
|
1859
|
+
const inner = path.slice(i + 1, close).trim();
|
|
1860
|
+
if (inner[0] === '"' && inner.endsWith('"') || inner[0] === "'" && inner.endsWith("'")) {
|
|
1861
|
+
out2.push(inner.slice(1, -1));
|
|
1862
|
+
} else if (/^-?\d+$/.test(inner)) {
|
|
1863
|
+
out2.push(parseInt(inner, 10));
|
|
1864
|
+
} else {
|
|
1865
|
+
out2.push(inner);
|
|
1866
|
+
}
|
|
1867
|
+
i = close + 1;
|
|
1868
|
+
continue;
|
|
1869
|
+
}
|
|
1870
|
+
cur += ch;
|
|
1871
|
+
i++;
|
|
1872
|
+
}
|
|
1873
|
+
flush();
|
|
1874
|
+
return out2;
|
|
1875
|
+
}
|
|
1876
|
+
|
|
1877
|
+
// src/cli.ts
|
|
1878
|
+
var VERSION = "0.1.0";
|
|
1879
|
+
var useColor = !process.env["NO_COLOR"] && stdout.isTTY !== false;
|
|
1880
|
+
var C = {
|
|
1881
|
+
reset: "\x1B[0m",
|
|
1882
|
+
bold: "\x1B[1m",
|
|
1883
|
+
dim: "\x1B[2m",
|
|
1884
|
+
green: "\x1B[32m",
|
|
1885
|
+
cyan: "\x1B[36m",
|
|
1886
|
+
yellow: "\x1B[33m",
|
|
1887
|
+
red: "\x1B[31m",
|
|
1888
|
+
magenta: "\x1B[35m"
|
|
1889
|
+
};
|
|
1890
|
+
var c = (k, s) => useColor ? `${C[k]}${s}${C.reset}` : s;
|
|
1891
|
+
var log = (s = "") => void stderr.write(s + "\n");
|
|
1892
|
+
var out = (s) => void stdout.write(s);
|
|
1893
|
+
var FORMATS2 = /* @__PURE__ */ new Set(["json", "yaml", "toml", "csv", "ndjson"]);
|
|
1894
|
+
function asFormat(v, flag) {
|
|
1895
|
+
if (!FORMATS2.has(v)) fail(`Unknown format "${v}" for ${flag} (json|yaml|toml|csv|ndjson)`, false);
|
|
1896
|
+
return v;
|
|
1897
|
+
}
|
|
1898
|
+
function parseArgs(list) {
|
|
1899
|
+
const a = {
|
|
1900
|
+
positional: [],
|
|
1901
|
+
indent: 2,
|
|
1902
|
+
sortKeys: false,
|
|
1903
|
+
min: false,
|
|
1904
|
+
raw: false,
|
|
1905
|
+
json: false,
|
|
1906
|
+
help: false,
|
|
1907
|
+
version: false
|
|
1908
|
+
};
|
|
1909
|
+
for (let i = 0; i < list.length; i++) {
|
|
1910
|
+
const arg = list[i];
|
|
1911
|
+
const nextVal = () => list[++i] ?? "";
|
|
1912
|
+
if (arg === "--from") a.from = asFormat(nextVal(), "--from");
|
|
1913
|
+
else if (arg === "--to") a.to = asFormat(nextVal(), "--to");
|
|
1914
|
+
else if (arg === "-q" || arg === "--query") a.query = nextVal();
|
|
1915
|
+
else if (arg === "--get") a.get = nextVal();
|
|
1916
|
+
else if (arg === "--schema") a.schema = nextVal();
|
|
1917
|
+
else if (arg === "--indent") a.indent = Number(nextVal());
|
|
1918
|
+
else if (arg === "--sort-keys") a.sortKeys = true;
|
|
1919
|
+
else if (arg === "--min" || arg === "--minify") a.min = true;
|
|
1920
|
+
else if (arg === "--raw" || arg === "-r") a.raw = true;
|
|
1921
|
+
else if (arg === "--json") a.json = true;
|
|
1922
|
+
else if (arg === "--array") a.array = nextVal();
|
|
1923
|
+
else if (arg === "--array-key") a.arrayKey = nextVal();
|
|
1924
|
+
else if (arg === "-h" || arg === "--help") a.help = true;
|
|
1925
|
+
else if (arg === "-v" || arg === "--version") a.version = true;
|
|
1926
|
+
else if (arg.startsWith("--") && arg.includes("=")) {
|
|
1927
|
+
const eq = arg.indexOf("=");
|
|
1928
|
+
list.splice(i + 1, 0, arg.slice(eq + 1));
|
|
1929
|
+
list[i] = arg.slice(0, eq);
|
|
1930
|
+
i--;
|
|
1931
|
+
} else a.positional.push(arg);
|
|
1932
|
+
}
|
|
1933
|
+
return a;
|
|
1934
|
+
}
|
|
1935
|
+
var HELP = `
|
|
1936
|
+
${c("bold", c("magenta", "\u25C6 lacspace-json"))} ${c("dim", "\u2014 the friendly jq: query, convert, validate, diff & merge data")}
|
|
1937
|
+
|
|
1938
|
+
${c("bold", "Usage")}
|
|
1939
|
+
npx lacspace-json <command> [input] [flags]
|
|
1940
|
+
cat data.json | npx lacspace-json <command> [flags]
|
|
1941
|
+
|
|
1942
|
+
${c("bold", "Commands")}
|
|
1943
|
+
${c("cyan", "query|get")} <input> -q "<expr>" Run a jq-style query (default when -q is given)
|
|
1944
|
+
${c("cyan", "convert")} <input> --to <fmt> JSON \u21C4 YAML \u21C4 TOML \u21C4 CSV \u21C4 NDJSON
|
|
1945
|
+
${c("cyan", "validate")} <data> --schema <s> Validate against a JSON Schema (draft-07 subset)
|
|
1946
|
+
${c("cyan", "diff")} <a> <b> Structural diff of two documents
|
|
1947
|
+
${c("cyan", "merge")} <a> <b> [...] Deep-merge N documents
|
|
1948
|
+
${c("cyan", "format")} <input> Pretty-print / minify (default when only input given)
|
|
1949
|
+
|
|
1950
|
+
${c("bold", "Flags")}
|
|
1951
|
+
--from <fmt> Input format override (json|yaml|toml|csv|ndjson)
|
|
1952
|
+
--to <fmt> Output format (convert)
|
|
1953
|
+
-q, --query <expr> Query expression (see below)
|
|
1954
|
+
--get <path> Shorthand: extract a single value at a path (.a.b[0])
|
|
1955
|
+
--schema <file> JSON Schema file (validate)
|
|
1956
|
+
--indent <n> Indent width for pretty JSON/YAML (default 2)
|
|
1957
|
+
--sort-keys Sort object keys recursively
|
|
1958
|
+
--min Minify JSON output
|
|
1959
|
+
-r, --raw Print string scalars unquoted (great for shell)
|
|
1960
|
+
--json Force machine-readable JSON output (diff/validate)
|
|
1961
|
+
--array <mode> Merge array strategy: concat | replace | by-key
|
|
1962
|
+
--array-key <k> Key for --array by-key
|
|
1963
|
+
-h, --help Show this help
|
|
1964
|
+
-v, --version Print the version
|
|
1965
|
+
|
|
1966
|
+
${c("bold", "Query language")} ${c("dim", "(hand-written, no eval)")}
|
|
1967
|
+
${c("dim", "paths")} .users[0].name .items[].price .a["b c"]
|
|
1968
|
+
${c("dim", "pipe")} .users[] | select(.age > 21) | .name
|
|
1969
|
+
${c("dim", "funcs")} keys values length type has(k) map(.x) unique reverse
|
|
1970
|
+
${c("dim", " ")} sort_by(.x) group_by(.x) first last min max sum avg add flatten
|
|
1971
|
+
|
|
1972
|
+
${c("bold", "Examples")}
|
|
1973
|
+
echo '{"a":{"b":[1,2,3]}}' | npx lacspace-json --get .a.b[1]
|
|
1974
|
+
npx lacspace-json query data.json -q ".users[] | select(.active) | .email" -r
|
|
1975
|
+
npx lacspace-json convert config.toml --to yaml
|
|
1976
|
+
cat data.csv | npx lacspace-json convert --from csv --to json
|
|
1977
|
+
npx lacspace-json validate user.json --schema user.schema.json
|
|
1978
|
+
npx lacspace-json diff old.json new.json
|
|
1979
|
+
npx lacspace-json merge base.json override.json --array by-key --array-key id
|
|
1980
|
+
`;
|
|
1981
|
+
function fail(msg, json, extra = {}) {
|
|
1982
|
+
if (json) out(JSON.stringify({ ok: false, error: msg, ...extra }) + "\n");
|
|
1983
|
+
else log(c("red", `
|
|
1984
|
+
\u2717 ${msg}
|
|
1985
|
+
`));
|
|
1986
|
+
exit(1);
|
|
1987
|
+
}
|
|
1988
|
+
function isStdinPiped() {
|
|
1989
|
+
try {
|
|
1990
|
+
return !stdin.isTTY;
|
|
1991
|
+
} catch {
|
|
1992
|
+
return false;
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
function readStdin() {
|
|
1996
|
+
try {
|
|
1997
|
+
return readFileSync(0, "utf8");
|
|
1998
|
+
} catch {
|
|
1999
|
+
return "";
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
function expandGlob(pattern) {
|
|
2003
|
+
if (!pattern.includes("*")) return [pattern];
|
|
2004
|
+
const dir = dirname(pattern);
|
|
2005
|
+
const base = basename(pattern);
|
|
2006
|
+
const re = new RegExp("^" + base.replace(/[.+^${}()|\\]/g, "\\$&").replace(/\*/g, ".*") + "$");
|
|
2007
|
+
try {
|
|
2008
|
+
return readdirSync(dir).filter((f) => re.test(f)).map((f) => join2(dir, f)).sort();
|
|
2009
|
+
} catch {
|
|
2010
|
+
return [pattern];
|
|
2011
|
+
}
|
|
2012
|
+
}
|
|
2013
|
+
function loadInput(source, from) {
|
|
2014
|
+
let text;
|
|
2015
|
+
let src = source ?? "-";
|
|
2016
|
+
let fmt = from;
|
|
2017
|
+
if (source && source !== "-") {
|
|
2018
|
+
try {
|
|
2019
|
+
const st = statSync(source);
|
|
2020
|
+
if (!st.isFile()) throw new JsonToolError(`Not a file: ${source}`);
|
|
2021
|
+
} catch {
|
|
2022
|
+
fail(`Cannot read "${source}"`, false);
|
|
2023
|
+
}
|
|
2024
|
+
text = readFileSync(source, "utf8");
|
|
2025
|
+
if (!fmt) fmt = formatFromExt(source);
|
|
2026
|
+
} else {
|
|
2027
|
+
if (!isStdinPiped()) fail("No input given. Pass a file or pipe data via stdin.", false);
|
|
2028
|
+
text = readStdin();
|
|
2029
|
+
src = "<stdin>";
|
|
2030
|
+
}
|
|
2031
|
+
if (!fmt) fmt = detectFormat(text);
|
|
2032
|
+
let value;
|
|
2033
|
+
try {
|
|
2034
|
+
value = parseFormat(text, fmt);
|
|
2035
|
+
} catch (err) {
|
|
2036
|
+
fail(err instanceof Error ? err.message : String(err), false);
|
|
2037
|
+
}
|
|
2038
|
+
return { value, format: fmt, source: src };
|
|
2039
|
+
}
|
|
2040
|
+
function printValue(value, a) {
|
|
2041
|
+
if (a.raw && (typeof value === "string" || typeof value === "number" || typeof value === "boolean" || value === null)) {
|
|
2042
|
+
out(String(value ?? "") + "\n");
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
2045
|
+
if (a.raw && Array.isArray(value) && value.every((v) => typeof v !== "object" || v === null)) {
|
|
2046
|
+
out(value.map((v) => v === null ? "null" : String(v)).join("\n") + "\n");
|
|
2047
|
+
return;
|
|
2048
|
+
}
|
|
2049
|
+
out(formatJson(value, { indent: a.indent, sortKeys: a.sortKeys, minify: a.min }) + (a.min ? "\n" : "\n"));
|
|
2050
|
+
}
|
|
2051
|
+
function cmdQuery(a) {
|
|
2052
|
+
const loaded = loadInput(a.positional[1], a.from);
|
|
2053
|
+
if (a.get) {
|
|
2054
|
+
printValue(getPath(loaded.value, a.get) ?? null, a);
|
|
2055
|
+
return;
|
|
2056
|
+
}
|
|
2057
|
+
const expr = a.query;
|
|
2058
|
+
if (!expr) fail('query needs an expression: -q "<expr>" (or use --get <path>)', a.json);
|
|
2059
|
+
let result;
|
|
2060
|
+
try {
|
|
2061
|
+
result = query(loaded.value, expr);
|
|
2062
|
+
} catch (err) {
|
|
2063
|
+
fail(err instanceof Error ? err.message : String(err), a.json);
|
|
2064
|
+
}
|
|
2065
|
+
printValue(result, a);
|
|
2066
|
+
}
|
|
2067
|
+
function cmdConvert(a) {
|
|
2068
|
+
const loaded = loadInput(a.positional[1], a.from);
|
|
2069
|
+
const to = a.to ?? "json";
|
|
2070
|
+
let value = loaded.value;
|
|
2071
|
+
if (a.query) {
|
|
2072
|
+
try {
|
|
2073
|
+
value = query(value, a.query);
|
|
2074
|
+
} catch (err) {
|
|
2075
|
+
fail(err instanceof Error ? err.message : String(err), a.json);
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
if (a.sortKeys) value = sortKeysDeep(value);
|
|
2079
|
+
let text;
|
|
2080
|
+
try {
|
|
2081
|
+
text = stringifyFormat(value, to, { indent: a.indent, minify: a.min });
|
|
2082
|
+
} catch (err) {
|
|
2083
|
+
fail(err instanceof Error ? err.message : String(err), a.json);
|
|
2084
|
+
}
|
|
2085
|
+
out(text);
|
|
2086
|
+
}
|
|
2087
|
+
function cmdValidate(a) {
|
|
2088
|
+
if (!a.schema) fail("validate needs --schema <file>", a.json);
|
|
2089
|
+
const loaded = loadInput(a.positional[1], a.from);
|
|
2090
|
+
let schema;
|
|
2091
|
+
try {
|
|
2092
|
+
const schemaText = readFileSync(a.schema, "utf8");
|
|
2093
|
+
schema = parseFormat(schemaText, formatFromExt(a.schema) ?? "json");
|
|
2094
|
+
} catch (err) {
|
|
2095
|
+
fail(`Cannot read schema: ${err.message}`, a.json);
|
|
2096
|
+
}
|
|
2097
|
+
const result = validateSchema(loaded.value, schema);
|
|
2098
|
+
if (a.json) {
|
|
2099
|
+
out(JSON.stringify(result) + "\n");
|
|
2100
|
+
if (!result.valid) exit(1);
|
|
2101
|
+
return;
|
|
2102
|
+
}
|
|
2103
|
+
if (result.valid) {
|
|
2104
|
+
log(`
|
|
2105
|
+
${c("green", "\u2713 valid")} ${c("dim", `\xB7 ${loaded.source} matches the schema`)}
|
|
2106
|
+
`);
|
|
2107
|
+
} else {
|
|
2108
|
+
log(`
|
|
2109
|
+
${c("red", `\u2717 invalid`)} ${c("dim", `\xB7 ${result.errors.length} error${result.errors.length === 1 ? "" : "s"}`)}`);
|
|
2110
|
+
for (const e of result.errors) log(` ${c("yellow", e.path)} ${c("dim", e.message)}`);
|
|
2111
|
+
log("");
|
|
2112
|
+
exit(1);
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
2115
|
+
function cmdDiff(a) {
|
|
2116
|
+
const files = a.positional.slice(1);
|
|
2117
|
+
if (files.length < 2) fail("diff needs two inputs: diff <a> <b>", a.json);
|
|
2118
|
+
const aDoc = loadInput(files[0], a.from);
|
|
2119
|
+
const bDoc = loadInput(files[1], a.from);
|
|
2120
|
+
const entries = diff(aDoc.value, bDoc.value);
|
|
2121
|
+
if (a.json) {
|
|
2122
|
+
out(JSON.stringify({ ok: true, changes: entries.length, diff: entries }) + "\n");
|
|
2123
|
+
if (entries.length) exit(1);
|
|
2124
|
+
return;
|
|
2125
|
+
}
|
|
2126
|
+
if (entries.length === 0) {
|
|
2127
|
+
log(`
|
|
2128
|
+
${c("green", "\u2713 no differences")}
|
|
2129
|
+
`);
|
|
2130
|
+
return;
|
|
2131
|
+
}
|
|
2132
|
+
log(`
|
|
2133
|
+
${c("bold", c("magenta", "\u25C6 lacspace-json diff"))} ${c("dim", `\xB7 ${entries.length} change${entries.length === 1 ? "" : "s"}`)}
|
|
2134
|
+
`);
|
|
2135
|
+
for (const e of entries) printDiff(e);
|
|
2136
|
+
log("");
|
|
2137
|
+
exit(1);
|
|
2138
|
+
}
|
|
2139
|
+
function printDiff(e) {
|
|
2140
|
+
const short = (v) => {
|
|
2141
|
+
const s = JSON.stringify(v);
|
|
2142
|
+
return s && s.length > 60 ? s.slice(0, 57) + "\u2026" : s ?? "undefined";
|
|
2143
|
+
};
|
|
2144
|
+
if (e.kind === "added") log(` ${c("green", "+")} ${c("cyan", e.path)} ${c("dim", short(e.after))}`);
|
|
2145
|
+
else if (e.kind === "removed") log(` ${c("red", "-")} ${c("cyan", e.path)} ${c("dim", short(e.before))}`);
|
|
2146
|
+
else log(` ${c("yellow", "~")} ${c("cyan", e.path)} ${c("dim", short(e.before))} ${c("dim", "\u2192")} ${c("dim", short(e.after))}`);
|
|
2147
|
+
}
|
|
2148
|
+
function cmdMerge(a) {
|
|
2149
|
+
const files = a.positional.slice(1);
|
|
2150
|
+
if (files.length < 1) fail("merge needs at least one input", a.json);
|
|
2151
|
+
const expanded = [];
|
|
2152
|
+
for (const f of files) expanded.push(...expandGlob(f));
|
|
2153
|
+
const values = expanded.map((f) => loadInput(f, a.from).value);
|
|
2154
|
+
const strat = parseArrayStrategy(a.array, a.arrayKey);
|
|
2155
|
+
const result = merge(values, { array: strat });
|
|
2156
|
+
const to = a.to ?? "json";
|
|
2157
|
+
let text;
|
|
2158
|
+
try {
|
|
2159
|
+
text = stringifyFormat(a.sortKeys ? sortKeysDeep(result) : result, to, { indent: a.indent, minify: a.min });
|
|
2160
|
+
} catch (err) {
|
|
2161
|
+
fail(err instanceof Error ? err.message : String(err), a.json);
|
|
2162
|
+
}
|
|
2163
|
+
out(text);
|
|
2164
|
+
}
|
|
2165
|
+
function cmdFormat(a) {
|
|
2166
|
+
const loaded = loadInput(a.positional[1], a.from);
|
|
2167
|
+
if (a.get) {
|
|
2168
|
+
printValue(getPath(loaded.value, a.get) ?? null, a);
|
|
2169
|
+
return;
|
|
2170
|
+
}
|
|
2171
|
+
let value = loaded.value;
|
|
2172
|
+
if (a.query) {
|
|
2173
|
+
try {
|
|
2174
|
+
value = query(value, a.query);
|
|
2175
|
+
} catch (err) {
|
|
2176
|
+
fail(err instanceof Error ? err.message : String(err), a.json);
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
if (a.to && a.to !== "json") {
|
|
2180
|
+
out(stringifyFormat(a.sortKeys ? sortKeysDeep(value) : value, a.to, { indent: a.indent, minify: a.min }));
|
|
2181
|
+
return;
|
|
2182
|
+
}
|
|
2183
|
+
printValue(value, a);
|
|
2184
|
+
}
|
|
2185
|
+
var COMMANDS = /* @__PURE__ */ new Set(["query", "get", "convert", "validate", "diff", "merge", "format"]);
|
|
2186
|
+
function main() {
|
|
2187
|
+
const args = parseArgs(argv.slice(2));
|
|
2188
|
+
if (args.version) {
|
|
2189
|
+
out(VERSION + "\n");
|
|
2190
|
+
return;
|
|
2191
|
+
}
|
|
2192
|
+
if (args.help) {
|
|
2193
|
+
out(HELP + "\n");
|
|
2194
|
+
return;
|
|
2195
|
+
}
|
|
2196
|
+
let cmd = args.positional[0];
|
|
2197
|
+
if (!cmd || !COMMANDS.has(cmd)) {
|
|
2198
|
+
if (args.positional.length === 0 && !isStdinPiped() && !args.query && !args.get) {
|
|
2199
|
+
out(HELP + "\n");
|
|
2200
|
+
return;
|
|
2201
|
+
}
|
|
2202
|
+
if (args.schema) {
|
|
2203
|
+
args.positional.unshift("validate");
|
|
2204
|
+
cmd = "validate";
|
|
2205
|
+
} else if (args.query || args.get) {
|
|
2206
|
+
args.positional.unshift("query");
|
|
2207
|
+
cmd = "query";
|
|
2208
|
+
} else if (args.to) {
|
|
2209
|
+
args.positional.unshift("convert");
|
|
2210
|
+
cmd = "convert";
|
|
2211
|
+
} else {
|
|
2212
|
+
args.positional.unshift("format");
|
|
2213
|
+
cmd = "format";
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
switch (cmd) {
|
|
2217
|
+
case "query":
|
|
2218
|
+
case "get":
|
|
2219
|
+
cmdQuery(args);
|
|
2220
|
+
return;
|
|
2221
|
+
case "convert":
|
|
2222
|
+
cmdConvert(args);
|
|
2223
|
+
return;
|
|
2224
|
+
case "validate":
|
|
2225
|
+
cmdValidate(args);
|
|
2226
|
+
return;
|
|
2227
|
+
case "diff":
|
|
2228
|
+
cmdDiff(args);
|
|
2229
|
+
return;
|
|
2230
|
+
case "merge":
|
|
2231
|
+
cmdMerge(args);
|
|
2232
|
+
return;
|
|
2233
|
+
case "format":
|
|
2234
|
+
cmdFormat(args);
|
|
2235
|
+
return;
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
try {
|
|
2239
|
+
main();
|
|
2240
|
+
} catch (err) {
|
|
2241
|
+
log(c("red", `
|
|
2242
|
+
\u2717 ${err instanceof Error ? err.message : String(err)}
|
|
2243
|
+
`));
|
|
2244
|
+
exit(1);
|
|
2245
|
+
}
|