fx 26.0.0 → 28.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -9
- package/index.js +492 -50
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# fx
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A non-interactive, JavaScript version of the [**fx**](https://fx.wtf).
|
|
4
|
+
Short for _Function eXecution_ or _f(x)_.
|
|
4
5
|
|
|
5
6
|
```sh
|
|
6
7
|
npm i -g fx
|
|
@@ -44,11 +45,56 @@ echo '{"name": "world"}' | fx '.name' '`Hello, ${this}!`'
|
|
|
44
45
|
Use other JS functions to process the data.
|
|
45
46
|
|
|
46
47
|
```sh
|
|
47
|
-
echo
|
|
48
|
+
echo '{"name": "world"}' | fx 'Object.keys'
|
|
48
49
|
```
|
|
49
50
|
|
|
50
51
|
## Advanced Usage
|
|
51
52
|
|
|
53
|
+
Fx can process a stream of json objects. Fx will apply arguments to each object.
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
echo '{"name": "hello"}\n{"name": "world"}' | fx '.name'
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
If you want to process a stream of json objects as a single array,
|
|
60
|
+
use the **--slurp** or **-s** flag.
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
echo '{"name": "hello"}\n{"name": "world"}' | fx --slurp '.map(x => x.name)' '.join(", ")'
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
If you want to process non-JSON data, use the **--raw** or **-r** flag.
|
|
67
|
+
|
|
68
|
+
```sh
|
|
69
|
+
ls | fx -r '[this, this.includes(".md")]'
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
You can use **--raw** and **--slurp** (or **-rs**) together to get a single array of strings.
|
|
73
|
+
|
|
74
|
+
```sh
|
|
75
|
+
ls | fx -rs '.filter(x => x.includes(".md"))'
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Fx has a special symbol **skip** for skipping the printing of the result.
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
ls | fx -r '.includes(".md") ? this : skip'
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Fx comes with a set of useful functions: **uniq**, **sort**, **groupBy**, **chunk**, **zip**.
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
cat file.json | fx 'uniq' 'sort' 'groupBy(x => x.name)'
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Fx works with promises.
|
|
91
|
+
|
|
92
|
+
```sh
|
|
93
|
+
echo '"https://medv.io/*"' | fx 'fetch' '.text()'
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Syntactic Sugar
|
|
97
|
+
|
|
52
98
|
Fx has a shortcut for the map function. Fox example, `this.map(x => x.commit.message)`
|
|
53
99
|
can be rewritten without leading dot and without `x => x` parts.
|
|
54
100
|
|
|
@@ -67,18 +113,20 @@ Fx has a special syntax for the flatMap function. Fox example,
|
|
|
67
113
|
curl https://api.github.com/repos/kubernetes/kubernetes/issues | fx '.[].labels[].name'
|
|
68
114
|
```
|
|
69
115
|
|
|
70
|
-
|
|
116
|
+
### .fxrc.js
|
|
71
117
|
|
|
72
|
-
|
|
73
|
-
|
|
118
|
+
Fx supports `.fxrc.js` file in the current directory or in the home directory.
|
|
119
|
+
|
|
120
|
+
Put the next code in the `.fxrc.js` file to make `myFunction` available in the fx.
|
|
121
|
+
|
|
122
|
+
```js
|
|
123
|
+
global.myFunction = x => x + 1
|
|
74
124
|
```
|
|
75
125
|
|
|
76
|
-
|
|
77
|
-
instead of JSON. This can be useful when working with non-JSON input data, such as
|
|
78
|
-
plain text or CSV data.
|
|
126
|
+
Now you can use `myFunction` in the fx.
|
|
79
127
|
|
|
80
128
|
```sh
|
|
81
|
-
|
|
129
|
+
echo '1' | fx 'myFunction'
|
|
82
130
|
```
|
|
83
131
|
|
|
84
132
|
## License
|
package/index.js
CHANGED
|
@@ -1,29 +1,43 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
2
4
|
void async function main() {
|
|
5
|
+
const os = await import('node:os')
|
|
3
6
|
const process = await import('node:process')
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
let flagHelp = false
|
|
8
|
+
let flagRaw = false
|
|
9
|
+
let flagSlurp = false
|
|
10
|
+
const args = []
|
|
11
|
+
for (const arg of process.argv.slice(2)) {
|
|
12
|
+
if (arg === '--help' || arg === '-h') flagHelp = true
|
|
13
|
+
else if (arg === '--raw' || arg === '-r') flagRaw = true
|
|
14
|
+
else if (arg === '--slurp' || arg === '-s') flagSlurp = true
|
|
15
|
+
else if (arg === '-rs' || arg === '-sr') flagRaw = flagSlurp = true
|
|
16
|
+
else args.push(arg)
|
|
9
17
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
process.
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
if (flagHelp || (args.length === 0 && process.stdin.isTTY))
|
|
19
|
+
return printUsage()
|
|
20
|
+
await importFxrc(process.cwd())
|
|
21
|
+
await importFxrc(os.homedir())
|
|
22
|
+
const stdin = await readStdinGenerator()
|
|
23
|
+
const input = flagRaw ? readLine(stdin) : parseJson(stdin)
|
|
24
|
+
if (flagSlurp) {
|
|
25
|
+
const array = []
|
|
26
|
+
for (const json of input) {
|
|
27
|
+
array.push(json)
|
|
28
|
+
}
|
|
29
|
+
await runTransforms(array, args)
|
|
30
|
+
} else {
|
|
31
|
+
for (const json of input) {
|
|
32
|
+
await runTransforms(json, args)
|
|
33
|
+
}
|
|
25
34
|
}
|
|
35
|
+
}()
|
|
26
36
|
|
|
37
|
+
const skip = Symbol('skip')
|
|
38
|
+
|
|
39
|
+
async function runTransforms(json, args) {
|
|
40
|
+
const process = await import('node:process')
|
|
27
41
|
let i, code, output = json
|
|
28
42
|
for ([i, code] of args.entries()) try {
|
|
29
43
|
output = await transform(output, code)
|
|
@@ -33,24 +47,26 @@ void async function main() {
|
|
|
33
47
|
}
|
|
34
48
|
|
|
35
49
|
if (typeof output === 'undefined')
|
|
36
|
-
|
|
50
|
+
console.error('undefined')
|
|
37
51
|
else if (typeof output === 'string')
|
|
38
52
|
console.log(output)
|
|
53
|
+
else if (output === skip)
|
|
54
|
+
return
|
|
39
55
|
else
|
|
40
|
-
console.log(
|
|
56
|
+
console.log(stringify(output, process.stdout.isTTY))
|
|
41
57
|
|
|
42
58
|
function printErr(err) {
|
|
43
59
|
let pre = args.slice(0, i).join(' ')
|
|
44
60
|
let post = args.slice(i + 1).join(' ')
|
|
45
61
|
if (pre.length > 20) pre = '...' + pre.substring(pre.length - 20)
|
|
46
62
|
if (post.length > 20) post = post.substring(0, 20) + '...'
|
|
47
|
-
|
|
63
|
+
console.error(
|
|
48
64
|
`\n ${pre} ${code} ${post}\n` +
|
|
49
65
|
` ${' '.repeat(pre.length + 1)}${'^'.repeat(code.length)}\n` +
|
|
50
|
-
`\n${err.stack || err}
|
|
66
|
+
`\n${err.stack || err}`
|
|
51
67
|
)
|
|
52
68
|
}
|
|
53
|
-
}
|
|
69
|
+
}
|
|
54
70
|
|
|
55
71
|
async function transform(json, code) {
|
|
56
72
|
if ('.' === code)
|
|
@@ -87,41 +103,466 @@ async function transform(json, code) {
|
|
|
87
103
|
})`).call(json)
|
|
88
104
|
|
|
89
105
|
return apply(fn, json)
|
|
106
|
+
|
|
107
|
+
function apply(fn, ...args) {
|
|
108
|
+
if (typeof fn === 'function') return fn(...args)
|
|
109
|
+
return fn
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function fold(s) {
|
|
113
|
+
if (s.length === 1)
|
|
114
|
+
return 'x => x' + s[0]
|
|
115
|
+
let obj = s.shift()
|
|
116
|
+
obj = obj === '.' ? 'x' : 'x' + obj
|
|
117
|
+
return `x => Object.values(${obj}).flatMap(${fold(s)})`
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function uniq(array) {
|
|
121
|
+
return [...new Set(array)]
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function sort(array) {
|
|
125
|
+
return array.sort()
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function groupBy(keyOrFunction) {
|
|
129
|
+
return array => {
|
|
130
|
+
const grouped = {}
|
|
131
|
+
for (const item of array) {
|
|
132
|
+
const key = typeof keyOrFunction === 'function'
|
|
133
|
+
? keyOrFunction(item)
|
|
134
|
+
: item[keyOrFunction]
|
|
135
|
+
if (!grouped.hasOwnProperty(key))
|
|
136
|
+
grouped[key] = []
|
|
137
|
+
grouped[key].push(item)
|
|
138
|
+
}
|
|
139
|
+
return grouped
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function chunk(size) {
|
|
144
|
+
return function (arr) {
|
|
145
|
+
const res = []
|
|
146
|
+
let i = 0
|
|
147
|
+
while (i < arr.length) {
|
|
148
|
+
res.push(arr.slice(i, i += size))
|
|
149
|
+
}
|
|
150
|
+
return res
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function zip(...arrays) {
|
|
155
|
+
const length = Math.min(...arrays.map(a => a.length))
|
|
156
|
+
const res = []
|
|
157
|
+
for (let i = 0; i < length; i++) {
|
|
158
|
+
res.push(arrays.map(a => a[i]))
|
|
159
|
+
}
|
|
160
|
+
return res
|
|
161
|
+
}
|
|
90
162
|
}
|
|
91
163
|
|
|
92
|
-
function
|
|
93
|
-
|
|
94
|
-
|
|
164
|
+
async function readStdinGenerator() {
|
|
165
|
+
const fs = await import('node:fs')
|
|
166
|
+
const {Buffer} = await import('node:buffer')
|
|
167
|
+
const {StringDecoder} = await import('node:string_decoder')
|
|
168
|
+
const decoder = new StringDecoder('utf8')
|
|
169
|
+
return function* () {
|
|
170
|
+
while (true) {
|
|
171
|
+
const buffer = Buffer.alloc(4_096)
|
|
172
|
+
let bytesRead
|
|
173
|
+
try {
|
|
174
|
+
bytesRead = fs.readSync(0, buffer, 0, buffer.length, null)
|
|
175
|
+
} catch (e) {
|
|
176
|
+
if (e.code === 'EAGAIN' || e.code === 'EWOULDBLOCK') {
|
|
177
|
+
sleepSync(10)
|
|
178
|
+
continue
|
|
179
|
+
}
|
|
180
|
+
if (e.code === 'EOF') break
|
|
181
|
+
throw e
|
|
182
|
+
}
|
|
183
|
+
if (bytesRead === 0) break
|
|
184
|
+
for (const ch of decoder.write(buffer.subarray(0, bytesRead)))
|
|
185
|
+
yield ch
|
|
186
|
+
}
|
|
187
|
+
for (const ch of decoder.end())
|
|
188
|
+
yield ch
|
|
189
|
+
}()
|
|
95
190
|
}
|
|
96
191
|
|
|
97
|
-
function
|
|
98
|
-
|
|
99
|
-
return 'x => x' + s[0]
|
|
100
|
-
let obj = s.shift()
|
|
101
|
-
obj = obj === '.' ? 'x' : 'x' + obj
|
|
102
|
-
return `x => Object.values(${obj}).flatMap(${fold(s)})`
|
|
192
|
+
function sleepSync(ms) {
|
|
193
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms)
|
|
103
194
|
}
|
|
104
195
|
|
|
105
|
-
function
|
|
106
|
-
|
|
196
|
+
function* readLine(stdin) {
|
|
197
|
+
let buffer = ''
|
|
198
|
+
for (const ch of stdin) {
|
|
199
|
+
if (ch === '\n') {
|
|
200
|
+
yield buffer
|
|
201
|
+
buffer = ''
|
|
202
|
+
} else {
|
|
203
|
+
buffer += ch
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return buffer
|
|
107
207
|
}
|
|
108
208
|
|
|
109
|
-
function
|
|
110
|
-
|
|
209
|
+
function* parseJson(stdin) {
|
|
210
|
+
let lineNumber = 1, buffer = '', lastChar, done = false
|
|
211
|
+
|
|
212
|
+
function next() {
|
|
213
|
+
({value: lastChar, done} = stdin.next())
|
|
214
|
+
if (lastChar === '\n') lineNumber++
|
|
215
|
+
buffer += (lastChar || '')
|
|
216
|
+
if (buffer.length > 100) buffer = buffer.slice(-40)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
next()
|
|
220
|
+
while (!done) {
|
|
221
|
+
const value = parseValue()
|
|
222
|
+
expectValue(value)
|
|
223
|
+
yield value
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function parseValue() {
|
|
227
|
+
skipWhitespace()
|
|
228
|
+
const value =
|
|
229
|
+
parseString() ??
|
|
230
|
+
parseNumber() ??
|
|
231
|
+
parseObject() ??
|
|
232
|
+
parseArray() ??
|
|
233
|
+
parseKeyword('true', true) ??
|
|
234
|
+
parseKeyword('false', false) ??
|
|
235
|
+
parseKeyword('null', null)
|
|
236
|
+
skipWhitespace()
|
|
237
|
+
return value
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function parseString() {
|
|
241
|
+
if (lastChar !== '"') return
|
|
242
|
+
let str = ''
|
|
243
|
+
let escaped = false
|
|
244
|
+
while (true) {
|
|
245
|
+
next()
|
|
246
|
+
if (escaped) {
|
|
247
|
+
if (lastChar === 'u') {
|
|
248
|
+
let unicode = ''
|
|
249
|
+
for (let i = 0; i < 4; i++) {
|
|
250
|
+
next()
|
|
251
|
+
if (!isHexDigit(lastChar)) {
|
|
252
|
+
throw new SyntaxError(errorSnippet(`Invalid Unicode escape sequence '\\u${unicode}${lastChar}'`))
|
|
253
|
+
}
|
|
254
|
+
unicode += lastChar
|
|
255
|
+
}
|
|
256
|
+
str += String.fromCharCode(parseInt(unicode, 16))
|
|
257
|
+
} else {
|
|
258
|
+
const escapedChar = {
|
|
259
|
+
'"': '"',
|
|
260
|
+
'\\': '\\',
|
|
261
|
+
'/': '/',
|
|
262
|
+
'b': '\b',
|
|
263
|
+
'f': '\f',
|
|
264
|
+
'n': '\n',
|
|
265
|
+
'r': '\r',
|
|
266
|
+
't': '\t'
|
|
267
|
+
}[lastChar]
|
|
268
|
+
if (!escapedChar) {
|
|
269
|
+
throw new SyntaxError(errorSnippet())
|
|
270
|
+
}
|
|
271
|
+
str += escapedChar
|
|
272
|
+
}
|
|
273
|
+
escaped = false
|
|
274
|
+
} else if (lastChar === '\\') {
|
|
275
|
+
escaped = true
|
|
276
|
+
} else if (lastChar === '"') {
|
|
277
|
+
break
|
|
278
|
+
} else if (lastChar === undefined) {
|
|
279
|
+
throw new SyntaxError(errorSnippet())
|
|
280
|
+
} else {
|
|
281
|
+
str += lastChar
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
next()
|
|
285
|
+
return str
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function parseNumber() {
|
|
289
|
+
if (!isDigit(lastChar) && lastChar !== '-') return
|
|
290
|
+
let numStr = ''
|
|
291
|
+
if (lastChar === '-') {
|
|
292
|
+
numStr += lastChar
|
|
293
|
+
next()
|
|
294
|
+
if (!isDigit(lastChar)) {
|
|
295
|
+
throw new SyntaxError(errorSnippet())
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (lastChar === '0') {
|
|
299
|
+
numStr += lastChar
|
|
300
|
+
next()
|
|
301
|
+
} else {
|
|
302
|
+
while (isDigit(lastChar)) {
|
|
303
|
+
numStr += lastChar
|
|
304
|
+
next()
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (lastChar === '.') {
|
|
308
|
+
numStr += lastChar
|
|
309
|
+
next()
|
|
310
|
+
if (!isDigit(lastChar)) {
|
|
311
|
+
throw new SyntaxError(errorSnippet())
|
|
312
|
+
}
|
|
313
|
+
while (isDigit(lastChar)) {
|
|
314
|
+
numStr += lastChar
|
|
315
|
+
next()
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (lastChar === 'e' || lastChar === 'E') {
|
|
319
|
+
numStr += lastChar
|
|
320
|
+
next()
|
|
321
|
+
if (lastChar === '+' || lastChar === '-') {
|
|
322
|
+
numStr += lastChar
|
|
323
|
+
next()
|
|
324
|
+
}
|
|
325
|
+
if (!isDigit(lastChar)) {
|
|
326
|
+
throw new SyntaxError(errorSnippet())
|
|
327
|
+
}
|
|
328
|
+
while (isDigit(lastChar)) {
|
|
329
|
+
numStr += lastChar
|
|
330
|
+
next()
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return isInteger(numStr) ? toSafeNumber(numStr) : parseFloat(numStr)
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function parseObject() {
|
|
337
|
+
if (lastChar !== '{') return
|
|
338
|
+
next()
|
|
339
|
+
skipWhitespace()
|
|
340
|
+
const obj = {}
|
|
341
|
+
if (lastChar === '}') {
|
|
342
|
+
next()
|
|
343
|
+
return obj
|
|
344
|
+
}
|
|
345
|
+
while (true) {
|
|
346
|
+
if (lastChar !== '"') {
|
|
347
|
+
throw new SyntaxError(errorSnippet())
|
|
348
|
+
}
|
|
349
|
+
const key = parseString()
|
|
350
|
+
skipWhitespace()
|
|
351
|
+
if (lastChar !== ':') {
|
|
352
|
+
throw new SyntaxError(errorSnippet())
|
|
353
|
+
}
|
|
354
|
+
next()
|
|
355
|
+
const value = parseValue()
|
|
356
|
+
expectValue(value)
|
|
357
|
+
obj[key] = value
|
|
358
|
+
skipWhitespace()
|
|
359
|
+
if (lastChar === '}') {
|
|
360
|
+
next()
|
|
361
|
+
return obj
|
|
362
|
+
} else if (lastChar === ',') {
|
|
363
|
+
next()
|
|
364
|
+
skipWhitespace()
|
|
365
|
+
if (lastChar === '}') {
|
|
366
|
+
next()
|
|
367
|
+
return obj
|
|
368
|
+
}
|
|
369
|
+
} else {
|
|
370
|
+
throw new SyntaxError(errorSnippet())
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function parseArray() {
|
|
376
|
+
if (lastChar !== '[') return
|
|
377
|
+
next()
|
|
378
|
+
skipWhitespace()
|
|
379
|
+
const array = []
|
|
380
|
+
if (lastChar === ']') {
|
|
381
|
+
next()
|
|
382
|
+
return array
|
|
383
|
+
}
|
|
384
|
+
while (true) {
|
|
385
|
+
const value = parseValue()
|
|
386
|
+
expectValue(value)
|
|
387
|
+
array.push(value)
|
|
388
|
+
skipWhitespace()
|
|
389
|
+
if (lastChar === ']') {
|
|
390
|
+
next()
|
|
391
|
+
return array
|
|
392
|
+
} else if (lastChar === ',') {
|
|
393
|
+
next()
|
|
394
|
+
skipWhitespace()
|
|
395
|
+
if (lastChar === ']') {
|
|
396
|
+
next()
|
|
397
|
+
return array
|
|
398
|
+
}
|
|
399
|
+
} else {
|
|
400
|
+
throw new SyntaxError(errorSnippet())
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function parseKeyword(name, value) {
|
|
406
|
+
if (lastChar !== name[0]) return
|
|
407
|
+
for (let i = 1; i < name.length; i++) {
|
|
408
|
+
next()
|
|
409
|
+
if (lastChar !== name[i]) {
|
|
410
|
+
throw new SyntaxError(errorSnippet())
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
next()
|
|
414
|
+
if (isWhitespace(lastChar) || lastChar === ',' || lastChar === '}' || lastChar === ']' || lastChar === undefined) {
|
|
415
|
+
return value
|
|
416
|
+
}
|
|
417
|
+
throw new SyntaxError(errorSnippet())
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function skipWhitespace() {
|
|
421
|
+
while (isWhitespace(lastChar)) {
|
|
422
|
+
next()
|
|
423
|
+
}
|
|
424
|
+
skipComment()
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function skipComment() {
|
|
428
|
+
if (lastChar === '/') {
|
|
429
|
+
next()
|
|
430
|
+
if (lastChar === '/') {
|
|
431
|
+
while (!done && lastChar !== '\n') {
|
|
432
|
+
next()
|
|
433
|
+
}
|
|
434
|
+
skipWhitespace()
|
|
435
|
+
} else if (lastChar === '*') {
|
|
436
|
+
while (!done) {
|
|
437
|
+
next()
|
|
438
|
+
if (lastChar === '*') {
|
|
439
|
+
next()
|
|
440
|
+
if (lastChar === '/') {
|
|
441
|
+
next()
|
|
442
|
+
break
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
skipWhitespace()
|
|
447
|
+
} else {
|
|
448
|
+
throw new SyntaxError(errorSnippet())
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function isWhitespace(ch) {
|
|
454
|
+
return ch === ' ' || ch === '\n' || ch === '\t' || ch === '\r'
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function isHexDigit(ch) {
|
|
458
|
+
return (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function isDigit(ch) {
|
|
462
|
+
return ch >= '0' && ch <= '9'
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function isInteger(value) {
|
|
466
|
+
return /^-?[0-9]+$/.test(value)
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function toSafeNumber(str) {
|
|
470
|
+
const maxSafeInteger = Number.MAX_SAFE_INTEGER
|
|
471
|
+
const minSafeInteger = Number.MIN_SAFE_INTEGER
|
|
472
|
+
const num = BigInt(str)
|
|
473
|
+
return num >= minSafeInteger && num <= maxSafeInteger ? Number(num) : num
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function expectValue(value) {
|
|
477
|
+
if (value === undefined) {
|
|
478
|
+
throw new SyntaxError(errorSnippet(`JSON value expected`))
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function errorSnippet(message = `Unexpected character '${lastChar}'`) {
|
|
483
|
+
if (!lastChar) {
|
|
484
|
+
message = 'Unexpected end of input'
|
|
485
|
+
}
|
|
486
|
+
const lines = buffer.slice(-40).split('\n')
|
|
487
|
+
const lastLine = lines.pop()
|
|
488
|
+
const source =
|
|
489
|
+
lines.map(line => ` ${line}\n`).join('')
|
|
490
|
+
+ ` ${lastLine}${readEOL()}\n`
|
|
491
|
+
const p = ` ${'.'.repeat(Math.max(0, lastLine.length - 1))}^\n`
|
|
492
|
+
return `${message} on line ${lineNumber}.\n\n${source}${p}`
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function readEOL() {
|
|
496
|
+
let line = ''
|
|
497
|
+
for (const ch of stdin) {
|
|
498
|
+
if (!ch || ch === '\n' || line.length >= 60) break
|
|
499
|
+
line += ch
|
|
500
|
+
}
|
|
501
|
+
return line
|
|
502
|
+
}
|
|
111
503
|
}
|
|
112
504
|
|
|
113
|
-
function
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
505
|
+
function stringify(value, isPretty = false) {
|
|
506
|
+
const colors = {
|
|
507
|
+
key: isPretty ? '\x1b[1;34m' : '',
|
|
508
|
+
string: isPretty ? '\x1b[32m' : '',
|
|
509
|
+
number: isPretty ? '\x1b[36m' : '',
|
|
510
|
+
boolean: isPretty ? '\x1b[35m' : '',
|
|
511
|
+
null: isPretty ? '\x1b[2m' : '',
|
|
512
|
+
reset: isPretty ? '\x1b[0m' : '',
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function getIndent(level) {
|
|
516
|
+
return ' '.repeat(2 * level)
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function stringifyValue(value, level = 0) {
|
|
520
|
+
if (typeof value === 'string') {
|
|
521
|
+
return `${colors.string}"${value}"${colors.reset}`
|
|
522
|
+
} else if (typeof value === 'number') {
|
|
523
|
+
return `${colors.number}${value}${colors.reset}`
|
|
524
|
+
} else if (typeof value === 'bigint') {
|
|
525
|
+
return `${colors.number}${value}${colors.reset}`
|
|
526
|
+
} else if (typeof value === 'boolean') {
|
|
527
|
+
return `${colors.boolean}${value}${colors.reset}`
|
|
528
|
+
} else if (value === null || typeof value === 'undefined') {
|
|
529
|
+
return `${colors.null}null${colors.reset}`
|
|
530
|
+
} else if (Array.isArray(value)) {
|
|
531
|
+
if (value.length === 0) {
|
|
532
|
+
return `[]`
|
|
533
|
+
}
|
|
534
|
+
const items = value
|
|
535
|
+
.map((v) => `${getIndent(level + 1)}${stringifyValue(v, level + 1)}`)
|
|
536
|
+
.join(',\n')
|
|
537
|
+
return `[\n${items}\n${getIndent(level)}]`
|
|
538
|
+
} else if (typeof value === 'object') {
|
|
539
|
+
const keys = Object.keys(value)
|
|
540
|
+
if (keys.length === 0) {
|
|
541
|
+
return `{}`
|
|
542
|
+
}
|
|
543
|
+
const entries = keys
|
|
544
|
+
.map(
|
|
545
|
+
(key) =>
|
|
546
|
+
`${getIndent(level + 1)}${colors.key}"${key}"${colors.reset}: ${stringifyValue(
|
|
547
|
+
value[key],
|
|
548
|
+
level + 1
|
|
549
|
+
)}`
|
|
550
|
+
)
|
|
551
|
+
.join(',\n')
|
|
552
|
+
return `{\n${entries}\n${getIndent(level)}}`
|
|
123
553
|
}
|
|
124
|
-
|
|
554
|
+
throw new Error(`Unsupported value type: ${typeof value}`)
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
return stringifyValue(value)
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
async function importFxrc(path) {
|
|
561
|
+
const {join} = await import('node:path')
|
|
562
|
+
try {
|
|
563
|
+
await import(join(path, '.fxrc.js'))
|
|
564
|
+
} catch (err) {
|
|
565
|
+
if (err.code !== 'ERR_MODULE_NOT_FOUND') throw err
|
|
125
566
|
}
|
|
126
567
|
}
|
|
127
568
|
|
|
@@ -131,6 +572,7 @@ function printUsage() {
|
|
|
131
572
|
|
|
132
573
|
Flags
|
|
133
574
|
-h, --help Display this help message
|
|
134
|
-
-r, --raw Treat input as raw string
|
|
575
|
+
-r, --raw Treat input as a raw string
|
|
576
|
+
-s, --slurp Read all inputs into an array`
|
|
135
577
|
console.log(usage)
|
|
136
578
|
}
|