+1
.gitignore
+1
.gitignore
···
1
+
target
+779
Cargo.lock
+779
Cargo.lock
···
1
+
# This file is automatically @generated by Cargo.
2
+
# It is not intended for manual editing.
3
+
version = 4
4
+
5
+
[[package]]
6
+
name = "anstream"
7
+
version = "0.6.21"
8
+
source = "registry+https://github.com/rust-lang/crates.io-index"
9
+
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
10
+
dependencies = [
11
+
"anstyle",
12
+
"anstyle-parse",
13
+
"anstyle-query",
14
+
"anstyle-wincon",
15
+
"colorchoice",
16
+
"is_terminal_polyfill",
17
+
"utf8parse",
18
+
]
19
+
20
+
[[package]]
21
+
name = "anstyle"
22
+
version = "1.0.13"
23
+
source = "registry+https://github.com/rust-lang/crates.io-index"
24
+
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
25
+
26
+
[[package]]
27
+
name = "anstyle-parse"
28
+
version = "0.2.7"
29
+
source = "registry+https://github.com/rust-lang/crates.io-index"
30
+
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
31
+
dependencies = [
32
+
"utf8parse",
33
+
]
34
+
35
+
[[package]]
36
+
name = "anstyle-query"
37
+
version = "1.1.4"
38
+
source = "registry+https://github.com/rust-lang/crates.io-index"
39
+
checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2"
40
+
dependencies = [
41
+
"windows-sys 0.60.2",
42
+
]
43
+
44
+
[[package]]
45
+
name = "anstyle-wincon"
46
+
version = "3.0.10"
47
+
source = "registry+https://github.com/rust-lang/crates.io-index"
48
+
checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a"
49
+
dependencies = [
50
+
"anstyle",
51
+
"once_cell_polyfill",
52
+
"windows-sys 0.60.2",
53
+
]
54
+
55
+
[[package]]
56
+
name = "base64"
57
+
version = "0.22.1"
58
+
source = "registry+https://github.com/rust-lang/crates.io-index"
59
+
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
60
+
61
+
[[package]]
62
+
name = "bitflags"
63
+
version = "2.10.0"
64
+
source = "registry+https://github.com/rust-lang/crates.io-index"
65
+
checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
66
+
67
+
[[package]]
68
+
name = "block-buffer"
69
+
version = "0.10.4"
70
+
source = "registry+https://github.com/rust-lang/crates.io-index"
71
+
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
72
+
dependencies = [
73
+
"generic-array",
74
+
]
75
+
76
+
[[package]]
77
+
name = "cfg-if"
78
+
version = "1.0.4"
79
+
source = "registry+https://github.com/rust-lang/crates.io-index"
80
+
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
81
+
82
+
[[package]]
83
+
name = "clap"
84
+
version = "4.5.51"
85
+
source = "registry+https://github.com/rust-lang/crates.io-index"
86
+
checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5"
87
+
dependencies = [
88
+
"clap_builder",
89
+
"clap_derive",
90
+
]
91
+
92
+
[[package]]
93
+
name = "clap_builder"
94
+
version = "4.5.51"
95
+
source = "registry+https://github.com/rust-lang/crates.io-index"
96
+
checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a"
97
+
dependencies = [
98
+
"anstream",
99
+
"anstyle",
100
+
"clap_lex",
101
+
"strsim",
102
+
]
103
+
104
+
[[package]]
105
+
name = "clap_derive"
106
+
version = "4.5.49"
107
+
source = "registry+https://github.com/rust-lang/crates.io-index"
108
+
checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671"
109
+
dependencies = [
110
+
"heck",
111
+
"proc-macro2",
112
+
"quote",
113
+
"syn",
114
+
]
115
+
116
+
[[package]]
117
+
name = "clap_lex"
118
+
version = "0.7.6"
119
+
source = "registry+https://github.com/rust-lang/crates.io-index"
120
+
checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d"
121
+
122
+
[[package]]
123
+
name = "colorchoice"
124
+
version = "1.0.4"
125
+
source = "registry+https://github.com/rust-lang/crates.io-index"
126
+
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
127
+
128
+
[[package]]
129
+
name = "cpufeatures"
130
+
version = "0.2.17"
131
+
source = "registry+https://github.com/rust-lang/crates.io-index"
132
+
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
133
+
dependencies = [
134
+
"libc",
135
+
]
136
+
137
+
[[package]]
138
+
name = "crypto-common"
139
+
version = "0.1.7"
140
+
source = "registry+https://github.com/rust-lang/crates.io-index"
141
+
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
142
+
dependencies = [
143
+
"generic-array",
144
+
"typenum",
145
+
]
146
+
147
+
[[package]]
148
+
name = "diff"
149
+
version = "0.1.13"
150
+
source = "registry+https://github.com/rust-lang/crates.io-index"
151
+
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
152
+
153
+
[[package]]
154
+
name = "digest"
155
+
version = "0.10.7"
156
+
source = "registry+https://github.com/rust-lang/crates.io-index"
157
+
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
158
+
dependencies = [
159
+
"block-buffer",
160
+
"crypto-common",
161
+
]
162
+
163
+
[[package]]
164
+
name = "dirs"
165
+
version = "5.0.1"
166
+
source = "registry+https://github.com/rust-lang/crates.io-index"
167
+
checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225"
168
+
dependencies = [
169
+
"dirs-sys",
170
+
]
171
+
172
+
[[package]]
173
+
name = "dirs-sys"
174
+
version = "0.4.1"
175
+
source = "registry+https://github.com/rust-lang/crates.io-index"
176
+
checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c"
177
+
dependencies = [
178
+
"libc",
179
+
"option-ext",
180
+
"redox_users",
181
+
"windows-sys 0.48.0",
182
+
]
183
+
184
+
[[package]]
185
+
name = "equivalent"
186
+
version = "1.0.2"
187
+
source = "registry+https://github.com/rust-lang/crates.io-index"
188
+
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
189
+
190
+
[[package]]
191
+
name = "generic-array"
192
+
version = "0.14.7"
193
+
source = "registry+https://github.com/rust-lang/crates.io-index"
194
+
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
195
+
dependencies = [
196
+
"typenum",
197
+
"version_check",
198
+
]
199
+
200
+
[[package]]
201
+
name = "getrandom"
202
+
version = "0.2.16"
203
+
source = "registry+https://github.com/rust-lang/crates.io-index"
204
+
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
205
+
dependencies = [
206
+
"cfg-if",
207
+
"libc",
208
+
"wasi",
209
+
]
210
+
211
+
[[package]]
212
+
name = "hashbrown"
213
+
version = "0.16.0"
214
+
source = "registry+https://github.com/rust-lang/crates.io-index"
215
+
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
216
+
217
+
[[package]]
218
+
name = "heck"
219
+
version = "0.5.0"
220
+
source = "registry+https://github.com/rust-lang/crates.io-index"
221
+
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
222
+
223
+
[[package]]
224
+
name = "hex"
225
+
version = "0.4.3"
226
+
source = "registry+https://github.com/rust-lang/crates.io-index"
227
+
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
228
+
229
+
[[package]]
230
+
name = "indexmap"
231
+
version = "2.12.0"
232
+
source = "registry+https://github.com/rust-lang/crates.io-index"
233
+
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
234
+
dependencies = [
235
+
"equivalent",
236
+
"hashbrown",
237
+
]
238
+
239
+
[[package]]
240
+
name = "is_terminal_polyfill"
241
+
version = "1.70.2"
242
+
source = "registry+https://github.com/rust-lang/crates.io-index"
243
+
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
244
+
245
+
[[package]]
246
+
name = "itoa"
247
+
version = "1.0.15"
248
+
source = "registry+https://github.com/rust-lang/crates.io-index"
249
+
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
250
+
251
+
[[package]]
252
+
name = "libc"
253
+
version = "0.2.177"
254
+
source = "registry+https://github.com/rust-lang/crates.io-index"
255
+
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
256
+
257
+
[[package]]
258
+
name = "libredox"
259
+
version = "0.1.10"
260
+
source = "registry+https://github.com/rust-lang/crates.io-index"
261
+
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
262
+
dependencies = [
263
+
"bitflags",
264
+
"libc",
265
+
]
266
+
267
+
[[package]]
268
+
name = "memchr"
269
+
version = "2.7.6"
270
+
source = "registry+https://github.com/rust-lang/crates.io-index"
271
+
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
272
+
273
+
[[package]]
274
+
name = "once_cell_polyfill"
275
+
version = "1.70.2"
276
+
source = "registry+https://github.com/rust-lang/crates.io-index"
277
+
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
278
+
279
+
[[package]]
280
+
name = "option-ext"
281
+
version = "0.2.0"
282
+
source = "registry+https://github.com/rust-lang/crates.io-index"
283
+
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
284
+
285
+
[[package]]
286
+
name = "percent-encoding"
287
+
version = "2.3.2"
288
+
source = "registry+https://github.com/rust-lang/crates.io-index"
289
+
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
290
+
291
+
[[package]]
292
+
name = "pest"
293
+
version = "2.8.3"
294
+
source = "registry+https://github.com/rust-lang/crates.io-index"
295
+
checksum = "989e7521a040efde50c3ab6bbadafbe15ab6dc042686926be59ac35d74607df4"
296
+
dependencies = [
297
+
"memchr",
298
+
"ucd-trie",
299
+
]
300
+
301
+
[[package]]
302
+
name = "pest_derive"
303
+
version = "2.8.3"
304
+
source = "registry+https://github.com/rust-lang/crates.io-index"
305
+
checksum = "187da9a3030dbafabbbfb20cb323b976dc7b7ce91fcd84f2f74d6e31d378e2de"
306
+
dependencies = [
307
+
"pest",
308
+
"pest_generator",
309
+
]
310
+
311
+
[[package]]
312
+
name = "pest_generator"
313
+
version = "2.8.3"
314
+
source = "registry+https://github.com/rust-lang/crates.io-index"
315
+
checksum = "49b401d98f5757ebe97a26085998d6c0eecec4995cad6ab7fc30ffdf4b052843"
316
+
dependencies = [
317
+
"pest",
318
+
"pest_meta",
319
+
"proc-macro2",
320
+
"quote",
321
+
"syn",
322
+
]
323
+
324
+
[[package]]
325
+
name = "pest_meta"
326
+
version = "2.8.3"
327
+
source = "registry+https://github.com/rust-lang/crates.io-index"
328
+
checksum = "72f27a2cfee9f9039c4d86faa5af122a0ac3851441a34865b8a043b46be0065a"
329
+
dependencies = [
330
+
"pest",
331
+
"sha2",
332
+
]
333
+
334
+
[[package]]
335
+
name = "pretty_assertions"
336
+
version = "1.4.1"
337
+
source = "registry+https://github.com/rust-lang/crates.io-index"
338
+
checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
339
+
dependencies = [
340
+
"diff",
341
+
"yansi",
342
+
]
343
+
344
+
[[package]]
345
+
name = "proc-macro2"
346
+
version = "1.0.103"
347
+
source = "registry+https://github.com/rust-lang/crates.io-index"
348
+
checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8"
349
+
dependencies = [
350
+
"unicode-ident",
351
+
]
352
+
353
+
[[package]]
354
+
name = "quote"
355
+
version = "1.0.42"
356
+
source = "registry+https://github.com/rust-lang/crates.io-index"
357
+
checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f"
358
+
dependencies = [
359
+
"proc-macro2",
360
+
]
361
+
362
+
[[package]]
363
+
name = "redox_users"
364
+
version = "0.4.6"
365
+
source = "registry+https://github.com/rust-lang/crates.io-index"
366
+
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
367
+
dependencies = [
368
+
"getrandom",
369
+
"libredox",
370
+
"thiserror 1.0.69",
371
+
]
372
+
373
+
[[package]]
374
+
name = "ryu"
375
+
version = "1.0.20"
376
+
source = "registry+https://github.com/rust-lang/crates.io-index"
377
+
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
378
+
379
+
[[package]]
380
+
name = "serde"
381
+
version = "1.0.228"
382
+
source = "registry+https://github.com/rust-lang/crates.io-index"
383
+
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
384
+
dependencies = [
385
+
"serde_core",
386
+
"serde_derive",
387
+
]
388
+
389
+
[[package]]
390
+
name = "serde_core"
391
+
version = "1.0.228"
392
+
source = "registry+https://github.com/rust-lang/crates.io-index"
393
+
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
394
+
dependencies = [
395
+
"serde_derive",
396
+
]
397
+
398
+
[[package]]
399
+
name = "serde_derive"
400
+
version = "1.0.228"
401
+
source = "registry+https://github.com/rust-lang/crates.io-index"
402
+
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
403
+
dependencies = [
404
+
"proc-macro2",
405
+
"quote",
406
+
"syn",
407
+
]
408
+
409
+
[[package]]
410
+
name = "serde_json"
411
+
version = "1.0.145"
412
+
source = "registry+https://github.com/rust-lang/crates.io-index"
413
+
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
414
+
dependencies = [
415
+
"itoa",
416
+
"memchr",
417
+
"ryu",
418
+
"serde",
419
+
"serde_core",
420
+
]
421
+
422
+
[[package]]
423
+
name = "serde_qs"
424
+
version = "0.13.0"
425
+
source = "registry+https://github.com/rust-lang/crates.io-index"
426
+
checksum = "cd34f36fe4c5ba9654417139a9b3a20d2e1de6012ee678ad14d240c22c78d8d6"
427
+
dependencies = [
428
+
"percent-encoding",
429
+
"serde",
430
+
"thiserror 1.0.69",
431
+
]
432
+
433
+
[[package]]
434
+
name = "serde_spanned"
435
+
version = "1.0.3"
436
+
source = "registry+https://github.com/rust-lang/crates.io-index"
437
+
checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392"
438
+
dependencies = [
439
+
"serde_core",
440
+
]
441
+
442
+
[[package]]
443
+
name = "serde_yaml"
444
+
version = "0.9.34+deprecated"
445
+
source = "registry+https://github.com/rust-lang/crates.io-index"
446
+
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
447
+
dependencies = [
448
+
"indexmap",
449
+
"itoa",
450
+
"ryu",
451
+
"serde",
452
+
"unsafe-libyaml",
453
+
]
454
+
455
+
[[package]]
456
+
name = "sha2"
457
+
version = "0.10.9"
458
+
source = "registry+https://github.com/rust-lang/crates.io-index"
459
+
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
460
+
dependencies = [
461
+
"cfg-if",
462
+
"cpufeatures",
463
+
"digest",
464
+
]
465
+
466
+
[[package]]
467
+
name = "strsim"
468
+
version = "0.11.1"
469
+
source = "registry+https://github.com/rust-lang/crates.io-index"
470
+
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
471
+
472
+
[[package]]
473
+
name = "syn"
474
+
version = "2.0.110"
475
+
source = "registry+https://github.com/rust-lang/crates.io-index"
476
+
checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea"
477
+
dependencies = [
478
+
"proc-macro2",
479
+
"quote",
480
+
"unicode-ident",
481
+
]
482
+
483
+
[[package]]
484
+
name = "thiserror"
485
+
version = "1.0.69"
486
+
source = "registry+https://github.com/rust-lang/crates.io-index"
487
+
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
488
+
dependencies = [
489
+
"thiserror-impl 1.0.69",
490
+
]
491
+
492
+
[[package]]
493
+
name = "thiserror"
494
+
version = "2.0.17"
495
+
source = "registry+https://github.com/rust-lang/crates.io-index"
496
+
checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8"
497
+
dependencies = [
498
+
"thiserror-impl 2.0.17",
499
+
]
500
+
501
+
[[package]]
502
+
name = "thiserror-impl"
503
+
version = "1.0.69"
504
+
source = "registry+https://github.com/rust-lang/crates.io-index"
505
+
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
506
+
dependencies = [
507
+
"proc-macro2",
508
+
"quote",
509
+
"syn",
510
+
]
511
+
512
+
[[package]]
513
+
name = "thiserror-impl"
514
+
version = "2.0.17"
515
+
source = "registry+https://github.com/rust-lang/crates.io-index"
516
+
checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913"
517
+
dependencies = [
518
+
"proc-macro2",
519
+
"quote",
520
+
"syn",
521
+
]
522
+
523
+
[[package]]
524
+
name = "toml"
525
+
version = "0.9.8"
526
+
source = "registry+https://github.com/rust-lang/crates.io-index"
527
+
checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8"
528
+
dependencies = [
529
+
"indexmap",
530
+
"serde_core",
531
+
"serde_spanned",
532
+
"toml_datetime",
533
+
"toml_parser",
534
+
"toml_writer",
535
+
"winnow",
536
+
]
537
+
538
+
[[package]]
539
+
name = "toml_datetime"
540
+
version = "0.7.3"
541
+
source = "registry+https://github.com/rust-lang/crates.io-index"
542
+
checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533"
543
+
dependencies = [
544
+
"serde_core",
545
+
]
546
+
547
+
[[package]]
548
+
name = "toml_parser"
549
+
version = "1.0.4"
550
+
source = "registry+https://github.com/rust-lang/crates.io-index"
551
+
checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e"
552
+
dependencies = [
553
+
"winnow",
554
+
]
555
+
556
+
[[package]]
557
+
name = "toml_writer"
558
+
version = "1.0.4"
559
+
source = "registry+https://github.com/rust-lang/crates.io-index"
560
+
checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2"
561
+
562
+
[[package]]
563
+
name = "typenum"
564
+
version = "1.19.0"
565
+
source = "registry+https://github.com/rust-lang/crates.io-index"
566
+
checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
567
+
568
+
[[package]]
569
+
name = "ucd-trie"
570
+
version = "0.1.7"
571
+
source = "registry+https://github.com/rust-lang/crates.io-index"
572
+
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
573
+
574
+
[[package]]
575
+
name = "unicode-ident"
576
+
version = "1.0.22"
577
+
source = "registry+https://github.com/rust-lang/crates.io-index"
578
+
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
579
+
580
+
[[package]]
581
+
name = "unsafe-libyaml"
582
+
version = "0.2.11"
583
+
source = "registry+https://github.com/rust-lang/crates.io-index"
584
+
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
585
+
586
+
[[package]]
587
+
name = "uson"
588
+
version = "0.1.0"
589
+
dependencies = [
590
+
"base64",
591
+
"clap",
592
+
"dirs",
593
+
"hex",
594
+
"pest",
595
+
"pest_derive",
596
+
"pretty_assertions",
597
+
"serde",
598
+
"serde_json",
599
+
"serde_qs",
600
+
"serde_yaml",
601
+
"thiserror 2.0.17",
602
+
"toml",
603
+
]
604
+
605
+
[[package]]
606
+
name = "utf8parse"
607
+
version = "0.2.2"
608
+
source = "registry+https://github.com/rust-lang/crates.io-index"
609
+
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
610
+
611
+
[[package]]
612
+
name = "version_check"
613
+
version = "0.9.5"
614
+
source = "registry+https://github.com/rust-lang/crates.io-index"
615
+
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
616
+
617
+
[[package]]
618
+
name = "wasi"
619
+
version = "0.11.1+wasi-snapshot-preview1"
620
+
source = "registry+https://github.com/rust-lang/crates.io-index"
621
+
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
622
+
623
+
[[package]]
624
+
name = "windows-link"
625
+
version = "0.2.1"
626
+
source = "registry+https://github.com/rust-lang/crates.io-index"
627
+
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
628
+
629
+
[[package]]
630
+
name = "windows-sys"
631
+
version = "0.48.0"
632
+
source = "registry+https://github.com/rust-lang/crates.io-index"
633
+
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
634
+
dependencies = [
635
+
"windows-targets 0.48.5",
636
+
]
637
+
638
+
[[package]]
639
+
name = "windows-sys"
640
+
version = "0.60.2"
641
+
source = "registry+https://github.com/rust-lang/crates.io-index"
642
+
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
643
+
dependencies = [
644
+
"windows-targets 0.53.5",
645
+
]
646
+
647
+
[[package]]
648
+
name = "windows-targets"
649
+
version = "0.48.5"
650
+
source = "registry+https://github.com/rust-lang/crates.io-index"
651
+
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
652
+
dependencies = [
653
+
"windows_aarch64_gnullvm 0.48.5",
654
+
"windows_aarch64_msvc 0.48.5",
655
+
"windows_i686_gnu 0.48.5",
656
+
"windows_i686_msvc 0.48.5",
657
+
"windows_x86_64_gnu 0.48.5",
658
+
"windows_x86_64_gnullvm 0.48.5",
659
+
"windows_x86_64_msvc 0.48.5",
660
+
]
661
+
662
+
[[package]]
663
+
name = "windows-targets"
664
+
version = "0.53.5"
665
+
source = "registry+https://github.com/rust-lang/crates.io-index"
666
+
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
667
+
dependencies = [
668
+
"windows-link",
669
+
"windows_aarch64_gnullvm 0.53.1",
670
+
"windows_aarch64_msvc 0.53.1",
671
+
"windows_i686_gnu 0.53.1",
672
+
"windows_i686_gnullvm",
673
+
"windows_i686_msvc 0.53.1",
674
+
"windows_x86_64_gnu 0.53.1",
675
+
"windows_x86_64_gnullvm 0.53.1",
676
+
"windows_x86_64_msvc 0.53.1",
677
+
]
678
+
679
+
[[package]]
680
+
name = "windows_aarch64_gnullvm"
681
+
version = "0.48.5"
682
+
source = "registry+https://github.com/rust-lang/crates.io-index"
683
+
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
684
+
685
+
[[package]]
686
+
name = "windows_aarch64_gnullvm"
687
+
version = "0.53.1"
688
+
source = "registry+https://github.com/rust-lang/crates.io-index"
689
+
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
690
+
691
+
[[package]]
692
+
name = "windows_aarch64_msvc"
693
+
version = "0.48.5"
694
+
source = "registry+https://github.com/rust-lang/crates.io-index"
695
+
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
696
+
697
+
[[package]]
698
+
name = "windows_aarch64_msvc"
699
+
version = "0.53.1"
700
+
source = "registry+https://github.com/rust-lang/crates.io-index"
701
+
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
702
+
703
+
[[package]]
704
+
name = "windows_i686_gnu"
705
+
version = "0.48.5"
706
+
source = "registry+https://github.com/rust-lang/crates.io-index"
707
+
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
708
+
709
+
[[package]]
710
+
name = "windows_i686_gnu"
711
+
version = "0.53.1"
712
+
source = "registry+https://github.com/rust-lang/crates.io-index"
713
+
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
714
+
715
+
[[package]]
716
+
name = "windows_i686_gnullvm"
717
+
version = "0.53.1"
718
+
source = "registry+https://github.com/rust-lang/crates.io-index"
719
+
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
720
+
721
+
[[package]]
722
+
name = "windows_i686_msvc"
723
+
version = "0.48.5"
724
+
source = "registry+https://github.com/rust-lang/crates.io-index"
725
+
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
726
+
727
+
[[package]]
728
+
name = "windows_i686_msvc"
729
+
version = "0.53.1"
730
+
source = "registry+https://github.com/rust-lang/crates.io-index"
731
+
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
732
+
733
+
[[package]]
734
+
name = "windows_x86_64_gnu"
735
+
version = "0.48.5"
736
+
source = "registry+https://github.com/rust-lang/crates.io-index"
737
+
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
738
+
739
+
[[package]]
740
+
name = "windows_x86_64_gnu"
741
+
version = "0.53.1"
742
+
source = "registry+https://github.com/rust-lang/crates.io-index"
743
+
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
744
+
745
+
[[package]]
746
+
name = "windows_x86_64_gnullvm"
747
+
version = "0.48.5"
748
+
source = "registry+https://github.com/rust-lang/crates.io-index"
749
+
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
750
+
751
+
[[package]]
752
+
name = "windows_x86_64_gnullvm"
753
+
version = "0.53.1"
754
+
source = "registry+https://github.com/rust-lang/crates.io-index"
755
+
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
756
+
757
+
[[package]]
758
+
name = "windows_x86_64_msvc"
759
+
version = "0.48.5"
760
+
source = "registry+https://github.com/rust-lang/crates.io-index"
761
+
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
762
+
763
+
[[package]]
764
+
name = "windows_x86_64_msvc"
765
+
version = "0.53.1"
766
+
source = "registry+https://github.com/rust-lang/crates.io-index"
767
+
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
768
+
769
+
[[package]]
770
+
name = "winnow"
771
+
version = "0.7.13"
772
+
source = "registry+https://github.com/rust-lang/crates.io-index"
773
+
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
774
+
775
+
[[package]]
776
+
name = "yansi"
777
+
version = "1.0.1"
778
+
source = "registry+https://github.com/rust-lang/crates.io-index"
779
+
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
+38
Cargo.toml
+38
Cargo.toml
···
1
+
[package]
2
+
name = "uson"
3
+
version = "0.1.0"
4
+
edition = "2021"
5
+
authors = ["Your Name"]
6
+
description = "A Rust parser for μson (microson) - a relaxed JSON variant"
7
+
license = "MIT"
8
+
readme = "README.md"
9
+
10
+
[[bin]]
11
+
name = "uson"
12
+
path = "src/main.rs"
13
+
14
+
[dependencies]
15
+
pest = "2.7"
16
+
pest_derive = "2.7"
17
+
serde = { version = "1.0", features = ["derive"] }
18
+
serde_json = "1.0"
19
+
thiserror = "2.0"
20
+
clap = { version = "4.5", features = ["derive"] }
21
+
base64 = "0.22"
22
+
hex = "0.4"
23
+
serde_yaml = { version = "0.9", optional = true }
24
+
serde_qs = { version = "0.13", optional = true }
25
+
toml = "0.9"
26
+
dirs = "5.0"
27
+
28
+
[dev-dependencies]
29
+
pretty_assertions = "1.4"
30
+
31
+
[features]
32
+
default = []
33
+
yaml = ["dep:serde_yaml"]
34
+
form = ["dep:serde_qs"]
35
+
36
+
[[test]]
37
+
name = "compliance"
38
+
path = "tests/compliance.rs"
+37
Makefile
+37
Makefile
···
1
+
.PHONY: build install release clean test run help
2
+
3
+
# Default target
4
+
help:
5
+
@echo "uson Makefile"
6
+
@echo ""
7
+
@echo "Available targets:"
8
+
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-12s\033[0m %s\n", $$1, $$2}'
9
+
10
+
build: ## Build in debug mode
11
+
cargo build
12
+
13
+
release: ## Build optimized release binary
14
+
cargo build --release
15
+
@echo ""
16
+
@echo "Release binary: target/release/uson"
17
+
18
+
install: ## Install to ~/.cargo/bin
19
+
cargo install --path .
20
+
21
+
clean: ## Clean build artifacts
22
+
cargo clean
23
+
24
+
test: ## Run tests
25
+
cargo test
26
+
27
+
run: ## Run with example query
28
+
cargo run -- "did" -b "1" -j 1
29
+
30
+
fmt: ## Format code
31
+
cargo fmt
32
+
33
+
lint: ## Run clippy linter
34
+
cargo clippy -- -D warnings
35
+
36
+
check: ## Check without building
37
+
cargo check
+387
README.md
+387
README.md
···
1
+
# μson (uson)
2
+
3
+
A compact human-readable data serialization format specially designed for shell.
4
+
5
+
This format is certainly not intended to replace the classical JSON format, but brings different syntax, for use in environments with specific requirements.
6
+
7
+
Main advantage should be in better writability (mostly in the command line), because it uses less expressive syntax. The purpose is not to create a format that is as small as possible in terms of byte size.
8
+
9
+
This is a Rust implementation of μson. Grammar is written using [Pest](https://pest.rs/) parser. This is a port of the original [JavaScript implementation](https://github.com/burningtree/uson).
10
+
11
+
* [μson Overview](#μson-overview)
12
+
* [Rust Library](#rust-library)
13
+
* [Command-line tool (CLI)](#command-line-tool-cli)
14
+
15
+
## μson Overview
16
+
17
+
* [Introduction](#introduction)
18
+
* [Principles](#principles)
19
+
* [Example](#example)
20
+
* [Basic usage](#basic-usage)
21
+
* [Standard types](#standard-types)
22
+
* [Arrays](#arrays)
23
+
* [Objects](#objects)
24
+
* [Nested objects](#nested-objects)
25
+
* [Type casting](#type-casting)
26
+
* [Custom types](#custom-types)
27
+
* [Comments](#comments)
28
+
29
+
### Introduction
30
+
31
+
#### Principles
32
+
33
+
* Superset of JSON (every JSON is valid μson).
34
+
* Whitespace is not significant.
35
+
* String quoting `"` is optional.
36
+
* In Array or Object, comma `,` can be replaced by whitespace ` `.
37
+
* Assignment with colon `:` can be repeated to create nested objects (see [Nested objects](#nested-objects)).
38
+
* You can use custom types, casting is done by `!` character (see [Type casting](#type-casting)).
39
+
40
+
### Example
41
+
42
+
```
43
+
endpoint:id:wikipedia pages:[Malta Prague "New York"]
44
+
```
45
+
46
+
Result in JSON:
47
+
```json
48
+
[
49
+
{
50
+
"endpoint": {
51
+
"id": "wikipedia"
52
+
}
53
+
},
54
+
{
55
+
"pages": [
56
+
"Malta",
57
+
"Prague",
58
+
"New York"
59
+
]
60
+
}
61
+
]
62
+
```
63
+
64
+
### Basic usage
65
+
66
+
```
67
+
expr1 expr2 expr3 ..
68
+
```
69
+
70
+
Supported types:
71
+
* false
72
+
* null
73
+
* true
74
+
* array
75
+
* object
76
+
* number
77
+
* string
78
+
79
+
#### Standard types
80
+
81
+
```
82
+
number:12.05 text:Banana quotedText:"John Devilseed" empty:null good:true
83
+
```
84
+
85
+
Output:
86
+
```json
87
+
[
88
+
{
89
+
"number": 12.05
90
+
},
91
+
{
92
+
"text": "Banana"
93
+
},
94
+
{
95
+
"quotedText": "John Devilseed"
96
+
},
97
+
{
98
+
"empty": null
99
+
},
100
+
{
101
+
"good": true
102
+
}
103
+
]
104
+
```
105
+
106
+
#### Arrays
107
+
108
+
```
109
+
simple:[1 2 3] texts:[Malta Budapest "New York"] objects:[{id:1}]
110
+
```
111
+
112
+
Output:
113
+
```json
114
+
[
115
+
{
116
+
"simple": [
117
+
1,
118
+
2,
119
+
3
120
+
]
121
+
},
122
+
{
123
+
"texts": [
124
+
"Malta",
125
+
"Budapest",
126
+
"New York"
127
+
]
128
+
},
129
+
{
130
+
"objects": [
131
+
{
132
+
"id": 1
133
+
}
134
+
]
135
+
}
136
+
]
137
+
```
138
+
139
+
#### Objects
140
+
141
+
```
142
+
obj:{name:John} {nested:[{id:42} value:"Nagano"]}
143
+
```
144
+
145
+
Output:
146
+
```json
147
+
[
148
+
{
149
+
"obj": {
150
+
"name": "John"
151
+
}
152
+
},
153
+
{
154
+
"nested": [
155
+
{
156
+
"id": 42
157
+
},
158
+
{
159
+
"value": "Nagano"
160
+
}
161
+
]
162
+
}
163
+
]
164
+
```
165
+
166
+
#### Nested objects
167
+
168
+
You can use standard colon notation to expand objects:
169
+
```
170
+
<key>:(<value>|(<key>:(<value>| .. )))
171
+
```
172
+
173
+
For example:
174
+
```
175
+
cities:eu:hu:budapest:Budapest
176
+
```
177
+
178
+
becomes:
179
+
```json
180
+
[
181
+
{
182
+
"cities": {
183
+
"eu": {
184
+
"hu": {
185
+
"budapest": "Budapest"
186
+
}
187
+
}
188
+
}
189
+
}
190
+
]
191
+
```
192
+
193
+
#### Type casting
194
+
195
+
If you want to return a value in specific type, you can use this syntax:
196
+
```
197
+
<type>!<expr>
198
+
```
199
+
200
+
For example, this input:
201
+
```
202
+
str!42
203
+
```
204
+
205
+
produces this output:
206
+
```json
207
+
["42"]
208
+
```
209
+
210
+
You can use casting repeatedly:
211
+
```
212
+
str!int!12.42
213
+
```
214
+
215
+
output:
216
+
```json
217
+
["12"]
218
+
```
219
+
220
+
##### Built-in casting types
221
+
222
+
**Scalars:**
223
+
* `str` - string
224
+
* `int` - integer
225
+
* `float` - float
226
+
* `null` - null
227
+
* `bool` - boolean
228
+
* `date` - date & time (ISO 8601 formatting)
229
+
230
+
**Data formats:**
231
+
* `base64` - decode base64 to bytes
232
+
* `hex` - decode hex to bytes
233
+
* `url` - parse URL
234
+
* `regex` - parse regular expression
235
+
* `toml` - parse TOML data
236
+
237
+
#### Custom types
238
+
239
+
Custom type support is planned for future versions.
240
+
241
+
#### Comments
242
+
243
+
Comments begin with `#` and terminate at end of line.
244
+
245
+
```
246
+
array:[1 2 3] # this is a comment
247
+
```
248
+
249
+
Output:
250
+
```json
251
+
[
252
+
{
253
+
"array": [
254
+
1,
255
+
2,
256
+
3
257
+
]
258
+
}
259
+
]
260
+
```
261
+
262
+
## Rust Library
263
+
264
+
### Installation
265
+
266
+
Add to your `Cargo.toml`:
267
+
268
+
```toml
269
+
[dependencies]
270
+
uson = "0.1.0"
271
+
```
272
+
273
+
### Usage
274
+
275
+
```rust
276
+
use uson::{parse, Value};
277
+
278
+
fn main() {
279
+
// Parse μson
280
+
let result = parse("name:John age:30 active:true").unwrap();
281
+
282
+
// Convert to JSON
283
+
let json = uson::to_json("name:John age:30", true, true).unwrap();
284
+
println!("{}", json);
285
+
}
286
+
```
287
+
288
+
### API
289
+
290
+
* `uson::parse(input: &str) -> Result<Vec<Value>, ParseError>` - Parse μson string
291
+
* `uson::to_json(input: &str, apply_types: bool, pretty: bool) -> Result<String, ParseError>` - Parse and convert to JSON
292
+
* `uson::stringify(input: &str, apply_types: bool) -> Result<String, ParseError>` - Parse and stringify back to μson
293
+
* `uson::apply_builtin_types(value: Value) -> Value` - Apply built-in type transformations
294
+
295
+
## Command-line tool (CLI)
296
+
297
+
### Installation
298
+
299
+
```bash
300
+
cargo install uson
301
+
```
302
+
303
+
### Usage
304
+
305
+
```bash
306
+
$ uson [options] [expression]
307
+
```
308
+
309
+
### Example
310
+
311
+
```bash
312
+
$ uson 'user:john age:42'
313
+
```
314
+
315
+
Returns:
316
+
```json
317
+
[{"user":"john"},{"age":42}]
318
+
```
319
+
320
+
### Options
321
+
322
+
For pretty-printed output, use `--pretty` or `-p`:
323
+
324
+
```bash
325
+
$ uson --pretty 'name:John age:30'
326
+
```
327
+
328
+
To read from a file, use `--file` or `-f`:
329
+
330
+
```bash
331
+
$ uson --file input.uson
332
+
```
333
+
334
+
To apply built-in type transformations, use `--types` or `-t`:
335
+
336
+
```bash
337
+
$ uson --types 'date!"2024-01-01"'
338
+
```
339
+
340
+
### Output Formats
341
+
342
+
- JSON (default): `--json` or `-j`
343
+
- YAML: `--yaml` or `-y` (requires `yaml` feature)
344
+
- Query string: `--form` or `-F` (requires `form` feature)
345
+
346
+
Example:
347
+
```bash
348
+
$ uson --yaml --pretty 'endpoint:id:wikipedia'
349
+
```
350
+
351
+
Returns:
352
+
```yaml
353
+
- endpoint:
354
+
id: wikipedia
355
+
```
356
+
357
+
### Streams support (pipe)
358
+
359
+
If you don't specify any input then input is taken from standard input (stdin):
360
+
361
+
```bash
362
+
$ echo "a b c:[a:42]" | uson --pretty
363
+
```
364
+
365
+
## Development
366
+
367
+
```bash
368
+
# Build
369
+
cargo build
370
+
371
+
# Run tests
372
+
cargo test
373
+
374
+
# Run compliance tests
375
+
cargo test --test compliance
376
+
377
+
# Build with all features
378
+
cargo build --all-features
379
+
```
380
+
381
+
## Inspiration
382
+
383
+
Inspired by the original JavaScript implementation by [@burningtree](https://github.com/burningtree).
384
+
385
+
## License
386
+
387
+
MIT
+258
src/ast.rs
+258
src/ast.rs
···
1
+
use serde::{Deserialize, Serialize};
2
+
use std::collections::HashMap;
3
+
4
+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
5
+
#[serde(untagged)]
6
+
pub enum Value {
7
+
Null,
8
+
Bool(bool),
9
+
Number(Number),
10
+
String(String),
11
+
Array(Vec<Value>),
12
+
Object(HashMap<String, Value>),
13
+
Typed {
14
+
type_name: String,
15
+
value: Box<Value>,
16
+
},
17
+
}
18
+
19
+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
20
+
#[serde(untagged)]
21
+
pub enum Number {
22
+
Integer(i64),
23
+
Float(f64),
24
+
}
25
+
26
+
impl Value {
27
+
pub fn as_bool(&self) -> Option<bool> {
28
+
match self {
29
+
Value::Bool(b) => Some(*b),
30
+
_ => None,
31
+
}
32
+
}
33
+
34
+
pub fn as_number(&self) -> Option<&Number> {
35
+
match self {
36
+
Value::Number(n) => Some(n),
37
+
_ => None,
38
+
}
39
+
}
40
+
41
+
pub fn as_string(&self) -> Option<&str> {
42
+
match self {
43
+
Value::String(s) => Some(s),
44
+
_ => None,
45
+
}
46
+
}
47
+
48
+
pub fn as_array(&self) -> Option<&Vec<Value>> {
49
+
match self {
50
+
Value::Array(a) => Some(a),
51
+
_ => None,
52
+
}
53
+
}
54
+
55
+
pub fn as_object(&self) -> Option<&HashMap<String, Value>> {
56
+
match self {
57
+
Value::Object(o) => Some(o),
58
+
_ => None,
59
+
}
60
+
}
61
+
62
+
/// Convert to minimal μson string format (no unnecessary quotes, compact)
63
+
pub fn to_uson_string(&self) -> String {
64
+
match self {
65
+
Value::Null => "null".to_string(),
66
+
Value::Bool(b) => b.to_string(),
67
+
Value::Number(Number::Integer(i)) => i.to_string(),
68
+
Value::Number(Number::Float(f)) => {
69
+
// Format float
70
+
let s = f.to_string();
71
+
// Avoid scientific notation for simple numbers if possible
72
+
if s.contains('e') && f.abs() < 1e10 && f.fract() == 0.0 {
73
+
format!("{:.0}", f)
74
+
} else {
75
+
s
76
+
}
77
+
}
78
+
Value::String(s) => {
79
+
// Use unquoted strings when possible
80
+
if needs_quoting(s) {
81
+
format!("\"{}\"", escape_string(s))
82
+
} else {
83
+
s.clone()
84
+
}
85
+
}
86
+
Value::Array(arr) => {
87
+
if arr.is_empty() {
88
+
"[]".to_string()
89
+
} else {
90
+
let items: Vec<String> = arr
91
+
.iter()
92
+
.map(|v| v.to_uson_string())
93
+
.collect();
94
+
format!("[{}]", items.join(" "))
95
+
}
96
+
}
97
+
Value::Object(obj) => {
98
+
if obj.is_empty() {
99
+
"{}".to_string()
100
+
} else {
101
+
let mut items: Vec<String> = obj
102
+
.iter()
103
+
.map(|(k, v)| {
104
+
let key = if needs_quoting(k) {
105
+
format!("\"{}\"", escape_string(k))
106
+
} else {
107
+
k.clone()
108
+
};
109
+
format!("{}:{}", key, v.to_uson_string())
110
+
})
111
+
.collect();
112
+
items.sort(); // Sort for consistent output
113
+
format!("{{{}}}", items.join(" "))
114
+
}
115
+
}
116
+
Value::Typed { type_name, value } => {
117
+
format!("{}!{}", type_name, value.to_uson_string())
118
+
}
119
+
}
120
+
}
121
+
122
+
/// Convert to pretty μson string format with indentation
123
+
pub fn to_uson_string_pretty(&self) -> String {
124
+
self.stringify_pretty(0)
125
+
}
126
+
127
+
fn stringify_pretty(&self, indent: usize) -> String {
128
+
let indent_str = " ".repeat(indent);
129
+
let next_indent_str = " ".repeat(indent + 1);
130
+
131
+
match self {
132
+
Value::Null => "null".to_string(),
133
+
Value::Bool(b) => b.to_string(),
134
+
Value::Number(Number::Integer(i)) => i.to_string(),
135
+
Value::Number(Number::Float(f)) => f.to_string(),
136
+
Value::String(s) => {
137
+
if needs_quoting(s) {
138
+
format!("\"{}\"", escape_string(s))
139
+
} else {
140
+
s.clone()
141
+
}
142
+
}
143
+
Value::Array(arr) => {
144
+
if arr.is_empty() {
145
+
"[]".to_string()
146
+
} else if arr.len() <= 3 && arr.iter().all(|v| matches!(v, Value::Number(_) | Value::String(_) | Value::Bool(_) | Value::Null)) {
147
+
// Keep simple arrays on one line
148
+
let items: Vec<String> = arr.iter().map(|v| v.to_uson_string()).collect();
149
+
format!("[{}]", items.join(" "))
150
+
} else {
151
+
// Multi-line for complex arrays
152
+
let items: Vec<String> = arr
153
+
.iter()
154
+
.map(|v| format!("{}{}", next_indent_str, v.stringify_pretty(indent + 1)))
155
+
.collect();
156
+
format!("[\n{}\n{}]", items.join("\n"), indent_str)
157
+
}
158
+
}
159
+
Value::Object(obj) => {
160
+
if obj.is_empty() {
161
+
"{}".to_string()
162
+
} else {
163
+
let mut items: Vec<(String, String)> = obj
164
+
.iter()
165
+
.map(|(k, v)| {
166
+
let key = if needs_quoting(k) {
167
+
format!("\"{}\"", escape_string(k))
168
+
} else {
169
+
k.clone()
170
+
};
171
+
(k.clone(), format!("{}{}: {}", next_indent_str, key, v.stringify_pretty(indent + 1)))
172
+
})
173
+
.collect();
174
+
items.sort_by(|a, b| a.0.cmp(&b.0));
175
+
let formatted: Vec<String> = items.into_iter().map(|(_, s)| s).collect();
176
+
format!("{{\n{}\n{}}}", formatted.join("\n"), indent_str)
177
+
}
178
+
}
179
+
Value::Typed { type_name, value } => {
180
+
format!("{}!{}", type_name, value.stringify_pretty(indent))
181
+
}
182
+
}
183
+
}
184
+
185
+
/// Convert to JSON string
186
+
pub fn to_json_string(&self) -> Result<String, serde_json::Error> {
187
+
serde_json::to_string(self)
188
+
}
189
+
190
+
/// Convert to pretty JSON string
191
+
pub fn to_json_string_pretty(&self) -> Result<String, serde_json::Error> {
192
+
serde_json::to_string_pretty(self)
193
+
}
194
+
}
195
+
196
+
fn needs_quoting(s: &str) -> bool {
197
+
if s.is_empty() {
198
+
return true;
199
+
}
200
+
201
+
// Check if it's a reserved keyword
202
+
if matches!(s, "true" | "false" | "null") {
203
+
return true;
204
+
}
205
+
206
+
// Check if it looks like a number
207
+
if s.parse::<f64>().is_ok() {
208
+
return true;
209
+
}
210
+
211
+
// Check if it contains special characters or starts with certain chars
212
+
for ch in s.chars() {
213
+
if matches!(ch, ' ' | '\t' | '\n' | '\r' | '"' | '\'' | '#' | ',' | ':' | '[' | ']' | '{' | '}' | '\\' | '!' | '/' | '=' | '<' | '>') {
214
+
return true;
215
+
}
216
+
}
217
+
218
+
false
219
+
}
220
+
221
+
fn escape_string(s: &str) -> String {
222
+
let mut result = String::new();
223
+
for ch in s.chars() {
224
+
match ch {
225
+
'"' => result.push_str("\\\""),
226
+
'\\' => result.push_str("\\\\"),
227
+
'\n' => result.push_str("\\n"),
228
+
'\r' => result.push_str("\\r"),
229
+
'\t' => result.push_str("\\t"),
230
+
'\x08' => result.push_str("\\b"),
231
+
'\x0C' => result.push_str("\\f"),
232
+
_ => result.push(ch),
233
+
}
234
+
}
235
+
result
236
+
}
237
+
238
+
impl Number {
239
+
pub fn as_i64(&self) -> Option<i64> {
240
+
match self {
241
+
Number::Integer(i) => Some(*i),
242
+
Number::Float(f) => {
243
+
if f.fract() == 0.0 && *f >= i64::MIN as f64 && *f <= i64::MAX as f64 {
244
+
Some(*f as i64)
245
+
} else {
246
+
None
247
+
}
248
+
}
249
+
}
250
+
}
251
+
252
+
pub fn as_f64(&self) -> f64 {
253
+
match self {
254
+
Number::Integer(i) => *i as f64,
255
+
Number::Float(f) => *f,
256
+
}
257
+
}
258
+
}
+214
src/lib.rs
+214
src/lib.rs
···
1
+
pub mod ast;
2
+
pub mod parser;
3
+
4
+
pub use ast::{Number, Value};
5
+
pub use parser::{parse, apply_builtin_types, ParseError, ParseResult};
6
+
7
+
/// Parse and stringify in one go (for convenience)
8
+
pub fn stringify(input: &str, apply_types: bool) -> ParseResult<String> {
9
+
let mut values = parse(input)?;
10
+
if apply_types {
11
+
values = values.into_iter().map(apply_builtin_types).collect();
12
+
}
13
+
14
+
if values.len() == 1 {
15
+
Ok(values[0].to_uson_string())
16
+
} else {
17
+
let strings: Vec<String> = values.iter().map(|v| v.to_uson_string()).collect();
18
+
Ok(strings.join(" "))
19
+
}
20
+
}
21
+
22
+
/// Parse and convert to JSON string
23
+
pub fn to_json(input: &str, apply_types: bool, pretty: bool) -> ParseResult<String> {
24
+
let mut values = parse(input)?;
25
+
if apply_types {
26
+
values = values.into_iter().map(apply_builtin_types).collect();
27
+
}
28
+
29
+
let json_value = serde_json::to_value(&values)
30
+
.map_err(|e| ParseError::NumberError(e.to_string()))?;
31
+
32
+
if pretty {
33
+
serde_json::to_string_pretty(&json_value)
34
+
.map_err(|e| ParseError::NumberError(e.to_string()))
35
+
} else {
36
+
serde_json::to_string(&json_value)
37
+
.map_err(|e| ParseError::NumberError(e.to_string()))
38
+
}
39
+
}
40
+
41
+
#[cfg(test)]
42
+
mod tests {
43
+
use super::*;
44
+
use pretty_assertions::assert_eq;
45
+
46
+
#[test]
47
+
fn test_null() {
48
+
let result = parse("null").unwrap();
49
+
assert_eq!(result, vec![Value::Null]);
50
+
}
51
+
52
+
#[test]
53
+
fn test_bool() {
54
+
assert_eq!(parse("true").unwrap(), vec![Value::Bool(true)]);
55
+
assert_eq!(parse("false").unwrap(), vec![Value::Bool(false)]);
56
+
}
57
+
58
+
#[test]
59
+
fn test_numbers() {
60
+
assert_eq!(
61
+
parse("42").unwrap(),
62
+
vec![Value::Number(Number::Integer(42))]
63
+
);
64
+
assert_eq!(
65
+
parse("3.14").unwrap(),
66
+
vec![Value::Number(Number::Float(3.14))]
67
+
);
68
+
assert_eq!(
69
+
parse("-10").unwrap(),
70
+
vec![Value::Number(Number::Integer(-10))]
71
+
);
72
+
}
73
+
74
+
#[test]
75
+
fn test_strings() {
76
+
assert_eq!(
77
+
parse("\"hello\"").unwrap(),
78
+
vec![Value::String("hello".to_string())]
79
+
);
80
+
assert_eq!(
81
+
parse("'world'").unwrap(),
82
+
vec![Value::String("world".to_string())]
83
+
);
84
+
assert_eq!(
85
+
parse("unquoted").unwrap(),
86
+
vec![Value::String("unquoted".to_string())]
87
+
);
88
+
}
89
+
90
+
#[test]
91
+
fn test_array() {
92
+
let result = parse("[1, 2, 3]").unwrap();
93
+
assert_eq!(
94
+
result,
95
+
vec![Value::Array(vec![
96
+
Value::Number(Number::Integer(1)),
97
+
Value::Number(Number::Integer(2)),
98
+
Value::Number(Number::Integer(3))
99
+
])]
100
+
);
101
+
}
102
+
103
+
#[test]
104
+
fn test_array_no_commas() {
105
+
let result = parse("[1 2 3]").unwrap();
106
+
assert_eq!(
107
+
result,
108
+
vec![Value::Array(vec![
109
+
Value::Number(Number::Integer(1)),
110
+
Value::Number(Number::Integer(2)),
111
+
Value::Number(Number::Integer(3))
112
+
])]
113
+
);
114
+
}
115
+
116
+
#[test]
117
+
fn test_object() {
118
+
let result = parse(r#"{"key": "value"}"#).unwrap();
119
+
let mut expected = std::collections::HashMap::new();
120
+
expected.insert("key".to_string(), Value::String("value".to_string()));
121
+
assert_eq!(result, vec![Value::Object(expected)]);
122
+
}
123
+
124
+
#[test]
125
+
fn test_comment() {
126
+
let result = parse("# comment\n42").unwrap();
127
+
assert_eq!(result, vec![Value::Number(Number::Integer(42))]);
128
+
}
129
+
130
+
#[test]
131
+
fn test_typed() {
132
+
let result = parse("date!\"2024-01-01\"").unwrap();
133
+
assert_eq!(
134
+
result,
135
+
vec![Value::Typed {
136
+
type_name: "date".to_string(),
137
+
value: Box::new(Value::String("2024-01-01".to_string()))
138
+
}]
139
+
);
140
+
}
141
+
142
+
#[test]
143
+
fn test_assign() {
144
+
let result = parse(r#"name: "John""#).unwrap();
145
+
let mut expected = std::collections::HashMap::new();
146
+
expected.insert("name".to_string(), Value::String("John".to_string()));
147
+
assert_eq!(result, vec![Value::Object(expected)]);
148
+
}
149
+
150
+
#[test]
151
+
fn test_multiple_expressions() {
152
+
let result = parse("1 2 3").unwrap();
153
+
assert_eq!(
154
+
result,
155
+
vec![
156
+
Value::Number(Number::Integer(1)),
157
+
Value::Number(Number::Integer(2)),
158
+
Value::Number(Number::Integer(3))
159
+
]
160
+
);
161
+
}
162
+
163
+
#[test]
164
+
fn test_stringify_simple() {
165
+
let value = Value::String("hello".to_string());
166
+
assert_eq!(value.to_uson_string(), "hello");
167
+
}
168
+
169
+
#[test]
170
+
fn test_stringify_number() {
171
+
let value = Value::Number(Number::Integer(42));
172
+
assert_eq!(value.to_uson_string(), "42");
173
+
}
174
+
175
+
#[test]
176
+
fn test_stringify_array() {
177
+
let value = Value::Array(vec![
178
+
Value::Number(Number::Integer(1)),
179
+
Value::Number(Number::Integer(2)),
180
+
Value::Number(Number::Integer(3)),
181
+
]);
182
+
assert_eq!(value.to_uson_string(), "[1 2 3]");
183
+
}
184
+
185
+
#[test]
186
+
fn test_stringify_object() {
187
+
let mut obj = std::collections::HashMap::new();
188
+
obj.insert("name".to_string(), Value::String("John".to_string()));
189
+
obj.insert("age".to_string(), Value::Number(Number::Integer(30)));
190
+
let value = Value::Object(obj);
191
+
let result = value.to_uson_string();
192
+
// Note: HashMap doesn't guarantee order, but our stringify sorts keys
193
+
assert!(result.contains("age:30"));
194
+
assert!(result.contains("name:John"));
195
+
}
196
+
197
+
#[test]
198
+
fn test_stringify_needs_quotes() {
199
+
let value = Value::String("hello world".to_string());
200
+
assert_eq!(value.to_uson_string(), "\"hello world\"");
201
+
}
202
+
203
+
#[test]
204
+
fn test_roundtrip() {
205
+
let input = "name:John age:30 active:true";
206
+
let parsed = parse(input).unwrap();
207
+
let stringified: Vec<String> = parsed.iter().map(|v| v.to_uson_string()).collect();
208
+
let result = stringified.join(" ");
209
+
210
+
// Parse again
211
+
let reparsed = parse(&result).unwrap();
212
+
assert_eq!(parsed, reparsed);
213
+
}
214
+
}
+332
src/main.rs
+332
src/main.rs
···
1
+
use clap::Parser;
2
+
use std::collections::HashMap;
3
+
use std::fs;
4
+
use std::io::{self, Read};
5
+
use std::path::PathBuf;
6
+
use uson::{parse, apply_builtin_types, Value};
7
+
use base64::Engine;
8
+
9
+
#[derive(Parser)]
10
+
#[command(name = "uson")]
11
+
#[command(author, version)]
12
+
#[command(about = "μson (uson) is a shorthand for JSON", long_about = None)]
13
+
struct Cli {
14
+
/// μson expression to parse (can be multiple words without quotes)
15
+
#[arg(value_name = "EXPRESSION", num_args = 0..)]
16
+
expression: Vec<String>,
17
+
18
+
/// "object" mode - combines all expressions into one object
19
+
#[arg(short, long)]
20
+
object: bool,
21
+
22
+
/// "json" mode (default)
23
+
#[arg(short, long)]
24
+
json: bool,
25
+
26
+
/// Load data from file
27
+
#[arg(short, long, value_name = "FILE")]
28
+
input: Option<PathBuf>,
29
+
30
+
/// Write output to file
31
+
#[arg(long, value_name = "FILE")]
32
+
output: Option<PathBuf>,
33
+
34
+
/// Pretty print output
35
+
#[arg(short, long)]
36
+
pretty: bool,
37
+
38
+
/// Output format: json (default), uson, form, yaml
39
+
#[arg(short = 'F', long, value_name = "FORMAT", default_value = "json")]
40
+
format: String,
41
+
42
+
/// Return output in form query-string
43
+
#[arg(short, long)]
44
+
#[cfg(feature = "form")]
45
+
form: bool,
46
+
47
+
/// Return output in YAML
48
+
#[arg(short = 'y', long)]
49
+
#[cfg(feature = "yaml")]
50
+
yaml: bool,
51
+
52
+
/// Use custom usonrc file instead of ~/.usonrc.toml
53
+
#[arg(short, long, value_name = "FILE")]
54
+
usonrc: Option<PathBuf>,
55
+
56
+
/// Output in hex encoding
57
+
#[arg(long)]
58
+
hex: bool,
59
+
60
+
/// Output in base64 encoding
61
+
#[arg(long)]
62
+
base64: bool,
63
+
}
64
+
65
+
#[derive(Debug, serde::Deserialize)]
66
+
struct UsonConfig {
67
+
#[serde(default)]
68
+
types: HashMap<String, String>,
69
+
}
70
+
71
+
fn main() {
72
+
let cli = Cli::parse();
73
+
74
+
if let Err(e) = run(cli) {
75
+
eprintln!("Error: {}", e);
76
+
std::process::exit(1);
77
+
}
78
+
}
79
+
80
+
fn run(cli: Cli) -> Result<(), Box<dyn std::error::Error>> {
81
+
// Extract all values we need from cli before any moves
82
+
let expression = if !cli.expression.is_empty() {
83
+
Some(cli.expression.join(" "))
84
+
} else {
85
+
None
86
+
};
87
+
let input_file = cli.input;
88
+
let object_mode = cli.object;
89
+
let output_file = cli.output;
90
+
let hex_encode = cli.hex;
91
+
let base64_encode = cli.base64;
92
+
let pretty = cli.pretty;
93
+
let usonrc_path = cli.usonrc;
94
+
let format = cli.format.to_lowercase();
95
+
96
+
#[cfg(feature = "yaml")]
97
+
let yaml_output = cli.yaml;
98
+
99
+
#[cfg(feature = "form")]
100
+
let form_output = cli.form;
101
+
102
+
// Load configuration
103
+
let config = load_config(usonrc_path)?;
104
+
105
+
// Get input
106
+
let input = get_input(expression, input_file)?;
107
+
108
+
// Parse
109
+
let mut values = parse(&input)?;
110
+
111
+
// Apply built-in type handlers first
112
+
values = values.into_iter().map(apply_builtin_types).collect();
113
+
114
+
// Apply custom type handlers from config
115
+
if !config.types.is_empty() {
116
+
values = apply_type_handlers(values, &config)?;
117
+
}
118
+
119
+
// Format output based on specified format
120
+
let output = match format.as_str() {
121
+
"uson" | "microson" => {
122
+
// Output in μson format
123
+
format_uson(&values, object_mode, pretty)?
124
+
}
125
+
"json" => {
126
+
// Convert to output format based on mode
127
+
let result = if object_mode {
128
+
merge_to_object(values)?
129
+
} else {
130
+
serde_json::to_value(&values)?
131
+
};
132
+
133
+
format_output(
134
+
&result,
135
+
pretty,
136
+
#[cfg(feature = "yaml")]
137
+
yaml_output,
138
+
#[cfg(feature = "form")]
139
+
form_output,
140
+
)?
141
+
}
142
+
_ => {
143
+
return Err(format!("Unknown format: {}", format).into());
144
+
}
145
+
};
146
+
147
+
// Encode if needed
148
+
let final_output = if hex_encode {
149
+
hex::encode(&output)
150
+
} else if base64_encode {
151
+
base64::engine::general_purpose::STANDARD.encode(&output)
152
+
} else {
153
+
output
154
+
};
155
+
156
+
// Write output
157
+
write_output(&final_output, output_file)?;
158
+
159
+
Ok(())
160
+
}
161
+
162
+
fn format_uson(values: &[Value], object_mode: bool, pretty: bool) -> Result<String, Box<dyn std::error::Error>> {
163
+
if object_mode {
164
+
// Merge all into one object first
165
+
let mut merged = HashMap::new();
166
+
for value in values {
167
+
match value {
168
+
Value::Object(obj) => {
169
+
for (key, val) in obj {
170
+
merged.insert(key.clone(), val.clone());
171
+
}
172
+
}
173
+
_ => {
174
+
return Err("Object mode requires all values to be objects".into());
175
+
}
176
+
}
177
+
}
178
+
let merged_value = Value::Object(merged);
179
+
Ok(if pretty {
180
+
merged_value.to_uson_string_pretty()
181
+
} else {
182
+
merged_value.to_uson_string()
183
+
})
184
+
} else {
185
+
// Output each value
186
+
if values.len() == 1 {
187
+
Ok(if pretty {
188
+
values[0].to_uson_string_pretty()
189
+
} else {
190
+
values[0].to_uson_string()
191
+
})
192
+
} else {
193
+
let strings: Vec<String> = values.iter().map(|v| {
194
+
if pretty {
195
+
v.to_uson_string_pretty()
196
+
} else {
197
+
v.to_uson_string()
198
+
}
199
+
}).collect();
200
+
Ok(strings.join(if pretty { "\n\n" } else { " " }))
201
+
}
202
+
}
203
+
}
204
+
205
+
fn load_config(custom_path: Option<PathBuf>) -> Result<UsonConfig, Box<dyn std::error::Error>> {
206
+
let config_path = if let Some(path) = custom_path {
207
+
path
208
+
} else {
209
+
let home = dirs::home_dir().ok_or("Could not find home directory")?;
210
+
home.join(".usonrc.toml")
211
+
};
212
+
213
+
if config_path.exists() {
214
+
let content = fs::read_to_string(config_path)?;
215
+
let config: UsonConfig = toml::from_str(&content)?;
216
+
Ok(config)
217
+
} else {
218
+
Ok(UsonConfig {
219
+
types: HashMap::new(),
220
+
})
221
+
}
222
+
}
223
+
224
+
fn get_input(
225
+
expression: Option<String>,
226
+
input_file: Option<PathBuf>,
227
+
) -> Result<String, Box<dyn std::error::Error>> {
228
+
if let Some(expr) = expression {
229
+
Ok(expr)
230
+
} else if let Some(file) = input_file {
231
+
Ok(fs::read_to_string(file)?)
232
+
} else {
233
+
// Read from stdin
234
+
let mut buffer = String::new();
235
+
io::stdin().read_to_string(&mut buffer)?;
236
+
Ok(buffer)
237
+
}
238
+
}
239
+
240
+
fn apply_type_handlers(
241
+
values: Vec<Value>,
242
+
_config: &UsonConfig,
243
+
) -> Result<Vec<Value>, Box<dyn std::error::Error>> {
244
+
// Note: Custom type handlers would require dynamic evaluation
245
+
// For now, we'll keep typed values as-is
246
+
// In a real implementation, you might want to use a scripting engine like rhai or lua
247
+
Ok(values)
248
+
}
249
+
250
+
fn merge_to_object(values: Vec<Value>) -> Result<serde_json::Value, Box<dyn std::error::Error>> {
251
+
let mut result = serde_json::Map::new();
252
+
253
+
for value in values {
254
+
match value {
255
+
Value::Object(obj) => {
256
+
for (key, val) in obj {
257
+
result.insert(key, serde_json::to_value(val)?);
258
+
}
259
+
}
260
+
_ => {
261
+
return Err("Object mode requires all values to be objects".into());
262
+
}
263
+
}
264
+
}
265
+
266
+
Ok(serde_json::Value::Object(result))
267
+
}
268
+
269
+
fn format_output(
270
+
value: &serde_json::Value,
271
+
pretty: bool,
272
+
#[cfg(feature = "yaml")]
273
+
yaml: bool,
274
+
#[cfg(feature = "form")]
275
+
form: bool,
276
+
) -> Result<String, Box<dyn std::error::Error>> {
277
+
#[cfg(feature = "yaml")]
278
+
if yaml {
279
+
return Ok(serde_yaml::to_string(value)?);
280
+
}
281
+
282
+
#[cfg(feature = "form")]
283
+
if form {
284
+
return Ok(serde_qs::to_string(value)?);
285
+
}
286
+
287
+
// Default: JSON
288
+
if pretty {
289
+
Ok(serde_json::to_string_pretty(value)?)
290
+
} else {
291
+
Ok(serde_json::to_string(value)?)
292
+
}
293
+
}
294
+
295
+
fn write_output(
296
+
output: &str,
297
+
output_file: Option<PathBuf>,
298
+
) -> Result<(), Box<dyn std::error::Error>> {
299
+
if let Some(file) = output_file {
300
+
fs::write(file, output)?;
301
+
} else {
302
+
println!("{}", output);
303
+
}
304
+
Ok(())
305
+
}
306
+
307
+
#[cfg(test)]
308
+
mod tests {
309
+
use super::*;
310
+
311
+
#[test]
312
+
fn test_merge_to_object() {
313
+
let values = vec![
314
+
Value::Object({
315
+
let mut map = HashMap::new();
316
+
map.insert("user".to_string(), Value::String("john".to_string()));
317
+
map
318
+
}),
319
+
Value::Object({
320
+
let mut map = HashMap::new();
321
+
map.insert("age".to_string(), Value::Number(uson::Number::Integer(42)));
322
+
map
323
+
}),
324
+
];
325
+
326
+
let result = merge_to_object(values).unwrap();
327
+
assert!(result.is_object());
328
+
let obj = result.as_object().unwrap();
329
+
assert_eq!(obj.get("user").unwrap().as_str().unwrap(), "john");
330
+
assert_eq!(obj.get("age").unwrap().as_i64().unwrap(), 42);
331
+
}
332
+
}
+309
src/parser.rs
+309
src/parser.rs
···
1
+
use crate::ast::{Number, Value};
2
+
use pest::iterators::Pair;
3
+
use pest::Parser;
4
+
use pest_derive::Parser;
5
+
use std::collections::HashMap;
6
+
use thiserror::Error;
7
+
8
+
#[derive(Parser)]
9
+
#[grammar = "uson.pest"]
10
+
pub struct UsonParser;
11
+
12
+
#[derive(Error, Debug)]
13
+
pub enum ParseError {
14
+
#[error("Parse error: {0}")]
15
+
PestError(#[from] pest::error::Error<Rule>),
16
+
17
+
#[error("Invalid number format: {0}")]
18
+
NumberError(String),
19
+
20
+
#[error("Invalid escape sequence: {0}")]
21
+
EscapeError(String),
22
+
}
23
+
24
+
pub type ParseResult<T> = Result<T, ParseError>;
25
+
26
+
pub fn parse(input: &str) -> ParseResult<Vec<Value>> {
27
+
let mut pairs = UsonParser::parse(Rule::uson_text, input)?;
28
+
let uson_text = pairs.next().unwrap();
29
+
30
+
let mut values = Vec::new();
31
+
for pair in uson_text.into_inner() {
32
+
if pair.as_rule() == Rule::EOI {
33
+
break;
34
+
}
35
+
if pair.as_rule() == Rule::expr {
36
+
let value_pair = pair.into_inner().next().unwrap();
37
+
values.push(parse_value(value_pair)?);
38
+
}
39
+
}
40
+
41
+
Ok(values)
42
+
}
43
+
44
+
fn parse_value(pair: Pair<Rule>) -> ParseResult<Value> {
45
+
match pair.as_rule() {
46
+
Rule::null_val => Ok(Value::Null),
47
+
Rule::true_val => Ok(Value::Bool(true)),
48
+
Rule::false_val => Ok(Value::Bool(false)),
49
+
Rule::number => parse_number(pair),
50
+
Rule::string => parse_string(pair),
51
+
Rule::array => parse_array(pair),
52
+
Rule::object => parse_object(pair),
53
+
Rule::assign => parse_assign(pair),
54
+
Rule::typed => parse_typed(pair),
55
+
Rule::value => {
56
+
let inner = pair.into_inner().next().unwrap();
57
+
parse_value(inner)
58
+
}
59
+
_ => unreachable!("Unexpected rule: {:?}", pair.as_rule()),
60
+
}
61
+
}
62
+
63
+
fn parse_number(pair: Pair<Rule>) -> ParseResult<Value> {
64
+
let num_str = pair.as_str();
65
+
66
+
if num_str.contains('.') || num_str.contains('e') || num_str.contains('E') {
67
+
let f = num_str.parse::<f64>()
68
+
.map_err(|e| ParseError::NumberError(e.to_string()))?;
69
+
Ok(Value::Number(Number::Float(f)))
70
+
} else {
71
+
let i = num_str.parse::<i64>()
72
+
.map_err(|e| ParseError::NumberError(e.to_string()))?;
73
+
Ok(Value::Number(Number::Integer(i)))
74
+
}
75
+
}
76
+
77
+
fn parse_string(pair: Pair<Rule>) -> ParseResult<Value> {
78
+
let inner = pair.into_inner().next().unwrap();
79
+
let s = match inner.as_rule() {
80
+
Rule::double_quoted_string => {
81
+
let s = inner.as_str();
82
+
unescape_string(&s[1..s.len()-1])?
83
+
}
84
+
Rule::single_quoted_string => {
85
+
let s = inner.as_str();
86
+
unescape_string(&s[1..s.len()-1])?
87
+
}
88
+
Rule::unquoted_string => {
89
+
let s = inner.as_str();
90
+
// Check if it's a number
91
+
if s.chars().all(|c| c.is_ascii_digit() || c == '.') {
92
+
if s.contains('.') {
93
+
if let Ok(f) = s.parse::<f64>() {
94
+
return Ok(Value::Number(Number::Float(f)));
95
+
}
96
+
} else if let Ok(i) = s.parse::<i64>() {
97
+
return Ok(Value::Number(Number::Integer(i)));
98
+
}
99
+
}
100
+
s.to_string()
101
+
}
102
+
_ => unreachable!(),
103
+
};
104
+
105
+
Ok(Value::String(s))
106
+
}
107
+
108
+
fn unescape_string(s: &str) -> ParseResult<String> {
109
+
let mut result = String::new();
110
+
let mut chars = s.chars();
111
+
112
+
while let Some(ch) = chars.next() {
113
+
if ch == '\\' {
114
+
match chars.next() {
115
+
Some('n') => result.push('\n'),
116
+
Some('r') => result.push('\r'),
117
+
Some('t') => result.push('\t'),
118
+
Some('b') => result.push('\u{0008}'),
119
+
Some('f') => result.push('\u{000C}'),
120
+
Some('\\') => result.push('\\'),
121
+
Some('/') => result.push('/'),
122
+
Some('#') => result.push('#'),
123
+
Some('"') => result.push('"'),
124
+
Some('\'') => result.push('\''),
125
+
Some('u') => {
126
+
let hex: String = chars.by_ref().take(4).collect();
127
+
let code = u32::from_str_radix(&hex, 16)
128
+
.map_err(|_| ParseError::EscapeError(format!("Invalid unicode escape: \\u{}", hex)))?;
129
+
let ch = char::from_u32(code)
130
+
.ok_or_else(|| ParseError::EscapeError(format!("Invalid unicode codepoint: {}", code)))?;
131
+
result.push(ch);
132
+
}
133
+
Some(c) => result.push(c),
134
+
None => return Err(ParseError::EscapeError("Incomplete escape sequence".to_string())),
135
+
}
136
+
} else {
137
+
result.push(ch);
138
+
}
139
+
}
140
+
141
+
Ok(result)
142
+
}
143
+
144
+
fn parse_array(pair: Pair<Rule>) -> ParseResult<Value> {
145
+
let mut values = Vec::new();
146
+
147
+
for inner in pair.into_inner() {
148
+
if inner.as_rule() == Rule::value {
149
+
values.push(parse_value(inner)?);
150
+
}
151
+
}
152
+
153
+
Ok(Value::Array(values))
154
+
}
155
+
156
+
fn parse_object(pair: Pair<Rule>) -> ParseResult<Value> {
157
+
let mut map = HashMap::new();
158
+
159
+
for inner in pair.into_inner() {
160
+
if inner.as_rule() == Rule::member {
161
+
let mut member_parts = inner.into_inner();
162
+
let key_pair = member_parts.next().unwrap();
163
+
let key = if let Value::String(s) = parse_string(key_pair)? {
164
+
s
165
+
} else {
166
+
unreachable!()
167
+
};
168
+
let value_pair = member_parts.next().unwrap();
169
+
let value = parse_value(value_pair)?;
170
+
map.insert(key, value);
171
+
}
172
+
}
173
+
174
+
Ok(Value::Object(map))
175
+
}
176
+
177
+
fn parse_assign(pair: Pair<Rule>) -> ParseResult<Value> {
178
+
let mut inner = pair.into_inner();
179
+
let key_pair = inner.next().unwrap();
180
+
let key = if let Value::String(s) = parse_string(key_pair)? {
181
+
s
182
+
} else {
183
+
unreachable!()
184
+
};
185
+
let value_pair = inner.next().unwrap();
186
+
let value = parse_value(value_pair)?;
187
+
188
+
let mut map = HashMap::new();
189
+
map.insert(key, value);
190
+
Ok(Value::Object(map))
191
+
}
192
+
193
+
fn parse_typed(pair: Pair<Rule>) -> ParseResult<Value> {
194
+
let mut inner = pair.into_inner();
195
+
let type_name = inner.next().unwrap().as_str().to_string();
196
+
let value_pair = inner.next().unwrap();
197
+
let value = parse_value(value_pair)?;
198
+
199
+
Ok(Value::Typed {
200
+
type_name,
201
+
value: Box::new(value),
202
+
})
203
+
}
204
+
205
+
pub fn apply_builtin_types(value: Value) -> Value {
206
+
match value {
207
+
Value::Typed { type_name, value } => {
208
+
let inner = apply_builtin_types(*value);
209
+
match type_name.as_str() {
210
+
"str" => match &inner {
211
+
Value::String(s) => Value::String(s.clone()),
212
+
Value::Number(Number::Integer(i)) => Value::String(i.to_string()),
213
+
Value::Number(Number::Float(f)) => Value::String(f.to_string()),
214
+
Value::Bool(b) => Value::String(b.to_string()),
215
+
Value::Null => Value::String("null".to_string()),
216
+
Value::Array(arr) => {
217
+
let strs: Vec<String> = arr.iter().map(|v| match v {
218
+
Value::String(s) => s.clone(),
219
+
Value::Number(Number::Integer(i)) => i.to_string(),
220
+
Value::Number(Number::Float(f)) => f.to_string(),
221
+
_ => "".to_string(),
222
+
}).collect();
223
+
Value::String(strs.join(","))
224
+
}
225
+
_ => inner,
226
+
},
227
+
"int" => match &inner {
228
+
Value::String(s) => s.parse::<i64>()
229
+
.map(|i| Value::Number(Number::Integer(i)))
230
+
.unwrap_or(inner),
231
+
Value::Number(_) => inner,
232
+
_ => inner,
233
+
},
234
+
"float" => match &inner {
235
+
Value::String(s) => s.parse::<f64>()
236
+
.map(|f| Value::Number(Number::Float(f)))
237
+
.unwrap_or(inner),
238
+
Value::Number(Number::Integer(i)) => Value::Number(Number::Float(*i as f64)),
239
+
Value::Number(_) => inner,
240
+
_ => inner,
241
+
},
242
+
"bool" => match &inner {
243
+
Value::String(s) => match s.as_str() {
244
+
"true" => Value::Bool(true),
245
+
"false" => Value::Bool(false),
246
+
_ => inner,
247
+
},
248
+
Value::Bool(_) => inner,
249
+
_ => inner,
250
+
},
251
+
"null" => Value::Null,
252
+
"arr" => match &inner {
253
+
Value::String(s) => {
254
+
let parts: Vec<Value> = s.split(',')
255
+
.filter_map(|p| p.trim().parse::<i64>().ok())
256
+
.map(|i| Value::Number(Number::Integer(i)))
257
+
.collect();
258
+
Value::Array(parts)
259
+
}
260
+
Value::Array(_) => inner,
261
+
_ => inner,
262
+
},
263
+
"obj" => match &inner {
264
+
Value::String(s) => {
265
+
// Try to parse as JSON
266
+
if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(s) {
267
+
json_to_value(parsed)
268
+
} else {
269
+
inner
270
+
}
271
+
}
272
+
_ => inner,
273
+
},
274
+
_ => inner, // Unknown type, return value as-is
275
+
}
276
+
}
277
+
Value::Array(arr) => Value::Array(arr.into_iter().map(apply_builtin_types).collect()),
278
+
Value::Object(obj) => Value::Object(
279
+
obj.into_iter()
280
+
.map(|(k, v)| (k, apply_builtin_types(v)))
281
+
.collect()
282
+
),
283
+
other => other,
284
+
}
285
+
}
286
+
287
+
fn json_to_value(json: serde_json::Value) -> Value {
288
+
match json {
289
+
serde_json::Value::Null => Value::Null,
290
+
serde_json::Value::Bool(b) => Value::Bool(b),
291
+
serde_json::Value::Number(n) => {
292
+
if let Some(i) = n.as_i64() {
293
+
Value::Number(Number::Integer(i))
294
+
} else if let Some(f) = n.as_f64() {
295
+
Value::Number(Number::Float(f))
296
+
} else {
297
+
Value::Null
298
+
}
299
+
}
300
+
serde_json::Value::String(s) => Value::String(s),
301
+
serde_json::Value::Array(arr) => {
302
+
Value::Array(arr.into_iter().map(json_to_value).collect())
303
+
}
304
+
serde_json::Value::Object(obj) => {
305
+
Value::Object(obj.into_iter().map(|(k, v)| (k, json_to_value(v))).collect())
306
+
}
307
+
}
308
+
}
309
+
+80
src/uson.pest
+80
src/uson.pest
···
1
+
WHITESPACE = _{ " " | "\t" | "\n" | "\r" }
2
+
COMMENT = _{ "#" ~ (!("\n" | "\r") ~ ANY)* }
3
+
4
+
uson_text = { SOI ~ expr* ~ EOI }
5
+
6
+
expr = { value }
7
+
8
+
// Values
9
+
value = {
10
+
typed
11
+
| false_val
12
+
| null_val
13
+
| true_val
14
+
| assign
15
+
| object
16
+
| array
17
+
| number
18
+
| string
19
+
}
20
+
21
+
false_val = { "false" }
22
+
null_val = { "null" }
23
+
true_val = { "true" }
24
+
25
+
// Objects - support optional commas like arrays
26
+
object = {
27
+
"{" ~ (member ~ (","? ~ member)* ~ ","?)? ~ "}"
28
+
}
29
+
30
+
member = { string ~ ":" ~ value }
31
+
32
+
// Arrays
33
+
array = {
34
+
"[" ~ (value ~ (","? ~ value)* ~ ","?)? ~ "]"
35
+
}
36
+
37
+
// Numbers - must not be followed by alphanumeric to avoid matching "1ab" as number
38
+
number = @{
39
+
"-"? ~ int ~ ("." ~ ASCII_DIGIT+)? ~ (^"e" ~ ("+" | "-")? ~ ASCII_DIGIT+)? ~ !ASCII_ALPHANUMERIC
40
+
}
41
+
42
+
int = { "0" | (ASCII_NONZERO_DIGIT ~ ASCII_DIGIT*) }
43
+
44
+
// Strings
45
+
string = {
46
+
double_quoted_string
47
+
| single_quoted_string
48
+
| unquoted_string
49
+
}
50
+
51
+
double_quoted_string = @{
52
+
"\"" ~ (escape_sequence | (!("\"" | "\\") ~ ANY))* ~ "\""
53
+
}
54
+
55
+
single_quoted_string = @{
56
+
"'" ~ (escape_sequence | (!("'" | "\\") ~ ANY))* ~ "'"
57
+
}
58
+
59
+
unquoted_string = @{
60
+
simple_char+
61
+
}
62
+
63
+
simple_char = {
64
+
!("\"" | "#" | "," | ":" | "[" | "\\" | "]" | "{" | "}" | WHITESPACE) ~ ANY
65
+
}
66
+
67
+
escape_sequence = @{
68
+
"\\" ~ (
69
+
"\"" | "'" | "\\" | "/" | "#"
70
+
| "b" | "f" | "n" | "r" | "t"
71
+
| ("u" ~ ASCII_HEX_DIGIT{4})
72
+
)
73
+
}
74
+
75
+
// Assignments
76
+
assign = { string ~ ":" ~ value }
77
+
78
+
// Typed literals
79
+
typed = { type_name ~ "!" ~ value }
80
+
type_name = @{ (ASCII_ALPHA | ASCII_DIGIT | "_" | "-")+ }
+495
tests/compliance.rs
+495
tests/compliance.rs
···
1
+
use uson::{parse, apply_builtin_types, Value, Number};
2
+
use serde_json;
3
+
4
+
fn parse_and_apply_types(input: &str) -> Vec<Value> {
5
+
let values = parse(input).expect("Parse should succeed");
6
+
values.into_iter().map(apply_builtin_types).collect()
7
+
}
8
+
9
+
fn to_array_mode(values: &[Value]) -> serde_json::Value {
10
+
serde_json::to_value(values).unwrap()
11
+
}
12
+
13
+
fn to_object_mode(values: &[Value]) -> serde_json::Value {
14
+
let mut result = serde_json::Map::new();
15
+
let mut non_object_index = 0;
16
+
17
+
for value in values.iter() {
18
+
match value {
19
+
Value::Object(obj) => {
20
+
for (key, val) in obj {
21
+
result.insert(key.clone(), serde_json::to_value(val).unwrap());
22
+
}
23
+
}
24
+
_ => {
25
+
result.insert(non_object_index.to_string(), serde_json::to_value(value).unwrap());
26
+
non_object_index += 1;
27
+
}
28
+
}
29
+
}
30
+
31
+
serde_json::Value::Object(result)
32
+
}
33
+
34
+
// BASIC TESTS
35
+
36
+
#[test]
37
+
fn test_empty_input() {
38
+
let values = parse_and_apply_types("");
39
+
assert_eq!(to_array_mode(&values), serde_json::json!([]));
40
+
assert_eq!(to_object_mode(&values), serde_json::json!({}));
41
+
}
42
+
43
+
#[test]
44
+
fn test_empty_whitespace() {
45
+
let values = parse_and_apply_types(" ");
46
+
assert_eq!(to_array_mode(&values), serde_json::json!([]));
47
+
assert_eq!(to_object_mode(&values), serde_json::json!({}));
48
+
}
49
+
50
+
#[test]
51
+
fn test_empty_newlines() {
52
+
let values = parse_and_apply_types("\n ");
53
+
assert_eq!(to_array_mode(&values), serde_json::json!([]));
54
+
assert_eq!(to_object_mode(&values), serde_json::json!({}));
55
+
}
56
+
57
+
#[test]
58
+
fn test_simple_values() {
59
+
let values = parse_and_apply_types("a b c");
60
+
assert_eq!(to_array_mode(&values), serde_json::json!(["a", "b", "c"]));
61
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": "a", "1": "b", "2": "c"}));
62
+
}
63
+
64
+
#[test]
65
+
fn test_nested_array_single() {
66
+
let values = parse_and_apply_types("[a]");
67
+
assert_eq!(to_array_mode(&values), serde_json::json!([["a"]]));
68
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": ["a"]}));
69
+
}
70
+
71
+
#[test]
72
+
fn test_nested_array_multiple() {
73
+
let values = parse_and_apply_types("[a b c]");
74
+
assert_eq!(to_array_mode(&values), serde_json::json!([["a", "b", "c"]]));
75
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": ["a", "b", "c"]}));
76
+
}
77
+
78
+
#[test]
79
+
fn test_nested_array_deep() {
80
+
let values = parse_and_apply_types("[a [[b]] c]");
81
+
assert_eq!(to_array_mode(&values), serde_json::json!([["a", [["b"]], "c"]]));
82
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": ["a", [["b"]], "c"]}));
83
+
}
84
+
85
+
#[test]
86
+
fn test_simple_objects() {
87
+
let values = parse_and_apply_types("a:1 b:2 c:3");
88
+
assert_eq!(to_array_mode(&values), serde_json::json!([{"a": 1}, {"b": 2}, {"c": 3}]));
89
+
assert_eq!(to_object_mode(&values), serde_json::json!({"a": 1, "b": 2, "c": 3}));
90
+
}
91
+
92
+
#[test]
93
+
fn test_simple_objects_in_array() {
94
+
let values = parse_and_apply_types("[a:1 b:2 c:3]");
95
+
assert_eq!(to_array_mode(&values), serde_json::json!([[{"a": 1}, {"b": 2}, {"c": 3}]]));
96
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": [{"a": 1}, {"b": 2}, {"c": 3}]}));
97
+
}
98
+
99
+
#[test]
100
+
fn test_type_str() {
101
+
let values = parse_and_apply_types("str!42");
102
+
assert_eq!(to_array_mode(&values), serde_json::json!(["42"]));
103
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": "42"}));
104
+
}
105
+
106
+
#[test]
107
+
fn test_type_nested() {
108
+
let values = parse_and_apply_types("int!str!42");
109
+
assert_eq!(to_array_mode(&values), serde_json::json!([42]));
110
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": 42}));
111
+
}
112
+
113
+
#[test]
114
+
fn test_type_triple_nested() {
115
+
let values = parse_and_apply_types("str!int!str!42");
116
+
assert_eq!(to_array_mode(&values), serde_json::json!(["42"]));
117
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": "42"}));
118
+
}
119
+
120
+
#[test]
121
+
fn test_type_in_object() {
122
+
let values = parse_and_apply_types("x:{test:str!42 multi:bool!str!true}");
123
+
assert_eq!(
124
+
to_array_mode(&values),
125
+
serde_json::json!([{"x": {"test": "42", "multi": true}}])
126
+
);
127
+
assert_eq!(
128
+
to_object_mode(&values),
129
+
serde_json::json!({"x": {"test": "42", "multi": true}})
130
+
);
131
+
}
132
+
133
+
#[test]
134
+
fn test_type_str_array() {
135
+
let values = parse_and_apply_types("str![1 2 3]");
136
+
assert_eq!(to_array_mode(&values), serde_json::json!(["1,2,3"]));
137
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": "1,2,3"}));
138
+
}
139
+
140
+
#[test]
141
+
fn test_type_nonexistent() {
142
+
let values = parse_and_apply_types("nonexists!42");
143
+
assert_eq!(to_array_mode(&values), serde_json::json!([42]));
144
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": 42}));
145
+
}
146
+
147
+
#[test]
148
+
fn test_type_custom_name() {
149
+
let values = parse_and_apply_types("A_b3-!42");
150
+
assert_eq!(to_array_mode(&values), serde_json::json!([42]));
151
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": 42}));
152
+
}
153
+
154
+
#[test]
155
+
fn test_advanced_example() {
156
+
let values = parse_and_apply_types(
157
+
r#"number:12.05 text:Banana quotedText:"John Devilseed" empty:null good:true"#
158
+
);
159
+
assert_eq!(
160
+
to_array_mode(&values),
161
+
serde_json::json!([
162
+
{"number": 12.05},
163
+
{"text": "Banana"},
164
+
{"quotedText": "John Devilseed"},
165
+
{"empty": null},
166
+
{"good": true}
167
+
])
168
+
);
169
+
assert_eq!(
170
+
to_object_mode(&values),
171
+
serde_json::json!({
172
+
"number": 12.05,
173
+
"text": "Banana",
174
+
"quotedText": "John Devilseed",
175
+
"empty": null,
176
+
"good": true
177
+
})
178
+
);
179
+
}
180
+
181
+
// TYPE TESTS
182
+
183
+
#[test]
184
+
fn test_number_integer() {
185
+
let values = parse_and_apply_types("1");
186
+
assert_eq!(to_array_mode(&values), serde_json::json!([1]));
187
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": 1}));
188
+
}
189
+
190
+
#[test]
191
+
fn test_number_float() {
192
+
let values = parse_and_apply_types("42.42");
193
+
assert_eq!(to_array_mode(&values), serde_json::json!([42.42]));
194
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": 42.42}));
195
+
}
196
+
197
+
#[test]
198
+
fn test_number_negative() {
199
+
let values = parse_and_apply_types("-50.43");
200
+
assert_eq!(to_array_mode(&values), serde_json::json!([-50.43]));
201
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": -50.43}));
202
+
}
203
+
204
+
#[test]
205
+
fn test_number_large() {
206
+
let values = parse_and_apply_types("1000000.0001");
207
+
assert_eq!(to_array_mode(&values), serde_json::json!([1000000.0001]));
208
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": 1000000.0001}));
209
+
}
210
+
211
+
#[test]
212
+
fn test_number_scientific() {
213
+
let values = parse_and_apply_types("10e5");
214
+
assert_eq!(to_array_mode(&values), serde_json::json!([10e5]));
215
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": 10e5}));
216
+
}
217
+
218
+
#[test]
219
+
fn test_float_type_cast() {
220
+
let values = parse_and_apply_types(r#"float!"42.42""#);
221
+
assert_eq!(to_array_mode(&values), serde_json::json!([42.42]));
222
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": 42.42}));
223
+
}
224
+
225
+
#[test]
226
+
fn test_string_simple() {
227
+
let values = parse_and_apply_types("abc");
228
+
assert_eq!(to_array_mode(&values), serde_json::json!(["abc"]));
229
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": "abc"}));
230
+
}
231
+
232
+
#[test]
233
+
fn test_string_starts_with_digit() {
234
+
let values = parse_and_apply_types("1ab");
235
+
assert_eq!(to_array_mode(&values), serde_json::json!(["1ab"]));
236
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": "1ab"}));
237
+
}
238
+
239
+
#[test]
240
+
fn test_string_multiple() {
241
+
let values = parse_and_apply_types("abc def");
242
+
assert_eq!(to_array_mode(&values), serde_json::json!(["abc", "def"]));
243
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": "abc", "1": "def"}));
244
+
}
245
+
246
+
#[test]
247
+
fn test_string_double_quoted() {
248
+
let values = parse_and_apply_types(r#""abc""#);
249
+
assert_eq!(to_array_mode(&values), serde_json::json!(["abc"]));
250
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": "abc"}));
251
+
}
252
+
253
+
#[test]
254
+
fn test_string_double_quoted_with_space() {
255
+
let values = parse_and_apply_types(r#""abc def""#);
256
+
assert_eq!(to_array_mode(&values), serde_json::json!(["abc def"]));
257
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": "abc def"}));
258
+
}
259
+
260
+
#[test]
261
+
fn test_string_double_quoted_escaped() {
262
+
let values = parse_and_apply_types(r#""abc \"ok def""#);
263
+
assert_eq!(to_array_mode(&values), serde_json::json!([r#"abc "ok def"#]));
264
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": r#"abc "ok def"#}));
265
+
}
266
+
267
+
#[test]
268
+
fn test_string_single_quoted() {
269
+
let values = parse_and_apply_types("'abc'");
270
+
assert_eq!(to_array_mode(&values), serde_json::json!(["abc"]));
271
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": "abc"}));
272
+
}
273
+
274
+
#[test]
275
+
fn test_string_single_quoted_with_space() {
276
+
let values = parse_and_apply_types("'abc def'");
277
+
assert_eq!(to_array_mode(&values), serde_json::json!(["abc def"]));
278
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": "abc def"}));
279
+
}
280
+
281
+
#[test]
282
+
fn test_string_single_quoted_escaped() {
283
+
let values = parse_and_apply_types(r#"'abc \'42 def'"#);
284
+
assert_eq!(to_array_mode(&values), serde_json::json!(["abc '42 def"]));
285
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": "abc '42 def"}));
286
+
}
287
+
288
+
#[test]
289
+
fn test_null_value() {
290
+
let values = parse_and_apply_types("null");
291
+
assert_eq!(to_array_mode(&values), serde_json::json!([null]));
292
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": null}));
293
+
}
294
+
295
+
#[test]
296
+
fn test_null_typed() {
297
+
let values = parse_and_apply_types("x:null!xx");
298
+
assert_eq!(to_array_mode(&values), serde_json::json!([{"x": null}]));
299
+
assert_eq!(to_object_mode(&values), serde_json::json!({"x": null}));
300
+
}
301
+
302
+
#[test]
303
+
fn test_true_value() {
304
+
let values = parse_and_apply_types("true");
305
+
assert_eq!(to_array_mode(&values), serde_json::json!([true]));
306
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": true}));
307
+
}
308
+
309
+
#[test]
310
+
fn test_bool_true_typed() {
311
+
let values = parse_and_apply_types(r#"bool!"true""#);
312
+
assert_eq!(to_array_mode(&values), serde_json::json!([true]));
313
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": true}));
314
+
}
315
+
316
+
#[test]
317
+
fn test_false_value() {
318
+
let values = parse_and_apply_types("false");
319
+
assert_eq!(to_array_mode(&values), serde_json::json!([false]));
320
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": false}));
321
+
}
322
+
323
+
#[test]
324
+
fn test_bool_false_typed() {
325
+
let values = parse_and_apply_types(r#"bool!"false""#);
326
+
assert_eq!(to_array_mode(&values), serde_json::json!([false]));
327
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": false}));
328
+
}
329
+
330
+
#[test]
331
+
fn test_array_single() {
332
+
let values = parse_and_apply_types("[a]");
333
+
assert_eq!(to_array_mode(&values), serde_json::json!([["a"]]));
334
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": ["a"]}));
335
+
}
336
+
337
+
#[test]
338
+
fn test_array_numbers_no_comma() {
339
+
let values = parse_and_apply_types("[1 2 3]");
340
+
assert_eq!(to_array_mode(&values), serde_json::json!([[1, 2, 3]]));
341
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": [1, 2, 3]}));
342
+
}
343
+
344
+
#[test]
345
+
fn test_array_numbers_with_commas() {
346
+
let values = parse_and_apply_types("[1, 2, 3]");
347
+
assert_eq!(to_array_mode(&values), serde_json::json!([[1, 2, 3]]));
348
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": [1, 2, 3]}));
349
+
}
350
+
351
+
#[test]
352
+
fn test_array_numbers_mixed_commas() {
353
+
let values = parse_and_apply_types("[1 2,3 ]");
354
+
assert_eq!(to_array_mode(&values), serde_json::json!([[1, 2, 3]]));
355
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": [1, 2, 3]}));
356
+
}
357
+
358
+
#[test]
359
+
fn test_array_typed() {
360
+
let values = parse_and_apply_types(r#"arr!"1,2,3""#);
361
+
assert_eq!(to_array_mode(&values), serde_json::json!([[1, 2, 3]]));
362
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": [1, 2, 3]}));
363
+
}
364
+
365
+
#[test]
366
+
fn test_object_single() {
367
+
let values = parse_and_apply_types("{a:1}");
368
+
assert_eq!(to_array_mode(&values), serde_json::json!([{"a": 1}]));
369
+
assert_eq!(to_object_mode(&values), serde_json::json!({"a": 1}));
370
+
}
371
+
372
+
#[test]
373
+
fn test_object_multiple_no_comma() {
374
+
let values = parse_and_apply_types("{a:1 b:two c:3}");
375
+
assert_eq!(to_array_mode(&values), serde_json::json!([{"a": 1, "b": "two", "c": 3}]));
376
+
assert_eq!(to_object_mode(&values), serde_json::json!({"a": 1, "b": "two", "c": 3}));
377
+
}
378
+
379
+
#[test]
380
+
fn test_object_multiple_with_commas() {
381
+
let values = parse_and_apply_types("{a:1,b:two,c:3}");
382
+
assert_eq!(to_array_mode(&values), serde_json::json!([{"a": 1, "b": "two", "c": 3}]));
383
+
assert_eq!(to_object_mode(&values), serde_json::json!({"a": 1, "b": "two", "c": 3}));
384
+
}
385
+
386
+
#[test]
387
+
fn test_object_typed() {
388
+
let values = parse_and_apply_types(r#"obj!'"a":1'"#);
389
+
assert_eq!(to_array_mode(&values), serde_json::json!([{"a": 1}]));
390
+
assert_eq!(to_object_mode(&values), serde_json::json!({"a": 1}));
391
+
}
392
+
393
+
#[test]
394
+
fn test_object_nested() {
395
+
let values = parse_and_apply_types("x:{a:1 b:coffee}");
396
+
assert_eq!(
397
+
to_array_mode(&values),
398
+
serde_json::json!([{"x": {"a": 1, "b": "coffee"}}])
399
+
);
400
+
assert_eq!(
401
+
to_object_mode(&values),
402
+
serde_json::json!({"x": {"a": 1, "b": "coffee"}})
403
+
);
404
+
}
405
+
406
+
#[test]
407
+
fn test_assign_simple() {
408
+
let values = parse_and_apply_types("a:1");
409
+
assert_eq!(to_array_mode(&values), serde_json::json!([{"a": 1}]));
410
+
assert_eq!(to_object_mode(&values), serde_json::json!({"a": 1}));
411
+
}
412
+
413
+
#[test]
414
+
fn test_assign_multiple() {
415
+
let values = parse_and_apply_types("a:1 b:2 c:3");
416
+
assert_eq!(to_array_mode(&values), serde_json::json!([{"a": 1}, {"b": 2}, {"c": 3}]));
417
+
assert_eq!(to_object_mode(&values), serde_json::json!({"a": 1, "b": 2, "c": 3}));
418
+
}
419
+
420
+
#[test]
421
+
fn test_assign_single_quoted_key() {
422
+
let values = parse_and_apply_types("'a':1");
423
+
assert_eq!(to_array_mode(&values), serde_json::json!([{"a": 1}]));
424
+
assert_eq!(to_object_mode(&values), serde_json::json!({"a": 1}));
425
+
}
426
+
427
+
#[test]
428
+
fn test_assign_double_quoted_key() {
429
+
let values = parse_and_apply_types(r#""a":1"#);
430
+
assert_eq!(to_array_mode(&values), serde_json::json!([{"a": 1}]));
431
+
assert_eq!(to_object_mode(&values), serde_json::json!({"a": 1}));
432
+
}
433
+
434
+
#[test]
435
+
fn test_assign_special_chars() {
436
+
let values = parse_and_apply_types("~@$%^&*()-:1");
437
+
assert_eq!(to_array_mode(&values), serde_json::json!([{"~@$%^&*()-": 1}]));
438
+
assert_eq!(to_object_mode(&values), serde_json::json!({"~@$%^&*()-": 1}));
439
+
}
440
+
441
+
#[test]
442
+
fn test_comment_empty() {
443
+
let values = parse_and_apply_types("#");
444
+
assert_eq!(to_array_mode(&values), serde_json::json!([]));
445
+
assert_eq!(to_object_mode(&values), serde_json::json!({}));
446
+
}
447
+
448
+
#[test]
449
+
fn test_comment_escaped_hash() {
450
+
let values = parse_and_apply_types(r"a:\#");
451
+
assert_eq!(to_array_mode(&values), serde_json::json!([{"a": "#"}]));
452
+
assert_eq!(to_object_mode(&values), serde_json::json!({"a": "#"}));
453
+
}
454
+
455
+
#[test]
456
+
fn test_comment_in_quotes() {
457
+
let values = parse_and_apply_types(r"a:'\#1'");
458
+
assert_eq!(to_array_mode(&values), serde_json::json!([{"a": "#1"}]));
459
+
assert_eq!(to_object_mode(&values), serde_json::json!({"a": "#1"}));
460
+
}
461
+
462
+
#[test]
463
+
fn test_comment_after_value() {
464
+
let values = parse_and_apply_types("a:1 # a");
465
+
assert_eq!(to_array_mode(&values), serde_json::json!([{"a": 1}]));
466
+
assert_eq!(to_object_mode(&values), serde_json::json!({"a": 1}));
467
+
}
468
+
469
+
#[test]
470
+
fn test_comment_with_spaces() {
471
+
let values = parse_and_apply_types(" # a");
472
+
assert_eq!(to_array_mode(&values), serde_json::json!([]));
473
+
assert_eq!(to_object_mode(&values), serde_json::json!({}));
474
+
}
475
+
476
+
#[test]
477
+
fn test_comment_line_start() {
478
+
let values = parse_and_apply_types("#a");
479
+
assert_eq!(to_array_mode(&values), serde_json::json!([]));
480
+
assert_eq!(to_object_mode(&values), serde_json::json!({}));
481
+
}
482
+
483
+
#[test]
484
+
fn test_comment_in_array() {
485
+
let values = parse_and_apply_types("x: [a # b c]\n# good\n tone ]");
486
+
assert_eq!(to_array_mode(&values), serde_json::json!([{"x": ["a", "tone"]}]));
487
+
assert_eq!(to_object_mode(&values), serde_json::json!({"x": ["a", "tone"]}));
488
+
}
489
+
490
+
#[test]
491
+
fn test_comment_complex() {
492
+
let values = parse_and_apply_types("{x: [#y ]\n] #n\n} 42 #^\n@");
493
+
assert_eq!(to_array_mode(&values), serde_json::json!([{"x": []}, 42, "@"]));
494
+
assert_eq!(to_object_mode(&values), serde_json::json!({"0": 42, "1": "@", "x": []}));
495
+
}