μson (uson) is a shorthand for JSON

initial commit

tree.fail 987d2752

+1
.gitignore
··· 1 + target
+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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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
··· 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 + }