diff --git a/.gitignore b/.gitignore index 16215cb..2e3d0f9 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ result # Everyone their own tmp/ +tests/.DS_Store +.DS_Store diff --git a/Cargo.lock b/Cargo.lock index bd358fb..2893d3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,30 +4,33 @@ version = 4 [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] [[package]] -name = "allocator-api2" -version = "0.2.21" +name = "alloca" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] [[package]] -name = "android-tzdata" -version = "0.1.1" +name = "allocator-api2" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android_system_properties" @@ -46,9 +49,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.18" +version = "0.6.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" dependencies = [ "anstyle", "anstyle-parse", @@ -61,37 +64,37 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.10" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" [[package]] name = "anstyle-parse" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.2" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.7" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", - "once_cell", - "windows-sys 0.59.0", + "once_cell_polyfill", + "windows-sys 0.61.2", ] [[package]] @@ -102,15 +105,15 @@ checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bindgen" -version = "0.70.1" +version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ "bitflags", "cexpr", @@ -141,37 +144,61 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "bstr" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" dependencies = [ "memchr", "serde", ] +[[package]] +name = "buf-trait" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21eaafc770e8c073d6c3facafe7617e774305d4954aa6351b9c452eb37ee17b4" +dependencies = [ + "zerocopy 0.7.35", +] + [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "bytesize" -version = "1.3.3" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bd91ee7b2422bcb158d90ef4d14f75ef67f340943fc4149891dcce8f8b972a3" + +[[package]] +name = "byteyarn" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e93abca9e28e0a1b9877922aacb20576e05d4679ffa78c3d6dc22a26a216659" +checksum = "b93e51d26468a15ea59f8525e0c13dc405db43e644a0b1e6d44346c72cf4cf7b" +dependencies = [ + "buf-trait", +] [[package]] name = "cast" @@ -179,12 +206,22 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +[[package]] +name = "castaway" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a" +dependencies = [ + "rustversion", +] + [[package]] name = "cc" -version = "1.2.21" +version = "1.2.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" dependencies = [ + "find-msvc-tools", "shlex", ] @@ -199,9 +236,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cfg_aliases" @@ -211,16 +248,15 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" +checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" dependencies = [ - "android-tzdata", "iana-time-zone", "num-traits", "pure-rust-locales", "serde", - "windows-link", + "windows-link 0.2.1", ] [[package]] @@ -272,9 +308,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.37" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", "clap_derive", @@ -282,23 +318,23 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.37" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", "unicase", - "unicode-width 0.2.0", + "unicode-width 0.2.2", ] [[package]] name = "clap_derive" -version = "4.5.32" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck", "proc-macro2", @@ -308,21 +344,21 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" [[package]] name = "colorchoice" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" [[package]] name = "const_format" -version = "0.2.34" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" +checksum = "7faa7469a93a566e9ccc1c73fe783b4a65c274c5ace346038dca9c39fe0030ad" dependencies = [ "const_format_proc_macros", ] @@ -338,6 +374,15 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -346,34 +391,33 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] [[package]] name = "criterion" -version = "0.5.1" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +checksum = "4d883447757bb0ee46f233e9dc22eb84d93a9508c9b868687b274fc431d886bf" dependencies = [ + "alloca", "anes", "cast", "ciborium", "clap", "criterion-plot", - "is-terminal", - "itertools 0.10.5", + "itertools 0.13.0", "num-traits", - "once_cell", "oorandom", + "page_size", "plotters", "rayon", "regex", "serde", - "serde_derive", "serde_json", "tinytemplate", "walkdir", @@ -381,12 +425,12 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.5.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +checksum = "ed943f81ea2faa8dcecbbfa50164acf95d555afec96a27871663b300e387b2e4" dependencies = [ "cast", - "itertools 0.10.5", + "itertools 0.13.0", ] [[package]] @@ -416,15 +460,17 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crossterm" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ "bitflags", "crossterm_winapi", + "derive_more", + "document-features", "mio", "parking_lot", - "rustix 0.38.44", + "rustix 1.1.2", "signal-hook", "signal-hook-mio", "winapi", @@ -441,38 +487,69 @@ dependencies = [ [[package]] name = "crunchy" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "deranged" -version = "0.4.0" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", ] +[[package]] +name = "derive_more" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10b768e943bed7bf2cab53df09f4bc34bfd217cdb57d971e769874c9a6710618" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d286bfdaf75e988b4a78e013ecd79c581e06399ab53fbacd2d916c2f904f30b" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + [[package]] name = "dirs" -version = "5.0.1" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ "dirs-sys", ] [[package]] name = "dirs-sys" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.48.0", + "windows-sys 0.61.2", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", ] [[package]] @@ -482,16 +559,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] -name = "env_logger" -version = "0.10.2" +name = "env_filter" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" dependencies = [ - "humantime", - "is-terminal", "log", "regex", - "termcolor", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", ] [[package]] @@ -502,29 +589,30 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "erased-serde" -version = "0.4.6" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" +checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3" dependencies = [ "serde", + "serde_core", "typeid", ] [[package]] name = "errno" -version = "0.3.11" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "fancy-regex" -version = "0.14.0" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298" +checksum = "998b056554fbe42e03ae0e152895cd1a7e1002aec800fdc6635d20270260c46f" dependencies = [ "bit-set", "regex-automata", @@ -537,11 +625,17 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + [[package]] name = "flate2" -version = "1.1.1" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" dependencies = [ "crc32fast", "miniz_oxide", @@ -604,32 +698,32 @@ checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", ] [[package]] name = "getrandom" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi", - "wasi 0.14.2+wasi-0.2.4", + "wasip2", ] [[package]] name = "glob" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "globset" -version = "0.4.16" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" +checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" dependencies = [ "aho-corasick", "bstr", @@ -640,19 +734,20 @@ dependencies = [ [[package]] name = "half" -version = "2.6.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "zerocopy 0.8.31", ] [[package]] name = "hashbrown" -version = "0.15.3" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", @@ -660,16 +755,16 @@ dependencies = [ ] [[package]] -name = "heck" -version = "0.5.0" +name = "hashbrown" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] -name = "hermit-abi" +name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hex" @@ -677,17 +772,11 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "humantime" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" - [[package]] name = "iana-time-zone" -version = "0.1.63" +version = "0.1.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" +checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -695,7 +784,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.61.0", + "windows-core 0.62.2", ] [[package]] @@ -709,9 +798,9 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.23" +version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" +checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" dependencies = [ "crossbeam-deque", "globset", @@ -725,34 +814,23 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.9.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.16.1", ] [[package]] name = "inventory" -version = "0.3.20" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab08d7cd2c5897f2c949e5383ea7c7db03fb19130ffcfbf7eda795137ae3cb83" +checksum = "bc61209c082fbeb19919bee74b176221b27223e27b65d781eb91af24eb1fb46e" dependencies = [ "rustversion", ] -[[package]] -name = "is-terminal" -version = "0.4.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.59.0", -] - [[package]] name = "is_ci" version = "1.2.0" @@ -767,24 +845,24 @@ checksum = "1fe266d2e243c931d8190177f20bf7f24eed45e96f39e87dc49a27b32d12d407" [[package]] name = "is_terminal_polyfill" -version = "1.70.1" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" -version = "0.10.5" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] [[package]] name = "itertools" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] @@ -795,37 +873,73 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jiff" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ "once_cell", "wasm-bindgen", ] +[[package]] +name = "lean_string" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "962df00ba70ac8d5ca5c064e17e5c3d090c087fd8d21aa45096c716b169da514" +dependencies = [ + "castaway", + "itoa", + "ryu", + "serde", +] + [[package]] name = "libc" -version = "0.2.172" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libloading" -version = "0.8.6" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-link 0.2.1", ] [[package]] name = "libproc" -version = "0.14.10" +version = "0.14.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78a09b56be5adbcad5aa1197371688dc6bb249a26da3bca2011ee2fb987ebfb" +checksum = "a54ad7278b8bc5301d5ffd2a94251c004feb971feba96c971ea4063645990757" dependencies = [ "bindgen", "errno", @@ -834,9 +948,9 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" dependencies = [ "bitflags", "libc", @@ -850,25 +964,30 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litrs" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.27" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lru" @@ -876,32 +995,33 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown", + "hashbrown 0.15.5", ] [[package]] name = "lscolors" -version = "0.17.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53304fff6ab1e597661eee37e42ea8c47a146fca280af902bb76bff8a896e523" +checksum = "61183da5de8ba09a58e330d55e5ea796539d8443bd00fdeb863eac39724aa4ab" dependencies = [ + "aho-corasick", "nu-ansi-term", ] [[package]] name = "mach2" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" dependencies = [ "libc", ] [[package]] name = "memchr" -version = "2.7.4" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "miette" @@ -939,30 +1059,31 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", + "simd-adler32", ] [[package]] name = "mio" -version = "1.0.3" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "wasi", + "windows-sys 0.61.2", ] [[package]] name = "nix" -version = "0.29.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ "bitflags", "cfg-if", @@ -991,21 +1112,37 @@ dependencies = [ [[package]] name = "nu-ansi-term" -version = "0.50.1" +version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", +] + +[[package]] +name = "nu-cmd-base" +version = "0.109.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2460ee389a43b935aa18ef5ed9fa8275bdf617e8c05eba7c2b82f92effd2132b" +dependencies = [ + "indexmap", + "miette", + "nu-engine", + "nu-parser", + "nu-path", + "nu-protocol", ] [[package]] name = "nu-cmd-lang" -version = "0.104.0" +version = "0.109.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e66adfeda88f8e27bcb25d068d9e6e8b3a94c2bf988a9c30e8e3b2045867aefe" +checksum = "5b266674d87b816264f6aff8cca351e6ebb156f34faab45d7d728c2aba005495" dependencies = [ - "itertools 0.13.0", + "itertools 0.14.0", + "nu-cmd-base", "nu-engine", + "nu-experimental", "nu-parser", "nu-protocol", "nu-utils", @@ -1014,9 +1151,9 @@ dependencies = [ [[package]] name = "nu-derive-value" -version = "0.104.0" +version = "0.109.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd0d8e358b6440d01fe4e617f180aea826bade72efb54f5dc1c22e0e8038b6f" +checksum = "1465d2d3ada6004cb6689f269a08c70ba81056231e2b5392d1e0ccf5825f81cb" dependencies = [ "heck", "proc-macro-error2", @@ -1027,32 +1164,44 @@ dependencies = [ [[package]] name = "nu-engine" -version = "0.104.0" +version = "0.109.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2b01483e3d09460375f0c0da7a83b6dc26fb319ca09c55d0665087b2d587c7" +checksum = "b3b777faf7c5180fe5d7f67d83c44fd14138d91f2938a36494ed6ac66b7160f3" dependencies = [ + "fancy-regex", "log", + "nu-experimental", "nu-glob", "nu-path", "nu-protocol", "nu-utils", ] +[[package]] +name = "nu-experimental" +version = "0.109.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73dd212a1afdad646a38c00579a0988264880aeb97fee820b349a28cdcc04df2" +dependencies = [ + "itertools 0.14.0", + "thiserror 2.0.17", +] + [[package]] name = "nu-glob" -version = "0.104.0" +version = "0.109.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "202ce25889336061efea24e69d4e0de7147c15fd9892cdd70533500d47db8364" +checksum = "15aa2c17078926f14e393b4b708e69f228cb6fd4c81136839bde82772bdde1b5" [[package]] name = "nu-parser" -version = "0.104.0" +version = "0.109.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0591ef4d4989c1930863d9d17d8fd2d70b03ec2d9caeca067e9626e05c49d9" +checksum = "237172636312c3566272511a00c1dc355202406c376e1546a45a33c65e81babe" dependencies = [ "bytesize", "chrono", - "itertools 0.13.0", + "itertools 0.14.0", "log", "nu-engine", "nu-path", @@ -1063,9 +1212,9 @@ dependencies = [ [[package]] name = "nu-path" -version = "0.104.0" +version = "0.109.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41c68c7c06898a5c4c9f10038da63759661cb8ac8f301ce7d159173a595c8258" +checksum = "dde9d8ba26f62c07176c0237a36f38ce964ab3a0dcfb6aab1feea7515d1c6594" dependencies = [ "dirs", "omnipath", @@ -1075,9 +1224,9 @@ dependencies = [ [[package]] name = "nu-protocol" -version = "0.104.0" +version = "0.109.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab657b1947f1fad3c5052cb210fa311744736a4800a966ae21c4bc63de7c60ab" +checksum = "038943300ca9de0924fef1c795a7dd16ffc67105629477cf163e8ee6bad95ea6" dependencies = [ "bytes", "chrono", @@ -1093,6 +1242,7 @@ dependencies = [ "miette", "nix", "nu-derive-value", + "nu-experimental", "nu-glob", "nu-path", "nu-system", @@ -1103,20 +1253,21 @@ dependencies = [ "serde_json", "strum", "strum_macros", - "thiserror 2.0.12", + "thiserror 2.0.17", "typetag", "web-time", - "windows-sys 0.48.0", + "windows 0.62.2", + "windows-sys 0.61.2", ] [[package]] name = "nu-system" -version = "0.104.0" +version = "0.109.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f47094aaab4f1e3a86c3960400d82a50fcabde907f964ae095963ec95669577a" +checksum = "46be734cc9b19e09a9665769e14360e13e6978490056ba5c8bfad7dd0537ea83" dependencies = [ "chrono", - "itertools 0.13.0", + "itertools 0.14.0", "libc", "libproc", "log", @@ -1126,20 +1277,23 @@ dependencies = [ "procfs", "sysinfo", "web-time", - "windows 0.56.0", + "windows 0.62.2", ] [[package]] name = "nu-utils" -version = "0.104.0" +version = "0.109.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327999b774d78b301a6b68c33d312a1a8047c59fb8971b6552ebf823251f1481" +checksum = "3f8eb43c29cc5bce85f87defdadc2cca964fa434d808af37036a7cb78f3c68e9" dependencies = [ + "byteyarn", "crossterm", "crossterm_winapi", "fancy-regex", + "lean_string", "log", "lscolors", + "memchr", "nix", "num-format", "serde", @@ -1166,7 +1320,7 @@ dependencies = [ "rayon", "rstest", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.17", ] [[package]] @@ -1205,9 +1359,9 @@ dependencies = [ [[package]] name = "nuon" -version = "0.104.0" +version = "0.109.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ad155fee37ed58420483d38c40cd9bea88160e281e1d04c1592525c8f8da9a5" +checksum = "f8c4f2e4460a6cf00e50cddf0840f954874d645be3af5196c5858c70c069d8c0" dependencies = [ "nu-engine", "nu-parser", @@ -1215,6 +1369,25 @@ dependencies = [ "nu-utils", ] +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" +dependencies = [ + "libc", + "objc2-core-foundation", +] + [[package]] name = "omnipath" version = "0.1.6" @@ -1227,6 +1400,12 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "oorandom" version = "11.1.5" @@ -1241,25 +1420,35 @@ checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "os_pipe" -version = "1.2.1" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982" +checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "owo-colors" -version = "4.2.0" +version = "4.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1036865bb9422d3300cf723f657c2851d0e9ab12567854b1f4eba3d77decf564" +checksum = "9c6901729fa79e91a0913333229e9ca5dc725089d1c363b2f4b4760709dc4a52" + +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -1267,15 +1456,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.6", + "windows-link 0.2.1", ] [[package]] @@ -1318,6 +1507,21 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -1326,9 +1530,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "proc-macro-crate" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" dependencies = [ "toml_edit", ] @@ -1357,9 +1561,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.95" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] @@ -1391,9 +1595,9 @@ dependencies = [ [[package]] name = "pure-rust-locales" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1190fd18ae6ce9e137184f207593877e70f39b015040156b1e05081cdfe3733a" +checksum = "869675ad2d7541aea90c6d88c81f46a7f4ea9af8cd0395d38f11a95126998a0d" [[package]] name = "pwd" @@ -1407,24 +1611,24 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.40" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "rayon" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -1432,9 +1636,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -1442,38 +1646,38 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.11" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] [[package]] name = "redox_users" -version = "0.4.6" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror 1.0.69", + "thiserror 2.0.17", ] [[package]] name = "ref-cast" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.24" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", @@ -1482,9 +1686,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.11.1" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -1494,9 +1698,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -1505,9 +1709,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "relative-path" @@ -1517,21 +1721,20 @@ checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "rstest" -version = "0.25.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fc39292f8613e913f7df8fa892b8944ceb47c247b78e1b1ae2f09e019be789d" +checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49" dependencies = [ "futures-timer", "futures-util", "rstest_macros", - "rustc_version", ] [[package]] name = "rstest_macros" -version = "0.25.0" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f168d99749d307be9de54d23fd226628d99768225ef08f6ffb52e0182a27746" +checksum = "9c845311f0ff7951c5506121a9ad75aec44d083c31583b2ea5a30bcb0b0abba0" dependencies = [ "cfg-if", "glob", @@ -1547,9 +1750,9 @@ dependencies = [ [[package]] name = "rustc-hash" -version = "1.1.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustc_version" @@ -1575,22 +1778,22 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.7" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" +checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys 0.9.4", - "windows-sys 0.59.0", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.2", ] [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" @@ -1615,24 +1818,34 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "semver" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -1641,21 +1854,22 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.140" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", "memchr", "ryu", "serde", + "serde_core", ] [[package]] name = "shadow-rs" -version = "1.1.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d5625ed609cf66d7e505e7d487aca815626dc4ebb6c0dd07637ca61a44651a6" +checksum = "72d18183cef626bce22836103349c7050d73db799be0171386b80947d157ae32" dependencies = [ "const_format", "is_debug", @@ -1671,9 +1885,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" dependencies = [ "libc", "signal-hook-registry", @@ -1681,9 +1895,9 @@ dependencies = [ [[package]] name = "signal-hook-mio" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" dependencies = [ "libc", "mio", @@ -1692,27 +1906,30 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.5" +version = "1.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + [[package]] name = "slab" -version = "0.4.9" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "strip-ansi-escapes" @@ -1737,14 +1954,13 @@ checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" [[package]] name = "strum_macros" -version = "0.26.4" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ "heck", "proc-macro2", "quote", - "rustversion", "syn", ] @@ -1759,9 +1975,9 @@ dependencies = [ [[package]] name = "supports-hyperlinks" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804f44ed3c63152de6a9f90acbea1a110441de43006ea51bcce8f436196a288b" +checksum = "e396b6523b11ccb83120b115a0b7366de372751aa6edf19844dfb13a6af97e91" [[package]] name = "supports-unicode" @@ -1771,9 +1987,9 @@ checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" [[package]] name = "syn" -version = "2.0.101" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -1791,48 +2007,39 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.33.1" +version = "0.37.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01" +checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f" dependencies = [ - "core-foundation-sys", "libc", "memchr", "ntapi", - "rayon", - "windows 0.57.0", + "objc2-core-foundation", + "objc2-io-kit", + "windows 0.61.3", ] [[package]] name = "tempfile" -version = "3.19.1" +version = "3.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" dependencies = [ "fastrand", - "getrandom 0.3.2", + "getrandom 0.3.4", "once_cell", - "rustix 1.0.7", - "windows-sys 0.59.0", -] - -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", + "rustix 1.1.2", + "windows-sys 0.61.2", ] [[package]] name = "terminal_size" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" +checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0" dependencies = [ - "rustix 1.0.7", - "windows-sys 0.59.0", + "rustix 1.1.2", + "windows-sys 0.60.2", ] [[package]] @@ -1842,7 +2049,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" dependencies = [ "unicode-linebreak", - "unicode-width 0.2.0", + "unicode-width 0.2.2", ] [[package]] @@ -1856,11 +2063,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.17", ] [[package]] @@ -1876,9 +2083,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", @@ -1887,9 +2094,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.41" +version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", "itoa", @@ -1904,15 +2111,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" [[package]] name = "time-macros" -version = "0.2.22" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" dependencies = [ "num-conv", "time-core", @@ -1930,18 +2137,31 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.9" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] [[package]] name = "toml_edit" -version = "0.22.26" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ "indexmap", "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +dependencies = [ "winnow", ] @@ -1953,9 +2173,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" [[package]] name = "typetag" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f22b40dd7bfe8c14230cf9702081366421890435b2d625fa92b4acc4c3de6f" +checksum = "be2212c8a9b9bcfca32024de14998494cf9a5dfa59ea1b829de98bac374b86bf" dependencies = [ "erased-serde", "inventory", @@ -1966,9 +2186,9 @@ dependencies = [ [[package]] name = "typetag-impl" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35f5380909ffc31b4de4f4bdf96b877175a016aa2ca98cee39fcfd8c4d53d952" +checksum = "27a7a9b72ba121f6f1f6c3632b85604cac41aedb5ddc70accbebb6cac83de846" dependencies = [ "proc-macro2", "quote", @@ -1977,15 +2197,15 @@ dependencies = [ [[package]] name = "tz-rs" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1450bf2b99397e72070e7935c89facaa80092ac812502200375f1f7d33c71a1" +checksum = "14eff19b8dc1ace5bf7e4d920b2628ae3837f422ff42210cb1567cbf68b5accf" [[package]] name = "tzdb" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0be2ea5956f295449f47c0b825c5e109022ff1a6a53bb4f77682a87c2341fbf5" +checksum = "56d4e985b6dda743ae7fd4140c28105316ffd75bc58258ee6cc12934e3eb7a0c" dependencies = [ "iana-time-zone", "tz-rs", @@ -1994,9 +2214,9 @@ dependencies = [ [[package]] name = "tzdb_data" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c4c81d75033770e40fbd3643ce7472a1a9fd301f90b7139038228daf8af03ec" +checksum = "42302a846dea7ab786f42dc5f519387069045acff793e1178d9368414168fe95" dependencies = [ "tz-rs", ] @@ -2009,9 +2229,9 @@ checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "unicode-linebreak" @@ -2019,6 +2239,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + [[package]] name = "unicode-width" version = "0.1.14" @@ -2027,9 +2253,9 @@ checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "unicode-width" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" [[package]] name = "unicode-xid" @@ -2064,50 +2290,37 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" +name = "wasip2" +version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ - "wit-bindgen-rt", + "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2115,31 +2328,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.77" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" dependencies = [ "js-sys", "wasm-bindgen", @@ -2173,11 +2386,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2188,99 +2401,100 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows" -version = "0.56.0" +version = "0.61.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1de69df01bdf1ead2f4ac895dc77c9351aefff65b2f3db429a343f9cbf05e132" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" dependencies = [ - "windows-core 0.56.0", - "windows-targets 0.52.6", + "windows-collections 0.2.0", + "windows-core 0.61.2", + "windows-future 0.2.1", + "windows-link 0.1.3", + "windows-numerics 0.2.0", ] [[package]] name = "windows" -version = "0.57.0" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" dependencies = [ - "windows-core 0.57.0", - "windows-targets 0.52.6", + "windows-collections 0.3.2", + "windows-core 0.62.2", + "windows-future 0.3.2", + "windows-numerics 0.3.1", ] [[package]] -name = "windows-core" -version = "0.56.0" +name = "windows-collections" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4698e52ed2d08f8658ab0c39512a7c00ee5fe2688c65f8c0a4f06750d729f2a6" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" dependencies = [ - "windows-implement 0.56.0", - "windows-interface 0.56.0", - "windows-result 0.1.2", - "windows-targets 0.52.6", + "windows-core 0.61.2", ] [[package]] -name = "windows-core" -version = "0.57.0" +name = "windows-collections" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" dependencies = [ - "windows-implement 0.57.0", - "windows-interface 0.57.0", - "windows-result 0.1.2", - "windows-targets 0.52.6", + "windows-core 0.62.2", ] [[package]] name = "windows-core" -version = "0.61.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-implement 0.60.0", - "windows-interface 0.59.1", - "windows-link", - "windows-result 0.3.2", - "windows-strings", + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", ] [[package]] -name = "windows-implement" -version = "0.56.0" +name = "windows-core" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "proc-macro2", - "quote", - "syn", + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] -name = "windows-implement" -version = "0.57.0" +name = "windows-future" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ - "proc-macro2", - "quote", - "syn", + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading 0.1.0", ] [[package]] -name = "windows-implement" -version = "0.60.0" +name = "windows-future" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" dependencies = [ - "proc-macro2", - "quote", - "syn", + "windows-core 0.62.2", + "windows-link 0.2.1", + "windows-threading 0.2.1", ] [[package]] -name = "windows-interface" -version = "0.56.0" +name = "windows-implement" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", @@ -2289,9 +2503,9 @@ dependencies = [ [[package]] name = "windows-interface" -version = "0.57.0" +version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", @@ -2299,89 +2513,98 @@ dependencies = [ ] [[package]] -name = "windows-interface" -version = "0.59.1" +name = "windows-link" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ - "proc-macro2", - "quote", - "syn", + "windows-core 0.61.2", + "windows-link 0.1.3", ] [[package]] -name = "windows-link" -version = "0.1.1" +name = "windows-numerics" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link 0.2.1", +] [[package]] name = "windows-result" -version = "0.1.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-targets 0.52.6", + "windows-link 0.1.3", ] [[package]] name = "windows-result" -version = "0.3.2" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-link", + "windows-link 0.2.1", ] [[package]] name = "windows-strings" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] -name = "windows-sys" -version = "0.48.0" +name = "windows-strings" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-targets 0.48.5", + "windows-link 0.2.1", ] [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" -version = "0.59.0" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows-targets 0.52.6", + "windows-targets 0.53.5", ] [[package]] -name = "windows-targets" -version = "0.48.5" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-link 0.2.1", ] [[package]] @@ -2393,7 +2616,7 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", @@ -2401,10 +2624,39 @@ dependencies = [ ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link 0.2.1", +] [[package]] name = "windows_aarch64_gnullvm" @@ -2413,10 +2665,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" +name = "windows_aarch64_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" @@ -2425,10 +2677,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] -name = "windows_i686_gnu" -version = "0.48.5" +name = "windows_aarch64_msvc" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" @@ -2436,6 +2688,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" @@ -2443,10 +2701,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] -name = "windows_i686_msvc" -version = "0.48.5" +name = "windows_i686_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" @@ -2455,10 +2713,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" +name = "windows_i686_msvc" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" @@ -2467,10 +2725,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" +name = "windows_x86_64_gnu" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" @@ -2479,10 +2737,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" +name = "windows_x86_64_gnullvm" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" @@ -2490,20 +2748,64 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + [[package]] name = "winnow" -version = "0.7.9" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9fb597c990f03753e08d3c29efbfcf2019a003b4bf4ba19225c158e1549f0f3" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] [[package]] -name = "wit-bindgen-rt" -version = "0.39.0" +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "zerocopy" +version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ - "bitflags", + "byteorder", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +dependencies = [ + "zerocopy-derive 0.8.31", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] diff --git a/Cargo.toml b/Cargo.toml index 0d13171..fbd53e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,22 +14,22 @@ categories = ["command-line-utilities"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -clap = { version = "4.3.0", optional = true, features = ["unicode", "derive"] } -env_logger = "0.10.0" -ignore = "0.4" -log = "0.4.17" -nu-ansi-term = "0.50.1" -nu-cmd-lang = "0.104.0" -nu-parser = "0.104.0" -nu-protocol = "0.104.0" -nuon = "0.104.0" -rayon = "1.10" -thiserror = "2" +clap = { version = "4.5.53", optional = true, features = ["unicode", "derive"] } +env_logger = "0.11.8" +ignore = "0.4.25" +log = "0.4.29" +nu-ansi-term = "0.50.3" +nu-cmd-lang = "0.109.1" +nu-parser = "0.109.1" +nu-protocol = "0.109.1" +nuon = "0.109.1" +rayon = "1.11.0" +thiserror = "2.0.17" [dev-dependencies] -criterion = "0.5.1" -rstest = "0.25" -tempfile = "3" +criterion = "0.8.1" +rstest = "0.26.1" +tempfile = "3.23.0" [features] default = ["bin"] diff --git a/README.md b/README.md index e8a3a79..8efedbd 100644 --- a/README.md +++ b/README.md @@ -13,78 +13,282 @@ [discord-url]: https://discord.gg/NtAbbGn [ci-badge]: https://github.com/nushell/nufmt/actions/workflows/main.yml/badge.svg [ci-url]: https://github.com/nushell/nufmt/actions/workflows/main.yml -[nushell-badge]: https://img.shields.io/badge/nushell-v0.104.0-green +[nushell-badge]: https://img.shields.io/badge/nushell-v0.109.1-green [nushell-url]: https://crates.io/crates/nu ## Table of contents -- [Status](#status) +- [Features](#features) +- [Installation](#installation) - [Usage](#usage) - [Files](#files) - [Options](#options) + - [Configuration](#configuration) +- [Supported Constructs](#supported-constructs) +- [Testing](#testing) + - [Ground Truth Tests](#ground-truth-tests) + - [Running Tests](#running-tests) - [Contributing](#contributing) -## Status +## Features -> [!IMPORTANT] -> `nufmt` is in a pre-alpha state, not suitable for usage at all. -> Do not use in productive nushell scripts! +`nufmt` is a formatter for Nushell scripts, built entirely on Nushell's own parsing infrastructure (`nu-parser`, `nu-protocol`). It provides: -Some of the outputs deletes comments, break the functionality of the script or doesn't format at all. - -To use the formatter, test it first and use it with caution!. +- **AST-based formatting**: Uses Nushell's actual parser for accurate code understanding +- **Idempotent output**: Running the formatter twice produces the same result +- **Comment preservation**: Comments are preserved in their original positions +- **Configurable**: Supports configuration via NUON files +- **Fast**: Parallel file processing with Rayon ## Installation -- `cargo install --git https://github.com/nushell/nufmt` -- Using our bundled Nix flake. +### From source -## Usage +```bash +cargo install --git https://github.com/nushell/nufmt +``` + +### Using Cargo + +```bash +cargo install nufmt +``` + +### Using Nix + +```bash +nix run github:nushell/nufmt +``` -If you still want to use it, or test it to contribute, this is the `--help`. +## Usage ```text -nufmt [OPTIONS] [FILES] ... +nufmt [OPTIONS] [FILES]... ``` ### Files -`Files` are a list of files. It cannot be used combined with `--stdin`. -You can format many files with one command!. For example: +Format one or more Nushell files: -```text -nufmt my-file1.nu my-file2.nu my-file3.nu +```bash +# Format a single file +nufmt script.nu + +# Format multiple files +nufmt file1.nu file2.nu file3.nu + +# Format all .nu files in a directory +nufmt src/ ``` ### Options -- `--dry-run` if you just want to check files without formatting them. It cannot be combined with stdin. -- `-s` or `--stdin` formats from `stdin`, returns to `stdout` as a String. It cannot be used combined with `files`. -- `-c` or `--config` pass the config file path. - Sample: +| Option | Short | Description | +|--------|-------|-------------| +| `--dry-run` | | Check files without modifying them. Returns exit code 1 if files would be reformatted. | +| `--stdin` | | Read from stdin and write formatted output to stdout. Cannot be combined with file arguments. | +| `--config` | `-c` | Path to a configuration file (NUON format). | +| `--help` | `-h` | Show help and exit. | +| `--version` | `-v` | Print version and exit. | + +### Examples - ```text - nufmt --config my-config.json - ``` +```bash +# Format files in place +nufmt *.nu + +# Check if files need formatting (CI mode) +nufmt --dry-run src/ + +# Format stdin +echo 'let x=1' | nufmt --stdin + +# Use custom config +nufmt --config nufmt.nuon src/ +``` - or +### Configuration - ```text - nufmt --stdin --config my-stdin-config.json - ``` -- `-h` or `--help` show help and exit -- `-v` or `--version` prints the version and exit +Create a `nufmt.nuon` file in your project root: + +```nuon +{ + indent: 4 + line_length: 80 + margin: 1 + exclude: ["vendor/**", "target/**"] +} +``` + +Configuration options: + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `indent` | int | 4 | Number of spaces per indentation level | +| `line_length` | int | 80 | Maximum line length (advisory) | +| `margin` | int | 1 | Number of blank lines between top-level items | +| `exclude` | list\ | [] | Glob patterns for files to exclude | ### Exit codes -``nufmt`` exits with the following status codes: -- **0**: if ``nufmt`` terminates successfully, regardless of whether files or stdin were formatted. -- **1** (only used in check mode): ``nufmt`` terminates successfully and at least one file would be formatted if check mode was off. -- **2**: ``nufmt`` terminates abnormally due to invalid configuration, invalid CLI options, or an internal error. +| Code | Description | +|------|-------------| +| 0 | Success (files formatted or already formatted) | +| 1 | Dry-run mode: at least one file would be reformatted | +| 2 | Error: invalid configuration, CLI options, or parse error | + +## Supported Constructs + +`nufmt` properly formats the following Nushell constructs: + +- ✅ Variable declarations (`let`, `mut`, `const`) +- ✅ Function definitions (`def`, `def-env`, `export def`) +- ✅ Control flow (`if`/`else`, `match`, `for`, `while`, `loop`) +- ✅ Pipelines with proper spacing around `|` +- ✅ Lists and records +- ✅ Closures with parameters (`{|x| ... }`) +- ✅ String interpolation (`$"Hello ($name)"`) +- ✅ Modules (`module`, `use`, `export`) +- ✅ Error handling (`try`/`catch`) +- ✅ Comments (preserved in output) +- ✅ Ranges (`1..10`, `1..2..10`) +- ✅ Binary operations with proper spacing + +## How It Works + +Unlike tree-sitter based formatters, `nufmt` uses Nushell's own `nu-parser` crate to parse scripts into an AST. This ensures: + +1. **Accuracy**: The same parser that runs your scripts formats them +2. **Compatibility**: Always in sync with Nushell's syntax +3. **Error detection**: Invalid syntax is detected before formatting + +The formatter walks the AST and emits properly formatted code with consistent: +- Indentation (configurable) +- Spacing around operators and keywords +- Brace placement for blocks +- Comment placement + +## Testing + +`nufmt` has a comprehensive ground truth testing system that verifies the formatter produces correct output for all supported Nushell constructs. + +### Ground Truth Tests + +The testing system uses two sets of fixture files: + +- **Input files** (`tests/fixtures/input/`): Contain valid Nushell code with intentional formatting issues (extra spaces, inconsistent indentation, etc.) +- **Expected files** (`tests/fixtures/expected/`): Contain the correctly formatted version of each input file + +When tests run, the formatter processes each input file and compares the output against the corresponding expected file. This ensures: +1. The formatter produces consistent, correct output +2. Formatting changes are intentional and reviewed +3. Regressions are caught immediately + +**Important**: Input files should always differ from expected files. Input files represent "what not to do" - poorly formatted but valid Nushell code that the formatter should fix. + +### Test Categories + +Tests are organized by Nushell construct category: + +| Category | Constructs | +|----------|------------| +| `core` | `let_statement`, `mut_statement`, `const_statement`, `def_statement` | +| `control_flow` | `if_else`, `for_loop`, `while_loop`, `loop_statement`, `match_expr`, `try_catch`, `break_continue`, `return_statement` | +| `data_structures` | `list`, `record`, `table`, `nested_structures` | +| `pipelines_expressions` | `pipeline`, `multiline_pipeline`, `closure`, `subexpression`, `binary_ops`, `range`, `cell_path`, `spread` | +| `strings_interpolation` | `string_interpolation`, `comment` | +| `types_values` | `value_with_unit`, `datetime`, `nothing`, `glob_pattern` | +| `modules_imports` | `module`, `use_statement`, `export`, `source`, `hide`, `overlay` | +| `commands_definitions` | `alias`, `extern`, `external_call` | +| `special_constructs` | `do_block`, `where_clause`, `error_make` | + +### Running Tests + +#### Rust Tests + +```bash +# Run all tests (unit + ground truth + idempotency) +cargo test + +# Run only ground truth tests +cargo test --test ground_truth + +# Run with verbose output +cargo test -- --nocapture +``` + +#### Nushell Test Runner + +A Nushell script (`tests/run_ground_truth_tests.nu`) provides a more interactive testing experience: + +```bash +# Build the release binary first +cargo build --release + +# Run all tests +nu tests/run_ground_truth_tests.nu + +# Run with verbose output (show diffs on failure) +nu tests/run_ground_truth_tests.nu --verbose + +# Run only ground truth tests (skip idempotency) +nu tests/run_ground_truth_tests.nu --ground-truth + +# Run only idempotency tests +nu tests/run_ground_truth_tests.nu --idempotency + +# Run tests for a specific category +nu tests/run_ground_truth_tests.nu --category control_flow + +# Run a single test +nu tests/run_ground_truth_tests.nu --test let_statement + +# List all available tests +nu tests/run_ground_truth_tests.nu --list + +# List available categories +nu tests/run_ground_truth_tests.nu --list-categories + +# Check which test files exist +nu tests/run_ground_truth_tests.nu --check-files +``` + +### Adding New Tests + +To add a test for a new construct: + +1. Create an input file at `tests/fixtures/input/.nu` with poorly-formatted but valid Nushell code +2. Create an expected file at `tests/fixtures/expected/.nu` with the correctly formatted version +3. Add the construct name to the appropriate category in `tests/run_ground_truth_tests.nu` +4. Run the tests to verify + +Example input file (`tests/fixtures/input/my_construct.nu`): +```nu +let x = 1 +let y = 2 +``` + +Example expected file (`tests/fixtures/expected/my_construct.nu`): +```nu +let x = 1 +let y = 2 +``` ## Contributing -We have a [contribution guide](docs/CONTRIBUTING.md). If you still have doubts, you can mention @`AucaCoyan` who is active on this repo. +Contributions are welcome! Please see our [contribution guide](docs/CONTRIBUTING.md). + +### Reporting issues + +If you encounter formatting issues, please: + +1. Check if the script is valid Nushell syntax +2. Provide a minimal reproduction case +3. Include your `nufmt` version and Nushell version + +## License + +MIT License - see [LICENSE](LICENSE) for details. diff --git a/benches/example.nu b/benches/example.nu index d53c23b..a34a61d 100644 --- a/benches/example.nu +++ b/benches/example.nu @@ -1,888 +1,571 @@ -alias ll = ls -l - - -[[status]; [UP] [UP]] | all {|el| $el.status == UP } -[foo bar 2 baz] | all {|| ($in | describe) == 'string' } -[0 2 4 6] | enumerate | all {|i| $i.item == $i.index * 2 } -let cond = {|el| ($el mod 2) == 0 }; [2 4 6 8] | all $cond - - -ansi green -ansi reset -$'(ansi red_bold)Hello(ansi reset) (ansi green_dimmed)Nu(ansi reset) (ansi purple_italic)World(ansi reset)' -$'(ansi rb)Hello(ansi reset) (ansi gd)Nu(ansi reset) (ansi pi)World(ansi reset)' -$"(ansi -e '3;93;41m')Hello(ansi reset)" # italic bright yellow on red background -let bold_blue_on_red = { # `fg`, `bg`, `attr` are the acceptable keys, all other keys are considered invalid and will throw errors. - fg: '#0000ff' - bg: '#ff0000' - attr: b - } - $"(ansi -e $bold_blue_on_red)Hello Nu World(ansi reset)" - - -'Hello, Nushell! This is a gradient.' | ansi gradient --fgstart '0x40c9ff' --fgend '0xe81cff' -'Hello, Nushell! This is a gradient.' | ansi gradient --fgstart '0x40c9ff' --fgend '0xe81cff' --bgstart '0xe81cff' --bgend '0x40c9ff' -'Hello, Nushell! This is a gradient.' | ansi gradient --fgstart '0x40c9ff' -'Hello, Nushell! This is a gradient.' | ansi gradient --fgend '0xe81cff' - - -'file:///file.txt' | ansi link --text 'Open Me!' -'https://www.nushell.sh/' | ansi link -[[url text]; [https://example.com Text]] | ansi link url - - -$'(ansi green)(ansi cursor_on)hello' | ansi strip - - -[[status]; [UP] [DOWN] [UP]] | any {|el| $el.status == DOWN } -[1 2 3 4] | any {|| ($in | describe) == 'string' } -[9 8 7 6] | enumerate | any {|i| $i.item == $i.index * 2 } -let cond = {|e| $e mod 2 == 1 }; [2 4 1 6 8] | any $cond - - -[0,1,2,3] | append 4 -[0,1] | append [2,3,4] -[0,1] | append [2,nu,4,shell] - - -ast 'hello' -ast 'ls | where name =~ README' -ast 'for x in 1..10 { echo $x ' - - - - -2 | bits and 2 -[4 3 2] | bits and 2 - - -[4 3 2] | bits not -[4 3 2] | bits not -n '2' -[4 3 2] | bits not -s - - -2 | bits or 6 -[8 3 2] | bits or 2 - - -17 | bits rol 2 -[5 3 2] | bits rol 2 - - -17 | bits ror 60 -[15 33 92] | bits ror 2 -n '1' - - -2 | bits shl 7 -2 | bits shl 7 -n '1' -0x7F | bits shl 1 -s -[5 3 2] | bits shl 2 - - -8 | bits shr 2 -[15 35 2] | bits shr 2 - - -2 | bits xor 2 -[8 3 2] | bits xor 2 - - -loop { break } - - - - -0x[1F FF AA AA] | bytes add 0x[AA] -0x[1F FF AA AA] | bytes add 0x[AA BB] -i 1 -0x[FF AA AA] | bytes add 0x[11] -e -0x[FF AA BB] | bytes add 0x[11 22 33] -e -i 1 - - - 0x[33 44 55 10 01 13] | bytes at 3..<4 - 0x[33 44 55 10 01 13] | bytes at 3..6 - 0x[33 44 55 10 01 13] | bytes at 3.. - 0x[33 44 55 10 01 13] | bytes at ..<4 - [[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes at 1.. ColB ColC - - -bytes build 0x[01 02] 0x[03] 0x[04] - - -[0x[11] 0x[13 15]] | bytes collect -[0x[11] 0x[33] 0x[44]] | bytes collect 0x[01] - - -0x[1F FF AA AA] | bytes ends-with 0x[AA] -0x[1F FF AA AA] | bytes ends-with 0x[FF AA AA] -0x[1F FF AA AA] | bytes ends-with 0x[11] - - - 0x[33 44 55 10 01 13 44 55] | bytes index-of 0x[44 55] - 0x[33 44 55 10 01 13 44 55] | bytes index-of -e 0x[44 55] - 0x[33 44 55 10 01 33 44 33 44] | bytes index-of -a 0x[33 44] - 0x[33 44 55 10 01 33 44 33 44] | bytes index-of -a -e 0x[33 44] - [[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes index-of 0x[11] ColA ColC - - -0x[1F FF AA AB] | bytes length -[0x[1F FF AA AB] 0x[1F]] | bytes length - - -0x[10 AA FF AA FF] | bytes remove 0x[10 AA] -0x[10 AA 10 BB 10] | bytes remove -a 0x[10] -0x[10 AA 10 BB CC AA 10] | bytes remove -e 0x[10] -[[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes remove 0x[11] ColA ColC - - -0x[10 AA FF AA FF] | bytes replace 0x[10 AA] 0x[FF] -0x[10 AA 10 BB 10] | bytes replace -a 0x[10] 0x[A0] -[[ColA ColB ColC]; [0x[11 12 13] 0x[14 15 16] 0x[17 18 19]]] | bytes replace -a 0x[11] 0x[13] ColA ColC - - -0x[1F FF AA AA] | bytes reverse -0x[FF AA AA] | bytes reverse - - -0x[1F FF AA AA] | bytes starts-with 0x[1F FF AA] -0x[1F FF AA AA] | bytes starts-with 0x[1F] -0x[1F FF AA AA] | bytes starts-with 0x[11] - - -cal -cal --full-year 2012 -cal --week-start monday - - -cd ~ -cd d/s/9 -cd - - - -char newline -char --list -(char prompt) + (char newline) + (char hamburger) -char -u 1f378 -char -i (0x60 + 1) (0x60 + 2) -char -u 1F468 200D 1F466 200D 1F466 - - -clear - - -[1 2 3] | collect { |x| $x.1 } - - -{ acronym:PWD, meaning:'Print Working Directory' } | columns -[[name,age,grade]; [bill,20,a]] | columns -[[name,age,grade]; [bill,20,a]] | columns | first -[[name,age,grade]; [bill,20,a]] | columns | select 1 - - - - -[["Hello" "World"]; [null 3]] | compact Hello -[["Hello" "World"]; [null 3]] | compact World -[1, null, 2] | compact - - -^external arg1 | complete -do { ^external arg1 } | complete - - - - -config env - - -config nu - - -config reset - - -const x = 10 -const x = { a: 10, b: 20 } - - -for i in 1..10 { if $i == 5 { continue }; print $i } - - -cp myfile dir_b -cp -r dir_a dir_b -cp -r -v dir_a dir_b -cp *.txt dir_a - - - - -"2021-10-22 20:00:12 +01:00" | date format -date now | date format "%Y-%m-%d %H:%M:%S" -date now | date format "%Y-%m-%d %H:%M:%S" -"2021-10-22 20:00:12 +01:00" | date format "%Y-%m-%d" - - -"2021-10-22 20:00:12 +01:00" | date humanize - - -date list-timezone | where timezone =~ Shanghai - - -date now | date format "%Y-%m-%d %H:%M:%S" -(date now) - 2019-05-01 -(date now) - 2019-05-01T04:12:05.20+08:00 -date now | debug - - -date to-record -date now | date to-record -'2020-04-12T22:10:57.123+02:00' | date to-record - - -date to-table -date now | date to-table -2020-04-12T22:10:57.000000789+02:00 | date to-table - - -date now | date to-timezone '+0500' -date now | date to-timezone local -date now | date to-timezone US/Hawaii -"2020-10-10 10:00:00 +02:00" | date to-timezone "+0500" - - -'hello' | debug -['hello'] | debug -[[version patch]; ['0.1.0' false] ['0.1.1' true] ['0.2.0' false]] | debug - - -^cat myfile.q | decode utf-8 -0x[00 53 00 6F 00 6D 00 65 00 20 00 44 00 61 00 74 00 61] | decode utf-16be - - -'U29tZSBEYXRh' | decode base64 -'U29tZSBEYXRh' | decode base64 --binary - - -'0102030A0a0B' | decode hex -'01 02 03 0A 0a 0B' | decode hex - - -def say-hi [] { echo 'hi' }; say-hi -def say-sth [sth: string] { echo $sth }; say-sth hi - - -def-env foo [] { let-env BAR = "BAZ" }; foo; $env.BAR - - -ls -la | default 'nothing' target -$env | get -i MY_ENV | default 'abc' -[1, 2, null, 4] | default 3 - - -'hello' | describe - - -'a b c' | detect columns -n -$'c1 c2 c3(char nl)a b c' | detect columns - - -[[a b]; [1 2] [1 4] [2 6] [2 4]] - | dfr into-df - | dfr group-by a - | dfr agg [ - (dfr col b | dfr min | dfr as "b_min") - (dfr col b | dfr max | dfr as "b_max") - (dfr col b | dfr sum | dfr as "b_sum") - ] -[[a b]; [1 2] [1 4] [2 6] [2 4]] - | dfr into-lazy - | dfr group-by a - | dfr agg [ - (dfr col b | dfr min | dfr as "b_min") - (dfr col b | dfr max | dfr as "b_max") - (dfr col b | dfr sum | dfr as "b_sum") - ] - | dfr collect - - - - - -[false false false] | dfr into-df | dfr all-false -let s = ([5 6 2 10] | dfr into-df); - let res = ($s > 9); - $res | dfr all-false - - -[true true true] | dfr into-df | dfr all-true -let s = ([5 6 2 8] | dfr into-df); - let res = ($s > 9); - $res | dfr all-true - - -let a = ([[a b]; [1 2] [3 4]] | dfr into-df); - $a | dfr append $a -let a = ([[a b]; [1 2] [3 4]] | dfr into-df); - $a | dfr append $a --col - - -[1 3 2] | dfr into-df | dfr arg-max - - -[1 3 2] | dfr into-df | dfr arg-min - - -[1 2 2 3 3] | dfr into-df | dfr arg-sort -[1 2 2 3 3] | dfr into-df | dfr arg-sort -r - - -[false true false] | dfr into-df | dfr arg-true - - -[1 2 2 3 3] | dfr into-df | dfr arg-unique - - -let df = ([[a b]; [one 1] [two 2] [three 3]] | dfr into-df); - $df | dfr select (dfr arg-where ((dfr col b) >= 2) | dfr as b_arg) - - -dfr col a | dfr as new_a | dfr into-nu - - -["2021-12-30" "2021-12-31"] | dfr into-df | dfr as-datetime "%Y-%m-%d" - - -["2021-12-30 00:00:00" "2021-12-31 00:00:00"] | dfr into-df | dfr as-datetime "%Y-%m-%d %H:%M:%S" - - -[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr reverse | dfr cache - - -dfr col a | dfr into-nu - - -[[a b]; [1 2] [3 4]] | dfr into-lazy | dfr collect - - -[[a b]; [1 2] [3 4]] | dfr into-df | dfr columns - - -let df = ([[a b c]; [one two 1] [three four 2]] | dfr into-df); - $df | dfr with-column ((dfr concat-str "-" [(dfr col a) (dfr col b) ((dfr col c) * 2)]) | dfr as concat) - - -let other = ([za xs cd] | dfr into-df); - [abc abc abc] | dfr into-df | dfr concatenate $other - - -[abc acb acb] | dfr into-df | dfr contains ab - - - - - -let s = ([1 1 0 0 3 3 4] | dfr into-df); - ($s / $s) | dfr count-null - - -[1 2 3 4 5] | dfr into-df | dfr cumulative sum - - -[[a b]; [1 2] [3 4]] | dfr into-df | dfr drop a - - -[[a b]; [1 2] [3 4] [1 2]] | dfr into-df | dfr drop-duplicates - - -let df = ([[a b]; [1 2] [3 0] [1 2]] | dfr into-df); - let res = ($df.b / $df.b); - let a = ($df | dfr with-column $res --name res); - $a | dfr drop-nulls -let s = ([1 2 0 0 3 4] | dfr into-df); - ($s / $s) | dfr drop-nulls - - -[[a b]; [1 2] [3 4]] | dfr into-df | dfr dtypes - - -[[a b]; [1 2] [3 4]] | dfr into-df | dfr dummies -[1 2 2 3 3] | dfr into-df | dfr dummies - - - - - -(dfr col a) > 2) | dfr expr-not - - -[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr fetch 2 - - -[1 2 NaN 3 NaN] | dfr into-df | dfr fill-nan 0 -[[a b]; [0.2 1] [0.1 NaN]] | dfr into-df | dfr fill-nan 0 - - -[1 2 2 3 3] | dfr into-df | dfr shift 2 | dfr fill-null 0 - - -[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr filter ((dfr col a) >= 4) - - -let mask = ([true false] | dfr into-df); - [[a b]; [1 2] [3 4]] | dfr into-df | dfr filter-with $mask -[[a b]; [1 2] [3 4]] | dfr into-df | dfr filter-with ((dfr col a) > 1) - - -dfr col a | dfr first - - -[[a b]; [1 2] [3 4]] | dfr into-df | dfr first -[[a b]; [1 2] [3 4]] | dfr into-df | dfr first 2 - - - - - -[[a b]; [1 2] [3 4]] | dfr into-df | dfr get a - - -let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC'); - let df = ([$dt $dt] | dfr into-df); +( + ( + ls -l [ + [status] + ] | all {|el| $el.status == UP } [foo, bar, 2, baz] | all {|| ($in | describe) == 'string' } [0, 2, 4, 6] | enumerate | all {|i| $i.item == $i.index * 2 } let cond = {|el| ($el mod 2) == 0 } + [2, 4, 6, 8] | all $cond + ansi green + ansi reset + $'(ansi red_bold)Hello(ansi reset) (ansi green_dimmed)Nu(ansi reset) (ansi purple_italic)World(ansi reset)' + $'(ansi rb)Hello(ansi reset) (ansi gd)Nu(ansi reset) (ansi pi)World(ansi reset)' + $"(ansi -e '3;93;41m')Hello(ansi reset)" # italic bright yellow on red background + let bold_blue_on_red = {fg: '#0000ff', bg: '#ff0000', attr: b} + $"(ansi -e $bold_blue_on_red)Hello Nu World(ansi reset)" + 'Hello, Nushell! This is a gradient.' | ansi gradient --fgstart '0x40c9ff' --fgend '0xe81cff' + 'Hello, Nushell! This is a gradient.' | ansi gradient --fgstart '0x40c9ff' --fgend '0xe81cff' --bgstart '0xe81cff' --bgend '0x40c9ff' + 'Hello, Nushell! This is a gradient.' | ansi gradient --fgstart '0x40c9ff' + 'Hello, Nushell! This is a gradient.' | ansi gradient --fgend '0xe81cff' + 'file:///file.txt' | ansi link --text 'Open Me!' + 'https://www.nushell.sh/' | ansi link + [[url, text]; [https://example.com, Text]] | ansi link url + $'(ansi green)(ansi cursor_on)hello' | ansi strip + [[status]; [UP], [DOWN], [UP]] | any {|el| $el.status == DOWN } + [1, 2, 3, 4] | any {|| ($in | describe) == 'string' } + [9, 8, 7, 6] | enumerate | any {|i| $i.item == $i.index * 2 } + let cond = {|e| $e mod 2 == 1 } + [2, 4, 1, 6, 8] | any $cond + [0, 1, 2, 3] | append 4 + [0, 1] | append [2, 3, 4] + [0, 1] | append [2, nu, 4, shell] + ast 'hello' + ast 'ls | where name =~ README' + ast 'for x in 1..10 { echo $x ' + 2 | bits and 2 + [4, 3, 2] | bits and 2 + [4, 3, 2] | bits not + [4, 3, 2] | bits not -n '2' + [4, 3, 2] | bits not -s + 2 | bits or 6 + [8, 3, 2] | bits or 2 + 17 | bits rol 2 + [5, 3, 2] | bits rol 2 + 17 | bits ror 60 + [15, 33, 92] | bits ror 2 -n '1' + 2 | bits shl 7 + 2 | bits shl 7 -n '1' + 0x7F | bits shl 1 -s + [5, 3, 2] | bits shl 2 + 8 | bits shr 2 + [15, 35, 2] | bits shr 2 + 2 | bits xor 2 + [8, 3, 2] | bits xor 2 + loop { break } + 0x[1F FF AA AA] | bytes add 0x[AA] + 0x[1F FF AA AA] | bytes add 0x[AA BB] -i 1 + 0x[FF AA AA] | bytes add 0x[11] -e + 0x[FF AA BB] | bytes add 0x[11 22 33] -e -i 1 + 0x[33 44 55 10 01 13] | bytes at 3..<4 + 0x[33 44 55 10 01 13] | bytes at 3..6 + 0x[33 44 55 10 01 13] | bytes at 3.. + 0x[33 44 55 10 01 13] | bytes at ..<4 + [[ColA, ColB, ColC]; [0x[11 12 13], 0x[14 15 16], 0x[17 18 19]]] | bytes at 1.. ColB ColC + bytes build 0x[01 02] 0x[03] 0x[04] + [ + 0x[11] + 0x[13 15] + ] | bytes collect + [ + 0x[11] + 0x[33] + 0x[44] + ] | bytes collect 0x[01] + 0x[1F FF AA AA] | bytes ends-with 0x[AA] + 0x[1F FF AA AA] | bytes ends-with 0x[FF AA AA] + 0x[1F FF AA AA] | bytes ends-with 0x[11] + 0x[33 44 55 10 01 13 44 55] | bytes index-of 0x[44 55] + 0x[33 44 55 10 01 13 44 55] | bytes index-of -e 0x[44 55] + 0x[33 44 55 10 01 33 44 33 44] | bytes index-of -a 0x[33 44] + 0x[33 44 55 10 01 33 44 33 44] | bytes index-of -a -e 0x[33 44] + [[ColA, ColB, ColC]; [0x[11 12 13], 0x[14 15 16], 0x[17 18 19]]] | bytes index-of 0x[11] ColA ColC + 0x[1F FF AA AB] | bytes length + [ + 0x[1F FF AA AB] + 0x[1F] + ] | bytes length + 0x[10 AA FF AA FF] | bytes remove 0x[10 AA] + 0x[10 AA 10 BB 10] | bytes remove -a 0x[10] + 0x[10 AA 10 BB CC AA 10] | bytes remove -e 0x[10] + [[ColA, ColB, ColC]; [0x[11 12 13], 0x[14 15 16], 0x[17 18 19]]] | bytes remove 0x[11] ColA ColC + 0x[10 AA FF AA FF] | bytes replace 0x[10 AA] 0x[FF] + 0x[10 AA 10 BB 10] | bytes replace -a 0x[10] 0x[A0] + [[ColA, ColB, ColC]; [0x[11 12 13], 0x[14 15 16], 0x[17 18 19]]] | bytes replace -a 0x[11] 0x[13] ColA ColC + 0x[1F FF AA AA] | bytes reverse + 0x[FF AA AA] | bytes reverse + 0x[1F FF AA AA] | bytes starts-with 0x[1F FF AA] + 0x[1F FF AA AA] | bytes starts-with 0x[1F] + 0x[1F FF AA AA] | bytes starts-with 0x[11] + cal + cal --full-year 2012 + cal --week-start monday + cd ~ + cd d/s/9 + cd - + char newline + char --list + (char prompt) + (char newline) + (char hamburger) + char -u 1f378 + char -i (0x60 + 1) (0x60 + 2) + char -u 1F468 200D 1F466 200D 1F466 + clear + [1, 2, 3] | collect {|| + x | $x.1 + } + {acronym: PWD, meaning: 'Print Working Directory'} | columns + [[name, age, grade]; [bill, 20, a]] | columns + [[name, age, grade]; [bill, 20, a]] | columns | first + [[name, age, grade]; [bill, 20, a]] | columns | select 1 + [["Hello", "World"]; [null, 3]] | compact Hello + [["Hello", "World"]; [null, 3]] | compact World + [1, null, 2] | compact + external arg1 | complete + do { external arg1 } | complete + config env + config nu + config reset + const x = (10) + const x = ({a: 10, b: 20}) + for i in 1..10 { + if $i == 5 { continue } + print $i + } + cp myfile dir_b + cp -r dir_a dir_b + cp -r -v dir_a dir_b + cp *.txt dir_a + "2021-10-22 20:00:12 +01:00" | date format + date now | date format "%Y-%m-%d %H:%M:%S" + date now | date format "%Y-%m-%d %H:%M:%S" + "2021-10-22 20:00:12 +01:00" | date format "%Y-%m-%d" + "2021-10-22 20:00:12 +01:00" | date humanize + date list-timezone | where timezone =~ Shanghai + date now | date format "%Y-%m-%d %H:%M:%S" + (date now) - 2019-05-01 + (date now) - 2019-05-01T04:12:05.20+08:00 + date now | debug + date to-record + date now | date to-record + '2020-04-12T22:10:57.123+02:00' | date to-record + date to-table + date now | date to-table + 2020-04-12T22:10:57.000000789+02:00 | date to-table + date now | date to-timezone '+0500' + date now | date to-timezone local + date now | date to-timezone US/Hawaii + "2020-10-10 10:00:00 +02:00" | date to-timezone "+0500" + 'hello' | debug + ['hello'] | debug + [[version, patch]; ['0.1.0', false], ['0.1.1', true], ['0.2.0', false]] | debug + cat myfile.q | decode utf-8 + 0x[00 53 00 6F 00 6D 00 65 00 20 00 44 00 61 00 74 00 61] | decode utf-16be + 'U29tZSBEYXRh' | decode base64 + 'U29tZSBEYXRh' | decode base64 --binary + '0102030A0a0B' | decode hex + '01 02 03 0A 0a 0B' | decode hex + def say-hi [] { echo 'hi' } + say-hi + def say-sth [sth: string] { echo $sth } + say-sth hi + def-env foo [] { let-env BAR = "BAZ" } + foo + $env.BAR + ls -la | default 'nothing' target + $env | get -i MY_ENV | default 'abc' + [1, 2, null, 4] | default 3 + 'hello' | describe + 'a b c' | detect columns -n + $'c1 c2 c3(char nl)a b c' | detect columns + [[a, b]; [1, 2], [1, 4], [2, 6], [2, 4]] | dfr into-df | dfr group-by a | dfr agg [ + (dfr col b | dfr min | dfr as "b_min") + (dfr col b | dfr max | dfr as "b_max") + (dfr col b | dfr sum | dfr as "b_sum") + ] + [[a, b]; [1, 2], [1, 4], [2, 6], [2, 4]] | dfr into-lazy | dfr group-by a | dfr agg [ + (dfr col b | dfr min | dfr as "b_min") + (dfr col b | dfr max | dfr as "b_max") + (dfr col b | dfr sum | dfr as "b_sum") + ] | dfr collect + [false, false, false] | dfr into-df | dfr all-false + let s = ([5, 6, 2, 10] | dfr into-df) + let res = ($s > 9) + $res | dfr all-false + [true, true, true] | dfr into-df | dfr all-true + let s = ([5, 6, 2, 8] | dfr into-df) + let res = ($s > 9) + $res | dfr all-true + let a = ([[a, b]; [1, 2], [3, 4]] | dfr into-df) + $a | dfr append $a + let a = ([[a, b]; [1, 2], [3, 4]] | dfr into-df) + $a | dfr append $a --col + [1, 3, 2] | dfr into-df | dfr arg-max + [1, 3, 2] | dfr into-df | dfr arg-min + [1, 2, 2, 3, 3] | dfr into-df | dfr arg-sort + [1, 2, 2, 3, 3] | dfr into-df | dfr arg-sort -r + [false, true, false] | dfr into-df | dfr arg-true + [1, 2, 2, 3, 3] | dfr into-df | dfr arg-unique + let df = ([[a, b]; [one, 1], [two, 2], [three, 3]] | dfr into-df) + $df | dfr select (dfr arg-where ((dfr col b) >= 2) | dfr as b_arg) + dfr col a | dfr as new_a | dfr into-nu + ["2021-12-30", "2021-12-31"] | dfr into-df | dfr as-datetime "%Y-%m-%d" + ["2021-12-30 00:00:00", "2021-12-31 00:00:00"] | dfr into-df | dfr as-datetime "%Y-%m-%d %H:%M:%S" + [[a, b]; [6, 2], [4, 2], [2, 2]] | dfr into-df | dfr reverse | dfr cache + dfr col a | dfr into-nu + [[a, b]; [1, 2], [3, 4]] | dfr into-lazy | dfr collect + [[a, b]; [1, 2], [3, 4]] | dfr into-df | dfr columns + let df = ([[a, b, c]; [one, two, 1], [three, four, 2]] | dfr into-df) + $df | dfr with-column ((dfr concat-str "-" [ + (dfr col a) + (dfr col b) + ((dfr col c) * 2) + ]) | dfr as concat) + let other = ([za, xs, cd] | dfr into-df) + [abc, abc, abc] | dfr into-df | dfr concatenate $other + [abc, acb, acb] | dfr into-df | dfr contains ab + let s = ([ + 1 + 1 + 0 + 0 + 3 + 3 + 4 + ] | dfr into-df) + ($s / $s) | dfr count-null + [1, 2, 3, 4, 5] | dfr into-df | dfr cumulative sum + [[a, b]; [1, 2], [3, 4]] | dfr into-df | dfr drop a + [[a, b]; [1, 2], [3, 4], [1, 2]] | dfr into-df | dfr drop-duplicates + let df = ([[a, b]; [1, 2], [3, 0], [1, 2]] | dfr into-df) + let res = ($df.b / $df.b) + let a = ($df | dfr with-column $res --name res) + $a | dfr drop-nulls + let s = ([ + 1 + 2 + 0 + 0 + 3 + 4 + ] | dfr into-df) + ($s / $s) | dfr drop-nulls + [[a, b]; [1, 2], [3, 4]] | dfr into-df | dfr dtypes + [[a, b]; [1, 2], [3, 4]] | dfr into-df | dfr dummies + [1, 2, 2, 3, 3] | dfr into-df | dfr dummies + (dfr col a) > 2 + ) | dfr expr-not [ + [a, b] + ] | dfr into-df | dfr fetch 2 [1, 2, NaN, 3, NaN] | dfr into-df | dfr fill-nan 0 [ + [a, b] + ] | dfr into-df | dfr fill-nan 0 [1, 2, 2, 3, 3] | dfr into-df | dfr shift 2 | dfr fill-null 0 [ + [a, b] + ] | dfr into-df | dfr filter ((dfr col a) >= 4) let mask = ([true, false] | dfr into-df) + [[a, b]; [1, 2], [3, 4]] | dfr into-df | dfr filter-with $mask + [[a, b]; [1, 2], [3, 4]] | dfr into-df | dfr filter-with ((dfr col a) > 1) + dfr col a | dfr first + [[a, b]; [1, 2], [3, 4]] | dfr into-df | dfr first + [[a, b]; [1, 2], [3, 4]] | dfr into-df | dfr first 2 + [[a, b]; [1, 2], [3, 4]] | dfr into-df | dfr get a + let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC') + let df = ([$dt, $dt] | dfr into-df) $df | dfr get-day - - -let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC'); - let df = ([$dt $dt] | dfr into-df); + let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC') + let df = ([$dt, $dt] | dfr into-df) $df | dfr get-hour - - -let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC'); - let df = ([$dt $dt] | dfr into-df); + let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC') + let df = ([$dt, $dt] | dfr into-df) $df | dfr get-minute - - -let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC'); - let df = ([$dt $dt] | dfr into-df); + let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC') + let df = ([$dt, $dt] | dfr into-df) $df | dfr get-month - - -let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC'); - let df = ([$dt $dt] | dfr into-df); + let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC') + let df = ([$dt, $dt] | dfr into-df) $df | dfr get-nanosecond - - -let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC'); - let df = ([$dt $dt] | dfr into-df); + let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC') + let df = ([$dt, $dt] | dfr into-df) $df | dfr get-ordinal - - -let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC'); - let df = ([$dt $dt] | dfr into-df); + let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC') + let df = ([$dt, $dt] | dfr into-df) $df | dfr get-second - - -let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC'); - let df = ([$dt $dt] | dfr into-df); + let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC') + let df = ([$dt, $dt] | dfr into-df) $df | dfr get-week - - -let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC'); - let df = ([$dt $dt] | dfr into-df); + let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC') + let df = ([$dt, $dt] | dfr into-df) $df | dfr get-weekday - - -let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC'); - let df = ([$dt $dt] | dfr into-df); + let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC') + let df = ([$dt, $dt] | dfr into-df) $df | dfr get-year - - -[[a b]; [1 2] [1 4] [2 6] [2 4]] - | dfr into-df - | dfr group-by a - | dfr agg [ + [[a, b]; [1, 2], [1, 4], [2, 6], [2, 4]] | dfr into-df | dfr group-by a | dfr agg [ (dfr col b | dfr min | dfr as "b_min") (dfr col b | dfr max | dfr as "b_max") (dfr col b | dfr sum | dfr as "b_sum") - ] -[[a b]; [1 2] [1 4] [2 6] [2 4]] - | dfr into-lazy - | dfr group-by a - | dfr agg [ + ] + [[a, b]; [1, 2], [1, 4], [2, 6], [2, 4]] | dfr into-lazy | dfr group-by a | dfr agg [ (dfr col b | dfr min | dfr as "b_min") (dfr col b | dfr max | dfr as "b_max") (dfr col b | dfr sum | dfr as "b_sum") - ] - | dfr collect - - -[[a b];[1 2] [3 4]] | dfr into-df -[[1 2 a] [3 4 b] [5 6 c]] | dfr into-df -[a b c] | dfr into-df -[true true false] | dfr into-df - - -[[a b];[1 2] [3 4]] | dfr into-lazy - - -dfr col a | dfr into-nu - - -[[a b]; [1 2] [3 4]] | dfr into-df | dfr into-nu -[[a b]; [1 2] [5 6] [3 4]] | dfr into-df | dfr into-nu -t -n 1 - - -[5 6 6 6 8 8 8] | dfr into-df | dfr is-duplicated -[[a, b]; [1 2] [1 2] [3 3] [3 3] [1 1]] | dfr into-df | dfr is-duplicated - - -let other = ([1 3 6] | dfr into-df); - [5 6 6 6 8 8 8] | dfr into-df | dfr is-in $other - - -let df = ([[a b]; [one 1] [two 2] [three 3]] | dfr into-df); - $df | dfr with-column (dfr col a | dfr is-in [one two] | dfr as a_in) - - -dfr col a | dfr is-not-null - - -let s = ([5 6 0 8] | dfr into-df); - let res = ($s / $s); + ] | dfr collect + [[a, b]; [1, 2], [3, 4]] | dfr into-df + [ + [1, 2, a] + [3, 4, b] + [5, 6, c] + ] | dfr into-df + [a, b, c] | dfr into-df + [true, true, false] | dfr into-df + [[a, b]; [1, 2], [3, 4]] | dfr into-lazy + dfr col a | dfr into-nu + [[a, b]; [1, 2], [3, 4]] | dfr into-df | dfr into-nu + [[a, b]; [1, 2], [5, 6], [3, 4]] | dfr into-df | dfr into-nu -t -n 1 + [ + 5 + 6 + 6 + 6 + 8 + 8 + 8 + ] | dfr into-df | dfr is-duplicated + [[a, b]; [1, 2], [1, 2], [3, 3], [3, 3], [1, 1]] | dfr into-df | dfr is-duplicated + let other = ([1, 3, 6] | dfr into-df) + [ + 5 + 6 + 6 + 6 + 8 + 8 + 8 + ] | dfr into-df | dfr is-in $other + let df = ([[a, b]; [one, 1], [two, 2], [three, 3]] | dfr into-df) + $df | dfr with-column (dfr col a | dfr is-in [one, two] | dfr as a_in) + dfr col a | dfr is-not-null + let s = ([5, 6, 0, 8] | dfr into-df) + let res = ($s / $s) $res | dfr is-not-null - - -dfr col a | dfr is-null - - -let s = ([5 6 0 8] | dfr into-df); - let res = ($s / $s); + dfr col a | dfr is-null + let s = ([5, 6, 0, 8] | dfr into-df) + let res = ($s / $s) $res | dfr is-null - - -[5 6 6 6 8 8 8] | dfr into-df | dfr is-unique -[[a, b]; [1 2] [1 2] [3 3] [3 3] [1 1]] | dfr into-df | dfr is-unique - - -let df_a = ([[a b c];[1 "a" 0] [2 "b" 1] [1 "c" 2] [1 "c" 3]] | dfr into-lazy); - let df_b = ([["foo" "bar" "ham"];[1 "a" "let"] [2 "c" "var"] [3 "c" "const"]] | dfr into-lazy); + [ + 5 + 6 + 6 + 6 + 8 + 8 + 8 + ] | dfr into-df | dfr is-unique + [[a, b]; [1, 2], [1, 2], [3, 3], [3, 3], [1, 1]] | dfr into-df | dfr is-unique + let df_a = ([[a, b, c]; [1, "a", 0], [2, "b", 1], [1, "c", 2], [1, "c", 3]] | dfr into-lazy) + let df_b = ([["foo", "bar", "ham"]; [1, "a", "let"], [2, "c", "var"], [3, "c", "const"]] | dfr into-lazy) $df_a | dfr join $df_b a foo | dfr collect -let df_a = ([[a b c];[1 "a" 0] [2 "b" 1] [1 "c" 2] [1 "c" 3]] | dfr into-df); - let df_b = ([["foo" "bar" "ham"];[1 "a" "let"] [2 "c" "var"] [3 "c" "const"]] | dfr into-lazy); + let df_a = ([[a, b, c]; [1, "a", 0], [2, "b", 1], [1, "c", 2], [1, "c", 3]] | dfr into-df) + let df_b = ([["foo", "bar", "ham"]; [1, "a", "let"], [2, "c", "var"], [3, "c", "const"]] | dfr into-lazy) $df_a | dfr join $df_b a foo - - -dfr col a | dfr last - - -[[a b]; [1 2] [3 4]] | dfr into-df | dfr last 1 - - - - - -dfr lit 2 | dfr into-nu - - -[Abc aBc abC] | dfr into-df | dfr lowercase - - -let test = ([[a b];[1 2] [3 4]] | dfr into-df); + dfr col a | dfr last + [[a, b]; [1, 2], [3, 4]] | dfr into-df | dfr last 1 + dfr lit 2 | dfr into-nu + [Abc, aBc, abC] | dfr into-df | dfr lowercase + let test = ([[a, b]; [1, 2], [3, 4]] | dfr into-df) ls - - -[[a b]; [6 2] [1 4] [4 1]] | dfr into-df | dfr max - - -[[a b]; [one 2] [one 4] [two 1]] - | dfr into-df - | dfr group-by a - | dfr agg (dfr col b | dfr max) - - -[[a b]; [one 2] [one 4] [two 1]] - | dfr into-df - | dfr group-by a - | dfr agg (dfr col b | dfr mean) - - -[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr mean - - -[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr median - - -[[a b]; [one 2] [one 4] [two 1]] - | dfr into-df - | dfr group-by a - | dfr agg (dfr col b | dfr median) - - -[[a b c d]; [x 1 4 a] [y 2 5 b] [z 3 6 c]] | dfr into-df | dfr melt -c [b c] -v [a d] - - -[[a b]; [one 2] [one 4] [two 1]] - | dfr into-df - | dfr group-by a - | dfr agg (dfr col b | dfr min) - - -[[a b]; [6 2] [1 4] [4 1]] | dfr into-df | dfr min - - -dfr col a | dfr n-unique - - -[1 1 2 2 3 3 4] | dfr into-df | dfr n-unique - - -[true false true] | dfr into-df | dfr not - - -dfr open test.csv - - -dfr when ((dfr col a) > 2) 4 | dfr otherwise 5 -dfr when ((dfr col a) > 2) 4 | dfr when ((dfr col a) < 0) 6 | dfr otherwise 0 -[[a b]; [6 2] [1 4] [4 1]] - | dfr into-lazy - | dfr with-column ( - dfr when ((dfr col a) > 2) 4 | dfr otherwise 5 | dfr as c - ) - | dfr with-column ( - dfr when ((dfr col a) > 5) 10 | dfr when ((dfr col a) < 2) 6 | dfr otherwise 0 | dfr as d - ) - | dfr collect - - -[[a b]; [one 2] [one 4] [two 1]] - | dfr into-df - | dfr group-by a - | dfr agg (dfr col b | dfr quantile 0.5) - - -[[a b]; [6 2] [1 4] [4 1]] | dfr into-df | dfr quantile 0.5 - - -[[a b]; [1 2] [3 4]] | dfr into-df | dfr query 'select a from df' - - -[5 6 7 8] | dfr into-df | dfr rename '0' new_name -[[a b]; [1 2] [3 4]] | dfr into-df | dfr rename a a_new -[[a b]; [1 2] [3 4]] | dfr into-df | dfr rename [a b] [a_new b_new] - - -[abc abc abc] | dfr into-df | dfr replace -p ab -r AB - - -[abac abac abac] | dfr into-df | dfr replace-all -p a -r A - - -[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr reverse - - -[1 2 3 4 5] | dfr into-df | dfr rolling sum 2 | dfr drop-nulls -[1 2 3 4 5] | dfr into-df | dfr rolling max 2 | dfr drop-nulls - - -[[a b]; [1 2] [3 4]] | dfr into-df | dfr sample -n 1 -[[a b]; [1 2] [3 4] [5 6]] | dfr into-df | dfr sample -f 0.5 -e - - -[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr select a - - -let s = ([1 2 2 3 3] | dfr into-df | dfr shift 2); - let mask = ($s | dfr is-null); + [[a, b]; [6, 2], [1, 4], [4, 1]] | dfr into-df | dfr max + [[a, b]; [one, 2], [one, 4], [two, 1]] | dfr into-df | dfr group-by a | dfr agg (dfr col b | dfr max) + [[a, b]; [one, 2], [one, 4], [two, 1]] | dfr into-df | dfr group-by a | dfr agg (dfr col b | dfr mean) + [[a, b]; [6, 2], [4, 2], [2, 2]] | dfr into-df | dfr mean + [[a, b]; [6, 2], [4, 2], [2, 2]] | dfr into-df | dfr median + [[a, b]; [one, 2], [one, 4], [two, 1]] | dfr into-df | dfr group-by a | dfr agg (dfr col b | dfr median) + [[a, b, c, d]; [x, 1, 4, a], [y, 2, 5, b], [z, 3, 6, c]] | dfr into-df | dfr melt -c [b, c] -v [a, d] + [[a, b]; [one, 2], [one, 4], [two, 1]] | dfr into-df | dfr group-by a | dfr agg (dfr col b | dfr min) + [[a, b]; [6, 2], [1, 4], [4, 1]] | dfr into-df | dfr min + dfr col a | dfr n-unique + [ + 1 + 1 + 2 + 2 + 3 + 3 + 4 + ] | dfr into-df | dfr n-unique + [true, false, true] | dfr into-df | dfr not + dfr open test.csv + dfr when ((dfr col a) > 2) 4 | dfr otherwise 5 + dfr when ((dfr col a) > 2) 4 | dfr when ((dfr col a) < 0) 6 | dfr otherwise 0 + [[a, b]; [6, 2], [1, 4], [4, 1]] | dfr into-lazy | dfr with-column (dfr when ((dfr col a) > 2) 4 | dfr otherwise 5 | dfr as c) | dfr with-column ( + dfr when ((dfr col a) > 5) 10 | dfr when ((dfr col a) < 2) 6 | dfr otherwise 0 | dfr as d + ) | dfr collect + [[a, b]; [one, 2], [one, 4], [two, 1]] | dfr into-df | dfr group-by a | dfr agg (dfr col b | dfr quantile 0.5) + [[a, b]; [6, 2], [1, 4], [4, 1]] | dfr into-df | dfr quantile 0.5 + [[a, b]; [1, 2], [3, 4]] | dfr into-df | dfr query 'select a from df' + [5, 6, 7, 8] | dfr into-df | dfr rename '0' new_name + [[a, b]; [1, 2], [3, 4]] | dfr into-df | dfr rename a a_new + [[a, b]; [1, 2], [3, 4]] | dfr into-df | dfr rename [a, b] [a_new, b_new] + [abc, abc, abc] | dfr into-df | dfr replace -p ab -r AB + [abac, abac, abac] | dfr into-df | dfr replace-all -p a -r A + [[a, b]; [6, 2], [4, 2], [2, 2]] | dfr into-df | dfr reverse + [1, 2, 3, 4, 5] | dfr into-df | dfr rolling sum 2 | dfr drop-nulls + [1, 2, 3, 4, 5] | dfr into-df | dfr rolling max 2 | dfr drop-nulls + [[a, b]; [1, 2], [3, 4]] | dfr into-df | dfr sample -n 1 + [[a, b]; [1, 2], [3, 4], [5, 6]] | dfr into-df | dfr sample -f 0.5 -e + [[a, b]; [6, 2], [4, 2], [2, 2]] | dfr into-df | dfr select a + let s = ([1, 2, 2, 3, 3] | dfr into-df | dfr shift 2) + let mask = ($s | dfr is-null) $s | dfr set 0 --mask $mask - - -let series = ([4 1 5 2 4 3] | dfr into-df); - let indices = ([0 2] | dfr into-df); + let series = ([ + 4 + 1 + 5 + 2 + 4 + 3 + ] | dfr into-df) + let indices = ([0, 2] | dfr into-df) $series | dfr set-with-idx 6 -i $indices - - -[[a b]; [1 2] [3 4]] | dfr into-df | dfr shape - - -[1 2 2 3 3] | dfr into-df | dfr shift 2 | dfr drop-nulls - - -[[a b]; [1 2] [3 4]] | dfr into-df | dfr slice 0 1 - - -[[a b]; [6 2] [1 4] [4 1]] | dfr into-df | dfr sort-by a -[[a b]; [6 2] [1 1] [1 4] [2 4]] | dfr into-df | dfr sort-by [a b] -r [false true] - - -[[a b]; [one 2] [one 2] [two 1] [two 1]] - | dfr into-df - | dfr group-by a - | dfr agg (dfr col b | dfr std) - - -[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr std - - -[a ab abc] | dfr into-df | dfr str-lengths - - -[abcded abc321 abc123] | dfr into-df | dfr str-slice 1 -l 2 - - -let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC'); - let df = ([$dt $dt] | dfr into-df); + [[a, b]; [1, 2], [3, 4]] | dfr into-df | dfr shape + [1, 2, 2, 3, 3] | dfr into-df | dfr shift 2 | dfr drop-nulls + [[a, b]; [1, 2], [3, 4]] | dfr into-df | dfr slice 0 1 + [[a, b]; [6, 2], [1, 4], [4, 1]] | dfr into-df | dfr sort-by a + [[a, b]; [6, 2], [1, 1], [1, 4], [2, 4]] | dfr into-df | dfr sort-by [a, b] -r [false, true] + [[a, b]; [one, 2], [one, 2], [two, 1], [two, 1]] | dfr into-df | dfr group-by a | dfr agg (dfr col b | dfr std) + [[a, b]; [6, 2], [4, 2], [2, 2]] | dfr into-df | dfr std + [a, ab, abc] | dfr into-df | dfr str-lengths + [abcded, abc321, abc123] | dfr into-df | dfr str-slice 1 -l 2 + let dt = ('2020-08-04T16:39:18+00:00' | into datetime -z 'UTC') + let df = ([$dt, $dt] | dfr into-df) $df | dfr strftime "%Y/%m/%d" - - -[[a b]; [one 2] [one 4] [two 1]] - | dfr into-df - | dfr group-by a - | dfr agg (dfr col b | dfr sum) - - -[[a b]; [6 2] [1 4] [4 1]] | dfr into-df | dfr sum - - -[[a b]; [1 1] [1 1]] | dfr into-df | dfr summary - - -let df = ([[a b]; [4 1] [5 2] [4 3]] | dfr into-df); - let indices = ([0 2] | dfr into-df); + [[a, b]; [one, 2], [one, 4], [two, 1]] | dfr into-df | dfr group-by a | dfr agg (dfr col b | dfr sum) + [[a, b]; [6, 2], [1, 4], [4, 1]] | dfr into-df | dfr sum + [[a, b]; [1, 1], [1, 1]] | dfr into-df | dfr summary + let df = ([[a, b]; [4, 1], [5, 2], [4, 3]] | dfr into-df) + let indices = ([0, 2] | dfr into-df) $df | dfr take $indices -let series = ([4 1 5 2 4 3] | dfr into-df); - let indices = ([0 2] | dfr into-df); + let series = ([ + 4 + 1 + 5 + 2 + 4 + 3 + ] | dfr into-df) + let indices = ([0, 2] | dfr into-df) $series | dfr take $indices - - -[[a b]; [1 2] [3 4]] | dfr into-df | dfr to-arrow test.arrow - - -[[a b]; [1 2] [3 4]] | dfr into-df | dfr to-csv test.csv -[[a b]; [1 2] [3 4]] | dfr into-df | dfr to-csv test.csv -d '|' - - -[[a b]; [1 2] [3 4]] | dfr into-df | dfr to-parquet test.parquet - - -[2 2 2 2 2] | dfr into-df | dfr unique -col a | unique - - -[Abc aBc abC] | dfr into-df | dfr uppercase - - -[5 5 5 5 6 6] | dfr into-df | dfr value-counts - - -[[a b]; [6 2] [4 2] [2 2]] | dfr into-df | dfr var - - -[[a b]; [one 2] [one 2] [two 1] [two 1]] - | dfr into-df - | dfr group-by a - | dfr agg (dfr col b | dfr var) - - -dfr when ((dfr col a) > 2) 4 -dfr when ((dfr col a) > 2) 4 | dfr when ((dfr col a) < 0) 6 -[[a b]; [6 2] [1 4] [4 1]] - | dfr into-lazy - | dfr with-column ( - dfr when ((dfr col a) > 2) 4 | dfr otherwise 5 | dfr as c - ) - | dfr with-column ( - dfr when ((dfr col a) > 5) 10 | dfr when ((dfr col a) < 2) 6 | dfr otherwise 0 | dfr as d - ) - | dfr collect - - -[[a b]; [1 2] [3 4]] - | dfr into-df - | dfr with-column ([5 6] | dfr into-df) --name c -[[a b]; [1 2] [3 4]] - | dfr into-lazy - | dfr with-column [ + [[a, b]; [1, 2], [3, 4]] | dfr into-df | dfr to-arrow test.arrow + [[a, b]; [1, 2], [3, 4]] | dfr into-df | dfr to-csv test.csv + [[a, b]; [1, 2], [3, 4]] | dfr into-df | dfr to-csv test.csv -d '|' + [[a, b]; [1, 2], [3, 4]] | dfr into-df | dfr to-parquet test.parquet + [2, 2, 2, 2, 2] | dfr into-df | dfr unique + col a | unique + [Abc, aBc, abC] | dfr into-df | dfr uppercase + [ + 5 + 5 + 5 + 5 + 6 + 6 + ] | dfr into-df | dfr value-counts + [[a, b]; [6, 2], [4, 2], [2, 2]] | dfr into-df | dfr var + [[a, b]; [one, 2], [one, 2], [two, 1], [two, 1]] | dfr into-df | dfr group-by a | dfr agg (dfr col b | dfr var) + dfr when ((dfr col a) > 2) 4 + dfr when ((dfr col a) > 2) 4 | dfr when ((dfr col a) < 0) 6 + [[a, b]; [6, 2], [1, 4], [4, 1]] | dfr into-lazy | dfr with-column (dfr when ((dfr col a) > 2) 4 | dfr otherwise 5 | dfr as c) | dfr with-column ( + dfr when ((dfr col a) > 5) 10 | dfr when ((dfr col a) < 2) 6 | dfr otherwise 0 | dfr as d + ) | dfr collect + [[a, b]; [1, 2], [3, 4]] | dfr into-df | dfr with-column ([5, 6] | dfr into-df) --name c + [[a, b]; [1, 2], [3, 4]] | dfr into-lazy | dfr with-column [ ((dfr col a) * 2 | dfr as "c") ((dfr col a) * 3 | dfr as "d") - ] - | dfr collect - - -do { echo hello } -let text = "I am enclosed"; let hello = {|| echo $text}; do $hello -do -i { thisisnotarealcommand } -do -s { thisisnotarealcommand } -do -p { nu -c 'exit 1' }; echo "I'll still run" -do -c { nu -c 'exit 1' } | myscarycommand -do {|x| 100 + $x } 77 -77 | do {|x| 100 + $in } - - -[0,1,2,3] | drop -[0,1,2,3] | drop 0 -[0,1,2,3] | drop 2 -[[a, b]; [1, 2] [3, 4]] | drop 1 - - -[[lib, extension]; [nu-lib, rs] [nu-core, rb]] | drop column - - -[sam,sarah,2,3,4,5] | drop nth 0 1 2 -[0,1,2,3,4,5] | drop nth 0 1 2 -[0,1,2,3,4,5] | drop nth 0 2 4 -[0,1,2,3,4,5] | drop nth 2 0 4 -[first second third fourth fifth] | drop nth (1..3) -[0,1,2,3,4,5] | drop nth 1.. -[0,1,2,3,4,5] | drop nth 3.. - - -du - - -[1 2 3] | each {|e| 2 * $e } -{major:2, minor:1, patch:4} | values | each {|| into string } -[1 2 3 2] | each {|e| if $e == 2 { "two" } } -[1 2 3] | enumerate | each {|e| if $e.item == 2 { $"found 2 at ($e.index)!"} } -[1 2 3] | each --keep-empty {|e| if $e == 2 { "found 2!"} } - - -[1 2 3 2 1] | each while {|e| if $e < 3 { $e * 2 } } -[1 2 stop 3 4] | each while {|e| if $e != 'stop' { $"Output: ($e)" } } -[1 2 3] | enumerate | each while {|e| if $e.item < 2 { $"value ($e.item) at ($e.index)!"} } - - -echo 1 2 3 -echo $in - - -"負けると知って戦うのが、遥かに美しいのだ" | encode shift-jis -"🎈" | encode -i shift-jis - - -0x[09 F9 11 02 9D 74 E3 5B D8 41 56 C5 63 56 88 C0] | encode base64 -'Some Data' | encode base64 -'Some Data' | encode base64 --character-set binhex - - -0x[09 F9 11 02 9D 74 E3 5B D8 41 56 C5 63 56 88 C0] | encode hex - - -enter ../dir-foo - - -[a, b, c] | enumerate - - -error make {msg: "my custom error message"} -error make { + ] | dfr collect + do { echo hello } + let text = "I am enclosed" + let hello = {|| echo $text } + do $hello + do -i { thisisnotarealcommand } + do -s-s-s-s-s-s-s-s { thisisnotarealcommand } + do -p-p-p-p-p-p-p-p { nu -c 'exit 1' } + echo "I'll still run" + do -c { nu -c 'exit 1' } | myscarycommand + do {|x| 100 + $x } 77 + 77 | do {|x| (((100 + $in))) } + [0, 1, 2, 3] | drop + [0, 1, 2, 3] | drop 0 + [0, 1, 2, 3] | drop 2 + [[a, b]; [1, 2], [3, 4]] | drop 1 + [[lib, extension]; [nu-lib, rs], [nu-core, rb]] | drop column + [ + sam + sarah + 2 + 3 + 4 + 5 + ] | drop nth 0 1 2 + [ + 0 + 1 + 2 + 3 + 4 + 5 + ] | drop nth 0 1 2 + [ + 0 + 1 + 2 + 3 + 4 + 5 + ] | drop nth 0 2 4 + [ + 0 + 1 + 2 + 3 + 4 + 5 + ] | drop nth 2 0 4 + [first, second, third, fourth, fifth] | drop nth (1..3) + [ + 0 + 1 + 2 + 3 + 4 + 5 + ] | drop nth 1.. + [ + 0 + 1 + 2 + 3 + 4 + 5 + ] | drop nth 3.. + du + [1, 2, 3] | each {|e| 2 * $e } + {major: 2, minor: 1, patch: 4} | values | each {|| into string } + [1, 2, 3, 2] | each {|e| if $e == 2 { "two" } } + [1, 2, 3] | enumerate | each {|e| if $e.item == 2 { $"found 2 at ($e.index)!"} } + [1, 2, 3] | each --keep-empty {|e| if $e == 2 { "found 2!"} } + [1, 2, 3, 2, 1] | each while {|e| if $e < 3 { $e * 2 } } + [1, 2, stop, 3, 4] | each while {|e| if $e != 'stop' { $"Output: ($e)" } } + [1, 2, 3] | enumerate | each while {|e| if $e.item < 2 { $"value ($e.item) at ($e.index)!"} } + echo 1 2 3 + echo $in + "負けると知って戦うのが、遥かに美しいのだ" | encode shift-jis + "🎈" | encode -i shift-jis + 0x[09 F9 11 02 9D 74 E3 5B D8 41 56 C5 63 56 88 C0] | encode base64 + 'Some Data' | encode base64 + 'Some Data' | encode base64 --character-set binhex + 0x[09 F9 11 02 9D 74 E3 5B D8 41 56 C5 63 56 88 C0] | encode hex + enter ../dir-foo + [a, b, c] | enumerate + error make {msg: "my custom error message"} + error make { msg: "my custom error message" - label: { - text: "my custom label text" # not mandatory unless $.label exists - start: 123 # not mandatory unless $.label.end is set - end: 456 # not mandatory unless $.label.start is set - } + label: {text: "my custom label text", start: 123, end: 456} } -def foo [x] { - let span = (metadata $x).span; + def foo [x] { + let span = (metadata $x).span error make { msg: "this is fishy" label: { @@ -892,1508 +575,1236 @@ def foo [x] { } } } - - -[1 2 3 4 5] | every 2 -[1 2 3 4 5] | every 2 --skip - - -exec ps aux -exec nautilus - - -exit -exit --now - - -explain {|| ls | sort-by name type -i | get name } | table -e - - -sys | explore -ls | explore --head false -glob *.md | each {|| open } | explore -i -open file.json | explore -p | to json | save part.json - - -module utils { export def my-command [] { "hello" } }; use utils my-command; my-command - - -module spam { export alias ll = ls -l } - - -module spam { export def foo [] { "foo" } }; use spam foo; foo - - -module foo { export def-env bar [] { let-env FOO_BAR = "BAZ" } }; use foo bar; bar; $env.FOO_BAR - - -export extern echo [text: string] - - -export old-alias ll = ls -l - - -module spam { export def foo [] { "foo" } } + [1, 2, 3, 4, 5] | every 2 + [1, 2, 3, 4, 5] | every 2 --skip + exec ps aux + exec nautilus + exit + exit --now + explain {|| ls | sort-by name type -i | get name } | table -e + sys | explore + ls | explore --head false + glob *.md | each {|| open } | explore -i + open file.json | explore -p | to json | save part.json + module utils { + export def my-command [] { "hello" } + } + use utils my-command + my-command + module spam { export alias ll = ls -l } + module spam { + export def foo [] { "foo" } + } + use spam foo + foo + module foo { export def-env bar [] { let-env FOO_BAR = "BAZ" } } + use foo bar + bar + $env.FOO_BAR + export extern echo [text: string] + export old-alias ll = ls -l + module spam { + export def foo [] { "foo" } + } module eggs { export use spam foo } use eggs foo foo - - - -export-env { let-env SPAM = 'eggs' } -export-env { let-env SPAM = 'eggs' }; $env.SPAM - - -extern echo [text: string] - - -'nushell' | fill -a l -c '─' -w 15 -'nushell' | fill -a r -c '─' -w 15 -'nushell' | fill -a m -c '─' -w 15 -1 | fill --alignment right --character '0' --width 5 -1.1 | fill --alignment center --character '0' --width 5 -1kib | fill --alignment middle --character '0' --width 10 - - -[1 2] | filter {|x| $x > 1} -[{a: 1} {a: 2}] | filter {|x| $x.a > 1} -let cond = {|x| $x.a > 1}; [{a: 1} {a: 2}] | filter $cond - - -ls | find toml md sh -'Cargo.toml' | find toml -[1 5 3kb 4 3Mb] | find 5 3kb -[moe larry curly] | find l -[abc bde arc abf] | find --regex "ab" -[aBc bde Arc abf] | find --regex "ab" -i -[[version name]; ['0.1.0' nushell] ['0.1.1' fish] ['0.2.0' zsh]] | find -r "nu" -[[foo bar]; [abc 123] [def 456]] | find 123 | get bar | ansi strip - - -[1 2 3] | first -[1 2 3] | first 2 -0x[01 23 45] | first 2 - - -[[N, u, s, h, e, l, l]] | flatten -[[N, u, s, h, e, l, l]] | flatten | first -[[origin, people]; [Ecuador, ([[name, meal]; ['Andres', 'arepa']])]] | flatten --all | get meal -[[origin, crate, versions]; [World, ([[name]; ['nu-cli']]), ['0.21', '0.22']]] | flatten versions --all | last | get versions -{ a: b, d: [ 1 2 3 4 ], e: [ 4 3 ] } | flatten d --all - - -42 | fmt - - -for x in [1 2 3] { print ($x * $x) } -for $x in 1..3 { print $x } -for $it in ['bob' 'fred'] --numbered { print $"($it.index) is ($it.item)" } - - -ls | format '{name}: {size}' -[[col1, col2]; [v1, v2] [v3, v4]] | format '{col2}' - - -ls | format filesize KB size -du | format filesize B apparent -4Gb | format filesize MB - - - - -"ColA,ColB + export-env { let-env SPAM = 'eggs' } + export-env { let-env SPAM = 'eggs' } + $env.SPAM + extern echo [text: string] + 'nushell' | fill -a l -c '─' -w 15 + 'nushell' | fill -a r -c '─' -w 15 + 'nushell' | fill -a m -c '─' -w 15 + 1 | fill --alignment right --character '0' --width 5 + 1.1 | fill --alignment center --character '0' --width 5 + 1kib | fill --alignment middle --character '0' --width 10 + [1, 2] | where {|x| $x > 1} + [ + {a: 1} + {a: 2} + ] | where {|x| $x.a > 1} + let cond = {|x| $x.a > 1 } + [ + {a: 1} + {a: 2} + ] | where $cond + ls | find toml md sh + 'Cargo.toml' | find toml + [ + 1 + 5 + 3kb + 4 + 3Mb + ] | find 5 3kb + [moe, larry, curly] | find l + [abc, bde, arc, abf] | find --regex "ab" + [aBc, bde, Arc, abf] | find --regex "ab" -i + [[version, name]; ['0.1.0', nushell], ['0.1.1', fish], ['0.2.0', zsh]] | find -r "nu" + [[foo, bar]; [abc, 123], [def, 456]] | find 123 | get bar | ansi strip + [1, 2, 3] | first + [1, 2, 3] | first 2 + 0x[01 23 45] | first 2 + [ + [ + N + u + s + h + e + l + l + ] + ] | flatten + [ + [ + N + u + s + h + e + l + l + ] + ] | flatten | first + [[origin, people]; [Ecuador, ([[name, meal]; ['Andres', 'arepa']])]] | flatten --all | get meal + [[origin, crate, versions]; [World, ([[name]; ['nu-cli']]), ['0.21', '0.22']]] | flatten versions --all | last | get versions + { + a: b + d: [1, 2, 3, 4] + e: [4, 3] + } | flatten d --all + 42 | fmt + for x in [1, 2, 3] { print ($x * $x) } + for $x in 1..3 { print $x } + for $it in ['bob', 'fred'] --numbered { print $"($it.index) is ($it.item)" } + ls | format '{name}: {size}' + [[col1, col2]; [v1, v2], [v3, v4]] | format '{col2}' + ls | format filesize KB size + du | format filesize B apparent + 4Gb | format filesize MB + "ColA,ColB 1,2" | from csv -open data.txt | from csv --noheaders -open data.txt | from csv --separator ';' -open data.txt | from csv --comment '#' -open data.txt | from csv --trim all -open data.txt | from csv --trim headers -open data.txt | from csv --trim fields - - -'From: test@email.com + open data.txt | from csv --noheaders + open data.txt | from csv --separator ';' + open data.txt | from csv --comment '#' + open data.txt | from csv --trim all + open data.txt | from csv --trim headers + open data.txt | from csv --trim fields + 'From: test@email.com Subject: Welcome To: someone@somewhere.com Test' | from eml -'From: test@email.com + 'From: test@email.com Subject: Welcome To: someone@somewhere.com Test' | from eml -b 1 - - -'BEGIN:VCALENDAR + 'BEGIN:VCALENDAR END:VCALENDAR' | from ics - - -'[foo] + '[foo] a=1 b=2' | from ini - - -'{ "a": 1 }' | from json -'{ "a": 1, "b": [1, 2] }' | from json - - -'{ a:1 }' | from nuon -'{ a:1, b: [1, 2] }' | from nuon - - -open --raw test.ods | from ods -open --raw test.ods | from ods -s [Spreadsheet1] - - -open --raw file.parquet | from parquet -open file.parquet - - - - -'FOO BAR + '{ "a": 1 }' | from json + '{ "a": 1, "b": [1, 2] }' | from json + '{ a:1 }' | from nuon + '{ a:1, b: [1, 2] }' | from nuon + open --raw test.ods | from ods + open --raw test.ods | from ods -s [Spreadsheet1] + open --raw file.parquet | from parquet + open file.parquet + 'FOO BAR 1 2' | from ssv -'FOO BAR + 'FOO BAR 1 2' | from ssv -n - - -'a = 1' | from toml -'a = 1 + 'a = 1' | from toml + 'a = 1 b = [1, 2]' | from toml - - -"ColA ColB + "ColA ColB 1 2" | from tsv -$'c1(char tab)c2(char tab)c3(char nl)1(char tab)2(char tab)3' | save tsv-data | open tsv-data | from tsv -$'a1(char tab)b1(char tab)c1(char nl)a2(char tab)b2(char tab)c2' | save tsv-data | open tsv-data | from tsv -n -$'a1(char tab)b1(char tab)c1(char nl)a2(char tab)b2(char tab)c2' | save tsv-data | open tsv-data | from tsv --trim all -$'a1(char tab)b1(char tab)c1(char nl)a2(char tab)b2(char tab)c2' | save tsv-data | open tsv-data | from tsv --trim headers -$'a1(char tab)b1(char tab)c1(char nl)a2(char tab)b2(char tab)c2' | save tsv-data | open tsv-data | from tsv --trim fields - - -'bread=baguette&cheese=comt%C3%A9&meat=ham&fat=butter' | from url - - -'BEGIN:VCARD + $'c1(char tab)c2(char tab)c3(char nl)1(char tab)2(char tab)3' | save tsv-data | open tsv-data | from tsv + $'a1(char tab)b1(char tab)c1(char nl)a2(char tab)b2(char tab)c2' | save tsv-data | open tsv-data | from tsv -n + $'a1(char tab)b1(char tab)c1(char nl)a2(char tab)b2(char tab)c2' | save tsv-data | open tsv-data | from tsv --trim all + $'a1(char tab)b1(char tab)c1(char nl)a2(char tab)b2(char tab)c2' | save tsv-data | open tsv-data | from tsv --trim headers + $'a1(char tab)b1(char tab)c1(char nl)a2(char tab)b2(char tab)c2' | save tsv-data | open tsv-data | from tsv --trim fields + 'bread=baguette&cheese=comt%C3%A9&meat=ham&fat=butter' | from url + 'BEGIN:VCARD N:Foo FN:Bar EMAIL:foo@bar.com END:VCARD' | from vcf - - -open --raw test.xlsx | from xlsx -open --raw test.xlsx | from xlsx -s [Spreadsheet1] - - -' + open --raw test.xlsx | from xlsx + open --raw test.xlsx | from xlsx -s [Spreadsheet1] + ' Event ' | from xml - - -'a: 1' | from yaml -'[ a: 1, b: [1, 2] ]' | from yaml - - -'a: 1' | from yaml -'[ a: 1, b: [1, 2] ]' | from yaml - - -g -mkdir foo bar; enter foo; enter ../bar; g 1 -shells; g 2 -mkdir foo bar; enter foo; enter ../bar; g - - - -[0 1 2] | get 1 -[{A: A0}] | get A -[{A: A0}] | get 0.A -ls | get name.2 -ls | get 2.name -sys | get cpu -$env | get paTH -$env | get -s Path - - -glob *.rs -glob **/*.{rs,toml} --depth 2 -glob "[Cc]*" -glob "{a?c,x?z}" -glob "(?i)c*" -glob "[!cCbMs]*" -glob -glob <[a-d]:1,10> -glob "[A-Z]*" --no-file --no-symlink - - -[1 2 3 a b c] | grid -[1 2 3 a b c] | wrap name | grid -{name: 'foo', b: 1, c: 2} | grid -[{name: 'A', v: 1} {name: 'B', v: 2} {name: 'C', v: 3}] | grid -[[name patch]; [0.1.0 false] [0.1.1 true] [0.2.0 false]] | grid - - -[1 2 3 4] | group 2 - - -ls | group-by type -['1' '3' '1' '3' '2' '1' '1'] | group-by - - - - - - - - -'abcdefghijklmnopqrstuvwxyz' | hash md5 -'abcdefghijklmnopqrstuvwxyz' | hash md5 --binary -open ./nu_0_24_1_windows.zip | hash md5 - - -'abcdefghijklmnopqrstuvwxyz' | hash sha256 -'abcdefghijklmnopqrstuvwxyz' | hash sha256 --binary -open ./nu_0_24_1_windows.zip | hash sha256 - - -"a b c|1 2 3" | split row "|" | split column " " | headers -"a b c|1 2 3|1 2 3 4" | split row "|" | split column " " | headers - - -help match -help str lpad -help --find char - - -help aliases -help aliases my-alias -help aliases --find my-alias - - - - -help externs -help externs smth -help externs --find smth - - -help modules -help modules my-module -help modules --find my-module - - - - -alias lll = ls -l; hide lll -def say-hi [] { echo 'Hi!' }; hide say-hi - - -let-env HZ_ENV_ABC = 1; hide-env HZ_ENV_ABC; 'HZ_ENV_ABC' in (env).name - - - - -ls | histogram type -ls | histogram type freq -[1 2 1] | histogram -[1 2 3 1 1 1 2 2 1 1] | histogram --percentage-type relative - - -history | length -history | last 5 -history | wrap cmd | where cmd =~ cargo - - -history session - - - - -http delete https://www.example.com -http delete -u myuser -p mypass https://www.example.com -http delete -H [my-header-key my-header-value] https://www.example.com -http delete -d 'body' https://www.example.com -http delete -t application/json -d { field: value } https://www.example.com - - -http get https://www.example.com -http get -u myuser -p mypass https://www.example.com -http get -H [my-header-key my-header-value] https://www.example.com - - -http head https://www.example.com -http head -u myuser -p mypass https://www.example.com -http head -H [my-header-key my-header-value] https://www.example.com - - -http patch https://www.example.com 'body' -http patch -u myuser -p mypass https://www.example.com 'body' -http patch -H [my-header-key my-header-value] https://www.example.com -http patch -t application/json https://www.example.com { field: value } - - -http post https://www.example.com 'body' -http post -u myuser -p mypass https://www.example.com 'body' -http post -H [my-header-key my-header-value] https://www.example.com -http post -t application/json https://www.example.com { field: value } - - -http put https://www.example.com 'body' -http put -u myuser -p mypass https://www.example.com 'body' -http put -H [my-header-key my-header-value] https://www.example.com -http put -t application/json https://www.example.com { field: value } - - -if 2 < 3 { 'yes!' } -if 5 < 3 { 'yes!' } else { 'no!' } -if 5 < 3 { 'yes!' } else if 4 < 5 { 'no!' } else { 'okay!' } - - -echo done | ignore - - - - -let user_input = (input) - - -{'name': 'nu', 'stars': 5} | insert alias 'Nushell' -[[project, lang]; ['Nushell', 'Rust']] | insert type 'shell' -[[foo]; [7] [8] [9]] | enumerate | insert bar {|e| $e.item.foo + $e.index } | flatten - - -ls | inspect | get name | inspect - - - - -'This is a string that is exactly 52 characters long.' | into binary -1 | into binary -true | into binary -ls | where name == LICENSE | get size | into binary -ls | where name == LICENSE | get name | path expand | into binary -1.234 | into binary - - -[[value]; ['false'] ['1'] [0] [1.0] [true]] | into bool value -true | into bool -1 | into bool -0.3 | into bool -'0.0' | into bool -'true' | into bool - - -'27.02.2021 1:55 pm +0000' | into datetime -'2021-02-27T13:55:40.2246+00:00' | into datetime -'20210227_135540+0000' | into datetime -f '%Y%m%d_%H%M%S%z' -1614434140123456789 | into datetime --offset -5 -1614434140 * 1_000_000_000 | into datetime - - -[[num]; ['5.01']] | into decimal num -'1.345' | into decimal -'-5.9' | into decimal -true | into decimal - - -[[value]; ['1sec'] ['2min'] ['3hr'] ['4day'] ['5wk']] | into duration value -'7min' | into duration -'7min' | into duration --convert sec -420sec | into duration -420sec | into duration --convert ms -1000000µs | into duration --convert sec -1sec | into duration --convert µs -1sec | into duration --convert us - - -[[bytes]; ['5'] [3.2] [4] [2kb]] | into filesize bytes -'2' | into filesize -8.3 | into filesize -5 | into filesize -4KB | into filesize - - -[[num]; ['-5'] [4] [1.5]] | into int num -'2' | into int -5.9 | into int -'5.9' | into int -4KB | into int -[false, true] | into int -1983-04-13T12:09:14.123456789-05:00 | into int -'1101' | into int -r 2 -'FF' | into int -r 16 -'0o10132' | into int -'0010132' | into int -'0010132' | into int -r 8 - - -[[value]; [false]] | into record -[1 2 3] | into record -0..2 | into record --500day | into record -{a: 1, b: 2} | into record -2020-04-12T22:10:57+02:00 | into record - - -ls | into sqlite my_ls.db -ls | into sqlite my_ls.db -t my_table -[[name]; [-----] [someone] [=====] [somename] ['(((((']] | into sqlite filename.db -[one 2 5.2 six true 100mib 25sec] | into sqlite variety.db - - -5 | into string -d 3 -1.7 | into string -d 0 -1.7 | into string -d 1 -1.734 | into string -d 2 -1.734 | into string -d -2 -4.3 | into string -'1234' | into string -true | into string -ls Cargo.toml | get name | into string -1KiB | into string - - -if is-admin { "iamroot" } else { "iamnotroot" } - - -'' | is-empty -[] | is-empty -[[meal size]; [arepa small] [taco '']] | is-empty meal size - - -{ new: york, san: francisco } | items {|key, value| echo $'($key) ($value)' } - - -[{a: 1 b: 2}] | join [{a: 1 c: 3}] a - - -open -r test.json | json path '$.store.book[*].author' - - - - -keybindings default - - -keybindings list -m -keybindings list -e -d -keybindings list - - -keybindings listen - - -ps | sort-by mem | last | kill $in.pid -kill --force 12345 -kill -s 2 12345 - - -[1,2,3] | last 2 -[1,2,3] | last - - -[1 2 3 4 5] | length -[{columnA: A0 columnB: B0}] | length -c - - -let x = 10 -let x = 10 + 100 -let x = if false { -1 } else { 1 } - - -let-env MY_ENV_VAR = 1; $env.MY_ENV_VAR - - -$"two\nlines" | lines - - -{NAME: ABE, AGE: UNKNOWN} | load-env; $env.NAME -load-env {NAME: ABE, AGE: UNKNOWN}; $env.NAME - - -mut x = 0; loop { if $x > 10 { break }; $x = $x + 1 }; $x - - -ls -ls subdir -ls -f .. -ls *.rs -ls -s | where name !~ bar -ls -a ~ | where type == dir -ls -as ~ | where type == dir and modified < ((date now) - 7day) -['/path/to/directory' '/path/to/file'] | each {|| ls -D $in } | flatten - - -match 3 { 1..10 => 'yes!' } -match {a: 100} { {a: $my_value} => { $my_value } } -match 3 { 1 => { 'yes!' }, _ => { 'no!' } } -match [1, 2, 3] { [$a, $b, $c] => { $a + $b + $c }, _ => 0 } -{a: {b: 3}} | match $in {{a: { $b }} => ($b + 10) } - - - - -[-50 -100.0 25] | math abs - - -1 | math arccos --1 | math arccos -d - - -1 | math arccosh - - -1 | math arcsin -1 | math arcsin -d - - -0 | math arcsinh - - -1 | math arctan --1 | math arctan -d - - -1 | math arctanh - - -[-50 100.0 25] | math avg - - -[1.5 2.3 -3.1] | math ceil - - -math pi | math cos -[0 90 180 270 360] | math cos -d - - -1 | math cosh - - -math e | math round --precision 3 - - - - -0 | math exp -1 | math exp - - -[1.5 2.3 -3.1] | math floor - - -math e | math ln - - -100 | math log 10 -[16 8 4] | math log 2 - - -[-50 100 25] | math max -[{a: 1 b: 3} {a: 2 b: -1}] | math max - - -[3 8 9 12 12 15] | math median -[{a: 1 b: 3} {a: 2 b: -1} {a: -3 b: 5}] | math median - - -[-50 100 25] | math min -[{a: 1 b: 3} {a: 2 b: -1}] | math min - - -[3 3 9 12 12 15] | math mode -[{a: 1 b: 3} {a: 2 b: -1} {a: 1 b: 5}] | math mode - - -math pi | math round --precision 2 - - -[2 3 3 4] | math product - - -[1.5 2.3 -3.1] | math round -[1.555 2.333 -3.111] | math round -p 2 - - -(math pi) / 2 | math sin -[0 90 180 270 360] | math sin -d | math round --precision 4 - - -1 | math sinh - - -[9 16] | math sqrt - - -[1 2 3 4 5] | math stddev -[1 2 3 4 5] | math stddev -s - - -[1 2 3] | math sum -ls | get size | math sum - - -(math pi) / 4 | math tan -[-45 0 45] | math tan -d - - -(math pi) * 10 | math tanh - - -math tau | math round --precision 2 - - -[1 2 3 4 5] | math variance -[1 2 3 4 5] | math variance -s - - -[a b c] | wrap name | merge ( [1 2 3] | wrap index ) -{a: 1, b: 2} | merge {c: 3} -[{columnA: A0 columnB: B0}] | merge [{columnA: 'A0*'}] - - -let a = 42; metadata $a -ls | metadata - - -mkdir foo -mkdir -v foo/bar foo2 - - -module spam { export def foo [] { "foo" } }; use spam foo; foo -module foo { export-env { let-env FOO = "BAZ" } }; use foo; $env.FOO -module foo { export def-env bar [] { let-env FOO_BAR = "BAZ" } }; use foo bar; bar; $env.FOO_BAR - - -[[name value index]; [foo a 1] [bar b 2] [baz c 3]] | move index --before name -[[name value index]; [foo a 1] [bar b 2] [baz c 3]] | move value name --after index -{ name: foo, value: a, index: 1 } | move name --before index - - -mut x = 10; $x = 12 -mut a = {b:{c:1}}; $a.b.c = 2 -mut x = 10 + 100 -mut x = if false { -1 } else { 1 } - - -mv before.txt after.txt -mv test.txt my/subdirectory -mv *.txt my/subdirectory - - -mkdir foo bar; enter foo; enter ../bar; n -n - - -nu-check script.nu -nu-check --as-module module.nu -nu-check -d script.nu -open foo.nu | nu-check -d script.nu -open module.nu | lines | nu-check -d --as-module module.nu -$'two(char nl)lines' | nu-check -nu-check -a script.nu -open foo.nu | lines | nu-check -ad - - -'let x = 3' | nu-highlight - - -old-alias ll = ls -l -old-alias customs = ($nu.scope.commands | where is_custom | get command) - - -open myfile.json -open myfile.json --raw -'myfile.txt' | open -open myfile.txt --raw | decode utf-8 - - - - -module spam { export def foo [] { "foo" } } + 'a: 1' | from yaml + '[ a: 1, b: [1, 2] ]' | from yaml + 'a: 1' | from yaml + '[ a: 1, b: [1, 2] ]' | from yaml + g + mkdir foo bar + enter foo + enter ../bar + g 1 + shells + g 2 + mkdir foo bar + enter foo + enter ../bar + g - + [0, 1, 2] | get 1 + [ + {A: A0} + ] | get A + [ + {A: A0} + ] | get 0.A + ls | get name.2 + ls | get 2.name + sys | get cpu + $env | get paTH + $env | get -s Path + glob *.rs + glob **/*.{rs,toml} --depth 2 + glob "[Cc]*" + glob "{a?c,x?z}" + glob "(?i)c*" + glob "[!cCbMs]*" + glob + glob <[a-d]:1,10> + glob "[A-Z]*" --no-file --no-symlink + [ + 1 + 2 + 3 + a + b + c + ] | grid + [ + 1 + 2 + 3 + a + b + c + ] | wrap name | grid + {name: 'foo', b: 1, c: 2} | grid + [ + {name: 'A', v: 1} + {name: 'B', v: 2} + {name: 'C', v: 3} + ] | grid + [[name, patch]; [0.1.0, false], [0.1.1, true], [0.2.0, false]] | grid + [1, 2, 3, 4] | group 2 + ls | group-by type + [ + '1' + '3' + '1' + '3' + '2' + '1' + '1' + ] | group-by + 'abcdefghijklmnopqrstuvwxyz' | hash md5 + 'abcdefghijklmnopqrstuvwxyz' | hash md5 --binary + open ./nu_0_24_1_windows.zip | hash md5 + 'abcdefghijklmnopqrstuvwxyz' | hash sha256 + 'abcdefghijklmnopqrstuvwxyz' | hash sha256 --binary + open ./nu_0_24_1_windows.zip | hash sha256 + "a b c|1 2 3" | split row "|" | split column " " | headers + "a b c|1 2 3|1 2 3 4" | split row "|" | split column " " | headers + help match + help str lpad + help --find char + help aliases + help aliases my-alias + help aliases --find my-alias + help externs + help externs smth + help externs --find smth + help modules + help modules my-module + help modules --find my-module + alias lll = ls -l + hide lll + def say-hi [] { echo 'Hi!' } + hide say-hi + let-env HZ_ENV_ABC = (1) + hide-env HZ_ENV_ABC + 'HZ_ENV_ABC' in (env).name + ls | histogram type + ls | histogram type freq + [1, 2, 1] | histogram + [ + 1 + 2 + 3 + 1 + 1 + 1 + 2 + 2 + 1 + 1 + ] | histogram --percentage-type relative + history | length + history | last 5 + history | wrap cmd | where cmd =~ cargo + history session + http delete https://www.example.com + http delete -u myuser -p mypass https://www.example.com + http delete -H [my-header-key, my-header-value] https://www.example.com + http delete -d 'body' https://www.example.com + http delete -t application/json -d { field: value } https://www.example.com + http get https://www.example.com + http get -u myuser -p mypass https://www.example.com + http get -H [my-header-key, my-header-value] https://www.example.com + http head https://www.example.com + http head -u myuser -p mypass https://www.example.com + http head -H [my-header-key, my-header-value] https://www.example.com + http patch https://www.example.com 'body' + http patch -u myuser -p mypass https://www.example.com 'body' + http patch -H [my-header-key, my-header-value] https://www.example.com + http patch -t application/json https://www.example.com { field: value } + http post https://www.example.com 'body' + http post -u myuser -p mypass https://www.example.com 'body' + http post -H [my-header-key, my-header-value] https://www.example.com + http post -t application/json https://www.example.com { field: value } + http put https://www.example.com 'body' + http put -u myuser -p mypass https://www.example.com 'body' + http put -H [my-header-key, my-header-value] https://www.example.com + http put -t application/json https://www.example.com { field: value } + if 2 < 3 { 'yes!' } + if 5 < 3 { 'yes!' } else { 'no!' } + if 5 < 3 { 'yes!' } else if 4 < 5 { 'no!' } else { 'okay!' } + echo done | ignore + let user_input = (input) + {'name': 'nu', 'stars': 5} | insert alias 'Nushell' + [[project, lang]; ['Nushell', 'Rust']] | insert type 'shell' + [[foo]; [7], [8], [9]] | enumerate | insert bar {|e| $e.item.foo + $e.index } | flatten + ls | inspect | get name | inspect + 'This is a string that is exactly 52 characters long.' | into binary + 1 | into binary + true | into binary + ls | where name == LICENSE | get size | into binary + ls | where name == LICENSE | get name | path expand | into binary + 1.234 | into binary + [[value]; ['false'], ['1'], [0], [1.0], [true]] | into bool value + true | into bool + 1 | into bool + 0.3 | into bool + '0.0' | into bool + 'true' | into bool + '27.02.2021 1:55 pm +0000' | into datetime + '2021-02-27T13:55:40.2246+00:00' | into datetime + '20210227_135540+0000' | into datetime -f '%Y%m%d_%H%M%S%z' + 1614434140123456789 | into datetime --offset -5 + 1614434140 * 1_000_000_000 | into datetime + [[num]; ['5.01']] | into decimal num + '1.345' | into decimal + '-5.9' | into decimal + true | into decimal + [[value]; ['1sec'], ['2min'], ['3hr'], ['4day'], ['5wk']] | into duration value + '7min' | into duration + '7min' | into duration --convert sec + 420sec | into duration + 420sec | into duration --convert ms + 1000000µs | into duration --convert sec + 1sec | into duration --convert µs + 1sec | into duration --convert us + [[bytes]; ['5'], [3.2], [4], [2kb]] | into filesize bytes + '2' | into filesize + 8.3 | into filesize + 5 | into filesize + 4KB | into filesize + [[num]; ['-5'], [4], [1.5]] | into int num + '2' | into int + 5.9 | into int + '5.9' | into int + 4KB | into int + [false, true] | into int + 1983-04-13T12:09:14.123456789-05:00 | into int + '1101' | into int -r 2 + 'FF' | into int -r 16 + '0o10132' | into int + '0010132' | into int + '0010132' | into int -r 8 + [[value]; [false]] | into record + [1, 2, 3] | into record + 0..2 | into record + -500day | into record + {a: 1, b: 2} | into record + 2020-04-12T22:10:57+02:00 | into record + ls | into sqlite my_ls.db + ls | into sqlite my_ls.db -t my_table + [[name]; [-----], [someone], [=====], [somename], ['(((((']] | into sqlite filename.db + [ + one + 2 + 5.2 + six + true + 100mib + 25sec + ] | into sqlite variety.db + 5 | into string -d 3 + 1.7 | into string -d 0 + 1.7 | into string -d 1 + 1.734 | into string -d 2 + 1.734 | into string -d -2 + 4.3 | into string + '1234' | into string + true | into string + ls Cargo.toml | get name | into string + 1KiB | into string + if is-admin { "iamroot" } else { "iamnotroot" } + '' | is-empty + [] | is-empty + [[meal, size]; [arepa, small], [taco, '']] | is-empty meal size + {new: york, san: francisco} | items {|key, value| echo $'($key) ($value)' } + [ + {a: 1, b: 2} + ] | join [ + {a: 1, c: 3} + ] a + open -r test.json | json path '$.store.book[*].author' + keybindings default + keybindings list -m + keybindings list -e -d + keybindings list + keybindings listen + ps | sort-by mem | last | kill $in.pid + kill --force 12345 + kill -s 2 12345 + [1, 2, 3] | last 2 + [1, 2, 3] | last + [1, 2, 3, 4, 5] | length + [ + {columnA: A0, columnB: B0} + ] | length -c + let x = 10 + let x = 10 + 100 + let x = if false { -1 } else { 1 } + let-env MY_ENV_VAR = (1) + $env.MY_ENV_VAR + $"two\nlines" | lines + {NAME: ABE, AGE: UNKNOWN} | load-env + $env.NAME + load-env {NAME: ABE, AGE: UNKNOWN} + $env.NAME + mut x = 0 + loop { + if $x > 10 { break } + $x = ($x + 1) + } + $x + ls + ls subdir + ls -f .. + ls *.rs + ls -s | where name !~ bar + ls -a ~ | where type == dir + ls -as ~ | where type == dir and modified < ((date now) - 7day) + ['/path/to/directory', '/path/to/file'] | each {|| ls -D $in } | flatten + match 3 { + 1..10 => 'yes!' + } + match {a: 100} { + {a: $my_value} => { $my_value } + } + match 3 { + 1 => { 'yes!' } + _ => { 'no!' } + } + match [1, 2, 3] { + [$a, $b, $c] => { $a + $b + $c } + _ => 0 + } + { + a: {b: 3} + } | match $in { + {a: {b: $b}} => ($b + 10) + } + [-50, -100.0, 25] | math abs + 1 | math arccos + -1 | math arccos -d + 1 | math arccosh + 1 | math arcsin + 1 | math arcsin -d + 0 | math arcsinh + 1 | math arctan + -1 | math arctan -d + 1 | math arctanh + [-50, 100.0, 25] | math avg + [1.5, 2.3, -3.1] | math ceil + math pi | math cos + [0, 90, 180, 270, 360] | math cos -d + 1 | math cosh + math e | math round --precision 3 + 0 | math exp + 1 | math exp + [1.5, 2.3, -3.1] | math floor + math e | math ln + 100 | math log 10 + [16, 8, 4] | math log 2 + [-50, 100, 25] | math max + [ + {a: 1, b: 3} + {a: 2, b: -1} + ] | math max + [ + 3 + 8 + 9 + 12 + 12 + 15 + ] | math median + [ + {a: 1, b: 3} + {a: 2, b: -1} + {a: -3, b: 5} + ] | math median + [-50, 100, 25] | math min + [ + {a: 1, b: 3} + {a: 2, b: -1} + ] | math min + [ + 3 + 3 + 9 + 12 + 12 + 15 + ] | math mode + [ + {a: 1, b: 3} + {a: 2, b: -1} + {a: 1, b: 5} + ] | math mode + math pi | math round --precision 2 + [2, 3, 3, 4] | math product + [1.5, 2.3, -3.1] | math round + [1.555, 2.333, -3.111] | math round -p 2 + (math pi) / 2 | math sin + [0, 90, 180, 270, 360] | math sin -d | math round --precision 4 + 1 | math sinh + [9, 16] | math sqrt + [1, 2, 3, 4, 5] | math stddev + [1, 2, 3, 4, 5] | math stddev -s + [1, 2, 3] | math sum + ls | get size | math sum + (math pi) / 4 | math tan + [-45, 0, 45] | math tan -d + (math pi) * 10 | math tanh + math tau | math round --precision 2 + [1, 2, 3, 4, 5] | math variance + [1, 2, 3, 4, 5] | math variance -s + [a, b, c] | wrap name | merge ([1, 2, 3] | wrap index) + {a: 1, b: 2} | merge {c: 3} + [ + {columnA: A0, columnB: B0} + ] | merge [ + {columnA: 'A0*'} + ] + let a = 42 + metadata $a + ls | metadata + mkdir foo + mkdir -v foo/bar foo2 + module spam { + export def foo [] { "foo" } + } + use spam foo + foo + module foo { export-env { let-env FOO = "BAZ" } } + use foo + $env.FOO + module foo { export def-env bar [] { let-env FOO_BAR = "BAZ" } } + use foo bar + bar + $env.FOO_BAR + [[name, value, index]; [foo, a, 1], [bar, b, 2], [baz, c, 3]] | move index --before name + [[name, value, index]; [foo, a, 1], [bar, b, 2], [baz, c, 3]] | move value name --after index + {name: foo, value: a, index: 1} | move name --before index + mut x = 10 + $x = (12) + mut a = { + b: {c: 1} + } + $a.b.c = (2) + mut x = 10 + 100 + mut x = if false { -1 } else { 1 } + mv before.txt after.txt + mv test.txt my/subdirectory + mv *.txt my/subdirectory + mkdir foo bar + enter foo + enter ../bar + n + n + nu-check script.nu + nu-check --as-module module.nu + nu-check -d script.nu + open foo.nu | nu-check -d script.nu + open module.nu | lines | nu-check -d --as-module module.nu + $'two(char nl)lines' | nu-check + nu-check -a script.nu + open foo.nu | lines | nu-check -ad + 'let x = 3' | nu-highlight + old-alias ll = (ls -l) + old-alias customs = (($nu.scope.commands | where is_custom | get command)) + open myfile.json + open myfile.json --raw + 'myfile.txt' | open + open myfile.txt --raw | decode utf-8 + module spam { + export def foo [] { "foo" } + } overlay use spam def bar [] { "bar" } overlay hide spam --keep-custom bar - -'export alias f = "foo"' | save spam.nu + 'export alias f = "foo"' | save spam.nu overlay use spam.nu overlay hide spam -module spam { export-env { let-env FOO = "foo" } } + module spam { export-env { let-env FOO = "foo" } } overlay use spam overlay hide -overlay new spam + overlay new spam cd some-dir - overlay hide --keep-env [ PWD ] spam - - -module spam { export def foo [] { "foo" } } + overlay hide --keep-env [PWD] spam + module spam { + export def foo [] { "foo" } + } overlay use spam overlay list | last - - -overlay new spam - - -module spam { export def foo [] { "foo" } } + overlay new spam + module spam { + export def foo [] { "foo" } + } overlay use spam foo -module spam { export def foo [] { "foo" } } + module spam { + export def foo [] { "foo" } + } overlay use spam as spam_new foo -'export def foo { "foo" }' + 'export def foo { "foo" }' overlay use --prefix spam spam foo -'export-env { let-env FOO = "foo" }' | save spam.nu + 'export-env { let-env FOO = "foo" }' | save spam.nu overlay use spam.nu $env.FOO - - -mkdir foo bar; enter foo; enter ../bar; p -p - - -[1 2 3] | par-each {|| 2 * $in } -[foo bar baz] | par-each {|e| $e + '!' } | sort -1..3 | enumerate | par-each {|p| update item ($p.item * 2)} | sort-by item | get item -[1 2 3] | enumerate | par-each { |e| if $e.item == 2 { $"found 2 at ($e.index)!"} } - - -"hi there" | parse "{foo} {bar}" -"hi there" | parse -r '(?P\w+) (?P\w+)' -"foo bar." | parse -r '\s*(?\w+)(?=\.)' -"foo! bar." | parse -r '(\w+)(?=\.)|(\w+)(?=!)' -" @another(foo bar) " | parse -r '\s*(?<=[() ])(@\w+)(\([^)]*\))?\s*' -"abcd" | parse -r '^a(bc(?=d)|b)cd$' - - - - -'/home/joe/test.txt' | path basename -[[name];[/home/joe]] | path basename -c [ name ] -'/home/joe/test.txt' | path basename -r 'spam.png' - - -'/home/joe/code/test.txt' | path dirname -ls ('.' | path expand) | path dirname -c [ name ] -'/home/joe/code/test.txt' | path dirname -n 2 -'/home/joe/code/test.txt' | path dirname -n 2 -r /home/viking - - -'/home/joe/todo.txt' | path exists -ls | path exists -c [ name ] - - -'/home/joe/foo/../bar' | path expand -ls | path expand -c [ name ] -'foo/../bar' | path expand - - -'/home/viking' | path join spam.txt -'/home/viking' | path join spams this_spam.txt -ls | path join spam.txt -c [ name ] -[ '/' 'home' 'viking' 'spam.txt' ] | path join -[[ parent stem extension ]; [ '/home/viking' 'spam' 'txt' ]] | path join - - -'/home/viking/spam.txt' | path parse -'/home/viking/spam.tar.gz' | path parse -e tar.gz | upsert extension { 'txt' } -'/etc/conf.d' | path parse -e '' -ls | path parse -c [ name ] - - -'/home/viking' | path relative-to '/home' -ls ~ | path relative-to ~ -c [ name ] -'eggs/bacon/sausage/spam' | path relative-to 'eggs/bacon/sausage' - - -'/home/viking/spam.txt' | path split -ls ('.' | path expand) | path split -c [ name ] - - -'.' | path type -ls | path type -c [ name ] - - - - - - -pnet - - -port 3121 4000 -port - - -[1,2,3,4] | prepend 0 -[2,3,4] | prepend [0,1] -[2,nu,4,shell] | prepend [0,1,rocks] - - -print "hello world" -print (2 + 3) - - -def spam [] { "spam" }; profile {|| spam | str length } -d 2 --source - - -ps -ps | sort-by mem | last 5 -ps | sort-by cpu | last 3 -ps | where name =~ 'nu' -ps | where pid == $nu.pid | get ppid - - - - -open foo.db | query db "SELECT * FROM Bar" - - - - -http get https://phoronix.com | query web -q 'header' -http get https://en.wikipedia.org/wiki/List_of_cities_in_India_by_population - | query web -t [Rank City 'Population(2011)[3]' 'Population(2001)' 'State or union territory'] -http get https://www.nushell.sh | query web -q 'h2, h2 + p' | group 2 | each {rotate --ccw tagline description} | flatten -http get https://example.org | query web --query a --attribute href - - - - - - -random bool -random bool --bias 0.75 - - -random chars -random chars -l 20 - - -random decimal -random decimal ..500 -random decimal 100000.. -random decimal 1.0..1.1 - - -random dice -random dice -d 10 -s 12 - - -random integer -random integer ..500 -random integer 100000.. -random integer 1..10 - - -random uuid - - -[0,1,2,3,4,5] | range 4..5 -[0,1,2,3,4,5] | range (-2).. -[0,1,2,3,4,5] | range (-3)..-2 - - -[ 1 2 3 4 ] | reduce {|it, acc| $it + $acc } -[ 8 7 6 ] | enumerate | reduce -f 0 {|it, acc| $acc + $it.item + $it.index } -[ 1 2 3 4 ] | reduce -f 10 {|it, acc| $acc + $it } -[ i o t ] | reduce -f "Arthur, King of the Britons" {|it, acc| $acc | str replace -a $it "X" } -['foo.gz', 'bar.gz', 'baz.gz'] | enumerate | reduce -f '' {|str all| $"($all)(if $str.index != 0 {'; '})($str.index + 1)-($str.item)" } - - -"hello world" | regex '(?P\w+) (?P\w+)' - - -register ~/.cargo/bin/nu_plugin_query -let plugin = ((which nu).path.0 | path dirname | path join 'nu_plugin_query'); nu -c $'register ($plugin); version' - - -ls | reject modified -[[a, b]; [1, 2]] | reject a -{a: 1, b: 2} | reject a -{a: {b: 3, c: 5}} | reject a.b - - -[[a, b]; [1, 2]] | rename my_column -[[a, b, c]; [1, 2, 3]] | rename eggs ham bacon -[[a, b, c]; [1, 2, 3]] | rename -c [a ham] -{a: 1 b: 2} | rename x y - - -def foo [] { return } - - -[0,1,2,3] | reverse -[{a: 1} {a: 2}] | reverse - - -rm file.txt -rm --trash file.txt -rm --permanent file.txt -rm --force file.txt -ls | where size == 0KB and type == file | each { rm $in.name } | null - - - - -[[a b]; [1 2] [3 4] [5 6]] | roll down - - -{a:1 b:2 c:3} | roll left -[[a b c]; [1 2 3] [4 5 6]] | roll left -[[a b c]; [1 2 3] [4 5 6]] | roll left --cells-only - - -{a:1 b:2 c:3} | roll right -[[a b c]; [1 2 3] [4 5 6]] | roll right -[[a b c]; [1 2 3] [4 5 6]] | roll right --cells-only - - -[[a b]; [1 2] [3 4] [5 6]] | roll up - - -{a:1, b:2} | rotate -[[a b]; [1 2] [3 4] [5 6]] | rotate -[[a b]; [1 2]] | rotate col_a col_b -[[a b]; [1 2]] | rotate --ccw -[[a b]; [1 2] [3 4] [5 6]] | rotate --ccw -[[a b]; [1 2]] | rotate --ccw col_a col_b - - -run-external "echo" "-n" "hello" -run-external --redirect-stdout "echo" "-n" "hello" | split chars - - -'save me' | save foo.txt -'append me' | save --append foo.txt -{ a: 1, b: 2 } | save foo.json -do -i {} | save foo.txt --stderr foo.txt -do -i {} | save foo.txt --stderr bar.txt - - -open foo.db | schema - - -[{a: a b: b}] | select a -{a: a b: b} | select a -ls | select name -ls | select 0 1 2 3 - - -seq 1 10 -seq 1.0 0.1 2.0 -seq 1 5 | str join '|' - - -seq char a e -seq char a e | str join '|' - - -seq date --days 10 -seq date --days 10 -r -seq date --days 10 -o '%m/%d/%Y' -r -seq date -b '2020-01-01' -e '2020-01-10' -seq date -b '2020-01-01' -e '2020-01-31' -n 5 - - -enter ..; shells -shells | where active == true - - -[[version patch]; ['1.0.0' false] ['3.0.1' true] ['2.0.0' false]] | shuffle - - -"There are seven words in this sentence" | size -'今天天气真好' | size -"Amélie Amelie" | size - - -[2 4 6 8] | skip 1 -[[editions]; [2015] [2018] [2021]] | skip 2 - - -[-2 0 2 -1] | skip until {|x| $x > 0 } -let cond = {|x| $x > 0 }; [-2 0 2 -1] | skip until $cond -[{a: -2} {a: 0} {a: 2} {a: -1}] | skip until {|x| $x.a > 0 } - - -[-2 0 2 -1] | skip while {|x| $x < 0 } -let cond = {|x| $x < 0 }; [-2 0 2 -1] | skip while $cond -[{a: -2} {a: 0} {a: 2} {a: -1}] | skip while {|x| $x.a < 0 } - - -sleep 1sec - - -[2 0 1] | sort -[2 0 1] | sort -r -[betty amy sarah] | sort -[betty amy sarah] | sort -r -[airplane Truck Car] | sort -i -[airplane Truck Car] | sort -i -r -{b: 3, a: 4} | sort -{b: 4, a: 3, c:1} | sort -v - - -ls | sort-by modified -ls | sort-by name -i -[[fruit count]; [apple 9] [pear 3] [orange 7]] | sort-by fruit -r - - -source foo.nu -source ./foo.nu; say-hi - - -source-env foo.nu - - - - -'hello' | split chars -'🇯🇵ほげ' | split chars -g - - -'a--b--c' | split column '--' -'abc' | split column -c '' -['a-b' 'c-d'] | split column - -['a - b' 'c - d'] | split column -r '\s*-\s*' - - -[a, b, c, d, e, f, g] | split list d -[[1,2], [2,3], [3,4]] | split list [2,3] -[a, b, c, d, a, e, f, g] | split list a -[a, b, c, d, a, e, f, g] | split list -r '(b|e)' - - -'abc' | split row '' -'a--b--c' | split row '--' -'-a-b-c-' | split row '-' -'a b c' | split row -r '\s+' - - -'hello world' | split words -'hello to the world' | split words -l 3 -http get https://www.gutenberg.org/files/11/11-0.txt | str downcase | split words -l 2 | uniq -c | sort-by count --reverse | first 10 - - -{ + mkdir foo bar + enter foo + enter ../bar + p + p + [1, 2, 3] | par-each {|| 2 * $in } + [foo, bar, baz] | par-each {|e| $e + '!' } | sort + 1..3 | enumerate | par-each {|p| update item ($p.item * 2)} | sort-by item | get item + [1, 2, 3] | enumerate | par-each { |e| if $e.item == 2 { $"found 2 at ($e.index)!"} } + "hi there" | parse "{foo} {bar}" + "hi there" | parse -r '(?P\w+) (?P\w+)' + "foo bar." | parse -r '\s*(?\w+)(?=\.)' + "foo! bar." | parse -r '(\w+)(?=\.)|(\w+)(?=!)' + " @another(foo bar) " | parse -r '\s*(?<=[() ])(@\w+)(\([^)]*\))?\s*' + "abcd" | parse -r '^a(bc(?=d)|b)cd$' + '/home/joe/test.txt' | path basename + [[name]; [/home/joe]] | path basename -c [name] + '/home/joe/test.txt' | path basename -r 'spam.png' + '/home/joe/code/test.txt' | path dirname + ls ('.' | path expand) | path dirname -c [name] + '/home/joe/code/test.txt' | path dirname -n 2 + '/home/joe/code/test.txt' | path dirname -n 2 -r /home/viking + '/home/joe/todo.txt' | path exists + ls | path exists -c [name] + '/home/joe/foo/../bar' | path expand + ls | path expand -c [name] + 'foo/../bar' | path expand + '/home/viking' | path join spam.txt + '/home/viking' | path join spams this_spam.txt + ls | path join spam.txt -c [name] + ['/', 'home', 'viking', 'spam.txt'] | path join + [[parent, stem, extension]; ['/home/viking', 'spam', 'txt']] | path join + '/home/viking/spam.txt' | path parse + '/home/viking/spam.tar.gz' | path parse -e tar.gz | upsert extension { 'txt' } + '/etc/conf.d' | path parse -e '' + ls | path parse -c [name] + '/home/viking' | path relative-to '/home' + ls ~ | path relative-to ~ -c [name] + 'eggs/bacon/sausage/spam' | path relative-to 'eggs/bacon/sausage' + '/home/viking/spam.txt' | path split + ls ('.' | path expand) | path split -c [name] + '.' | path type + ls | path type -c [name] + pnet + port 3121 4000 + port + [1, 2, 3, 4] | prepend 0 + [2, 3, 4] | prepend [0, 1] + [2, nu, 4, shell] | prepend [0, 1, rocks] + print "hello world" + print (2 + 3) + def spam [] { "spam" } + profile {|| spam | str length } -d 2 --source + ps + ps | sort-by mem | last 5 + ps | sort-by cpu | last 3 + ps | where name =~ 'nu' + ps | where pid == $nu.pid | get ppid + open foo.db | query db "SELECT * FROM Bar" + http get https://phoronix.com | query web -q 'header' + http get https://en.wikipedia.org/wiki/List_of_cities_in_India_by_population | query web -t [Rank, City, 'Population(2011)[3]', 'Population(2001)', 'State or union territory'] + http get https://www.nushell.sh | query web -q 'h2, h2 + p' | group 2 | each {rotate --ccw tagline description} | flatten + http get https://example.org | query web --query a --attribute href + random bool + random bool --bias 0.75 + random chars + random chars -l 20 + random decimal + random decimal ..500 + random decimal 100000.. + random decimal 1.0..1.1 + random dice + random dice -d 10 -s 12 + random integer + random integer ..500 + random integer 100000.. + random integer 1..10 + random uuid + [ + 0 + 1 + 2 + 3 + 4 + 5 + ] | range 4..5 + [ + 0 + 1 + 2 + 3 + 4 + 5 + ] | range (-2).. + [ + 0 + 1 + 2 + 3 + 4 + 5 + ] | range (-3)..-2 + [1, 2, 3, 4] | reduce {|it, acc| $it + $acc } + [8, 7, 6] | enumerate | reduce -f 0 {|it, acc| $acc + $it.item + $it.index } + [1, 2, 3, 4] | reduce -f 10 {|it, acc| $acc + $it } + [i, o, t] | reduce -f "Arthur, King of the Britons" {|it, acc| $acc | str replace -a $it "X" } + ['foo.gz', 'bar.gz', 'baz.gz'] | enumerate | reduce -f '' {|str all| $"($all)(if $str.index != 0 {'; '})($str.index + 1)-($str.item)" } + "hello world" | regex '(?P\w+) (?P\w+)' + register ~/.cargo/bin/nu_plugin_query + let plugin = ((which nu).path.0 | path dirname | path join 'nu_plugin_query') + nu -c $'register ($plugin); version' + ls | reject modified + [[a, b]; [1, 2]] | reject a + {a: 1, b: 2} | reject a + { + a: {b: 3, c: 5} + } | reject a.b + [[a, b]; [1, 2]] | rename my_column + [[a, b, c]; [1, 2, 3]] | rename eggs ham bacon + [[a, b, c]; [1, 2, 3]] | rename -c [a, ham] + {a: 1, b: 2} | rename x y + def foo [] { return } + [0, 1, 2, 3] | reverse + [ + {a: 1} + {a: 2} + ] | reverse + rm file.txt + rm --trash file.txt + rm --permanent file.txt + rm --force file.txt + ls | where size == 0KB and type == file | each { rm $in.name } | null + [[a, b]; [1, 2], [3, 4], [5, 6]] | roll down + {a: 1, b: 2, c: 3} | roll left + [[a, b, c]; [1, 2, 3], [4, 5, 6]] | roll left + [[a, b, c]; [1, 2, 3], [4, 5, 6]] | roll left --cells-only + {a: 1, b: 2, c: 3} | roll right + [[a, b, c]; [1, 2, 3], [4, 5, 6]] | roll right + [[a, b, c]; [1, 2, 3], [4, 5, 6]] | roll right --cells-only + [[a, b]; [1, 2], [3, 4], [5, 6]] | roll up + {a: 1, b: 2} | rotate + [[a, b]; [1, 2], [3, 4], [5, 6]] | rotate + [[a, b]; [1, 2]] | rotate col_a col_b + [[a, b]; [1, 2]] | rotate --ccw + [[a, b]; [1, 2], [3, 4], [5, 6]] | rotate --ccw + [[a, b]; [1, 2]] | rotate --ccw col_a col_b + run-external "echo" "-n" "hello" + run-external --redirect-stdout "echo" "-n" "hello" | split chars + 'save me' | save foo.txt + 'append me' | save --append foo.txt + {a: 1, b: 2} | save foo.json + do -i { } | save foo.txt --stderr foo.txt + do -i { } | save foo.txt --stderr bar.txt + open foo.db | schema + [ + {a: a, b: b} + ] | select a + {a: a, b: b} | select a + ls | select name + ls | select 0 1 2 3 + seq 1 10 + seq 1.0 0.1 2.0 + seq 1 5 | str join '|' + seq char a e + seq char a e | str join '|' + seq date --days 10 + seq date --days 10 -r + seq date --days 10 -o '%m/%d/%Y' -r + seq date -b '2020-01-01' -e '2020-01-10' + seq date -b '2020-01-01' -e '2020-01-31' -n 5 + enter .. + shells + shells | where active == true + [[version, patch]; ['1.0.0', false], ['3.0.1', true], ['2.0.0', false]] | shuffle + "There are seven words in this sentence" | size + '今天天气真好' | size + "Amélie Amelie" | size + [2, 4, 6, 8] | skip 1 + [[editions]; [2015], [2018], [2021]] | skip 2 + [-2, 0, 2, -1] | skip until {|x| $x > 0 } + let cond = {|x| $x > 0 } + [-2, 0, 2, -1] | skip until $cond + [ + {a: -2} + {a: 0} + {a: 2} + {a: -1} + ] | skip until {|x| $x.a > 0 } + [-2, 0, 2, -1] | skip while {|x| $x < 0 } + let cond = {|x| $x < 0 } + [-2, 0, 2, -1] | skip while $cond + [ + {a: -2} + {a: 0} + {a: 2} + {a: -1} + ] | skip while {|x| $x.a < 0 } + sleep 1sec + [2, 0, 1] | sort + [2, 0, 1] | sort -r + [betty, amy, sarah] | sort + [betty, amy, sarah] | sort -r + [airplane, Truck, Car] | sort -i + [airplane, Truck, Car] | sort -i -r + {b: 3, a: 4} | sort + {b: 4, a: 3, c: 1} | sort -v + ls | sort-by modified + ls | sort-by name -i + [[fruit, count]; [apple, 9], [pear, 3], [orange, 7]] | sort-by fruit -r + source foo.nu + source ./foo.nu + say-hi + source-env foo.nu + 'hello' | split chars + '🇯🇵ほげ' | split chars -g + 'a--b--c' | split column '--' + 'abc' | split column -c '' + ['a-b', 'c-d'] | split column - + ['a - b', 'c - d'] | split column -r '\s*-\s*' + [ + a + b + c + d + e + f + g + ] | split list d + [ + [1, 2] + [2, 3] + [3, 4] + ] | split list [2, 3] + [ + a + b + c + d + a + e + f + g + ] | split list a + [ + a + b + c + d + a + e + f + g + ] | split list -r '(b|e)' + 'abc' | split row '' + 'a--b--c' | split row '--' + '-a-b-c-' | split row '-' + 'a b c' | split row -r '\s+' + 'hello world' | split words + 'hello to the world' | split words -l 3 + http get https://www.gutenberg.org/files/11/11-0.txt | str downcase | split words -l 2 | uniq -c | sort-by count --reverse | first 10 + { '2019': [ - { name: 'andres', lang: 'rb', year: '2019' }, - { name: 'jt', lang: 'rs', year: '2019' } - ], + {name: 'andres', lang: 'rb', year: '2019'} + {name: 'jt', lang: 'rs', year: '2019'} + ] '2021': [ - { name: 'storm', lang: 'rs', 'year': '2021' } + {name: 'storm', lang: 'rs', 'year': '2021'} ] } | split-by lang - - -start file.txt -start file.jpg -start . -start file.pdf -start https://www.nushell.sh - - - - - 'NuShell' | str camel-case -'this-is-the-first-case' | str camel-case - 'this_is_the_second_case' | str camel-case -[[lang, gems]; [nu_test, 100]] | str camel-case lang - - -'good day' | str capitalize -'anton' | str capitalize -[[lang, gems]; [nu_test, 100]] | str capitalize lang - - - - -'my_library.rb' | str contains '.rb' -'my_library.rb' | str contains -i '.RB' - [[ColA ColB]; [test 100]] | str contains 'e' ColA - [[ColA ColB]; [test 100]] | str contains -i 'E' ColA - [[ColA ColB]; [test hello]] | str contains 'e' ColA ColB -'hello' | str contains 'banana' -[one two three] | str contains o -[one two three] | str contains -n o - - -'nushell' | str distance 'nutshell' -[{a: 'nutshell' b: 'numetal'}] | str distance 'nushell' 'a' 'b' - - -'NU' | str downcase -'TESTa' | str downcase -[[ColA ColB]; [Test ABC]] | str downcase ColA -[[ColA ColB]; [Test ABC]] | str downcase ColA ColB - - -'my_library.rb' | str ends-with '.rb' -'my_library.rb' | str ends-with '.txt' -'my_library.rb' | str ends-with -i '.RB' - - - - - 'my_library.rb' | str index-of '.rb' -'🇯🇵ほげ ふが ぴよ' | str index-of -g 'ふが' - '.rb.rb' | str index-of '.rb' -r 1.. - '123456' | str index-of '6' -r ..4 - '123456' | str index-of '3' -r 1..4 - '/this/is/some/path/file.txt' | str index-of '/' -e - - -['nu', 'shell'] | str join -['nu', 'shell'] | str join '-' - - -'NuShell' | str kebab-case -'thisIsTheFirstCase' | str kebab-case -'THIS_IS_THE_SECOND_CASE' | str kebab-case -[[lang, gems]; [nuTest, 100]] | str kebab-case lang - - -'hello' | str length -'🇯🇵ほげ ふが ぴよ' | str length -g -['hi' 'there'] | str length - - - - -'nu-shell' | str pascal-case -'this-is-the-first-case' | str pascal-case -'this_is_the_second_case' | str pascal-case -[[lang, gems]; [nu_test, 100]] | str pascal-case lang - - -'my_library.rb' | str replace '(.+).rb' '$1.nu' -'abc abc abc' | str replace -a 'b' 'z' -[[ColA ColB ColC]; [abc abc ads]] | str replace -a 'b' 'z' ColA ColC -'dogs_$1_cats' | str replace '\$1' '$2' -n -'c:\some\cool\path' | str replace 'c:\some\cool' '~' -s -'abc abc abc' | str replace -a 'b' 'z' -s -'a successful b' | str replace '\b([sS])uc(?:cs|s?)e(ed(?:ed|ing|s?)|ss(?:es|ful(?:ly)?|i(?:ons?|ve(?:ly)?)|ors?)?)\b' '${1}ucce$2' -'GHIKK-9+*' | str replace '[*[:xdigit:]+]' 'z' - - -'Nushell' | str reverse -['Nushell' 'is' 'cool'] | str reverse - - - - - "NuShell" | str screaming-snake-case - "this_is_the_second_case" | str screaming-snake-case -"this-is-the-first-case" | str screaming-snake-case -[[lang, gems]; [nu_test, 100]] | str screaming-snake-case lang - - - "NuShell" | str snake-case - "this_is_the_second_case" | str snake-case -"this-is-the-first-case" | str snake-case -[[lang, gems]; [nuTest, 100]] | str snake-case lang - - -'my_library.rb' | str starts-with 'my' -'Cargo.toml' | str starts-with 'Car' -'Cargo.toml' | str starts-with '.toml' -'Cargo.toml' | str starts-with -i 'cargo' - - - 'good nushell' | str substring 5..12 - '🇯🇵ほげ ふが ぴよ' | str substring -g 4..6 - - -'nu-shell' | str title-case -'this is a test case' | str title-case -[[title, count]; ['nu test', 100]] | str title-case title - - - - - - - - -'Nu shell ' | str trim -'=== Nu shell ===' | str trim -c '=' | str trim -' Nu shell ' | str trim -l -'=== Nu shell ===' | str trim -c '=' -' Nu shell ' | str trim -r -'=== Nu shell ===' | str trim -r -c '=' - - -'nu' | str upcase - - -sys -(sys).host | get name -(sys).host.name - - -ls | table -n 1 -[[a b]; [1 2] [3 4]] | table -[[a b]; [1 2] [2 [4 4]]] | table --expand -[[a b]; [1 2] [2 [4 4]]] | table --collapse - - -[1 2 3] | take 1 -[1 2 3] | take 2 -[[editions]; [2015] [2018] [2021]] | take 2 -0x[01 23 45] | take 2 -1..10 | take 3 - - -[-1 -2 9 1] | take until {|x| $x > 0 } -let cond = {|x| $x > 0 }; [-1 -2 9 1] | take until $cond -[{a: -1} {a: -2} {a: 9} {a: 1}] | take until {|x| $x.a > 0 } - - -[-1 -2 9 1] | take while {|x| $x < 0 } -let cond = {|x| $x < 0 }; [-1 -2 9 1] | take while $cond -[{a: -1} {a: -2} {a: 9} {a: 1}] | take while {|x| $x.a < 0 } - - -term size -(term size).columns -(term size).rows - - -timeit { sleep 500ms } -http get https://www.nushell.sh/book/ | timeit { split chars } -timeit ls -la - - - - -[[foo bar]; [1 2]] | to csv -[[foo bar]; [1 2]] | to csv -s ';' -{a: 1 b: 2} | to csv - - -[[foo bar]; [1 2]] | to html -[[foo bar]; [1 2]] | to html --partial -[[foo bar]; [1 2]] | to html --dark - - -[a b c] | to json -[Joe Bob Sam] | to json -i 4 -[1 2 3] | to json -r - - -[[foo bar]; [1 2]] | to md -[[foo bar]; [1 2]] | to md --pretty -[{"H1": "Welcome to Nushell" } [[foo bar]; [1 2]]] | to md --per-element --pretty -[0 1 2] | to md --pretty - - -[1 2 3] | to nuon -[1 2 3] | to nuon --indent 2 -[1 2 3] | to nuon --indent 2 --raw -{date: 2000-01-01, data: [1 [2 3] 4.56]} | to nuon --indent 2 - - -1 | to text -git help -a | lines | find -r '^ ' | to text -ls | to text - - -{foo: 1 bar: 'qwe'} | to toml - - -[[foo bar]; [1 2]] | to tsv -{a: 1 b: 2} | to tsv - - -{tag: note attributes: {} content : [{tag: remember attributes: {} content : [{tag: null attrs: null content : Event}]}]} | to xml -{tag: note content : [{tag: remember content : [Event]}]} | to xml -{tag: note content : [{tag: remember content : [Event]}]} | to xml -p 3 - - -[[foo bar]; ["1" "2"]] | to yaml - - -touch fixture.json -touch a b c -touch -m fixture.json -touch -m -d "yesterday" a b c -touch -m -r fixture.json d e -touch -a -d "August 24, 2019; 12:30:30" fixture.json - - -[[c1 c2]; [1 2]] | transpose -[[c1 c2]; [1 2]] | transpose key val -[[c1 c2]; [1 2]] | transpose -i val -{c1: 1, c2: 2} | transpose | transpose -i -r -d - - -try { asdfasdf } -try { asdfasdf } catch { echo 'missing' } - - -tutor begin -tutor -f "$in" - - -[2 3 3 4] | uniq -[1 2 2] | uniq -d -[1 2 2] | uniq -u -['hello' 'goodbye' 'Hello'] | uniq -i -[1 2 2] | uniq -c - - -[[fruit count]; [apple 9] [apple 2] [pear 3] [orange 7]] | uniq-by fruit - - -{'name': 'nu', 'stars': 5} | update name 'Nushell' -[[count fruit]; [1 'apple']] | enumerate | update item.count {|e| ($e.item.fruit | str length) + $e.index } | get item -[[project, authors]; ['nu', ['Andrés', 'JT', 'Yehuda']]] | update authors {|row| $row.authors | str join ','} -[[project, authors]; ['nu', ['Andrés', 'JT', 'Yehuda']]] | update authors {|| str join ','} - - -[ - ["2021-04-16", "2021-06-10", "2021-09-18", "2021-10-15", "2021-11-16", "2021-11-17", "2021-11-18"]; - [ 37, 0, 0, 0, 37, 0, 0] - ] | update cells { |value| + start file.txt + start file.jpg + start . + start file.pdf + start https://www.nushell.sh + 'NuShell' | str camel-case + 'this-is-the-first-case' | str camel-case + 'this_is_the_second_case' | str camel-case + [[lang, gems]; [nu_test, 100]] | str camel-case lang + 'good day' | str capitalize + 'anton' | str capitalize + [[lang, gems]; [nu_test, 100]] | str capitalize lang + 'my_library.rb' | str contains '.rb' + 'my_library.rb' | str contains -i '.RB' + [[ColA, ColB]; [test, 100]] | str contains 'e' ColA + [[ColA, ColB]; [test, 100]] | str contains -i 'E' ColA + [[ColA, ColB]; [test, hello]] | str contains 'e' ColA ColB + 'hello' | str contains 'banana' + [one, two, three] | str contains o + [one, two, three] | str contains -n o + 'nushell' | str distance 'nutshell' + [ + {a: 'nutshell', b: 'numetal'} + ] | str distance 'nushell' 'a' 'b' + 'NU' | str downcase + 'TESTa' | str downcase + [[ColA, ColB]; [Test, ABC]] | str downcase ColA + [[ColA, ColB]; [Test, ABC]] | str downcase ColA ColB + 'my_library.rb' | str ends-with '.rb' + 'my_library.rb' | str ends-with '.txt' + 'my_library.rb' | str ends-with -i '.RB' + 'my_library.rb' | str index-of '.rb' + '🇯🇵ほげ ふが ぴよ' | str index-of -g 'ふが' + '.rb.rb' | str index-of '.rb' -r 1.. + '123456' | str index-of '6' -r ..4 + '123456' | str index-of '3' -r 1..4 + '/this/is/some/path/file.txt' | str index-of '/' -e + ['nu', 'shell'] | str join + ['nu', 'shell'] | str join '-' + 'NuShell' | str kebab-case + 'thisIsTheFirstCase' | str kebab-case + 'THIS_IS_THE_SECOND_CASE' | str kebab-case + [[lang, gems]; [nuTest, 100]] | str kebab-case lang + 'hello' | str length + '🇯🇵ほげ ふが ぴよ' | str length -g + ['hi', 'there'] | str length + 'nu-shell' | str pascal-case + 'this-is-the-first-case' | str pascal-case + 'this_is_the_second_case' | str pascal-case + [[lang, gems]; [nu_test, 100]] | str pascal-case lang + 'my_library.rb' | str replace '(.+).rb' '$1.nu' + 'abc abc abc' | str replace -a 'b' 'z' + [[ColA, ColB, ColC]; [abc, abc, ads]] | str replace -a 'b' 'z' ColA ColC + 'dogs_$1_cats' | str replace '\$1' '$2' -n + 'c:\some\cool\path' | str replace 'c:\some\cool' '~' -s + 'abc abc abc' | str replace -a 'b' 'z' -s + 'a successful b' | str replace '\b([sS])uc(?:cs|s?)e(ed(?:ed|ing|s?)|ss(?:es|ful(?:ly)?|i(?:ons?|ve(?:ly)?)|ors?)?)\b' '${1}ucce$2' + 'GHIKK-9+*' | str replace '[*[:xdigit:]+]' 'z' + 'Nushell' | str reverse + ['Nushell', 'is', 'cool'] | str reverse + "NuShell" | str screaming-snake-case + "this_is_the_second_case" | str screaming-snake-case + "this-is-the-first-case" | str screaming-snake-case + [[lang, gems]; [nu_test, 100]] | str screaming-snake-case lang + "NuShell" | str snake-case + "this_is_the_second_case" | str snake-case + "this-is-the-first-case" | str snake-case + [[lang, gems]; [nuTest, 100]] | str snake-case lang + 'my_library.rb' | str starts-with 'my' + 'Cargo.toml' | str starts-with 'Car' + 'Cargo.toml' | str starts-with '.toml' + 'Cargo.toml' | str starts-with -i 'cargo' + 'good nushell' | str substring 5..12 + '🇯🇵ほげ ふが ぴよ' | str substring -g 4..6 + 'nu-shell' | str title-case + 'this is a test case' | str title-case + [[title, count]; ['nu test', 100]] | str title-case title + 'Nu shell ' | str trim + '=== Nu shell ===' | str trim -c '=' | str trim + ' Nu shell ' | str trim -l + '=== Nu shell ===' | str trim -c '=' + ' Nu shell ' | str trim -r + '=== Nu shell ===' | str trim -r -c '=' + 'nu' | str upcase + sys + (sys).host | get name + (sys).host.name + ls | table -n 1 + [[a, b]; [1, 2], [3, 4]] | table + [[a, b]; [1, 2], [2, [4, 4]]] | table --expand + [[a, b]; [1, 2], [2, [4, 4]]] | table --collapse + [1, 2, 3] | take 1 + [1, 2, 3] | take 2 + [[editions]; [2015], [2018], [2021]] | take 2 + 0x[01 23 45] | take 2 + 1..10 | take 3 + [-1, -2, 9, 1] | take until {|x| $x > 0 } + let cond = {|x| $x > 0 } + [-1, -2, 9, 1] | take until $cond + [ + {a: -1} + {a: -2} + {a: 9} + {a: 1} + ] | take until {|x| $x.a > 0 } + [-1, -2, 9, 1] | take while {|x| $x < 0 } + let cond = {|x| $x < 0 } + [-1, -2, 9, 1] | take while $cond + [ + {a: -1} + {a: -2} + {a: 9} + {a: 1} + ] | take while {|x| $x.a < 0 } + term size + (term size).columns + (term size).rows + timeit { sleep 500ms } + http get https://www.nushell.sh/book/ | timeit { split chars } + timeit ls -la + [[foo, bar]; [1, 2]] | to csv + [[foo, bar]; [1, 2]] | to csv -s ';' + {a: 1, b: 2} | to csv + [[foo, bar]; [1, 2]] | to html + [[foo, bar]; [1, 2]] | to html --partial + [[foo, bar]; [1, 2]] | to html --dark + [a, b, c] | to json + [Joe, Bob, Sam] | to json -i 4 + [1, 2, 3] | to json -r + [[foo, bar]; [1, 2]] | to md + [[foo, bar]; [1, 2]] | to md --pretty + [ + {"H1": "Welcome to Nushell"} + [[foo, bar]; [1, 2]] + ] | to md --per-element --pretty + [0, 1, 2] | to md --pretty + [1, 2, 3] | to nuon + [1, 2, 3] | to nuon --indent 2 + [1, 2, 3] | to nuon --indent 2 --raw + { + date: 2000-01-01 + data: [ + 1 + [2, 3] + 4.56 + ] + } | to nuon --indent 2 + 1 | to text + git help -a | lines | find -r '^ ' | to text + ls | to text + {foo: 1, bar: 'qwe'} | to toml + [[foo, bar]; [1, 2]] | to tsv + {a: 1, b: 2} | to tsv + { + tag: note + attributes: {} + content: [ + { + tag: remember + attributes: {} + content: [ + {tag: null, attrs: null, content: Event} + ] + } + ] + } | to xml + { + tag: note + content: [ + { + tag: remember + content: [Event] + } + ] + } | to xml + { + tag: note + content: [ + { + tag: remember + content: [Event] + } + ] + } | to xml -p 3 + [[foo, bar]; ["1", "2"]] | to yaml + touch fixture.json + touch a b c + touch -m fixture.json + touch -m -d "yesterday" a b c + touch -m -r fixture.json d e + touch -a -d "August 24, 2019; 12:30:30" fixture.json + [[c1, c2]; [1, 2]] | transpose + [[c1, c2]; [1, 2]] | transpose key val + [[c1, c2]; [1, 2]] | transpose -i val + {c1: 1, c2: 2} | transpose | transpose -i -r -d + try { asdfasdf } + try { asdfasdf } catch { echo 'missing' } + tutor begin + tutor -f "$in" + [2, 3, 3, 4] | uniq + [1, 2, 2] | uniq -d + [1, 2, 2] | uniq -u + ['hello', 'goodbye', 'Hello'] | uniq -i + [1, 2, 2] | uniq -c + [[fruit, count]; [apple, 9], [apple, 2], [pear, 3], [orange, 7]] | uniq-by fruit + {'name': 'nu', 'stars': 5} | update name 'Nushell' + [[count, fruit]; [1, 'apple']] | enumerate | update item.count {|e| ($e.item.fruit | str length) + $e.index } | get item + [[project, authors]; ['nu', ['Andrés', 'JT', 'Yehuda']]] | update authors {|row| $row.authors | str join ','} + [[project, authors]; ['nu', ['Andrés', 'JT', 'Yehuda']]] | update authors {|| str join ','} + [["2021-04-16", "2021-06-10", "2021-09-18", "2021-10-15", "2021-11-16", "2021-11-17", "2021-11-18"]; [37, 0, 0, 0, 37, 0, 0]] | update cells { |value| if $value == 0 { "" } else { $value } } -[ - ["2021-04-16", "2021-06-10", "2021-09-18", "2021-10-15", "2021-11-16", "2021-11-17", "2021-11-18"]; - [ 37, 0, 0, 0, 37, 0, 0] - ] | update cells -c ["2021-11-18", "2021-11-17"] { |value| + [["2021-04-16", "2021-06-10", "2021-09-18", "2021-10-15", "2021-11-16", "2021-11-17", "2021-11-18"]; [37, 0, 0, 0, 37, 0, 0]] | update cells -c ["2021-11-18", "2021-11-17"] { |value| if $value == 0 { "" } else { $value } } - - -{'name': 'nu', 'stars': 5} | upsert name 'Nushell' -[[name lang]; [Nushell ''] [Reedline '']] | upsert lang 'Rust' -{'name': 'nu', 'stars': 5} | upsert language 'Rust' -[[count fruit]; [1 'apple']] | enumerate | upsert item.count {|e| ($e.item.fruit | str length) + $e.index } | get item -[1 2 3] | upsert 0 2 -[1 2 3] | upsert 3 4 - - - - -{ mode:normal userid:31415 } | url build-query -[[foo bar]; ["1" "2"]] | url build-query -{a:"AT&T", b: "AT T"} | url build-query - - -'https://example.com/foo bar' | url encode -['https://example.com/foo bar' 'https://example.com/a>b' '中文字/eng/12 34'] | url encode -'https://example.com/foo bar' | url encode --all - - -{ - "scheme": "http", - "username": "", - "password": "", - "host": "www.pixiv.net", - "port": "", - "path": "/member_illust.php", - "query": "mode=medium&illust_id=99260204", - "fragment": "", - "params": - { - "mode": "medium", - "illust_id": "99260204" - } + {'name': 'nu', 'stars': 5} | upsert name 'Nushell' + [[name, lang]; [Nushell, ''], [Reedline, '']] | upsert lang 'Rust' + {'name': 'nu', 'stars': 5} | upsert language 'Rust' + [[count, fruit]; [1, 'apple']] | enumerate | upsert item.count {|e| ($e.item.fruit | str length) + $e.index } | get item + [1, 2, 3] | upsert 0 2 + [1, 2, 3] | upsert 3 4 + {mode: normal, userid: 31415} | url build-query + [[foo, bar]; ["1", "2"]] | url build-query + {a: "AT&T", b: "AT T"} | url build-query + 'https://example.com/foo bar' | url encode + ['https://example.com/foo bar', 'https://example.com/a>b', '中文字/eng/12 34'] | url encode + 'https://example.com/foo bar' | url encode --all + { + "scheme": "http" + "username": "" + "password": "" + "host": "www.pixiv.net" + "port": "" + "path": "/member_illust.php" + "query": "mode=medium&illust_id=99260204" + "fragment": "" + "params": {"mode": "medium", "illust_id": "99260204"} } | url join -{ - "scheme": "http", - "username": "user", - "password": "pwd", - "host": "www.pixiv.net", - "port": "1234", - "query": "test=a", + { + "scheme": "http" + "username": "user" + "password": "pwd" + "host": "www.pixiv.net" + "port": "1234" + "query": "test=a" "fragment": "" } | url join -{ - "scheme": "http", - "host": "www.pixiv.net", - "port": "1234", - "path": "user", + { + "scheme": "http" + "host": "www.pixiv.net" + "port": "1234" + "path": "user" "fragment": "frag" } | url join - - -'http://user123:pass567@www.example.com:8081/foo/bar?param1=section&p2=&f[name]=vldc#hello' | url parse - - -module spam { export def foo [] { "foo" } }; use spam foo; foo -module foo { export def-env bar [] { let-env FOO_BAR = "BAZ" } }; use foo bar; bar; $env.FOO_BAR - - -{ mode:normal userid:31415 } | values -{ f:250 g:191 c:128 d:1024 e:2000 a:16 b:32 } | values -[[name meaning]; [ls list] [mv move] [cd 'change directory']] | values - - -version - - - - -view files - - -let abc = {|| echo 'hi' }; view source $abc -def hi [] { echo 'Hi!' }; view source hi -def-env foo [] { let-env BAR = 'BAZ' }; view source foo -module mod-foo { export-env { let-env FOO_ENV = 'BAZ' } }; view source mod-foo -alias hello = echo hi; view source hello - - -some | pipeline | or | variable | debug -r; view span 1 2 - - -watch . --glob=**/*.rs {|| cargo test } -watch . { |op, path, new_path| $"($op) ($path) ($new_path)"} -watch /foo/bar { |op, path| $"($op) - ($path)(char nl)" | save --append changes_in_bar.log } - - -[{a: 1} {a: 2}] | where a > 1 -[1 2] | where {|x| $x > 1} -ls | where size > 2kb -ls | where type == file -ls | where name =~ "Car" -ls | where modified >= (date now) - 2wk -ls | where type == file | sort-by name -n | enumerate | where {|e| $e.item.name !~ $'^($e.index + 1)' } | each {|| get item } - - -which myapp - - -mut x = 0; while $x < 10 { $x = $x + 1 } - - -[1 2 3 4] | window 2 -[1, 2, 3, 4, 5, 6, 7, 8] | window 2 --stride 3 -[1, 2, 3, 4, 5] | window 3 --stride 3 --remainder - - -with-env [MYENV "my env value"] { $env.MYENV } -with-env [X Y W Z] { $env.X } -with-env [[X W]; [Y Z]] { $env.W } -with-env {X: "Y", W: "Z"} { [$env.X $env.W] } - - -[1 2 3] | wrap num -1..3 | wrap num - - - - -[1 2] | zip [3 4] -1..3 | zip 4..6 -glob *.ogg | zip ['bang.ogg', 'fanfare.ogg', 'laser.ogg'] | each {|| mv $in.0 $in.1 } - - + 'http://user123:pass567@www.example.com:8081/foo/bar?param1=section&p2=&f[name]=vldc#hello' | url parse + module spam { + export def foo [] { "foo" } + } + use spam foo + foo + module foo { export def-env bar [] { let-env FOO_BAR = "BAZ" } } + use foo bar + bar + $env.FOO_BAR + {mode: normal, userid: 31415} | values + { + f: 250 + g: 191 + c: 128 + d: 1024 + e: 2000 + a: 16 + b: 32 + } | values + [[name, meaning]; [ls, list], [mv, move], [cd, 'change directory']] | values + version + view files + let abc = {|| echo 'hi' } + view source $abc + def hi [] { echo 'Hi!' } + view source hi + def-env foo [] { let-env BAR = 'BAZ' } + view source foo + module mod-foo { export-env { let-env FOO_ENV = 'BAZ' } } + view source mod-foo + alias hello = echo hi + view source hello + some | pipeline | or | variable | debug -r + view span 1 2 + watch . --glob=**/*.rs {|| cargo test } + watch . { |op, path, new_path| $"($op) ($path) ($new_path)"} + watch /foo/bar { |op, path| $"($op) - ($path)(char nl)" | save --append changes_in_bar.log } + [ + {a: 1} + {a: 2} + ] | where a > 1 + [1, 2] | where {|x| $x > 1} + ls | where size > 2kb + ls | where type == file + ls | where name =~ "Car" + ls | where modified >= (date now) - 2wk + ls | where type == file | sort-by name -n | enumerate | where {|e| $e.item.name !~ $'^($e.index + 1)' } | each {|| get item } + which myapp + mut x = 0 + while $x < 10 { $x = ($x + 1) } + [1, 2, 3, 4] | window 2 + [ + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + ] | window 2 --stride 3 + [1, 2, 3, 4, 5] | window 3 --stride 3 --remainder + with-env [MYENV, "my env value"] { $env.MYENV } + with-env [X, Y, W, Z] { $env.X } + with-env [ + [X, W] + ] { $env.W } + with-env {X: "Y", W: "Z"} { [$env.X $env.W] } + [1, 2, 3] | wrap num + 1..3 | wrap num + [1, 2] | zip [3, 4] + 1..3 | zip 4..6 + glob *.ogg | zip ['bang.ogg', 'fanfare.ogg', 'laser.ogg'] | each {|| mv $in.0 $in.1 } +) +) diff --git a/src/config.rs b/src/config.rs index b4cb598..1e54a19 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,6 +5,7 @@ use std::convert::TryFrom; use crate::config_error::ConfigError; use nu_protocol::Value; +/// Configuration options for the formatter #[derive(Debug, Clone, PartialEq, Eq)] pub struct Config { pub indent: usize, @@ -15,22 +16,22 @@ pub struct Config { impl Default for Config { fn default() -> Self { - Config { + Self { indent: 4, line_length: 80, margin: 1, - excludes: vec![], + excludes: Vec::new(), } } } impl Config { pub fn new(tab_spaces: usize, max_width: usize, margin: usize) -> Self { - Config { + Self { indent: tab_spaces, line_length: max_width, margin, - excludes: vec![], + excludes: Vec::new(), } } } @@ -40,83 +41,70 @@ impl TryFrom for Config { fn try_from(value: Value) -> Result { let mut config = Config::default(); - match value { - Value::Nothing { .. } => (), - Value::Record { val: record, .. } => { - for (key, value) in record.iter() { - match key.as_str() { - "indent" => { - let indent = parse_value_to_usize(key, value)?; - config.indent = indent; - } - "line_length" => { - let line_length = parse_value_to_usize(key, value)?; - config.line_length = line_length; - } - "margin" => { - let margin = parse_value_to_usize(key, value)?; - config.margin = margin; - } - "exclude" => { - let excludes = parse_excludes(value)?; - config.excludes = excludes; - } - unknown => return Err(ConfigError::UnknownOption(unknown.to_string())), - } - } - } - _ => { - return Err(ConfigError::InvalidFormat); + + let Value::Record { val: record, .. } = value else { + // Nothing means use defaults + if matches!(value, Value::Nothing { .. }) { + return Ok(config); } + return Err(ConfigError::InvalidFormat); }; + + for (key, value) in record.iter() { + match key.as_str() { + "indent" => config.indent = parse_positive_int(key, value)?, + "line_length" => config.line_length = parse_positive_int(key, value)?, + "margin" => config.margin = parse_positive_int(key, value)?, + "exclude" => config.excludes = parse_string_list(value)?, + unknown => return Err(ConfigError::UnknownOption(unknown.to_string())), + } + } + Ok(config) } } -fn parse_value_to_usize(key: &str, value: &Value) -> Result { - match value { - Value::Int { val, .. } => { - if *val <= 0 { - return Err(ConfigError::InvalidOptionValue( - key.to_string(), - format!("{}", val), - "a positive number", - )); - } - Ok(*val as usize) - } - other => Err(ConfigError::InvalidOptionType( +/// Parse a value as a positive integer (usize) +fn parse_positive_int(key: &str, value: &Value) -> Result { + let Value::Int { val, .. } = value else { + return Err(ConfigError::InvalidOptionType( key.to_string(), - other.get_type().to_string(), + value.get_type().to_string(), "number", - )), + )); + }; + + if *val <= 0 { + return Err(ConfigError::InvalidOptionValue( + key.to_string(), + val.to_string(), + "a positive number", + )); } + + Ok(*val as usize) } -fn parse_excludes(value: &Value) -> Result, ConfigError> { - match value { - Value::List { vals, .. } => { - let mut excludes = vec![]; - for val in vals { - match val { - Value::String { val, .. } => { - excludes.push(val.clone()); - } - other => { - return Err(ConfigError::InvalidOptionType( - "excludes".to_string(), - other.get_type().to_string(), - "list", - )); - } - } - } - Ok(excludes) - } - other => Err(ConfigError::InvalidOptionType( +/// Parse a value as a list of strings +fn parse_string_list(value: &Value) -> Result, ConfigError> { + let Value::List { vals, .. } = value else { + return Err(ConfigError::InvalidOptionType( "excludes".to_string(), - other.get_type().to_string(), + value.get_type().to_string(), "list", - )), - } + )); + }; + + vals.iter() + .map(|val| { + let Value::String { val, .. } = val else { + return Err(ConfigError::InvalidOptionType( + "excludes".to_string(), + val.get_type().to_string(), + "list", + )); + }; + Ok(val.clone()) + }) + .collect() } diff --git a/src/formatting.rs b/src/formatting.rs index 84397d2..6450df9 100644 --- a/src/formatting.rs +++ b/src/formatting.rs @@ -1,257 +1,1338 @@ //! In this module occurs most of the magic in `nufmt`. //! -//! It has functions to format slice of bytes and some help functions to separate concerns while doing the job. +//! It walks the Nushell AST and emits properly formatted code. + use crate::config::Config; use crate::format_error::FormatError; use log::{debug, trace}; -use nu_parser::{flatten_block, parse, FlatShape}; +use nu_parser::parse; use nu_protocol::{ - ast::Block, + ast::{ + Argument, Block, Expr, Expression, ExternalArgument, ListItem, MatchPattern, PathMember, + Pattern, Pipeline, PipelineElement, PipelineRedirection, RecordItem, RedirectionTarget, + }, engine::{EngineState, StateWorkingSet}, - DeclId, Span, + Signature, Span, SyntaxShape, }; -trait DeclsExt { - fn get_decl_name(&self, decl_id: usize) -> Option<&str>; +/// Commands that format their block arguments in a special way +const BLOCK_COMMANDS: &[&str] = &["for", "while", "loop", "module"]; +const CONDITIONAL_COMMANDS: &[&str] = &["if", "try"]; +const DEF_COMMANDS: &[&str] = &["def", "def-env", "export def"]; +const EXTERN_COMMANDS: &[&str] = &["extern"]; +const LET_COMMANDS: &[&str] = &["let", "let-env", "mut", "const"]; + +/// Get the default engine state with built-in commands +fn get_engine_state() -> EngineState { + nu_cmd_lang::create_default_context() +} + +/// The main formatter context that tracks indentation and other state +struct Formatter<'a> { + /// The original source bytes + source: &'a [u8], + /// The working set for looking up blocks and other data + working_set: &'a StateWorkingSet<'a>, + /// Configuration options + config: &'a Config, + /// Current indentation level + indent_level: usize, + /// Output buffer + output: Vec, + /// Track if we're at the start of a line (for indentation) + at_line_start: bool, + /// Comments extracted from source, indexed by their end position + comments: Vec<(Span, Vec)>, + /// Track which comments have been written + written_comments: Vec, + /// Current position in source being processed + last_pos: usize, } -impl DeclsExt for Vec<(Vec, DeclId)> { - fn get_decl_name(&self, decl_id: usize) -> Option<&str> { - for decl in self { - if decl_id == decl.1.get() { - return Some(std::str::from_utf8(&decl.0).expect("Failed to parse DeclId's name")); +impl<'a> Formatter<'a> { + fn new(source: &'a [u8], working_set: &'a StateWorkingSet<'a>, config: &'a Config) -> Self { + let comments = extract_comments(source); + let written_comments = vec![false; comments.len()]; + Self { + source, + working_set, + config, + indent_level: 0, + output: Vec::new(), + at_line_start: true, + comments, + written_comments, + last_pos: 0, + } + } + + // ───────────────────────────────────────────────────────────────────────────── + // Basic output methods + // ───────────────────────────────────────────────────────────────────────────── + + /// Write indentation if at start of line + fn write_indent(&mut self) { + if self.at_line_start { + let indent = " ".repeat(self.config.indent * self.indent_level); + self.output.extend(indent.as_bytes()); + self.at_line_start = false; + } + } + + /// Write a string to output + fn write(&mut self, s: &str) { + self.write_indent(); + self.output.extend(s.as_bytes()); + } + + /// Write bytes to output + fn write_bytes(&mut self, bytes: &[u8]) { + self.write_indent(); + self.output.extend(bytes); + } + + /// Write a newline + fn newline(&mut self) { + self.output.push(b'\n'); + self.at_line_start = true; + } + + /// Write a space if not at line start and not already following whitespace/opener + fn space(&mut self) { + if !self.at_line_start && !self.output.is_empty() { + if let Some(&last) = self.output.last() { + if !matches!(last, b' ' | b'\n' | b'\t' | b'(' | b'[') { + self.output.push(b' '); + } } } - None } -} -fn get_engine_state() -> EngineState { - nu_cmd_lang::create_default_context() -} + // ───────────────────────────────────────────────────────────────────────────── + // Span and source helpers + // ───────────────────────────────────────────────────────────────────────────── -/// format an array of bytes -/// -/// Reading the file gives you a list of bytes -pub(crate) fn format_inner(contents: &[u8], _config: &Config) -> Result, FormatError> { - let engine_state = get_engine_state(); - let decls_sorted: Vec<(Vec, nu_protocol::Id)> = - engine_state.get_decls_sorted(false); + /// Get the source content for a span (returns owned Vec to avoid borrow issues) + fn get_span_content(&self, span: Span) -> Vec { + self.source[span.start..span.end].to_vec() + } - let mut working_set = StateWorkingSet::new(&engine_state); + /// Write the original source content for a span + fn write_span(&mut self, span: Span) { + let content = self.source[span.start..span.end].to_vec(); + self.write_bytes(&content); + } - let parsed_block = parse(&mut working_set, None, contents, false); - trace!("parsed block:\n{:?}", &parsed_block); + /// Write the original source content for an expression's span + fn write_expr_span(&mut self, expr: &Expression) { + self.write_span(expr.span); + } - if !block_has_pipelines(&parsed_block) { - trace!("block has no pipelines!"); - debug!("File has no code to format."); - return Ok(contents.to_vec()); + // ───────────────────────────────────────────────────────────────────────────── + // Comment handling + // ───────────────────────────────────────────────────────────────────────────── + + /// Check if there are any comments between `last_pos` and the given position + fn write_comments_before(&mut self, pos: usize) { + let mut comments_to_write: Vec<_> = self + .comments + .iter() + .enumerate() + .filter(|(i, (span, _))| { + !self.written_comments[*i] && span.start >= self.last_pos && span.end <= pos + }) + .map(|(i, (span, content))| (i, span.start, content.clone())) + .collect(); + + comments_to_write.sort_by_key(|(_, start, _)| *start); + + for (idx, _, content) in comments_to_write { + self.written_comments[idx] = true; + if !self.at_line_start { + if let Some(&last) = self.output.last() { + if last != b'\n' { + self.newline(); + } + } + } + self.write_indent(); + self.output.extend(&content); + self.newline(); + } } - let flat = flatten_block(&working_set, &parsed_block); - trace!("flattened block:\n{:?}", &flat); + /// Check for inline comment after a position (on the same line) + fn write_inline_comment(&mut self, after_pos: usize) { + let line_end = self.source[after_pos..] + .iter() + .position(|&b| b == b'\n') + .map_or(self.source.len(), |p| after_pos + p); - let mut out: Vec = vec![]; - let mut start = 0; - let end_of_file = contents.len(); + let found = self + .comments + .iter() + .enumerate() + .find(|(i, (span, _))| { + !self.written_comments[*i] && span.start >= after_pos && span.start < line_end + }) + .map(|(i, (span, content))| (i, *span, content.clone())); - let mut after_a_def = false; + if let Some((idx, span, content)) = found { + self.written_comments[idx] = true; + self.write(" "); + self.output.extend(&content); + self.last_pos = span.end; + } + } - for (span, shape) in flat.clone() { - if span.start > start { - trace!( - "Span does not start at the beginning! span {0}, start: {1}", - span.start, - start - ); + // ───────────────────────────────────────────────────────────────────────────── + // Block and pipeline formatting + // ───────────────────────────────────────────────────────────────────────────── - let skipped_contents = &contents[start..span.start]; - let printable = String::from_utf8_lossy(skipped_contents).to_string(); - trace!("contents: {:?}", printable); + /// Format a block + fn format_block(&mut self, block: &Block) { + let num_pipelines = block.pipelines.len(); + for (i, pipeline) in block.pipelines.iter().enumerate() { + if let Some(first_elem) = pipeline.elements.first() { + self.write_comments_before(first_elem.expr.span.start); + } + + self.format_pipeline(pipeline); + + if let Some(last_elem) = pipeline.elements.last() { + let end_pos = self.get_element_end_pos(last_elem); + self.write_inline_comment(end_pos); + self.last_pos = end_pos; + } + + if i < num_pipelines - 1 { + self.newline(); + } + } + } + + /// Get the end position of a pipeline element, including any redirections + fn get_element_end_pos(&self, element: &PipelineElement) -> usize { + element + .redirection + .as_ref() + .map_or(element.expr.span.end, |redir| match redir { + PipelineRedirection::Single { target, .. } => target.span().end, + PipelineRedirection::Separate { out, err } => out.span().end.max(err.span().end), + }) + } + + /// Format a pipeline + fn format_pipeline(&mut self, pipeline: &Pipeline) { + for (i, element) in pipeline.elements.iter().enumerate() { + if i > 0 { + self.write(" | "); + } + self.format_pipeline_element(element); + } + } + + /// Format a pipeline element + fn format_pipeline_element(&mut self, element: &PipelineElement) { + self.format_expression(&element.expr); + if let Some(ref redirection) = element.redirection { + self.format_redirection(redirection); + } + } + + /// Format a redirection + fn format_redirection(&mut self, redir: &PipelineRedirection) { + match redir { + PipelineRedirection::Single { target, .. } => { + self.space(); + self.format_redirection_target(target); + } + PipelineRedirection::Separate { out, err } => { + self.space(); + self.format_redirection_target(out); + self.space(); + self.format_redirection_target(err); + } + } + } - out = write_only_if_have_hastag_or_equal(skipped_contents, out, true); + /// Format a redirection target + fn format_redirection_target(&mut self, target: &RedirectionTarget) { + match target { + RedirectionTarget::File { expr, span, .. } => { + self.write_span(*span); + self.space(); + self.format_expression(expr); + } + RedirectionTarget::Pipe { span } => { + self.write_span(*span); + } } + } + + // ───────────────────────────────────────────────────────────────────────────── + // Expression formatting + // ───────────────────────────────────────────────────────────────────────────── - let mut bytes = working_set.get_span_contents(span); - let content = String::from_utf8_lossy(bytes).to_string(); - trace!("shape is {shape}"); - trace!("shape contents: {:?}", &content); + /// Format an expression + fn format_expression(&mut self, expr: &Expression) { + match &expr.expr { + // Literals and simple values - preserve original + Expr::Int(_) + | Expr::Float(_) + | Expr::Bool(_) + | Expr::Nothing + | Expr::DateTime(_) + | Expr::String(_) + | Expr::RawString(_) + | Expr::Binary(_) + | Expr::Filepath(_, _) + | Expr::Directory(_, _) + | Expr::GlobPattern(_, _) + | Expr::Var(_) + | Expr::VarDecl(_) + | Expr::Operator(_) + | Expr::StringInterpolation(_) + | Expr::GlobInterpolation(_, _) + | Expr::ImportPattern(_) + | Expr::Overlay(_) + | Expr::Garbage => { + self.write_expr_span(expr); + } - match shape { - FlatShape::Int | FlatShape::Nothing => out.extend(bytes), - FlatShape::StringInterpolation => { - out.extend(bytes); + Expr::Signature(sig) => self.format_signature(sig), + + Expr::Call(call) => self.format_call(call), + Expr::ExternalCall(head, args) => self.format_external_call(head, args), + Expr::BinaryOp(lhs, op, rhs) => self.format_binary_op(lhs, op, rhs), + Expr::UnaryNot(inner) => { + self.write("not "); + self.format_expression(inner); + } + + Expr::Block(block_id) => { + self.format_block_expression(*block_id, expr.span, false); + } + Expr::Closure(block_id) => { + self.format_closure_expression(*block_id, expr.span); + } + Expr::Subexpression(block_id) => { + self.format_subexpression(*block_id); } - FlatShape::List | FlatShape::Record => { - bytes = trim_ascii_whitespace(bytes); - out.extend(bytes); + + Expr::List(items) => self.format_list(items), + Expr::Record(items) => self.format_record(items), + Expr::Table(table) => self.format_table(&table.columns, &table.rows), + + Expr::Range(range) => self.format_range(range), + Expr::CellPath(cell_path) => self.format_cell_path_members(&cell_path.members), + Expr::FullCellPath(full_path) => { + self.format_expression(&full_path.head); + self.format_cell_path_members(&full_path.tail); + } + + Expr::RowCondition(block_id) => { + let block = self.working_set.get_block(*block_id); + self.format_block(block); + } + + Expr::Keyword(keyword) => { + self.write_span(keyword.span); + self.space(); + self.format_block_or_expr(&keyword.expr); + } + + Expr::ValueWithUnit(_) => { + // Preserve original span since the parser normalizes units + // (e.g., 1kb becomes 1000b internally) + self.write_expr_span(expr); + } + + Expr::MatchBlock(matches) => self.format_match_block(matches), + + Expr::Collect(_, inner) => self.format_expression(inner), + + Expr::AttributeBlock(attr_block) => { + for attr in &attr_block.attributes { + self.write_span(attr.expr.span); + self.newline(); + } + self.format_expression(&attr_block.item); } - FlatShape::Block | FlatShape::Closure => { - bytes = trim_ascii_whitespace(bytes); - out.extend(bytes); + } + } + + /// Format a call expression + fn format_call(&mut self, call: &nu_protocol::ast::Call) { + let decl = self.working_set.get_decl(call.decl_id); + let decl_name = decl.name(); + + // Determine command type + let cmd_type = Self::classify_command(decl_name); + + // Write command name + if call.head.end != 0 { + self.write_span(call.head); + } + + // Format arguments based on command type + for arg in &call.arguments { + self.format_call_argument(arg, &cmd_type); + } + } + + /// Classify a command by its formatting requirements + fn classify_command(name: &str) -> CommandType { + if DEF_COMMANDS.contains(&name) { + CommandType::Def + } else if EXTERN_COMMANDS.contains(&name) { + CommandType::Extern + } else if CONDITIONAL_COMMANDS.contains(&name) { + CommandType::Conditional + } else if LET_COMMANDS.contains(&name) { + CommandType::Let + } else if BLOCK_COMMANDS.contains(&name) { + CommandType::Block + } else { + CommandType::Regular + } + } + + /// Format a call argument based on command type + fn format_call_argument(&mut self, arg: &Argument, cmd_type: &CommandType) { + match arg { + Argument::Positional(positional) | Argument::Unknown(positional) => { + self.format_positional_argument(positional, cmd_type); } - FlatShape::String => { - out.extend(bytes); - // if it'a string after a `def`, add a space before the `[` - if after_a_def { - out.extend(b" "); + Argument::Named(named) => { + self.space(); + if named.0.span.end != 0 { + self.write_span(named.0.span); + } + if let Some(short) = &named.1 { + self.write_span(short.span); + } + if let Some(value) = &named.2 { + self.space(); + self.format_expression(value); } } - FlatShape::Pipe => { - out.extend(b"| "); + Argument::Spread(spread_expr) => { + self.space(); + self.write("..."); + self.format_expression(spread_expr); } - FlatShape::InternalCall(declid) => { - let declid = declid.get(); - let decl_name = decls_sorted.get_decl_name(declid); + } + } - trace!("Called Internal call with {declid}"); + /// Format a positional argument based on command type + fn format_positional_argument(&mut self, positional: &Expression, cmd_type: &CommandType) { + self.space(); + match cmd_type { + CommandType::Def => self.format_def_argument(positional), + CommandType::Extern => self.format_extern_argument(positional), + CommandType::Conditional | CommandType::Block => { + self.format_block_or_expr(positional); + } + CommandType::Let => self.format_let_argument(positional), + CommandType::Regular => self.format_expression(positional), + } + } + + /// Format an argument for def commands + fn format_def_argument(&mut self, positional: &Expression) { + match &positional.expr { + Expr::String(_) => self.format_expression(positional), + Expr::Signature(sig) => self.format_signature(sig), + Expr::Closure(block_id) | Expr::Block(block_id) => { + self.format_block_expression(*block_id, positional.span, true); + } + _ => self.format_expression(positional), + } + } - if let Some(decl_name) = decl_name { - out = resolve_call(bytes, decl_name, out); - after_a_def = decl_name == "def"; + /// Format an argument for extern commands (preserve original signature) + fn format_extern_argument(&mut self, positional: &Expression) { + match &positional.expr { + // For extern, preserve the signature span to maintain parameter order + Expr::Signature(_) => self.write_expr_span(positional), + _ => self.format_expression(positional), + } + } + + /// Format an argument for let/mut/const commands + fn format_let_argument(&mut self, positional: &Expression) { + match &positional.expr { + Expr::VarDecl(_) => self.format_expression(positional), + Expr::Block(block_id) | Expr::Subexpression(block_id) => { + self.write("= "); + let block = self.working_set.get_block(*block_id); + self.format_block(block); + } + _ => { + self.write("= "); + self.format_expression(positional); + } + } + } + + /// Format an expression that could be a block or a regular expression + fn format_block_or_expr(&mut self, expr: &Expression) { + match &expr.expr { + Expr::Block(block_id) | Expr::Closure(block_id) => { + self.format_block_expression(*block_id, expr.span, true); + } + _ => self.format_expression(expr), + } + } + + /// Format an external call + fn format_external_call(&mut self, head: &Expression, args: &[ExternalArgument]) { + // Check if the original source had an explicit ^ prefix + // by looking at the byte before the head span + if head.span.start > 0 && self.source.get(head.span.start - 1) == Some(&b'^') { + self.write("^"); + } + self.format_expression(head); + for arg in args { + self.space(); + match arg { + ExternalArgument::Regular(arg_expr) => self.format_expression(arg_expr), + ExternalArgument::Spread(spread_expr) => { + self.write("..."); + self.format_expression(spread_expr); } } - FlatShape::External => out = resolve_external(bytes, out), - FlatShape::ExternalArg | FlatShape::Signature | FlatShape::Keyword => { - out.extend(bytes); - out = insert_newline(out); + } + } + + /// Format a binary operation + fn format_binary_op(&mut self, lhs: &Expression, op: &Expression, rhs: &Expression) { + self.format_expression(lhs); + // Always add space around binary operators for valid nushell syntax + self.write(" "); + self.format_expression(op); + self.write(" "); + + // For assignment operators, unwrap Subexpression on RHS to avoid double parens + if let Expr::Operator(nu_protocol::ast::Operator::Assignment(_)) = &op.expr { + if let Expr::Subexpression(block_id) = &rhs.expr { + let block = self.working_set.get_block(*block_id); + self.format_block(block); + return; } - FlatShape::VarDecl(varid) | FlatShape::Variable(varid) => { - trace!("Called variable or vardecl with {}", varid.get()); - out.extend(bytes); - out.extend(b" "); + } + self.format_expression(rhs); + } + + /// Format a range expression + fn format_range(&mut self, range: &nu_protocol::ast::Range) { + if let Some(from) = &range.from { + self.format_expression(from); + } + self.write(".."); + if let Some(next) = &range.next { + self.format_expression(next); + // For step ranges (start..step..end), write the operator again before end + self.write(".."); + } + if let Some(to) = &range.to { + self.format_expression(to); + } + } + + /// Format a signature (for def commands) + fn format_signature(&mut self, sig: &Signature) { + self.write("["); + + let param_count = sig.required_positional.len() + + sig.optional_positional.len() + + sig.named.iter().filter(|f| f.long != "help").count() + + if sig.rest_positional.is_some() { 1 } else { 0 }; + let has_multiline = param_count > 3; + + if has_multiline { + self.newline(); + self.indent_level += 1; + } + + let mut first = true; + + // Helper to write separator + let write_sep = |formatter: &mut Formatter, first: &mut bool, has_multiline: bool| { + if !*first { + if has_multiline { + formatter.newline(); + formatter.write_indent(); + } else { + formatter.write(", "); + } } - FlatShape::Garbage => { - debug!("found garbage 😢 {content}"); - return Err(FormatError::GarbageFound); + *first = false; + }; + + // Required positional + for param in &sig.required_positional { + write_sep(self, &mut first, has_multiline); + self.write(¶m.name); + if param.shape != SyntaxShape::Any { + self.write(": "); + self.write(&format!("{}", param.shape)); } + } - _ => out.extend(bytes), + // Optional positional + for param in &sig.optional_positional { + write_sep(self, &mut first, has_multiline); + self.write(¶m.name); + // If there's a default value, don't use ? syntax, use = syntax + if param.default_value.is_none() { + self.write("?"); + } + if param.shape != SyntaxShape::Any { + self.write(": "); + self.write(&format!("{}", param.shape)); + } + if let Some(default) = ¶m.default_value { + self.write(" = "); + self.write(&default.to_expanded_string(" ", &nu_protocol::Config::default())); + } } - if is_last_span(span, &flat) && span.end < end_of_file { - trace!( - "The last span doesn't end the file! span: {0}, end: {1}", - span.end, - end_of_file - ); + // Named flags (before rest positional to match common convention) + for flag in &sig.named { + // Skip help flag as it's auto-added + if flag.long == "help" { + continue; + } + write_sep(self, &mut first, has_multiline); - let remaining_contents = &contents[span.end..end_of_file]; - let printable = String::from_utf8_lossy(remaining_contents).to_string(); - trace!("contents: {:?}", printable); + // Handle short-only flags (empty long name) + if flag.long.is_empty() { + if let Some(short) = flag.short { + self.write("-"); + self.write(&short.to_string()); + } + } else { + self.write("--"); + self.write(&flag.long); + if let Some(short) = flag.short { + self.write("(-"); + self.write(&short.to_string()); + self.write(")"); + } + } + if let Some(shape) = &flag.arg { + self.write(": "); + self.write(&format!("{}", shape)); + } + if let Some(default) = &flag.default_value { + self.write(" = "); + self.write(&default.to_expanded_string(" ", &nu_protocol::Config::default())); + } + } - out = write_only_if_have_hastag_or_equal(remaining_contents, out, false); + // Rest positional (comes last) + if let Some(rest) = &sig.rest_positional { + write_sep(self, &mut first, has_multiline); + self.write("..."); + self.write(&rest.name); + if rest.shape != SyntaxShape::Any { + self.write(": "); + self.write(&format!("{}", rest.shape)); + } } - start = span.end + 1; + if has_multiline { + self.newline(); + self.indent_level -= 1; + self.write_indent(); + } + self.write("]"); } - Ok(out) -} + /// Format cell path members (shared between `CellPath` and `FullCellPath`) + fn format_cell_path_members(&mut self, members: &[PathMember]) { + for member in members { + self.write("."); + match member { + PathMember::String { val, optional, .. } => { + if *optional { + self.write("?"); + } + self.write(val); + } + PathMember::Int { val, optional, .. } => { + if *optional { + self.write("?"); + } + self.write(&val.to_string()); + } + } + } + } -/// insert a newline at the end of a buffer -fn insert_newline(mut bytes: Vec) -> Vec { - bytes.extend(b"\n"); - bytes -} + /// Format a subexpression + fn format_subexpression(&mut self, block_id: nu_protocol::BlockId) { + self.write("("); + let block = self.working_set.get_block(block_id); + let is_simple = block.pipelines.len() == 1 && block.pipelines[0].elements.len() <= 3; -/// given a list of `bytes` and a `out`put to write only the bytes if they contain `#` or `=` -/// -/// One tiny little detail: the order of bytes is important to nufmt. -/// It is not the same to have -/// `bytes` + `out` (you have to put \n after bytes) -/// `bytes` + \n + `out` -/// -/// than having: -/// `out` + `bytes` (you have to put \n before bytes) -/// `out` + \n + `bytes` -/// -/// That's what `bytes_before_content` bool is for -fn write_only_if_have_hastag_or_equal( - bytes: &[u8], - mut out: Vec, - bytes_before_content: bool, -) -> Vec { - if bytes.contains(&b'#') { - trace!("This have a comment. Writing."); - if bytes_before_content { - out.extend(trim_ascii_whitespace(bytes)); - out = insert_newline(out); + if is_simple { + self.format_block(block); } else { - out = insert_newline(out); - out.extend(trim_ascii_whitespace(bytes)); + self.newline(); + self.indent_level += 1; + self.format_block(block); + self.newline(); + self.indent_level -= 1; + self.write_indent(); } - } else if bytes.contains(&b'=') { - out.extend(trim_ascii_whitespace(bytes)); - out.extend(b" "); - } else { - trace!("The contents doesn't have a '#'. Skipping."); + self.write(")"); } - out -} -#[allow(clippy::wildcard_in_or_patterns)] -fn resolve_call(c_bytes: &[u8], decl_name: &str, mut out: Vec) -> Vec { - out = match decl_name { - "if" => insert_newline(out), - "def" => insert_newline(out), - "export def" | _ => out, - }; - out.extend(c_bytes); - out.extend(b" "); - out + // ───────────────────────────────────────────────────────────────────────────── + // Block expression formatting + // ───────────────────────────────────────────────────────────────────────────── + + /// Format a block expression with optional braces + fn format_block_expression( + &mut self, + block_id: nu_protocol::BlockId, + _span: Span, + with_braces: bool, + ) { + let block = self.working_set.get_block(block_id); + + if with_braces { + self.write("{"); + } + + let is_simple = block.pipelines.len() == 1 + && block.pipelines[0].elements.len() == 1 + && !self.block_has_nested_structures(block); + + if is_simple && with_braces { + self.write(" "); + self.format_block(block); + self.write(" "); + } else if block.pipelines.is_empty() { + if with_braces { + self.write(" "); + } + } else { + self.newline(); + self.indent_level += 1; + self.format_block(block); + self.newline(); + self.indent_level -= 1; + self.write_indent(); + } + + if with_braces { + self.write("}"); + } + } + + /// Check if a block has nested structures that require multiline formatting + fn block_has_nested_structures(&self, block: &Block) -> bool { + block + .pipelines + .iter() + .flat_map(|p| &p.elements) + .any(|e| self.expr_is_complex(&e.expr)) + } + + /// Check if an expression is complex enough to warrant multiline formatting + fn expr_is_complex(&self, expr: &Expression) -> bool { + match &expr.expr { + Expr::Block(_) | Expr::Closure(_) => true, + Expr::List(items) => items.len() > 3, + Expr::Record(items) => items.len() > 2, + Expr::Call(call) => call.arguments.iter().any(|arg| match arg { + Argument::Positional(e) | Argument::Unknown(e) | Argument::Spread(e) => { + self.expr_is_complex(e) + } + Argument::Named(n) => n.2.as_ref().is_some_and(|e| self.expr_is_complex(e)), + }), + _ => false, + } + } + + /// Format a closure expression + fn format_closure_expression(&mut self, block_id: nu_protocol::BlockId, span: Span) { + let content = self.get_span_content(span); + let has_params = content.starts_with(b"{|") || content.starts_with(b"{ |"); + + if !has_params { + self.format_block_expression(block_id, span, true); + return; + } + + // Find the end of the parameter section (second |) + let param_end = content.iter().position(|&b| b == b'|').and_then(|first| { + content[first + 1..] + .iter() + .position(|&b| b == b'|') + .map(|p| first + 1 + p + 1) + }); + + let Some(end) = param_end else { + self.write_bytes(&content); + return; + }; + + self.write("{|"); + // Extract and trim parameter content + let params = &content[2..end - 1]; + let trimmed: Vec = params + .iter() + .copied() + .skip_while(|b| b.is_ascii_whitespace()) + .collect::>() + .into_iter() + .rev() + .skip_while(|b| b.is_ascii_whitespace()) + .collect::>() + .into_iter() + .rev() + .collect(); + self.write_bytes(&trimmed); + self.write("| "); + + let block = self.working_set.get_block(block_id); + let is_simple = block.pipelines.len() == 1 + && block.pipelines[0].elements.len() == 1 + && !self.block_has_nested_structures(block); + + if is_simple { + self.format_block(block); + self.write(" }"); + } else { + self.newline(); + self.indent_level += 1; + self.format_block(block); + self.newline(); + self.indent_level -= 1; + self.write_indent(); + self.write("}"); + } + } + + // ───────────────────────────────────────────────────────────────────────────── + // Collection formatting (lists, records, tables) + // ───────────────────────────────────────────────────────────────────────────── + + /// Format a list + fn format_list(&mut self, items: &[ListItem]) { + if items.is_empty() { + self.write("[]"); + return; + } + + // Check if all items are simple (primitives) + let all_simple = items.iter().all(|item| match item { + ListItem::Item(expr) => self.is_simple_expr(expr), + ListItem::Spread(_, expr) => self.is_simple_expr(expr), + }); + + if all_simple && items.len() <= 5 { + // Inline format + self.write("["); + for (i, item) in items.iter().enumerate() { + if i > 0 { + self.write(", "); + } + self.format_list_item(item); + } + self.write("]"); + } else { + // Multiline format + self.write("["); + self.newline(); + self.indent_level += 1; + for item in items { + self.write_indent(); + self.format_list_item(item); + self.newline(); + } + self.indent_level -= 1; + self.write_indent(); + self.write("]"); + } + } + + /// Format a single list item + fn format_list_item(&mut self, item: &ListItem) { + match item { + ListItem::Item(expr) => self.format_expression(expr), + ListItem::Spread(_, expr) => { + self.write("..."); + self.format_expression(expr); + } + } + } + + /// Format a record + fn format_record(&mut self, items: &[RecordItem]) { + if items.is_empty() { + self.write("{}"); + return; + } + + // Check if all items are simple + let all_simple = items.iter().all(|item| match item { + RecordItem::Pair(k, v) => self.is_simple_expr(k) && self.is_simple_expr(v), + RecordItem::Spread(_, expr) => self.is_simple_expr(expr), + }); + + // Check if any value contains nested structures (records, lists, closures) or variables + let has_nested_complex = items.iter().any(|item| match item { + RecordItem::Pair(_, v) => matches!( + &v.expr, + Expr::Record(_) + | Expr::List(_) + | Expr::Closure(_) + | Expr::Block(_) + | Expr::Var(_) + | Expr::FullCellPath(_) + ), + RecordItem::Spread(_, _) => false, + }); + + // When nested with complex values or variables, records with 2+ items should be multiline + let nested_multiline = self.indent_level > 0 && items.len() >= 2 && has_nested_complex; + + if all_simple && items.len() <= 3 && !nested_multiline { + // Inline format + self.write("{"); + for (i, item) in items.iter().enumerate() { + if i > 0 { + self.write(", "); + } + self.format_record_item(item); + } + self.write("}"); + } else { + // Multiline format + self.write("{"); + self.newline(); + self.indent_level += 1; + for item in items { + self.write_indent(); + self.format_record_item(item); + self.newline(); + } + self.indent_level -= 1; + self.write_indent(); + self.write("}"); + } + } + + /// Format a single record item + fn format_record_item(&mut self, item: &RecordItem) { + match item { + RecordItem::Pair(key, value) => { + self.format_expression(key); + self.write(": "); + self.format_expression(value); + } + RecordItem::Spread(_, expr) => { + self.write("..."); + self.format_expression(expr); + } + } + } + + /// Format a table + fn format_table(&mut self, columns: &[Expression], rows: &[Box<[Expression]>]) { + self.write("["); + + // Format header row + self.write("["); + for (i, col) in columns.iter().enumerate() { + if i > 0 { + self.write(", "); + } + self.format_expression(col); + } + self.write("]"); + + // Format data rows + if !rows.is_empty() { + self.write("; "); + for (i, row) in rows.iter().enumerate() { + if i > 0 { + self.write(", "); + } + self.write("["); + for (j, cell) in row.iter().enumerate() { + if j > 0 { + self.write(", "); + } + self.format_expression(cell); + } + self.write("]"); + } + } + + self.write("]"); + } + + // ───────────────────────────────────────────────────────────────────────────── + // Match block formatting + // ───────────────────────────────────────────────────────────────────────────── + + /// Format a match block + fn format_match_block(&mut self, matches: &[(MatchPattern, Expression)]) { + self.write("{"); + self.newline(); + self.indent_level += 1; + + for (pattern, expr) in matches { + self.write_indent(); + self.format_match_pattern(pattern); + self.write(" => "); + self.format_block_or_expr(expr); + self.newline(); + } + + self.indent_level -= 1; + self.write_indent(); + self.write("}"); + } + + /// Format a match pattern + fn format_match_pattern(&mut self, pattern: &MatchPattern) { + match &pattern.pattern { + Pattern::Expression(expr) => self.format_expression(expr), + Pattern::Value(_) | Pattern::Variable(_) | Pattern::Rest(_) | Pattern::Garbage => { + self.write_span(pattern.span); + } + Pattern::Or(patterns) => { + for (i, p) in patterns.iter().enumerate() { + if i > 0 { + self.write(" | "); + } + self.format_match_pattern(p); + } + } + Pattern::List(patterns) => { + self.write("["); + for (i, p) in patterns.iter().enumerate() { + if i > 0 { + self.write(", "); + } + self.format_match_pattern(p); + } + self.write("]"); + } + Pattern::Record(entries) => { + self.write("{"); + for (i, (key, pat)) in entries.iter().enumerate() { + if i > 0 { + self.write(", "); + } + self.write(key); + self.write(": "); + self.format_match_pattern(pat); + } + self.write("}"); + } + Pattern::IgnoreRest => self.write(".."), + Pattern::IgnoreValue => self.write("_"), + } + } + + // ───────────────────────────────────────────────────────────────────────────── + // Helpers + // ───────────────────────────────────────────────────────────────────────────── + + /// Check if an expression is simple (primitive type) + fn is_simple_expr(&self, expr: &Expression) -> bool { + match &expr.expr { + Expr::Int(_) + | Expr::Float(_) + | Expr::Bool(_) + | Expr::String(_) + | Expr::RawString(_) + | Expr::Nothing + | Expr::Var(_) + | Expr::Filepath(_, _) + | Expr::Directory(_, _) + | Expr::GlobPattern(_, _) + | Expr::DateTime(_) => true, + // FullCellPath with empty tail is simple (e.g., $var or undefined $var parsed as Garbage) + Expr::FullCellPath(full_path) => { + full_path.tail.is_empty() + && matches!( + &full_path.head.expr, + Expr::Var(_) | Expr::Garbage | Expr::Int(_) | Expr::String(_) + ) + } + _ => false, + } + } + + /// Get the final output + fn finish(self) -> Vec { + self.output + } } -fn resolve_external(c_bytes: &[u8], mut out: Vec) -> Vec { - out = match c_bytes { - [b'c', b'd'] => insert_newline(out), - _ => out, - }; - out.extend(c_bytes); - out.extend(b" "); - out +/// Command types for formatting purposes +enum CommandType { + Def, + Extern, + Conditional, + Let, + Block, + Regular, } -/// make sure there is a newline at the end of a buffer -pub(crate) fn add_newline_at_end_of_file(out: Vec) -> Vec { - match out.last() { - Some(&b'\n') => out, - _ => insert_newline(out), +/// Extract comments from source code +fn extract_comments(source: &[u8]) -> Vec<(Span, Vec)> { + let mut comments = Vec::new(); + let mut i = 0; + let mut in_string = false; + let mut string_char = b'"'; + + while i < source.len() { + let c = source[i]; + + // Track string state to avoid matching # inside strings + if !in_string && (c == b'"' || c == b'\'') { + in_string = true; + string_char = c; + i += 1; + continue; + } + + if in_string { + if c == b'\\' && i + 1 < source.len() { + i += 2; // Skip escaped character + continue; + } + if c == string_char { + in_string = false; + } + i += 1; + continue; + } + + // Found a comment + if c == b'#' { + let start = i; + while i < source.len() && source[i] != b'\n' { + i += 1; + } + comments.push((Span::new(start, i), source[start..i].to_vec())); + } + + i += 1; } + + comments } -/// strip all spaces, new lines and tabs found a sequence of bytes +/// Format an array of bytes /// -/// Because you don't know how the incoming code is formatted, -/// the best way to format is to strip all the whitespace -/// and afterwards include the new lines and indentation correctly -/// according to the configuration -fn trim_ascii_whitespace(x: &[u8]) -> &[u8] { - let Some(from) = x.iter().position(|x| !x.is_ascii_whitespace()) else { - return &x[0..0]; - }; - let to = x.iter().rposition(|x| !x.is_ascii_whitespace()).unwrap(); - let result = &x[from..=to]; - let printable = String::from_utf8_lossy(result).to_string(); - trace!("stripped the whitespace, result: {:?}", printable); - result +/// Reading the file gives you a list of bytes +pub(crate) fn format_inner(contents: &[u8], config: &Config) -> Result, FormatError> { + let engine_state = get_engine_state(); + let mut working_set = StateWorkingSet::new(&engine_state); + + let parsed_block = parse(&mut working_set, None, contents, false); + trace!("parsed block:\n{:?}", &parsed_block); + + // Note: We don't reject files with "garbage" nodes because the parser + // produces garbage for commands it doesn't know about (e.g., `where`, `each`) + // when using only nu-cmd-lang context. Instead, we output original span + // content for expressions we can't format. + + if parsed_block.pipelines.is_empty() { + trace!("block has no pipelines!"); + debug!("File has no code to format."); + let comments = extract_comments(contents); + if comments.is_empty() { + return Ok(contents.to_vec()); + } + } + + let mut formatter = Formatter::new(contents, &working_set, config); + + // Write leading comments + if let Some(first_pipeline) = parsed_block.pipelines.first() { + if let Some(first_elem) = first_pipeline.elements.first() { + formatter.write_comments_before(first_elem.expr.span.start); + } + } + + formatter.format_block(&parsed_block); + + // Write trailing comments + let end_pos = parsed_block + .pipelines + .last() + .and_then(|p| p.elements.last()) + .map(|e| e.expr.span.end) + .unwrap_or(0); + + if end_pos > 0 { + formatter.last_pos = end_pos; + formatter.write_comments_before(contents.len()); + } + + Ok(formatter.finish()) } -/// return true if the Nushell block has at least 1 pipeline -/// -/// This function exists because sometimes is passed to `nufmt` an empty String, -/// or a nu code which the parser can't identify something runnable -/// (like a list of comments) -/// -/// We don't want to return a blank file if that is the case, -/// so this check gives the opportunity to `nufmt` -/// to know when not to touch the file at all in the implementation. -fn block_has_pipelines(block: &Block) -> bool { - !block.pipelines.is_empty() +/// Make sure there is a newline at the end of a buffer +pub(crate) fn add_newline_at_end_of_file(out: Vec) -> Vec { + if out.last() == Some(&b'\n') { + out + } else { + let mut result = out; + result.push(b'\n'); + result + } } -/// return true if the given span is the last one -fn is_last_span(span: Span, flat: &[(Span, FlatShape)]) -> bool { - span == flat.last().unwrap().0 +#[cfg(test)] +mod tests { + use super::*; + + fn format(input: &str) -> String { + let config = Config::default(); + let result = format_inner(input.as_bytes(), &config).expect("formatting failed"); + String::from_utf8(result).expect("invalid utf8") + } + + #[test] + fn test_simple_let() { + let input = "let x = 1"; + let output = format(input); + assert_eq!(output, "let x = 1"); + } + + #[test] + fn test_let_with_spaces() { + let input = "let x = 1"; + let output = format(input); + assert_eq!(output, "let x = 1"); + } + + #[test] + fn test_simple_def() { + let input = "def foo [] { echo hello }"; + let output = format(input); + assert!(output.contains("def foo")); + } + + #[test] + fn test_pipeline() { + let input = "ls | get name"; + let output = format(input); + assert!(output.contains("| get")); + } + + #[test] + fn test_if_else() { + let input = "if true { echo yes } else { echo no }"; + let output = format(input); + assert!(output.contains("if true")); + assert!(output.contains("else")); + } + + #[test] + fn test_for_loop() { + let input = "for x in [1, 2, 3] { print $x }"; + let output = format(input); + assert!(output.contains("for x in")); + assert!(output.contains("{ print")); + } + + #[test] + fn test_while_loop() { + let input = "while true { break }"; + let output = format(input); + assert!(output.contains("while true")); + assert!(output.contains("{ break }")); + } + + #[test] + fn test_closure() { + let input = "{|x| $x * 2 }"; + let output = format(input); + assert!(output.contains("{|x|")); + } + + #[test] + fn test_multiline() { + let input = "let x = 1\nlet y = 2"; + let output = format(input); + assert!(output.contains("let x = 1")); + assert!(output.contains("let y = 2")); + assert!(output.contains("\n")); + } + + #[test] + fn test_list_simple() { + let input = "[1, 2, 3]"; + let output = format(input); + assert_eq!(output, "[1, 2, 3]"); + } + + #[test] + fn test_record_simple() { + let input = "{a: 1, b: 2}"; + let output = format(input); + assert!(output.contains("a: 1")); + } + + #[test] + fn test_comment_preservation() { + let input = "# this is a comment\nlet x = 1"; + let output = format(input); + assert!(output.contains("# this is a comment")); + } + + #[test] + fn test_idempotency_let() { + let input = "let x = 1"; + let first = format(input); + let second = format(&first); + assert_eq!(first, second, "Formatting should be idempotent"); + } + + #[test] + fn test_idempotency_def() { + let input = "def foo [x: int] { $x + 1 }"; + let first = format(input); + let second = format(&first); + assert_eq!(first, second, "Formatting should be idempotent"); + } + + #[test] + fn test_idempotency_if_else() { + let input = "if true { echo yes } else { echo no }"; + let first = format(input); + let second = format(&first); + assert_eq!(first, second, "Formatting should be idempotent"); + } + + #[test] + fn test_idempotency_for_loop() { + let input = "for x in [1, 2, 3] { print $x }"; + let first = format(input); + let second = format(&first); + assert_eq!(first, second, "Formatting should be idempotent"); + } + + #[test] + fn test_idempotency_complex() { + let input = "# comment\nlet x = 1\ndef foo [] { $x }"; + let first = format(input); + let second = format(&first); + assert_eq!(first, second, "Formatting should be idempotent"); + } } diff --git a/src/lib.rs b/src/lib.rs index 5f4353f..39ade8c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ //! `nu_formatter` is a library for formatting nu. //! //! It does not do anything more than that, which makes it so fast. + use config::Config; use format_error::FormatError; use formatting::{add_newline_at_end_of_file, format_inner}; @@ -15,7 +16,7 @@ pub mod format_error; mod formatting; /// Possible modes the formatter can run on -#[derive(Debug, Clone, Default, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub enum Mode { #[default] Normal, @@ -33,7 +34,7 @@ pub enum FileDiagnostic { Failure(String), } -/// format a Nushell file in place. Do not write in dry-run mode. +/// Format a Nushell file in place. Do not write in dry-run mode. pub fn format_single_file( file: PathBuf, config: &Config, @@ -41,46 +42,41 @@ pub fn format_single_file( ) -> (PathBuf, FileDiagnostic) { let contents = match std::fs::read(&file) { Ok(content) => content, - Err(err) => { - return (file, FileDiagnostic::Failure(err.to_string())); - } + Err(err) => return (file, FileDiagnostic::Failure(err.to_string())), }; - let formatted_bytes = add_newline_at_end_of_file(match format_inner(&contents, config) { - Ok(bytes) => bytes, - Err(err) => { - return (file, FileDiagnostic::Failure(err.to_string())); - } - }); + let formatted_bytes = match format_inner(&contents, config) { + Ok(bytes) => add_newline_at_end_of_file(bytes), + Err(err) => return (file, FileDiagnostic::Failure(err.to_string())), + }; if formatted_bytes == contents { debug!("File is already formatted correctly."); return (file, FileDiagnostic::AlreadyFormatted); } - match mode { - Mode::DryRun => { - debug!("File not formatted because running in dry run, but would be reformatted in normal mode."); - } - Mode::Normal => { - let mut writer = match File::create(&file) { - Ok(file) => file, - Err(err) => { - return (file, FileDiagnostic::Failure(err.to_string())); - } - }; - let file_bytes = formatted_bytes.as_slice(); - if let Err(err) = writer.write_all(file_bytes) { - return (file, FileDiagnostic::Failure(err.to_string())); - } - debug!("File formatted."); - } - }; + if *mode == Mode::DryRun { + debug!("File not formatted because running in dry run, but would be reformatted in normal mode."); + return (file, FileDiagnostic::Reformatted); + } + + // Normal mode: write the formatted content + if let Err(err) = write_file(&file, &formatted_bytes) { + return (file, FileDiagnostic::Failure(err.to_string())); + } + + debug!("File formatted."); (file, FileDiagnostic::Reformatted) } -/// format a string of Nushell code -pub fn format_string(input_string: &String, config: &Config) -> Result { +/// Write bytes to a file +fn write_file(path: &PathBuf, contents: &[u8]) -> std::io::Result<()> { + let mut writer = File::create(path)?; + writer.write_all(contents) +} + +/// Format a string of Nushell code +pub fn format_string(input_string: &str, config: &Config) -> Result { let contents = input_string.as_bytes(); let formatted_bytes = format_inner(contents, config)?; Ok(String::from_utf8(formatted_bytes) @@ -91,16 +87,17 @@ pub fn format_string(input_string: &String, config: &Config) -> Result i32 { - match self { - ExitCode::Success => 0, - ExitCode::CheckFailed => 1, - ExitCode::Failure => 2, - } + fn code(self) -> i32 { + self as i32 } } @@ -80,54 +73,35 @@ struct Cli { config: Option, } -fn exit_with_code(exit_code: ExitCode) { +fn exit_with_code(exit_code: ExitCode) -> ! { let code = exit_code.code(); trace!("exit code: {code}"); // NOTE: this immediately terminates the process without doing any cleanup, // so make sure to finish all necessary cleanup before this is called. - std::process::exit(code); + std::process::exit(code) } fn main() { env_logger::init(); let cli = Cli::parse(); - trace!("recieved cli.files: {:?}", cli.files); - trace!("recieved cli.stdin: {:?}", cli.stdin); - trace!("recieved cli.config: {:?}", cli.config); - - let config_file = cli.config.or(find_in_parent_dirs(DEFAULT_CONFIG_FILE)); - let config = match config_file { - None => Config::default(), - Some(cli_config) => match read_config(&cli_config) { - Ok(config) => config, - Err(err) => { - eprintln!("{}: {}", Color::LightRed.paint("error"), &err); - return exit_with_code(ExitCode::Failure); - } - }, + trace!("received cli.files: {:?}", cli.files); + trace!("received cli.stdin: {:?}", cli.stdin); + trace!("received cli.config: {:?}", cli.config); + + let config = match load_config(cli.config) { + Ok(config) => config, + Err(err) => { + eprintln!("{}: {}", Color::LightRed.paint("error"), &err); + exit_with_code(ExitCode::Failure); + } }; let exit_code = if cli.stdin { - let stdin_input = io::stdin().lines().map(|x| x.unwrap()).collect(); - format_string(stdin_input, &config) + format_stdin(&config) } else { - let (target_files, invalid_files) = match discover_nu_files(cli.files, &config.excludes) { - Ok(files) => files, - Err(err) => { - eprintln!("{}: {}", Color::LightRed.paint("error"), err); - return exit_with_code(ExitCode::Failure); - } - }; - let mode = if cli.dry_run { - Mode::DryRun - } else { - Mode::default() - }; - let mut results = handle_invalid_file(invalid_files); - results.extend(format_files(target_files, &config, &mode)); - display_diagnostic_and_compute_exit_code(&results, cli.dry_run) + format_files_from_paths(cli.files, &config, cli.dry_run) }; std::io::stdout() @@ -137,15 +111,31 @@ fn main() { exit_with_code(exit_code); } +/// Load configuration from file or use defaults +fn load_config(cli_config: Option) -> Result { + let config_file = cli_config.or_else(|| find_in_parent_dirs(DEFAULT_CONFIG_FILE)); + + match config_file { + None => Ok(Config::default()), + Some(path) => read_config(&path), + } +} + fn read_config(path: &PathBuf) -> Result { let content = std::fs::read_to_string(path)?; let content_nuon = nuon::from_nuon(&content, None)?; Config::try_from(content_nuon) } -/// format a string passed via stdin and output it directly to stdout -fn format_string(string: String, options: &Config) -> ExitCode { - match nu_formatter::format_string(&string, options) { +/// Format a string passed via stdin and output it directly to stdout +fn format_stdin(config: &Config) -> ExitCode { + let stdin_input: String = io::stdin() + .lines() + .map_while(Result::ok) + .collect::>() + .join("\n"); + + match nu_formatter::format_string(&stdin_input, config) { Ok(output) => { println!("{output}"); ExitCode::Success @@ -161,43 +151,63 @@ fn format_string(string: String, options: &Config) -> ExitCode { } } -fn handle_invalid_file(files: Vec) -> Vec<(PathBuf, FileDiagnostic)> { - let mut results: Vec<(PathBuf, FileDiagnostic)> = vec![]; - for file in files { - results.push(( - file, - FileDiagnostic::Failure("cannot find the file specified".to_string()), - )); - } - results +/// Format files from the given paths +fn format_files_from_paths(paths: Vec, config: &Config, dry_run: bool) -> ExitCode { + let (target_files, invalid_files) = match discover_nu_files(paths, &config.excludes) { + Ok(files) => files, + Err(err) => { + eprintln!("{}: {}", Color::LightRed.paint("error"), err); + return ExitCode::Failure; + } + }; + + let mode = if dry_run { + Mode::DryRun + } else { + Mode::default() + }; + + let mut results = mark_invalid_files(invalid_files); + results.extend(format_files(target_files, config, &mode)); + display_diagnostic_and_compute_exit_code(&results, dry_run) } -/// format a list of files, possibly one, and modify them in place -/// if check mode is on, only check the files but do not modify them in place +/// Mark invalid file paths as failures +fn mark_invalid_files(files: Vec) -> Vec<(PathBuf, FileDiagnostic)> { + files + .into_iter() + .map(|file| { + ( + file, + FileDiagnostic::Failure("cannot find the file specified".to_string()), + ) + }) + .collect() +} + +/// Format a list of files and modify them in place +/// If check mode is on, only check the files but do not modify them fn format_files( files: Vec, - options: &Config, + config: &Config, mode: &Mode, ) -> Vec<(PathBuf, FileDiagnostic)> { files .into_par_iter() .map(|file| { info!("formatting file: {:?}", &file); - nu_formatter::format_single_file(file, options, mode) + nu_formatter::format_single_file(file, config, mode) }) .collect() } -/// Display results and return the appropriate exit code after formatting in check mode +/// Display results and return the appropriate exit code after formatting fn display_diagnostic_and_compute_exit_code( results: &[(PathBuf, FileDiagnostic)], check_mode: bool, ) -> ExitCode { - let mut already_formatted: usize = 0; - let mut reformatted_or_would_reformat: usize = 0; - let mut failures: usize = 0; - let mut at_least_one_failure = false; - let mut warning_messages: Vec = vec![]; + let mut stats = FormattingStats::default(); + let mut warning_messages = Vec::new(); let file_failed_msg = if check_mode { "Failed to check" @@ -207,97 +217,110 @@ fn display_diagnostic_and_compute_exit_code( for (file, result) in results { match result { - FileDiagnostic::AlreadyFormatted => already_formatted += 1, + FileDiagnostic::AlreadyFormatted => stats.already_formatted += 1, FileDiagnostic::Reformatted => { - reformatted_or_would_reformat += 1; + stats.reformatted += 1; if check_mode { warning_messages.push(format!( "Would reformat: {}", Style::new().bold().paint(make_relative(file)) )); - }; + } } FileDiagnostic::Failure(reason) => { - failures += 1; + stats.failures += 1; eprintln!( "{}: {} {}: {}", Color::LightRed.paint("error"), Style::new().bold().paint(file_failed_msg), Style::new().bold().paint(make_relative(file)), - &reason + reason ); - at_least_one_failure = true; } } } + // Print warnings after processing for msg in warning_messages { - println!("{}", msg); + println!("{msg}"); } - if already_formatted + reformatted_or_would_reformat + failures == 0 { - print!( - "{}: no Nushell files found under the given path(s)", - Color::LightYellow.paint("warning"), - ); - return ExitCode::Success; + // Print summary and determine exit code + stats.print_summary(check_mode) +} + +/// Statistics about formatting results +#[derive(Default)] +struct FormattingStats { + already_formatted: usize, + reformatted: usize, + failures: usize, +} + +impl FormattingStats { + fn total(&self) -> usize { + self.already_formatted + self.reformatted + self.failures } - if reformatted_or_would_reformat > 0 { - let msg = if check_mode { - "would be reformatted" - } else { - "were formatted" - }; - println!( - "{} file{} {}", - reformatted_or_would_reformat, - if reformatted_or_would_reformat == 1 { - "" + fn print_summary(&self, check_mode: bool) -> ExitCode { + if self.total() == 0 { + print!( + "{}: no Nushell files found under the given path(s)", + Color::LightYellow.paint("warning"), + ); + return ExitCode::Success; + } + + if self.reformatted > 0 { + let msg = if check_mode { + "would be reformatted" } else { - "s" - }, - msg, - ); + "were formatted" + }; + println!( + "{} file{} {}", + self.reformatted, + plural(self.reformatted), + msg + ); + } + + if self.already_formatted > 0 { + println!( + "{} file{} already formatted", + self.already_formatted, + plural(self.already_formatted) + ); + } + + if self.failures > 0 { + ExitCode::Failure + } else if check_mode && self.reformatted > 0 { + ExitCode::CheckFailed + } else { + ExitCode::Success + } } - if already_formatted > 0 { - println!( - "{} file{} already formatted", - already_formatted, - if already_formatted == 1 { "" } else { "s" } - ); - }; - if at_least_one_failure { - ExitCode::Failure - } else if check_mode && reformatted_or_would_reformat > 0 { - ExitCode::CheckFailed +} + +/// Return "s" for plural, empty string for singular +fn plural(count: usize) -> &'static str { + if count == 1 { + "" } else { - ExitCode::Success + "s" } } -/// Return the different files to analyze, taking only files with .nu extension and discarding files excluded in the config -/// and the invalid paths provided +/// Return the different files to analyze, filtering by .nu extension and config excludes fn discover_nu_files( paths: Vec, - excludes: &Vec, + excludes: &[String], ) -> Result<(Vec, Vec), ConfigError> { - let mut valid_paths: Vec = vec![]; - let mut invalid_paths: Vec = vec![]; + let (valid_paths, invalid_paths): (Vec<_>, Vec<_>) = + paths.into_iter().partition(|p| p.exists()); - for path in paths { - if path.exists() { - valid_paths.push(path); - } else { - invalid_paths.push(path); - } - } - - let mut overrides = OverrideBuilder::new("."); - for pattern in excludes { - overrides.add(&format!("!{}", pattern))?; - } - let overrides = overrides.build()?; + let overrides = build_overrides(excludes)?; let nu_files = valid_paths .iter() @@ -307,43 +330,52 @@ fn discover_nu_files( .build() .filter_map(Result::ok) .filter(is_nu_file) - .map(|path| path.into_path()) - .collect::>() + .map(|entry| entry.into_path()) }) .collect(); Ok((nu_files, invalid_paths)) } -/// Return whether a `DirEntry` is a .nu file or not +/// Build override rules for excluded patterns +fn build_overrides(excludes: &[String]) -> Result { + let mut builder = OverrideBuilder::new("."); + for pattern in excludes { + builder.add(&format!("!{pattern}"))?; + } + Ok(builder.build()?) +} + +/// Return whether a `DirEntry` is a .nu file fn is_nu_file(entry: &DirEntry) -> bool { - entry.file_type().map(|ft| ft.is_file()).unwrap_or(false) + entry.file_type().is_some_and(|ft| ft.is_file()) && entry.path().extension().is_some_and(|ext| ext == "nu") } +/// Convert a path to a relative path string for display fn make_relative(path: &Path) -> String { - let current = std::env::current_dir().unwrap_or(PathBuf::from(".")); - path.strip_prefix(¤t) + std::env::current_dir() + .ok() + .and_then(|cwd| path.strip_prefix(&cwd).ok()) .unwrap_or(path) .display() .to_string() - .replace("\\", "/") + .replace('\\', "/") .trim_start_matches("./") .to_string() } -/// Search for `filename` in current or any parent directories. -/// If `start_dir` is not provided, the current directory is used +/// Search for `filename` in current or any parent directories fn find_in_parent_dirs(filename: &str) -> Option { - let start_dir = std::env::current_dir().unwrap_or(PathBuf::from(".")); + let start_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); - let mut dir = Some(start_dir.as_path()); - while let Some(current) = dir { - let candidate = current.join(filename); + let mut current = Some(start_dir.as_path()); + while let Some(dir) = current { + let candidate = dir.join(filename); if candidate.exists() { return Some(candidate); } - dir = current.parent(); + current = dir.parent(); } None } @@ -402,25 +434,31 @@ mod tests { #[rstest] #[case(vec![ (PathBuf::from("a.nu"), FileDiagnostic::AlreadyFormatted), - (PathBuf::from("b.nu"), FileDiagnostic::AlreadyFormatted),], false, ExitCode::Success)] + (PathBuf::from("b.nu"), FileDiagnostic::AlreadyFormatted), + ], false, ExitCode::Success)] #[case(vec![ (PathBuf::from("a.nu"), FileDiagnostic::AlreadyFormatted), - (PathBuf::from("b.nu"), FileDiagnostic::AlreadyFormatted),], true, ExitCode::Success)] + (PathBuf::from("b.nu"), FileDiagnostic::AlreadyFormatted), + ], true, ExitCode::Success)] #[case(vec![ (PathBuf::from("a.nu"), FileDiagnostic::AlreadyFormatted), - (PathBuf::from("b.nu"), FileDiagnostic::Reformatted),], false, ExitCode::Success)] + (PathBuf::from("b.nu"), FileDiagnostic::Reformatted), + ], false, ExitCode::Success)] #[case(vec![ (PathBuf::from("a.nu"), FileDiagnostic::AlreadyFormatted), - (PathBuf::from("b.nu"), FileDiagnostic::Reformatted),], true, ExitCode::CheckFailed)] + (PathBuf::from("b.nu"), FileDiagnostic::Reformatted), + ], true, ExitCode::CheckFailed)] #[case(vec![ (PathBuf::from("a.nu"), FileDiagnostic::AlreadyFormatted), (PathBuf::from("b.nu"), FileDiagnostic::Reformatted), - (PathBuf::from("c.nu"), FileDiagnostic::Failure("some error".to_string())),], false, ExitCode::Failure)] + (PathBuf::from("c.nu"), FileDiagnostic::Failure("some error".to_string())), + ], false, ExitCode::Failure)] #[case(vec![ (PathBuf::from("a.nu"), FileDiagnostic::AlreadyFormatted), (PathBuf::from("b.nu"), FileDiagnostic::Reformatted), - (PathBuf::from("c.nu"), FileDiagnostic::Failure("some error".to_string())),], true, ExitCode::Failure)] - fn exit_code( + (PathBuf::from("c.nu"), FileDiagnostic::Failure("some error".to_string())), + ], true, ExitCode::Failure)] + fn exit_code_tests( #[case] results: Vec<(PathBuf, FileDiagnostic)>, #[case] check_mode: bool, #[case] expected: ExitCode, diff --git a/test.nu b/test.nu new file mode 100644 index 0000000..562c173 --- /dev/null +++ b/test.nu @@ -0,0 +1,9 @@ +# function comment +def fun1 [text: string] { print $"fun1: ($text)" } +# this is a +# multi-line comment +# before a function +def fun2 [text: string] { print $"fun2: ($text)" } +# call the functions +fun1 "hello" +fun2 "world" diff --git a/tests/fixtures/basic.nu b/tests/fixtures/basic.nu new file mode 100644 index 0000000..0b8ceae --- /dev/null +++ b/tests/fixtures/basic.nu @@ -0,0 +1,58 @@ +# Basic Nushell constructs for formatter testing +# Simple variable declarations +let x = 1 +let name = "world" +mut counter = 0 +# Arithmetic expressions +let result = $x + 10 +let sum = 1 + 2 + 3 +# Function definition +def greet [name: string] { print $"Hello, ($name)!" } +# Function with multiple parameters +def add [a: int, b: int] { $a + $b } +# Function with default value +def greet_default [name: string = stranger] { print $"Hello, ($name)!" } +# Lists +let numbers = [1, 2, 3, 4, 5] +let mixed = [1, "two", 3.0] +let empty_list = [] +# Records +let person = {name: "Alice", age: 30} +let config = {host: "localhost", port: 8080, debug: true} +let empty_record = {} +# Conditionals +if true { print "yes" } else { print "no" } +# Nested if +if $x > 0 { + if $x > 10 { print "large" } else { print "small" } +} +# For loop +for item in [1, 2, 3] { print $item } +# While loop +while $counter < 5 { $counter = ($counter + 1) } +# Loop +loop { + if $counter > 10 { break } + $counter = ($counter + 1) +} +# Closures +let double = {|x| $x * 2 } +let add_one = {|n: int| $n + 1 } +# String interpolation +let greeting = $"Hello, ($name)!" +# Range +let range = 1..10 +let range_step = 1,2..10 +# Binary operations +let and_result = true and false +let or_result = true or false +let not_result = not true +# Match expression (if available in lang) +# match $x { +# 1 => { print "one" } +# _ => { print "other" } +# } +# Error handling +try { error make {msg: "test error"} } catch { print "caught error" } +# Comments at end of file +# End of test file diff --git a/tests/fixtures/complex.nu b/tests/fixtures/complex.nu new file mode 100644 index 0000000..66494a8 --- /dev/null +++ b/tests/fixtures/complex.nu @@ -0,0 +1,87 @@ +# Complex Nushell constructs for formatter testing +# Module definition +module math { + export def add [a: int, b: int] { $a + $b } + export def multiply [a: int, b: int] { $a * $b } +} +# Using modules +use math +# Custom commands with type annotations +def process-data [data: list>, --verbose(-v), --output(-o): string = result.txt] { + if $verbose { print "Processing data..." } + $data +} +# Nested data structures +let complex_data = { + users: [ + { + name: "Alice" + age: 30 + scores: [95, 87, 92] + } + { + name: "Bob" + age: 25 + scores: [88, 91, 85] + } + ] + metadata: {version: "1.0", created: 2024-01-01} +} +# Match expressions +def classify [x: int] { match $x { + 0 => "zero" + 1..10 => "small" + _ => "large" +} } +# Closures with multiple parameters +let transform = {|x, y, z| ($x + $y) * $z } +# Pipelines with closures +let processed = [1, 2, 3, 4, 5] | each {|n| $n * 2 } | where {|n| $n > 4 } +# Error handling with catch +def safe_divide [a: int, b: int] { + try { + if $b == 0 { error make {msg: "Division by zero"} } + $a / $b + } catch { + print $"Error: ($err.msg)" + null + } +} +# Conditional with else if +def grade [score: int] { + if $score >= 90 { "A" } else if $score >= 80 { "B" } else if $score >= 70 { "C" } else { "F" } +} +# Nested loops +for i in 1..3 { + for j in 1..3 { print $"($i), ($j)" } +} +# Table with complex data +let report = [[name, department, salary]; ["Alice", "Engineering", 100000], ["Bob", "Marketing", 80000], ["Carol", "Engineering", 95000]] +# String with multiple interpolations +let message = $"User ($complex_data.users.0.name) has scores: ($complex_data.users.0.scores | str join ', ')" +# Range operations +let numbers = 1..100 | where {|n| $n mod 2 == 0 } | take 10 +# Record spread +let base_config = {host: "localhost", port: 8080} +let full_config = {...$base_config, debug: true, timeout: 30} +# List spread +let list1 = [1, 2, 3] +let list2 = [0, ...$list1, 4, 5] +# Multiline string (if supported) +let long_text = "This is a +multiline +string" +# Binary operations with parentheses +let result = (1 + 2) * (3 + 4) / 2 +# Comparison chains +let in_range = $result > 0 and $result < 100 +# Null coalescing (if supported) +let value = $env.? | default "fallback" +# Return from function +def early_return [x: int] { + if $x < 0 { return "negative" } + if $x == 0 { return "zero" } + "positive" +} +# Final comment +# End of complex test file diff --git a/tests/fixtures/expected/alias.nu b/tests/fixtures/expected/alias.nu new file mode 100644 index 0000000..9077dbd --- /dev/null +++ b/tests/fixtures/expected/alias.nu @@ -0,0 +1,6 @@ +alias ll = ls -l +alias la = ls -a +alias lla = ls -la +alias grep = grep --color=auto +alias myalias = echo "hello world" +alias complex = ls | get name | first diff --git a/tests/fixtures/expected/binary_ops.nu b/tests/fixtures/expected/binary_ops.nu new file mode 100644 index 0000000..db86e82 --- /dev/null +++ b/tests/fixtures/expected/binary_ops.nu @@ -0,0 +1,26 @@ +1 + 2 +3 - 1 +2 * 4 +8 / 2 +10 mod 3 +2 ** 3 +$x + $y +$a * $b + $c +(1 + 2) * 3 +1 + (2 * 3) +true and false +true or false +not true +$x > 0 and $x < 10 +$a == $b +$a != $b +$a < $b +$a <= $b +$a > $b +$a >= $b +"hello" ++ " world" +"hello" =~ "ell" +"hello" !~ "xyz" +[1, 2] ++ [3, 4] +1 in [1, 2, 3] +4 not-in [1, 2, 3] diff --git a/tests/fixtures/expected/break_continue.nu b/tests/fixtures/expected/break_continue.nu new file mode 100644 index 0000000..6241809 --- /dev/null +++ b/tests/fixtures/expected/break_continue.nu @@ -0,0 +1,17 @@ +loop { break } +loop { break } +for x in [1, 2, 3] { + if $x == 2 { break } +} +for x in [1, 2, 3] { + if $x == 2 { continue } +} +while true { break } +while $x > 0 { + if $x == 5 { continue } + $x = $x - 1 +} +loop { + if $done { break } + continue +} diff --git a/tests/fixtures/expected/cell_path.nu b/tests/fixtures/expected/cell_path.nu new file mode 100644 index 0000000..e6db170 --- /dev/null +++ b/tests/fixtures/expected/cell_path.nu @@ -0,0 +1,6 @@ +$record.name +$record.a.b.c +$list.0 +$list.5 +$data.users.0.name +$table.column.0 diff --git a/tests/fixtures/expected/closure.nu b/tests/fixtures/expected/closure.nu new file mode 100644 index 0000000..29be762 --- /dev/null +++ b/tests/fixtures/expected/closure.nu @@ -0,0 +1,8 @@ +{|| 1 } +{|x| $x } +{|x| $x * 2 } +{|x| $x * 2 } +{|x, y| $x + $y } +{|x: int| $x * 2 } +{|x: int, y: int| $x + $y } +{|x| $x * 2 } diff --git a/tests/fixtures/expected/comment.nu b/tests/fixtures/expected/comment.nu new file mode 100644 index 0000000..5fa40ff --- /dev/null +++ b/tests/fixtures/expected/comment.nu @@ -0,0 +1,12 @@ +# This is a comment +let x = 1 +# Another comment without space +let y = 2 +let z = 3 # inline comment +# Multiple +# consecutive +# comments +def foo [] { 1 } +def bar [] { +# comment inside block +print "hello" } diff --git a/tests/fixtures/expected/const_statement.nu b/tests/fixtures/expected/const_statement.nu new file mode 100644 index 0000000..ac9012b --- /dev/null +++ b/tests/fixtures/expected/const_statement.nu @@ -0,0 +1,5 @@ +const x = 1 +const y = 2 +const PI = 3.14159 +const name = "hello" +const result = 1 + 2 diff --git a/tests/fixtures/expected/datetime.nu b/tests/fixtures/expected/datetime.nu new file mode 100644 index 0000000..b81bf2a --- /dev/null +++ b/tests/fixtures/expected/datetime.nu @@ -0,0 +1,8 @@ +2024-01-15 +2024-01-15T10:30:00 +2024-01-15T10:30:00+05:00 +2024-01-15T10:30:00Z +2024-12-31 +2000-01-01T00:00:00 +1970-01-01T00:00:00Z +2024-06-15T14:30:00-07:00 diff --git a/tests/fixtures/expected/def_statement.nu b/tests/fixtures/expected/def_statement.nu new file mode 100644 index 0000000..fb896eb --- /dev/null +++ b/tests/fixtures/expected/def_statement.nu @@ -0,0 +1,14 @@ +def foo [] { 1 } +def bar [x] { $x } +def add [a: int, b: int] { $a + $b } +def greet [name: string] { $"Hello ($name)!" } +def with_default [x: int = 10] { $x * 2 } +def with_flag [--verbose(-v)] { + if $verbose { print "verbose" } +} +def complex [ + a: int + b: string + --flag(-f) + --value(-v): int = 5 +] { print $"($a) ($b) $flag $value" } diff --git a/tests/fixtures/expected/do_block.nu b/tests/fixtures/expected/do_block.nu new file mode 100644 index 0000000..d8ede7b --- /dev/null +++ b/tests/fixtures/expected/do_block.nu @@ -0,0 +1,14 @@ +do { print "hello" } +do { print "hello" } +do { $x + 1 } +do { + ls | get name +} +do --ignore-errors { risky_command } +do -i { might_fail } +do { + let x = 1 + $x + 2 +} +do --env { $env.PATH } +do {|x| $x * 2 } diff --git a/tests/fixtures/expected/error_make.nu b/tests/fixtures/expected/error_make.nu new file mode 100644 index 0000000..c005227 --- /dev/null +++ b/tests/fixtures/expected/error_make.nu @@ -0,0 +1,22 @@ +error make {msg: "simple error"} +error make {msg: "simple error"} +error make { + msg: "error message" + label: {text: "label text"} +} +error make { + msg: "error" + label: { + text: "here" + span: $span + } +} +error make {msg: "test error"} +error make { + msg: "detailed error" + label: {text: "error occurred here"} +} +error make { + msg: $"interpolated ($value) error" +} +if $invalid { error make {msg: "validation failed"} } diff --git a/tests/fixtures/expected/export.nu b/tests/fixtures/expected/export.nu new file mode 100644 index 0000000..4185ef0 --- /dev/null +++ b/tests/fixtures/expected/export.nu @@ -0,0 +1,5 @@ +export def foo [] { 1 } +export def bar [x: int] { $x * 2 } +export def add [a: int, b: int] { $a + $b } +export alias ll = ls -l +export alias la = ls -a diff --git a/tests/fixtures/expected/extern.nu b/tests/fixtures/expected/extern.nu new file mode 100644 index 0000000..2da6cd5 --- /dev/null +++ b/tests/fixtures/expected/extern.nu @@ -0,0 +1,24 @@ +extern "git" [] +extern "git status" [--short(-s)] +extern "cargo build" [--release --target: string] +extern "npm" [ + command: string + --global(-g) + --save-dev(-D) +] +extern "docker run" [ + image: string + --detach(-d) + --name: string + --port(-p): string + ...args: string +] +extern ls [] +extern cat [file: path] +extern grep [ + pattern: string + ...files: path + -i + -r + -n +] diff --git a/tests/fixtures/expected/external_call.nu b/tests/fixtures/expected/external_call.nu new file mode 100644 index 0000000..9651d87 --- /dev/null +++ b/tests/fixtures/expected/external_call.nu @@ -0,0 +1,5 @@ +^git status +^echo "hello world" +^git log --oneline +^ls -la /tmp +^command arg1 arg2 diff --git a/tests/fixtures/expected/for_loop.nu b/tests/fixtures/expected/for_loop.nu new file mode 100644 index 0000000..78e5788 --- /dev/null +++ b/tests/fixtures/expected/for_loop.nu @@ -0,0 +1,5 @@ +for x in [1, 2, 3] { print $x } +for x in [1, 2, 3] { print $x } +for item in $list { print $item } +for i in 1..10 { print $i } +for item in [1, 2, 3] { print $item } diff --git a/tests/fixtures/expected/glob_pattern.nu b/tests/fixtures/expected/glob_pattern.nu new file mode 100644 index 0000000..414f138 --- /dev/null +++ b/tests/fixtures/expected/glob_pattern.nu @@ -0,0 +1,10 @@ +*.nu +**/*.rs +src/**/*.nu +test?.nu +[abc].txt +file[0-9].nu +path/to/*.txt +**/* +*.{nu,rs} +!excluded.nu diff --git a/tests/fixtures/expected/hide.nu b/tests/fixtures/expected/hide.nu new file mode 100644 index 0000000..606cd32 --- /dev/null +++ b/tests/fixtures/expected/hide.nu @@ -0,0 +1,7 @@ +hide foo +hide foo +hide mymodule +hide mymodule bar +hide-env FOO +hide-env FOO +hide-env PATH diff --git a/tests/fixtures/expected/if_else.nu b/tests/fixtures/expected/if_else.nu new file mode 100644 index 0000000..5c2174f --- /dev/null +++ b/tests/fixtures/expected/if_else.nu @@ -0,0 +1,9 @@ +if true { print "yes" } +if false { print "no" } +if true { print "yes" } else { print "no" } +if true { print "yes" } else { print "no" } +if $x > 0 { print "positive" } else if $x < 0 { print "negative" } else { print "zero" } +if true { + print "multiline" + print "body" +} diff --git a/tests/fixtures/expected/let_statement.nu b/tests/fixtures/expected/let_statement.nu new file mode 100644 index 0000000..42637ef --- /dev/null +++ b/tests/fixtures/expected/let_statement.nu @@ -0,0 +1,5 @@ +let x = 1 +let y = 2 +let name = "hello" +let result = $x + $y +let long_name = "this is a long string" diff --git a/tests/fixtures/expected/list.nu b/tests/fixtures/expected/list.nu new file mode 100644 index 0000000..ae79195 --- /dev/null +++ b/tests/fixtures/expected/list.nu @@ -0,0 +1,12 @@ +[] +[1, 2, 3] +[1, 2, 3] +[1, 2, 3] +["a", "b", "c"] +[1, "two", 3.0, true] +[1, 2, 3] +[ + [1, 2] + [3, 4] + [5, 6] +] diff --git a/tests/fixtures/expected/loop_statement.nu b/tests/fixtures/expected/loop_statement.nu new file mode 100644 index 0000000..3aa2247 --- /dev/null +++ b/tests/fixtures/expected/loop_statement.nu @@ -0,0 +1,9 @@ +loop { break } +loop { break } +loop { + if $x > 10 { break } +} +loop { + $x = $x + 1 + if $x > 10 { break } +} diff --git a/tests/fixtures/expected/match_expr.nu b/tests/fixtures/expected/match_expr.nu new file mode 100644 index 0000000..0ec71cd --- /dev/null +++ b/tests/fixtures/expected/match_expr.nu @@ -0,0 +1,31 @@ +match $x { + 0=>"zero" => 1=>"one" +} +match $x { + 0 => "zero" + _ => "other" +} +match $x { + 0 => "zero" + 1 => "one" + _ => "other" +} +match $value { + "a"=>"alpha" => "b"=>"beta" +} +match $num { + 0 => "zero" + 1..10 => "small" + _ => "large" +} +match $data { + {type: "user"} => "is user" + {type: "admin"} => "is admin" + _ => "unknown type" +} +match $list { + [] => "empty" + [x] => $"single: ($x)" + [x, y] => $"pair: ($x), ($y)" + _ => "many" +} diff --git a/tests/fixtures/expected/module.nu b/tests/fixtures/expected/module.nu new file mode 100644 index 0000000..1fc22ca --- /dev/null +++ b/tests/fixtures/expected/module.nu @@ -0,0 +1,11 @@ +module mymod { + export def foo [] { 1 } +} +module math { + export def add [a, b] { $a + $b } + export def sub [a, b] { $a - $b } +} +module utils { + export def greet [name] { $"Hello ($name)" } + export def farewell [name] { $"Goodbye ($name)" } +} diff --git a/tests/fixtures/expected/multiline_pipeline.nu b/tests/fixtures/expected/multiline_pipeline.nu new file mode 100644 index 0000000..ec6509c --- /dev/null +++ b/tests/fixtures/expected/multiline_pipeline.nu @@ -0,0 +1,7 @@ +ls | where type == "file" | get name +ls | get name | first +[1, 2, 3] | each {|x| $x * 2 } | where {|x| $x > 2 } +$data | group-by category | transpose key value | each {|row| {name: $row.key, count: ($row.value | length)} } +open file.txt | lines | length +ls | sort-by size | reverse | first 5 +$table | select name age | where age > 18 diff --git a/tests/fixtures/expected/mut_statement.nu b/tests/fixtures/expected/mut_statement.nu new file mode 100644 index 0000000..80ba2ca --- /dev/null +++ b/tests/fixtures/expected/mut_statement.nu @@ -0,0 +1,5 @@ +mut x = 1 +mut y = 2 +mut counter = 0 +mut name = "hello" +mut result = $x + $y diff --git a/tests/fixtures/expected/nested_structures.nu b/tests/fixtures/expected/nested_structures.nu new file mode 100644 index 0000000..8711748 --- /dev/null +++ b/tests/fixtures/expected/nested_structures.nu @@ -0,0 +1,87 @@ +# Nested structures ground truth +# Nested records +{ + a: { + b: {c: 1} + } +} +{ + a: { + b: {c: 1} + } +} +{ + outer: { + middle: {inner: "value"} + } +} +# Nested lists +[ + [1, 2] + [3, 4] + [5, 6] +] +[ + [1, 2] + [3, 4] + [5, 6] +] +[ + [1, 2, 3] + [4, 5, 6] + [7, 8, 9] +] +# Records containing lists +{ + names: ["Alice", "Bob"] + ages: [30, 25] +} +{ + data: [1, 2, 3] + labels: ["a", "b", "c"] +} +# Lists containing records +[ + {name: "Alice"} + {name: "Bob"} +] +[ + {id: 1, value: "first"} + {id: 2, value: "second"} +] +# Deeply nested mixed structures +{ + users: [ + { + name: "Alice" + scores: [95, 87, 92] + metadata: {active: true} + } + { + name: "Bob" + scores: [88, 91, 85] + metadata: {active: false} + } + ] + config: { + version: "1.0" + settings: {debug: true, verbose: false} + } +} +# Nested closures in data +let transform = {|data| + $data | each {|item| {|x| $x * $item } } +} +# Nested control flow +if $outer { + if $inner { + if $deep { "very deep" } else { "deep" } + } +} +# Nested function definitions +def outer [] { + def inner [] { "inner result" } + inner +} +# Complex pipeline with nested structures +$data | each {|row| {name: $row.name, values: ($row.items | each {|i| $i * 2 })} } | where {|r| ($r.values | length) > 0 } diff --git a/tests/fixtures/expected/nothing.nu b/tests/fixtures/expected/nothing.nu new file mode 100644 index 0000000..1492fe0 --- /dev/null +++ b/tests/fixtures/expected/nothing.nu @@ -0,0 +1,7 @@ +null +null +let x = null +if $value == null { "is null" } +$record.field | default null +[1, null, 3] +{a: null, b: 2} diff --git a/tests/fixtures/expected/overlay.nu b/tests/fixtures/expected/overlay.nu new file mode 100644 index 0000000..f6d5daa --- /dev/null +++ b/tests/fixtures/expected/overlay.nu @@ -0,0 +1,11 @@ +overlay use mymod +overlay use mymod +overlay use module.nu +overlay use mod as alias +overlay hide mymod +overlay hide mymod +overlay hide +overlay list +overlay new temp +overlay use mymod --prefix +overlay hide mymod --keep-env [VAR1, VAR2] diff --git a/tests/fixtures/expected/pipeline.nu b/tests/fixtures/expected/pipeline.nu new file mode 100644 index 0000000..737afa4 --- /dev/null +++ b/tests/fixtures/expected/pipeline.nu @@ -0,0 +1,6 @@ +ls | get name +ls | get name +ls | get name | first +[1, 2, 3] | each {|x| $x * 2 } +[1, 2, 3] | each {|x| $x * 2 } | where {|x| $x > 2 } +$data | where size > 1kb | sort-by name diff --git a/tests/fixtures/expected/range.nu b/tests/fixtures/expected/range.nu new file mode 100644 index 0000000..768df1b --- /dev/null +++ b/tests/fixtures/expected/range.nu @@ -0,0 +1,6 @@ +1..10 +1..100 +0..-5 +1..2..10 +0.. +..10 diff --git a/tests/fixtures/expected/record.nu b/tests/fixtures/expected/record.nu new file mode 100644 index 0000000..d70b577 --- /dev/null +++ b/tests/fixtures/expected/record.nu @@ -0,0 +1,13 @@ +{} +{a: 1} +{a: 1, b: 2} +{a: 1, b: 2} +{a: 1, b: 2} +{name: "Alice", age: 30} +{name: "Alice", age: 30, city: "NYC"} +{ + a: { + b: {c: 1} + } +} +{...$base, extra: true} diff --git a/tests/fixtures/expected/return_statement.nu b/tests/fixtures/expected/return_statement.nu new file mode 100644 index 0000000..423a773 --- /dev/null +++ b/tests/fixtures/expected/return_statement.nu @@ -0,0 +1,17 @@ +def returns_nothing [] { return } +def returns_one [] { return 1 } +def returns_42 [] { return 42 } +def returns_var [x] { return $x } +def returns_expr [x, y] { return ($x + $y) } +def returns_string [] { return "hello" } +def returns_list [] { return [1, 2, 3] } +def returns_record [] { return {a: 1, b: 2} } +def foo [] { return 1 } +def bar [x] { + if $x > 0 { return "positive" } + return "not positive" +} +def early [x] { + if $x == null { return } + $x +} diff --git a/tests/fixtures/expected/source.nu b/tests/fixtures/expected/source.nu new file mode 100644 index 0000000..bf79cdf --- /dev/null +++ b/tests/fixtures/expected/source.nu @@ -0,0 +1,10 @@ +source script.nu +source script.nu +source ./path/to/file.nu +source ~/scripts/utils.nu +source ../other/script.nu +source "path with spaces/script.nu" +source $script_path +source-env config.nu +source-env config.nu +source-env ./env_setup.nu diff --git a/tests/fixtures/expected/spread.nu b/tests/fixtures/expected/spread.nu new file mode 100644 index 0000000..b981928 --- /dev/null +++ b/tests/fixtures/expected/spread.nu @@ -0,0 +1,9 @@ +[...$list] +[1, ...$rest] +[...$first, ...$second] +{...$record} +{...$base, extra: true} +{a: 1, ...$rest, b: 2} +command ...$args +[0, ...$middle, 10] +{...$defaults, ...$overrides} diff --git a/tests/fixtures/expected/string_interpolation.nu b/tests/fixtures/expected/string_interpolation.nu new file mode 100644 index 0000000..c8305cf --- /dev/null +++ b/tests/fixtures/expected/string_interpolation.nu @@ -0,0 +1,5 @@ +$"hello" +$"Hello ($name)" +$"Result: ($x + $y)" +$"Multi ($a) values ($b) here ($c)" +$" spaces ($x) preserved " diff --git a/tests/fixtures/expected/subexpression.nu b/tests/fixtures/expected/subexpression.nu new file mode 100644 index 0000000..3e1cd40 --- /dev/null +++ b/tests/fixtures/expected/subexpression.nu @@ -0,0 +1,11 @@ +(1 + 2) +(1 + 2) +(1 + 2) +($x + $y) +(ls) +(ls | get name) +(ls | get name) +let result = (1 + 2) * 3 +let value = ($x + (($y * 2))) +print (echo "hello") +if (true) { print "yes" } diff --git a/tests/fixtures/expected/table.nu b/tests/fixtures/expected/table.nu new file mode 100644 index 0000000..de03714 --- /dev/null +++ b/tests/fixtures/expected/table.nu @@ -0,0 +1,10 @@ +[[a, b]; [1, 2], [3, 4]] +[[a, b]; [1, 2], [3, 4]] +[[name, age]; ["Alice", 30], ["Bob", 25]] +[[col1, col2, col3]; [1, 2, 3], [4, 5, 6], [7, 8, 9]] +[[a]; [1]] +[ + [header1, header2] + [val1, val2] + [val3, val4] +] diff --git a/tests/fixtures/expected/try_catch.nu b/tests/fixtures/expected/try_catch.nu new file mode 100644 index 0000000..31a2503 --- /dev/null +++ b/tests/fixtures/expected/try_catch.nu @@ -0,0 +1,6 @@ +try { error make {msg: "test"} } +try { error make {msg: "test"} } +try { error make {msg: "test"} } catch { print "caught" } +try { 1 / 0 } catch { print "error" } +try { risky_operation } catch { print "error occurred" } +try { risky } catch { print $err.msg } diff --git a/tests/fixtures/expected/use_statement.nu b/tests/fixtures/expected/use_statement.nu new file mode 100644 index 0000000..06714a4 --- /dev/null +++ b/tests/fixtures/expected/use_statement.nu @@ -0,0 +1,8 @@ +use std +use std +use std * +use std assert +use std [assert, log] +use module.nu +use module.nu foo +use module.nu [foo, bar] diff --git a/tests/fixtures/expected/value_with_unit.nu b/tests/fixtures/expected/value_with_unit.nu new file mode 100644 index 0000000..d2d0dbb --- /dev/null +++ b/tests/fixtures/expected/value_with_unit.nu @@ -0,0 +1,15 @@ +1kb +5mb +10gb +100ms +5sec +30min +2hr +1day +7wk +1024b +500ns +100us +10kb +5mb + 10mb +1hr + 30min diff --git a/tests/fixtures/expected/where_clause.nu b/tests/fixtures/expected/where_clause.nu new file mode 100644 index 0000000..893667f --- /dev/null +++ b/tests/fixtures/expected/where_clause.nu @@ -0,0 +1,10 @@ +ls | where size > 1kb +ls | where name == "test" +ls | where type == "file" +$data | where size > 1kb +$list | where name == "test" +$table | where $it.value > 10 +$records | where status == "active" and age > 18 +$items | where {|row| $row.count > 0 } +ls | where size > 1mb | where name =~ "\.rs$" +$data | where { $in.field | is-not-empty } diff --git a/tests/fixtures/expected/while_loop.nu b/tests/fixtures/expected/while_loop.nu new file mode 100644 index 0000000..1168ffc --- /dev/null +++ b/tests/fixtures/expected/while_loop.nu @@ -0,0 +1,7 @@ +while true { break } +while $x > 0 { $x = $x - 1 } +while $condition { print "looping" } +while $counter < 10 { + $counter = $counter + 1 + print $counter +} diff --git a/tests/fixtures/input/alias.nu b/tests/fixtures/input/alias.nu new file mode 100644 index 0000000..049cfdb --- /dev/null +++ b/tests/fixtures/input/alias.nu @@ -0,0 +1,6 @@ +alias ll = ls -l +alias la = ls -a +alias lla = ls -la +alias grep = grep --color=auto +alias myalias = echo "hello world" +alias complex = ls | get name | first diff --git a/tests/fixtures/input/binary_ops.nu b/tests/fixtures/input/binary_ops.nu new file mode 100644 index 0000000..0562ca6 --- /dev/null +++ b/tests/fixtures/input/binary_ops.nu @@ -0,0 +1,26 @@ +1 + 2 +3 - 1 +2 * 4 +8 / 2 +10 mod 3 +2 ** 3 +$x + $y +$a * $b + $c +(1 + 2) * 3 +1 + (2 * 3) +true and false +true or false +not true +$x > 0 and $x < 10 +$a == $b +$a != $b +$a < $b +$a <= $b +$a > $b +$a >= $b +"hello" ++ " world" +"hello" =~ "ell" +"hello" !~ "xyz" +[1, 2] ++ [3, 4] +1 in [1, 2, 3] +4 not-in [1, 2, 3] diff --git a/tests/fixtures/input/break_continue.nu b/tests/fixtures/input/break_continue.nu new file mode 100644 index 0000000..2731bbd --- /dev/null +++ b/tests/fixtures/input/break_continue.nu @@ -0,0 +1,17 @@ +loop { break } +loop { break } +for x in [1, 2, 3] { + if $x == 2 { break } +} +for x in [1, 2, 3] { + if $x == 2 { continue } +} +while true { break } +while $x > 0 { + if $x == 5 { continue } + $x = $x - 1 +} +loop { + if $done { break } + continue +} diff --git a/tests/fixtures/input/cell_path.nu b/tests/fixtures/input/cell_path.nu new file mode 100644 index 0000000..5f532e6 --- /dev/null +++ b/tests/fixtures/input/cell_path.nu @@ -0,0 +1,6 @@ + $record.name + $record.a.b.c + $list.0 + $list.5 + $data.users.0.name + $table.column.0 diff --git a/tests/fixtures/input/closure.nu b/tests/fixtures/input/closure.nu new file mode 100644 index 0000000..4db61ab --- /dev/null +++ b/tests/fixtures/input/closure.nu @@ -0,0 +1,8 @@ +{|| 1 } +{|x| $x } +{|x| $x * 2 } +{|x| $x * 2 } +{|x, y| $x + $y } +{|x: int| $x * 2 } +{|x: int, y: int| $x + $y } +{|x| $x * 2 } diff --git a/tests/fixtures/input/comment.nu b/tests/fixtures/input/comment.nu new file mode 100644 index 0000000..157fade --- /dev/null +++ b/tests/fixtures/input/comment.nu @@ -0,0 +1,12 @@ +# This is a comment +let x = 1 +# Another comment without space +let y = 2 +let z = 3 # inline comment +# Multiple +# consecutive +# comments +def foo [] { 1 } +def bar [] { +# comment inside block +print "hello" } diff --git a/tests/fixtures/input/const_statement.nu b/tests/fixtures/input/const_statement.nu new file mode 100644 index 0000000..7b8dfe7 --- /dev/null +++ b/tests/fixtures/input/const_statement.nu @@ -0,0 +1,5 @@ +const x = 1 +const y = 2 +const PI = 3.14159 +const name = "hello" +const result = 1 + 2 diff --git a/tests/fixtures/input/datetime.nu b/tests/fixtures/input/datetime.nu new file mode 100644 index 0000000..1f8dcf9 --- /dev/null +++ b/tests/fixtures/input/datetime.nu @@ -0,0 +1,8 @@ + 2024-01-15 + 2024-01-15T10:30:00 + 2024-01-15T10:30:00+05:00 + 2024-01-15T10:30:00Z + 2024-12-31 + 2000-01-01T00:00:00 + 1970-01-01T00:00:00Z + 2024-06-15T14:30:00-07:00 diff --git a/tests/fixtures/input/def_statement.nu b/tests/fixtures/input/def_statement.nu new file mode 100644 index 0000000..6b3ebcb --- /dev/null +++ b/tests/fixtures/input/def_statement.nu @@ -0,0 +1,14 @@ +def foo [] { 1 } +def bar [x] { $x } +def add [a: int, b: int] { $a + $b } +def greet [name: string] { $"Hello ($name)!" } +def with_default [x: int = 10] { $x * 2 } +def with_flag [--verbose(-v)] { + if $verbose { print "verbose" } +} +def complex [ + a: int + b: string + --flag(-f) + --value(-v): int = 5 +] { print $"($a) ($b) $flag $value" } diff --git a/tests/fixtures/input/do_block.nu b/tests/fixtures/input/do_block.nu new file mode 100644 index 0000000..7626fdd --- /dev/null +++ b/tests/fixtures/input/do_block.nu @@ -0,0 +1,14 @@ +do { print "hello" } +do { print "hello" } +do { $x + 1 } +do { + ls | get name +} +do --ignore-errors { risky_command } +do -i { might_fail } +do { + let x = 1 + $x + 2 +} +do --env { $env.PATH } +do {|x| $x * 2 } diff --git a/tests/fixtures/input/error_make.nu b/tests/fixtures/input/error_make.nu new file mode 100644 index 0000000..e390ce7 --- /dev/null +++ b/tests/fixtures/input/error_make.nu @@ -0,0 +1,22 @@ +error make {msg: "simple error"} +error make {msg:"simple error"} +error make { + msg: "error message" + label: {text: "label text"} +} +error make { + msg: "error" + label: { + text: "here" + span: $span + } +} +error make {msg: "test error"} +error make { + msg: "detailed error" + label: {text: "error occurred here"} +} +error make { + msg: $"interpolated ($value) error" +} +if $invalid { error make {msg: "validation failed"} } diff --git a/tests/fixtures/input/export.nu b/tests/fixtures/input/export.nu new file mode 100644 index 0000000..4d8b7a0 --- /dev/null +++ b/tests/fixtures/input/export.nu @@ -0,0 +1,5 @@ +export def foo [] { 1 } +export def bar [x: int] { $x * 2 } +export def add [a: int, b: int] { $a + $b } +export alias ll = ls -l +export alias la = ls -a diff --git a/tests/fixtures/input/extern.nu b/tests/fixtures/input/extern.nu new file mode 100644 index 0000000..0f92fce --- /dev/null +++ b/tests/fixtures/input/extern.nu @@ -0,0 +1,24 @@ +extern "git" [] +extern "git status" [--short(-s)] +extern "cargo build" [--release --target: string] +extern "npm" [ + command: string + --global(-g) + --save-dev(-D) +] +extern "docker run" [ + image: string + --detach(-d) + --name: string + --port(-p): string + ...args: string +] +extern ls [] +extern cat [file: path] +extern grep [ + pattern: string + ...files: path + -i + -r + -n +] diff --git a/tests/fixtures/input/external_call.nu b/tests/fixtures/input/external_call.nu new file mode 100644 index 0000000..e8d5869 --- /dev/null +++ b/tests/fixtures/input/external_call.nu @@ -0,0 +1,5 @@ + ^git status +^echo "hello world" + ^git log --oneline +^ls -la /tmp + ^command arg1 arg2 diff --git a/tests/fixtures/input/for_loop.nu b/tests/fixtures/input/for_loop.nu new file mode 100644 index 0000000..23a6d0b --- /dev/null +++ b/tests/fixtures/input/for_loop.nu @@ -0,0 +1,5 @@ +for x in [1, 2, 3] { print $x } +for x in [1, 2, 3] { print $x } +for item in $list { print $item } +for i in 1..10 { print $i } +for item in [1, 2, 3] { print $item } diff --git a/tests/fixtures/input/glob_pattern.nu b/tests/fixtures/input/glob_pattern.nu new file mode 100644 index 0000000..ab228cb --- /dev/null +++ b/tests/fixtures/input/glob_pattern.nu @@ -0,0 +1,10 @@ + *.nu + **/*.rs + src/**/*.nu + test?.nu + [abc].txt + file[0-9].nu + path/to/*.txt + **/* + *.{nu,rs} + !excluded.nu diff --git a/tests/fixtures/input/hide.nu b/tests/fixtures/input/hide.nu new file mode 100644 index 0000000..b061b0e --- /dev/null +++ b/tests/fixtures/input/hide.nu @@ -0,0 +1,7 @@ + hide foo + hide foo + hide mymodule + hide mymodule bar + hide-env FOO + hide-env FOO + hide-env PATH diff --git a/tests/fixtures/input/if_else.nu b/tests/fixtures/input/if_else.nu new file mode 100644 index 0000000..ce74e9e --- /dev/null +++ b/tests/fixtures/input/if_else.nu @@ -0,0 +1,9 @@ +if true { print "yes" } +if false { print "no" } +if true { print "yes" } else { print "no" } +if true { print "yes" } else { print "no" } +if $x > 0 { print "positive" } else if $x < 0 { print "negative" } else { print "zero" } +if true { + print "multiline" + print "body" +} diff --git a/tests/fixtures/input/let_statement.nu b/tests/fixtures/input/let_statement.nu new file mode 100644 index 0000000..8568ef0 --- /dev/null +++ b/tests/fixtures/input/let_statement.nu @@ -0,0 +1,5 @@ +let x = 1 +let y = 2 +let name = "hello" +let result = $x + $y +let long_name = "this is a long string" diff --git a/tests/fixtures/input/list.nu b/tests/fixtures/input/list.nu new file mode 100644 index 0000000..0ef0cdd --- /dev/null +++ b/tests/fixtures/input/list.nu @@ -0,0 +1,12 @@ +[ ] +[1,2,3] +[1, 2, 3] +[ 1 , 2 , 3 ] +["a", "b", "c"] +[1, "two", 3.0, true] +[ 1,2,3 ] +[ + [1, 2] + [3, 4] + [5, 6] +] diff --git a/tests/fixtures/input/loop_statement.nu b/tests/fixtures/input/loop_statement.nu new file mode 100644 index 0000000..bfd60d1 --- /dev/null +++ b/tests/fixtures/input/loop_statement.nu @@ -0,0 +1,9 @@ +loop { break } +loop { break } +loop { + if $x > 10 { break } +} +loop { + $x = $x + 1 + if $x > 10 { break } +} diff --git a/tests/fixtures/input/match_expr.nu b/tests/fixtures/input/match_expr.nu new file mode 100644 index 0000000..e4b3172 --- /dev/null +++ b/tests/fixtures/input/match_expr.nu @@ -0,0 +1,31 @@ +match $x { + 0=>"zero" => 1=>"one" +} +match $x { + 0 => "zero" + _ => "other" +} +match $x { + 0 => "zero" + 1 => "one" + _ => "other" +} +match $value { + "a"=>"alpha" => "b"=>"beta" +} +match $num { + 0 => "zero" + 1..10 => "small" + _ => "large" +} +match $data { + {type: "user"} => "is user" + {type: "admin"} => "is admin" + _ => "unknown type" +} +match $list { + [] => "empty" + [x] => $"single: ($x)" + [x, y] => $"pair: ($x), ($y)" + _ => "many" +} diff --git a/tests/fixtures/input/module.nu b/tests/fixtures/input/module.nu new file mode 100644 index 0000000..4f827ab --- /dev/null +++ b/tests/fixtures/input/module.nu @@ -0,0 +1,11 @@ +module mymod { + export def foo [] { 1 } +} +module math { + export def add [a, b] { $a + $b } + export def sub [a, b] { $a - $b } +} +module utils { + export def greet [name] { $"Hello ($name)" } + export def farewell [name] { $"Goodbye ($name)" } +} diff --git a/tests/fixtures/input/multiline_pipeline.nu b/tests/fixtures/input/multiline_pipeline.nu new file mode 100644 index 0000000..42f7799 --- /dev/null +++ b/tests/fixtures/input/multiline_pipeline.nu @@ -0,0 +1,7 @@ + ls | where type == "file" | get name + ls | get name | first + [1, 2, 3] | each {|x| $x * 2 } | where {|x| $x > 2 } + $data | group-by category | transpose key value | each {|row| {name: $row.key, count: ($row.value | length)} } + open file.txt | lines | length + ls | sort-by size | reverse | first 5 + $table | select name age | where age > 18 diff --git a/tests/fixtures/input/mut_statement.nu b/tests/fixtures/input/mut_statement.nu new file mode 100644 index 0000000..b00b390 --- /dev/null +++ b/tests/fixtures/input/mut_statement.nu @@ -0,0 +1,5 @@ +mut x = 1 +mut y = 2 +mut counter = 0 +mut name = "hello" +mut result = $x + $y diff --git a/tests/fixtures/input/nested_structures.nu b/tests/fixtures/input/nested_structures.nu new file mode 100644 index 0000000..fba3f7d --- /dev/null +++ b/tests/fixtures/input/nested_structures.nu @@ -0,0 +1,87 @@ +# Nested structures ground truth +# Nested records + { + a: { + b: {c: 1} + } +} + { + a: { + b: {c: 1} + } +} + { + outer: { + middle: {inner: "value"} + } +} +# Nested lists + [ + [1, 2] + [3, 4] + [5, 6] +] + [ + [1, 2] + [3, 4] + [5, 6] +] + [ + [1, 2, 3] + [4, 5, 6] + [7, 8, 9] +] +# Records containing lists + { + names: ["Alice", "Bob"] + ages: [30, 25] +} + { + data: [1, 2, 3] + labels: ["a", "b", "c"] +} +# Lists containing records + [ + {name: "Alice"} + {name: "Bob"} +] + [ + {id: 1, value: "first"} + {id: 2, value: "second"} +] +# Deeply nested mixed structures + { + users: [ + { + name: "Alice" + scores: [95, 87, 92] + metadata: {active: true} + } + { + name: "Bob" + scores: [88, 91, 85] + metadata: {active: false} + } + ] + config: { + version: "1.0" + settings: {debug: true, verbose: false} + } +} +# Nested closures in data + let transform = {|data| + $data | each {|item| {|x| $x * $item } } +} +# Nested control flow + if $outer { + if $inner { + if $deep { "very deep" } else { "deep" } + } +} +# Nested function definitions + def outer [] { + def inner [] { "inner result" } + inner +} +# Complex pipeline with nested structures + $data | each {|row| {name: $row.name, values: ($row.items | each {|i| $i * 2 })} } | where {|r| ($r.values | length) > 0 } diff --git a/tests/fixtures/input/nothing.nu b/tests/fixtures/input/nothing.nu new file mode 100644 index 0000000..2ea1579 --- /dev/null +++ b/tests/fixtures/input/nothing.nu @@ -0,0 +1,7 @@ + null + null +let x = null +if $value == null { "is null" } +$record.field | default null +[1, null, 3] +{a: null, b: 2} diff --git a/tests/fixtures/input/overlay.nu b/tests/fixtures/input/overlay.nu new file mode 100644 index 0000000..b0590fc --- /dev/null +++ b/tests/fixtures/input/overlay.nu @@ -0,0 +1,11 @@ +overlay use mymod +overlay use mymod +overlay use module.nu +overlay use mod as alias +overlay hide mymod +overlay hide mymod +overlay hide +overlay list +overlay new temp +overlay use mymod --prefix +overlay hide mymod --keep-env [VAR1, VAR2] diff --git a/tests/fixtures/input/pipeline.nu b/tests/fixtures/input/pipeline.nu new file mode 100644 index 0000000..1c1e407 --- /dev/null +++ b/tests/fixtures/input/pipeline.nu @@ -0,0 +1,6 @@ + ls | get name + ls | get name + ls | get name | first + [1, 2, 3] | each {|x| $x * 2 } + [1, 2, 3] | each {|x| $x * 2 } | where {|x| $x > 2 } + $data | where size > 1kb | sort-by name diff --git a/tests/fixtures/input/range.nu b/tests/fixtures/input/range.nu new file mode 100644 index 0000000..8ed1a15 --- /dev/null +++ b/tests/fixtures/input/range.nu @@ -0,0 +1,6 @@ + 1..10 + 1..100 + 0..-5 + 1..2..10 + 0.. + ..10 diff --git a/tests/fixtures/input/record.nu b/tests/fixtures/input/record.nu new file mode 100644 index 0000000..f26aeef --- /dev/null +++ b/tests/fixtures/input/record.nu @@ -0,0 +1,13 @@ +{ } +{a: 1} +{a:1, b:2} +{a: 1, b: 2} +{a: 1, b: 2} +{name: "Alice", age: 30} +{name: "Alice", age: 30, city: "NYC"} +{ + a: { + b: {c: 1} + } +} +{...$base, extra: true} diff --git a/tests/fixtures/input/return_statement.nu b/tests/fixtures/input/return_statement.nu new file mode 100644 index 0000000..b5d94d3 --- /dev/null +++ b/tests/fixtures/input/return_statement.nu @@ -0,0 +1,17 @@ +def returns_nothing [] { return } +def returns_one [] { return 1 } +def returns_42 [] { return 42 } +def returns_var [x] { return $x } +def returns_expr [x, y] { return ($x + $y) } +def returns_string [] { return "hello" } +def returns_list [] { return [1, 2, 3] } +def returns_record [] { return {a: 1, b: 2} } +def foo [] { return 1 } +def bar [x] { + if $x > 0 { return "positive" } + return "not positive" +} +def early [x] { + if $x == null { return } + $x +} diff --git a/tests/fixtures/input/source.nu b/tests/fixtures/input/source.nu new file mode 100644 index 0000000..f952b5a --- /dev/null +++ b/tests/fixtures/input/source.nu @@ -0,0 +1,10 @@ + source script.nu + source script.nu + source ./path/to/file.nu + source ~/scripts/utils.nu + source ../other/script.nu + source "path with spaces/script.nu" + source $script_path + source-env config.nu + source-env config.nu + source-env ./env_setup.nu diff --git a/tests/fixtures/input/spread.nu b/tests/fixtures/input/spread.nu new file mode 100644 index 0000000..d87944e --- /dev/null +++ b/tests/fixtures/input/spread.nu @@ -0,0 +1,9 @@ +[ ...$list ] +[1, ...$rest] +[...$first, ...$second] +{ ...$record } +{...$base, extra: true} +{a: 1, ...$rest, b: 2} +command ...$args +[0, ...$middle, 10] +{...$defaults, ...$overrides} diff --git a/tests/fixtures/input/string_interpolation.nu b/tests/fixtures/input/string_interpolation.nu new file mode 100644 index 0000000..1339550 --- /dev/null +++ b/tests/fixtures/input/string_interpolation.nu @@ -0,0 +1,5 @@ + $"hello" +$"Hello ($name)" + $"Result: ($x + $y)" +$"Multi ($a) values ($b) here ($c)" + $" spaces ($x) preserved " diff --git a/tests/fixtures/input/subexpression.nu b/tests/fixtures/input/subexpression.nu new file mode 100644 index 0000000..27a6625 --- /dev/null +++ b/tests/fixtures/input/subexpression.nu @@ -0,0 +1,11 @@ +(1 + 2) +( 1 + 2 ) +(1 + 2) +($x + $y) +( ls ) +(ls | get name) +( ls | get name ) +let result = (1 + 2) * 3 +let value = ($x + (($y * 2))) +print (echo "hello") +if (true) { print "yes" } diff --git a/tests/fixtures/input/table.nu b/tests/fixtures/input/table.nu new file mode 100644 index 0000000..822b7d4 --- /dev/null +++ b/tests/fixtures/input/table.nu @@ -0,0 +1,10 @@ +[[a, b]; [1, 2], [3, 4]] +[[a, b]; [1, 2], [3, 4]] +[[name, age]; ["Alice", 30], ["Bob", 25]] +[[col1, col2, col3]; [1, 2, 3], [4, 5, 6], [7, 8, 9]] +[[a]; [1]] +[ + [header1, header2] + [val1, val2] + [val3, val4] +] diff --git a/tests/fixtures/input/try_catch.nu b/tests/fixtures/input/try_catch.nu new file mode 100644 index 0000000..2e83156 --- /dev/null +++ b/tests/fixtures/input/try_catch.nu @@ -0,0 +1,6 @@ +try { error make {msg: "test"} } +try { error make {msg: "test"} } +try { error make {msg: "test"} } catch { print "caught" } +try { 1 / 0 } catch { print "error" } +try { risky_operation } catch { print "error occurred" } +try { risky } catch { print $err.msg } diff --git a/tests/fixtures/input/use_statement.nu b/tests/fixtures/input/use_statement.nu new file mode 100644 index 0000000..ba26a53 --- /dev/null +++ b/tests/fixtures/input/use_statement.nu @@ -0,0 +1,8 @@ +use std +use std +use std * +use std assert +use std [assert, log] +use module.nu +use module.nu foo +use module.nu [foo, bar] diff --git a/tests/fixtures/input/value_with_unit.nu b/tests/fixtures/input/value_with_unit.nu new file mode 100644 index 0000000..3909e06 --- /dev/null +++ b/tests/fixtures/input/value_with_unit.nu @@ -0,0 +1,15 @@ + 1kb +5mb + 10gb +100ms + 5sec + 30min +2hr + 1day + 7wk +1024b + 500ns + 100us + 10kb +5mb + 10mb +1hr + 30min diff --git a/tests/fixtures/input/where_clause.nu b/tests/fixtures/input/where_clause.nu new file mode 100644 index 0000000..064d740 --- /dev/null +++ b/tests/fixtures/input/where_clause.nu @@ -0,0 +1,10 @@ + ls | where size > 1kb + ls | where name == "test" + ls | where type == "file" + $data | where size > 1kb + $list | where name == "test" + $table | where $it.value > 10 + $records | where status == "active" and age > 18 + $items | where {|row| $row.count > 0 } + ls | where size > 1mb | where name =~ "\.rs$" + $data | where { $in.field | is-not-empty } diff --git a/tests/fixtures/input/while_loop.nu b/tests/fixtures/input/while_loop.nu new file mode 100644 index 0000000..963b14d --- /dev/null +++ b/tests/fixtures/input/while_loop.nu @@ -0,0 +1,7 @@ +while true { break } +while $x > 0 { $x = $x - 1 } +while $condition { print "looping" } +while $counter < 10 { + $counter = $counter + 1 + print $counter +} diff --git a/tests/ground_truth.rs b/tests/ground_truth.rs new file mode 100644 index 0000000..813be50 --- /dev/null +++ b/tests/ground_truth.rs @@ -0,0 +1,650 @@ +//! Ground truth tests for nufmt +//! +//! These tests compare formatter output against expected ground truth files. +//! Each construct has a separate input and expected file for easy editing. + +use std::fs; +use std::path::PathBuf; +use std::process::Command; + +const TEST_BINARY: &str = "target/debug/nufmt"; + +/// Helper to run the formatter on input and compare with expected output +fn run_ground_truth_test(name: &str) { + let input_path = PathBuf::from(format!("tests/fixtures/input/{}.nu", name)); + let expected_path = PathBuf::from(format!("tests/fixtures/expected/{}.nu", name)); + + // Ensure files exist + assert!( + input_path.exists(), + "Input file not found: {:?}", + input_path + ); + assert!( + expected_path.exists(), + "Expected file not found: {:?}", + expected_path + ); + + // Read input + let input = fs::read_to_string(&input_path).expect("Failed to read input file"); + + // Run formatter via stdin + let output = Command::new(TEST_BINARY) + .arg("--stdin") + .stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .expect("Failed to spawn nufmt"); + + use std::io::Write; + output + .stdin + .as_ref() + .unwrap() + .write_all(input.as_bytes()) + .expect("Failed to write to stdin"); + + let output = output.wait_with_output().expect("Failed to wait for nufmt"); + + // Check for errors + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + panic!( + "Formatter failed for {}: exit code {:?}\nstderr: {}", + name, + output.status.code(), + stderr + ); + } + + // Get formatted output + let formatted = String::from_utf8(output.stdout).expect("Invalid UTF-8 in output"); + + // Read expected output + let expected = fs::read_to_string(&expected_path).expect("Failed to read expected file"); + + // Compare (normalize line endings) + let formatted_normalized = formatted.trim().replace("\r\n", "\n"); + let expected_normalized = expected.trim().replace("\r\n", "\n"); + + if formatted_normalized != expected_normalized { + // Print detailed diff + eprintln!("=== Ground truth test failed for: {} ===", name); + eprintln!("\n--- Expected ---"); + eprintln!("{}", expected_normalized); + eprintln!("\n--- Got ---"); + eprintln!("{}", formatted_normalized); + eprintln!("\n--- Diff ---"); + + // Line by line diff + let expected_lines: Vec<&str> = expected_normalized.lines().collect(); + let formatted_lines: Vec<&str> = formatted_normalized.lines().collect(); + + let max_lines = expected_lines.len().max(formatted_lines.len()); + for i in 0..max_lines { + let exp = expected_lines.get(i).unwrap_or(&""); + let got = formatted_lines.get(i).unwrap_or(&""); + if exp != got { + eprintln!("Line {}: ", i + 1); + eprintln!(" expected: {:?}", exp); + eprintln!(" got: {:?}", got); + } + } + + panic!("Ground truth mismatch for {}. See diff above.", name); + } +} + +/// Test that formatting is idempotent (formatting twice gives same result) +fn run_idempotency_test(name: &str) { + let input_path = PathBuf::from(format!("tests/fixtures/input/{}.nu", name)); + + if !input_path.exists() { + return; // Skip if input doesn't exist + } + + let input = fs::read_to_string(&input_path).expect("Failed to read input file"); + + // First format + let first_output = format_via_stdin(&input); + if first_output.is_err() { + return; // Skip if formatting fails + } + let first = first_output.unwrap(); + + // Second format + let second_output = format_via_stdin(&first); + if second_output.is_err() { + panic!("Second format failed for {}, but first succeeded", name); + } + let second = second_output.unwrap(); + + if first != second { + eprintln!("=== Idempotency test failed for: {} ===", name); + eprintln!("\n--- First format ---"); + eprintln!("{}", first); + eprintln!("\n--- Second format ---"); + eprintln!("{}", second); + panic!("Formatting is not idempotent for {}", name); + } +} + +fn format_via_stdin(input: &str) -> Result { + let output = Command::new(TEST_BINARY) + .arg("--stdin") + .stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .expect("Failed to spawn nufmt"); + + use std::io::Write; + output + .stdin + .as_ref() + .unwrap() + .write_all(input.as_bytes()) + .expect("Failed to write to stdin"); + + let output = output.wait_with_output().expect("Failed to wait for nufmt"); + + if output.status.success() { + Ok(String::from_utf8(output.stdout).expect("Invalid UTF-8")) + } else { + Err(String::from_utf8_lossy(&output.stderr).to_string()) + } +} + +// ============================================================================ +// Ground Truth Tests - Core Language Constructs +// ============================================================================ + +#[test] +fn ground_truth_let_statement() { + run_ground_truth_test("let_statement"); +} + +#[test] +fn ground_truth_mut_statement() { + run_ground_truth_test("mut_statement"); +} + +#[test] +fn ground_truth_const_statement() { + run_ground_truth_test("const_statement"); +} + +#[test] +fn ground_truth_def_statement() { + run_ground_truth_test("def_statement"); +} + +// ============================================================================ +// Ground Truth Tests - Control Flow +// ============================================================================ + +#[test] +fn ground_truth_if_else() { + run_ground_truth_test("if_else"); +} + +#[test] +fn ground_truth_for_loop() { + run_ground_truth_test("for_loop"); +} + +#[test] +fn ground_truth_while_loop() { + run_ground_truth_test("while_loop"); +} + +#[test] +fn ground_truth_loop_statement() { + run_ground_truth_test("loop_statement"); +} + +#[test] +fn ground_truth_match_expr() { + run_ground_truth_test("match_expr"); +} + +#[test] +fn ground_truth_try_catch() { + run_ground_truth_test("try_catch"); +} + +#[test] +fn ground_truth_break_continue() { + run_ground_truth_test("break_continue"); +} + +#[test] +fn ground_truth_return_statement() { + run_ground_truth_test("return_statement"); +} + +// ============================================================================ +// Ground Truth Tests - Data Structures +// ============================================================================ + +#[test] +fn ground_truth_list() { + run_ground_truth_test("list"); +} + +#[test] +fn ground_truth_record() { + run_ground_truth_test("record"); +} + +#[test] +fn ground_truth_table() { + run_ground_truth_test("table"); +} + +#[test] +fn ground_truth_nested_structures() { + run_ground_truth_test("nested_structures"); +} + +// ============================================================================ +// Ground Truth Tests - Pipelines and Expressions +// ============================================================================ + +#[test] +fn ground_truth_pipeline() { + run_ground_truth_test("pipeline"); +} + +#[test] +fn ground_truth_multiline_pipeline() { + run_ground_truth_test("multiline_pipeline"); +} + +#[test] +fn ground_truth_closure() { + run_ground_truth_test("closure"); +} + +#[test] +fn ground_truth_subexpression() { + run_ground_truth_test("subexpression"); +} + +#[test] +fn ground_truth_binary_ops() { + run_ground_truth_test("binary_ops"); +} + +#[test] +fn ground_truth_range() { + run_ground_truth_test("range"); +} + +#[test] +fn ground_truth_cell_path() { + run_ground_truth_test("cell_path"); +} + +#[test] +fn ground_truth_spread() { + run_ground_truth_test("spread"); +} + +// ============================================================================ +// Ground Truth Tests - Strings and Interpolation +// ============================================================================ + +#[test] +fn ground_truth_string_interpolation() { + run_ground_truth_test("string_interpolation"); +} + +#[test] +fn ground_truth_comment() { + run_ground_truth_test("comment"); +} + +// ============================================================================ +// Ground Truth Tests - Types and Values +// ============================================================================ + +#[test] +fn ground_truth_value_with_unit() { + run_ground_truth_test("value_with_unit"); +} + +#[test] +fn ground_truth_datetime() { + run_ground_truth_test("datetime"); +} + +#[test] +fn ground_truth_nothing() { + run_ground_truth_test("nothing"); +} + +#[test] +fn ground_truth_glob_pattern() { + run_ground_truth_test("glob_pattern"); +} + +// ============================================================================ +// Ground Truth Tests - Modules and Imports +// ============================================================================ + +#[test] +fn ground_truth_module() { + run_ground_truth_test("module"); +} + +#[test] +fn ground_truth_use_statement() { + run_ground_truth_test("use_statement"); +} + +#[test] +fn ground_truth_export() { + run_ground_truth_test("export"); +} + +#[test] +fn ground_truth_source() { + run_ground_truth_test("source"); +} + +#[test] +fn ground_truth_hide() { + run_ground_truth_test("hide"); +} + +#[test] +fn ground_truth_overlay() { + run_ground_truth_test("overlay"); +} + +// ============================================================================ +// Ground Truth Tests - Commands and Definitions +// ============================================================================ + +#[test] +fn ground_truth_alias() { + run_ground_truth_test("alias"); +} + +#[test] +fn ground_truth_extern() { + run_ground_truth_test("extern"); +} + +#[test] +fn ground_truth_external_call() { + run_ground_truth_test("external_call"); +} + +// ============================================================================ +// Ground Truth Tests - Special Constructs +// ============================================================================ + +#[test] +fn ground_truth_do_block() { + run_ground_truth_test("do_block"); +} + +#[test] +fn ground_truth_where_clause() { + run_ground_truth_test("where_clause"); +} + +#[test] +fn ground_truth_error_make() { + run_ground_truth_test("error_make"); +} + +// ============================================================================ +// Idempotency Tests - Core Language Constructs +// ============================================================================ + +#[test] +fn idempotency_let_statement() { + run_idempotency_test("let_statement"); +} + +#[test] +fn idempotency_mut_statement() { + run_idempotency_test("mut_statement"); +} + +#[test] +fn idempotency_const_statement() { + run_idempotency_test("const_statement"); +} + +#[test] +fn idempotency_def_statement() { + run_idempotency_test("def_statement"); +} + +// ============================================================================ +// Idempotency Tests - Control Flow +// ============================================================================ + +#[test] +fn idempotency_if_else() { + run_idempotency_test("if_else"); +} + +#[test] +fn idempotency_for_loop() { + run_idempotency_test("for_loop"); +} + +#[test] +fn idempotency_while_loop() { + run_idempotency_test("while_loop"); +} + +#[test] +fn idempotency_loop_statement() { + run_idempotency_test("loop_statement"); +} + +#[test] +fn idempotency_match_expr() { + run_idempotency_test("match_expr"); +} + +#[test] +fn idempotency_try_catch() { + run_idempotency_test("try_catch"); +} + +#[test] +fn idempotency_break_continue() { + run_idempotency_test("break_continue"); +} + +#[test] +fn idempotency_return_statement() { + run_idempotency_test("return_statement"); +} + +// ============================================================================ +// Idempotency Tests - Data Structures +// ============================================================================ + +#[test] +fn idempotency_list() { + run_idempotency_test("list"); +} + +#[test] +fn idempotency_record() { + run_idempotency_test("record"); +} + +#[test] +fn idempotency_table() { + run_idempotency_test("table"); +} + +#[test] +fn idempotency_nested_structures() { + run_idempotency_test("nested_structures"); +} + +// ============================================================================ +// Idempotency Tests - Pipelines and Expressions +// ============================================================================ + +#[test] +fn idempotency_pipeline() { + run_idempotency_test("pipeline"); +} + +#[test] +fn idempotency_multiline_pipeline() { + run_idempotency_test("multiline_pipeline"); +} + +#[test] +fn idempotency_closure() { + run_idempotency_test("closure"); +} + +#[test] +fn idempotency_subexpression() { + run_idempotency_test("subexpression"); +} + +#[test] +fn idempotency_binary_ops() { + run_idempotency_test("binary_ops"); +} + +#[test] +fn idempotency_range() { + run_idempotency_test("range"); +} + +#[test] +fn idempotency_cell_path() { + run_idempotency_test("cell_path"); +} + +#[test] +fn idempotency_spread() { + run_idempotency_test("spread"); +} + +// ============================================================================ +// Idempotency Tests - Strings and Interpolation +// ============================================================================ + +#[test] +fn idempotency_string_interpolation() { + run_idempotency_test("string_interpolation"); +} + +#[test] +fn idempotency_comment() { + run_idempotency_test("comment"); +} + +// ============================================================================ +// Idempotency Tests - Types and Values +// ============================================================================ + +#[test] +fn idempotency_value_with_unit() { + run_idempotency_test("value_with_unit"); +} + +#[test] +fn idempotency_datetime() { + run_idempotency_test("datetime"); +} + +#[test] +fn idempotency_nothing() { + run_idempotency_test("nothing"); +} + +#[test] +fn idempotency_glob_pattern() { + run_idempotency_test("glob_pattern"); +} + +// ============================================================================ +// Idempotency Tests - Modules and Imports +// ============================================================================ + +#[test] +fn idempotency_module() { + run_idempotency_test("module"); +} + +#[test] +fn idempotency_use_statement() { + run_idempotency_test("use_statement"); +} + +#[test] +fn idempotency_export() { + run_idempotency_test("export"); +} + +#[test] +fn idempotency_source() { + run_idempotency_test("source"); +} + +#[test] +fn idempotency_hide() { + run_idempotency_test("hide"); +} + +#[test] +fn idempotency_overlay() { + run_idempotency_test("overlay"); +} + +// ============================================================================ +// Idempotency Tests - Commands and Definitions +// ============================================================================ + +#[test] +fn idempotency_alias() { + run_idempotency_test("alias"); +} + +#[test] +fn idempotency_extern() { + run_idempotency_test("extern"); +} + +#[test] +fn idempotency_external_call() { + run_idempotency_test("external_call"); +} + +// ============================================================================ +// Idempotency Tests - Special Constructs +// ============================================================================ + +#[test] +fn idempotency_do_block() { + run_idempotency_test("do_block"); +} + +#[test] +fn idempotency_where_clause() { + run_idempotency_test("where_clause"); +} + +#[test] +fn idempotency_error_make() { + run_idempotency_test("error_make"); +} diff --git a/tests/main.rs b/tests/main.rs index 569f791..347530d 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -1,4 +1,4 @@ -use std::{fs, process::Command}; +use std::{fs, path::PathBuf, process::Command}; use tempfile::tempdir; const INVALID: &str = "# beginning of script comment @@ -137,3 +137,158 @@ fn files_are_checked() { assert_eq!(file_a_content.as_str(), INVALID); assert_eq!(file_b_content.as_str(), INVALID); } + +#[test] +fn format_let_statement() { + let dir = tempdir().unwrap(); + let file = dir.path().join("test.nu"); + fs::write(&file, "let x = 1").unwrap(); + + let output = Command::new(TEST_BINARY) + .arg(file.to_str().unwrap()) + .output() + .unwrap(); + + assert_eq!(output.status.code(), Some(0)); + let content = fs::read_to_string(&file).unwrap(); + assert_eq!(content.trim(), "let x = 1"); +} + +#[test] +fn format_def_statement() { + let dir = tempdir().unwrap(); + let file = dir.path().join("test.nu"); + fs::write(&file, "def foo [x: int] { $x + 1 }").unwrap(); + + let output = Command::new(TEST_BINARY) + .arg(file.to_str().unwrap()) + .output() + .unwrap(); + + assert_eq!(output.status.code(), Some(0)); + let content = fs::read_to_string(&file).unwrap(); + assert!(content.contains("def foo")); + assert!(content.contains("$x + 1")); +} + +#[test] +fn format_if_else() { + let dir = tempdir().unwrap(); + let file = dir.path().join("test.nu"); + fs::write(&file, "if true { echo yes } else { echo no }").unwrap(); + + let output = Command::new(TEST_BINARY) + .arg(file.to_str().unwrap()) + .output() + .unwrap(); + + assert_eq!(output.status.code(), Some(0)); + let content = fs::read_to_string(&file).unwrap(); + assert!(content.contains("if true")); + assert!(content.contains("else")); +} + +#[test] +fn format_pipeline() { + let dir = tempdir().unwrap(); + let file = dir.path().join("test.nu"); + fs::write(&file, "ls|get name").unwrap(); + + let output = Command::new(TEST_BINARY) + .arg(file.to_str().unwrap()) + .output() + .unwrap(); + + assert_eq!(output.status.code(), Some(0)); + let content = fs::read_to_string(&file).unwrap(); + assert!(content.contains(" | ")); +} + +#[test] +fn format_preserves_comments() { + let dir = tempdir().unwrap(); + let file = dir.path().join("test.nu"); + fs::write(&file, "# comment\nlet x = 1").unwrap(); + + let output = Command::new(TEST_BINARY) + .arg(file.to_str().unwrap()) + .output() + .unwrap(); + + assert_eq!(output.status.code(), Some(0)); + let content = fs::read_to_string(&file).unwrap(); + assert!(content.contains("# comment")); + assert!(content.contains("let x = 1")); +} + +#[test] +fn format_is_idempotent() { + let dir = tempdir().unwrap(); + let file = dir.path().join("test.nu"); + fs::write(&file, "let x = 1\nlet y = 2").unwrap(); + + // First format + Command::new(TEST_BINARY) + .arg(file.to_str().unwrap()) + .output() + .unwrap(); + let first = fs::read_to_string(&file).unwrap(); + + // Second format + Command::new(TEST_BINARY) + .arg(file.to_str().unwrap()) + .output() + .unwrap(); + let second = fs::read_to_string(&file).unwrap(); + + assert_eq!(first, second, "Formatting should be idempotent"); +} + +#[test] +fn format_for_loop() { + let dir = tempdir().unwrap(); + let file = dir.path().join("test.nu"); + fs::write(&file, "for x in [1, 2, 3] { print $x }").unwrap(); + + let output = Command::new(TEST_BINARY) + .arg(file.to_str().unwrap()) + .output() + .unwrap(); + + assert_eq!(output.status.code(), Some(0)); + let content = fs::read_to_string(&file).unwrap(); + assert!(content.contains("for x in")); + assert!(content.contains("{ print")); +} + +#[test] +fn format_closure() { + let dir = tempdir().unwrap(); + let file = dir.path().join("test.nu"); + fs::write(&file, "{|x| $x * 2 }").unwrap(); + + let output = Command::new(TEST_BINARY) + .arg(file.to_str().unwrap()) + .output() + .unwrap(); + + assert_eq!(output.status.code(), Some(0)); + let content = fs::read_to_string(&file).unwrap(); + assert!(content.contains("{|x|")); +} + +#[test] +fn format_fixtures_basic() { + // Test that the basic fixture can be formatted without errors + let fixture_path = PathBuf::from("tests/fixtures/basic.nu"); + if fixture_path.exists() { + let output = Command::new(TEST_BINARY) + .arg("--dry-run") + .arg(fixture_path.to_str().unwrap()) + .output() + .unwrap(); + + // Should either succeed or report would-reformat + assert!(output.status.code() == Some(0) || output.status.code() == Some(1)); + } +} diff --git a/tests/run_ground_truth_tests.nu b/tests/run_ground_truth_tests.nu new file mode 100755 index 0000000..12a323d --- /dev/null +++ b/tests/run_ground_truth_tests.nu @@ -0,0 +1,356 @@ +#!/usr/bin/env nu +# Ground truth test runner for nufmt +# Run with: nu tests/run_ground_truth_tests.nu + +# Configuration +const NUFMT_BINARY = "./target/release/nufmt" +const INPUT_DIR = "tests/fixtures/input" +const EXPECTED_DIR = "tests/fixtures/expected" + +# All test constructs organized by category +const TEST_CONSTRUCTS = { + core: ["let_statement", "mut_statement", "const_statement", "def_statement"] + control_flow: [ + "if_else" + "for_loop" + "while_loop" + "loop_statement" + "match_expr" + "try_catch" + "break_continue" + "return_statement" + ] + data_structures: ["list", "record", "table", "nested_structures"] + pipelines_expressions: [ + "pipeline" + "multiline_pipeline" + "closure" + "subexpression" + "binary_ops" + "range" + "cell_path" + "spread" + ] + strings_interpolation: ["string_interpolation", "comment"] + types_values: ["value_with_unit", "datetime", "nothing", "glob_pattern"] + modules_imports: [ + "module" + "use_statement" + "export" + "source" + "hide" + "overlay" + ] + commands_definitions: ["alias", "extern", "external_call"] + special_constructs: ["do_block", "where_clause", "error_make"] +} + +# Colors for output +def green [text: string] { $"(ansi green)($text)(ansi reset)" } +def red [text: string] { $"(ansi red)($text)(ansi reset)" } +def yellow [text: string] { $"(ansi yellow)($text)(ansi reset)" } +def cyan [text: string] { $"(ansi cyan)($text)(ansi reset)" } +def bold [text: string] { $"(ansi white_bold)($text)(ansi reset)" } + +# Test result record +def make_result [name: string, passed: bool, message: string = ""] { + {name: $name, passed: $passed, message: $message} +} + +# Run a single ground truth test +def run_test [name: string] { + let input_file = $"($INPUT_DIR)/($name).nu" + let expected_file = $"($EXPECTED_DIR)/($name).nu" + + # Check if files exist + if not ($input_file | path exists) { + return (make_result $name false $"Input file not found: ($input_file)") + } + if not ($expected_file | path exists) { + return (make_result $name false $"Expected file not found: ($expected_file)") + } + + # Read input + let input = open $input_file + + # Run formatter + let result = try { + $input | ^$NUFMT_BINARY --stdin + } catch { + return (make_result $name false $"Formatter error") + } + + # Read expected output + let expected = open $expected_file + + # Compare (normalize line endings and trim) + let formatted_normalized = $result | str trim + let expected_normalized = $expected | str trim + + if $formatted_normalized == $expected_normalized { + make_result $name true + } else { + let diff_msg = $"Output differs from expected.\n--- Expected ---\n($expected_normalized)\n--- Got ---\n($formatted_normalized)" + make_result $name false $diff_msg + } +} + +# Run idempotency test +def run_idempotency_test [name: string] { + let input_file = $"($INPUT_DIR)/($name).nu" + + if not ($input_file | path exists) { + return (make_result $"($name)_idempotency" false $"Input file not found") + } + + let input = open $input_file + + # First format + let first = try { + $input | ^$NUFMT_BINARY --stdin + } catch { + return (make_result $"($name)_idempotency" false "First format failed") + } + + # Second format + let second = try { + $first | ^$NUFMT_BINARY --stdin + } catch { + return (make_result $"($name)_idempotency" false "Second format failed") + } + + if ($first | str trim) == ($second | str trim) { + make_result $"($name)_idempotency" true + } else { + make_result $"($name)_idempotency" false "Output changed on second format" + } +} + +# Get all test names from input directory +def get_test_names [] { + ls $INPUT_DIR + | where name =~ '\.nu$' + | get name + | each {|f| $f | path basename | str replace '.nu' ''} +} + +# Get all test names from the constructs definition +def get_all_defined_tests [] { + $TEST_CONSTRUCTS | values | flatten +} + +# Get tests by category +def get_tests_by_category [category: string] { + if ($category in $TEST_CONSTRUCTS) { + $TEST_CONSTRUCTS | get $category + } else { + [] + } +} + +# Print a section header +def print_section [title: string] { + print "" + print (bold $"── ($title) ──") +} + +# Main test runner +def main [ + --test(-t): string # Run specific test by name + --category(-c): string # Run tests in a specific category + --idempotency(-i) # Only run idempotency tests + --ground-truth(-g) # Only run ground truth tests + --verbose(-v) # Show detailed output for failures + --list(-l) # List available tests + --list-categories # List available categories + --check-files # Check which test files exist +] { + print (bold "=== nufmt Ground Truth Test Runner ===") + print "" + + # Check if binary exists + if not ($NUFMT_BINARY | path exists) { + print (red $"Error: nufmt binary not found at ($NUFMT_BINARY)") + print "Run 'cargo build --release' first" + exit 1 + } + + # List categories + if $list_categories { + print "Available test categories:" + for category in ($TEST_CONSTRUCTS | columns) { + let count = ($TEST_CONSTRUCTS | get $category | length) + print $" - (cyan $category) \(($count) tests\)" + } + return + } + + # List tests + if $list { + print "Available tests by category:" + for category in ($TEST_CONSTRUCTS | columns) { + print_section $category + for name in ($TEST_CONSTRUCTS | get $category) { + let input_exists = ($"($INPUT_DIR)/($name).nu" | path exists) + let expected_exists = ($"($EXPECTED_DIR)/($name).nu" | path exists) + let status = if $input_exists and $expected_exists { + green "✓" + } else if $input_exists { + yellow "○" # missing expected + } else { + red "✗" # missing input + } + print $" ($status) ($name)" + } + } + return + } + + # Check files + if $check_files { + print "Checking test file status..." + print "" + + let defined_tests = get_all_defined_tests + let existing_inputs = get_test_names + + mut missing_inputs = [] + mut missing_expected = [] + mut undefined_tests = [] + + for test in $defined_tests { + if not ($"($INPUT_DIR)/($test).nu" | path exists) { + $missing_inputs = ($missing_inputs | append $test) + } + if not ($"($EXPECTED_DIR)/($test).nu" | path exists) { + $missing_expected = ($missing_expected | append $test) + } + } + + for test in $existing_inputs { + if not ($test in $defined_tests) { + $undefined_tests = ($undefined_tests | append $test) + } + } + + if ($missing_inputs | length) > 0 { + print (red "Missing input files:") + for t in $missing_inputs { print $" - ($t)" } + } + + if ($missing_expected | length) > 0 { + print (yellow "Missing expected files:") + for t in $missing_expected { print $" - ($t)" } + } + + if ($undefined_tests | length) > 0 { + print (cyan "Tests not in category definition:") + for t in $undefined_tests { print $" - ($t)" } + } + + if ($missing_inputs | length) == 0 and ($missing_expected | length) == 0 { + print (green "All defined tests have both input and expected files!") + } + return + } + + # Determine which tests to run + let tests_to_run = if $test != null { + [$test] + } else if $category != null { + get_tests_by_category $category + } else { + get_all_defined_tests + } + + if ($tests_to_run | is-empty) { + print (red "No tests found to run") + if $category != null { + print $"Unknown category: ($category)" + print "Use --list-categories to see available categories" + } + exit 1 + } + + mut results = [] + + # Run ground truth tests + if not $idempotency { + print (bold "Running ground truth tests...") + for name in $tests_to_run { + # Check if files exist before running + let input_exists = ($"($INPUT_DIR)/($name).nu" | path exists) + let expected_exists = ($"($EXPECTED_DIR)/($name).nu" | path exists) + + if not $input_exists or not $expected_exists { + let result = make_result $name false "Missing test files" + $results = ($results | append $result) + print $" (yellow '○') ($name) - missing files" + continue + } + + let result = run_test $name + $results = ($results | append $result) + + if $result.passed { + print $" (green '✓') ($name)" + } else { + print $" (red '✗') ($name)" + if $verbose { + print $" ($result.message)" + } + } + } + print "" + } + + # Run idempotency tests + if not $ground_truth { + print (bold "Running idempotency tests...") + for name in $tests_to_run { + # Check if input file exists + if not ($"($INPUT_DIR)/($name).nu" | path exists) { + # Skip silently for idempotency if no input + continue + } + + let result = run_idempotency_test $name + $results = ($results | append $result) + + if $result.passed { + print $" (green '✓') ($result.name)" + } else { + print $" (red '✗') ($result.name)" + if $verbose { + print $" ($result.message)" + } + } + } + print "" + } + + # Summary + let passed = $results | where passed | length + let failed = $results | where {|r| not $r.passed} | length + let total = $results | length + + print (bold "=== Summary ===") + print $"Total: ($total)" + print $"Passed: (green ($passed | into string))" + print $"Failed: (if $failed > 0 { red ($failed | into string) } else { $failed | into string })" + + if $failed > 0 { + print "" + print (bold "Failed tests:") + for result in ($results | where {|r| not $r.passed}) { + print $" - ($result.name)" + if $verbose and ($result.message | str length) > 0 { + print $" ($result.message | str substring 0..200)..." + } + } + exit 1 + } + + print "" + print (green "All tests passed!") +} diff --git a/toolkit.nu b/toolkit.nu index 4b7c519..aef595e 100644 --- a/toolkit.nu +++ b/toolkit.nu @@ -5,73 +5,32 @@ # developer during a PR cycle, namely to (**1**) format the source base, # (**2**) catch classical flaws in the new changes with *clippy* and (**3**) # make sure all the tests pass. - # print the pipe input inside backticks, dimmed and italic, as a pretty command -def pretty-print-command [] { - $"`(ansi default_dimmed)(ansi default_italic)($in)(ansi reset)`" -} - +def pretty-print-command [] { ($"`(ansi default_dimmed)(ansi default_italic)($in)(ansi reset)`") } # check standard code formatting and apply the changes -export def fmt [ - --check # do not apply the format changes, only check the syntax - --verbose # print extra information about the command's progress -] { - if $verbose { - print $"running ('toolkit fmt' | pretty-print-command)" - } - +export def fmt [--check, --verbose] { + # do not apply the format changes, only check the syntax + # print extra information about the command's progress + if $verbose { print $"running ('toolkit fmt' | pretty-print-command)" } if $check { - try { - cargo fmt --all -- --check - } catch { - error make --unspanned { - msg: $"\nplease run ('toolkit fmt' | pretty-print-command) to fix formatting!" - } - } - } else { - cargo fmt --all - } + try { cargo fmt --all -- --check } catch { error make --unspanned { + msg: $"\nplease run ('toolkit fmt' | pretty-print-command) to fix formatting!" + } } + } else { cargo fmt --all } } - # check that you're using the standard code style # # > it is important to make `clippy` happy :relieved: -export def clippy [ - --verbose # print extra information about the command's progress -] { - if $verbose { - print $"running ('toolkit clippy' | pretty-print-command)" - } - - try {( - cargo clippy - --all-targets - --no-deps - --workspace - -- - -D warnings - -D rustdoc::broken_intra_doc_links - -W clippy::explicit_iter_loop - -W clippy::explicit_into_iter_loop - -W clippy::semicolon_if_nothing_returned - -W clippy::doc_markdown - -W clippy::manual_let_else - )} catch { - error make --unspanned { - msg: $"\nplease fix the above ('clippy' | pretty-print-command) errors before continuing!" - } - } +export def clippy [--verbose] { + # print extra information about the command's progress + if $verbose { print $"running ('toolkit clippy' | pretty-print-command)" } + try { (cargo clippy --all-targets --no-deps --workspace -- -D warnings -D rustdoc::broken_intra_doc_links -W clippy::explicit_iter_loop -W clippy::explicit_into_iter_loop -W clippy::semicolon_if_nothing_returned -W clippy::doc_markdown -W clippy::manual_let_else) } catch { error make --unspanned { + msg: $"\nplease fix the above ('clippy' | pretty-print-command) errors before continuing!" + } } } - # check that all the tests pass -export def test [ - --fast # use the "nextext" `cargo` subcommand to speed up the tests (see [`cargo-nextest`](https://nexte.st/) and [`nextest-rs/nextest`](https://github.com/nextest-rs/nextest)) -] { - if $fast { - cargo nextest run --all - } else { - cargo test --workspace - } +export def test [--fast] { + # use the "nextext" `cargo` subcommand to speed up the tests (see [`cargo-nextest`](https://nexte.st/) and [`nextest-rs/nextest`](https://github.com/nextest-rs/nextest)) + if $fast { cargo nextest run --all } else { cargo test --workspace } } - export def main [] { help toolkit }