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