diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index badadb56ae..f3daa9a466 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -162,3 +162,34 @@ jobs: else echo "no changes in working directory"; fi + + build-wasm: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4.1.7 + + - name: Setup Rust toolchain and cache + uses: actions-rust-lang/setup-rust-toolchain@v1.10.1 + + - name: Add wasm32-unknown-unknown target + run: rustup target add wasm32-unknown-unknown + + - run: cargo build -p nu-cmd-base --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-cmd-extra --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-cmd-lang --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-color-config --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-command --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-derive-value --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-engine --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-glob --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-json --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-parser --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-path --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-pretty-hex --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-protocol --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-std --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-system --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-table --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-term-grid --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nu-utils --no-default-features --target wasm32-unknown-unknown + - run: cargo build -p nuon --no-default-features --target wasm32-unknown-unknown diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml index 2c0fcbd771..3d649176db 100644 --- a/.github/workflows/typos.yml +++ b/.github/workflows/typos.yml @@ -10,4 +10,4 @@ jobs: uses: actions/checkout@v4.1.7 - name: Check spelling - uses: crate-ci/typos@v1.27.3 + uses: crate-ci/typos@v1.28.2 diff --git a/Cargo.lock b/Cargo.lock index f3b8c0ec5a..042fc5a192 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -82,9 +82,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "alphanumeric-sort" @@ -177,9 +177,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.93" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" [[package]] name = "arbitrary" @@ -221,6 +221,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf7d0a018de4f6aa429b9d33d69edf69072b1c5b1cb8d3e4a5f7ef898fc3eb76" +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "arrayvec" version = "0.5.2" @@ -278,7 +284,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -289,7 +295,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -376,9 +382,9 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -423,6 +429,19 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake3" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8ee0c1824c4dea5b5f81736aff91bae041d2c07ee1192bec91054e10e3e601e" +dependencies = [ + "arrayref", + "arrayvec 0.7.6", + "cc", + "cfg-if", + "constant_time_eq", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -461,7 +480,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -493,9 +512,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22" +checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8" dependencies = [ "memchr", "regex-automata", @@ -549,9 +568,9 @@ checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" [[package]] name = "bytemuck" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" +checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" dependencies = [ "bytemuck_derive", ] @@ -564,7 +583,7 @@ checksum = "bcfcc3cd946cb52f0bbfdbbcfa2f4e24f75ebb6c0e1002f7c25904fada18b9ec" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -575,9 +594,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" [[package]] name = "bytesize" @@ -618,9 +637,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.1" +version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47" +checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d" dependencies = [ "jobserver", "libc", @@ -677,9 +696,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", @@ -753,6 +772,33 @@ dependencies = [ "stacker", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -766,9 +812,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.21" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" dependencies = [ "clap_builder", "clap_derive", @@ -776,15 +822,15 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.21" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", - "terminal_size 0.4.0", + "terminal_size", ] [[package]] @@ -793,17 +839,17 @@ version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "clap_lex" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afb84c814227b90d6895e01398aee0d8033c00e7466aca416fb6a8e0eb19d8a7" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "clipboard-win" @@ -831,11 +877,11 @@ checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "colorz" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc2a5df6ee18d52a36920c93a7736761c6fcffa72b9d960fd9133dd8d57c5184" +checksum = "6ceb37c5798821e37369cb546f430f19da2f585e0364c9615ae340a9f2e6067b" dependencies = [ - "supports-color 2.1.0", + "supports-color", ] [[package]] @@ -913,24 +959,30 @@ dependencies = [ [[package]] name = "const_format" -version = "0.2.33" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c655d81ff1114fb0dcdea9225ea9f0cc712a6f8d189378e82bdf62a473a64b" +checksum = "126f97965c8ad46d6d9163268ff28432e8f6a1196a55578867832e3049df63dd" dependencies = [ "const_format_proc_macros", ] [[package]] name = "const_format_proc_macros" -version = "0.2.33" +version = "0.2.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eff1a44b93f47b1bac19a27932f5c591e43d1ba357ee4f61526c8a25603f0eb1" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + [[package]] name = "core-foundation" version = "0.9.4" @@ -941,6 +993,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -949,9 +1011,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ca741a962e1b0bff6d724a1a0958b686406e853bb14061f218562e1896f95e6" +checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" dependencies = [ "libc", ] @@ -1047,7 +1109,7 @@ checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ "bitflags 2.6.0", "crossterm_winapi", - "mio 1.0.2", + "mio 1.0.3", "parking_lot", "rustix", "serde", @@ -1101,7 +1163,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1188,7 +1250,7 @@ checksum = "d150dea618e920167e5973d70ae6ece4385b7164e0d799fe7c122dd0a5d912ad" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1199,7 +1261,7 @@ checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1210,7 +1272,7 @@ checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1276,7 +1338,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1356,9 +1418,9 @@ checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "ego-tree" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c6ba7d4eec39eaa9ab24d44a0e73a7949a1095a8b3f3abb11eddf27dbb56a53" +checksum = "b2972feb8dffe7bc8c5463b1dacda1b0dfbed3710e50f977d965429692d74cd8" [[package]] name = "either" @@ -1403,7 +1465,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1457,12 +1519,12 @@ dependencies = [ [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1508,9 +1570,9 @@ checksum = "95765f67b4b18863968b4a1bd5bb576f732b29a4a28c7cd84c09fa3e2875f33c" [[package]] name = "fastrand" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fd-lock" @@ -1571,9 +1633,9 @@ dependencies = [ [[package]] name = "float-cmp" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" dependencies = [ "num-traits", ] @@ -1614,6 +1676,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs4" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c6b3bd49c37d2aa3f3f2220233b29a7cd23f79d1fe70e5337d25fb390793de" +dependencies = [ + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "fs_extra" version = "1.3.0" @@ -1701,7 +1773,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1837,9 +1909,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" dependencies = [ "atomic-waker", "bytes", @@ -1854,6 +1926,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "halfbrown" version = "0.2.5" @@ -1896,13 +1978,15 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.1" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ "allocator-api2", "equivalent", "foldhash", + "rayon", + "serde", ] [[package]] @@ -1924,24 +2008,18 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hermit-abi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" - [[package]] name = "hex" version = "0.4.3" @@ -1968,7 +2046,7 @@ dependencies = [ "markup5ever 0.12.1", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -1982,14 +2060,14 @@ dependencies = [ "markup5ever 0.14.0", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "http" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" dependencies = [ "bytes", "fnv", @@ -2051,9 +2129,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" dependencies = [ "bytes", "futures-channel", @@ -2067,6 +2145,25 @@ dependencies = [ "pin-project-lite", "smallvec", "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", ] [[package]] @@ -2076,12 +2173,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", + "futures-channel", "futures-util", "http", "http-body", "hyper", "pin-project-lite", + "socket2", "tokio", + "tower-service", + "tracing", ] [[package]] @@ -2231,7 +2332,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -2257,12 +2358,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" dependencies = [ "equivalent", - "hashbrown 0.15.1", + "hashbrown 0.15.2", "serde", ] @@ -2279,6 +2380,12 @@ dependencies = [ "web-time", ] +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + [[package]] name = "inotify" version = "0.9.6" @@ -2301,9 +2408,9 @@ dependencies = [ [[package]] name = "interprocess" -version = "2.2.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f4e4a06d42fab3e85ab1b419ad32b09eab58b901d40c57935ff92db3287a13" +checksum = "894148491d817cb36b6f778017b8ac46b17408d522dd90f539d677ea938362eb" dependencies = [ "doctest-file", "libc", @@ -2318,6 +2425,12 @@ version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" +[[package]] +name = "ipnet" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + [[package]] name = "is-docker" version = "0.2.0" @@ -2327,17 +2440,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "is-terminal" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" -dependencies = [ - "hermit-abi 0.4.0", - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "is-wsl" version = "0.4.0" @@ -2404,9 +2506,9 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "itoap" @@ -2423,18 +2525,13 @@ dependencies = [ "libc", ] -[[package]] -name = "joinery" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72167d68f5fce3b8655487b8038691a3c9984ee769590f93f2a631f4ad64e4f5" - [[package]] name = "js-sys" -version = "0.3.72" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -2475,75 +2572,11 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "lexical-core" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0431c65b318a590c1de6b8fd6e72798c92291d27762d94c9e6c37ed7a73d8458" -dependencies = [ - "lexical-parse-float", - "lexical-parse-integer", - "lexical-util", - "lexical-write-float", - "lexical-write-integer", -] - -[[package]] -name = "lexical-parse-float" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb17a4bdb9b418051aa59d41d65b1c9be5affab314a872e5ad7f06231fb3b4e0" -dependencies = [ - "lexical-parse-integer", - "lexical-util", - "static_assertions", -] - -[[package]] -name = "lexical-parse-integer" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5df98f4a4ab53bf8b175b363a34c7af608fe31f93cc1fb1bf07130622ca4ef61" -dependencies = [ - "lexical-util", - "static_assertions", -] - -[[package]] -name = "lexical-util" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85314db53332e5c192b6bca611fb10c114a80d1b831ddac0af1e9be1b9232ca0" -dependencies = [ - "static_assertions", -] - -[[package]] -name = "lexical-write-float" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e7c3ad4e37db81c1cbe7cf34610340adc09c322871972f74877a712abc6c809" -dependencies = [ - "lexical-util", - "lexical-write-integer", - "static_assertions", -] - -[[package]] -name = "lexical-write-integer" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb89e9f6958b83258afa3deed90b5de9ef68eef090ad5086c791cd2345610162" -dependencies = [ - "lexical-util", - "static_assertions", -] - [[package]] name = "libc" -version = "0.2.162" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "libflate" @@ -2581,9 +2614,9 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", "windows-targets 0.52.6", @@ -2681,9 +2714,9 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "litemap" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" [[package]] name = "lock_api" @@ -2713,7 +2746,7 @@ version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.15.1", + "hashbrown 0.15.2", ] [[package]] @@ -2850,20 +2883,29 @@ dependencies = [ ] [[package]] -name = "miette" -version = "7.2.0" +name = "memoffset" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4edc8853320c2a0dab800fbda86253c8938f6ea88510dc92c5f1ed20e794afc1" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miette" +version = "7.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317f146e2eb7021892722af37cf1b971f0a70c8406f487e24952667616192c64" dependencies = [ "backtrace", "backtrace-ext", "cfg-if", "miette-derive", "owo-colors", - "supports-color 3.0.1", + "supports-color", "supports-hyperlinks", "supports-unicode", - "terminal_size 0.3.0", + "terminal_size", "textwrap", "thiserror 1.0.69", "unicode-width 0.1.11", @@ -2871,13 +2913,13 @@ dependencies = [ [[package]] name = "miette-derive" -version = "7.2.0" +version = "7.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" +checksum = "23c9b935fbe1d6cbd1dac857b54a688145e2d93f48db36010514d0f612d0ad67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -2934,11 +2976,10 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ - "hermit-abi 0.3.9", "libc", "log", "wasi", @@ -2970,9 +3011,9 @@ dependencies = [ [[package]] name = "multipart-rs" -version = "0.1.11" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ea34e5c0fa65ba84707cfaf5bf43d500f1c5a4c6c36327bf5541c5bcd17e98" +checksum = "64cae00e7e52aa5072342ef9a2ccd71669be913c2176a81a665b1f9cd79345f2" dependencies = [ "bytes", "futures-core", @@ -3016,7 +3057,7 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework", + "security-framework 2.11.1", "security-framework-sys", "tempfile", ] @@ -3193,7 +3234,7 @@ dependencies = [ "percent-encoding", "reedline", "rstest", - "sysinfo 0.32.0", + "sysinfo 0.32.1", "tempfile", "unicode-segmentation", "uuid", @@ -3217,7 +3258,7 @@ name = "nu-cmd-extra" version = "0.100.1" dependencies = [ "fancy-regex", - "heck", + "heck 0.5.0", "itertools 0.13.0", "nu-ansi-term", "nu-cmd-base", @@ -3298,6 +3339,7 @@ dependencies = [ "fancy-regex", "filesize", "filetime", + "getrandom", "human-date-parser", "indexmap", "indicatif", @@ -3339,7 +3381,7 @@ dependencies = [ "pretty_assertions", "print-positions", "procfs", - "quick-xml 0.37.0", + "quick-xml 0.37.1", "quickcheck", "quickcheck_macros", "rand", @@ -3350,21 +3392,21 @@ dependencies = [ "roxmltree", "rstest", "rusqlite", + "scopeguard", "serde", "serde_json", "serde_urlencoded", "serde_yaml", "sha2", - "sysinfo 0.32.0", + "sysinfo 0.32.1", "tabled", "tempfile", - "terminal_size 0.4.0", "titlecase", - "toml 0.8.19", + "toml", "trash", "umask", "unicode-segmentation", - "unicode-width 0.1.11", + "unicode-width 0.2.0", "ureq", "url", "uu_cp", @@ -3387,11 +3429,11 @@ dependencies = [ name = "nu-derive-value" version = "0.100.1" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -3403,7 +3445,7 @@ dependencies = [ "nu-path", "nu-protocol", "nu-utils", - "terminal_size 0.4.0", + "terminal_size", ] [[package]] @@ -3427,8 +3469,7 @@ dependencies = [ "nu-utils", "ratatui", "strip-ansi-escapes", - "terminal_size 0.4.0", - "unicode-width 0.1.11", + "unicode-width 0.2.0", ] [[package]] @@ -3510,7 +3551,7 @@ dependencies = [ "nu-protocol", "nu-utils", "serde", - "thiserror 2.0.3", + "thiserror 2.0.6", "typetag", ] @@ -3595,7 +3636,7 @@ dependencies = [ "dirs", "dirs-sys", "fancy-regex", - "heck", + "heck 0.5.0", "indexmap", "log", "lru", @@ -3616,7 +3657,7 @@ dependencies = [ "strum", "strum_macros", "tempfile", - "thiserror 2.0.3", + "thiserror 2.0.6", "typetag", "windows-sys 0.48.0", ] @@ -3645,7 +3686,7 @@ dependencies = [ "nix 0.29.0", "ntapi", "procfs", - "sysinfo 0.32.0", + "sysinfo 0.32.1", "windows 0.56.0", ] @@ -3660,7 +3701,7 @@ dependencies = [ "nu-protocol", "nu-utils", "tabled", - "terminal_size 0.4.0", + "terminal_size", ] [[package]] @@ -3668,7 +3709,7 @@ name = "nu-term-grid" version = "0.100.1" dependencies = [ "nu-utils", - "unicode-width 0.1.11", + "unicode-width 0.2.0", ] [[package]] @@ -3687,6 +3728,7 @@ dependencies = [ name = "nu-utils" version = "0.100.1" dependencies = [ + "crossterm 0.28.1", "crossterm_winapi", "fancy-regex", "log", @@ -4036,6 +4078,36 @@ dependencies = [ "memchr", ] +[[package]] +name = "object_store" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6da452820c715ce78221e8202ccc599b4a52f3e1eb3eedb487b680c81a8e3f3" +dependencies = [ + "async-trait", + "base64", + "bytes", + "chrono", + "futures", + "humantime", + "hyper", + "itertools 0.13.0", + "md-5", + "parking_lot", + "percent-encoding", + "quick-xml 0.36.2", + "rand", + "reqwest", + "ring", + "serde", + "serde_json", + "snafu", + "tokio", + "tracing", + "url", + "walkdir", +] + [[package]] name = "oem_cp" version = "2.0.0" @@ -4094,7 +4166,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -4105,9 +4177,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-src" -version = "300.4.0+3.4.0" +version = "300.4.1+3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a709e02f2b4aca747929cca5ed248880847c650233cf8b8cdc48f40aaf4898a6" +checksum = "faa4eac4138c62414b5622d1b31c5c304f34b406b013c079c2bbc652fdd6678c" dependencies = [ "cc", ] @@ -4202,16 +4274,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "parquet-format-safe" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1131c54b167dd4e4799ce762e1ab01549ebb94d5bdd13e6ec1b467491c378e1f" -dependencies = [ - "async-trait", - "futures", -] - [[package]] name = "parse-zoneinfo" version = "0.3.1" @@ -4240,9 +4302,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pathdiff" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c5ce1153ab5b689d0c074c4e7fc613e942dfb7dd9eea5ab202d2ad91fe361" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] name = "percent-encoding" @@ -4258,20 +4320,20 @@ checksum = "f658886ed52e196e850cfbbfddab9eaa7f6d90dd0929e264c31e5cec07e09e57" [[package]] name = "pest" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" +checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc" dependencies = [ "memchr", - "thiserror 1.0.69", + "thiserror 2.0.6", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" +checksum = "816518421cfc6887a0d62bf441b6ffb4536fcc926395a69e1a85852d4363f57e" dependencies = [ "pest", "pest_generator", @@ -4279,22 +4341,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" +checksum = "7d1396fd3a870fc7838768d171b4616d5c91f6cc25e377b673d714567d99377b" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "pest_meta" -version = "2.7.14" +version = "2.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" +checksum = "e1e58089ea25d717bfd31fb534e4f3afcc2cc569c70de3e239778991ea3b7dea" dependencies = [ "once_cell", "pest", @@ -4361,7 +4423,7 @@ dependencies = [ "phf_shared 0.11.2", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -4440,9 +4502,9 @@ dependencies = [ [[package]] name = "polars" -version = "0.43.1" +version = "0.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e248cf2f0069277f8fe80d413cfb9240c7dd1cfa382b5674c1b4afa57222747" +checksum = "f65c6aa86d991a64c95416a61202f7952da2f8cccefa448f9a23c1b8f2301ecc" dependencies = [ "getrandom", "polars-arrow", @@ -4460,9 +4522,9 @@ dependencies = [ [[package]] name = "polars-arrow" -version = "0.43.1" +version = "0.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2981d5b2f34c84069a39fceca0d36dffeb97db8cadba101e7ea6605c8d42294d" +checksum = "87dbb24d29ddea5abb73d7954df8b8d3d4bb7f02a3e5c96d1519cdad9e816a3d" dependencies = [ "ahash 0.8.11", "atoi", @@ -4475,9 +4537,8 @@ dependencies = [ "either", "ethnum", "fast-float", - "futures", "getrandom", - "hashbrown 0.14.5", + "hashbrown 0.15.2", "itoa", "itoap", "lz4", @@ -4493,6 +4554,7 @@ dependencies = [ "simdutf8", "streaming-iterator", "strength_reduce", + "strum_macros", "version_check", "zstd", ] @@ -4509,9 +4571,9 @@ dependencies = [ [[package]] name = "polars-compute" -version = "0.43.1" +version = "0.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a97b2a5c9b880ab7e52553c40a336fdb6e3244bf896b4d4917700defe8085d5" +checksum = "cbdb1071147452a4c4b25560f23d2fbaffef255b04757291131b22fc2c0d35b2" dependencies = [ "bytemuck", "either", @@ -4525,9 +4587,9 @@ dependencies = [ [[package]] name = "polars-core" -version = "0.43.1" +version = "0.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5bc2cadcca904a9dc4d2c2b437c346712806e9a678bf17c7e94ebf622faae76" +checksum = "dd5df9b55e614088a3270b06f8649dce76537c268d6b1ca4d9c37008b2be5949" dependencies = [ "ahash 0.8.11", "bitflags 2.6.0", @@ -4537,6 +4599,7 @@ dependencies = [ "comfy-table", "either", "hashbrown 0.14.5", + "hashbrown 0.15.2", "indexmap", "num-traits", "once_cell", @@ -4552,6 +4615,7 @@ dependencies = [ "regex", "serde", "serde_json", + "strum_macros", "thiserror 1.0.69", "version_check", "xxhash-rust", @@ -4559,11 +4623,12 @@ dependencies = [ [[package]] name = "polars-error" -version = "0.43.1" +version = "0.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56b0a8eb9b1e56a4640de6887d613cb4de73c4e09d491f3b779855d4c3bcb9ba" +checksum = "4643898a644f30c83737db85f942f8c8956b0c11190b39afec745218eae1746b" dependencies = [ "avro-schema", + "object_store", "polars-arrow-format", "regex", "simdutf8", @@ -4572,12 +4637,14 @@ dependencies = [ [[package]] name = "polars-expr" -version = "0.43.1" +version = "0.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34e9c0e8c7ba93aac64051b92dc68eac5a0e9543cf44ca784467db2c035821fe" +checksum = "ea1b431ed816cba1120cff200f06b962748001bbb2e615ce53cfbbdf701cc136" dependencies = [ "ahash 0.8.11", "bitflags 2.6.0", + "hashbrown 0.15.2", + "num-traits", "once_cell", "polars-arrow", "polars-compute", @@ -4585,32 +4652,37 @@ dependencies = [ "polars-io", "polars-ops", "polars-plan", + "polars-row", "polars-time", "polars-utils", + "rand", "rayon", ] [[package]] name = "polars-io" -version = "0.43.1" +version = "0.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "454ebbebe1cb8cb4768adca44b8fc9431abc3c91d5927f6824e73f916bced911" +checksum = "b2fab2c016635cb416b49461fd6419b0208c6c13a4fd065bd65e4a87dbb66314" dependencies = [ "ahash 0.8.11", "async-trait", "atoi_simd", + "blake3", "bytes", "chrono", "fast-float", "flate2", + "fs4", "futures", "glob", - "hashbrown 0.14.5", + "hashbrown 0.15.2", "home", "itoa", "memchr", "memmap2", "num-traits", + "object_store", "once_cell", "percent-encoding", "polars-arrow", @@ -4621,27 +4693,31 @@ dependencies = [ "polars-schema", "polars-time", "polars-utils", + "pyo3", "rayon", "regex", + "reqwest", "ryu", "serde", + "serde_json", "simd-json", "simdutf8", "tokio", "tokio-util", + "url", "zstd", ] [[package]] name = "polars-json" -version = "0.43.1" +version = "0.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ca086fbbff6e46efbc97032e93f92690c1fc9c662fd5e1f13a42922bd7d3aa4" +checksum = "d5c8c057ef04feaf34b6ce52096bdea3a766fa4725f50442078c8a4ee86397bf" dependencies = [ "ahash 0.8.11", "chrono", "fallible-streaming-iterator", - "hashbrown 0.14.5", + "hashbrown 0.15.2", "indexmap", "itoa", "num-traits", @@ -4655,9 +4731,9 @@ dependencies = [ [[package]] name = "polars-lazy" -version = "0.43.1" +version = "0.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e61c062e833d2376de0a4cf745504449215cbf499cea293cb592e674ffb39ca" +checksum = "4a8ca74f42e7b47cad241b36b98d991cc7fbb51b8d0695a055eb937588d1f310" dependencies = [ "ahash 0.8.11", "bitflags 2.6.0", @@ -4672,6 +4748,7 @@ dependencies = [ "polars-ops", "polars-pipe", "polars-plan", + "polars-stream", "polars-time", "polars-utils", "rayon", @@ -4680,10 +4757,11 @@ dependencies = [ [[package]] name = "polars-mem-engine" -version = "0.43.1" +version = "0.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0643812829cc990e1533a5bf48c21a1b3eaa46aabf2967b0f53f99097cbc74c" +checksum = "7a32614e5b52c9b83856d80c7e2880b79d83055bfd59969bd1d0b148f9cfdc7a" dependencies = [ + "futures", "memmap2", "polars-arrow", "polars-core", @@ -4695,14 +4773,16 @@ dependencies = [ "polars-plan", "polars-time", "polars-utils", + "pyo3", "rayon", + "tokio", ] [[package]] name = "polars-ops" -version = "0.43.1" +version = "0.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ac14a136d87bea798f3db51d5987556deb2293da34bfc8b105ebffa05f6e810" +checksum = "035c800fbe5bbd820afeb8313713ed345853bb014e0f821a4025d40cf0d60e1a" dependencies = [ "ahash 0.8.11", "argminmax", @@ -4711,7 +4791,7 @@ dependencies = [ "chrono", "chrono-tz 0.8.6", "either", - "hashbrown 0.14.5", + "hashbrown 0.15.2", "hex", "indexmap", "jsonpath_lib_polars_vendor", @@ -4728,17 +4808,19 @@ dependencies = [ "rand_distr", "rayon", "regex", + "regex-syntax", "serde", "serde_json", + "strum_macros", "unicode-reverse", "version_check", ] [[package]] name = "polars-parquet" -version = "0.43.1" +version = "0.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "491f5af321169259d5b1294c9fe8ed89faaeac34b4dec4abcedc0d1b3d11013a" +checksum = "91dcf1d9f048079376949eaf2e24e240b313ff4a102fb83b57c9a5f807cdca52" dependencies = [ "ahash 0.8.11", "async-stream", @@ -4748,13 +4830,13 @@ dependencies = [ "ethnum", "flate2", "futures", - "hashbrown 0.14.5", + "hashbrown 0.15.2", "lz4", "num-traits", - "parquet-format-safe", "polars-arrow", "polars-compute", "polars-error", + "polars-parquet-format", "polars-utils", "serde", "simdutf8", @@ -4764,16 +4846,26 @@ dependencies = [ ] [[package]] -name = "polars-pipe" -version = "0.43.1" +name = "polars-parquet-format" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29215c31f599295cc0f803c42fc812cc518db6d5ed4d6c7cc03daf3976a0add5" +checksum = "c025243dcfe8dbc57e94d9f82eb3bef10b565ab180d5b99bed87fd8aea319ce1" +dependencies = [ + "async-trait", + "futures", +] + +[[package]] +name = "polars-pipe" +version = "0.44.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05936f2b3981eecb2fe74d8ef092bb75a93d2a056b3e4f339f4ac20c71c9e331" dependencies = [ "crossbeam-channel", "crossbeam-queue", "enum_dispatch", "futures", - "hashbrown 0.14.5", + "hashbrown 0.15.2", "num-traits", "polars-arrow", "polars-compute", @@ -4791,9 +4883,9 @@ dependencies = [ [[package]] name = "polars-plan" -version = "0.43.1" +version = "0.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3f728df4bc643492a2057a0a125c7e550cbcfe35b391444653ad294be9ab190" +checksum = "23de436f33f4d1134c58f24e7059a221b957ec20730807e0ef0c80c8e4b3d06a" dependencies = [ "ahash 0.8.11", "bitflags 2.6.0", @@ -4801,9 +4893,12 @@ dependencies = [ "bytes", "chrono", "chrono-tz 0.8.6", + "ciborium", "either", - "hashbrown 0.14.5", + "futures", + "hashbrown 0.15.2", "memmap2", + "num-traits", "once_cell", "percent-encoding", "polars-arrow", @@ -4814,6 +4909,7 @@ dependencies = [ "polars-parquet", "polars-time", "polars-utils", + "pyo3", "rayon", "recursive", "regex", @@ -4824,9 +4920,9 @@ dependencies = [ [[package]] name = "polars-row" -version = "0.43.1" +version = "0.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eb931f0929ca7498b3ed5056357d2d364cad42cce95383a7e3983dbceb4bed1" +checksum = "3823d3de3e614509bba6929798f1f3d5ae05c1cdfc4eb7029d2ec6ad77201da2" dependencies = [ "bytemuck", "polars-arrow", @@ -4836,9 +4932,9 @@ dependencies = [ [[package]] name = "polars-schema" -version = "0.43.1" +version = "0.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7e1234b942d3244024ecbac9c7f5a48a52a815f8ca4b9d075fbba16afb1a39" +checksum = "d88667f770291cefa2e8cd366a54f29dc6fe362e9a263914c903db411a58ac1d" dependencies = [ "indexmap", "polars-error", @@ -4849,9 +4945,9 @@ dependencies = [ [[package]] name = "polars-sql" -version = "0.43.1" +version = "0.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce52bfd2ef1e2e18ac26d7d7ea3f9132b199cff06d975156703fa5badcfae187" +checksum = "69451f08363bb497407f6ebebe00bc01972a51716d20d115b75f9b5326f1f3c8" dependencies = [ "hex", "once_cell", @@ -4870,10 +4966,39 @@ dependencies = [ ] [[package]] -name = "polars-time" -version = "0.43.1" +name = "polars-stream" +version = "0.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9925ab75e1d859ae2283ca09d7683198b0b9ff5afecd03f2c9180f3e36e35056" +checksum = "188622b0a4bc4530cf91a288134254ffa065d18932e261075377914225e757c2" +dependencies = [ + "atomic-waker", + "crossbeam-deque", + "crossbeam-utils", + "futures", + "memmap2", + "parking_lot", + "pin-project-lite", + "polars-core", + "polars-error", + "polars-expr", + "polars-io", + "polars-mem-engine", + "polars-parquet", + "polars-plan", + "polars-utils", + "rand", + "rayon", + "recursive", + "slotmap", + "tokio", + "version_check", +] + +[[package]] +name = "polars-time" +version = "0.44.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f36e4d6b19f2c406faea585b9a1814f422fc5b310f65ccf8a55216df0754ef" dependencies = [ "atoi", "bytemuck", @@ -4888,25 +5013,27 @@ dependencies = [ "polars-utils", "regex", "serde", + "strum_macros", ] [[package]] name = "polars-utils" -version = "0.43.1" +version = "0.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b44846e1fc6ae1dfdc7f65a37af7d270d0a6a17a58fff76716561f5b887a8ad7" +checksum = "96186b70bda00c90b5027bf2f69193c5c40571e80d3e8ec505c22cdc8e3e39aa" dependencies = [ "ahash 0.8.11", "bytemuck", "bytes", "compact_str 0.8.0", - "hashbrown 0.14.5", + "hashbrown 0.15.2", "indexmap", "libc", "memmap2", "num-traits", "once_cell", "polars-error", + "pyo3", "raw-cpuid", "rayon", "serde", @@ -4926,9 +5053,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" [[package]] name = "powerfmt" @@ -5003,7 +5130,7 @@ version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ - "toml_edit 0.22.22", + "toml_edit", ] [[package]] @@ -5031,33 +5158,32 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.89" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] [[package]] name = "procfs" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "731e0d9356b0c25f16f33b5be79b1c57b562f141ebfcdb0ad8ac2c13a24293b4" +checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f" dependencies = [ "bitflags 2.6.0", "chrono", "flate2", "hex", - "lazy_static", "procfs-core", "rustix", ] [[package]] name = "procfs-core" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3554923a69f4ce04c4a754260c338f505ce22642d3830e049a399fc2059a29" +checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec" dependencies = [ "bitflags 2.6.0", "chrono", @@ -5109,6 +5235,69 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "pyo3" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e00b96a521718e08e03b1a622f01c8a8deb50719335de3f60b3b3950f069d8" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "parking_lot", + "portable-atomic", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7883df5835fafdad87c0d888b266c8ec0f4c9ca48a5bed6bbb592e8dedee1b50" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01be5843dc60b916ab4dad1dca6d20b9b4e6ddc8e15f50c47fe6d85f1fb97403" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77b34069fc0682e11b31dbd10321cbf94808394c56fd996796ce45217dfac53c" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08260721f32db5e1a5beae69a55553f56b99bd0e1c3e6e0a5e8851a9d0f5a85c" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "pyo3-build-config", + "quote", + "syn 2.0.90", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -5147,13 +5336,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" dependencies = [ "memchr", + "serde", ] [[package]] name = "quick-xml" -version = "0.37.0" +version = "0.37.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbfb3ddf5364c9cfcd65549a1e7b801d0e8d1b14c1a1590a6408aa93cfbfa84" +checksum = "f22f29bdff3987b4d8632ef95fd6424ec7e4e0a57e2f4fc63e489e75357f6a03" dependencies = [ "memchr", ] @@ -5180,6 +5370,58 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "quinn" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62e96808277ec6f97351a2380e6c25114bc9e67037775464979f3037c92d05ef" +dependencies = [ + "bytes", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.1.0", + "rustls", + "socket2", + "thiserror 2.0.6", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2fe5ef3495d7d2e377ff17b1a8ce2ee2ec2a18cde8b6ad6619d65d0701c135d" +dependencies = [ + "bytes", + "getrandom", + "rand", + "ring", + "rustc-hash 2.1.0", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.6", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52cd4b1eff68bf27940dd39811292c49e007f4d0b4c357358dc9b0197be6b527" +dependencies = [ + "cfg_aliases 0.2.1", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "quote" version = "1.0.37" @@ -5307,7 +5549,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76009fbe0614077fc1a2ce255e3a1881a2e3a3527097d5dc6d8212c585e7e38b" dependencies = [ "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -5318,9 +5560,9 @@ checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175" [[package]] name = "redox_syscall" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" dependencies = [ "bitflags 2.6.0", ] @@ -5339,8 +5581,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.37.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9834765acaa2deedf29197f2da3d309801683beb20e25775bc0249f97a7b9a74" +source = "git+https://github.com/nushell/reedline?branch=main#9eb3c2dd1375119c7f6bb8ecac07b715e72fe692" dependencies = [ "arboard", "chrono", @@ -5376,7 +5617,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -5423,6 +5664,51 @@ dependencies = [ "bytecheck", ] +[[package]] +name = "reqwest" +version = "0.12.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" +dependencies = [ + "base64", + "bytes", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-native-certs", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "windows-registry", +] + [[package]] name = "rfc2047-decoder" version = "1.0.6" @@ -5434,7 +5720,22 @@ dependencies = [ "chumsky", "memchr", "quoted_printable", - "thiserror 2.0.3", + "thiserror 2.0.6", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", ] [[package]] @@ -5506,9 +5807,9 @@ dependencies = [ [[package]] name = "roxmltree" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" [[package]] name = "rstest" @@ -5533,7 +5834,7 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn 2.0.87", + "syn 2.0.90", "unicode-ident", ] @@ -5572,7 +5873,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.87", + "syn 2.0.90", "walkdir", ] @@ -5625,6 +5926,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" + [[package]] name = "rustc_version" version = "0.4.1" @@ -5636,15 +5943,70 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.40" +version = "0.38.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" +checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" dependencies = [ "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls" +version = "0.23.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework 3.0.1", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +dependencies = [ + "web-time", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] @@ -5679,9 +6041,9 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.26" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] @@ -5700,11 +6062,10 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "scraper" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0e749d29b2064585327af5038a5a8eb73aeebad4a3472e83531a436563f7208" +checksum = "cc3d051b884f40e309de6c149734eab57aa8cc1347992710dc80bcc1c2194c15" dependencies = [ - "ahash 0.8.11", "cssparser", "ego-tree", "html5ever 0.29.0", @@ -5730,7 +6091,7 @@ checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -5752,7 +6113,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.6.0", - "core-foundation", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1415a607e92bec364ea2cf9264646dcce0f91e6d65281bd6f2819cca3bf39c8" +dependencies = [ + "bitflags 2.6.0", + "core-foundation 0.10.0", "core-foundation-sys", "libc", "security-framework-sys", @@ -5795,29 +6169,29 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "indexmap", "itoa", @@ -5834,7 +6208,7 @@ checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -5893,7 +6267,7 @@ checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -5957,7 +6331,7 @@ checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", "mio 0.8.11", - "mio 1.0.2", + "mio 1.0.3", "signal-hook", ] @@ -5978,14 +6352,13 @@ checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "simd-json" -version = "0.13.11" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0228a564470f81724e30996bbc2b171713b37b15254a6440c7e2d5449b95691" +checksum = "aa2bcf6c6e164e81bc7a5d49fc6988b3d515d9e8c07457d7b74ffb9324b9cd40" dependencies = [ "ahash 0.8.11", "getrandom", "halfbrown", - "lexical-core", "once_cell", "ref-cast", "serde", @@ -6032,6 +6405,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -6039,10 +6421,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] -name = "smawk" -version = "0.3.2" +name = "snafu" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" +checksum = "e4de37ad025c587a29e8f3f5605c00f70b98715ef90b9061a815b9e59e9042d6" +dependencies = [ + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 1.0.109", +] [[package]] name = "snap" @@ -6052,14 +6450,20 @@ checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "sqlparser" version = "0.49.0" @@ -6076,7 +6480,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" dependencies = [ "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -6187,37 +6591,33 @@ version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "rustversion", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] -name = "supports-color" -version = "2.1.0" +name = "subtle" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6398cde53adc3c4557306a96ce67b302968513830a77a95b2b17305d9719a89" -dependencies = [ - "is-terminal", - "is_ci", -] +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "supports-color" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8775305acf21c96926c900ad056abeef436701108518cf890020387236ac5a77" +checksum = "c64fc7232dd8d2e4ac5ce4ef302b1d81e0b80d055b9d77c7c4f51f6aa4c867d6" dependencies = [ "is_ci", ] [[package]] name = "supports-hyperlinks" -version = "3.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c0a1e5168041f5f3ff68ff7d95dcb9c8749df29f6e7e89ada40dd4c9de404ee" +checksum = "804f44ed3c63152de6a9f90acbea1a110441de43006ea51bcce8f436196a288b" [[package]] name = "supports-unicode" @@ -6259,15 +6659,24 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.87" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "synstructure" version = "0.13.1" @@ -6276,7 +6685,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -6303,9 +6712,9 @@ dependencies = [ [[package]] name = "sysinfo" -version = "0.32.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ae3f4f7d64646c46c4cae4e3f01d1c5d255c7406fdd7c7f999a94e488791" +checksum = "4c33cd241af0f2e9e3b5c32163b873b29956890b5342e6745b917ce9d490f4af" dependencies = [ "core-foundation-sys", "libc", @@ -6359,6 +6768,12 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1bbb9f3c5c463a01705937a24fdabc5047929ac764b2d5b9cf681c1f5041ed5" +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + [[package]] name = "tempfile" version = "3.14.0" @@ -6394,19 +6809,9 @@ dependencies = [ [[package]] name = "terminal_size" -version = "0.3.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" -dependencies = [ - "rustix", - "windows-sys 0.48.0", -] - -[[package]] -name = "terminal_size" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" +checksum = "5352447f921fda68cf61b4101566c0bdb5104eff6804d0678e5227580ab6a4e9" dependencies = [ "rustix", "windows-sys 0.59.0", @@ -6424,7 +6829,6 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" dependencies = [ - "smawk", "unicode-linebreak", "unicode-width 0.1.11", ] @@ -6440,11 +6844,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.3" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" +checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" dependencies = [ - "thiserror-impl 2.0.3", + "thiserror-impl 2.0.6", ] [[package]] @@ -6455,18 +6859,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "thiserror-impl" -version = "2.0.3" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" +checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -6481,9 +6885,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -6504,9 +6908,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", @@ -6548,36 +6952,56 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "titlecase" -version = "2.2.1" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38397a8cdb017cfeb48bf6c154d6de975ac69ffeed35980fde199d2ee0842042" +checksum = "e0e20e744fbec1913fa168f3ffbef64324bbcb152c6cda8394baa79fa5ec9142" dependencies = [ - "joinery", - "lazy_static", "regex", ] [[package]] name = "tokio" -version = "1.41.1" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ "backtrace", "bytes", "libc", - "mio 1.0.2", + "mio 1.0.3", "parking_lot", "pin-project-lite", "socket2", + "tokio-macros", "windows-sys 0.52.0", ] [[package]] -name = "tokio-util" -version = "0.7.12" +name = "tokio-macros" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", @@ -6586,18 +7010,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "toml" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit 0.19.15", -] - [[package]] name = "toml" version = "0.8.19" @@ -6608,7 +7020,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.22", + "toml_edit", ] [[package]] @@ -6620,19 +7032,6 @@ dependencies = [ "serde", ] -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow 0.5.40", -] - [[package]] name = "toml_edit" version = "0.22.22" @@ -6643,33 +7042,51 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.20", + "winnow", ] [[package]] -name = "tracing" -version = "0.1.40" +name = "tower-service" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", + "tracing-attributes", "tracing-core", ] [[package]] -name = "tracing-core" -version = "0.1.32" +name = "tracing-attributes" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "tracing-core" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", ] [[package]] name = "trash" -version = "5.2.0" +version = "5.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defe1fdd4232e407b312377885a2c5396764972bddad87baf304753374a1bfc8" +checksum = "a8e5ca62c20366b4685e3e41fba17bc7c9bbdcb82e65a89d6fda2ceea5fffd2f" dependencies = [ "chrono", "libc", @@ -6677,6 +7094,7 @@ dependencies = [ "objc2", "objc2-foundation", "once_cell", + "percent-encoding", "scopeguard", "urlencoding", "windows 0.56.0", @@ -6701,6 +7119,12 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "typed-arena" version = "1.7.0" @@ -6740,7 +7164,7 @@ checksum = "70b20a22c42c8f1cd23ce5e34f165d4d37038f5b663ad20fb6adbdf029172483" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -6766,9 +7190,9 @@ checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "unicode-linebreak" @@ -6820,6 +7244,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "unindent" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" + [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -6827,10 +7257,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] -name = "ureq" -version = "2.10.1" +name = "untrusted" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" dependencies = [ "base64", "encoding_rs", @@ -6845,9 +7281,9 @@ dependencies = [ [[package]] name = "url" -version = "2.5.3" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -7002,9 +7438,9 @@ dependencies = [ [[package]] name = "uucore_procs" -version = "0.0.27" +version = "0.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d588f57acb2ba416e072a6fa652f2e11cf727267c697d2e2d65175f3b10c41" +checksum = "d91d598cf92275d9373e1eb0b9dcf85b730bde48186ed7db3bde713803090511" dependencies = [ "proc-macro2", "quote", @@ -7013,9 +7449,9 @@ dependencies = [ [[package]] name = "uuhelp_parser" -version = "0.0.27" +version = "0.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96f26868814bf1ca9deec910a08007c93eb1d8e407ce36451999d4c1c1ea6767" +checksum = "86b4d8c991cc252d4b4ea2958d0d842e36af0d1ca7f1fdc4fdce8fd0a2ff6e9c" [[package]] name = "uuid" @@ -7035,9 +7471,9 @@ checksum = "4e8257fbc510f0a46eb602c10215901938b5c2a7d5e70fc11483b1d3c9b5b18c" [[package]] name = "value-trait" -version = "0.8.1" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad8db98c1e677797df21ba03fca7d3bf9bec3ca38db930954e4fe6e1ea27eb4" +checksum = "9170e001f458781e92711d2ad666110f153e4e50bfd5cbd02db6547625714187" dependencies = [ "float-cmp", "halfbrown", @@ -7107,6 +7543,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -7115,9 +7560,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" dependencies = [ "cfg-if", "once_cell", @@ -7126,24 +7571,36 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "wasm-bindgen-shared", ] [[package]] -name = "wasm-bindgen-macro" -version = "0.2.95" +name = "wasm-bindgen-futures" +version = "0.4.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -7151,22 +7608,35 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.95" +version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] [[package]] name = "wax" @@ -7256,6 +7726,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "web-sys" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "web-time" version = "1.1.0" @@ -7282,9 +7762,9 @@ dependencies = [ [[package]] name = "which" -version = "6.0.3" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" +checksum = "c9cad3279ade7346b96e38731a641d7343dd6a53d55083dd54eadfa5a1b38c6b" dependencies = [ "either", "home", @@ -7375,7 +7855,7 @@ checksum = "4698e52ed2d08f8658ab0c39512a7c00ee5fe2688c65f8c0a4f06750d729f2a6" dependencies = [ "windows-implement 0.56.0", "windows-interface 0.56.0", - "windows-result", + "windows-result 0.1.2", "windows-targets 0.52.6", ] @@ -7387,7 +7867,7 @@ checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" dependencies = [ "windows-implement 0.57.0", "windows-interface 0.57.0", - "windows-result", + "windows-result 0.1.2", "windows-targets 0.52.6", ] @@ -7399,7 +7879,7 @@ checksum = "f6fc35f58ecd95a9b71c4f2329b911016e6bec66b3f2e6a4aad86bd2e99e2f9b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -7410,7 +7890,7 @@ checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -7421,7 +7901,7 @@ checksum = "08990546bf4edef8f431fa6326e032865f27138718c587dc21bc0265bbcb57cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] @@ -7432,7 +7912,18 @@ checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", +] + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result 0.2.0", + "windows-strings", + "windows-targets 0.52.6", ] [[package]] @@ -7444,6 +7935,25 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -7592,15 +8102,6 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - [[package]] name = "winnow" version = "0.6.20" @@ -7622,11 +8123,11 @@ dependencies = [ [[package]] name = "winresource" -version = "0.1.17" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77e2aaaf8cfa92078c0c0375423d631f82f2f57979c2884fdd5f604a11e45329" +checksum = "7276691b353ad4547af8c3268488d1311f4be791ffdc0c65b8cfa8f41eed693b" dependencies = [ - "toml 0.7.8", + "toml", "version_check", ] @@ -7730,9 +8231,9 @@ checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", @@ -7742,13 +8243,13 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "synstructure", ] @@ -7770,30 +8271,36 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "zerofrom" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + [[package]] name = "zerovec" version = "0.10.4" @@ -7813,14 +8320,14 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn 2.0.90", ] [[package]] name = "zip" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc5e4288ea4057ae23afc69a4472434a87a2495cafce6632fd1c4ec9f5cf3494" +checksum = "99d52293fc86ea7cf13971b3bb81eb21683636e7ae24c729cdaf1b7c4157a352" dependencies = [ "arbitrary", "crc32fast", @@ -7829,7 +8336,7 @@ dependencies = [ "flate2", "indexmap", "memchr", - "thiserror 1.0.69", + "thiserror 2.0.6", "zopfli", ] diff --git a/Cargo.toml b/Cargo.toml index cb417c5bd3..b7b13e2967 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ homepage = "https://www.nushell.sh" license = "MIT" name = "nu" repository = "https://github.com/nushell/nushell" -rust-version = "1.80.1" +rust-version = "1.81.0" version = "0.100.1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -92,7 +92,7 @@ filetime = "0.2" fuzzy-matcher = "0.3" heck = "0.5.0" human-date-parser = "0.2.0" -indexmap = "2.6" +indexmap = "2.7" indicatif = "0.17" interprocess = "2.2.0" is_executable = "1.0" @@ -106,11 +106,11 @@ lsp-server = "0.7.5" lsp-types = { version = "0.95.0", features = ["proposed"] } mach2 = "0.4" md5 = { version = "0.10", package = "md-5" } -miette = "7.2" +miette = "7.3" mime = "0.3.17" mime_guess = "2.0" mockito = { version = "1.6", default-features = false } -multipart-rs = "0.1.11" +multipart-rs = "0.1.13" native-tls = "0.2" nix = { version = "0.29", default-features = false } notify-debouncer-full = { version = "0.3", default-features = false } @@ -127,13 +127,14 @@ pretty_assertions = "1.4" print-positions = "0.6" proc-macro-error = { version = "1.0", default-features = false } proc-macro2 = "1.0" -procfs = "0.16.0" +procfs = "0.17.0" pwd = "1.3" quick-xml = "0.37.0" quickcheck = "1.0" quickcheck_macros = "1.0" quote = "1.0" rand = "0.8" +getrandom = "0.2" # pick same version that rand requires rand_chacha = "0.3.1" ratatui = "0.26" rayon = "1.10" @@ -142,10 +143,11 @@ regex = "1.9.5" rmp = "0.8" rmp-serde = "1.3" ropey = "1.6.1" -roxmltree = "0.19" +roxmltree = "0.20" rstest = { version = "0.23", default-features = false } rusqlite = "0.31" rust-embed = "8.5.0" +scopeguard = { version = "1.2.0" } serde = { version = "1.0" } serde_json = "1.0" serde_urlencoded = "0.7.1" @@ -157,13 +159,13 @@ sysinfo = "0.32" tabled = { version = "0.17.0", default-features = false } tempfile = "3.14" terminal_size = "0.4" -titlecase = "2.0" +titlecase = "3.0" toml = "0.8" trash = "5.2" umask = "2.1" unicode-segmentation = "1.12" -unicode-width = "0.1" -ureq = { version = "2.10", default-features = false } +unicode-width = "0.2" +ureq = { version = "2.12", default-features = false } url = "2.2" uu_cp = "0.0.28" uu_mkdir = "0.0.28" @@ -176,7 +178,7 @@ uucore = "0.0.28" uuid = "1.11.0" v_htmlescape = "0.15.0" wax = "0.6" -which = "6.0.0" +which = "7.0.0" windows = "0.56" windows-sys = "0.48" winreg = "0.52" @@ -249,13 +251,18 @@ tempfile = { workspace = true } [features] plugin = [ - "nu-plugin-engine", + # crates "nu-cmd-plugin", + "nu-plugin-engine", + + # features "nu-cli/plugin", - "nu-parser/plugin", + "nu-cmd-lang/plugin", "nu-command/plugin", - "nu-protocol/plugin", "nu-engine/plugin", + "nu-engine/plugin", + "nu-parser/plugin", + "nu-protocol/plugin", ] default = [ @@ -314,7 +321,7 @@ bench = false # To use a development version of a dependency please use a global override here # changing versions in each sub-crate of the workspace is tedious [patch.crates-io] -# reedline = { git = "https://github.com/nushell/reedline", branch = "main" } +reedline = { git = "https://github.com/nushell/reedline", branch = "main" } # nu-ansi-term = {git = "https://github.com/nushell/nu-ansi-term.git", branch = "main"} # Run all benchmarks with `cargo bench` diff --git a/README.md b/README.md index 66988a104d..698a06e4f3 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ For details about which platforms the Nushell team actively supports, see [our p ## Configuration -The default configurations can be found at [sample_config](crates/nu-utils/src/sample_config) +The default configurations can be found at [sample_config](crates/nu-utils/src/default_files) which are the configuration files one gets when they startup Nushell for the first time. It sets all of the default configuration to run Nushell. From here one can diff --git a/crates/nu-cli/Cargo.toml b/crates/nu-cli/Cargo.toml index b91314ddcf..993bb1ac72 100644 --- a/crates/nu-cli/Cargo.toml +++ b/crates/nu-cli/Cargo.toml @@ -19,11 +19,11 @@ tempfile = { workspace = true } [dependencies] nu-cmd-base = { path = "../nu-cmd-base", version = "0.100.1" } -nu-engine = { path = "../nu-engine", version = "0.100.1" } +nu-engine = { path = "../nu-engine", version = "0.100.1", features = ["os"] } nu-path = { path = "../nu-path", version = "0.100.1" } nu-parser = { path = "../nu-parser", version = "0.100.1" } nu-plugin-engine = { path = "../nu-plugin-engine", version = "0.100.1", optional = true } -nu-protocol = { path = "../nu-protocol", version = "0.100.1" } +nu-protocol = { path = "../nu-protocol", version = "0.100.1", features = ["os"] } nu-utils = { path = "../nu-utils", version = "0.100.1" } nu-color-config = { path = "../nu-color-config", version = "0.100.1" } nu-ansi-term = { workspace = true } diff --git a/crates/nu-cli/src/completions/command_completions.rs b/crates/nu-cli/src/completions/command_completions.rs index b12df4735a..ce4383483a 100644 --- a/crates/nu-cli/src/completions/command_completions.rs +++ b/crates/nu-cli/src/completions/command_completions.rs @@ -41,8 +41,7 @@ impl CommandCompletion { ) -> HashMap { let mut suggs = HashMap::new(); - // os agnostic way to get the PATH env var - let paths = working_set.permanent_state.get_path_env_var(); + let paths = working_set.permanent_state.get_env_var_insensitive("path"); if let Some(paths) = paths { if let Ok(paths) = paths.as_list() { diff --git a/crates/nu-cli/src/completions/completer.rs b/crates/nu-cli/src/completions/completer.rs index 555d14ff27..16b1673a4f 100644 --- a/crates/nu-cli/src/completions/completer.rs +++ b/crates/nu-cli/src/completions/completer.rs @@ -297,7 +297,7 @@ impl NuCompleter { let mut completer = OperatorCompletion::new(pipeline_element.expr.clone()); - return self.process_completion( + let operator_suggestion = self.process_completion( &mut completer, &working_set, prefix, @@ -305,6 +305,9 @@ impl NuCompleter { fake_offset, pos, ); + if !operator_suggestion.is_empty() { + return operator_suggestion; + } } } } diff --git a/crates/nu-cli/src/completions/custom_completions.rs b/crates/nu-cli/src/completions/custom_completions.rs index 63e65053b4..4f19cd0eb0 100644 --- a/crates/nu-cli/src/completions/custom_completions.rs +++ b/crates/nu-cli/src/completions/custom_completions.rs @@ -1,13 +1,12 @@ use crate::completions::{ - completer::map_value_completions, Completer, CompletionOptions, MatchAlgorithm, - SemanticSuggestion, + completer::map_value_completions, Completer, CompletionOptions, SemanticSuggestion, }; use nu_engine::eval_call; use nu_protocol::{ ast::{Argument, Call, Expr, Expression}, debugger::WithoutDebug, engine::{Stack, StateWorkingSet}, - CompletionSort, DeclId, PipelineData, Span, Type, Value, + DeclId, PipelineData, Span, Type, Value, }; use std::collections::HashMap; @@ -68,6 +67,7 @@ impl Completer for CustomCompletion { ); let mut custom_completion_options = None; + let mut should_sort = true; // Parse result let suggestions = result @@ -85,10 +85,9 @@ impl Completer for CustomCompletion { let options = val.get("options"); if let Some(Value::Record { val: options, .. }) = &options { - let should_sort = options - .get("sort") - .and_then(|val| val.as_bool().ok()) - .unwrap_or(false); + if let Some(sort) = options.get("sort").and_then(|val| val.as_bool().ok()) { + should_sort = sort; + } custom_completion_options = Some(CompletionOptions { case_sensitive: options @@ -98,20 +97,16 @@ impl Completer for CustomCompletion { positional: options .get("positional") .and_then(|val| val.as_bool().ok()) - .unwrap_or(true), + .unwrap_or(completion_options.positional), match_algorithm: match options.get("completion_algorithm") { Some(option) => option .coerce_string() .ok() .and_then(|option| option.try_into().ok()) - .unwrap_or(MatchAlgorithm::Prefix), + .unwrap_or(completion_options.match_algorithm), None => completion_options.match_algorithm, }, - sort: if should_sort { - CompletionSort::Alphabetical - } else { - CompletionSort::Smart - }, + sort: completion_options.sort, }); } @@ -124,9 +119,17 @@ impl Completer for CustomCompletion { let options = custom_completion_options.unwrap_or(completion_options.clone()); let mut matcher = NuMatcher::new(String::from_utf8_lossy(prefix), options); - for sugg in suggestions { - matcher.add_semantic_suggestion(sugg); + + if should_sort { + for sugg in suggestions { + matcher.add_semantic_suggestion(sugg); + } + matcher.results() + } else { + suggestions + .into_iter() + .filter(|sugg| matcher.matches(&sugg.suggestion.value)) + .collect() } - matcher.results() } } diff --git a/crates/nu-cli/src/completions/operator_completions.rs b/crates/nu-cli/src/completions/operator_completions.rs index 0ce4e02e17..aebb176b76 100644 --- a/crates/nu-cli/src/completions/operator_completions.rs +++ b/crates/nu-cli/src/completions/operator_completions.rs @@ -60,10 +60,6 @@ impl Completer for OperatorCompletion { ("bit-shr", "Bitwise shift right"), ("in", "Is a member of (doesn't use regex)"), ("not-in", "Is not a member of (doesn't use regex)"), - ( - "++", - "Appends two lists, a list and a value, two strings, or two binary values", - ), ], Expr::String(_) => vec![ ("=~", "Contains regex match"), @@ -72,7 +68,7 @@ impl Completer for OperatorCompletion { ("not-like", "Does not contain regex match"), ( "++", - "Appends two lists, a list and a value, two strings, or two binary values", + "Concatenates two lists, two strings, or two binary values", ), ("in", "Is a member of (doesn't use regex)"), ("not-in", "Is not a member of (doesn't use regex)"), @@ -95,10 +91,6 @@ impl Completer for OperatorCompletion { ("**", "Power of"), ("in", "Is a member of (doesn't use regex)"), ("not-in", "Is not a member of (doesn't use regex)"), - ( - "++", - "Appends two lists, a list and a value, two strings, or two binary values", - ), ], Expr::Bool(_) => vec![ ( @@ -113,15 +105,11 @@ impl Completer for OperatorCompletion { ("not", "Negates a value or expression"), ("in", "Is a member of (doesn't use regex)"), ("not-in", "Is not a member of (doesn't use regex)"), - ( - "++", - "Appends two lists, a list and a value, two strings, or two binary values", - ), ], Expr::FullCellPath(path) => match path.head.expr { Expr::List(_) => vec![( "++", - "Appends two lists, a list and a value, two strings, or two binary values", + "Concatenates two lists, two strings, or two binary values", )], Expr::Var(id) => get_variable_completions(id, working_set), _ => vec![], @@ -161,7 +149,7 @@ pub fn get_variable_completions<'a>( Type::List(_) | Type::String | Type::Binary => vec![ ( "++=", - "Appends a list, a value, a string, or a binary value to a variable.", + "Concatenates two lists, two strings, or two binary values", ), ("=", "Assigns a value to a variable."), ], diff --git a/crates/nu-cli/src/repl.rs b/crates/nu-cli/src/repl.rs index 5440f9caa2..4bd86ddbf9 100644 --- a/crates/nu-cli/src/repl.rs +++ b/crates/nu-cli/src/repl.rs @@ -16,7 +16,7 @@ use crate::{ use crossterm::cursor::SetCursorStyle; use log::{error, trace, warn}; use miette::{ErrReport, IntoDiagnostic, Result}; -use nu_cmd_base::{hook::eval_hook, util::get_editor}; +use nu_cmd_base::util::get_editor; use nu_color_config::StyleComputer; #[allow(deprecated)] use nu_engine::{convert_env_values, current_dir_str, env_to_strings}; @@ -313,20 +313,26 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) { perf!("reset signals", start_time, use_color); start_time = std::time::Instant::now(); - // Right before we start our prompt and take input from the user, - // fire the "pre_prompt" hook - if let Some(hook) = engine_state.get_config().hooks.pre_prompt.clone() { - if let Err(err) = eval_hook(engine_state, &mut stack, None, vec![], &hook, "pre_prompt") { - report_shell_error(engine_state, &err); - } + // Right before we start our prompt and take input from the user, fire the "pre_prompt" hook + if let Err(err) = hook::eval_hooks( + engine_state, + &mut stack, + vec![], + &engine_state.get_config().hooks.pre_prompt.clone(), + "pre_prompt", + ) { + report_shell_error(engine_state, &err); } perf!("pre-prompt hook", start_time, use_color); start_time = std::time::Instant::now(); // Next, check all the environment variables they ask for // fire the "env_change" hook - let env_change = engine_state.get_config().hooks.env_change.clone(); - if let Err(error) = hook::eval_env_change_hook(env_change, engine_state, &mut stack) { + if let Err(error) = hook::eval_env_change_hook( + &engine_state.get_config().hooks.env_change.clone(), + engine_state, + &mut stack, + ) { report_shell_error(engine_state, &error) } perf!("env-change hook", start_time, use_color); @@ -511,18 +517,17 @@ fn loop_iteration(ctx: LoopContext) -> (bool, Stack, Reedline) { // Right before we start running the code the user gave us, fire the `pre_execution` // hook - if let Some(hook) = config.hooks.pre_execution.clone() { + { // Set the REPL buffer to the current command for the "pre_execution" hook let mut repl = engine_state.repl_state.lock().expect("repl state mutex"); repl.buffer = repl_cmd_line_text.to_string(); drop(repl); - if let Err(err) = eval_hook( + if let Err(err) = hook::eval_hooks( engine_state, &mut stack, - None, vec![], - &hook, + &engine_state.get_config().hooks.pre_execution.clone(), "pre_execution", ) { report_shell_error(engine_state, &err); diff --git a/crates/nu-cli/src/syntax_highlight.rs b/crates/nu-cli/src/syntax_highlight.rs index ebd8315190..b8f7abe0c5 100644 --- a/crates/nu-cli/src/syntax_highlight.rs +++ b/crates/nu-cli/src/syntax_highlight.rs @@ -144,8 +144,6 @@ impl Highlighter for NuHighlighter { } FlatShape::Flag => add_colored_token(&shape.1, next_token), FlatShape::Pipe => add_colored_token(&shape.1, next_token), - FlatShape::And => add_colored_token(&shape.1, next_token), - FlatShape::Or => add_colored_token(&shape.1, next_token), FlatShape::Redirection => add_colored_token(&shape.1, next_token), FlatShape::Custom(..) => add_colored_token(&shape.1, next_token), FlatShape::MatchPattern => add_colored_token(&shape.1, next_token), diff --git a/crates/nu-cli/src/util.rs b/crates/nu-cli/src/util.rs index 7b9d783534..50db15a68e 100644 --- a/crates/nu-cli/src/util.rs +++ b/crates/nu-cli/src/util.rs @@ -1,3 +1,5 @@ +#![allow(clippy::byte_char_slices)] + use nu_cmd_base::hook::eval_hook; use nu_engine::{eval_block, eval_block_with_early_return}; use nu_parser::{lex, parse, unescape_unquote_string, Token, TokenContents}; diff --git a/crates/nu-cli/tests/completions/mod.rs b/crates/nu-cli/tests/completions/mod.rs index 4bcf8b55c0..e32ac8c4f2 100644 --- a/crates/nu-cli/tests/completions/mod.rs +++ b/crates/nu-cli/tests/completions/mod.rs @@ -88,6 +88,27 @@ fn completer_strings_with_options() -> NuCompleter { NuCompleter::new(Arc::new(engine), Arc::new(stack)) } +#[fixture] +fn completer_strings_no_sort() -> NuCompleter { + // Create a new engine + let (_, _, mut engine, mut stack) = new_engine(); + let command = r#" + def animals [] { + { + completions: ["zzzfoo", "foo", "not matched", "abcfoo" ], + options: { + completion_algorithm: "fuzzy", + sort: false, + } + } + } + def my-command [animal: string@animals] { print $animal }"#; + assert!(support::merge_input(command.as_bytes(), &mut engine, &mut stack).is_ok()); + + // Instantiate a new completer + NuCompleter::new(Arc::new(engine), Arc::new(stack)) +} + #[fixture] fn custom_completer() -> NuCompleter { // Create a new engine @@ -210,6 +231,13 @@ fn customcompletions_case_insensitive(mut completer_strings_with_options: NuComp match_suggestions(&expected, &suggestions); } +#[rstest] +fn customcompletions_no_sort(mut completer_strings_no_sort: NuCompleter) { + let suggestions = completer_strings_no_sort.complete("my-command foo", 14); + let expected: Vec = vec!["zzzfoo".into(), "foo".into(), "abcfoo".into()]; + match_suggestions(&expected, &suggestions); +} + #[test] fn dotnu_completions() { // Create a new engine @@ -329,6 +357,39 @@ fn file_completions() { // Match the results match_suggestions(&expected_paths, &suggestions); + // Test completions for the current folder even with parts before the autocomplet + let target_dir = format!("cp somefile.txt {dir_str}{MAIN_SEPARATOR}"); + let suggestions = completer.complete(&target_dir, target_dir.len()); + + // Create the expected values + let expected_paths: Vec = vec![ + folder(dir.join("another")), + file(dir.join("custom_completion.nu")), + folder(dir.join("directory_completion")), + file(dir.join("nushell")), + folder(dir.join("test_a")), + folder(dir.join("test_b")), + file(dir.join(".hidden_file")), + folder(dir.join(".hidden_folder")), + ]; + + #[cfg(windows)] + { + let separator = '/'; + let target_dir = format!("cp somefile.txt {dir_str}{separator}"); + let slash_suggestions = completer.complete(&target_dir, target_dir.len()); + + let expected_slash_paths: Vec = expected_paths + .iter() + .map(|s| s.replace('\\', "/")) + .collect(); + + match_suggestions(&expected_slash_paths, &slash_suggestions); + } + + // Match the results + match_suggestions(&expected_paths, &suggestions); + // Test completions for a file let target_dir = format!("cp {}", folder(dir.join("another"))); let suggestions = completer.complete(&target_dir, target_dir.len()); @@ -363,6 +424,75 @@ fn file_completions() { match_suggestions(&expected_paths, &suggestions); } +#[test] +fn custom_command_rest_any_args_file_completions() { + // Create a new engine + let (dir, dir_str, mut engine, mut stack) = new_engine(); + let command = r#"def list [ ...args: any ] {}"#; + assert!(support::merge_input(command.as_bytes(), &mut engine, &mut stack).is_ok()); + + // Instantiate a new completer + let mut completer = NuCompleter::new(Arc::new(engine), Arc::new(stack)); + + // Test completions for the current folder + let target_dir = format!("list {dir_str}{MAIN_SEPARATOR}"); + let suggestions = completer.complete(&target_dir, target_dir.len()); + + // Create the expected values + let expected_paths: Vec = vec![ + folder(dir.join("another")), + file(dir.join("custom_completion.nu")), + folder(dir.join("directory_completion")), + file(dir.join("nushell")), + folder(dir.join("test_a")), + folder(dir.join("test_b")), + file(dir.join(".hidden_file")), + folder(dir.join(".hidden_folder")), + ]; + + // Match the results + match_suggestions(&expected_paths, &suggestions); + + // Test completions for the current folder even with parts before the autocomplet + let target_dir = format!("list somefile.txt {dir_str}{MAIN_SEPARATOR}"); + let suggestions = completer.complete(&target_dir, target_dir.len()); + + // Create the expected values + let expected_paths: Vec = vec![ + folder(dir.join("another")), + file(dir.join("custom_completion.nu")), + folder(dir.join("directory_completion")), + file(dir.join("nushell")), + folder(dir.join("test_a")), + folder(dir.join("test_b")), + file(dir.join(".hidden_file")), + folder(dir.join(".hidden_folder")), + ]; + + // Match the results + match_suggestions(&expected_paths, &suggestions); + + // Test completions for a file + let target_dir = format!("list {}", folder(dir.join("another"))); + let suggestions = completer.complete(&target_dir, target_dir.len()); + + // Create the expected values + let expected_paths: Vec = vec![file(dir.join("another").join("newfile"))]; + + // Match the results + match_suggestions(&expected_paths, &suggestions); + + // Test completions for hidden files + let target_dir = format!("list {}", file(dir.join(".hidden_folder").join("."))); + let suggestions = completer.complete(&target_dir, target_dir.len()); + + let expected_paths: Vec = + vec![file(dir.join(".hidden_folder").join(".hidden_subfile"))]; + + // Match the results + match_suggestions(&expected_paths, &suggestions); +} + #[cfg(windows)] #[test] fn file_completions_with_mixed_separators() { @@ -1629,13 +1759,3 @@ fn alias_offset_bug_7754() { // This crashes before PR #7756 let _suggestions = completer.complete("ll -a | c", 9); } - -#[test] -fn get_path_env_var_8003() { - // Create a new engine - let (_, _, engine, _) = new_engine(); - // Get the path env var in a platform agnostic way - let the_path = engine.get_path_env_var(); - // Make sure it's not empty - assert!(the_path.is_some()); -} diff --git a/crates/nu-cmd-base/Cargo.toml b/crates/nu-cmd-base/Cargo.toml index e47dc12641..fb36998bbd 100644 --- a/crates/nu-cmd-base/Cargo.toml +++ b/crates/nu-cmd-base/Cargo.toml @@ -13,10 +13,10 @@ version = "0.100.1" workspace = true [dependencies] -nu-engine = { path = "../nu-engine", version = "0.100.1" } +nu-engine = { path = "../nu-engine", version = "0.100.1", default-features = false } nu-parser = { path = "../nu-parser", version = "0.100.1" } nu-path = { path = "../nu-path", version = "0.100.1" } -nu-protocol = { path = "../nu-protocol", version = "0.100.1" } +nu-protocol = { path = "../nu-protocol", version = "0.100.1", default-features = false } indexmap = { workspace = true } miette = { workspace = true } diff --git a/crates/nu-cmd-base/src/hook.rs b/crates/nu-cmd-base/src/hook.rs index 4983dbdc8f..bddf63f334 100644 --- a/crates/nu-cmd-base/src/hook.rs +++ b/crates/nu-cmd-base/src/hook.rs @@ -7,49 +7,55 @@ use nu_protocol::{ engine::{Closure, EngineState, Stack, StateWorkingSet}, PipelineData, PositionalArg, ShellError, Span, Type, Value, VarId, }; -use std::sync::Arc; +use std::{collections::HashMap, sync::Arc}; pub fn eval_env_change_hook( - env_change_hook: Option, + env_change_hook: &HashMap>, engine_state: &mut EngineState, stack: &mut Stack, ) -> Result<(), ShellError> { - if let Some(hook) = env_change_hook { - match hook { - Value::Record { val, .. } => { - for (env_name, hook_value) in &*val { - let before = engine_state.previous_env_vars.get(env_name); - let after = stack.get_env_var(engine_state, env_name); - if before != after { - let before = before.cloned().unwrap_or_default(); - let after = after.cloned().unwrap_or_default(); + for (env, hooks) in env_change_hook { + let before = engine_state.previous_env_vars.get(env); + let after = stack.get_env_var(engine_state, env); + if before != after { + let before = before.cloned().unwrap_or_default(); + let after = after.cloned().unwrap_or_default(); - eval_hook( - engine_state, - stack, - None, - vec![("$before".into(), before), ("$after".into(), after.clone())], - hook_value, - "env_change", - )?; + eval_hooks( + engine_state, + stack, + vec![("$before".into(), before), ("$after".into(), after.clone())], + hooks, + "env_change", + )?; - Arc::make_mut(&mut engine_state.previous_env_vars) - .insert(env_name.clone(), after); - } - } - } - x => { - return Err(ShellError::TypeMismatch { - err_message: "record for the 'env_change' hook".to_string(), - span: x.span(), - }); - } + Arc::make_mut(&mut engine_state.previous_env_vars).insert(env.clone(), after); } } Ok(()) } +pub fn eval_hooks( + engine_state: &mut EngineState, + stack: &mut Stack, + arguments: Vec<(String, Value)>, + hooks: &[Value], + hook_name: &str, +) -> Result<(), ShellError> { + for hook in hooks { + eval_hook( + engine_state, + stack, + None, + arguments.clone(), + hook, + &format!("{hook_name} list, recursive"), + )?; + } + Ok(()) +} + pub fn eval_hook( engine_state: &mut EngineState, stack: &mut Stack, @@ -127,16 +133,7 @@ pub fn eval_hook( } } Value::List { vals, .. } => { - for val in vals { - eval_hook( - engine_state, - stack, - None, - arguments.clone(), - val, - &format!("{hook_name} list, recursive"), - )?; - } + eval_hooks(engine_state, stack, arguments, vals, hook_name)?; } Value::Record { val, .. } => { // Hooks can optionally be a record in this form: diff --git a/crates/nu-cmd-extra/Cargo.toml b/crates/nu-cmd-extra/Cargo.toml index eec817059b..b54597f35a 100644 --- a/crates/nu-cmd-extra/Cargo.toml +++ b/crates/nu-cmd-extra/Cargo.toml @@ -17,12 +17,12 @@ workspace = true [dependencies] nu-cmd-base = { path = "../nu-cmd-base", version = "0.100.1" } -nu-engine = { path = "../nu-engine", version = "0.100.1" } +nu-engine = { path = "../nu-engine", version = "0.100.1", default-features = false } nu-json = { version = "0.100.1", path = "../nu-json" } nu-parser = { path = "../nu-parser", version = "0.100.1" } nu-pretty-hex = { version = "0.100.1", path = "../nu-pretty-hex" } -nu-protocol = { path = "../nu-protocol", version = "0.100.1" } -nu-utils = { path = "../nu-utils", version = "0.100.1" } +nu-protocol = { path = "../nu-protocol", version = "0.100.1", default-features = false } +nu-utils = { path = "../nu-utils", version = "0.100.1", default-features = false } # Potential dependencies for extras heck = { workspace = true } diff --git a/crates/nu-cmd-extra/src/extra/bits/into.rs b/crates/nu-cmd-extra/src/extra/bits/into.rs index 7edc8d3584..3452c1eeee 100644 --- a/crates/nu-cmd-extra/src/extra/bits/into.rs +++ b/crates/nu-cmd-extra/src/extra/bits/into.rs @@ -203,7 +203,7 @@ pub fn action(input: &Value, _args: &Arguments, span: Span) -> Value { Value::string(raw_string.trim(), span) } Value::Int { val, .. } => convert_to_smallest_number_type(*val, span), - Value::Filesize { val, .. } => convert_to_smallest_number_type(*val, span), + Value::Filesize { val, .. } => convert_to_smallest_number_type(val.get(), span), Value::Duration { val, .. } => convert_to_smallest_number_type(*val, span), Value::String { val, .. } => { let raw_bytes = val.as_bytes(); diff --git a/crates/nu-cmd-extra/src/extra/conversions/fmt.rs b/crates/nu-cmd-extra/src/extra/conversions/fmt.rs index 15ca742c76..5bf2ae47a4 100644 --- a/crates/nu-cmd-extra/src/extra/conversions/fmt.rs +++ b/crates/nu-cmd-extra/src/extra/conversions/fmt.rs @@ -66,7 +66,7 @@ fn action(input: &Value, _args: &CellPathOnlyArgs, span: Span) -> Value { match input { Value::Float { val, .. } => fmt_it_64(*val, span), Value::Int { val, .. } => fmt_it(*val, span), - Value::Filesize { val, .. } => fmt_it(*val, span), + Value::Filesize { val, .. } => fmt_it(val.get(), span), // Propagate errors by explicitly matching them before the final case. Value::Error { .. } => input.clone(), other => Value::error( diff --git a/crates/nu-cmd-extra/src/extra/filters/each_while.rs b/crates/nu-cmd-extra/src/extra/filters/each_while.rs index 67f0cc9482..cc5c066185 100644 --- a/crates/nu-cmd-extra/src/extra/filters/each_while.rs +++ b/crates/nu-cmd-extra/src/extra/filters/each_while.rs @@ -25,7 +25,7 @@ impl Command for EachWhile { )]) .required( "closure", - SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Int])), + SyntaxShape::Closure(Some(vec![SyntaxShape::Any])), "the closure to run", ) .category(Category::Filters) diff --git a/crates/nu-cmd-extra/src/extra/formats/mod.rs b/crates/nu-cmd-extra/src/extra/formats/mod.rs index 8c49349390..04c03dce49 100644 --- a/crates/nu-cmd-extra/src/extra/formats/mod.rs +++ b/crates/nu-cmd-extra/src/extra/formats/mod.rs @@ -2,4 +2,4 @@ mod from; mod to; pub(crate) use from::url::FromUrl; -pub(crate) use to::html::ToHtml; +pub use to::html::ToHtml; diff --git a/crates/nu-cmd-extra/src/extra/mod.rs b/crates/nu-cmd-extra/src/extra/mod.rs index 3936b94a0f..8204f1bd9a 100644 --- a/crates/nu-cmd-extra/src/extra/mod.rs +++ b/crates/nu-cmd-extra/src/extra/mod.rs @@ -9,6 +9,7 @@ mod strings; pub use bits::{ Bits, BitsAnd, BitsInto, BitsNot, BitsOr, BitsRol, BitsRor, BitsShl, BitsShr, BitsXor, }; +pub use formats::ToHtml; pub use math::{MathArcCos, MathArcCosH, MathArcSin, MathArcSinH, MathArcTan, MathArcTanH}; pub use math::{MathCos, MathCosH, MathSin, MathSinH, MathTan, MathTanH}; pub use math::{MathExp, MathLn}; @@ -54,7 +55,8 @@ pub fn add_extra_command_context(mut engine_state: EngineState) -> EngineState { strings::str_::case::StrTitleCase ); - bind_command!(formats::ToHtml, formats::FromUrl); + bind_command!(ToHtml, formats::FromUrl); + // Bits bind_command! { Bits, diff --git a/crates/nu-cmd-lang/Cargo.toml b/crates/nu-cmd-lang/Cargo.toml index 6e8cb824ba..00ba449785 100644 --- a/crates/nu-cmd-lang/Cargo.toml +++ b/crates/nu-cmd-lang/Cargo.toml @@ -15,10 +15,10 @@ bench = false workspace = true [dependencies] -nu-engine = { path = "../nu-engine", version = "0.100.1" } +nu-engine = { path = "../nu-engine", version = "0.100.1", default-features = false } nu-parser = { path = "../nu-parser", version = "0.100.1" } -nu-protocol = { path = "../nu-protocol", version = "0.100.1" } -nu-utils = { path = "../nu-utils", version = "0.100.1" } +nu-protocol = { path = "../nu-protocol", version = "0.100.1", default-features = false } +nu-utils = { path = "../nu-utils", version = "0.100.1", default-features = false } itertools = { workspace = true } shadow-rs = { version = "0.36", default-features = false } @@ -27,6 +27,17 @@ shadow-rs = { version = "0.36", default-features = false } shadow-rs = { version = "0.36", default-features = false } [features] +default = ["os"] +os = [ + "nu-engine/os", + "nu-protocol/os", + "nu-utils/os", +] +plugin = [ + "nu-protocol/plugin", + "os", +] + mimalloc = [] trash-support = [] sqlite = [] diff --git a/crates/nu-cmd-lang/src/core_commands/describe.rs b/crates/nu-cmd-lang/src/core_commands/describe.rs index ec98dc896f..375a840b16 100644 --- a/crates/nu-cmd-lang/src/core_commands/describe.rs +++ b/crates/nu-cmd-lang/src/core_commands/describe.rs @@ -169,6 +169,7 @@ fn run( let origin = match stream.source() { ByteStreamSource::Read(_) => "unknown", ByteStreamSource::File(_) => "file", + #[cfg(feature = "os")] ByteStreamSource::Child(_) => "external", }; diff --git a/crates/nu-cmd-lang/src/core_commands/do_.rs b/crates/nu-cmd-lang/src/core_commands/do_.rs index 5f1b42cb66..05d22c6cea 100644 --- a/crates/nu-cmd-lang/src/core_commands/do_.rs +++ b/crates/nu-cmd-lang/src/core_commands/do_.rs @@ -1,9 +1,8 @@ use nu_engine::{command_prelude::*, get_eval_block_with_early_return, redirect_env}; -use nu_protocol::{ - engine::Closure, - process::{ChildPipe, ChildProcess}, - ByteStream, ByteStreamSource, OutDest, -}; +#[cfg(feature = "os")] +use nu_protocol::process::{ChildPipe, ChildProcess}; +use nu_protocol::{engine::Closure, ByteStream, ByteStreamSource, OutDest}; + use std::{ io::{Cursor, Read}, thread, @@ -69,6 +68,33 @@ impl Command for Do { let block: Closure = call.req(engine_state, caller_stack, 0)?; let rest: Vec = call.rest(engine_state, caller_stack, 1)?; let ignore_all_errors = call.has_flag(engine_state, caller_stack, "ignore-errors")?; + + if call.has_flag(engine_state, caller_stack, "ignore-shell-errors")? { + nu_protocol::report_shell_warning( + engine_state, + &ShellError::GenericError { + error: "Deprecated option".into(), + msg: "`--ignore-shell-errors` is deprecated and will be removed in 0.102.0." + .into(), + span: Some(call.head), + help: Some("Please use the `--ignore-errors(-i)`".into()), + inner: vec![], + }, + ); + } + if call.has_flag(engine_state, caller_stack, "ignore-program-errors")? { + nu_protocol::report_shell_warning( + engine_state, + &ShellError::GenericError { + error: "Deprecated option".into(), + msg: "`--ignore-program-errors` is deprecated and will be removed in 0.102.0." + .into(), + span: Some(call.head), + help: Some("Please use the `--ignore-errors(-i)`".into()), + inner: vec![], + }, + ); + } let ignore_shell_errors = ignore_all_errors || call.has_flag(engine_state, caller_stack, "ignore-shell-errors")?; let ignore_program_errors = ignore_all_errors @@ -92,6 +118,13 @@ impl Command for Do { match result { Ok(PipelineData::ByteStream(stream, metadata)) if capture_errors => { let span = stream.span(); + #[cfg(not(feature = "os"))] + return Err(ShellError::DisabledOsSupport { + msg: "Cannot create a thread to receive stdout message.".to_string(), + span: Some(span), + }); + + #[cfg(feature = "os")] match stream.into_child() { Ok(mut child) => { // Use a thread to receive stdout message. @@ -169,6 +202,7 @@ impl Command for Do { OutDest::Pipe | OutDest::PipeSeparate | OutDest::Value ) => { + #[cfg(feature = "os")] if let ByteStreamSource::Child(child) = stream.source_mut() { child.ignore_error(true); } @@ -208,16 +242,6 @@ impl Command for Do { example: r#"do --ignore-errors { thisisnotarealcommand }"#, result: None, }, - Example { - description: "Run the closure and ignore shell errors", - example: r#"do --ignore-shell-errors { thisisnotarealcommand }"#, - result: None, - }, - Example { - description: "Run the closure and ignore external program errors", - example: r#"do --ignore-program-errors { nu --commands 'exit 1' }; echo "I'll still run""#, - result: None, - }, Example { description: "Abort the pipeline if a program returns a non-zero exit code", example: r#"do --capture-errors { nu --commands 'exit 1' } | myscarycommand"#, diff --git a/crates/nu-cmd-lang/src/core_commands/ignore.rs b/crates/nu-cmd-lang/src/core_commands/ignore.rs index 382dc4fedd..b28a9582a3 100644 --- a/crates/nu-cmd-lang/src/core_commands/ignore.rs +++ b/crates/nu-cmd-lang/src/core_commands/ignore.rs @@ -35,6 +35,7 @@ impl Command for Ignore { mut input: PipelineData, ) -> Result { if let PipelineData::ByteStream(stream, _) = &mut input { + #[cfg(feature = "os")] if let ByteStreamSource::Child(child) = stream.source_mut() { child.ignore_error(true); } diff --git a/crates/nu-cmd-lang/src/core_commands/try_.rs b/crates/nu-cmd-lang/src/core_commands/try_.rs index c7242550a7..4b5361c681 100644 --- a/crates/nu-cmd-lang/src/core_commands/try_.rs +++ b/crates/nu-cmd-lang/src/core_commands/try_.rs @@ -107,11 +107,7 @@ fn run_catch( if let Some(catch) = catch { stack.set_last_error(&error); - let fancy_errors = match engine_state.get_config().error_style { - nu_protocol::ErrorStyle::Fancy => true, - nu_protocol::ErrorStyle::Plain => false, - }; - let error = error.into_value(span, fancy_errors); + let error = error.into_value(&StateWorkingSet::new(engine_state), span); let block = engine_state.get_block(catch.block_id); // Put the error value in the positional closure var if let Some(var) = block.signature.get_positional(0) { diff --git a/crates/nu-cmd-lang/src/core_commands/version.rs b/crates/nu-cmd-lang/src/core_commands/version.rs index f5716be6b3..9ebc43dee2 100644 --- a/crates/nu-cmd-lang/src/core_commands/version.rs +++ b/crates/nu-cmd-lang/src/core_commands/version.rs @@ -116,24 +116,30 @@ pub fn version(engine_state: &EngineState, span: Span) -> Result>(); + #[cfg(not(feature = "plugin"))] + let _ = engine_state; - record.push( - "installed_plugins", - Value::string(installed_plugins.join(", "), span), - ); + #[cfg(feature = "plugin")] + { + // Get a list of plugin names and versions if present + let installed_plugins = engine_state + .plugins() + .iter() + .map(|x| { + let name = x.identity().name(); + if let Some(version) = x.metadata().and_then(|m| m.version) { + format!("{name} {version}") + } else { + name.into() + } + }) + .collect::>(); + + record.push( + "installed_plugins", + Value::string(installed_plugins.join(", "), span), + ); + } Ok(Value::record(record, span).into_pipeline_data()) } diff --git a/crates/nu-cmd-lang/src/lib.rs b/crates/nu-cmd-lang/src/lib.rs index 41022cc231..1a46c0a350 100644 --- a/crates/nu-cmd-lang/src/lib.rs +++ b/crates/nu-cmd-lang/src/lib.rs @@ -1,3 +1,4 @@ +#![cfg_attr(not(feature = "os"), allow(unused))] #![doc = include_str!("../README.md")] mod core_commands; mod default_context; diff --git a/crates/nu-cmd-plugin/src/util.rs b/crates/nu-cmd-plugin/src/util.rs index 80d1a766b4..57de225ace 100644 --- a/crates/nu-cmd-plugin/src/util.rs +++ b/crates/nu-cmd-plugin/src/util.rs @@ -135,18 +135,24 @@ pub(crate) fn get_plugin_dirs( engine_state: &EngineState, stack: &Stack, ) -> impl Iterator { - // Get the NU_PLUGIN_DIRS constant or env var + // Get the NU_PLUGIN_DIRS from the constant and/or env var let working_set = StateWorkingSet::new(engine_state); - let value = working_set + let dirs_from_const = working_set .find_variable(b"$NU_PLUGIN_DIRS") .and_then(|var_id| working_set.get_constant(var_id).ok()) - .or_else(|| stack.get_env_var(engine_state, "NU_PLUGIN_DIRS")) - .cloned(); // TODO: avoid this clone - - // Get all of the strings in the list, if possible - value + .cloned() // TODO: avoid this clone .into_iter() .flat_map(|value| value.into_list().ok()) .flatten() - .flat_map(|list_item| list_item.coerce_into_string().ok()) + .flat_map(|list_item| list_item.coerce_into_string().ok()); + + let dirs_from_env = stack + .get_env_var(engine_state, "NU_PLUGIN_DIRS") + .cloned() // TODO: avoid this clone + .into_iter() + .flat_map(|value| value.into_list().ok()) + .flatten() + .flat_map(|list_item| list_item.coerce_into_string().ok()); + + dirs_from_const.chain(dirs_from_env) } diff --git a/crates/nu-color-config/Cargo.toml b/crates/nu-color-config/Cargo.toml index abf95023cc..19faec8e1b 100644 --- a/crates/nu-color-config/Cargo.toml +++ b/crates/nu-color-config/Cargo.toml @@ -14,8 +14,8 @@ bench = false workspace = true [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.100.1" } -nu-engine = { path = "../nu-engine", version = "0.100.1" } +nu-protocol = { path = "../nu-protocol", version = "0.100.1", default-features = false } +nu-engine = { path = "../nu-engine", version = "0.100.1", default-features = false } nu-json = { path = "../nu-json", version = "0.100.1" } nu-ansi-term = { workspace = true } diff --git a/crates/nu-color-config/src/shape_color.rs b/crates/nu-color-config/src/shape_color.rs index 8da397e914..190d44525d 100644 --- a/crates/nu-color-config/src/shape_color.rs +++ b/crates/nu-color-config/src/shape_color.rs @@ -5,7 +5,6 @@ use nu_protocol::{Config, Value}; // The default colors for shapes, used when there is no config for them. pub fn default_shape_color(shape: &str) -> Style { match shape { - "shape_and" => Style::new().fg(Color::Purple).bold(), "shape_binary" => Style::new().fg(Color::Purple).bold(), "shape_block" => Style::new().fg(Color::Blue).bold(), "shape_bool" => Style::new().fg(Color::LightCyan), @@ -30,7 +29,6 @@ pub fn default_shape_color(shape: &str) -> Style { "shape_match_pattern" => Style::new().fg(Color::Green), "shape_nothing" => Style::new().fg(Color::LightCyan), "shape_operator" => Style::new().fg(Color::Yellow), - "shape_or" => Style::new().fg(Color::Purple).bold(), "shape_pipe" => Style::new().fg(Color::Purple).bold(), "shape_range" => Style::new().fg(Color::Yellow).bold(), "shape_raw_string" => Style::new().fg(Color::LightMagenta).bold(), diff --git a/crates/nu-command/Cargo.toml b/crates/nu-command/Cargo.toml index c37954e9c1..ef88aa92e4 100644 --- a/crates/nu-command/Cargo.toml +++ b/crates/nu-command/Cargo.toml @@ -18,17 +18,17 @@ workspace = true [dependencies] nu-cmd-base = { path = "../nu-cmd-base", version = "0.100.1" } nu-color-config = { path = "../nu-color-config", version = "0.100.1" } -nu-engine = { path = "../nu-engine", version = "0.100.1" } +nu-engine = { path = "../nu-engine", version = "0.100.1", default-features = false } nu-glob = { path = "../nu-glob", version = "0.100.1" } nu-json = { path = "../nu-json", version = "0.100.1" } nu-parser = { path = "../nu-parser", version = "0.100.1" } nu-path = { path = "../nu-path", version = "0.100.1" } nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.100.1" } -nu-protocol = { path = "../nu-protocol", version = "0.100.1" } +nu-protocol = { path = "../nu-protocol", version = "0.100.1", default-features = false } nu-system = { path = "../nu-system", version = "0.100.1" } nu-table = { path = "../nu-table", version = "0.100.1" } nu-term-grid = { path = "../nu-term-grid", version = "0.100.1" } -nu-utils = { path = "../nu-utils", version = "0.100.1" } +nu-utils = { path = "../nu-utils", version = "0.100.1", default-features = false } nu-ansi-term = { workspace = true } nuon = { path = "../nuon", version = "0.100.1" } @@ -43,7 +43,7 @@ chardetng = { workspace = true } chrono = { workspace = true, features = ["std", "unstable-locales", "clock"], default-features = false } chrono-humanize = { workspace = true } chrono-tz = { workspace = true } -crossterm = { workspace = true } +crossterm = { workspace = true, optional = true } csv = { workspace = true } dialoguer = { workspace = true, default-features = false, features = ["fuzzy-select"] } digest = { workspace = true, default-features = false } @@ -61,24 +61,26 @@ lscolors = { workspace = true, default-features = false, features = ["nu-ansi-te md5 = { workspace = true } mime = { workspace = true } mime_guess = { workspace = true } -multipart-rs = { workspace = true } -native-tls = { workspace = true } -notify-debouncer-full = { workspace = true, default-features = false } +multipart-rs = { workspace = true, optional = true } +native-tls = { workspace = true, optional = true } +notify-debouncer-full = { workspace = true, default-features = false, optional = true } num-format = { workspace = true } num-traits = { workspace = true } oem_cp = { workspace = true } -open = { workspace = true } -os_pipe = { workspace = true } +open = { workspace = true, optional = true } +os_pipe = { workspace = true, optional = true } pathdiff = { workspace = true } percent-encoding = { workspace = true } print-positions = { workspace = true } quick-xml = { workspace = true } -rand = { workspace = true } +rand = { workspace = true, optional = true } +getrandom = { workspace = true, optional = true } rayon = { workspace = true } regex = { workspace = true } roxmltree = { workspace = true } rusqlite = { workspace = true, features = ["bundled", "backup", "chrono"], optional = true } rmp = { workspace = true } +scopeguard = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, features = ["preserve_order"] } serde_urlencoded = { workspace = true } @@ -86,30 +88,29 @@ serde_yaml = { workspace = true } sha2 = { workspace = true } sysinfo = { workspace = true } tabled = { workspace = true, features = ["ansi"], default-features = false } -terminal_size = { workspace = true } titlecase = { workspace = true } toml = { workspace = true, features = ["preserve_order"] } unicode-segmentation = { workspace = true } -ureq = { workspace = true, default-features = false, features = ["charset", "gzip", "json", "native-tls"] } +ureq = { workspace = true, default-features = false, features = ["charset", "gzip", "json"] } url = { workspace = true } -uu_cp = { workspace = true } -uu_mkdir = { workspace = true } -uu_mktemp = { workspace = true } -uu_mv = { workspace = true } -uu_touch = { workspace = true } -uu_uname = { workspace = true } -uu_whoami = { workspace = true } -uuid = { workspace = true, features = ["v4"] } +uu_cp = { workspace = true, optional = true } +uu_mkdir = { workspace = true, optional = true } +uu_mktemp = { workspace = true, optional = true } +uu_mv = { workspace = true, optional = true } +uu_touch = { workspace = true, optional = true } +uu_uname = { workspace = true, optional = true } +uu_whoami = { workspace = true, optional = true } +uuid = { workspace = true, features = ["v4"], optional = true } v_htmlescape = { workspace = true } wax = { workspace = true } -which = { workspace = true } +which = { workspace = true, optional = true } unicode-width = { workspace = true } data-encoding = { version = "2.6.0", features = ["alloc"] } [target.'cfg(windows)'.dependencies] winreg = { workspace = true } -[target.'cfg(not(windows))'.dependencies] +[target.'cfg(all(not(windows), not(target_arch = "wasm32")))'.dependencies] uucore = { workspace = true, features = ["mode"] } [target.'cfg(unix)'.dependencies] @@ -135,7 +136,53 @@ features = [ workspace = true [features] -plugin = ["nu-parser/plugin"] +default = ["os"] +os = [ + # include other features + "js", + "network", + "nu-protocol/os", + "nu-utils/os", + + # os-dependant dependencies + "crossterm", + "notify-debouncer-full", + "open", + "os_pipe", + "uu_cp", + "uu_mkdir", + "uu_mktemp", + "uu_mv", + "uu_touch", + "uu_uname", + "uu_whoami", + "which", +] + +# The dependencies listed below need 'getrandom'. +# They work with JS (usually with wasm-bindgen) or regular OS support. +# Hence they are also put under the 'os' feature to avoid repetition. +js = [ + "getrandom", + "getrandom/js", + "rand", + "uuid", +] + +# These dependencies require networking capabilities, especially the http +# interface requires openssl which is not easy to embed into wasm, +# using rustls could solve this issue. +network = [ + "multipart-rs", + "native-tls", + "ureq/native-tls", + "uuid", +] + +plugin = [ + "nu-parser/plugin", + "os", +] sqlite = ["rusqlite"] trash-support = ["trash"] @@ -150,4 +197,4 @@ quickcheck_macros = { workspace = true } rstest = { workspace = true, default-features = false } pretty_assertions = { workspace = true } tempfile = { workspace = true } -rand_chacha = { workspace = true } \ No newline at end of file +rand_chacha = { workspace = true } diff --git a/crates/nu-command/src/bytes/build_.rs b/crates/nu-command/src/bytes/build_.rs index f9ca58b10f..de68632ac8 100644 --- a/crates/nu-command/src/bytes/build_.rs +++ b/crates/nu-command/src/bytes/build_.rs @@ -1,4 +1,4 @@ -use nu_engine::{command_prelude::*, get_eval_expression}; +use nu_engine::command_prelude::*; #[derive(Clone)] pub struct BytesBuild; @@ -49,8 +49,7 @@ impl Command for BytesBuild { _input: PipelineData, ) -> Result { let mut output = vec![]; - let eval_expression = get_eval_expression(engine_state); - for val in call.rest_iter_flattened(engine_state, stack, eval_expression, 0)? { + for val in call.rest::(engine_state, stack, 0)? { let val_span = val.span(); match val { Value::Binary { mut val, .. } => output.append(&mut val), diff --git a/crates/nu-command/src/charting/hashable_value.rs b/crates/nu-command/src/charting/hashable_value.rs index b74b5f7761..636f5b7a05 100644 --- a/crates/nu-command/src/charting/hashable_value.rs +++ b/crates/nu-command/src/charting/hashable_value.rs @@ -1,5 +1,5 @@ use chrono::{DateTime, FixedOffset}; -use nu_protocol::{ShellError, Span, Value}; +use nu_protocol::{Filesize, ShellError, Span, Value}; use std::hash::{Hash, Hasher}; /// A subset of [`Value`], which is hashable. @@ -30,7 +30,7 @@ pub enum HashableValue { span: Span, }, Filesize { - val: i64, + val: Filesize, span: Span, }, Duration { @@ -198,7 +198,10 @@ mod test { (Value::int(1, span), HashableValue::Int { val: 1, span }), ( Value::filesize(1, span), - HashableValue::Filesize { val: 1, span }, + HashableValue::Filesize { + val: 1.into(), + span, + }, ), ( Value::duration(1, span), diff --git a/crates/nu-command/src/conversions/fill.rs b/crates/nu-command/src/conversions/fill.rs index d125ee29b2..65d6b20b22 100644 --- a/crates/nu-command/src/conversions/fill.rs +++ b/crates/nu-command/src/conversions/fill.rs @@ -167,7 +167,7 @@ fn fill( fn action(input: &Value, args: &Arguments, span: Span) -> Value { match input { Value::Int { val, .. } => fill_int(*val, args, span), - Value::Filesize { val, .. } => fill_int(*val, args, span), + Value::Filesize { val, .. } => fill_int(val.get(), args, span), Value::Float { val, .. } => fill_float(*val, args, span), Value::String { val, .. } => fill_string(val, args, span), // Propagate errors by explicitly matching them before the final case. diff --git a/crates/nu-command/src/conversions/into/binary.rs b/crates/nu-command/src/conversions/into/binary.rs index d82f911506..fb549e50a7 100644 --- a/crates/nu-command/src/conversions/into/binary.rs +++ b/crates/nu-command/src/conversions/into/binary.rs @@ -147,7 +147,7 @@ pub fn action(input: &Value, _args: &Arguments, span: Span) -> Value { Value::Binary { .. } => input.clone(), Value::Int { val, .. } => Value::binary(val.to_ne_bytes().to_vec(), span), Value::Float { val, .. } => Value::binary(val.to_ne_bytes().to_vec(), span), - Value::Filesize { val, .. } => Value::binary(val.to_ne_bytes().to_vec(), span), + Value::Filesize { val, .. } => Value::binary(val.get().to_ne_bytes().to_vec(), span), Value::String { val, .. } => Value::binary(val.as_bytes().to_vec(), span), Value::Bool { val, .. } => Value::binary(i64::from(*val).to_ne_bytes().to_vec(), span), Value::Duration { val, .. } => Value::binary(val.to_ne_bytes().to_vec(), span), diff --git a/crates/nu-command/src/conversions/into/int.rs b/crates/nu-command/src/conversions/into/int.rs index f55770e7ff..67cba23090 100644 --- a/crates/nu-command/src/conversions/into/int.rs +++ b/crates/nu-command/src/conversions/into/int.rs @@ -253,7 +253,7 @@ fn action(input: &Value, args: &Arguments, span: Span) -> Value { convert_int(input, span, radix) } } - Value::Filesize { val, .. } => Value::int(*val, span), + Value::Filesize { val, .. } => Value::int(val.get(), span), Value::Float { val, .. } => Value::int( { if radix == 10 { diff --git a/crates/nu-command/src/conversions/into/record.rs b/crates/nu-command/src/conversions/into/record.rs index 445d81cb86..fea5fe1e4e 100644 --- a/crates/nu-command/src/conversions/into/record.rs +++ b/crates/nu-command/src/conversions/into/record.rs @@ -99,6 +99,11 @@ impl Command for SubCommand { "timezone" => Value::test_string("+02:00"), })), }, + Example { + description: "convert date components to table columns", + example: "2020-04-12T22:10:57+02:00 | into record | transpose | transpose -r", + result: None, + }, ] } } diff --git a/crates/nu-command/src/database/commands/into_sqlite.rs b/crates/nu-command/src/database/commands/into_sqlite.rs index fcfa228f3b..3236f90999 100644 --- a/crates/nu-command/src/database/commands/into_sqlite.rs +++ b/crates/nu-command/src/database/commands/into_sqlite.rs @@ -359,7 +359,6 @@ fn nu_value_to_sqlite_type(val: &Value) -> Result<&'static str, ShellError> { | Type::Custom(_) | Type::Error | Type::List(_) - | Type::ListStream | Type::Range | Type::Record(_) | Type::Signature diff --git a/crates/nu-command/src/database/values/sqlite.rs b/crates/nu-command/src/database/values/sqlite.rs index 5d0f7d1fe3..620ac95324 100644 --- a/crates/nu-command/src/database/values/sqlite.rs +++ b/crates/nu-command/src/database/values/sqlite.rs @@ -421,7 +421,7 @@ pub fn value_to_sql(value: Value) -> Result, ShellError Value::Bool { val, .. } => Box::new(val), Value::Int { val, .. } => Box::new(val), Value::Float { val, .. } => Box::new(val), - Value::Filesize { val, .. } => Box::new(val), + Value::Filesize { val, .. } => Box::new(val.get()), Value::Duration { val, .. } => Box::new(val), Value::Date { val, .. } => Box::new(val), Value::String { val, .. } => Box::new(val), diff --git a/crates/nu-command/src/date/to_record.rs b/crates/nu-command/src/date/to_record.rs index f683ca1eec..fc28ba46f5 100644 --- a/crates/nu-command/src/date/to_record.rs +++ b/crates/nu-command/src/date/to_record.rs @@ -1,6 +1,7 @@ use crate::date::utils::parse_date_from_string; use chrono::{DateTime, Datelike, FixedOffset, Local, Timelike}; use nu_engine::command_prelude::*; +use nu_protocol::{report_parse_warning, ParseWarning}; #[derive(Clone)] pub struct SubCommand; @@ -17,7 +18,7 @@ impl Command for SubCommand { (Type::String, Type::record()), ]) .allow_variants_without_examples(true) // https://github.com/nushell/nushell/issues/7032 - .category(Category::Date) + .category(Category::Deprecated) } fn description(&self) -> &str { @@ -35,6 +36,17 @@ impl Command for SubCommand { call: &Call, input: PipelineData, ) -> Result { + let head = call.head; + report_parse_warning( + &StateWorkingSet::new(engine_state), + &ParseWarning::DeprecatedWarning { + old_command: "date to-record".into(), + new_suggestion: "see `into record` command examples".into(), + span: head, + url: "`help into record`".into(), + }, + ); + let head = call.head; // This doesn't match explicit nulls if matches!(input, PipelineData::Empty) { diff --git a/crates/nu-command/src/date/to_table.rs b/crates/nu-command/src/date/to_table.rs index 1588d40063..cb635b039f 100644 --- a/crates/nu-command/src/date/to_table.rs +++ b/crates/nu-command/src/date/to_table.rs @@ -1,6 +1,7 @@ use crate::date::utils::parse_date_from_string; use chrono::{DateTime, Datelike, FixedOffset, Local, Timelike}; use nu_engine::command_prelude::*; +use nu_protocol::{report_parse_warning, ParseWarning}; #[derive(Clone)] pub struct SubCommand; @@ -17,7 +18,7 @@ impl Command for SubCommand { (Type::String, Type::table()), ]) .allow_variants_without_examples(true) // https://github.com/nushell/nushell/issues/7032 - .category(Category::Date) + .category(Category::Deprecated) } fn description(&self) -> &str { @@ -36,6 +37,16 @@ impl Command for SubCommand { input: PipelineData, ) -> Result { let head = call.head; + report_parse_warning( + &StateWorkingSet::new(engine_state), + &ParseWarning::DeprecatedWarning { + old_command: "date to-table".into(), + new_suggestion: "see `into record` command examples".into(), + span: head, + url: "`help into record`".into(), + }, + ); + // This doesn't match explicit nulls if matches!(input, PipelineData::Empty) { return Err(ShellError::PipelineEmpty { dst_span: head }); diff --git a/crates/nu-command/src/debug/info.rs b/crates/nu-command/src/debug/info.rs index 2a680ae20d..4912da8a1d 100644 --- a/crates/nu-command/src/debug/info.rs +++ b/crates/nu-command/src/debug/info.rs @@ -177,4 +177,9 @@ fn get_thread_id() -> u64 { { nix::sys::pthread::pthread_self() as u64 } + #[cfg(target_arch = "wasm32")] + { + // wasm doesn't have any threads accessible, so we return 0 as a fallback + 0 + } } diff --git a/crates/nu-command/src/debug/inspect.rs b/crates/nu-command/src/debug/inspect.rs index 97217780bf..f8ef63ce02 100644 --- a/crates/nu-command/src/debug/inspect.rs +++ b/crates/nu-command/src/debug/inspect.rs @@ -1,6 +1,6 @@ use super::inspect_table; use nu_engine::command_prelude::*; -use terminal_size::{terminal_size, Height, Width}; +use nu_utils::terminal_size; #[derive(Clone)] pub struct Inspect; @@ -38,12 +38,9 @@ impl Command for Inspect { let original_input = input_val.clone(); let description = input_val.get_type().to_string(); - let (cols, _rows) = match terminal_size() { - Some((w, h)) => (Width(w.0), Height(h.0)), - None => (Width(0), Height(0)), - }; + let (cols, _rows) = terminal_size().unwrap_or((0, 0)); - let table = inspect_table::build_table(input_val, description, cols.0 as usize); + let table = inspect_table::build_table(input_val, description, cols as usize); // Note that this is printed to stderr. The reason for this is so it doesn't disrupt the regular nushell // tabular output. If we printed to stdout, nushell would get confused with two outputs. diff --git a/crates/nu-command/src/debug/timeit.rs b/crates/nu-command/src/debug/timeit.rs index 7bb492769c..182cab87d7 100644 --- a/crates/nu-command/src/debug/timeit.rs +++ b/crates/nu-command/src/debug/timeit.rs @@ -1,4 +1,5 @@ -use nu_engine::{command_prelude::*, get_eval_block, get_eval_expression_with_input}; +use nu_engine::{command_prelude::*, ClosureEvalOnce}; +use nu_protocol::engine::Closure; use std::time::Instant; #[derive(Clone)] @@ -10,16 +11,18 @@ impl Command for TimeIt { } fn description(&self) -> &str { - "Time the running time of a block." + "Time how long it takes a closure to run." + } + + fn extra_description(&self) -> &str { + "Any pipeline input given to this command is passed to the closure. Note that streaming inputs may affect timing results, and it is recommended to add a `collect` command before this if the input is a stream. + +This command will bubble up any errors encountered when running the closure. The return pipeline of the closure is collected into a value and then discarded." } fn signature(&self) -> nu_protocol::Signature { Signature::build("timeit") - .required( - "command", - SyntaxShape::OneOf(vec![SyntaxShape::Block, SyntaxShape::Expression]), - "The command or block to run.", - ) + .required("command", SyntaxShape::Closure(None), "The closure to run.") .input_output_types(vec![ (Type::Any, Type::Duration), (Type::Nothing, Type::Duration), @@ -46,51 +49,38 @@ impl Command for TimeIt { // reset outdest, so the command can write to stdout and stderr. let stack = &mut stack.push_redirection(None, None); - let command_to_run = call.positional_nth(stack, 0); + let closure: Closure = call.req(engine_state, stack, 0)?; + let closure = ClosureEvalOnce::new_preserve_out_dest(engine_state, stack, closure); // Get the start time after all other computation has been done. let start_time = Instant::now(); + closure.run_with_input(input)?.into_value(call.head)?; + let time = start_time.elapsed(); - if let Some(command_to_run) = command_to_run { - if let Some(block_id) = command_to_run.as_block() { - let eval_block = get_eval_block(engine_state); - let block = engine_state.get_block(block_id); - eval_block(engine_state, stack, block, input)? - } else { - let eval_expression_with_input = get_eval_expression_with_input(engine_state); - let expression = &command_to_run.clone(); - eval_expression_with_input(engine_state, stack, expression, input)? - } - } else { - PipelineData::empty() - } - .into_value(call.head)?; - - let end_time = Instant::now(); - - let output = Value::duration( - end_time.saturating_duration_since(start_time).as_nanos() as i64, - call.head, - ); - + let output = Value::duration(time.as_nanos() as i64, call.head); Ok(output.into_pipeline_data()) } fn examples(&self) -> Vec { vec![ Example { - description: "Times a command within a closure", + description: "Time a closure containing one command", example: "timeit { sleep 500ms }", result: None, }, Example { - description: "Times a command using an existing input", - example: "http get https://www.nushell.sh/book/ | timeit { split chars }", + description: "Time a closure with an input value", + example: "'A really long string' | timeit { split chars }", result: None, }, Example { - description: "Times a command invocation", - example: "timeit ls -la", + description: "Time a closure with an input stream", + example: "open some_file.txt | collect | timeit { split chars }", + result: None, + }, + Example { + description: "Time a closure containing a pipeline", + example: "timeit { open some_file.txt | split chars }", result: None, }, ] diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index cbbee717d4..a4288c685c 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -27,6 +27,10 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { } // Filters + #[cfg(feature = "rand")] + bind_command! { + Shuffle + } bind_command! { All, Any, @@ -64,6 +68,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { Length, Lines, ParEach, + ChunkBy, Prepend, Range, Reduce, @@ -71,7 +76,6 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { Rename, Reverse, Select, - Shuffle, Skip, SkipUntil, SkipWhile, @@ -102,6 +106,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { bind_command! { Path, PathBasename, + PathSelf, PathDirname, PathExists, PathExpand, @@ -113,6 +118,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { }; // System + #[cfg(feature = "os")] bind_command! { Complete, External, @@ -160,17 +166,20 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { ViewSpan, }; - #[cfg(windows)] + #[cfg(all(feature = "os", windows))] bind_command! { RegistryQuery } - #[cfg(any( - target_os = "android", - target_os = "linux", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd", - target_os = "macos", - target_os = "windows" + #[cfg(all( + feature = "os", + any( + target_os = "android", + target_os = "linux", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd", + target_os = "macos", + target_os = "windows" + ) ))] bind_command! { Ps }; @@ -218,6 +227,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { }; // FileSystem + #[cfg(feature = "os")] bind_command! { Cd, Ls, @@ -236,6 +246,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { }; // Platform + #[cfg(feature = "os")] bind_command! { Ansi, AnsiLink, @@ -248,11 +259,13 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { IsTerminal, Kill, Sleep, + Term, TermSize, + TermQuery, Whoami, }; - #[cfg(unix)] + #[cfg(all(unix, feature = "os"))] bind_command! { ULimit }; // Date @@ -377,6 +390,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { } // Network + #[cfg(feature = "network")] bind_command! { Http, HttpDelete, @@ -386,6 +400,9 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { HttpPost, HttpPut, HttpOptions, + Port, + } + bind_command! { Url, UrlBuildQuery, UrlSplitQuery, @@ -393,10 +410,10 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { UrlEncode, UrlJoin, UrlParse, - Port, } // Random + #[cfg(feature = "rand")] bind_command! { Random, RandomBool, diff --git a/crates/nu-command/src/env/config/config_.rs b/crates/nu-command/src/env/config/config_.rs index 207c8c8651..71afd57c5a 100644 --- a/crates/nu-command/src/env/config/config_.rs +++ b/crates/nu-command/src/env/config/config_.rs @@ -1,4 +1,6 @@ -use nu_engine::{command_prelude::*, get_full_help}; +use nu_cmd_base::util::get_editor; +use nu_engine::{command_prelude::*, env_to_strings, get_full_help}; +use nu_system::ForegroundChild; #[derive(Clone)] pub struct ConfigMeta; @@ -36,3 +38,79 @@ impl Command for ConfigMeta { vec!["options", "setup"] } } + +#[cfg(not(feature = "os"))] +pub(super) fn start_editor( + _: &'static str, + _: &EngineState, + _: &mut Stack, + call: &Call, +) -> Result { + Err(ShellError::DisabledOsSupport { + msg: "Running external commands is not available without OS support.".to_string(), + span: Some(call.head), + }) +} + +#[cfg(feature = "os")] +pub(super) fn start_editor( + config_path: &'static str, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, +) -> Result { + // Find the editor executable. + let (editor_name, editor_args) = get_editor(engine_state, stack, call.head)?; + let paths = nu_engine::env::path_str(engine_state, stack, call.head)?; + let cwd = engine_state.cwd(Some(stack))?; + let editor_executable = + crate::which(&editor_name, &paths, cwd.as_ref()).ok_or(ShellError::ExternalCommand { + label: format!("`{editor_name}` not found"), + help: "Failed to find the editor executable".into(), + span: call.head, + })?; + + let Some(config_path) = engine_state.get_config_path(config_path) else { + return Err(ShellError::GenericError { + error: format!("Could not find $nu.{config_path}"), + msg: format!("Could not find $nu.{config_path}"), + span: None, + help: None, + inner: vec![], + }); + }; + let config_path = config_path.to_string_lossy().to_string(); + + // Create the command. + let mut command = std::process::Command::new(editor_executable); + + // Configure PWD. + command.current_dir(cwd); + + // Configure environment variables. + let envs = env_to_strings(engine_state, stack)?; + command.env_clear(); + command.envs(envs); + + // Configure args. + command.arg(config_path); + command.args(editor_args); + + // Spawn the child process. On Unix, also put the child process to + // foreground if we're in an interactive session. + #[cfg(windows)] + let child = ForegroundChild::spawn(command)?; + #[cfg(unix)] + let child = ForegroundChild::spawn( + command, + engine_state.is_interactive, + &engine_state.pipeline_externals_state, + )?; + + // Wrap the output into a `PipelineData::ByteStream`. + let child = nu_protocol::process::ChildProcess::new(child, None, false, call.head)?; + Ok(PipelineData::ByteStream( + ByteStream::child(child, call.head), + None, + )) +} diff --git a/crates/nu-command/src/env/config/config_env.rs b/crates/nu-command/src/env/config/config_env.rs index 5331d2d3be..5fdc39a903 100644 --- a/crates/nu-command/src/env/config/config_env.rs +++ b/crates/nu-command/src/env/config/config_env.rs @@ -1,7 +1,4 @@ -use nu_cmd_base::util::get_editor; -use nu_engine::{command_prelude::*, env_to_strings}; -use nu_protocol::{process::ChildProcess, ByteStream}; -use nu_system::ForegroundChild; +use nu_engine::command_prelude::*; #[derive(Clone)] pub struct ConfigEnv; @@ -81,60 +78,6 @@ impl Command for ConfigEnv { return Ok(Value::string(nu_utils::get_sample_env(), head).into_pipeline_data()); } - // Find the editor executable. - let (editor_name, editor_args) = get_editor(engine_state, stack, call.head)?; - let paths = nu_engine::env::path_str(engine_state, stack, call.head)?; - let cwd = engine_state.cwd(Some(stack))?; - let editor_executable = crate::which(&editor_name, &paths, cwd.as_ref()).ok_or( - ShellError::ExternalCommand { - label: format!("`{editor_name}` not found"), - help: "Failed to find the editor executable".into(), - span: call.head, - }, - )?; - - let Some(env_path) = engine_state.get_config_path("env-path") else { - return Err(ShellError::GenericError { - error: "Could not find $nu.env-path".into(), - msg: "Could not find $nu.env-path".into(), - span: None, - help: None, - inner: vec![], - }); - }; - let env_path = env_path.to_string_lossy().to_string(); - - // Create the command. - let mut command = std::process::Command::new(editor_executable); - - // Configure PWD. - command.current_dir(cwd); - - // Configure environment variables. - let envs = env_to_strings(engine_state, stack)?; - command.env_clear(); - command.envs(envs); - - // Configure args. - command.arg(env_path); - command.args(editor_args); - - // Spawn the child process. On Unix, also put the child process to - // foreground if we're in an interactive session. - #[cfg(windows)] - let child = ForegroundChild::spawn(command)?; - #[cfg(unix)] - let child = ForegroundChild::spawn( - command, - engine_state.is_interactive, - &engine_state.pipeline_externals_state, - )?; - - // Wrap the output into a `PipelineData::ByteStream`. - let child = ChildProcess::new(child, None, false, call.head)?; - Ok(PipelineData::ByteStream( - ByteStream::child(child, call.head), - None, - )) + super::config_::start_editor("env-path", engine_state, stack, call) } } diff --git a/crates/nu-command/src/env/config/config_nu.rs b/crates/nu-command/src/env/config/config_nu.rs index 176643a76f..75d971f36e 100644 --- a/crates/nu-command/src/env/config/config_nu.rs +++ b/crates/nu-command/src/env/config/config_nu.rs @@ -1,7 +1,4 @@ -use nu_cmd_base::util::get_editor; -use nu_engine::{command_prelude::*, env_to_strings}; -use nu_protocol::{process::ChildProcess, ByteStream}; -use nu_system::ForegroundChild; +use nu_engine::command_prelude::*; #[derive(Clone)] pub struct ConfigNu; @@ -83,60 +80,6 @@ impl Command for ConfigNu { return Ok(Value::string(nu_utils::get_sample_config(), head).into_pipeline_data()); } - // Find the editor executable. - let (editor_name, editor_args) = get_editor(engine_state, stack, call.head)?; - let paths = nu_engine::env::path_str(engine_state, stack, call.head)?; - let cwd = engine_state.cwd(Some(stack))?; - let editor_executable = crate::which(&editor_name, &paths, cwd.as_ref()).ok_or( - ShellError::ExternalCommand { - label: format!("`{editor_name}` not found"), - help: "Failed to find the editor executable".into(), - span: call.head, - }, - )?; - - let Some(config_path) = engine_state.get_config_path("config-path") else { - return Err(ShellError::GenericError { - error: "Could not find $nu.config-path".into(), - msg: "Could not find $nu.config-path".into(), - span: None, - help: None, - inner: vec![], - }); - }; - let config_path = config_path.to_string_lossy().to_string(); - - // Create the command. - let mut command = std::process::Command::new(editor_executable); - - // Configure PWD. - command.current_dir(cwd); - - // Configure environment variables. - let envs = env_to_strings(engine_state, stack)?; - command.env_clear(); - command.envs(envs); - - // Configure args. - command.arg(config_path); - command.args(editor_args); - - // Spawn the child process. On Unix, also put the child process to - // foreground if we're in an interactive session. - #[cfg(windows)] - let child = ForegroundChild::spawn(command)?; - #[cfg(unix)] - let child = ForegroundChild::spawn( - command, - engine_state.is_interactive, - &engine_state.pipeline_externals_state, - )?; - - // Wrap the output into a `PipelineData::ByteStream`. - let child = ChildProcess::new(child, None, false, call.head)?; - Ok(PipelineData::ByteStream( - ByteStream::child(child, call.head), - None, - )) + super::config_::start_editor("config-path", engine_state, stack, call) } } diff --git a/crates/nu-command/src/experimental/is_admin.rs b/crates/nu-command/src/experimental/is_admin.rs index 2dbeb0e988..37434f716e 100644 --- a/crates/nu-command/src/experimental/is_admin.rs +++ b/crates/nu-command/src/experimental/is_admin.rs @@ -103,3 +103,9 @@ fn is_root_impl() -> bool { elevated } + +#[cfg(target_arch = "wasm32")] +fn is_root_impl() -> bool { + // in wasm we don't have a user system, so technically we are never root + false +} diff --git a/crates/nu-command/src/filesystem/du.rs b/crates/nu-command/src/filesystem/du.rs index 6ae07974b9..15dc0d78d6 100644 --- a/crates/nu-command/src/filesystem/du.rs +++ b/crates/nu-command/src/filesystem/du.rs @@ -1,4 +1,3 @@ -use super::util::get_rest_for_glob_pattern; use crate::{DirBuilder, DirInfo, FileInfo}; #[allow(deprecated)] use nu_engine::{command_prelude::*, current_dir}; @@ -13,8 +12,8 @@ pub struct Du; #[derive(Deserialize, Clone, Debug)] pub struct DuArgs { path: Option>, - all: bool, deref: bool, + long: bool, exclude: Option>, #[serde(rename = "max-depth")] max_depth: Option>, @@ -50,6 +49,11 @@ impl Command for Du { "Dereference symlinks to their targets for size", Some('r'), ) + .switch( + "long", + "Get underlying directories and files for each entry", + Some('l'), + ) .named( "exclude", SyntaxShape::GlobPattern, @@ -95,13 +99,13 @@ impl Command for Du { }); } } - let all = call.has_flag(engine_state, stack, "all")?; let deref = call.has_flag(engine_state, stack, "deref")?; + let long = call.has_flag(engine_state, stack, "long")?; let exclude = call.get_flag(engine_state, stack, "exclude")?; #[allow(deprecated)] let current_dir = current_dir(engine_state, stack)?; - let paths = get_rest_for_glob_pattern(engine_state, stack, call, 0)?; + let paths = call.rest::>(engine_state, stack, 0)?; let paths = if !call.has_positional_args(stack, 0) { None } else { @@ -112,8 +116,8 @@ impl Command for Du { None => { let args = DuArgs { path: None, - all, deref, + long, exclude, max_depth, min_size, @@ -128,8 +132,8 @@ impl Command for Du { for p in paths { let args = DuArgs { path: Some(p), - all, deref, + long, exclude: exclude.clone(), max_depth, min_size, @@ -175,7 +179,6 @@ fn du_for_one_pattern( }) })?; - let include_files = args.all; let mut paths = match args.path { Some(p) => nu_engine::glob_from(&p, current_dir, span, None), // The * pattern should never fail. @@ -189,17 +192,10 @@ fn du_for_one_pattern( None, ), } - .map(|f| f.1)? - .filter(move |p| { - if include_files { - true - } else { - matches!(p, Ok(f) if f.is_dir()) - } - }); + .map(|f| f.1)?; - let all = args.all; let deref = args.deref; + let long = args.long; let max_depth = args.max_depth.map(|f| f.item as u64); let min_size = args.min_size.map(|f| f.item as u64); @@ -208,7 +204,7 @@ fn du_for_one_pattern( min: min_size, deref, exclude, - all, + long, }; let mut output: Vec = vec![]; @@ -217,7 +213,7 @@ fn du_for_one_pattern( Ok(a) => { if a.is_dir() { output.push(DirInfo::new(a, ¶ms, max_depth, span, signals)?.into()); - } else if let Ok(v) = FileInfo::new(a, deref, span) { + } else if let Ok(v) = FileInfo::new(a, deref, span, params.long) { output.push(v.into()); } } diff --git a/crates/nu-command/src/filesystem/glob.rs b/crates/nu-command/src/filesystem/glob.rs index 1f475efbac..4a293e82fe 100644 --- a/crates/nu-command/src/filesystem/glob.rs +++ b/crates/nu-command/src/filesystem/glob.rs @@ -1,5 +1,5 @@ use nu_engine::command_prelude::*; -use nu_protocol::Signals; +use nu_protocol::{ListStream, Signals}; use wax::{Glob as WaxGlob, WalkBehavior, WalkEntry}; #[derive(Clone)] @@ -223,6 +223,7 @@ impl Command for Glob { ..Default::default() }, ) + .into_owned() .not(np) .map_err(|err| ShellError::GenericError { error: "error with glob's not pattern".into(), @@ -249,6 +250,7 @@ impl Command for Glob { ..Default::default() }, ) + .into_owned() .flatten(); glob_to_value( engine_state.signals(), @@ -258,11 +260,9 @@ impl Command for Glob { no_symlinks, span, ) - }?; + }; - Ok(result - .into_iter() - .into_pipeline_data(span, engine_state.signals().clone())) + Ok(result.into_pipeline_data(span, engine_state.signals().clone())) } } @@ -281,29 +281,33 @@ fn convert_patterns(columns: &[Value]) -> Result, ShellError> { Ok(res) } -fn glob_to_value<'a>( +fn glob_to_value( signals: &Signals, - glob_results: impl Iterator>, + glob_results: impl Iterator> + Send + 'static, no_dirs: bool, no_files: bool, no_symlinks: bool, span: Span, -) -> Result, ShellError> { - let mut result: Vec = Vec::new(); - for entry in glob_results { - signals.check(span)?; +) -> ListStream { + let map_signals = signals.clone(); + let result = glob_results.filter_map(move |entry| { + if let Err(err) = map_signals.check(span) { + return Some(Value::error(err, span)); + }; let file_type = entry.file_type(); if !(no_dirs && file_type.is_dir() || no_files && file_type.is_file() || no_symlinks && file_type.is_symlink()) { - result.push(Value::string( + Some(Value::string( entry.into_path().to_string_lossy().to_string(), span, - )); + )) + } else { + None } - } + }); - Ok(result) + ListStream::new(result, span, signals.clone()) } diff --git a/crates/nu-command/src/filesystem/ls.rs b/crates/nu-command/src/filesystem/ls.rs index c4944c6531..1e9e66c96b 100644 --- a/crates/nu-command/src/filesystem/ls.rs +++ b/crates/nu-command/src/filesystem/ls.rs @@ -1,4 +1,3 @@ -use super::util::get_rest_for_glob_pattern; use crate::{DirBuilder, DirInfo}; use chrono::{DateTime, Local, LocalResult, TimeZone, Utc}; use nu_engine::glob_from; @@ -114,7 +113,7 @@ impl Command for Ls { call_span, }; - let pattern_arg = get_rest_for_glob_pattern(engine_state, stack, call, 0)?; + let pattern_arg = call.rest::>(engine_state, stack, 0)?; let input_pattern_arg = if !call.has_positional_args(stack, 0) { None } else { diff --git a/crates/nu-command/src/filesystem/open.rs b/crates/nu-command/src/filesystem/open.rs index 855272d978..d9240cfae5 100644 --- a/crates/nu-command/src/filesystem/open.rs +++ b/crates/nu-command/src/filesystem/open.rs @@ -1,7 +1,6 @@ -use super::util::get_rest_for_glob_pattern; #[allow(deprecated)] use nu_engine::{command_prelude::*, current_dir, get_eval_block}; -use nu_protocol::{ast, ByteStream, DataSource, NuGlob, PipelineMetadata}; +use nu_protocol::{ast, DataSource, NuGlob, PipelineMetadata}; use std::path::Path; #[cfg(feature = "sqlite")] @@ -53,7 +52,7 @@ impl Command for Open { let call_span = call.head; #[allow(deprecated)] let cwd = current_dir(engine_state, stack)?; - let mut paths = get_rest_for_glob_pattern(engine_state, stack, call, 0)?; + let mut paths = call.rest::>(engine_state, stack, 0)?; let eval_block = get_eval_block(engine_state); if paths.is_empty() && !call.has_positional_args(stack, 0) { diff --git a/crates/nu-command/src/filesystem/rm.rs b/crates/nu-command/src/filesystem/rm.rs index 3d2c712155..9a13442aa3 100644 --- a/crates/nu-command/src/filesystem/rm.rs +++ b/crates/nu-command/src/filesystem/rm.rs @@ -1,4 +1,4 @@ -use super::util::{get_rest_for_glob_pattern, try_interaction}; +use super::util::try_interaction; #[allow(deprecated)] use nu_engine::{command_prelude::*, env::current_dir}; use nu_glob::MatchOptions; @@ -118,7 +118,7 @@ fn rm( let interactive = call.has_flag(engine_state, stack, "interactive")?; let interactive_once = call.has_flag(engine_state, stack, "interactive-once")? && !interactive; - let mut paths = get_rest_for_glob_pattern(engine_state, stack, call, 0)?; + let mut paths = call.rest::>(engine_state, stack, 0)?; if paths.is_empty() { return Err(ShellError::MissingParameter { diff --git a/crates/nu-command/src/filesystem/save.rs b/crates/nu-command/src/filesystem/save.rs index 6fda754341..c4ae00f593 100644 --- a/crates/nu-command/src/filesystem/save.rs +++ b/crates/nu-command/src/filesystem/save.rs @@ -102,6 +102,7 @@ impl Command for Save { ByteStreamSource::File(source) => { stream_to_file(source, size, signals, file, span, progress)?; } + #[cfg(feature = "os")] ByteStreamSource::Child(mut child) => { fn write_or_consume_stderr( stderr: ChildPipe, diff --git a/crates/nu-command/src/filesystem/touch.rs b/crates/nu-command/src/filesystem/touch.rs index b4cd808be5..93b960d843 100644 --- a/crates/nu-command/src/filesystem/touch.rs +++ b/crates/nu-command/src/filesystem/touch.rs @@ -2,11 +2,8 @@ use filetime::FileTime; use nu_engine::command_prelude::*; use nu_path::expand_path_with; use nu_protocol::NuGlob; - use std::{fs::OpenOptions, time::SystemTime}; -use super::util::get_rest_for_glob_pattern; - #[derive(Clone)] pub struct Touch; @@ -72,7 +69,7 @@ impl Command for Touch { let no_follow_symlinks: bool = call.has_flag(engine_state, stack, "no-deref")?; let reference: Option> = call.get_flag(engine_state, stack, "reference")?; let no_create: bool = call.has_flag(engine_state, stack, "no-create")?; - let files: Vec> = get_rest_for_glob_pattern(engine_state, stack, call, 0)?; + let files = call.rest::>(engine_state, stack, 0)?; let cwd = engine_state.cwd(Some(stack))?; diff --git a/crates/nu-command/src/filesystem/ucp.rs b/crates/nu-command/src/filesystem/ucp.rs index 3bc7f13325..b100a23bff 100644 --- a/crates/nu-command/src/filesystem/ucp.rs +++ b/crates/nu-command/src/filesystem/ucp.rs @@ -1,6 +1,6 @@ -use super::util::get_rest_for_glob_pattern; #[allow(deprecated)] use nu_engine::{command_prelude::*, current_dir}; +use nu_protocol::NuGlob; use std::path::PathBuf; use uu_cp::{BackupMode, CopyMode, UpdateMode}; @@ -156,7 +156,7 @@ impl Command for UCp { target_os = "macos" )))] let reflink_mode = uu_cp::ReflinkMode::Never; - let mut paths = get_rest_for_glob_pattern(engine_state, stack, call, 0)?; + let mut paths = call.rest::>(engine_state, stack, 0)?; if paths.is_empty() { return Err(ShellError::GenericError { error: "Missing file operand".into(), diff --git a/crates/nu-command/src/filesystem/umkdir.rs b/crates/nu-command/src/filesystem/umkdir.rs index 6f4c77ef1b..589fd6f76d 100644 --- a/crates/nu-command/src/filesystem/umkdir.rs +++ b/crates/nu-command/src/filesystem/umkdir.rs @@ -1,12 +1,10 @@ #[allow(deprecated)] use nu_engine::{command_prelude::*, current_dir}; - +use nu_protocol::NuGlob; use uu_mkdir::mkdir; #[cfg(not(windows))] use uucore::mode; -use super::util::get_rest_for_glob_pattern; - #[derive(Clone)] pub struct UMkdir; @@ -61,7 +59,8 @@ impl Command for UMkdir { ) -> Result { #[allow(deprecated)] let cwd = current_dir(engine_state, stack)?; - let mut directories = get_rest_for_glob_pattern(engine_state, stack, call, 0)? + let mut directories = call + .rest::>(engine_state, stack, 0)? .into_iter() .map(|dir| nu_path::expand_path_with(dir.item.as_ref(), &cwd, dir.item.is_expand())) .peekable(); diff --git a/crates/nu-command/src/filesystem/umv.rs b/crates/nu-command/src/filesystem/umv.rs index 929fc7bcbb..cdf0c05b1f 100644 --- a/crates/nu-command/src/filesystem/umv.rs +++ b/crates/nu-command/src/filesystem/umv.rs @@ -1,4 +1,3 @@ -use super::util::get_rest_for_glob_pattern; #[allow(deprecated)] use nu_engine::{command_prelude::*, current_dir}; use nu_path::expand_path_with; @@ -100,7 +99,7 @@ impl Command for UMv { #[allow(deprecated)] let cwd = current_dir(engine_state, stack)?; - let mut paths = get_rest_for_glob_pattern(engine_state, stack, call, 0)?; + let mut paths = call.rest::>(engine_state, stack, 0)?; if paths.is_empty() { return Err(ShellError::GenericError { error: "Missing file operand".into(), diff --git a/crates/nu-command/src/filesystem/util.rs b/crates/nu-command/src/filesystem/util.rs index de32d204a0..74a6fb14e9 100644 --- a/crates/nu-command/src/filesystem/util.rs +++ b/crates/nu-command/src/filesystem/util.rs @@ -1,6 +1,4 @@ use dialoguer::Input; -use nu_engine::{command_prelude::*, get_eval_expression}; -use nu_protocol::{FromValue, NuGlob}; use std::{ error::Error, path::{Path, PathBuf}, @@ -89,22 +87,3 @@ pub fn is_older(src: &Path, dst: &Path) -> Option { Some(src_ctime <= dst_ctime) } } - -/// Get rest arguments from given `call`, starts with `starting_pos`. -/// -/// It's similar to `call.rest`, except that it always returns NuGlob. -pub fn get_rest_for_glob_pattern( - engine_state: &EngineState, - stack: &mut Stack, - call: &Call, - starting_pos: usize, -) -> Result>, ShellError> { - let eval_expression = get_eval_expression(engine_state); - - call.rest_iter_flattened(engine_state, stack, eval_expression, starting_pos)? - .into_iter() - // This used to be much more complex, but I think `FromValue` should be able to handle the - // nuance here. - .map(FromValue::from_value) - .collect() -} diff --git a/crates/nu-command/src/filesystem/utouch.rs b/crates/nu-command/src/filesystem/utouch.rs index f32364b28a..b3d827f7b2 100644 --- a/crates/nu-command/src/filesystem/utouch.rs +++ b/crates/nu-command/src/filesystem/utouch.rs @@ -1,19 +1,10 @@ -use std::io::ErrorKind; -use std::path::PathBuf; - use chrono::{DateTime, FixedOffset}; use filetime::FileTime; - -use nu_engine::CallExt; +use nu_engine::command_prelude::*; use nu_path::expand_path_with; -use nu_protocol::engine::{Call, Command, EngineState, Stack}; -use nu_protocol::{ - Category, Example, NuGlob, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, -}; -use uu_touch::error::TouchError; -use uu_touch::{ChangeTimes, InputFile, Options, Source}; - -use super::util::get_rest_for_glob_pattern; +use nu_protocol::NuGlob; +use std::{io::ErrorKind, path::PathBuf}; +use uu_touch::{error::TouchError, ChangeTimes, InputFile, Options, Source}; #[derive(Clone)] pub struct UTouch; @@ -24,7 +15,7 @@ impl Command for UTouch { } fn search_terms(&self) -> Vec<&str> { - vec!["create", "file"] + vec!["create", "file", "coreutils"] } fn signature(&self) -> Signature { @@ -91,8 +82,7 @@ impl Command for UTouch { let change_atime: bool = call.has_flag(engine_state, stack, "access")?; let no_create: bool = call.has_flag(engine_state, stack, "no-create")?; let no_deref: bool = call.has_flag(engine_state, stack, "no-dereference")?; - let file_globs: Vec> = - get_rest_for_glob_pattern(engine_state, stack, call, 0)?; + let file_globs = call.rest::>(engine_state, stack, 0)?; let cwd = engine_state.cwd(Some(stack))?; if file_globs.is_empty() { diff --git a/crates/nu-command/src/filters/all.rs b/crates/nu-command/src/filters/all.rs index e4ffdf2c87..aa36118273 100644 --- a/crates/nu-command/src/filters/all.rs +++ b/crates/nu-command/src/filters/all.rs @@ -14,7 +14,7 @@ impl Command for All { .input_output_types(vec![(Type::List(Box::new(Type::Any)), Type::Bool)]) .required( "predicate", - SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Int])), + SyntaxShape::Closure(Some(vec![SyntaxShape::Any])), "A closure that must evaluate to a boolean.", ) .category(Category::Filters) diff --git a/crates/nu-command/src/filters/any.rs b/crates/nu-command/src/filters/any.rs index cd8a6b65e4..230a96ebe7 100644 --- a/crates/nu-command/src/filters/any.rs +++ b/crates/nu-command/src/filters/any.rs @@ -14,7 +14,7 @@ impl Command for Any { .input_output_types(vec![(Type::List(Box::new(Type::Any)), Type::Bool)]) .required( "predicate", - SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Int])), + SyntaxShape::Closure(Some(vec![SyntaxShape::Any])), "A closure that must evaluate to a boolean.", ) .category(Category::Filters) diff --git a/crates/nu-command/src/filters/chunk_by.rs b/crates/nu-command/src/filters/chunk_by.rs new file mode 100644 index 0000000000..18a5110918 --- /dev/null +++ b/crates/nu-command/src/filters/chunk_by.rs @@ -0,0 +1,256 @@ +use super::utils::chain_error_with_input; +use nu_engine::{command_prelude::*, ClosureEval}; +use nu_protocol::engine::Closure; +use nu_protocol::Signals; + +#[derive(Clone)] +pub struct ChunkBy; + +impl Command for ChunkBy { + fn name(&self) -> &str { + "chunk-by" + } + + fn signature(&self) -> Signature { + Signature::build("chunk-by") + .input_output_types(vec![ + ( + Type::List(Box::new(Type::Any)), + Type::list(Type::list(Type::Any)), + ), + (Type::Range, Type::list(Type::list(Type::Any))), + ]) + .required( + "closure", + SyntaxShape::Closure(Some(vec![SyntaxShape::Any])), + "The closure to run.", + ) + .category(Category::Filters) + } + + fn description(&self) -> &str { + r#"Divides a sequence into sub-sequences based on a closure."# + } + + fn extra_description(&self) -> &str { + r#"chunk-by applies the given closure to each value of the input list, and groups +consecutive elements that share the same closure result value into lists."# + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + chunk_by(engine_state, stack, call, input) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Chunk data into runs of larger than zero or not.", + example: "[1, 3, -2, -2, 0, 1, 2] | chunk-by {|it| $it >= 0 }", + result: Some(Value::test_list(vec![ + Value::test_list(vec![Value::test_int(1), Value::test_int(3)]), + Value::test_list(vec![Value::test_int(-2), Value::test_int(-2)]), + Value::test_list(vec![ + Value::test_int(0), + Value::test_int(1), + Value::test_int(2), + ]), + ])), + }, + Example { + description: "Identify repetitions in a string", + example: r#"[a b b c c c] | chunk-by { |it| $it }"#, + result: Some(Value::test_list(vec![ + Value::test_list(vec![Value::test_string("a")]), + Value::test_list(vec![Value::test_string("b"), Value::test_string("b")]), + Value::test_list(vec![ + Value::test_string("c"), + Value::test_string("c"), + Value::test_string("c"), + ]), + ])), + }, + Example { + description: "Chunk values of range by predicate", + example: r#"(0..8) | chunk-by { |it| $it // 3 }"#, + result: Some(Value::test_list(vec![ + Value::test_list(vec![ + Value::test_int(0), + Value::test_int(1), + Value::test_int(2), + ]), + Value::test_list(vec![ + Value::test_int(3), + Value::test_int(4), + Value::test_int(5), + ]), + Value::test_list(vec![ + Value::test_int(6), + Value::test_int(7), + Value::test_int(8), + ]), + ])), + }, + ] + } +} + +struct Chunk { + iterator: I, + last_value: Option<(T, K)>, + closure: F, + done: bool, + signals: Signals, +} + +impl Chunk +where + I: Iterator, + F: FnMut(&T) -> K, + K: PartialEq, +{ + fn inner_iterator_next(&mut self) -> Option { + if self.signals.interrupted() { + self.done = true; + return None; + } + self.iterator.next() + } +} + +impl Iterator for Chunk +where + I: Iterator, + F: FnMut(&T) -> K, + K: PartialEq, +{ + type Item = Vec; + + fn next(&mut self) -> Option { + if self.done { + return None; + } + + let (head, head_key) = match self.last_value.take() { + None => { + let head = self.inner_iterator_next()?; + + let key = (self.closure)(&head); + + (head, key) + } + + Some((value, key)) => (value, key), + }; + + let mut result = vec![head]; + + loop { + match self.inner_iterator_next() { + None => { + self.done = true; + return Some(result); + } + Some(value) => { + let value_key = (self.closure)(&value); + + if value_key == head_key { + result.push(value); + } else { + self.last_value = Some((value, value_key)); + return Some(result); + } + } + } + } + } +} + +/// An iterator with the semantics of the chunk_by operation. +fn chunk_iter_by(iterator: I, signals: Signals, closure: F) -> Chunk +where + I: Iterator, + F: FnMut(&T) -> K, + K: PartialEq, +{ + Chunk { + closure, + iterator, + last_value: None, + done: false, + signals, + } +} + +pub fn chunk_by( + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, +) -> Result { + let head = call.head; + let closure: Closure = call.req(engine_state, stack, 0)?; + + let metadata = input.metadata(); + + match input { + PipelineData::Empty => Ok(PipelineData::Empty), + PipelineData::Value(Value::Range { .. }, ..) + | PipelineData::Value(Value::List { .. }, ..) + | PipelineData::ListStream(..) => { + let closure = ClosureEval::new(engine_state, stack, closure); + + let result = chunk_value_stream( + input.into_iter(), + closure, + head, + engine_state.signals().clone(), + ); + + Ok(result.into_pipeline_data(head, engine_state.signals().clone())) + } + + PipelineData::ByteStream(..) | PipelineData::Value(..) => { + Err(input.unsupported_input_error("list", head)) + } + } + .map(|data| data.set_metadata(metadata)) +} + +fn chunk_value_stream( + iterator: I, + mut closure: ClosureEval, + head: Span, + signals: Signals, +) -> impl Iterator + 'static + Send +where + I: Iterator + 'static + Send, +{ + chunk_iter_by(iterator, signals, move |value| { + match closure.run_with_value(value.clone()) { + Ok(data) => data.into_value(head).unwrap_or_else(|error| { + Value::error(chain_error_with_input(error, value.is_error(), head), head) + }), + + Err(error) => Value::error(chain_error_with_input(error, value.is_error(), head), head), + } + }) + .map(move |it| Value::list(it, head)) +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(ChunkBy {}) + } +} diff --git a/crates/nu-command/src/filters/drop/column.rs b/crates/nu-command/src/filters/drop/column.rs index f39e0e2276..b69a15b2d7 100644 --- a/crates/nu-command/src/filters/drop/column.rs +++ b/crates/nu-command/src/filters/drop/column.rs @@ -29,7 +29,7 @@ impl Command for DropColumn { } fn search_terms(&self) -> Vec<&str> { - vec!["delete"] + vec!["delete", "remove"] } fn run( diff --git a/crates/nu-command/src/filters/drop/drop_.rs b/crates/nu-command/src/filters/drop/drop_.rs index 67abea625d..d35e191a19 100644 --- a/crates/nu-command/src/filters/drop/drop_.rs +++ b/crates/nu-command/src/filters/drop/drop_.rs @@ -26,7 +26,7 @@ impl Command for Drop { } fn search_terms(&self) -> Vec<&str> { - vec!["delete"] + vec!["delete", "remove"] } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/filters/drop/nth.rs b/crates/nu-command/src/filters/drop/nth.rs index d2aed50332..282aa41c1d 100644 --- a/crates/nu-command/src/filters/drop/nth.rs +++ b/crates/nu-command/src/filters/drop/nth.rs @@ -32,7 +32,7 @@ impl Command for DropNth { } fn search_terms(&self) -> Vec<&str> { - vec!["delete"] + vec!["delete", "remove", "index"] } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/filters/filter.rs b/crates/nu-command/src/filters/filter.rs index 93fe5a6e6d..ea7960c4d3 100644 --- a/crates/nu-command/src/filters/filter.rs +++ b/crates/nu-command/src/filters/filter.rs @@ -30,7 +30,7 @@ a variable. On the other hand, the "row condition" syntax is not supported."# ]) .required( "closure", - SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Int])), + SyntaxShape::Closure(Some(vec![SyntaxShape::Any])), "Predicate closure.", ) .category(Category::Filters) diff --git a/crates/nu-command/src/filters/insert.rs b/crates/nu-command/src/filters/insert.rs index e6f9a7d8d7..0d3a234f58 100644 --- a/crates/nu-command/src/filters/insert.rs +++ b/crates/nu-command/src/filters/insert.rs @@ -129,6 +129,8 @@ fn insert( let replacement: Value = call.req(engine_state, stack, 1)?; match input { + // Propagate errors in the pipeline + PipelineData::Value(Value::Error { error, .. }, ..) => Err(*error), PipelineData::Value(mut value, metadata) => { if let Value::Closure { val, .. } = replacement { match (cell_path.members.first(), &mut value) { diff --git a/crates/nu-command/src/filters/length.rs b/crates/nu-command/src/filters/length.rs index 3d7b739ef2..b643c4dfea 100644 --- a/crates/nu-command/src/filters/length.rs +++ b/crates/nu-command/src/filters/length.rs @@ -19,6 +19,7 @@ impl Command for Length { .input_output_types(vec![ (Type::List(Box::new(Type::Any)), Type::Int), (Type::Binary, Type::Int), + (Type::Nothing, Type::Int), ]) .category(Category::Filters) } @@ -54,6 +55,11 @@ impl Command for Length { example: "0x[01 02] | length", result: Some(Value::test_int(2)), }, + Example { + description: "Count the length a null value", + example: "null | length", + result: Some(Value::test_int(0)), + }, ] } } @@ -61,23 +67,19 @@ impl Command for Length { fn length_row(call: &Call, input: PipelineData) -> Result { let span = input.span().unwrap_or(call.head); match input { - PipelineData::Value(Value::Nothing { .. }, ..) => { + PipelineData::Empty | PipelineData::Value(Value::Nothing { .. }, ..) => { Ok(Value::int(0, call.head).into_pipeline_data()) } - // I added this here because input_output_type() wasn't catching a record - // being sent in as input from echo. e.g. "echo {a:1 b:2} | length" - PipelineData::Value(Value::Record { .. }, ..) => { - Err(ShellError::OnlySupportsThisInputType { - exp_input_type: "list, and table".into(), - wrong_type: "record".into(), - dst_span: call.head, - src_span: span, - }) - } PipelineData::Value(Value::Binary { val, .. }, ..) => { Ok(Value::int(val.len() as i64, call.head).into_pipeline_data()) } - PipelineData::ByteStream(stream, _) if stream.type_().is_binary_coercible() => { + PipelineData::Value(Value::List { vals, .. }, ..) => { + Ok(Value::int(vals.len() as i64, call.head).into_pipeline_data()) + } + PipelineData::ListStream(stream, ..) => { + Ok(Value::int(stream.into_iter().count() as i64, call.head).into_pipeline_data()) + } + PipelineData::ByteStream(stream, ..) if stream.type_().is_binary_coercible() => { Ok(Value::int( match stream.reader() { Some(r) => r.bytes().count() as i64, @@ -87,17 +89,12 @@ fn length_row(call: &Call, input: PipelineData) -> Result { - let mut count: i64 = 0; - // Check for and propagate errors - for value in input.into_iter() { - if let Value::Error { error, .. } = value { - return Err(*error); - } - count += 1 - } - Ok(Value::int(count, call.head).into_pipeline_data()) - } + _ => Err(ShellError::OnlySupportsThisInputType { + exp_input_type: "list, table, binary, and nothing".into(), + wrong_type: input.get_type().to_string(), + dst_span: call.head, + src_span: span, + }), } } diff --git a/crates/nu-command/src/filters/merge.rs b/crates/nu-command/src/filters/merge.rs index e1d3095b20..588138fb42 100644 --- a/crates/nu-command/src/filters/merge.rs +++ b/crates/nu-command/src/filters/merge.rs @@ -120,6 +120,8 @@ repeating this process with row 1, and so on."# PipelineData::Value(Value::Record { val: inp, .. }, ..), Value::Record { val: to_merge, .. }, ) => Ok(Value::record(do_merge(inp, &to_merge), head).into_pipeline_data()), + // Propagate errors in the pipeline + (PipelineData::Value(Value::Error { error, .. }, ..), _) => Err(*error.clone()), (PipelineData::Value(val, ..), ..) => { // Only point the "value originates here" arrow at the merge value // if it was generated from a block. Otherwise, point at the pipeline value. -Leon 2022-10-27 diff --git a/crates/nu-command/src/filters/mod.rs b/crates/nu-command/src/filters/mod.rs index 8b56bf7a87..8eac0fc70b 100644 --- a/crates/nu-command/src/filters/mod.rs +++ b/crates/nu-command/src/filters/mod.rs @@ -1,6 +1,7 @@ mod all; mod any; mod append; +mod chunk_by; mod chunks; mod columns; mod compact; @@ -36,6 +37,7 @@ mod reject; mod rename; mod reverse; mod select; +#[cfg(feature = "rand")] mod shuffle; mod skip; mod sort; @@ -58,6 +60,7 @@ mod zip; pub use all::All; pub use any::Any; pub use append::Append; +pub use chunk_by::ChunkBy; pub use chunks::Chunks; pub use columns::Columns; pub use compact::Compact; @@ -93,6 +96,7 @@ pub use reject::Reject; pub use rename::Rename; pub use reverse::Reverse; pub use select::Select; +#[cfg(feature = "rand")] pub use shuffle::Shuffle; pub use skip::*; pub use sort::Sort; diff --git a/crates/nu-command/src/filters/par_each.rs b/crates/nu-command/src/filters/par_each.rs index fc0da7d3d3..4e4d526657 100644 --- a/crates/nu-command/src/filters/par_each.rs +++ b/crates/nu-command/src/filters/par_each.rs @@ -38,7 +38,7 @@ impl Command for ParEach { ) .required( "closure", - SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Int])), + SyntaxShape::Closure(Some(vec![SyntaxShape::Any])), "The closure to run.", ) .allow_variants_without_examples(true) diff --git a/crates/nu-command/src/filters/reduce.rs b/crates/nu-command/src/filters/reduce.rs index ed3c61ec32..9d17339d0e 100644 --- a/crates/nu-command/src/filters/reduce.rs +++ b/crates/nu-command/src/filters/reduce.rs @@ -24,11 +24,7 @@ impl Command for Reduce { ) .required( "closure", - SyntaxShape::Closure(Some(vec![ - SyntaxShape::Any, - SyntaxShape::Any, - SyntaxShape::Int, - ])), + SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Any])), "Reducing function.", ) .allow_variants_without_examples(true) @@ -88,6 +84,15 @@ impl Command for Reduce { "Concatenate a string with itself, using a range to determine the number of times.", result: Some(Value::test_string("StrStrStr")), }, + Example { + example: r#"[{a: 1} {b: 2} {c: 3}] | reduce {|it| merge $it}"#, + description: "Merge multiple records together, making use of the fact that the accumulated value is also supplied as pipeline input to the closure.", + result: Some(Value::test_record(record!( + "a" => Value::test_int(1), + "b" => Value::test_int(2), + "c" => Value::test_int(3), + ))), + } ] } @@ -135,8 +140,8 @@ mod test { #[test] fn test_examples() { - use crate::test_examples; + use crate::{test_examples_with_commands, Merge}; - test_examples(Reduce {}) + test_examples_with_commands(Reduce {}, &[&Merge]) } } diff --git a/crates/nu-command/src/filters/select.rs b/crates/nu-command/src/filters/select.rs index 71a2389c79..f630f19df8 100644 --- a/crates/nu-command/src/filters/select.rs +++ b/crates/nu-command/src/filters/select.rs @@ -206,7 +206,6 @@ fn select( let columns = new_columns; let input = if !unique_rows.is_empty() { - // let skip = call.has_flag(engine_state, stack, "skip")?; let metadata = input.metadata(); let pipeline_iter: PipelineIterator = input.into_iter(); @@ -231,37 +230,31 @@ fn select( Value::List { vals: input_vals, .. } => { - let mut output = vec![]; - let mut columns_with_value = Vec::new(); - for input_val in input_vals { - if !columns.is_empty() { - let mut record = Record::new(); - for path in &columns { - //FIXME: improve implementation to not clone - match input_val.clone().follow_cell_path(&path.members, false) { - Ok(fetcher) => { - record.push(path.to_column_name(), fetcher); - if !columns_with_value.contains(&path) { - columns_with_value.push(path); + Ok(input_vals + .into_iter() + .map(move |input_val| { + if !columns.is_empty() { + let mut record = Record::new(); + for path in &columns { + //FIXME: improve implementation to not clone + match input_val.clone().follow_cell_path(&path.members, false) { + Ok(fetcher) => { + record.push(path.to_column_name(), fetcher); } - } - Err(e) => { - return Err(e); + Err(e) => return Value::error(e, call_span), } } + + Value::record(record, span) + } else { + input_val.clone() } - - output.push(Value::record(record, span)) - } else { - output.push(input_val) - } - } - - Ok(output.into_iter().into_pipeline_data_with_metadata( - call_span, - engine_state.signals().clone(), - metadata, - )) + }) + .into_pipeline_data_with_metadata( + call_span, + engine_state.signals().clone(), + metadata, + )) } _ => { if !columns.is_empty() { @@ -286,31 +279,29 @@ fn select( } } PipelineData::ListStream(stream, metadata, ..) => { - let mut values = vec![]; - - for x in stream { - if !columns.is_empty() { - let mut record = Record::new(); - for path in &columns { - //FIXME: improve implementation to not clone - match x.clone().follow_cell_path(&path.members, false) { - Ok(value) => { - record.push(path.to_column_name(), value); + Ok(stream + .map(move |x| { + if !columns.is_empty() { + let mut record = Record::new(); + for path in &columns { + //FIXME: improve implementation to not clone + match x.clone().follow_cell_path(&path.members, false) { + Ok(value) => { + record.push(path.to_column_name(), value); + } + Err(e) => return Value::error(e, call_span), } - Err(e) => return Err(e), } + Value::record(record, call_span) + } else { + x } - values.push(Value::record(record, call_span)); - } else { - values.push(x); - } - } - - Ok(values.into_pipeline_data_with_metadata( - call_span, - engine_state.signals().clone(), - metadata, - )) + }) + .into_pipeline_data_with_metadata( + call_span, + engine_state.signals().clone(), + metadata, + )) } _ => Ok(PipelineData::empty()), } diff --git a/crates/nu-command/src/filters/skip/skip_until.rs b/crates/nu-command/src/filters/skip/skip_until.rs index b73c817e9b..6f9ada280c 100644 --- a/crates/nu-command/src/filters/skip/skip_until.rs +++ b/crates/nu-command/src/filters/skip/skip_until.rs @@ -20,7 +20,7 @@ impl Command for SkipUntil { ]) .required( "predicate", - SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Int])), + SyntaxShape::Closure(Some(vec![SyntaxShape::Any])), "The predicate that skipped element must not match.", ) .category(Category::Filters) diff --git a/crates/nu-command/src/filters/skip/skip_while.rs b/crates/nu-command/src/filters/skip/skip_while.rs index 9423915242..494cf91558 100644 --- a/crates/nu-command/src/filters/skip/skip_while.rs +++ b/crates/nu-command/src/filters/skip/skip_while.rs @@ -20,7 +20,7 @@ impl Command for SkipWhile { ]) .required( "predicate", - SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Int])), + SyntaxShape::Closure(Some(vec![SyntaxShape::Any])), "The predicate that skipped element must match.", ) .category(Category::Filters) diff --git a/crates/nu-command/src/filters/take/take_until.rs b/crates/nu-command/src/filters/take/take_until.rs index e1dcd6ceda..4e8f38a3d2 100644 --- a/crates/nu-command/src/filters/take/take_until.rs +++ b/crates/nu-command/src/filters/take/take_until.rs @@ -17,7 +17,7 @@ impl Command for TakeUntil { )]) .required( "predicate", - SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Int])), + SyntaxShape::Closure(Some(vec![SyntaxShape::Any])), "The predicate that element(s) must not match.", ) .category(Category::Filters) diff --git a/crates/nu-command/src/filters/take/take_while.rs b/crates/nu-command/src/filters/take/take_while.rs index 54e9384cba..c9e218f543 100644 --- a/crates/nu-command/src/filters/take/take_while.rs +++ b/crates/nu-command/src/filters/take/take_while.rs @@ -20,7 +20,7 @@ impl Command for TakeWhile { ]) .required( "predicate", - SyntaxShape::Closure(Some(vec![SyntaxShape::Any, SyntaxShape::Int])), + SyntaxShape::Closure(Some(vec![SyntaxShape::Any])), "The predicate that element(s) must match.", ) .category(Category::Filters) diff --git a/crates/nu-command/src/filters/tee.rs b/crates/nu-command/src/filters/tee.rs index a6f0027016..e643939894 100644 --- a/crates/nu-command/src/filters/tee.rs +++ b/crates/nu-command/src/filters/tee.rs @@ -1,7 +1,9 @@ use nu_engine::{command_prelude::*, get_eval_block_with_early_return}; +#[cfg(feature = "os")] +use nu_protocol::process::ChildPipe; use nu_protocol::{ - byte_stream::copy_with_signals, engine::Closure, process::ChildPipe, report_shell_error, - ByteStream, ByteStreamSource, OutDest, PipelineMetadata, Signals, + byte_stream::copy_with_signals, engine::Closure, report_shell_error, ByteStream, + ByteStreamSource, OutDest, PipelineMetadata, Signals, }; use std::{ io::{self, Read, Write}, @@ -152,6 +154,7 @@ use it in your pipeline."# metadata, )) } + #[cfg(feature = "os")] ByteStreamSource::Child(mut child) => { let stderr_thread = if use_stderr { let stderr_thread = if let Some(stderr) = child.stderr.take() { @@ -454,6 +457,7 @@ fn copy(src: impl Read, dest: impl Write, info: &StreamInfo) -> Result<(), Shell Ok(()) } +#[cfg(feature = "os")] fn copy_pipe(pipe: ChildPipe, dest: impl Write, info: &StreamInfo) -> Result<(), ShellError> { match pipe { ChildPipe::Pipe(pipe) => copy(pipe, dest, info), @@ -477,6 +481,7 @@ fn copy_on_thread( .map_err(|e| e.into_spanned(span).into()) } +#[cfg(feature = "os")] fn copy_pipe_on_thread( pipe: ChildPipe, dest: impl Write + Send + 'static, diff --git a/crates/nu-command/src/formats/from/csv.rs b/crates/nu-command/src/formats/from/csv.rs index bde84c2c73..74b9bcd33f 100644 --- a/crates/nu-command/src/formats/from/csv.rs +++ b/crates/nu-command/src/formats/from/csv.rs @@ -204,12 +204,45 @@ fn from_csv( #[cfg(test)] mod test { + use nu_cmd_lang::eval_pipeline_without_terminal_expression; + use super::*; + use crate::{Metadata, MetadataSet}; + #[test] fn test_examples() { use crate::test_examples; test_examples(FromCsv {}) } + + #[test] + fn test_content_type_metadata() { + let mut engine_state = Box::new(EngineState::new()); + let delta = { + let mut working_set = StateWorkingSet::new(&engine_state); + + working_set.add_decl(Box::new(FromCsv {})); + working_set.add_decl(Box::new(Metadata {})); + working_set.add_decl(Box::new(MetadataSet {})); + + working_set.render() + }; + + engine_state + .merge_delta(delta) + .expect("Error merging delta"); + + let cmd = r#""a,b\n1,2" | metadata set --content-type 'text/csv' --datasource-ls | from csv | metadata | $in"#; + let result = eval_pipeline_without_terminal_expression( + cmd, + std::env::temp_dir().as_ref(), + &mut engine_state, + ); + assert_eq!( + Value::test_record(record!("source" => Value::test_string("ls"))), + result.expect("There should be a result") + ) + } } diff --git a/crates/nu-command/src/formats/from/delimited.rs b/crates/nu-command/src/formats/from/delimited.rs index 5dfdd4ad82..865bc79a41 100644 --- a/crates/nu-command/src/formats/from/delimited.rs +++ b/crates/nu-command/src/formats/from/delimited.rs @@ -93,9 +93,10 @@ pub(super) fn from_delimited_data( input: PipelineData, name: Span, ) -> Result { + let metadata = input.metadata().map(|md| md.with_content_type(None)); match input { PipelineData::Empty => Ok(PipelineData::Empty), - PipelineData::Value(value, metadata) => { + PipelineData::Value(value, ..) => { let string = value.into_string()?; let byte_stream = ByteStream::read_string(string, name, Signals::empty()); Ok(PipelineData::ListStream( @@ -109,7 +110,7 @@ pub(super) fn from_delimited_data( dst_span: name, src_span: list_stream.span(), }), - PipelineData::ByteStream(byte_stream, metadata) => Ok(PipelineData::ListStream( + PipelineData::ByteStream(byte_stream, ..) => Ok(PipelineData::ListStream( from_delimited_stream(config, byte_stream, name)?, metadata, )), diff --git a/crates/nu-command/src/formats/from/json.rs b/crates/nu-command/src/formats/from/json.rs index e3de2cbafd..36a05ea4e1 100644 --- a/crates/nu-command/src/formats/from/json.rs +++ b/crates/nu-command/src/formats/from/json.rs @@ -70,23 +70,22 @@ impl Command for FromJson { let span = call.head; let strict = call.has_flag(engine_state, stack, "strict")?; + let metadata = input.metadata().map(|md| md.with_content_type(None)); // TODO: turn this into a structured underline of the nu_json error if call.has_flag(engine_state, stack, "objects")? { // Return a stream of JSON values, one for each non-empty line match input { - PipelineData::Value(Value::String { val, .. }, metadata) => { - Ok(PipelineData::ListStream( - read_json_lines( - Cursor::new(val), - span, - strict, - engine_state.signals().clone(), - ), - metadata, - )) - } - PipelineData::ByteStream(stream, metadata) + PipelineData::Value(Value::String { val, .. }, ..) => Ok(PipelineData::ListStream( + read_json_lines( + Cursor::new(val), + span, + strict, + engine_state.signals().clone(), + ), + metadata, + )), + PipelineData::ByteStream(stream, ..) if stream.type_() != ByteStreamType::Binary => { if let Some(reader) = stream.reader() { @@ -107,7 +106,7 @@ impl Command for FromJson { } } else { // Return a single JSON value - let (string_input, span, metadata) = input.collect_string_strict(span)?; + let (string_input, span, ..) = input.collect_string_strict(span)?; if string_input.is_empty() { return Ok(Value::nothing(span).into_pipeline_data()); @@ -267,6 +266,10 @@ fn convert_string_to_value_strict(string_input: &str, span: Span) -> Result Value::test_string("ls"))), + result.expect("There should be a result") + ) + } } diff --git a/crates/nu-command/src/formats/from/msgpack.rs b/crates/nu-command/src/formats/from/msgpack.rs index a3538ea69f..4fe849b8fe 100644 --- a/crates/nu-command/src/formats/from/msgpack.rs +++ b/crates/nu-command/src/formats/from/msgpack.rs @@ -113,7 +113,8 @@ MessagePack: https://msgpack.org/ objects, signals: engine_state.signals().clone(), }; - match input { + let metadata = input.metadata().map(|md| md.with_content_type(None)); + let out = match input { // Deserialize from a byte buffer PipelineData::Value(Value::Binary { val: bytes, .. }, _) => { read_msgpack(Cursor::new(bytes), opts) @@ -136,7 +137,8 @@ MessagePack: https://msgpack.org/ dst_span: call.head, src_span: input.span().unwrap_or(call.head), }), - } + }; + out.map(|pd| pd.set_metadata(metadata)) } } @@ -510,6 +512,10 @@ fn assert_eof(input: &mut impl io::Read, span: Span) -> Result<(), ShellError> { #[cfg(test)] mod test { + use nu_cmd_lang::eval_pipeline_without_terminal_expression; + + use crate::{Metadata, MetadataSet, ToMsgpack}; + use super::*; #[test] @@ -518,4 +524,34 @@ mod test { test_examples(FromMsgpack {}) } + + #[test] + fn test_content_type_metadata() { + let mut engine_state = Box::new(EngineState::new()); + let delta = { + let mut working_set = StateWorkingSet::new(&engine_state); + + working_set.add_decl(Box::new(ToMsgpack {})); + working_set.add_decl(Box::new(FromMsgpack {})); + working_set.add_decl(Box::new(Metadata {})); + working_set.add_decl(Box::new(MetadataSet {})); + + working_set.render() + }; + + engine_state + .merge_delta(delta) + .expect("Error merging delta"); + + let cmd = r#"{a: 1 b: 2} | to msgpack | metadata set --datasource-ls | from msgpack | metadata | $in"#; + let result = eval_pipeline_without_terminal_expression( + cmd, + std::env::temp_dir().as_ref(), + &mut engine_state, + ); + assert_eq!( + Value::test_record(record!("source" => Value::test_string("ls"))), + result.expect("There should be a result") + ) + } } diff --git a/crates/nu-command/src/formats/from/msgpackz.rs b/crates/nu-command/src/formats/from/msgpackz.rs index 81cc901614..4f31f74ec6 100644 --- a/crates/nu-command/src/formats/from/msgpackz.rs +++ b/crates/nu-command/src/formats/from/msgpackz.rs @@ -43,7 +43,8 @@ impl Command for FromMsgpackz { objects, signals: engine_state.signals().clone(), }; - match input { + let metadata = input.metadata().map(|md| md.with_content_type(None)); + let out = match input { // Deserialize from a byte buffer PipelineData::Value(Value::Binary { val: bytes, .. }, _) => { let reader = brotli::Decompressor::new(Cursor::new(bytes), BUFFER_SIZE); @@ -68,6 +69,7 @@ impl Command for FromMsgpackz { dst_span: call.head, src_span: span, }), - } + }; + out.map(|pd| pd.set_metadata(metadata)) } } diff --git a/crates/nu-command/src/formats/from/nuon.rs b/crates/nu-command/src/formats/from/nuon.rs index 107ee9c3b4..7cb45c5721 100644 --- a/crates/nu-command/src/formats/from/nuon.rs +++ b/crates/nu-command/src/formats/from/nuon.rs @@ -49,7 +49,8 @@ impl Command for FromNuon { let (string_input, _span, metadata) = input.collect_string_strict(head)?; match nuon::from_nuon(&string_input, Some(head)) { - Ok(result) => Ok(result.into_pipeline_data_with_metadata(metadata)), + Ok(result) => Ok(result + .into_pipeline_data_with_metadata(metadata.map(|md| md.with_content_type(None)))), Err(err) => Err(ShellError::GenericError { error: "error when loading nuon text".into(), msg: "could not load nuon text".into(), @@ -63,6 +64,10 @@ impl Command for FromNuon { #[cfg(test)] mod test { + use nu_cmd_lang::eval_pipeline_without_terminal_expression; + + use crate::{Metadata, MetadataSet}; + use super::*; #[test] @@ -71,4 +76,33 @@ mod test { test_examples(FromNuon {}) } + + #[test] + fn test_content_type_metadata() { + let mut engine_state = Box::new(EngineState::new()); + let delta = { + let mut working_set = StateWorkingSet::new(&engine_state); + + working_set.add_decl(Box::new(FromNuon {})); + working_set.add_decl(Box::new(Metadata {})); + working_set.add_decl(Box::new(MetadataSet {})); + + working_set.render() + }; + + engine_state + .merge_delta(delta) + .expect("Error merging delta"); + + let cmd = r#"'[[a, b]; [1, 2]]' | metadata set --content-type 'application/x-nuon' --datasource-ls | from nuon | metadata | $in"#; + let result = eval_pipeline_without_terminal_expression( + cmd, + std::env::temp_dir().as_ref(), + &mut engine_state, + ); + assert_eq!( + Value::test_record(record!("source" => Value::test_string("ls"))), + result.expect("There should be a result") + ) + } } diff --git a/crates/nu-command/src/formats/from/ods.rs b/crates/nu-command/src/formats/from/ods.rs index c6f08f4481..f1f4252f1e 100644 --- a/crates/nu-command/src/formats/from/ods.rs +++ b/crates/nu-command/src/formats/from/ods.rs @@ -46,7 +46,8 @@ impl Command for FromOds { vec![] }; - from_ods(input, head, sel_sheets) + let metadata = input.metadata().map(|md| md.with_content_type(None)); + from_ods(input, head, sel_sheets).map(|pd| pd.set_metadata(metadata)) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/formats/from/toml.rs b/crates/nu-command/src/formats/from/toml.rs index a61ced65a0..8f4242e5db 100644 --- a/crates/nu-command/src/formats/from/toml.rs +++ b/crates/nu-command/src/formats/from/toml.rs @@ -29,7 +29,8 @@ impl Command for FromToml { let span = call.head; let (mut string_input, span, metadata) = input.collect_string_strict(span)?; string_input.push('\n'); - Ok(convert_string_to_value(string_input, span)?.into_pipeline_data_with_metadata(metadata)) + Ok(convert_string_to_value(string_input, span)? + .into_pipeline_data_with_metadata(metadata.map(|md| md.with_content_type(None)))) } fn examples(&self) -> Vec { @@ -144,8 +145,11 @@ pub fn convert_string_to_value(string_input: String, span: Span) -> Result Value::test_string("ls"))), + result.expect("There should be a result") + ) + } } diff --git a/crates/nu-command/src/formats/from/tsv.rs b/crates/nu-command/src/formats/from/tsv.rs index 09bee4803f..2d77342307 100644 --- a/crates/nu-command/src/formats/from/tsv.rs +++ b/crates/nu-command/src/formats/from/tsv.rs @@ -165,6 +165,10 @@ fn from_tsv( #[cfg(test)] mod test { + use nu_cmd_lang::eval_pipeline_without_terminal_expression; + + use crate::{Metadata, MetadataSet}; + use super::*; #[test] @@ -173,4 +177,33 @@ mod test { test_examples(FromTsv {}) } + + #[test] + fn test_content_type_metadata() { + let mut engine_state = Box::new(EngineState::new()); + let delta = { + let mut working_set = StateWorkingSet::new(&engine_state); + + working_set.add_decl(Box::new(FromTsv {})); + working_set.add_decl(Box::new(Metadata {})); + working_set.add_decl(Box::new(MetadataSet {})); + + working_set.render() + }; + + engine_state + .merge_delta(delta) + .expect("Error merging delta"); + + let cmd = r#""a\tb\n1\t2" | metadata set --content-type 'text/tab-separated-values' --datasource-ls | from tsv | metadata | $in"#; + let result = eval_pipeline_without_terminal_expression( + cmd, + std::env::temp_dir().as_ref(), + &mut engine_state, + ); + assert_eq!( + Value::test_record(record!("source" => Value::test_string("ls"))), + result.expect("There should be a result") + ) + } } diff --git a/crates/nu-command/src/formats/from/xlsx.rs b/crates/nu-command/src/formats/from/xlsx.rs index 1e02cf432c..bc486d1f18 100644 --- a/crates/nu-command/src/formats/from/xlsx.rs +++ b/crates/nu-command/src/formats/from/xlsx.rs @@ -47,7 +47,8 @@ impl Command for FromXlsx { vec![] }; - from_xlsx(input, head, sel_sheets) + let metadata = input.metadata().map(|md| md.with_content_type(None)); + from_xlsx(input, head, sel_sheets).map(|pd| pd.set_metadata(metadata)) } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/formats/from/xml.rs b/crates/nu-command/src/formats/from/xml.rs index 4c0675a109..70c1048972 100644 --- a/crates/nu-command/src/formats/from/xml.rs +++ b/crates/nu-command/src/formats/from/xml.rs @@ -206,7 +206,9 @@ fn from_xml(input: PipelineData, info: &ParsingInfo) -> Result Ok(x.into_pipeline_data_with_metadata(metadata)), + Ok(x) => { + Ok(x.into_pipeline_data_with_metadata(metadata.map(|md| md.with_content_type(None)))) + } Err(err) => Err(process_xml_parse_error(err, span)), } } @@ -322,10 +324,14 @@ fn make_cant_convert_error(help: impl Into, span: Span) -> ShellError { #[cfg(test)] mod tests { + use crate::Metadata; + use crate::MetadataSet; + use super::*; use indexmap::indexmap; use indexmap::IndexMap; + use nu_cmd_lang::eval_pipeline_without_terminal_expression; fn string(input: impl Into) -> Value { Value::test_string(input) @@ -480,4 +486,36 @@ mod tests { test_examples(FromXml {}) } + + #[test] + fn test_content_type_metadata() { + let mut engine_state = Box::new(EngineState::new()); + let delta = { + let mut working_set = StateWorkingSet::new(&engine_state); + + working_set.add_decl(Box::new(FromXml {})); + working_set.add_decl(Box::new(Metadata {})); + working_set.add_decl(Box::new(MetadataSet {})); + + working_set.render() + }; + + engine_state + .merge_delta(delta) + .expect("Error merging delta"); + + let cmd = r#"' + + Event +' | metadata set --content-type 'application/xml' --datasource-ls | from xml | metadata | $in"#; + let result = eval_pipeline_without_terminal_expression( + cmd, + std::env::temp_dir().as_ref(), + &mut engine_state, + ); + assert_eq!( + Value::test_record(record!("source" => Value::test_string("ls"))), + result.expect("There should be a result") + ) + } } diff --git a/crates/nu-command/src/formats/from/yaml.rs b/crates/nu-command/src/formats/from/yaml.rs index 5c463cc2a9..649f7e83ac 100644 --- a/crates/nu-command/src/formats/from/yaml.rs +++ b/crates/nu-command/src/formats/from/yaml.rs @@ -235,14 +235,19 @@ fn from_yaml(input: PipelineData, head: Span) -> Result Ok(x.into_pipeline_data_with_metadata(metadata)), + Ok(x) => { + Ok(x.into_pipeline_data_with_metadata(metadata.map(|md| md.with_content_type(None)))) + } Err(other) => Err(other), } } #[cfg(test)] mod test { + use crate::{Metadata, MetadataSet}; + use super::*; + use nu_cmd_lang::eval_pipeline_without_terminal_expression; use nu_protocol::Config; #[test] @@ -395,4 +400,33 @@ mod test { assert!(result.ok().unwrap() == test_case.expected.ok().unwrap()); } } + + #[test] + fn test_content_type_metadata() { + let mut engine_state = Box::new(EngineState::new()); + let delta = { + let mut working_set = StateWorkingSet::new(&engine_state); + + working_set.add_decl(Box::new(FromYaml {})); + working_set.add_decl(Box::new(Metadata {})); + working_set.add_decl(Box::new(MetadataSet {})); + + working_set.render() + }; + + engine_state + .merge_delta(delta) + .expect("Error merging delta"); + + let cmd = r#""a: 1\nb: 2" | metadata set --content-type 'application/yaml' --datasource-ls | from yaml | metadata | $in"#; + let result = eval_pipeline_without_terminal_expression( + cmd, + std::env::temp_dir().as_ref(), + &mut engine_state, + ); + assert_eq!( + Value::test_record(record!("source" => Value::test_string("ls"))), + result.expect("There should be a result") + ) + } } diff --git a/crates/nu-command/src/formats/to/json.rs b/crates/nu-command/src/formats/to/json.rs index 9e68961092..27c0af856a 100644 --- a/crates/nu-command/src/formats/to/json.rs +++ b/crates/nu-command/src/formats/to/json.rs @@ -109,7 +109,7 @@ pub fn value_to_json_value(v: &Value) -> Result { let span = v.span(); Ok(match v { Value::Bool { val, .. } => nu_json::Value::Bool(*val), - Value::Filesize { val, .. } => nu_json::Value::I64(*val), + Value::Filesize { val, .. } => nu_json::Value::I64(val.get()), Value::Duration { val, .. } => nu_json::Value::I64(*val), Value::Date { val, .. } => nu_json::Value::String(val.to_string()), Value::Float { val, .. } => nu_json::Value::F64(*val), diff --git a/crates/nu-command/src/formats/to/msgpack.rs b/crates/nu-command/src/formats/to/msgpack.rs index 537aa9efdb..f7c4c7df3d 100644 --- a/crates/nu-command/src/formats/to/msgpack.rs +++ b/crates/nu-command/src/formats/to/msgpack.rs @@ -168,7 +168,7 @@ pub(crate) fn write_value( mp::write_f64(out, *val).err_span(span)?; } Value::Filesize { val, .. } => { - mp::write_sint(out, *val).err_span(span)?; + mp::write_sint(out, val.get()).err_span(span)?; } Value::Duration { val, .. } => { mp::write_sint(out, *val).err_span(span)?; diff --git a/crates/nu-command/src/formats/to/toml.rs b/crates/nu-command/src/formats/to/toml.rs index 9e5728ef1e..5f28284904 100644 --- a/crates/nu-command/src/formats/to/toml.rs +++ b/crates/nu-command/src/formats/to/toml.rs @@ -47,7 +47,7 @@ fn helper(engine_state: &EngineState, v: &Value) -> Result toml::Value::Boolean(*val), Value::Int { val, .. } => toml::Value::Integer(*val), - Value::Filesize { val, .. } => toml::Value::Integer(*val), + Value::Filesize { val, .. } => toml::Value::Integer(val.get()), Value::Duration { val, .. } => toml::Value::String(val.to_string()), Value::Date { val, .. } => toml::Value::Datetime(to_toml_datetime(val)), Value::Range { .. } => toml::Value::String("".to_string()), diff --git a/crates/nu-command/src/formats/to/yaml.rs b/crates/nu-command/src/formats/to/yaml.rs index 73c743a056..4fc4e62319 100644 --- a/crates/nu-command/src/formats/to/yaml.rs +++ b/crates/nu-command/src/formats/to/yaml.rs @@ -44,7 +44,9 @@ pub fn value_to_yaml_value(v: &Value) -> Result { Ok(match &v { Value::Bool { val, .. } => serde_yaml::Value::Bool(*val), Value::Int { val, .. } => serde_yaml::Value::Number(serde_yaml::Number::from(*val)), - Value::Filesize { val, .. } => serde_yaml::Value::Number(serde_yaml::Number::from(*val)), + Value::Filesize { val, .. } => { + serde_yaml::Value::Number(serde_yaml::Number::from(val.get())) + } Value::Duration { val, .. } => serde_yaml::Value::String(val.to_string()), Value::Date { val, .. } => serde_yaml::Value::String(val.to_string()), Value::Range { .. } => serde_yaml::Value::Null, diff --git a/crates/nu-command/src/help/help_commands.rs b/crates/nu-command/src/help/help_commands.rs index f0f8df9c02..9d3ab9c924 100644 --- a/crates/nu-command/src/help/help_commands.rs +++ b/crates/nu-command/src/help/help_commands.rs @@ -87,21 +87,10 @@ pub fn help_commands( name.push_str(&r.item); } - let output = engine_state - .get_decls_sorted(false) - .into_iter() - .filter_map(|(_, decl_id)| { - let decl = engine_state.get_decl(decl_id); - (decl.name() == name).then_some(decl) - }) - .map(|cmd| get_full_help(cmd, engine_state, stack)) - .collect::>(); - - if !output.is_empty() { - Ok( - Value::string(output.join("======================\n\n"), call.head) - .into_pipeline_data(), - ) + if let Some(decl) = engine_state.find_decl(name.as_bytes(), &[]) { + let cmd = engine_state.get_decl(decl); + let help_text = get_full_help(cmd, engine_state, stack); + Ok(Value::string(help_text, call.head).into_pipeline_data()) } else { Err(ShellError::CommandNotFound { span: Span::merge_many(rest.iter().map(|s| s.span)), diff --git a/crates/nu-command/src/help/help_externs.rs b/crates/nu-command/src/help/help_externs.rs index af07835af4..27775b416e 100644 --- a/crates/nu-command/src/help/help_externs.rs +++ b/crates/nu-command/src/help/help_externs.rs @@ -107,21 +107,10 @@ pub fn help_externs( name.push_str(&r.item); } - let output = engine_state - .get_decls_sorted(false) - .into_iter() - .filter_map(|(_, decl_id)| { - let decl = engine_state.get_decl(decl_id); - (decl.name() == name).then_some(decl) - }) - .map(|cmd| get_full_help(cmd, engine_state, stack)) - .collect::>(); - - if !output.is_empty() { - Ok( - Value::string(output.join("======================\n\n"), call.head) - .into_pipeline_data(), - ) + if let Some(decl) = engine_state.find_decl(name.as_bytes(), &[]) { + let cmd = engine_state.get_decl(decl); + let help_text = get_full_help(cmd, engine_state, stack); + Ok(Value::string(help_text, call.head).into_pipeline_data()) } else { Err(ShellError::CommandNotFound { span: Span::merge_many(rest.iter().map(|s| s.span)), diff --git a/crates/nu-command/src/help/help_operators.rs b/crates/nu-command/src/help/help_operators.rs index 28be6a68ed..28bb223e61 100644 --- a/crates/nu-command/src/help/help_operators.rs +++ b/crates/nu-command/src/help/help_operators.rs @@ -31,7 +31,7 @@ impl Command for HelpOperators { let mut operators = [ Operator::Assignment(Assignment::Assign), Operator::Assignment(Assignment::PlusAssign), - Operator::Assignment(Assignment::AppendAssign), + Operator::Assignment(Assignment::ConcatAssign), Operator::Assignment(Assignment::MinusAssign), Operator::Assignment(Assignment::MultiplyAssign), Operator::Assignment(Assignment::DivideAssign), @@ -48,7 +48,7 @@ impl Command for HelpOperators { Operator::Comparison(Comparison::StartsWith), Operator::Comparison(Comparison::EndsWith), Operator::Math(Math::Plus), - Operator::Math(Math::Append), + Operator::Math(Math::Concat), Operator::Math(Math::Minus), Operator::Math(Math::Multiply), Operator::Math(Math::Divide), @@ -144,8 +144,8 @@ fn description(operator: &Operator) -> &'static str { Operator::Comparison(Comparison::StartsWith) => "Checks if a string starts with another.", Operator::Comparison(Comparison::EndsWith) => "Checks if a string ends with another.", Operator::Math(Math::Plus) => "Adds two values.", - Operator::Math(Math::Append) => { - "Appends two lists, a list and a value, two strings, or two binary values." + Operator::Math(Math::Concat) => { + "Concatenates two lists, two strings, or two binary values." } Operator::Math(Math::Minus) => "Subtracts two values.", Operator::Math(Math::Multiply) => "Multiplies two values.", @@ -163,8 +163,8 @@ fn description(operator: &Operator) -> &'static str { Operator::Bits(Bits::ShiftRight) => "Bitwise shifts a value right by another.", Operator::Assignment(Assignment::Assign) => "Assigns a value to a variable.", Operator::Assignment(Assignment::PlusAssign) => "Adds a value to a variable.", - Operator::Assignment(Assignment::AppendAssign) => { - "Appends a list, a value, a string, or a binary value to a variable." + Operator::Assignment(Assignment::ConcatAssign) => { + "Concatenates two lists, two strings, or two binary values." } Operator::Assignment(Assignment::MinusAssign) => "Subtracts a value from a variable.", Operator::Assignment(Assignment::MultiplyAssign) => "Multiplies a variable by a value.", diff --git a/crates/nu-command/src/lib.rs b/crates/nu-command/src/lib.rs index 55c4974325..e3b1a7f538 100644 --- a/crates/nu-command/src/lib.rs +++ b/crates/nu-command/src/lib.rs @@ -1,3 +1,4 @@ +#![cfg_attr(not(feature = "os"), allow(unused))] #![doc = include_str!("../README.md")] mod bytes; mod charting; @@ -8,6 +9,7 @@ mod default_context; mod env; mod example_test; mod experimental; +#[cfg(feature = "os")] mod filesystem; mod filters; mod formats; @@ -18,8 +20,10 @@ mod math; mod misc; mod network; mod path; +#[cfg(feature = "os")] mod platform; mod progress_bar; +#[cfg(feature = "rand")] mod random; mod removed; mod shells; @@ -27,6 +31,7 @@ mod sort_utils; #[cfg(feature = "sqlite")] mod stor; mod strings; +#[cfg(feature = "os")] mod system; mod viewers; @@ -40,6 +45,7 @@ pub use env::*; #[cfg(test)] pub use example_test::{test_examples, test_examples_with_commands}; pub use experimental::*; +#[cfg(feature = "os")] pub use filesystem::*; pub use filters::*; pub use formats::*; @@ -50,7 +56,9 @@ pub use math::*; pub use misc::*; pub use network::*; pub use path::*; +#[cfg(feature = "os")] pub use platform::*; +#[cfg(feature = "rand")] pub use random::*; pub use removed::*; pub use shells::*; @@ -58,6 +66,7 @@ pub use sort_utils::*; #[cfg(feature = "sqlite")] pub use stor::*; pub use strings::*; +#[cfg(feature = "os")] pub use system::*; pub use viewers::*; diff --git a/crates/nu-command/src/math/avg.rs b/crates/nu-command/src/math/avg.rs index 07e2ed00bf..a7f6d3088a 100644 --- a/crates/nu-command/src/math/avg.rs +++ b/crates/nu-command/src/math/avg.rs @@ -90,7 +90,7 @@ pub fn average(values: &[Value], span: Span, head: Span) -> Result Ok(Value::filesize(val / values.len() as i64, span)), + Value::Filesize { val, .. } => Ok(Value::filesize(val.get() / values.len() as i64, span)), Value::Duration { val, .. } => Ok(Value::duration(val / values.len() as i64, span)), _ => total.div(head, &Value::int(values.len() as i64, head), head), } diff --git a/crates/nu-command/src/math/mode.rs b/crates/nu-command/src/math/mode.rs index b89896ca88..cf4ae36b2d 100644 --- a/crates/nu-command/src/math/mode.rs +++ b/crates/nu-command/src/math/mode.rs @@ -142,9 +142,10 @@ pub fn mode(values: &[Value], _span: Span, head: Span) -> Result { Ok(HashableType::new(val.to_ne_bytes(), NumberTypes::Float)) } - Value::Filesize { val, .. } => { - Ok(HashableType::new(val.to_ne_bytes(), NumberTypes::Filesize)) - } + Value::Filesize { val, .. } => Ok(HashableType::new( + val.get().to_ne_bytes(), + NumberTypes::Filesize, + )), Value::Error { error, .. } => Err(*error.clone()), other => Err(ShellError::UnsupportedInput { msg: "Unable to give a result with this input".to_string(), diff --git a/crates/nu-command/src/misc/source.rs b/crates/nu-command/src/misc/source.rs index 01f971a103..a12a57ebef 100644 --- a/crates/nu-command/src/misc/source.rs +++ b/crates/nu-command/src/misc/source.rs @@ -1,4 +1,5 @@ use nu_engine::{command_prelude::*, get_eval_block_with_early_return}; +use nu_path::canonicalize_with; use nu_protocol::{engine::CommandType, BlockId}; /// Source a file for environment variables. @@ -41,15 +42,57 @@ impl Command for Source { call: &Call, input: PipelineData, ) -> Result { - // Note: this hidden positional is the block_id that corresponded to the 0th position - // it is put here by the parser + // Note: two hidden positionals are used here that are injected by the parser: + // 1. The block_id that corresponded to the 0th position + // 2. The block_id_name that corresponded to the file name at the 0th position let block_id: i64 = call.req_parser_info(engine_state, stack, "block_id")?; + let block_id_name: String = call.req_parser_info(engine_state, stack, "block_id_name")?; let block_id = BlockId::new(block_id as usize); let block = engine_state.get_block(block_id).clone(); + let cwd = engine_state.cwd_as_string(Some(stack))?; + let pb = std::path::PathBuf::from(block_id_name); + let parent = pb.parent().unwrap_or(std::path::Path::new("")); + let file_path = + canonicalize_with(pb.as_path(), cwd).map_err(|err| ShellError::FileNotFoundCustom { + msg: format!("Could not access file '{}': {err}", pb.as_path().display()), + span: Span::unknown(), + })?; + + // Note: We intentionally left out PROCESS_PATH since it's supposed to + // to work like argv[0] in C, which is the name of the program being executed. + // Since we're not executing a program, we don't need to set it. + + // Save the old env vars so we can restore them after the script has ran + let old_file_pwd = stack.get_env_var(engine_state, "FILE_PWD").cloned(); + let old_current_file = stack.get_env_var(engine_state, "CURRENT_FILE").cloned(); + + // Add env vars so they are available to the script + stack.add_env_var( + "FILE_PWD".to_string(), + Value::string(parent.to_string_lossy(), Span::unknown()), + ); + stack.add_env_var( + "CURRENT_FILE".to_string(), + Value::string(file_path.to_string_lossy(), Span::unknown()), + ); let eval_block_with_early_return = get_eval_block_with_early_return(engine_state); + let return_result = eval_block_with_early_return(engine_state, stack, &block, input); - eval_block_with_early_return(engine_state, stack, &block, input) + // After the script has ran, restore the old values unless they didn't exist. + // If they didn't exist prior, remove the env vars + if let Some(old_file_pwd) = old_file_pwd { + stack.add_env_var("FILE_PWD".to_string(), old_file_pwd.clone()); + } else { + stack.remove_env_var(engine_state, "FILE_PWD"); + } + if let Some(old_current_file) = old_current_file { + stack.add_env_var("CURRENT_FILE".to_string(), old_current_file.clone()); + } else { + stack.remove_env_var(engine_state, "CURRENT_FILE"); + } + + return_result } fn examples(&self) -> Vec { diff --git a/crates/nu-command/src/network/http/client.rs b/crates/nu-command/src/network/http/client.rs index 62dd693627..504cc7b632 100644 --- a/crates/nu-command/src/network/http/client.rs +++ b/crates/nu-command/src/network/http/client.rs @@ -376,7 +376,7 @@ fn send_multipart_request( format!("Content-Length: {}", val.len()), ]; builder - .add(&mut Cursor::new(val), &headers.join("\n")) + .add(&mut Cursor::new(val), &headers.join("\r\n")) .map_err(err)?; } else { let headers = format!(r#"Content-Disposition: form-data; name="{}""#, col); diff --git a/crates/nu-command/src/network/mod.rs b/crates/nu-command/src/network/mod.rs index 5ca523a1d3..ea46fd3dc8 100644 --- a/crates/nu-command/src/network/mod.rs +++ b/crates/nu-command/src/network/mod.rs @@ -1,8 +1,12 @@ +#[cfg(feature = "network")] mod http; +#[cfg(feature = "network")] mod port; mod url; +#[cfg(feature = "network")] pub use self::http::*; pub use self::url::*; +#[cfg(feature = "network")] pub use port::SubCommand as Port; diff --git a/crates/nu-command/src/path/mod.rs b/crates/nu-command/src/path/mod.rs index 8a7b9a6f2c..4b6405c8d8 100644 --- a/crates/nu-command/src/path/mod.rs +++ b/crates/nu-command/src/path/mod.rs @@ -6,6 +6,7 @@ mod join; mod parse; pub mod path_; mod relative_to; +mod self_; mod split; mod r#type; @@ -18,6 +19,7 @@ pub use parse::SubCommand as PathParse; pub use path_::PathCommand as Path; pub use r#type::SubCommand as PathType; pub use relative_to::SubCommand as PathRelativeTo; +pub use self_::SubCommand as PathSelf; pub use split::SubCommand as PathSplit; use nu_protocol::{ShellError, Span, Value}; diff --git a/crates/nu-command/src/path/self_.rs b/crates/nu-command/src/path/self_.rs new file mode 100644 index 0000000000..242fbea7a8 --- /dev/null +++ b/crates/nu-command/src/path/self_.rs @@ -0,0 +1,129 @@ +use nu_engine::command_prelude::*; +use nu_path::expand_path_with; +use nu_protocol::engine::StateWorkingSet; + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "path self" + } + + fn signature(&self) -> Signature { + Signature::build("path self") + .input_output_type(Type::Nothing, Type::String) + .allow_variants_without_examples(true) + .optional( + "path", + SyntaxShape::Filepath, + "Path to get instead of the current file.", + ) + .category(Category::Path) + } + + fn description(&self) -> &str { + "Get the absolute path of the script or module containing this command at parse time." + } + + fn is_const(&self) -> bool { + true + } + + fn run( + &self, + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + Err(ShellError::GenericError { + error: "this command can only run during parse-time".into(), + msg: "can't run after parse-time".into(), + span: Some(call.head), + help: Some("try assigning this command's output to a const variable".into()), + inner: vec![], + }) + } + + fn run_const( + &self, + working_set: &StateWorkingSet, + call: &Call, + _input: PipelineData, + ) -> Result { + let path: Option = call.opt_const(working_set, 0)?; + let cwd = working_set.permanent_state.cwd(None)?; + let current_file = + working_set + .files + .top() + .ok_or_else(|| ShellError::FileNotFoundCustom { + msg: "Couldn't find current file".into(), + span: call.head, + })?; + + let out = if let Some(path) = path { + let dir = expand_path_with( + current_file + .parent() + .ok_or_else(|| ShellError::FileNotFoundCustom { + msg: "Couldn't find current file's parent.".into(), + span: call.head, + })?, + &cwd, + true, + ); + Value::string( + expand_path_with(path, dir, false).to_string_lossy(), + call.head, + ) + } else { + Value::string( + expand_path_with(current_file, &cwd, true).to_string_lossy(), + call.head, + ) + }; + + Ok(out.into_pipeline_data()) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Get the path of the current file", + example: r#"const current_file = path self"#, + result: None, + }, + Example { + description: "Get the path of the directory containing the current file", + example: r#"const current_file = path self ."#, + result: None, + }, + #[cfg(windows)] + Example { + description: "Get the absolute form of a path relative to the current file", + example: r#"const current_file = path self ..\foo"#, + result: None, + }, + #[cfg(not(windows))] + Example { + description: "Get the absolute form of a path relative to the current file", + example: r#"const current_file = path self ../foo"#, + result: None, + }, + ] + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SubCommand {}) + } +} diff --git a/crates/nu-command/src/platform/dir_info.rs b/crates/nu-command/src/platform/dir_info.rs index 10ce4f5420..9b8ea1929e 100644 --- a/crates/nu-command/src/platform/dir_info.rs +++ b/crates/nu-command/src/platform/dir_info.rs @@ -9,7 +9,7 @@ pub struct DirBuilder { pub min: Option, pub deref: bool, pub exclude: Option, - pub all: bool, + pub long: bool, } impl DirBuilder { @@ -18,14 +18,14 @@ impl DirBuilder { min: Option, deref: bool, exclude: Option, - all: bool, + long: bool, ) -> DirBuilder { DirBuilder { tag, min, deref, exclude, - all, + long, } } } @@ -39,6 +39,7 @@ pub struct DirInfo { blocks: u64, path: PathBuf, tag: Span, + long: bool, } #[derive(Debug, Clone)] @@ -47,10 +48,16 @@ pub struct FileInfo { size: u64, blocks: Option, tag: Span, + long: bool, } impl FileInfo { - pub fn new(path: impl Into, deref: bool, tag: Span) -> Result { + pub fn new( + path: impl Into, + deref: bool, + tag: Span, + long: bool, + ) -> Result { let path = path.into(); let m = if deref { std::fs::metadata(&path) @@ -67,6 +74,7 @@ impl FileInfo { blocks: block_size, size: d.len(), tag, + long, }) } Err(e) => Err(e.into()), @@ -92,6 +100,7 @@ impl DirInfo { blocks: 0, tag: params.tag, path, + long: params.long, }; match std::fs::metadata(&s.path) { @@ -154,13 +163,13 @@ impl DirInfo { .as_ref() .map_or(true, |x| !x.matches_path(&f)); if include { - match FileInfo::new(f, params.deref, self.tag) { + match FileInfo::new(f, params.deref, self.tag, self.long) { Ok(file) => { let inc = params.min.map_or(true, |s| file.size >= s); if inc { self.size += file.size; self.blocks += file.blocks.unwrap_or(0); - if params.all { + if params.long { self.files.push(file); } } @@ -197,16 +206,27 @@ impl From for Value { // }) // } - Value::record( - record! { - "path" => Value::string(d.path.display().to_string(), d.tag), - "apparent" => Value::filesize(d.size as i64, d.tag), - "physical" => Value::filesize(d.blocks as i64, d.tag), - "directories" => value_from_vec(d.dirs, d.tag), - "files" => value_from_vec(d.files, d.tag) - }, - d.tag, - ) + if d.long { + Value::record( + record! { + "path" => Value::string(d.path.display().to_string(), d.tag), + "apparent" => Value::filesize(d.size as i64, d.tag), + "physical" => Value::filesize(d.blocks as i64, d.tag), + "directories" => value_from_vec(d.dirs, d.tag), + "files" => value_from_vec(d.files, d.tag) + }, + d.tag, + ) + } else { + Value::record( + record! { + "path" => Value::string(d.path.display().to_string(), d.tag), + "apparent" => Value::filesize(d.size as i64, d.tag), + "physical" => Value::filesize(d.blocks as i64, d.tag), + }, + d.tag, + ) + } } } @@ -215,16 +235,27 @@ impl From for Value { // cols.push("errors".into()); // vals.push(Value::nothing(Span::unknown())); - Value::record( - record! { - "path" => Value::string(f.path.display().to_string(), f.tag), - "apparent" => Value::filesize(f.size as i64, f.tag), - "physical" => Value::filesize(f.blocks.unwrap_or(0) as i64, f.tag), - "directories" => Value::nothing(Span::unknown()), - "files" => Value::nothing(Span::unknown()), - }, - f.tag, - ) + if f.long { + Value::record( + record! { + "path" => Value::string(f.path.display().to_string(), f.tag), + "apparent" => Value::filesize(f.size as i64, f.tag), + "physical" => Value::filesize(f.blocks.unwrap_or(0) as i64, f.tag), + "directories" => Value::nothing(Span::unknown()), + "files" => Value::nothing(Span::unknown()), + }, + f.tag, + ) + } else { + Value::record( + record! { + "path" => Value::string(f.path.display().to_string(), f.tag), + "apparent" => Value::filesize(f.size as i64, f.tag), + "physical" => Value::filesize(f.blocks.unwrap_or(0) as i64, f.tag), + }, + f.tag, + ) + } } } diff --git a/crates/nu-command/src/platform/mod.rs b/crates/nu-command/src/platform/mod.rs index 16a245f87b..25df4e0c59 100644 --- a/crates/nu-command/src/platform/mod.rs +++ b/crates/nu-command/src/platform/mod.rs @@ -5,7 +5,7 @@ mod input; mod is_terminal; mod kill; mod sleep; -mod term_size; +mod term; #[cfg(unix)] mod ulimit; mod whoami; @@ -19,7 +19,7 @@ pub use input::InputListen; pub use is_terminal::IsTerminal; pub use kill::Kill; pub use sleep::Sleep; -pub use term_size::TermSize; +pub use term::{Term, TermQuery, TermSize}; #[cfg(unix)] pub use ulimit::ULimit; pub use whoami::Whoami; diff --git a/crates/nu-command/src/platform/sleep.rs b/crates/nu-command/src/platform/sleep.rs index c63de6ae9d..f995ac85be 100644 --- a/crates/nu-command/src/platform/sleep.rs +++ b/crates/nu-command/src/platform/sleep.rs @@ -70,8 +70,8 @@ impl Command for Sleep { result: Some(Value::nothing(Span::test_data())), }, Example { - description: "Sleep for 3sec", - example: "sleep 1sec 1sec 1sec", + description: "Use multiple arguments to write a duration with multiple units, which is unsupported by duration literals", + example: "sleep 1min 30sec", result: None, }, Example { diff --git a/crates/nu-command/src/platform/term/mod.rs b/crates/nu-command/src/platform/term/mod.rs new file mode 100644 index 0000000000..cd37a0583b --- /dev/null +++ b/crates/nu-command/src/platform/term/mod.rs @@ -0,0 +1,7 @@ +mod term_; +mod term_query; +mod term_size; + +pub use term_::Term; +pub use term_query::TermQuery; +pub use term_size::TermSize; diff --git a/crates/nu-command/src/platform/term/term_.rs b/crates/nu-command/src/platform/term/term_.rs new file mode 100644 index 0000000000..b28f226263 --- /dev/null +++ b/crates/nu-command/src/platform/term/term_.rs @@ -0,0 +1,34 @@ +use nu_engine::{command_prelude::*, get_full_help}; + +#[derive(Clone)] +pub struct Term; + +impl Command for Term { + fn name(&self) -> &str { + "term" + } + + fn signature(&self) -> Signature { + Signature::build("term") + .category(Category::Platform) + .input_output_types(vec![(Type::Nothing, Type::String)]) + } + + fn description(&self) -> &str { + "Commands for querying information about the terminal." + } + + fn extra_description(&self) -> &str { + "You must use one of the following subcommands. Using this command as-is will only produce this help message." + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + Ok(Value::string(get_full_help(self, engine_state, stack), call.head).into_pipeline_data()) + } +} diff --git a/crates/nu-command/src/platform/term/term_query.rs b/crates/nu-command/src/platform/term/term_query.rs new file mode 100644 index 0000000000..2cb11f1dc3 --- /dev/null +++ b/crates/nu-command/src/platform/term/term_query.rs @@ -0,0 +1,173 @@ +use std::{ + io::{Read, Write}, + time::Duration, +}; + +use nu_engine::command_prelude::*; + +const CTRL_C: u8 = 3; + +#[derive(Clone)] +pub struct TermQuery; + +impl Command for TermQuery { + fn name(&self) -> &str { + "term query" + } + + fn description(&self) -> &str { + "Query the terminal for information." + } + + fn extra_description(&self) -> &str { + "Print the given query, and read the immediate result from stdin. + +The standard input will be read right after `query` is printed, and consumed until the `terminator` +sequence is encountered. The `terminator` is not included in the output. + +If `terminator` is not supplied, input will be read until Ctrl-C is pressed. + +If `prefix` is supplied, input's initial bytes will be validated against it. +The `prefix` is not included in the output." + } + + fn signature(&self) -> Signature { + Signature::build("term query") + .category(Category::Platform) + .input_output_types(vec![(Type::Nothing, Type::Binary)]) + .allow_variants_without_examples(true) + .required( + "query", + SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::String]), + "The query that will be printed to stdout.", + ) + .named( + "prefix", + SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::String]), + "Prefix sequence for the expected reply.", + Some('p'), + ) + .named( + "terminator", + SyntaxShape::OneOf(vec![SyntaxShape::Binary, SyntaxShape::String]), + "Terminator sequence for the expected reply.", + Some('t'), + ) + .switch( + "keep", + "Include prefix and terminator in the output.", + Some('k'), + ) + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Get cursor position.", + example: r#"term query (ansi cursor_position) --prefix (ansi csi) --terminator 'R'"#, + result: None, + }, + Example { + description: "Get terminal background color.", + example: r#"term query $'(ansi osc)10;?(ansi st)' --prefix $'(ansi osc)10;' --terminator (ansi st)"#, + result: None, + }, + Example { + description: "Get terminal background color. (some terminals prefer `char bel` rather than `ansi st` as string terminator)", + example: r#"term query $'(ansi osc)10;?(char bel)' --prefix $'(ansi osc)10;' --terminator (char bel)"#, + result: None, + }, + Example { + description: "Read clipboard content on terminals supporting OSC-52.", + example: r#"term query $'(ansi osc)52;c;?(ansi st)' --prefix $'(ansi osc)52;c;' --terminator (ansi st)"#, + result: None, + }, + ] + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + _input: PipelineData, + ) -> Result { + let query: Vec = call.req(engine_state, stack, 0)?; + let keep = call.has_flag(engine_state, stack, "keep")?; + let prefix: Option> = call.get_flag(engine_state, stack, "prefix")?; + let prefix = prefix.unwrap_or_default(); + let terminator: Option> = call.get_flag(engine_state, stack, "terminator")?; + + crossterm::terminal::enable_raw_mode()?; + scopeguard::defer! { + let _ = crossterm::terminal::disable_raw_mode(); + } + + // clear terminal events + while crossterm::event::poll(Duration::from_secs(0))? { + // If there's an event, read it to remove it from the queue + let _ = crossterm::event::read()?; + } + + let mut b = [0u8; 1]; + let mut buf = vec![]; + let mut stdin = std::io::stdin().lock(); + + { + let mut stdout = std::io::stdout().lock(); + stdout.write_all(&query)?; + stdout.flush()?; + } + + // Validate and skip prefix + for bc in prefix { + stdin.read_exact(&mut b)?; + if b[0] != bc { + return Err(ShellError::GenericError { + error: "Input did not begin with expected sequence".into(), + msg: "".into(), + span: None, + help: Some("Try running without `--prefix` and inspecting the output.".into()), + inner: vec![], + }); + } + if keep { + buf.push(b[0]); + } + } + + if let Some(terminator) = terminator { + loop { + stdin.read_exact(&mut b)?; + + if b[0] == CTRL_C { + return Err(ShellError::InterruptedByUser { + span: Some(call.head), + }); + } + + buf.push(b[0]); + + if buf.ends_with(&terminator) { + if !keep { + // Remove terminator + buf.drain((buf.len() - terminator.len())..); + } + break; + } + } + } else { + loop { + stdin.read_exact(&mut b)?; + + if b[0] == CTRL_C { + break; + } + + buf.push(b[0]); + } + }; + + Ok(Value::binary(buf, call.head).into_pipeline_data()) + } +} diff --git a/crates/nu-command/src/platform/term_size.rs b/crates/nu-command/src/platform/term/term_size.rs similarity index 82% rename from crates/nu-command/src/platform/term_size.rs rename to crates/nu-command/src/platform/term/term_size.rs index a4261dee81..b41d9da6c1 100644 --- a/crates/nu-command/src/platform/term_size.rs +++ b/crates/nu-command/src/platform/term/term_size.rs @@ -1,5 +1,5 @@ +use crossterm::terminal::size; use nu_engine::command_prelude::*; -use terminal_size::{terminal_size, Height, Width}; #[derive(Clone)] pub struct TermSize; @@ -51,15 +51,12 @@ impl Command for TermSize { ) -> Result { let head = call.head; - let (cols, rows) = match terminal_size() { - Some((w, h)) => (Width(w.0), Height(h.0)), - None => (Width(0), Height(0)), - }; + let (cols, rows) = size().unwrap_or((0, 0)); Ok(Value::record( record! { - "columns" => Value::int(cols.0 as i64, head), - "rows" => Value::int(rows.0 as i64, head), + "columns" => Value::int(cols as i64, head), + "rows" => Value::int(rows as i64, head), }, head, ) diff --git a/crates/nu-command/src/random/binary.rs b/crates/nu-command/src/random/binary.rs index 539796b8c5..36d0179e3a 100644 --- a/crates/nu-command/src/random/binary.rs +++ b/crates/nu-command/src/random/binary.rs @@ -1,5 +1,5 @@ use nu_engine::command_prelude::*; - +use nu_protocol::format_filesize_from_conf; use rand::{thread_rng, RngCore}; #[derive(Clone)] @@ -37,7 +37,27 @@ impl Command for SubCommand { call: &Call, _input: PipelineData, ) -> Result { - let length = call.req(engine_state, stack, 0)?; + let length_val = call.req(engine_state, stack, 0)?; + let length = match length_val { + Value::Int { val, .. } => usize::try_from(val).map_err(|_| ShellError::InvalidValue { + valid: "a non-negative int or filesize".into(), + actual: val.to_string(), + span: length_val.span(), + }), + Value::Filesize { val, .. } => { + usize::try_from(val).map_err(|_| ShellError::InvalidValue { + valid: "a non-negative int or filesize".into(), + actual: format_filesize_from_conf(val, engine_state.get_config()), + span: length_val.span(), + }) + } + val => Err(ShellError::RuntimeTypeMismatch { + expected: Type::custom("int or filesize"), + actual: val.get_type(), + span: val.span(), + }), + }?; + let mut rng = thread_rng(); let mut out = vec![0u8; length]; diff --git a/crates/nu-command/src/random/chars.rs b/crates/nu-command/src/random/chars.rs index d445a4aab8..e426fb5dbc 100644 --- a/crates/nu-command/src/random/chars.rs +++ b/crates/nu-command/src/random/chars.rs @@ -1,5 +1,5 @@ use nu_engine::command_prelude::*; - +use nu_protocol::format_filesize_from_conf; use rand::{ distributions::{Alphanumeric, Distribution}, thread_rng, @@ -73,14 +73,36 @@ fn chars( call: &Call, ) -> Result { let span = call.head; - let length: Option = call.get_flag(engine_state, stack, "length")?; + let length: Option = call.get_flag(engine_state, stack, "length")?; + let length = if let Some(length_val) = length { + match length_val { + Value::Int { val, .. } => usize::try_from(val).map_err(|_| ShellError::InvalidValue { + valid: "a non-negative int or filesize".into(), + actual: val.to_string(), + span: length_val.span(), + }), + Value::Filesize { val, .. } => { + usize::try_from(val).map_err(|_| ShellError::InvalidValue { + valid: "a non-negative int or filesize".into(), + actual: format_filesize_from_conf(val, engine_state.get_config()), + span: length_val.span(), + }) + } + val => Err(ShellError::RuntimeTypeMismatch { + expected: Type::custom("int or filesize"), + actual: val.get_type(), + span: val.span(), + }), + }? + } else { + DEFAULT_CHARS_LENGTH + }; - let chars_length = length.unwrap_or(DEFAULT_CHARS_LENGTH); let mut rng = thread_rng(); let random_string = Alphanumeric .sample_iter(&mut rng) - .take(chars_length) + .take(length) .map(char::from) .collect::(); diff --git a/crates/nu-command/src/random/dice.rs b/crates/nu-command/src/random/dice.rs index b11eb59e3e..32768a9937 100644 --- a/crates/nu-command/src/random/dice.rs +++ b/crates/nu-command/src/random/dice.rs @@ -12,7 +12,7 @@ impl Command for SubCommand { fn signature(&self) -> Signature { Signature::build("random dice") - .input_output_types(vec![(Type::Nothing, Type::ListStream)]) + .input_output_types(vec![(Type::Nothing, Type::list(Type::Int))]) .allow_variants_without_examples(true) .named( "dice", diff --git a/crates/nu-command/src/strings/str_/stats.rs b/crates/nu-command/src/strings/str_/stats.rs index 586a48ffde..8722a030d4 100644 --- a/crates/nu-command/src/strings/str_/stats.rs +++ b/crates/nu-command/src/strings/str_/stats.rs @@ -307,7 +307,7 @@ fn test_one_newline() { correct_counts.insert(Counter::GraphemeClusters, 1); correct_counts.insert(Counter::Bytes, 1); correct_counts.insert(Counter::CodePoints, 1); - correct_counts.insert(Counter::UnicodeWidth, 0); + correct_counts.insert(Counter::UnicodeWidth, 1); assert_eq!(correct_counts, counts); } @@ -347,7 +347,7 @@ fn test_count_counts_lines() { // one more than grapheme clusters because of \r\n correct_counts.insert(Counter::CodePoints, 24); - correct_counts.insert(Counter::UnicodeWidth, 17); + correct_counts.insert(Counter::UnicodeWidth, 23); assert_eq!(correct_counts, counts); } diff --git a/crates/nu-command/src/system/exec.rs b/crates/nu-command/src/system/exec.rs index 7bf9daecc9..8a2b24a113 100644 --- a/crates/nu-command/src/system/exec.rs +++ b/crates/nu-command/src/system/exec.rs @@ -45,6 +45,7 @@ On Windows based systems, Nushell will wait for the command to finish and then e call.head, engine_state, stack, + &cwd, )); }; executable diff --git a/crates/nu-command/src/system/nu_check.rs b/crates/nu-command/src/system/nu_check.rs index 59982dfaa7..945440cca4 100644 --- a/crates/nu-command/src/system/nu_check.rs +++ b/crates/nu-command/src/system/nu_check.rs @@ -15,7 +15,6 @@ impl Command for NuCheck { Signature::build("nu-check") .input_output_types(vec![ (Type::String, Type::Bool), - (Type::ListStream, Type::Bool), (Type::List(Box::new(Type::Any)), Type::Bool), ]) // type is string to avoid automatically canonicalizing the path diff --git a/crates/nu-command/src/system/run_external.rs b/crates/nu-command/src/system/run_external.rs index ffb34bf077..e3d74c055b 100644 --- a/crates/nu-command/src/system/run_external.rs +++ b/crates/nu-command/src/system/run_external.rs @@ -1,6 +1,6 @@ use nu_cmd_base::hook::eval_hook; -use nu_engine::{command_prelude::*, env_to_strings, get_eval_expression}; -use nu_path::{dots::expand_ndots, expand_tilde}; +use nu_engine::{command_prelude::*, env_to_strings}; +use nu_path::{dots::expand_ndots, expand_tilde, AbsolutePath}; use nu_protocol::{did_you_mean, process::ChildProcess, ByteStream, NuGlob, OutDest, Signals}; use nu_system::ForegroundChild; use nu_utils::IgnoreCaseExt; @@ -126,7 +126,13 @@ impl Command for External { // effect if it's an absolute path already let paths = nu_engine::env::path_str(engine_state, stack, call.head)?; let Some(executable) = which(&expanded_name, &paths, cwd.as_ref()) else { - return Err(command_not_found(&name_str, call.head, engine_state, stack)); + return Err(command_not_found( + &name_str, + call.head, + engine_state, + stack, + &cwd, + )); }; executable }; @@ -289,8 +295,7 @@ pub fn eval_arguments_from_call( call: &Call, ) -> Result>, ShellError> { let cwd = engine_state.cwd(Some(stack))?; - let eval_expression = get_eval_expression(engine_state); - let call_args = call.rest_iter_flattened(engine_state, stack, eval_expression, 1)?; + let call_args = call.rest::(engine_state, stack, 1)?; let mut args: Vec> = Vec::with_capacity(call_args.len()); for arg in call_args { @@ -433,6 +438,7 @@ pub fn command_not_found( span: Span, engine_state: &EngineState, stack: &mut Stack, + cwd: &AbsolutePath, ) -> ShellError { // Run the `command_not_found` hook if there is one. if let Some(hook) = &stack.get_config(engine_state).hooks.command_not_found { @@ -543,12 +549,12 @@ pub fn command_not_found( } // If we find a file, it's likely that the user forgot to set permissions - if Path::new(name).is_file() { + if cwd.join(name).is_file() { return ShellError::ExternalCommand { - label: format!("Command `{name}` not found"), - help: format!("`{name}` refers to a file that is not executable. Did you forget to set execute permissions?"), - span, - }; + label: format!("Command `{name}` not found"), + help: format!("`{name}` refers to a file that is not executable. Did you forget to set execute permissions?"), + span, + }; } // We found nothing useful. Give up and return a generic error message. diff --git a/crates/nu-command/src/system/sys/cpu.rs b/crates/nu-command/src/system/sys/cpu.rs index 3243478e03..de24efd898 100644 --- a/crates/nu-command/src/system/sys/cpu.rs +++ b/crates/nu-command/src/system/sys/cpu.rs @@ -13,6 +13,11 @@ impl Command for SysCpu { fn signature(&self) -> Signature { Signature::build("sys cpu") .filter() + .switch( + "long", + "Get all available columns (slower, needs to sample CPU over time)", + Some('l'), + ) .category(Category::System) .input_output_types(vec![(Type::Nothing, Type::table())]) } @@ -23,12 +28,13 @@ impl Command for SysCpu { fn run( &self, - _engine_state: &EngineState, - _stack: &mut Stack, + engine_state: &EngineState, + stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { - Ok(cpu(call.head).into_pipeline_data()) + let long = call.has_flag(engine_state, stack, "long")?; + Ok(cpu(long, call.head).into_pipeline_data()) } fn examples(&self) -> Vec { @@ -40,38 +46,44 @@ impl Command for SysCpu { } } -fn cpu(span: Span) -> Value { +fn cpu(long: bool, span: Span) -> Value { let mut sys = System::new(); - sys.refresh_cpu_specifics(CpuRefreshKind::everything()); - // We must refresh the CPU twice a while apart to get valid usage data. - // In theory we could just sleep MINIMUM_CPU_UPDATE_INTERVAL, but I've noticed that - // that gives poor results (error of ~5%). Decided to wait 2x that long, somewhat arbitrarily - std::thread::sleep(MINIMUM_CPU_UPDATE_INTERVAL * 2); - sys.refresh_cpu_specifics(CpuRefreshKind::new().with_cpu_usage()); + if long { + sys.refresh_cpu_specifics(CpuRefreshKind::everything()); + // We must refresh the CPU twice a while apart to get valid usage data. + // In theory we could just sleep MINIMUM_CPU_UPDATE_INTERVAL, but I've noticed that + // that gives poor results (error of ~5%). Decided to wait 2x that long, somewhat arbitrarily + std::thread::sleep(MINIMUM_CPU_UPDATE_INTERVAL * 2); + sys.refresh_cpu_specifics(CpuRefreshKind::new().with_cpu_usage()); + } else { + sys.refresh_cpu_specifics(CpuRefreshKind::new().with_frequency()); + } let cpus = sys .cpus() .iter() .map(|cpu| { - // sysinfo CPU usage numbers are not very precise unless you wait a long time between refreshes. - // Round to 1DP (chosen somewhat arbitrarily) so people aren't misled by high-precision floats. - let rounded_usage = (cpu.cpu_usage() * 10.0).round() / 10.0; - let load_avg = System::load_average(); let load_avg = format!( "{:.2}, {:.2}, {:.2}", load_avg.one, load_avg.five, load_avg.fifteen ); - let record = record! { + let mut record = record! { "name" => Value::string(trim_cstyle_null(cpu.name()), span), "brand" => Value::string(trim_cstyle_null(cpu.brand()), span), - "freq" => Value::int(cpu.frequency() as i64, span), - "cpu_usage" => Value::float(rounded_usage.into(), span), - "load_average" => Value::string(load_avg, span), "vendor_id" => Value::string(trim_cstyle_null(cpu.vendor_id()), span), + "freq" => Value::int(cpu.frequency() as i64, span), + "load_average" => Value::string(load_avg, span), }; + if long { + // sysinfo CPU usage numbers are not very precise unless you wait a long time between refreshes. + // Round to 1DP (chosen somewhat arbitrarily) so people aren't misled by high-precision floats. + let rounded_usage = (f64::from(cpu.cpu_usage()) * 10.0).round() / 10.0; + record.push("cpu_usage", rounded_usage.into_value(span)); + } + Value::record(record, span) }) .collect(); diff --git a/crates/nu-command/src/viewers/griddle.rs b/crates/nu-command/src/viewers/griddle.rs index 2774903d0f..23b8259ddd 100644 --- a/crates/nu-command/src/viewers/griddle.rs +++ b/crates/nu-command/src/viewers/griddle.rs @@ -4,9 +4,8 @@ use lscolors::Style; use nu_engine::{command_prelude::*, env_to_string}; use nu_protocol::Config; use nu_term_grid::grid::{Alignment, Cell, Direction, Filling, Grid, GridOptions}; -use nu_utils::get_ls_colors; +use nu_utils::{get_ls_colors, terminal_size}; use std::path::Path; -use terminal_size::{Height, Width}; #[derive(Clone)] pub struct Griddle; @@ -192,7 +191,7 @@ fn create_grid_output( let cols = if let Some(col) = width_param { col as u16 - } else if let Some((Width(w), Height(_h))) = terminal_size::terminal_size() { + } else if let Ok((w, _h)) = terminal_size() { w } else { 80u16 diff --git a/crates/nu-command/src/viewers/table.rs b/crates/nu-command/src/viewers/table.rs index 721d10192f..e8f266c29b 100644 --- a/crates/nu-command/src/viewers/table.rs +++ b/crates/nu-command/src/viewers/table.rs @@ -2,7 +2,14 @@ // overall reduce the redundant calls to StyleComputer etc. // the goal is to configure it once... -use lscolors::{LsColors, Style}; +use std::{ + collections::VecDeque, + io::{IsTerminal, Read}, + path::PathBuf, + str::FromStr, + time::Instant, +}; + use nu_color_config::{color_from_hex, StyleComputer, TextStyle}; use nu_engine::{command_prelude::*, env_to_string}; use nu_path::form::Absolute; @@ -15,17 +22,12 @@ use nu_table::{ NuTable, StringResult, TableOpts, TableOutput, }; use nu_utils::get_ls_colors; -use std::{ - collections::VecDeque, - io::{IsTerminal, Read}, - path::PathBuf, - str::FromStr, - time::Instant, -}; -use terminal_size::{Height, Width}; + +use lscolors::{LsColors, Style}; use url::Url; type ShellResult = Result; +type NuPathBuf = nu_path::PathBuf; const STREAM_PAGE_SIZE: usize = 1000; const DEFAULT_TABLE_WIDTH: usize = 80; @@ -119,6 +121,7 @@ impl Command for Table { let val = Value::list(supported_table_modes(), Span::test_data()); return Ok(val.into_pipeline_data()); } + let input = CmdInput::parse(engine_state, stack, call, input)?; // reset vt processing, aka ansi because illbehaved externals can break it @@ -384,7 +387,7 @@ struct CmdInput<'a> { call: &'a Call<'a>, data: PipelineData, cfg: TableConfig, - cwd: nu_path::PathBuf, + cwd: Option, } impl<'a> CmdInput<'a> { @@ -394,8 +397,8 @@ impl<'a> CmdInput<'a> { call: &'a Call<'a>, data: PipelineData, ) -> ShellResult { - let cwd = engine_state.cwd(Some(stack))?; let cfg = parse_table_config(call, engine_state, stack)?; + let cwd = get_cwd(engine_state, stack)?; Ok(Self { engine_state, @@ -746,6 +749,13 @@ fn make_clickable_link( ) -> String { // uri's based on this https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda + #[cfg(any( + unix, + windows, + target_os = "redox", + target_os = "wasi", + target_os = "hermit" + ))] if show_clickable_links { format!( "\x1b]8;;{}\x1b\\{}\x1b]8;;\x1b\\", @@ -761,6 +771,18 @@ fn make_clickable_link( None => full_path, } } + + #[cfg(not(any( + unix, + windows, + target_os = "redox", + target_os = "wasi", + target_os = "hermit" + )))] + match link_name { + Some(link_name) => link_name.to_string(), + None => full_path, + } } struct PagingTableCreator { @@ -980,14 +1002,18 @@ fn render_path_name( path: &str, config: &Config, ls_colors: &LsColors, - cwd: nu_path::PathBuf, + cwd: Option, span: Span, ) -> Option { if !config.ls.use_ls_colors { return None; } - let fullpath = cwd.join(path); + let fullpath = match cwd { + Some(cwd) => PathBuf::from(cwd.join(path)), + None => PathBuf::from(path), + }; + let stripped_path = nu_utils::strip_ansi_unlikely(path); let metadata = std::fs::symlink_metadata(fullpath); let has_metadata = metadata.is_ok(); @@ -1124,10 +1150,20 @@ fn create_table_opts<'a>( TableOpts::new(cfg, comp, signals, span, width, theme, offset, index) } -fn get_table_width(width: Option) -> usize { - if let Some(col) = width { +fn get_cwd(engine_state: &EngineState, stack: &mut Stack) -> ShellResult> { + #[cfg(feature = "os")] + let cwd = engine_state.cwd(Some(stack)).map(Some)?; + + #[cfg(not(feature = "os"))] + let cwd = None; + + Ok(cwd) +} + +fn get_table_width(width_param: Option) -> usize { + if let Some(col) = width_param { col as usize - } else if let Some((Width(w), Height(_))) = terminal_size::terminal_size() { + } else if let Ok((w, _h)) = crossterm::terminal::size() { w as usize } else { DEFAULT_TABLE_WIDTH diff --git a/crates/nu-command/tests/commands/assignment/append_assign.rs b/crates/nu-command/tests/commands/assignment/append_assign.rs deleted file mode 100644 index 97abc8eb6f..0000000000 --- a/crates/nu-command/tests/commands/assignment/append_assign.rs +++ /dev/null @@ -1,88 +0,0 @@ -use nu_test_support::nu; - -#[test] -fn append_assign_int() { - let actual = nu!(r#" - mut a = [1 2]; - $a ++= [3 4]; - $a == [1 2 3 4] - "#); - - assert_eq!(actual.out, "true") -} - -#[test] -fn append_assign_string() { - let actual = nu!(r#" - mut a = [a b]; - $a ++= [c d]; - $a == [a b c d] - "#); - - assert_eq!(actual.out, "true") -} - -#[test] -fn append_assign_any() { - let actual = nu!(r#" - mut a = [1 2 a]; - $a ++= [b 3]; - $a == [1 2 a b 3] - "#); - - assert_eq!(actual.out, "true") -} - -#[test] -fn append_assign_both_empty() { - let actual = nu!(r#" - mut a = []; - $a ++= []; - $a == [] - "#); - - assert_eq!(actual.out, "true") -} - -#[test] -fn append_assign_type_mismatch() { - let actual = nu!(r#" - mut a = [1 2]; - $a ++= [a]; - $a == [1 2 "a"] - "#); - - assert_eq!(actual.out, "true") -} - -#[test] -fn append_assign_single_element() { - let actual = nu!(r#" - mut a = ["list" "and"]; - $a ++= "a single element"; - $a == ["list" "and" "a single element"] - "#); - - assert_eq!(actual.out, "true") -} - -#[test] -fn append_assign_to_single_element() { - let actual = nu!(r#" - mut a = "string"; - $a ++= ["and" "the" "list"]; - $a == ["string" "and" "the" "list"] - "#); - - assert_eq!(actual.out, "true") -} - -#[test] -fn append_assign_single_to_single() { - let actual = nu!(r#" - mut a = 1; - $a ++= "and a single element"; - "#); - - assert!(actual.err.contains("nu::parser::unsupported_operation")); -} diff --git a/crates/nu-command/tests/commands/assignment/concat.rs b/crates/nu-command/tests/commands/assignment/concat.rs new file mode 100644 index 0000000000..603a486f14 --- /dev/null +++ b/crates/nu-command/tests/commands/assignment/concat.rs @@ -0,0 +1,76 @@ +use nu_test_support::nu; + +#[test] +fn concat_assign_list_int() { + let actual = nu!(r#" + mut a = [1 2]; + $a ++= [3 4]; + $a == [1 2 3 4] + "#); + + assert_eq!(actual.out, "true") +} + +#[test] +fn concat_assign_list_string() { + let actual = nu!(r#" + mut a = [a b]; + $a ++= [c d]; + $a == [a b c d] + "#); + + assert_eq!(actual.out, "true") +} + +#[test] +fn concat_assign_any() { + let actual = nu!(r#" + mut a = [1 2 a]; + $a ++= [b 3]; + $a == [1 2 a b 3] + "#); + + assert_eq!(actual.out, "true") +} + +#[test] +fn concat_assign_both_empty() { + let actual = nu!(r#" + mut a = []; + $a ++= []; + $a == [] + "#); + + assert_eq!(actual.out, "true") +} + +#[test] +fn concat_assign_string() { + let actual = nu!(r#" + mut a = 'hello'; + $a ++= ' world'; + $a == 'hello world' + "#); + + assert_eq!(actual.out, "true") +} + +#[test] +fn concat_assign_type_mismatch() { + let actual = nu!(r#" + mut a = []; + $a ++= 'str' + "#); + + assert!(actual.err.contains("nu::parser::unsupported_operation")); +} + +#[test] +fn concat_assign_runtime_type_mismatch() { + let actual = nu!(r#" + mut a = []; + $a ++= if true { 'str' } + "#); + + assert!(actual.err.contains("nu::shell::type_mismatch")); +} diff --git a/crates/nu-command/tests/commands/assignment/mod.rs b/crates/nu-command/tests/commands/assignment/mod.rs index 8235f532f5..0859deee1a 100644 --- a/crates/nu-command/tests/commands/assignment/mod.rs +++ b/crates/nu-command/tests/commands/assignment/mod.rs @@ -1 +1 @@ -mod append_assign; +mod concat; diff --git a/crates/nu-command/tests/commands/chunk_by.rs b/crates/nu-command/tests/commands/chunk_by.rs new file mode 100644 index 0000000000..1a5a1ed043 --- /dev/null +++ b/crates/nu-command/tests/commands/chunk_by.rs @@ -0,0 +1,58 @@ +use nu_test_support::{nu, pipeline}; + +#[test] +fn chunk_by_on_empty_input_returns_empty_list() { + let actual = nu!("[] | chunk-by {|it| $it} | to nuon"); + assert!(actual.err.is_empty()); + assert_eq!(actual.out, "[]"); +} + +#[test] +fn chunk_by_strings_works() { + let sample = r#" + [a a a b b b c c c a a a] + "#; + + let actual = nu!(pipeline(&format!( + r#" + {sample} + | chunk-by {{|it| $it}} + | to nuon + "# + ))); + + assert_eq!(actual.out, "[[a, a, a], [b, b, b], [c, c, c], [a, a, a]]"); +} + +#[test] +fn chunk_by_field_works() { + let sample = r#"[ + { + name: bob, + age: 20, + cool: false + }, + { + name: jane, + age: 30, + cool: false + }, + { + name: marie, + age: 19, + cool: true + }, + { + name: carl, + age: 36, + cool: true + } ]"#; + + let actual = nu!(pipeline(&format!( + r#"{sample} + | chunk-by {{|it| $it.cool}} + | length"# + ))); + + assert_eq!(actual.out, "2"); +} diff --git a/crates/nu-command/tests/commands/debug/timeit.rs b/crates/nu-command/tests/commands/debug/timeit.rs index a59f67d26a..509f5bc4e7 100644 --- a/crates/nu-command/tests/commands/debug/timeit.rs +++ b/crates/nu-command/tests/commands/debug/timeit.rs @@ -2,7 +2,7 @@ use nu_test_support::nu; #[test] fn timeit_show_stdout() { - let actual = nu!("let t = timeit nu --testbin cococo abcdefg"); + let actual = nu!("let t = timeit { nu --testbin cococo abcdefg }"); assert_eq!(actual.out, "abcdefg"); } diff --git a/crates/nu-command/tests/commands/do_.rs b/crates/nu-command/tests/commands/do_.rs index bbce5648db..1a2258cf9c 100644 --- a/crates/nu-command/tests/commands/do_.rs +++ b/crates/nu-command/tests/commands/do_.rs @@ -44,7 +44,7 @@ fn do_with_semicolon_break_on_failed_external() { fn ignore_shell_errors_works_for_external_with_semicolon() { let actual = nu!(r#"do -s { open asdfasdf.txt }; "text""#); - assert_eq!(actual.err, ""); + assert!(actual.err.contains("Deprecated option")); assert_eq!(actual.out, "text"); } @@ -52,7 +52,7 @@ fn ignore_shell_errors_works_for_external_with_semicolon() { fn ignore_program_errors_works_for_external_with_semicolon() { let actual = nu!(r#"do -p { nu -n -c 'exit 1' }; "text""#); - assert_eq!(actual.err, ""); + assert!(actual.err.contains("Deprecated option")); assert_eq!(actual.out, "text"); } @@ -80,6 +80,7 @@ fn run_closure_with_it_using() { #[test] fn waits_for_external() { let actual = nu!(r#"do -p { nu -c 'sleep 1sec; print before; exit 1'}; print after"#); - assert!(actual.err.is_empty()); + + assert!(actual.err.contains("Deprecated option")); assert_eq!(actual.out, "beforeafter"); } diff --git a/crates/nu-command/tests/commands/du.rs b/crates/nu-command/tests/commands/du.rs index 1e22d54f2f..c88eb546be 100644 --- a/crates/nu-command/tests/commands/du.rs +++ b/crates/nu-command/tests/commands/du.rs @@ -100,3 +100,17 @@ fn du_with_multiple_path() { let actual = nu!(cwd: "tests/fixtures", "du ...[] | length"); assert_eq!(actual.out, "0"); } + +#[test] +fn test_du_output_columns() { + let actual = nu!( + cwd: "tests/fixtures/formats", + "du -m 1 | columns | str join ','" + ); + assert_eq!(actual.out, "path,apparent,physical"); + let actual = nu!( + cwd: "tests/fixtures/formats", + "du -m 1 -l | columns | str join ','" + ); + assert_eq!(actual.out, "path,apparent,physical,directories,files"); +} diff --git a/crates/nu-command/tests/commands/match_.rs b/crates/nu-command/tests/commands/match_.rs index 23e4450d2b..5d41b3291a 100644 --- a/crates/nu-command/tests/commands/match_.rs +++ b/crates/nu-command/tests/commands/match_.rs @@ -140,6 +140,15 @@ fn match_constant_7() { assert_eq!(actual.out, "success"); } +#[test] +fn match_constant_8() { + let actual = + nu!(r#"match "foo" { r#'foo'# => { print "success" }, _ => { print "failure" } }"#); + // Make sure we don't see any of these values in the output + // As we do not auto-print loops anymore + assert_eq!(actual.out, "success"); +} + #[test] fn match_null() { let actual = nu!(r#"match null { null => { print "success"}, _ => { print "failure" }}"#); diff --git a/crates/nu-command/tests/commands/math/mod.rs b/crates/nu-command/tests/commands/math/mod.rs index cde6a2da35..5d04f450a4 100644 --- a/crates/nu-command/tests/commands/math/mod.rs +++ b/crates/nu-command/tests/commands/math/mod.rs @@ -483,7 +483,7 @@ fn compound_where_paren() { // TODO: these ++ tests are not really testing *math* functionality, maybe find another place for them #[test] -fn adding_lists() { +fn concat_lists() { let actual = nu!(pipeline( r#" [1 3] ++ [5 6] | to nuon @@ -494,29 +494,7 @@ fn adding_lists() { } #[test] -fn adding_list_and_value() { - let actual = nu!(pipeline( - r#" - [1 3] ++ 5 | to nuon - "# - )); - - assert_eq!(actual.out, "[1, 3, 5]"); -} - -#[test] -fn adding_value_and_list() { - let actual = nu!(pipeline( - r#" - 1 ++ [3 5] | to nuon - "# - )); - - assert_eq!(actual.out, "[1, 3, 5]"); -} - -#[test] -fn adding_tables() { +fn concat_tables() { let actual = nu!(pipeline( r#" [[a b]; [1 2]] ++ [[c d]; [10 11]] | to nuon @@ -526,7 +504,7 @@ fn adding_tables() { } #[test] -fn append_strings() { +fn concat_strings() { let actual = nu!(pipeline( r#" "foo" ++ "bar" @@ -536,7 +514,7 @@ fn append_strings() { } #[test] -fn append_binary_values() { +fn concat_binary_values() { let actual = nu!(pipeline( r#" 0x[01 02] ++ 0x[03 04] | to nuon diff --git a/crates/nu-command/tests/commands/mod.rs b/crates/nu-command/tests/commands/mod.rs index 678b8d8896..a8ffdeb917 100644 --- a/crates/nu-command/tests/commands/mod.rs +++ b/crates/nu-command/tests/commands/mod.rs @@ -8,6 +8,7 @@ mod break_; mod bytes; mod cal; mod cd; +mod chunk_by; mod chunks; mod compact; mod complete; diff --git a/crates/nu-command/tests/commands/mut_.rs b/crates/nu-command/tests/commands/mut_.rs index 52aa9fcff1..7c55110f5f 100644 --- a/crates/nu-command/tests/commands/mut_.rs +++ b/crates/nu-command/tests/commands/mut_.rs @@ -148,3 +148,14 @@ fn def_should_not_mutate_mut() { assert!(actual.err.contains("capture of mutable variable")); assert!(!actual.status.success()) } + +#[test] +fn assign_to_non_mut_variable_raises_parse_error() { + let actual = nu!("let x = 3; $x = 4"); + assert!(actual + .err + .contains("parser::assignment_requires_mutable_variable")); + + let actual = nu!("mut x = 3; x = 5"); + assert!(actual.err.contains("parser::assignment_requires_variable")); +} diff --git a/crates/nu-command/tests/commands/network/http/delete.rs b/crates/nu-command/tests/commands/network/http/delete.rs index 2e2624122e..ac79a4fe62 100644 --- a/crates/nu-command/tests/commands/network/http/delete.rs +++ b/crates/nu-command/tests/commands/network/http/delete.rs @@ -131,14 +131,21 @@ fn http_delete_timeout() { let _mock = server .mock("DELETE", "/") .with_chunked_body(|w| { - thread::sleep(Duration::from_secs(1)); + thread::sleep(Duration::from_secs(10)); w.write_all(b"Delayed response!") }) .create(); let actual = nu!(pipeline( - format!("http delete --max-time 500ms {url}", url = server.url()).as_str() + format!("http delete --max-time 100ms {url}", url = server.url()).as_str() )); - assert!(&actual.err.contains("nu::shell::io_error")); + assert!(&actual.err.contains("nu::shell::network_failure")); + + #[cfg(not(target_os = "windows"))] + assert!(&actual.err.contains("timed out reading response")); + #[cfg(target_os = "windows")] + assert!(&actual + .err + .contains("did not properly respond after a period of time")); } diff --git a/crates/nu-command/tests/commands/network/http/get.rs b/crates/nu-command/tests/commands/network/http/get.rs index e75536abb4..87b6b93388 100644 --- a/crates/nu-command/tests/commands/network/http/get.rs +++ b/crates/nu-command/tests/commands/network/http/get.rs @@ -325,14 +325,21 @@ fn http_get_timeout() { let _mock = server .mock("GET", "/") .with_chunked_body(|w| { - thread::sleep(Duration::from_secs(1)); + thread::sleep(Duration::from_secs(10)); w.write_all(b"Delayed response!") }) .create(); let actual = nu!(pipeline( - format!("http get --max-time 500ms {url}", url = server.url()).as_str() + format!("http get --max-time 100ms {url}", url = server.url()).as_str() )); - assert!(&actual.err.contains("nu::shell::io_error")); + assert!(&actual.err.contains("nu::shell::network_failure")); + + #[cfg(not(target_os = "windows"))] + assert!(&actual.err.contains("timed out reading response")); + #[cfg(target_os = "windows")] + assert!(&actual + .err + .contains("did not properly respond after a period of time")); } diff --git a/crates/nu-command/tests/commands/network/http/options.rs b/crates/nu-command/tests/commands/network/http/options.rs index 82dcf33a5a..b1478b4ecc 100644 --- a/crates/nu-command/tests/commands/network/http/options.rs +++ b/crates/nu-command/tests/commands/network/http/options.rs @@ -50,14 +50,21 @@ fn http_options_timeout() { let _mock = server .mock("OPTIONS", "/") .with_chunked_body(|w| { - thread::sleep(Duration::from_secs(1)); + thread::sleep(Duration::from_secs(10)); w.write_all(b"Delayed response!") }) .create(); let actual = nu!(pipeline( - format!("http options --max-time 500ms {url}", url = server.url()).as_str() + format!("http options --max-time 100ms {url}", url = server.url()).as_str() )); - assert!(&actual.err.contains("nu::shell::io_error")); + assert!(&actual.err.contains("nu::shell::network_failure")); + + #[cfg(not(target_os = "windows"))] + assert!(&actual.err.contains("timed out reading response")); + #[cfg(target_os = "windows")] + assert!(&actual + .err + .contains("did not properly respond after a period of time")); } diff --git a/crates/nu-command/tests/commands/network/http/patch.rs b/crates/nu-command/tests/commands/network/http/patch.rs index 79e6a63096..90788f6769 100644 --- a/crates/nu-command/tests/commands/network/http/patch.rs +++ b/crates/nu-command/tests/commands/network/http/patch.rs @@ -171,18 +171,25 @@ fn http_patch_timeout() { let _mock = server .mock("PATCH", "/") .with_chunked_body(|w| { - thread::sleep(Duration::from_secs(1)); + thread::sleep(Duration::from_secs(10)); w.write_all(b"Delayed response!") }) .create(); let actual = nu!(pipeline( format!( - "http patch --max-time 500ms {url} patchbody", + "http patch --max-time 100ms {url} patchbody", url = server.url() ) .as_str() )); - assert!(&actual.err.contains("nu::shell::io_error")); + assert!(&actual.err.contains("nu::shell::network_failure")); + + #[cfg(not(target_os = "windows"))] + assert!(&actual.err.contains("timed out reading response")); + #[cfg(target_os = "windows")] + assert!(&actual + .err + .contains("did not properly respond after a period of time")); } diff --git a/crates/nu-command/tests/commands/network/http/post.rs b/crates/nu-command/tests/commands/network/http/post.rs index 9d327bf167..2b238573fa 100644 --- a/crates/nu-command/tests/commands/network/http/post.rs +++ b/crates/nu-command/tests/commands/network/http/post.rs @@ -285,18 +285,25 @@ fn http_post_timeout() { let _mock = server .mock("POST", "/") .with_chunked_body(|w| { - thread::sleep(Duration::from_secs(1)); + thread::sleep(Duration::from_secs(10)); w.write_all(b"Delayed response!") }) .create(); let actual = nu!(pipeline( format!( - "http post --max-time 500ms {url} postbody", + "http post --max-time 100ms {url} postbody", url = server.url() ) .as_str() )); - assert!(&actual.err.contains("nu::shell::io_error")); + assert!(&actual.err.contains("nu::shell::network_failure")); + + #[cfg(not(target_os = "windows"))] + assert!(&actual.err.contains("timed out reading response")); + #[cfg(target_os = "windows")] + assert!(&actual + .err + .contains("did not properly respond after a period of time")); } diff --git a/crates/nu-command/tests/commands/network/http/put.rs b/crates/nu-command/tests/commands/network/http/put.rs index 3405c19bbf..41a4bf7848 100644 --- a/crates/nu-command/tests/commands/network/http/put.rs +++ b/crates/nu-command/tests/commands/network/http/put.rs @@ -171,18 +171,25 @@ fn http_put_timeout() { let _mock = server .mock("PUT", "/") .with_chunked_body(|w| { - thread::sleep(Duration::from_secs(1)); + thread::sleep(Duration::from_secs(10)); w.write_all(b"Delayed response!") }) .create(); let actual = nu!(pipeline( format!( - "http put --max-time 500ms {url} putbody", + "http put --max-time 100ms {url} putbody", url = server.url() ) .as_str() )); - assert!(&actual.err.contains("nu::shell::io_error")); + assert!(&actual.err.contains("nu::shell::network_failure")); + + #[cfg(not(target_os = "windows"))] + assert!(&actual.err.contains("timed out reading response")); + #[cfg(target_os = "windows")] + assert!(&actual + .err + .contains("did not properly respond after a period of time")); } diff --git a/crates/nu-command/tests/commands/select.rs b/crates/nu-command/tests/commands/select.rs index 004aa16d6b..534943767f 100644 --- a/crates/nu-command/tests/commands/select.rs +++ b/crates/nu-command/tests/commands/select.rs @@ -63,7 +63,7 @@ fn complex_nested_columns() { fn fails_if_given_unknown_column_name() { let actual = nu!(pipeline( r#" - echo [ + [ [first_name, last_name, rusty_at, type]; [Andrés Robalino '10/11/2013' A] @@ -71,7 +71,6 @@ fn fails_if_given_unknown_column_name() { [Yehuda Katz '10/11/2013' A] ] | select rrusty_at first_name - | length "# )); diff --git a/crates/nu-command/tests/commands/ucp.rs b/crates/nu-command/tests/commands/ucp.rs index e3fa2ca931..7834e91e7c 100644 --- a/crates/nu-command/tests/commands/ucp.rs +++ b/crates/nu-command/tests/commands/ucp.rs @@ -610,9 +610,7 @@ static TEST_COPY_TO_FOLDER: &str = "hello_dir/"; static TEST_COPY_TO_FOLDER_FILE: &str = "hello_dir/hello_world.txt"; static TEST_COPY_FROM_FOLDER: &str = "hello_dir_with_file/"; static TEST_COPY_FROM_FOLDER_FILE: &str = "hello_dir_with_file/hello_world.txt"; -#[cfg(not(target_os = "macos"))] static TEST_COPY_TO_FOLDER_NEW: &str = "hello_dir_new"; -#[cfg(not(target_os = "macos"))] static TEST_COPY_TO_FOLDER_NEW_FILE: &str = "hello_dir_new/hello_world.txt"; #[test] @@ -707,7 +705,6 @@ fn test_cp_multiple_files() { } #[test] -#[cfg(not(target_os = "macos"))] fn test_cp_recurse() { Playground::setup("ucp_test_22", |dirs, sandbox| { // Create the relevant target directories diff --git a/crates/nu-command/tests/commands/url/join.rs b/crates/nu-command/tests/commands/url/join.rs index 92257a41ff..086d230cc4 100644 --- a/crates/nu-command/tests/commands/url/join.rs +++ b/crates/nu-command/tests/commands/url/join.rs @@ -27,7 +27,7 @@ fn url_join_with_only_user() { "password": "", "host": "localhost", "port": "", - } | url join + } | url join "# )); @@ -44,7 +44,7 @@ fn url_join_with_only_pwd() { "password": "pwd", "host": "localhost", "port": "", - } | url join + } | url join "# )); @@ -61,7 +61,7 @@ fn url_join_with_user_and_pwd() { "password": "pwd", "host": "localhost", "port": "", - } | url join + } | url join "# )); @@ -79,7 +79,7 @@ fn url_join_with_query() { "host": "localhost", "query": "par_1=aaa&par_2=bbb" "port": "", - } | url join + } | url join "# )); @@ -411,12 +411,9 @@ fn url_join_with_params_invalid_table() { "host": "localhost", "params": ( [ - ["key", "value"]; - ["par_1", "aaa"], - ["par_2", "bbb"], - ["par_1", "ccc"], - ["par_2", "ddd"], - ] ++ ["not a record"] + { key: foo, value: bar } + "not a record" + ] ), "port": "1234", } | url join diff --git a/crates/nu-command/tests/commands/use_.rs b/crates/nu-command/tests/commands/use_.rs index 2822be83c7..cb5f262e72 100644 --- a/crates/nu-command/tests/commands/use_.rs +++ b/crates/nu-command/tests/commands/use_.rs @@ -284,9 +284,9 @@ fn use_main_def_known_external() { #[test] fn use_main_not_exported() { let inp = &[ - r#"module spam { def main [] { "spam" } }"#, - r#"use spam"#, - r#"spam"#, + r#"module my-super-cool-and-unique-module-name { def main [] { "hi" } }"#, + r#"use my-super-cool-and-unique-module-name"#, + r#"my-super-cool-and-unique-module-name"#, ]; let actual = nu!(&inp.join("; ")); diff --git a/crates/nu-command/tests/format_conversions/nuon.rs b/crates/nu-command/tests/format_conversions/nuon.rs index 8262ee8dfc..e4f6281c01 100644 --- a/crates/nu-command/tests/format_conversions/nuon.rs +++ b/crates/nu-command/tests/format_conversions/nuon.rs @@ -5,9 +5,9 @@ fn to_nuon_correct_compaction() { let actual = nu!( cwd: "tests/fixtures/formats", pipeline( r#" - open appveyor.yml - | to nuon - | str length + open appveyor.yml + | to nuon + | str length | $in > 500 "# )); diff --git a/crates/nu-engine/Cargo.toml b/crates/nu-engine/Cargo.toml index dfe634d078..38752422da 100644 --- a/crates/nu-engine/Cargo.toml +++ b/crates/nu-engine/Cargo.toml @@ -14,12 +14,20 @@ bench = false workspace = true [dependencies] -nu-protocol = { path = "../nu-protocol", features = ["plugin"], version = "0.100.1" } +nu-protocol = { path = "../nu-protocol", version = "0.100.1", default-features = false } nu-path = { path = "../nu-path", version = "0.100.1" } nu-glob = { path = "../nu-glob", version = "0.100.1" } -nu-utils = { path = "../nu-utils", version = "0.100.1" } +nu-utils = { path = "../nu-utils", version = "0.100.1", default-features = false } log = { workspace = true } terminal_size = { workspace = true } [features] -plugin = [] \ No newline at end of file +default = ["os"] +os = [ + "nu-protocol/os", + "nu-utils/os", +] +plugin = [ + "nu-protocol/plugin", + "os", +] diff --git a/crates/nu-engine/src/call_ext.rs b/crates/nu-engine/src/call_ext.rs index d3f36215e6..4473739b35 100644 --- a/crates/nu-engine/src/call_ext.rs +++ b/crates/nu-engine/src/call_ext.rs @@ -121,15 +121,12 @@ impl CallExt for ast::Call { starting_pos: usize, ) -> Result, ShellError> { let stack = &mut stack.use_call_arg_out_dest(); - let mut output = vec![]; - - for result in self.rest_iter_flattened(starting_pos, |expr| { + self.rest_iter_flattened(starting_pos, |expr| { eval_expression::(engine_state, stack, expr) - })? { - output.push(FromValue::from_value(result)?); - } - - Ok(output) + })? + .into_iter() + .map(FromValue::from_value) + .collect() } fn opt( diff --git a/crates/nu-engine/src/closure_eval.rs b/crates/nu-engine/src/closure_eval.rs index b271d90cbe..66c6287cca 100644 --- a/crates/nu-engine/src/closure_eval.rs +++ b/crates/nu-engine/src/closure_eval.rs @@ -88,6 +88,29 @@ impl ClosureEval { } } + pub fn new_preserve_out_dest( + engine_state: &EngineState, + stack: &Stack, + closure: Closure, + ) -> Self { + let engine_state = engine_state.clone(); + let stack = stack.captures_to_stack_preserve_out_dest(closure.captures); + let block = engine_state.get_block(closure.block_id).clone(); + let env_vars = stack.env_vars.clone(); + let env_hidden = stack.env_hidden.clone(); + let eval = get_eval_block_with_early_return(&engine_state); + + Self { + engine_state, + stack, + block, + arg_index: 0, + env_vars, + env_hidden, + eval, + } + } + /// Sets whether to enable debugging when evaluating the closure. /// /// By default, this is controlled by the [`EngineState`] used to create this [`ClosureEval`]. @@ -189,6 +212,22 @@ impl<'a> ClosureEvalOnce<'a> { } } + pub fn new_preserve_out_dest( + engine_state: &'a EngineState, + stack: &Stack, + closure: Closure, + ) -> Self { + let block = engine_state.get_block(closure.block_id); + let eval = get_eval_block_with_early_return(engine_state); + Self { + engine_state, + stack: stack.captures_to_stack_preserve_out_dest(closure.captures), + block, + arg_index: 0, + eval, + } + } + /// Sets whether to enable debugging when evaluating the closure. /// /// By default, this is controlled by the [`EngineState`] used to create this [`ClosureEvalOnce`]. diff --git a/crates/nu-engine/src/command_prelude.rs b/crates/nu-engine/src/command_prelude.rs index e6ddb5fb91..0c5c250a27 100644 --- a/crates/nu-engine/src/command_prelude.rs +++ b/crates/nu-engine/src/command_prelude.rs @@ -3,6 +3,6 @@ pub use nu_protocol::{ ast::CellPath, engine::{Call, Command, EngineState, Stack, StateWorkingSet}, record, ByteStream, ByteStreamType, Category, ErrSpan, Example, IntoInterruptiblePipelineData, - IntoPipelineData, IntoSpanned, PipelineData, Record, ShellError, Signature, Span, Spanned, - SyntaxShape, Type, Value, + IntoPipelineData, IntoSpanned, IntoValue, PipelineData, Record, ShellError, Signature, Span, + Spanned, SyntaxShape, Type, Value, }; diff --git a/crates/nu-engine/src/compile/call.rs b/crates/nu-engine/src/compile/call.rs index 4112e889b1..e58a7b3efe 100644 --- a/crates/nu-engine/src/compile/call.rs +++ b/crates/nu-engine/src/compile/call.rs @@ -20,12 +20,11 @@ pub(crate) fn compile_call( // Check if this call has --help - if so, just redirect to `help` if call.named_iter().any(|(name, _, _)| name.item == "help") { - return compile_help( - working_set, - builder, - decl.name().into_spanned(call.head), - io_reg, - ); + let name = working_set + .find_decl_name(call.decl_id) // check for name in scope + .and_then(|name| std::str::from_utf8(name).ok()) + .unwrap_or(decl.name()); // fall back to decl's name + return compile_help(working_set, builder, name.into_spanned(call.head), io_reg); } // Try to figure out if this is a keyword call like `if`, and handle those specially diff --git a/crates/nu-engine/src/compile/operator.rs b/crates/nu-engine/src/compile/operator.rs index bad206358d..0f7059e027 100644 --- a/crates/nu-engine/src/compile/operator.rs +++ b/crates/nu-engine/src/compile/operator.rs @@ -155,7 +155,7 @@ pub(crate) fn decompose_assignment(assignment: Assignment) -> Option { match assignment { Assignment::Assign => None, Assignment::PlusAssign => Some(Operator::Math(Math::Plus)), - Assignment::AppendAssign => Some(Operator::Math(Math::Append)), + Assignment::ConcatAssign => Some(Operator::Math(Math::Concat)), Assignment::MinusAssign => Some(Operator::Math(Math::Minus)), Assignment::MultiplyAssign => Some(Operator::Math(Math::Multiply)), Assignment::DivideAssign => Some(Operator::Math(Math::Divide)), diff --git a/crates/nu-engine/src/env.rs b/crates/nu-engine/src/env.rs index c4460f4695..2ef7ecc1d9 100644 --- a/crates/nu-engine/src/env.rs +++ b/crates/nu-engine/src/env.rs @@ -11,13 +11,6 @@ use std::{ sync::Arc, }; -#[cfg(windows)] -const ENV_PATH_NAME: &str = "Path"; -#[cfg(windows)] -const ENV_PATH_NAME_SECONDARY: &str = "PATH"; -#[cfg(not(windows))] -const ENV_PATH_NAME: &str = "PATH"; - const ENV_CONVERSIONS: &str = "ENV_CONVERSIONS"; enum ConversionResult { @@ -40,27 +33,33 @@ pub fn convert_env_values(engine_state: &mut EngineState, stack: &Stack) -> Resu let env_vars = engine_state.render_env_vars(); for (name, val) in env_vars { - match get_converted_value(engine_state, stack, name, val, "from_string") { - ConversionResult::Ok(v) => { - let _ = new_scope.insert(name.to_string(), v); - } - ConversionResult::ConversionError(e) => error = error.or(Some(e)), - ConversionResult::CellPathError => { - let _ = new_scope.insert(name.to_string(), val.clone()); + if let Value::String { .. } = val { + // Only run from_string on string values + match get_converted_value(engine_state, stack, name, val, "from_string") { + ConversionResult::Ok(v) => { + let _ = new_scope.insert(name.to_string(), v); + } + ConversionResult::ConversionError(e) => error = error.or(Some(e)), + ConversionResult::CellPathError => { + let _ = new_scope.insert(name.to_string(), val.clone()); + } } + } else { + // Skip values that are already converted (not a string) + let _ = new_scope.insert(name.to_string(), val.clone()); } } #[cfg(not(windows))] { - error = error.or_else(|| ensure_path(&mut new_scope, ENV_PATH_NAME)); + error = error.or_else(|| ensure_path(&mut new_scope, "PATH")); } #[cfg(windows)] { - let first_result = ensure_path(&mut new_scope, ENV_PATH_NAME); + let first_result = ensure_path(&mut new_scope, "Path"); if first_result.is_some() { - let second_result = ensure_path(&mut new_scope, ENV_PATH_NAME_SECONDARY); + let second_result = ensure_path(&mut new_scope, "PATH"); if second_result.is_some() { error = error.or(first_result); @@ -107,7 +106,7 @@ pub fn env_to_string( ConversionResult::CellPathError => match value.coerce_string() { Ok(s) => Ok(s), Err(_) => { - if env_name == ENV_PATH_NAME { + if env_name.to_lowercase() == "path" { // Try to convert PATH/Path list to a string match value { Value::List { vals, .. } => { @@ -213,31 +212,21 @@ pub fn current_dir_const(working_set: &StateWorkingSet) -> Result Result { - let (pathname, pathval) = match stack.get_env_var(engine_state, ENV_PATH_NAME) { - Some(v) => Ok((ENV_PATH_NAME, v)), - None => { - #[cfg(windows)] - match stack.get_env_var(engine_state, ENV_PATH_NAME_SECONDARY) { - Some(v) => Ok((ENV_PATH_NAME_SECONDARY, v)), - None => Err(ShellError::EnvVarNotFoundAtRuntime { - envvar_name: ENV_PATH_NAME_SECONDARY.to_string(), - span, - }), - } - #[cfg(not(windows))] - Err(ShellError::EnvVarNotFoundAtRuntime { - envvar_name: ENV_PATH_NAME.to_string(), - span, - }) - } + let (pathname, pathval) = match stack.get_env_var_insensitive(engine_state, "path") { + Some(v) => Ok((if cfg!(windows) { "Path" } else { "PATH" }, v)), + None => Err(ShellError::EnvVarNotFoundAtRuntime { + envvar_name: if cfg!(windows) { + "Path".to_string() + } else { + "PATH".to_string() + }, + span, + }), }?; env_to_string(pathname, pathval, engine_state, stack) diff --git a/crates/nu-engine/src/eval.rs b/crates/nu-engine/src/eval.rs index fc4a88f83e..0e3917290f 100644 --- a/crates/nu-engine/src/eval.rs +++ b/crates/nu-engine/src/eval.rs @@ -547,9 +547,9 @@ impl Eval for EvalRuntime { let lhs = eval_expression::(engine_state, stack, lhs)?; lhs.div(op_span, &rhs, op_span)? } - Assignment::AppendAssign => { + Assignment::ConcatAssign => { let lhs = eval_expression::(engine_state, stack, lhs)?; - lhs.append(op_span, &rhs, op_span)? + lhs.concat(op_span, &rhs, op_span)? } }; diff --git a/crates/nu-engine/src/eval_ir.rs b/crates/nu-engine/src/eval_ir.rs index 8d809f182e..afcb58a90a 100644 --- a/crates/nu-engine/src/eval_ir.rs +++ b/crates/nu-engine/src/eval_ir.rs @@ -4,11 +4,13 @@ use nu_path::{expand_path_with, AbsolutePathBuf}; use nu_protocol::{ ast::{Bits, Block, Boolean, CellPath, Comparison, Math, Operator}, debugger::DebugContext, - engine::{Argument, Closure, EngineState, ErrorHandler, Matcher, Redirection, Stack}, + engine::{ + Argument, Closure, EngineState, ErrorHandler, Matcher, Redirection, Stack, StateWorkingSet, + }, ir::{Call, DataSlice, Instruction, IrAstRef, IrBlock, Literal, RedirectMode}, - ByteStreamSource, DataSource, DeclId, ErrSpan, Flag, IntoPipelineData, IntoSpanned, ListStream, - OutDest, PipelineData, PipelineMetadata, PositionalArg, Range, Record, RegId, ShellError, - Signals, Signature, Span, Spanned, Type, Value, VarId, ENV_VARIABLE_ID, + DataSource, DeclId, ErrSpan, Flag, IntoPipelineData, IntoSpanned, ListStream, OutDest, + PipelineData, PipelineMetadata, PositionalArg, Range, Record, RegId, ShellError, Signals, + Signature, Span, Spanned, Type, Value, VarId, ENV_VARIABLE_ID, }; use nu_utils::IgnoreCaseExt; @@ -220,17 +222,8 @@ fn eval_ir_block_impl( } Err(err) => { if let Some(error_handler) = ctx.stack.error_handlers.pop(ctx.error_handler_base) { - let fancy_errors = match ctx.engine_state.get_config().error_style { - nu_protocol::ErrorStyle::Fancy => true, - nu_protocol::ErrorStyle::Plain => false, - }; // If an error handler is set, branch there - prepare_error_handler( - ctx, - error_handler, - Some(err.into_spanned(*span)), - fancy_errors, - ); + prepare_error_handler(ctx, error_handler, Some(err.into_spanned(*span))); pc = error_handler.handler_index; } else { // If not, exit the block with the error @@ -255,7 +248,6 @@ fn prepare_error_handler( ctx: &mut EvalContext<'_>, error_handler: ErrorHandler, error: Option>, - fancy_errors: bool, ) { if let Some(reg_id) = error_handler.error_register { if let Some(error) = error { @@ -266,7 +258,7 @@ fn prepare_error_handler( reg_id, error .item - .into_value(error.span, fancy_errors) + .into_value(&StateWorkingSet::new(ctx.engine_state), error.span) .into_pipeline_data(), ); } else { @@ -486,8 +478,9 @@ fn eval_instruction( Ok(Continue) } Instruction::CheckErrRedirected { src } => match ctx.borrow_reg(*src) { + #[cfg(feature = "os")] PipelineData::ByteStream(stream, _) - if matches!(stream.source(), ByteStreamSource::Child(_)) => + if matches!(stream.source(), nu_protocol::ByteStreamSource::Child(_)) => { Ok(Continue) } @@ -521,7 +514,7 @@ fn eval_instruction( span: Some(*span), })?; let is_external = if let PipelineData::ByteStream(stream, ..) = &src { - matches!(stream.source(), ByteStreamSource::Child(..)) + stream.source().is_external() } else { false }; @@ -956,7 +949,7 @@ fn binary_op( }, Operator::Math(mat) => match mat { Math::Plus => lhs_val.add(op_span, &rhs_val, span)?, - Math::Append => lhs_val.append(op_span, &rhs_val, span)?, + Math::Concat => lhs_val.concat(op_span, &rhs_val, span)?, Math::Minus => lhs_val.sub(op_span, &rhs_val, span)?, Math::Multiply => lhs_val.mul(op_span, &rhs_val, span)?, Math::Divide => lhs_val.div(op_span, &rhs_val, span)?, diff --git a/crates/nu-engine/src/scope.rs b/crates/nu-engine/src/scope.rs index 62acd6a9a7..8774a8996b 100644 --- a/crates/nu-engine/src/scope.rs +++ b/crates/nu-engine/src/scope.rs @@ -56,11 +56,12 @@ impl<'e, 's> ScopeData<'e, 's> { let var_type = Value::string(var.ty.to_string(), span); let is_const = Value::bool(var.const_val.is_some(), span); - let var_value = if let Ok(val) = self.stack.get_var(**var_id, span) { - val - } else { - Value::nothing(span) - }; + let var_value = self + .stack + .get_var(**var_id, span) + .ok() + .or(var.const_val.clone()) + .unwrap_or(Value::nothing(span)); let var_id_val = Value::int(var_id.get() as i64, span); @@ -488,6 +489,7 @@ impl<'e, 's> ScopeData<'e, 's> { "description" => Value::string(module_desc, span), "extra_description" => Value::string(module_extra_desc, span), "module_id" => Value::int(module_id.get() as i64, span), + "file" => Value::string(module.file.clone().map_or("unknown".to_string(), |(p, _)| p.path().to_string_lossy().to_string()), span), }, span, ) diff --git a/crates/nu-explore/Cargo.toml b/crates/nu-explore/Cargo.toml index 6ea09e3f13..e201262750 100644 --- a/crates/nu-explore/Cargo.toml +++ b/crates/nu-explore/Cargo.toml @@ -27,7 +27,6 @@ nu-pretty-hex = { path = "../nu-pretty-hex", version = "0.100.1" } anyhow = { workspace = true } log = { workspace = true } -terminal_size = { workspace = true } strip-ansi-escapes = { workspace = true } crossterm = { workspace = true } ratatui = { workspace = true } diff --git a/crates/nu-explore/src/commands/help.rs b/crates/nu-explore/src/commands/help.rs index 63c24969ec..3083b83a9b 100644 --- a/crates/nu-explore/src/commands/help.rs +++ b/crates/nu-explore/src/commands/help.rs @@ -33,13 +33,14 @@ Launch Explore by piping data into it: {} Move around: Use the cursor keys Drill down into records+tables: Press to select a cell, move around with cursor keys, press again - Go back/up a level: Press + Go back/up a level: Press or "q" Transpose (flip rows+columns): Press "t" Expand (show all nested data): Press "e" Open this help page : Type ":help" then Open an interactive REPL: Type ":try" then - Scroll up/down: Use the "Page Up" and "Page Down" keys - Exit Explore: Type ":q" then , or Ctrl+D. Alternately, press until Explore exits + Scroll up: Press "Page Up", Ctrl+B, or Alt+V + Scroll down: Press "Page Down", Ctrl+F, or Ctrl+V + Exit Explore: Type ":q" then , or Ctrl+D. Alternately, press or "q" until Explore exits {} Most commands support search via regular expressions. diff --git a/crates/nu-explore/src/commands/nu.rs b/crates/nu-explore/src/commands/nu.rs index 8b5bc71f69..4bdd55318b 100644 --- a/crates/nu-explore/src/commands/nu.rs +++ b/crates/nu-explore/src/commands/nu.rs @@ -93,7 +93,7 @@ impl View for NuView { layout: &Layout, info: &mut crate::pager::ViewInfo, key: crossterm::event::KeyEvent, - ) -> Option { + ) -> crate::pager::Transition { match self { NuView::Records(v) => v.handle_input(engine_state, stack, layout, info, key), NuView::Preview(v) => v.handle_input(engine_state, stack, layout, info, key), diff --git a/crates/nu-explore/src/lib.rs b/crates/nu-explore/src/lib.rs index d645572c89..a6d4161631 100644 --- a/crates/nu-explore/src/lib.rs +++ b/crates/nu-explore/src/lib.rs @@ -9,6 +9,7 @@ mod views; use anyhow::Result; use commands::{ExpandCmd, HelpCmd, NuCmd, QuitCmd, TableCmd, TryCmd}; +use crossterm::terminal::size; pub use default_context::add_explore_context; pub use explore::Explore; use explore::ExploreConfig; @@ -19,7 +20,6 @@ use nu_protocol::{ }; use pager::{Page, Pager, PagerConfig}; use registry::CommandRegistry; -use terminal_size::{Height, Width}; use views::{BinaryView, Orientation, Preview, RecordView}; mod util { @@ -80,7 +80,7 @@ fn create_record_view( } if config.tail { - if let Some((Width(w), Height(h))) = terminal_size::terminal_size() { + if let Ok((w, h)) = size() { view.tail(w, h); } } diff --git a/crates/nu-explore/src/pager/mod.rs b/crates/nu-explore/src/pager/mod.rs index 2141f89cec..2d4da2e1a7 100644 --- a/crates/nu-explore/src/pager/mod.rs +++ b/crates/nu-explore/src/pager/mod.rs @@ -128,10 +128,17 @@ impl<'a> Pager<'a> { #[derive(Debug, Clone)] pub enum Transition { - // TODO: should we add a noop transition instead of doing Option everywhere? Ok, Exit, Cmd(String), + None, +} + +#[derive(Debug, Clone)] +pub enum StatusTopOrEnd { + Top, + End, + None, } #[derive(Debug, Clone)] @@ -212,34 +219,32 @@ fn render_ui( view_stack.curr_view.as_mut().map(|p| &mut p.view), ); - if let Some(transition) = transition { - let (exit, cmd_name) = react_to_event_result( - transition, - engine_state, - &commands, - pager, - &mut view_stack, - stack, - info, - ); + let (exit, cmd_name) = react_to_event_result( + transition, + engine_state, + &commands, + pager, + &mut view_stack, + stack, + info, + ); - if let Some(value) = exit { - break Ok(value); + if let Some(value) = exit { + break Ok(value); + } + + if !cmd_name.is_empty() { + if let Some(r) = info.report.as_mut() { + r.message = cmd_name; + r.level = Severity::Success; + } else { + info.report = Some(Report::success(cmd_name)); } - if !cmd_name.is_empty() { - if let Some(r) = info.report.as_mut() { - r.message = cmd_name; - r.level = Severity::Success; - } else { - info.report = Some(Report::success(cmd_name)); - } - - let info = info.clone(); - term.draw(|f| { - draw_info(f, pager, info); - })?; - } + let info = info.clone(); + term.draw(|f| { + draw_info(f, pager, info); + })?; } if pager.cmd_buf.run_cmd { @@ -319,6 +324,7 @@ fn react_to_event_result( } } } + Transition::None => (None, String::default()), } } @@ -419,6 +425,7 @@ fn run_command( Transition::Ok => Ok(CmdResult::new(false, false, String::new())), Transition::Exit => Ok(CmdResult::new(true, false, String::new())), Transition::Cmd { .. } => todo!("not used so far"), + Transition::None => panic!("Transition::None not expected from command.react()"), } } Command::View { mut cmd, stackable } => { @@ -617,17 +624,17 @@ fn handle_events( search: &mut SearchBuf, command: &mut CommandBuf, mut view: Option<&mut V>, -) -> Option { +) -> Transition { // We are only interested in Pressed events; // It's crucial because there are cases where terminal MIGHT produce false events; // 2 events 1 for release 1 for press. // Want to react only on 1 of them so we do. let mut key = match events.next_key_press() { Ok(Some(key)) => key, - Ok(None) => return None, + Ok(None) => return Transition::None, Err(e) => { log::error!("Failed to read key event: {e}"); - return None; + return Transition::None; } }; @@ -647,15 +654,15 @@ fn handle_events( view.as_deref_mut(), key, ); - if result.is_some() { + if !matches!(result, Transition::None) { return result; } match events.try_next_key_press() { Ok(Some(next_key)) => key = next_key, - Ok(None) => return None, + Ok(None) => return Transition::None, Err(e) => { log::error!("Failed to peek key event: {e}"); - return None; + return Transition::None; } } } @@ -671,29 +678,29 @@ fn handle_event( command: &mut CommandBuf, mut view: Option<&mut V>, key: KeyEvent, -) -> Option { +) -> Transition { if handle_exit_key_event(&key) { - return Some(Transition::Exit); + return Transition::Exit; } if handle_general_key_events1(&key, search, command, view.as_deref_mut()) { - return None; + return Transition::None; } if let Some(view) = &mut view { let t = view.handle_input(engine_state, stack, layout, info, key); match t { - Some(Transition::Exit) => return Some(Transition::Ok), - Some(Transition::Cmd(cmd)) => return Some(Transition::Cmd(cmd)), - Some(Transition::Ok) => return None, - None => {} + Transition::Exit => return Transition::Ok, + Transition::Cmd(cmd) => return Transition::Cmd(cmd), + Transition::Ok => return Transition::None, + Transition::None => {} } } // was not handled so we must check our default controls handle_general_key_events2(&key, search, command, view, info); - None + Transition::None } fn handle_exit_key_event(key: &KeyEvent) -> bool { diff --git a/crates/nu-explore/src/views/binary/mod.rs b/crates/nu-explore/src/views/binary/mod.rs index 2a35b7c180..a9ba47d650 100644 --- a/crates/nu-explore/src/views/binary/mod.rs +++ b/crates/nu-explore/src/views/binary/mod.rs @@ -2,7 +2,7 @@ mod binary_widget; -use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; +use crossterm::event::KeyEvent; use nu_protocol::{ engine::{EngineState, Stack}, Value, @@ -21,7 +21,7 @@ use crate::{ use self::binary_widget::{BinarySettings, BinaryStyle, BinaryWidget}; -use super::{cursor::WindowCursor2D, Layout, View, ViewConfig}; +use super::{cursor::CursorMoveHandler, cursor::WindowCursor2D, Layout, View, ViewConfig}; /// An interactive view that displays binary data in a hex dump format. /// Not finished; many aspects are still WIP. @@ -66,15 +66,14 @@ impl View for BinaryView { _: &Layout, info: &mut ViewInfo, key: KeyEvent, - ) -> Option { - let result = handle_event_view_mode(self, &key); - - if matches!(&result, Some(Transition::Ok)) { + ) -> Transition { + // currently only handle_enter() in crates/nu-explore/src/views/record/mod.rs raises an Err() + if let Ok((Transition::Ok, ..)) = self.handle_input_key(&key) { let report = create_report(self.cursor); info.status = Some(report); } - None + Transition::None } fn collect_data(&self) -> Vec { @@ -93,6 +92,12 @@ impl View for BinaryView { } } +impl CursorMoveHandler for BinaryView { + fn get_cursor(&mut self) -> &mut WindowCursor2D { + &mut self.cursor + } +} + fn create_binary_widget(v: &BinaryView) -> BinaryWidget<'_> { let start_line = v.cursor.window_origin().row; let count_elements = @@ -106,73 +111,6 @@ fn create_binary_widget(v: &BinaryView) -> BinaryWidget<'_> { w } -fn handle_event_view_mode(view: &mut BinaryView, key: &KeyEvent) -> Option { - match key { - KeyEvent { - code: KeyCode::Char('u'), - modifiers: KeyModifiers::CONTROL, - .. - } - | KeyEvent { - code: KeyCode::PageUp, - .. - } => { - view.cursor.prev_row_page(); - - return Some(Transition::Ok); - } - KeyEvent { - code: KeyCode::Char('d'), - modifiers: KeyModifiers::CONTROL, - .. - } - | KeyEvent { - code: KeyCode::PageDown, - .. - } => { - view.cursor.next_row_page(); - - return Some(Transition::Ok); - } - _ => {} - } - - match key.code { - KeyCode::Esc => Some(Transition::Exit), - KeyCode::Up | KeyCode::Char('k') => { - view.cursor.prev_row_i(); - - Some(Transition::Ok) - } - KeyCode::Down | KeyCode::Char('j') => { - view.cursor.next_row_i(); - - Some(Transition::Ok) - } - KeyCode::Left | KeyCode::Char('h') => { - view.cursor.prev_column_i(); - - Some(Transition::Ok) - } - KeyCode::Right | KeyCode::Char('l') => { - view.cursor.next_column_i(); - - Some(Transition::Ok) - } - KeyCode::Home | KeyCode::Char('g') => { - view.cursor.row_move_to_start(); - - Some(Transition::Ok) - } - KeyCode::End | KeyCode::Char('G') => { - view.cursor.row_move_to_end(); - - Some(Transition::Ok) - } - _ => None, - } -} - fn settings_from_config(config: &ExploreConfig) -> Settings { // Most of this is hardcoded for now, add it to the config later if needed Settings { diff --git a/crates/nu-explore/src/views/cursor/mod.rs b/crates/nu-explore/src/views/cursor/mod.rs index 8044b59aca..4c37b70977 100644 --- a/crates/nu-explore/src/views/cursor/mod.rs +++ b/crates/nu-explore/src/views/cursor/mod.rs @@ -3,7 +3,7 @@ mod window_cursor_2d; use anyhow::{bail, Result}; pub use window_cursor::WindowCursor; -pub use window_cursor_2d::{Position, WindowCursor2D}; +pub use window_cursor_2d::{CursorMoveHandler, Position, WindowCursor2D}; /// A 1-dimensional cursor to track a position from 0 to N /// diff --git a/crates/nu-explore/src/views/cursor/window_cursor_2d.rs b/crates/nu-explore/src/views/cursor/window_cursor_2d.rs index e22ec09ce3..8dfafcd1a4 100644 --- a/crates/nu-explore/src/views/cursor/window_cursor_2d.rs +++ b/crates/nu-explore/src/views/cursor/window_cursor_2d.rs @@ -1,3 +1,6 @@ +use crate::pager::{StatusTopOrEnd, Transition}; +use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; + use super::WindowCursor; use anyhow::Result; @@ -170,3 +173,133 @@ impl WindowCursor2D { .set_window_start_position(self.y.window_starts_at() - 1) } } + +pub trait CursorMoveHandler { + fn get_cursor(&mut self) -> &mut WindowCursor2D; + + // standard handle_EVENT handlers that can be overwritten + fn handle_enter(&mut self) -> Result { + Ok(Transition::None) + } + fn handle_esc(&mut self) -> Transition { + Transition::Exit + } + fn handle_expand(&mut self) -> Transition { + Transition::None + } + fn handle_left(&mut self) { + self.get_cursor().prev_column_i() + } + fn handle_right(&mut self) { + self.get_cursor().next_column_i() + } + fn handle_up(&mut self) { + self.get_cursor().prev_row_i() + } + fn handle_down(&mut self) { + self.get_cursor().next_row_i() + } + fn handle_transpose(&mut self) -> Transition { + Transition::None + } + + // top-level event handler should not be overwritten + fn handle_input_key(&mut self, key: &KeyEvent) -> Result<(Transition, StatusTopOrEnd)> { + let key_combo_status = match key { + // PageUp supports Vi (Ctrl+b) and Emacs (Alt+v) keybindings + KeyEvent { + modifiers: KeyModifiers::CONTROL, + code: KeyCode::Char('b'), + .. + } + | KeyEvent { + modifiers: KeyModifiers::ALT, + code: KeyCode::Char('v'), + .. + } + | KeyEvent { + code: KeyCode::PageUp, + .. + } => { + self.get_cursor().prev_row_page(); + StatusTopOrEnd::Top + } + // PageDown supports Vi (Ctrl+f) and Emacs (Ctrl+v) keybindings + KeyEvent { + modifiers: KeyModifiers::CONTROL, + code: KeyCode::Char('f'), + .. + } + | KeyEvent { + modifiers: KeyModifiers::CONTROL, + code: KeyCode::Char('v'), + .. + } + | KeyEvent { + code: KeyCode::PageDown, + .. + } => { + self.get_cursor().next_row_page(); + self.get_cursor().prev_row(); + StatusTopOrEnd::End + } + // Up support Emacs (Ctrl+p) keybinding + KeyEvent { + modifiers: KeyModifiers::CONTROL, + code: KeyCode::Char('p'), + .. + } => { + self.handle_up(); + StatusTopOrEnd::Top + } + // Down support Emacs (Ctrl+n) keybinding + KeyEvent { + modifiers: KeyModifiers::CONTROL, + code: KeyCode::Char('n'), + .. + } => { + self.handle_down(); + StatusTopOrEnd::End + } + _ => StatusTopOrEnd::None, + }; + match key_combo_status { + StatusTopOrEnd::Top | StatusTopOrEnd::End => { + return Ok((Transition::Ok, key_combo_status)); + } + _ => {} // not page up or page down, so don't return; continue to next match block + } + + match key.code { + KeyCode::Char('q') | KeyCode::Esc => Ok((self.handle_esc(), StatusTopOrEnd::None)), + KeyCode::Char('i') | KeyCode::Enter => Ok((self.handle_enter()?, StatusTopOrEnd::None)), + KeyCode::Char('t') => Ok((self.handle_transpose(), StatusTopOrEnd::None)), + KeyCode::Char('e') => Ok((self.handle_expand(), StatusTopOrEnd::None)), + KeyCode::Up | KeyCode::Char('k') => { + self.handle_up(); + Ok((Transition::Ok, StatusTopOrEnd::Top)) + } + KeyCode::Down | KeyCode::Char('j') => { + self.handle_down(); + Ok((Transition::Ok, StatusTopOrEnd::End)) + } + KeyCode::Left | KeyCode::Char('h') => { + self.handle_left(); + Ok((Transition::Ok, StatusTopOrEnd::None)) + } + KeyCode::Right | KeyCode::Char('l') => { + self.handle_right(); + Ok((Transition::Ok, StatusTopOrEnd::None)) + } + KeyCode::Home | KeyCode::Char('g') => { + self.get_cursor().row_move_to_start(); + Ok((Transition::Ok, StatusTopOrEnd::Top)) + } + KeyCode::End | KeyCode::Char('G') => { + self.get_cursor().row_move_to_end(); + Ok((Transition::Ok, StatusTopOrEnd::End)) + } + _ => Ok((Transition::None, StatusTopOrEnd::None)), + } + } +} diff --git a/crates/nu-explore/src/views/mod.rs b/crates/nu-explore/src/views/mod.rs index 589134016b..7147a74807 100644 --- a/crates/nu-explore/src/views/mod.rs +++ b/crates/nu-explore/src/views/mod.rs @@ -89,7 +89,7 @@ pub trait View { layout: &Layout, info: &mut ViewInfo, key: KeyEvent, - ) -> Option; + ) -> Transition; fn show_data(&mut self, _: usize) -> bool { false @@ -116,7 +116,7 @@ impl View for Box { layout: &Layout, info: &mut ViewInfo, key: KeyEvent, - ) -> Option { + ) -> Transition { self.as_mut() .handle_input(engine_state, stack, layout, info, key) } diff --git a/crates/nu-explore/src/views/preview.rs b/crates/nu-explore/src/views/preview.rs index de4d710113..6b62bc0d0f 100644 --- a/crates/nu-explore/src/views/preview.rs +++ b/crates/nu-explore/src/views/preview.rs @@ -1,11 +1,12 @@ use super::{ - colored_text_widget::ColoredTextWidget, cursor::WindowCursor2D, Layout, View, ViewConfig, + colored_text_widget::ColoredTextWidget, cursor::CursorMoveHandler, cursor::WindowCursor2D, + Layout, View, ViewConfig, }; use crate::{ nu_common::{NuSpan, NuText}, - pager::{report::Report, Frame, Transition, ViewInfo}, + pager::{report::Report, Frame, StatusTopOrEnd, Transition, ViewInfo}, }; -use crossterm::event::{KeyCode, KeyEvent}; +use crossterm::event::KeyEvent; use nu_color_config::TextStyle; use nu_protocol::{ engine::{EngineState, Stack}, @@ -65,58 +66,17 @@ impl View for Preview { _: &Layout, info: &mut ViewInfo, // add this arg to draw too? key: KeyEvent, - ) -> Option { - match key.code { - KeyCode::Left => { - self.cursor - .prev_column_by(max(1, self.cursor.window_width_in_columns() / 2)); - - Some(Transition::Ok) + ) -> Transition { + match self.handle_input_key(&key) { + Ok((transition, status_top_or_end)) => { + match status_top_or_end { + StatusTopOrEnd::Top => set_status_top(self, info), + StatusTopOrEnd::End => set_status_end(self, info), + _ => {} + } + transition } - KeyCode::Right => { - self.cursor - .next_column_by(max(1, self.cursor.window_width_in_columns() / 2)); - - Some(Transition::Ok) - } - KeyCode::Up => { - self.cursor.prev_row_i(); - set_status_top(self, info); - - Some(Transition::Ok) - } - KeyCode::Down => { - self.cursor.next_row_i(); - set_status_end(self, info); - - Some(Transition::Ok) - } - KeyCode::PageUp => { - self.cursor.prev_row_page(); - set_status_top(self, info); - - Some(Transition::Ok) - } - KeyCode::PageDown => { - self.cursor.next_row_page(); - set_status_end(self, info); - - Some(Transition::Ok) - } - KeyCode::Home => { - self.cursor.row_move_to_start(); - set_status_top(self, info); - - Some(Transition::Ok) - } - KeyCode::End => { - self.cursor.row_move_to_end(); - set_status_end(self, info); - - Some(Transition::Ok) - } - KeyCode::Esc => Some(Transition::Exit), - _ => None, + _ => Transition::None, // currently only handle_enter() in crates/nu-explore/src/views/record/mod.rs raises an Err() } } @@ -147,6 +107,20 @@ impl View for Preview { } } +impl CursorMoveHandler for Preview { + fn get_cursor(&mut self) -> &mut WindowCursor2D { + &mut self.cursor + } + fn handle_left(&mut self) { + self.cursor + .prev_column_by(max(1, self.cursor.window_width_in_columns() / 2)); + } + fn handle_right(&mut self) { + self.cursor + .next_column_by(max(1, self.cursor.window_width_in_columns() / 2)); + } +} + fn set_status_end(view: &Preview, info: &mut ViewInfo) { if view.cursor.row() + 1 == view.cursor.row_limit() { info.status = Some(Report::info("END")); diff --git a/crates/nu-explore/src/views/record/mod.rs b/crates/nu-explore/src/views/record/mod.rs index a4e86a8fad..e003348258 100644 --- a/crates/nu-explore/src/views/record/mod.rs +++ b/crates/nu-explore/src/views/record/mod.rs @@ -2,7 +2,7 @@ mod table_widget; use self::table_widget::{TableWidget, TableWidgetState}; use super::{ - cursor::{Position, WindowCursor2D}, + cursor::{CursorMoveHandler, Position, WindowCursor2D}, util::{make_styled_string, nu_style_to_tui}, Layout, View, ViewConfig, }; @@ -16,7 +16,7 @@ use crate::{ views::ElementInfo, }; use anyhow::Result; -use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; +use crossterm::event::KeyEvent; use nu_color_config::StyleComputer; use nu_protocol::{ engine::{EngineState, Stack}, @@ -213,26 +213,21 @@ impl View for RecordView { _: &Layout, info: &mut ViewInfo, key: KeyEvent, - ) -> Option { - let result = match self.mode { - UIMode::View => Ok(handle_key_event_view_mode(self, &key)), - UIMode::Cursor => handle_key_event_cursor_mode(self, &key), - }; - - match result { - Ok(result) => { - if matches!(&result, Some(Transition::Ok) | Some(Transition::Cmd { .. })) { + ) -> Transition { + match self.handle_input_key(&key) { + Ok((transition, ..)) => { + if matches!(&transition, Transition::Ok | Transition::Cmd { .. }) { let report = self.create_records_report(); info.status = Some(report); } - result + transition } Err(e) => { log::error!("Error handling input in RecordView: {e}"); let report = Report::message(e.to_string(), Severity::Err); info.status = Some(report); - None + Transition::None } } } @@ -372,188 +367,94 @@ impl RecordLayer { } } -fn handle_key_event_view_mode(view: &mut RecordView, key: &KeyEvent) -> Option { - match key { - KeyEvent { - code: KeyCode::Char('u'), - modifiers: KeyModifiers::CONTROL, - .. - } - | KeyEvent { - code: KeyCode::PageUp, - .. - } => { - view.get_top_layer_mut().cursor.prev_row_page(); - - return Some(Transition::Ok); - } - KeyEvent { - code: KeyCode::Char('d'), - modifiers: KeyModifiers::CONTROL, - .. - } - | KeyEvent { - code: KeyCode::PageDown, - .. - } => { - view.get_top_layer_mut().cursor.next_row_page(); - - return Some(Transition::Ok); - } - _ => {} +impl CursorMoveHandler for RecordView { + fn get_cursor(&mut self) -> &mut WindowCursor2D { + &mut self.get_top_layer_mut().cursor } + fn handle_enter(&mut self) -> Result { + match self.mode { + UIMode::View => self.set_cursor_mode(), + UIMode::Cursor => { + let value = self.get_current_value(); - match key.code { - KeyCode::Esc => { - if view.layer_stack.len() > 1 { - view.layer_stack.pop(); - view.mode = UIMode::Cursor; + // ...but it only makes sense to drill down into a few types of values + if !matches!( + value, + Value::Record { .. } | Value::List { .. } | Value::Custom { .. } + ) { + return Ok(Transition::None); + } - Some(Transition::Ok) - } else { - Some(Transition::Exit) + let is_record = matches!(value, Value::Record { .. }); + let next_layer = create_layer(value.clone())?; + push_layer(self, next_layer); + + if is_record { + self.set_top_layer_orientation(Orientation::Left); + } else { + self.set_top_layer_orientation(self.orientation); + } } } - KeyCode::Char('i') | KeyCode::Enter => { - view.set_cursor_mode(); - Some(Transition::Ok) - } - KeyCode::Char('t') => { - view.transpose(); - - Some(Transition::Ok) - } - KeyCode::Char('e') => Some(Transition::Cmd(String::from("expand"))), - KeyCode::Up | KeyCode::Char('k') => { - view.get_top_layer_mut().cursor.prev_row_i(); - - Some(Transition::Ok) - } - KeyCode::Down | KeyCode::Char('j') => { - view.get_top_layer_mut().cursor.next_row_i(); - - Some(Transition::Ok) - } - KeyCode::Left | KeyCode::Char('h') => { - view.get_top_layer_mut().cursor.prev_column_i(); - - Some(Transition::Ok) - } - KeyCode::Right | KeyCode::Char('l') => { - view.get_top_layer_mut().cursor.next_column_i(); - - Some(Transition::Ok) - } - KeyCode::Home | KeyCode::Char('g') => { - view.get_top_layer_mut().cursor.row_move_to_start(); - - Some(Transition::Ok) - } - KeyCode::End | KeyCode::Char('G') => { - view.get_top_layer_mut().cursor.row_move_to_end(); - - Some(Transition::Ok) - } - _ => None, + Ok(Transition::Ok) } -} - -fn handle_key_event_cursor_mode( - view: &mut RecordView, - key: &KeyEvent, -) -> Result> { - match key { - KeyEvent { - code: KeyCode::Char('u'), - modifiers: KeyModifiers::CONTROL, - .. + fn handle_esc(&mut self) -> Transition { + match self.mode { + UIMode::View => { + if self.layer_stack.len() > 1 { + self.layer_stack.pop(); + self.mode = UIMode::Cursor; + } else { + return Transition::Exit; + } + } + UIMode::Cursor => self.set_view_mode(), } - | KeyEvent { - code: KeyCode::PageUp, - .. - } => { - view.get_top_layer_mut().cursor.prev_row_page(); - - return Ok(Some(Transition::Ok)); - } - KeyEvent { - code: KeyCode::Char('d'), - modifiers: KeyModifiers::CONTROL, - .. - } - | KeyEvent { - code: KeyCode::PageDown, - .. - } => { - view.get_top_layer_mut().cursor.next_row_page(); - - return Ok(Some(Transition::Ok)); - } - _ => {} + Transition::Ok } - - match key.code { - KeyCode::Esc => { - view.set_view_mode(); - - Ok(Some(Transition::Ok)) + fn handle_expand(&mut self) -> Transition { + match self.mode { + UIMode::View => Transition::Cmd(String::from("expand")), + _ => Transition::None, } - KeyCode::Up | KeyCode::Char('k') => { - view.get_top_layer_mut().cursor.prev_row(); + } + fn handle_transpose(&mut self) -> Transition { + match self.mode { + UIMode::View => { + self.transpose(); - Ok(Some(Transition::Ok)) - } - KeyCode::Down | KeyCode::Char('j') => { - view.get_top_layer_mut().cursor.next_row(); - - Ok(Some(Transition::Ok)) - } - KeyCode::Left | KeyCode::Char('h') => { - view.get_top_layer_mut().cursor.prev_column(); - - Ok(Some(Transition::Ok)) - } - KeyCode::Right | KeyCode::Char('l') => { - view.get_top_layer_mut().cursor.next_column(); - - Ok(Some(Transition::Ok)) - } - KeyCode::Home | KeyCode::Char('g') => { - view.get_top_layer_mut().cursor.row_move_to_start(); - - Ok(Some(Transition::Ok)) - } - KeyCode::End | KeyCode::Char('G') => { - view.get_top_layer_mut().cursor.row_move_to_end(); - - Ok(Some(Transition::Ok)) - } - // Try to "drill down" into the selected value - KeyCode::Enter => { - let value = view.get_current_value(); - - // ...but it only makes sense to drill down into a few types of values - if !matches!( - value, - Value::Record { .. } | Value::List { .. } | Value::Custom { .. } - ) { - return Ok(None); + Transition::Ok } - - let is_record = matches!(value, Value::Record { .. }); - let next_layer = create_layer(value.clone())?; - push_layer(view, next_layer); - - if is_record { - view.set_top_layer_orientation(Orientation::Left); - } else { - view.set_top_layer_orientation(view.orientation); - } - - Ok(Some(Transition::Ok)) + _ => Transition::None, + } + } + // for these, copy standard CursorMoveHandler for UIMode::View, but use special handling for UIMode::Cursor + // NOTE: https://stackoverflow.com/a/31462293/2016290 says there's plans for Rust to allow calling super functions, + // but not yet, and since they're all one line, it seems simpler to copy than make a lot of helper functions + fn handle_left(&mut self) { + match self.mode { + UIMode::View => self.get_top_layer_mut().cursor.prev_column_i(), + _ => self.get_top_layer_mut().cursor.prev_column(), + } + } + fn handle_right(&mut self) { + match self.mode { + UIMode::View => self.get_top_layer_mut().cursor.next_column_i(), + _ => self.get_top_layer_mut().cursor.next_column(), + } + } + fn handle_up(&mut self) { + match self.mode { + UIMode::View => self.get_top_layer_mut().cursor.prev_row_i(), + _ => self.get_top_layer_mut().cursor.prev_row(), + } + } + fn handle_down(&mut self) { + match self.mode { + UIMode::View => self.get_top_layer_mut().cursor.next_row_i(), + _ => self.get_top_layer_mut().cursor.next_row(), } - _ => Ok(None), } } diff --git a/crates/nu-explore/src/views/try.rs b/crates/nu-explore/src/views/try.rs index 099e792e38..6438909b5b 100644 --- a/crates/nu-explore/src/views/try.rs +++ b/crates/nu-explore/src/views/try.rs @@ -149,7 +149,7 @@ impl View for TryView { layout: &Layout, info: &mut ViewInfo, key: KeyEvent, - ) -> Option { + ) -> Transition { if self.view_mode { let table = self .table @@ -160,28 +160,28 @@ impl View for TryView { if was_at_the_top && matches!(key.code, KeyCode::Up | KeyCode::PageUp) { self.view_mode = false; - return Some(Transition::Ok); + return Transition::Ok; } if matches!(key.code, KeyCode::Tab) { self.view_mode = false; - return Some(Transition::Ok); + return Transition::Ok; } let result = table.handle_input(engine_state, stack, layout, info, key); return match result { - Some(Transition::Ok | Transition::Cmd { .. }) => Some(Transition::Ok), - Some(Transition::Exit) => { + Transition::Ok | Transition::Cmd { .. } => Transition::Ok, + Transition::Exit => { self.view_mode = false; - Some(Transition::Ok) + Transition::Ok } - None => None, + Transition::None => Transition::None, }; } match &key.code { - KeyCode::Esc => Some(Transition::Exit), + KeyCode::Esc => Transition::Exit, KeyCode::Backspace => { if !self.command.is_empty() { self.command.pop(); @@ -194,7 +194,7 @@ impl View for TryView { } } - Some(Transition::Ok) + Transition::Ok } KeyCode::Char(c) => { self.command.push(*c); @@ -206,14 +206,14 @@ impl View for TryView { } } - Some(Transition::Ok) + Transition::Ok } KeyCode::Down | KeyCode::Tab => { if self.table.is_some() { self.view_mode = true; } - Some(Transition::Ok) + Transition::Ok } KeyCode::Enter => { match self.try_run(engine_state, stack) { @@ -221,9 +221,9 @@ impl View for TryView { Err(err) => info.report = Some(Report::error(format!("Error: {err}"))), } - Some(Transition::Ok) + Transition::Ok } - _ => None, + _ => Transition::None, } } diff --git a/crates/nu-lsp/src/diagnostics.rs b/crates/nu-lsp/src/diagnostics.rs index e1ae63dc59..53b47bd2f0 100644 --- a/crates/nu-lsp/src/diagnostics.rs +++ b/crates/nu-lsp/src/diagnostics.rs @@ -7,7 +7,7 @@ use miette::{IntoDiagnostic, Result}; use nu_parser::parse; use nu_protocol::{ engine::{EngineState, StateWorkingSet}, - Value, + Span, Value, }; impl LanguageServer { @@ -28,6 +28,7 @@ impl LanguageServer { let contents = rope_of_file.bytes().collect::>(); let offset = working_set.next_span_start(); + working_set.files.push(file_path.into(), Span::unknown())?; parse( &mut working_set, Some(&file_path.to_string_lossy()), diff --git a/crates/nu-lsp/src/lib.rs b/crates/nu-lsp/src/lib.rs index 0fe100c6b4..3e12a24b2a 100644 --- a/crates/nu-lsp/src/lib.rs +++ b/crates/nu-lsp/src/lib.rs @@ -274,6 +274,9 @@ impl LanguageServer { // TODO: think about passing down the rope into the working_set let contents = file.bytes().collect::>(); + let _ = working_set + .files + .push(file_path.as_ref().into(), Span::unknown()); let block = parse(working_set, Some(&file_path), &contents, false); let flattened = flatten_block(working_set, &block); @@ -576,7 +579,6 @@ impl LanguageServer { } Id::Value(shape) => { let hover = String::from(match shape { - FlatShape::And => "and", FlatShape::Binary => "binary", FlatShape::Block => "block", FlatShape::Bool => "bool", diff --git a/crates/nu-parser/Cargo.toml b/crates/nu-parser/Cargo.toml index bfef11acb2..0c15a327e6 100644 --- a/crates/nu-parser/Cargo.toml +++ b/crates/nu-parser/Cargo.toml @@ -15,11 +15,11 @@ bench = false workspace = true [dependencies] -nu-engine = { path = "../nu-engine", version = "0.100.1" } +nu-engine = { path = "../nu-engine", version = "0.100.1", default-features = false } nu-path = { path = "../nu-path", version = "0.100.1" } nu-plugin-engine = { path = "../nu-plugin-engine", optional = true, version = "0.100.1" } -nu-protocol = { path = "../nu-protocol", version = "0.100.1" } -nu-utils = { path = "../nu-utils", version = "0.100.1" } +nu-protocol = { path = "../nu-protocol", version = "0.100.1", default-features = false } +nu-utils = { path = "../nu-utils", version = "0.100.1", default-features = false } bytesize = { workspace = true } chrono = { default-features = false, features = ['std'], workspace = true } diff --git a/crates/nu-parser/src/flatten.rs b/crates/nu-parser/src/flatten.rs index 2f71ccea28..318ae00ffd 100644 --- a/crates/nu-parser/src/flatten.rs +++ b/crates/nu-parser/src/flatten.rs @@ -11,7 +11,6 @@ use std::fmt::{Display, Formatter, Result}; #[derive(Debug, Eq, PartialEq, Ord, Clone, PartialOrd)] pub enum FlatShape { - And, Binary, Block, Bool, @@ -36,7 +35,6 @@ pub enum FlatShape { MatchPattern, Nothing, Operator, - Or, Pipe, Range, RawString, @@ -53,7 +51,6 @@ pub enum FlatShape { impl FlatShape { pub fn as_str(&self) -> &str { match self { - FlatShape::And => "shape_and", FlatShape::Binary => "shape_binary", FlatShape::Block => "shape_block", FlatShape::Bool => "shape_bool", @@ -78,7 +75,6 @@ impl FlatShape { FlatShape::MatchPattern => "shape_match_pattern", FlatShape::Nothing => "shape_nothing", FlatShape::Operator => "shape_operator", - FlatShape::Or => "shape_or", FlatShape::Pipe => "shape_pipe", FlatShape::Range => "shape_range", FlatShape::RawString => "shape_raw_string", diff --git a/crates/nu-parser/src/lex.rs b/crates/nu-parser/src/lex.rs index f0802fcd7a..6d1adf28ef 100644 --- a/crates/nu-parser/src/lex.rs +++ b/crates/nu-parser/src/lex.rs @@ -51,7 +51,7 @@ impl BlockKind { } // A baseline token is terminated if it's not nested inside of a paired -// delimiter and the next character is one of: `|`, `;`, `#` or any +// delimiter and the next character is one of: `|`, `;` or any // whitespace. fn is_item_terminator( block_level: &[BlockKind], @@ -115,6 +115,7 @@ pub fn lex_item( // character (whitespace, `|`, `;` or `#`) is encountered, the baseline // token is done. // - Otherwise, accumulate the character into the current baseline token. + let mut previous_char = None; while let Some(c) = input.get(*curr_offset) { let c = *c; @@ -147,11 +148,9 @@ pub fn lex_item( // Also need to check to make sure we aren't escaped quote_start = None; } - } else if c == b'#' { - if is_item_terminator(&block_level, c, additional_whitespace, special_tokens) { - break; - } - in_comment = true; + } else if c == b'#' && !in_comment { + // To start a comment, It either need to be the first character of the token or prefixed with space. + in_comment = previous_char.map(|pc| pc == b' ').unwrap_or(true); } else if c == b'\n' || c == b'\r' { in_comment = false; if is_item_terminator(&block_level, c, additional_whitespace, special_tokens) { @@ -254,6 +253,7 @@ pub fn lex_item( } *curr_offset += 1; + previous_char = Some(c); } let span = Span::new(span_offset + token_start, span_offset + *curr_offset); diff --git a/crates/nu-parser/src/parse_keywords.rs b/crates/nu-parser/src/parse_keywords.rs index 3292405cab..e01743321c 100644 --- a/crates/nu-parser/src/parse_keywords.rs +++ b/crates/nu-parser/src/parse_keywords.rs @@ -3544,6 +3544,17 @@ pub fn parse_source(working_set: &mut StateWorkingSet, lite_command: &LiteComman ), ); + // store the file path as a string to be gathered later + call_with_block.set_parser_info( + "block_id_name".to_string(), + Expression::new( + working_set, + Expr::Filepath(path.path_buf().display().to_string(), false), + spans[1], + Type::String, + ), + ); + return Pipeline::from_vec(vec![Expression::new( working_set, Expr::Call(call_with_block), diff --git a/crates/nu-parser/src/parse_patterns.rs b/crates/nu-parser/src/parse_patterns.rs index dc4a64ce37..6f7cb62bc2 100644 --- a/crates/nu-parser/src/parse_patterns.rs +++ b/crates/nu-parser/src/parse_patterns.rs @@ -1,3 +1,5 @@ +#![allow(clippy::byte_char_slices)] + use crate::{ lex, lite_parse, parser::{is_variable, parse_value}, @@ -7,7 +9,6 @@ use nu_protocol::{ engine::StateWorkingSet, ParseError, Span, SyntaxShape, Type, VarId, }; - pub fn garbage(span: Span) -> MatchPattern { MatchPattern { pattern: Pattern::Garbage, diff --git a/crates/nu-parser/src/parse_shape_specs.rs b/crates/nu-parser/src/parse_shape_specs.rs index 2a59bb8a59..20d88554dc 100644 --- a/crates/nu-parser/src/parse_shape_specs.rs +++ b/crates/nu-parser/src/parse_shape_specs.rs @@ -1,3 +1,5 @@ +#![allow(clippy::byte_char_slices)] + use crate::{lex::lex_signature, parser::parse_value, trim_quotes, TokenContents}; use nu_protocol::{engine::StateWorkingSet, ParseError, Span, SyntaxShape, Type}; diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 7db2e112bf..d71ef77182 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -1,3 +1,5 @@ +#![allow(clippy::byte_char_slices)] + use crate::{ lex::{is_assignment_operator, lex, lex_n_tokens, lex_signature, LexState}, lite_parser::{lite_parse, LiteCommand, LitePipeline, LiteRedirection, LiteRedirectionTarget}, @@ -11,9 +13,9 @@ use itertools::Itertools; use log::trace; use nu_engine::DIR_VAR_PARSER_INFO; use nu_protocol::{ - ast::*, engine::StateWorkingSet, eval_const::eval_constant, BlockId, DeclId, DidYouMean, Flag, - ParseError, PositionalArg, Signature, Span, Spanned, SyntaxShape, Type, VarId, ENV_VARIABLE_ID, - IN_VARIABLE_ID, + ast::*, engine::StateWorkingSet, eval_const::eval_constant, BlockId, DeclId, DidYouMean, + FilesizeUnit, Flag, ParseError, PositionalArg, Signature, Span, Spanned, SyntaxShape, Type, + VarId, ENV_VARIABLE_ID, IN_VARIABLE_ID, }; use std::{ collections::{HashMap, HashSet}, @@ -772,6 +774,51 @@ fn calculate_end_span( } } +fn parse_oneof( + working_set: &mut StateWorkingSet, + spans: &[Span], + spans_idx: &mut usize, + possible_shapes: &Vec, + multispan: bool, +) -> Expression { + for shape in possible_shapes { + let starting_error_count = working_set.parse_errors.len(); + let value = match multispan { + true => parse_multispan_value(working_set, spans, spans_idx, shape), + false => parse_value(working_set, spans[*spans_idx], shape), + }; + + if starting_error_count == working_set.parse_errors.len() { + return value; + } + + // while trying the possible shapes, ignore Expected type errors + // unless they're inside a block, closure, or expression + let propagate_error = match working_set.parse_errors.last() { + Some(ParseError::Expected(_, error_span)) + | Some(ParseError::ExpectedWithStringMsg(_, error_span)) => { + matches!( + shape, + SyntaxShape::Block | SyntaxShape::Closure(_) | SyntaxShape::Expression + ) && *error_span != spans[*spans_idx] + } + _ => true, + }; + if !propagate_error { + working_set.parse_errors.truncate(starting_error_count); + } + } + + if working_set.parse_errors.is_empty() { + working_set.error(ParseError::ExpectedWithStringMsg( + format!("one of a list of accepted shapes: {possible_shapes:?}"), + spans[*spans_idx], + )); + } + + Expression::garbage(working_set, spans[*spans_idx]) +} + pub fn parse_multispan_value( working_set: &mut StateWorkingSet, spans: &[Span], @@ -800,54 +847,10 @@ pub fn parse_multispan_value( arg } - SyntaxShape::OneOf(shapes) => { - // handle for `if` command. - //let block_then_exp = shapes.as_slice() == [SyntaxShape::Block, SyntaxShape::Expression]; - for shape in shapes.iter() { - let starting_error_count = working_set.parse_errors.len(); - let s = parse_multispan_value(working_set, spans, spans_idx, shape); - - if starting_error_count == working_set.parse_errors.len() { - return s; - } else if let Some( - ParseError::Expected(..) | ParseError::ExpectedWithStringMsg(..), - ) = working_set.parse_errors.last() - { - working_set.parse_errors.truncate(starting_error_count); - continue; - } - // `if` is parsing block first and then expression. - // when we're writing something like `else if $a`, parsing as a - // block will result to error(because it's not a block) - // - // If parse as a expression also failed, user is more likely concerned - // about expression failure rather than "expect block failure"". - - // FIXME FIXME FIXME - // if block_then_exp { - // match &err { - // Some(ParseError::Expected(expected, _)) => { - // if expected.starts_with("block") { - // err = e - // } - // } - // _ => err = err.or(e), - // } - // } else { - // err = err.or(e) - // } - } - let span = spans[*spans_idx]; - - if working_set.parse_errors.is_empty() { - working_set.error(ParseError::ExpectedWithStringMsg( - format!("one of a list of accepted shapes: {shapes:?}"), - span, - )); - } - - Expression::garbage(working_set, span) + SyntaxShape::OneOf(possible_shapes) => { + parse_oneof(working_set, spans, spans_idx, possible_shapes, true) } + SyntaxShape::Expression => { trace!("parsing: expression"); @@ -1529,7 +1532,9 @@ pub fn parse_int(working_set: &mut StateWorkingSet, span: Span) -> Expression { span: Span, radix: u32, ) -> Expression { - if let Ok(num) = i64::from_str_radix(token, radix) { + // Parse as a u64, then cast to i64, otherwise, for numbers like "0xffffffffffffffef", + // you'll get `Error parsing hex string: number too large to fit in target type`. + if let Ok(num) = u64::from_str_radix(token, radix).map(|val| val as i64) { Expression::new(working_set, Expr::Int(num), span, Type::Int) } else { working_set.error(ParseError::InvalidLiteral( @@ -2119,7 +2124,21 @@ pub fn parse_variable_expr(working_set: &mut StateWorkingSet, span: Span) -> Exp String::from_utf8_lossy(contents).to_string() }; - if let Some(id) = parse_variable(working_set, span) { + let bytes = working_set.get_span_contents(span); + let suggestion = || { + DidYouMean::new( + &working_set.list_variables(), + working_set.get_span_contents(span), + ) + }; + if !is_variable(bytes) { + working_set.error(ParseError::ExpectedWithDidYouMean( + "valid variable name", + suggestion(), + span, + )); + garbage(working_set, span) + } else if let Some(id) = working_set.find_variable(bytes) { Expression::new( working_set, Expr::Var(id), @@ -2130,9 +2149,7 @@ pub fn parse_variable_expr(working_set: &mut StateWorkingSet, span: Span) -> Exp working_set.error(ParseError::EnvVarNotVar(name, span)); garbage(working_set, span) } else { - let ws = &*working_set; - let suggestion = DidYouMean::new(&ws.list_variables(), ws.get_span_contents(span)); - working_set.error(ParseError::VariableNotFound(suggestion, span)); + working_set.error(ParseError::VariableNotFound(suggestion(), span)); garbage(working_set, span) } } @@ -2572,19 +2589,67 @@ pub fn parse_unit_value<'res>( } pub const FILESIZE_UNIT_GROUPS: &[UnitGroup] = &[ - (Unit::Kilobyte, "KB", Some((Unit::Byte, 1000))), - (Unit::Megabyte, "MB", Some((Unit::Kilobyte, 1000))), - (Unit::Gigabyte, "GB", Some((Unit::Megabyte, 1000))), - (Unit::Terabyte, "TB", Some((Unit::Gigabyte, 1000))), - (Unit::Petabyte, "PB", Some((Unit::Terabyte, 1000))), - (Unit::Exabyte, "EB", Some((Unit::Petabyte, 1000))), - (Unit::Kibibyte, "KIB", Some((Unit::Byte, 1024))), - (Unit::Mebibyte, "MIB", Some((Unit::Kibibyte, 1024))), - (Unit::Gibibyte, "GIB", Some((Unit::Mebibyte, 1024))), - (Unit::Tebibyte, "TIB", Some((Unit::Gibibyte, 1024))), - (Unit::Pebibyte, "PIB", Some((Unit::Tebibyte, 1024))), - (Unit::Exbibyte, "EIB", Some((Unit::Pebibyte, 1024))), - (Unit::Byte, "B", None), + ( + Unit::Filesize(FilesizeUnit::KB), + "KB", + Some((Unit::Filesize(FilesizeUnit::B), 1000)), + ), + ( + Unit::Filesize(FilesizeUnit::MB), + "MB", + Some((Unit::Filesize(FilesizeUnit::KB), 1000)), + ), + ( + Unit::Filesize(FilesizeUnit::GB), + "GB", + Some((Unit::Filesize(FilesizeUnit::MB), 1000)), + ), + ( + Unit::Filesize(FilesizeUnit::TB), + "TB", + Some((Unit::Filesize(FilesizeUnit::GB), 1000)), + ), + ( + Unit::Filesize(FilesizeUnit::PB), + "PB", + Some((Unit::Filesize(FilesizeUnit::TB), 1000)), + ), + ( + Unit::Filesize(FilesizeUnit::EB), + "EB", + Some((Unit::Filesize(FilesizeUnit::PB), 1000)), + ), + ( + Unit::Filesize(FilesizeUnit::KiB), + "KIB", + Some((Unit::Filesize(FilesizeUnit::B), 1024)), + ), + ( + Unit::Filesize(FilesizeUnit::MiB), + "MIB", + Some((Unit::Filesize(FilesizeUnit::KiB), 1024)), + ), + ( + Unit::Filesize(FilesizeUnit::GiB), + "GIB", + Some((Unit::Filesize(FilesizeUnit::MiB), 1024)), + ), + ( + Unit::Filesize(FilesizeUnit::TiB), + "TIB", + Some((Unit::Filesize(FilesizeUnit::GiB), 1024)), + ), + ( + Unit::Filesize(FilesizeUnit::PiB), + "PIB", + Some((Unit::Filesize(FilesizeUnit::TiB), 1024)), + ), + ( + Unit::Filesize(FilesizeUnit::EiB), + "EIB", + Some((Unit::Filesize(FilesizeUnit::EiB), 1024)), + ), + (Unit::Filesize(FilesizeUnit::B), "B", None), ]; pub const DURATION_UNIT_GROUPS: &[UnitGroup] = &[ @@ -3298,26 +3363,66 @@ pub fn parse_input_output_types( } pub fn parse_full_signature(working_set: &mut StateWorkingSet, spans: &[Span]) -> Expression { - let arg_signature = working_set.get_span_contents(spans[0]); - - if arg_signature.ends_with(b":") { - let mut arg_signature = - parse_signature(working_set, Span::new(spans[0].start, spans[0].end - 1)); - - let input_output_types = parse_input_output_types(working_set, &spans[1..]); - - if let Expression { - expr: Expr::Signature(sig), - span: expr_span, - .. - } = &mut arg_signature - { - sig.input_output_types = input_output_types; - expr_span.end = Span::concat(&spans[1..]).end; + match spans.len() { + // This case should never happen. It corresponds to declarations like `def foo {}`, + // which should throw a 'Missing required positional argument.' before getting to this point + 0 => { + working_set.error(ParseError::InternalError( + "failed to catch missing positional arguments".to_string(), + Span::concat(spans), + )); + garbage(working_set, Span::concat(spans)) + } + + // e.g. `[ b"[foo: string]" ]` + 1 => parse_signature(working_set, spans[0]), + + // This case is needed to distinguish between e.g. + // `[ b"[]", b"{ true }" ]` vs `[ b"[]:", b"int" ]` + 2 if working_set.get_span_contents(spans[1]).starts_with(b"{") => { + parse_signature(working_set, spans[0]) + } + + // This should handle every other case, e.g. + // `[ b"[]:", b"int" ]` + // `[ b"[]", b":", b"int" ]` + // `[ b"[]", b":", b"int", b"->", b"bool" ]` + _ => { + let (mut arg_signature, input_output_types_pos) = + if working_set.get_span_contents(spans[0]).ends_with(b":") { + ( + parse_signature(working_set, Span::new(spans[0].start, spans[0].end - 1)), + 1, + ) + } else if working_set.get_span_contents(spans[1]) == b":" { + (parse_signature(working_set, spans[0]), 2) + } else { + // This should be an error case, but we call parse_signature anyway + // so it can handle the various possible errors + // e.g. `[ b"[]", b"int" ]` or `[ + working_set.error(ParseError::Expected( + "colon (:) before type signature", + Span::concat(&spans[1..]), + )); + // (garbage(working_set, Span::concat(spans)), 1) + + (parse_signature(working_set, spans[0]), 1) + }; + + let input_output_types = + parse_input_output_types(working_set, &spans[input_output_types_pos..]); + + if let Expression { + expr: Expr::Signature(sig), + span: expr_span, + .. + } = &mut arg_signature + { + sig.input_output_types = input_output_types; + expr_span.end = Span::concat(&spans[input_output_types_pos..]).end; + } + arg_signature } - arg_signature - } else { - parse_signature(working_set, spans[0]) } } @@ -4826,29 +4931,7 @@ pub fn parse_value( SyntaxShape::ExternalArgument => parse_regular_external_arg(working_set, span), SyntaxShape::OneOf(possible_shapes) => { - for s in possible_shapes { - let starting_error_count = working_set.parse_errors.len(); - let value = parse_value(working_set, span, s); - - if starting_error_count == working_set.parse_errors.len() { - return value; - } else if let Some( - ParseError::Expected(..) | ParseError::ExpectedWithStringMsg(..), - ) = working_set.parse_errors.last() - { - working_set.parse_errors.truncate(starting_error_count); - continue; - } - } - - if working_set.parse_errors.is_empty() { - working_set.error(ParseError::ExpectedWithStringMsg( - format!("one of a list of accepted shapes: {possible_shapes:?}"), - span, - )); - } - - Expression::garbage(working_set, span) + parse_oneof(working_set, &[span], &mut 0, possible_shapes, false) } SyntaxShape::Any => { @@ -4908,7 +4991,7 @@ pub fn parse_assignment_operator(working_set: &mut StateWorkingSet, span: Span) let operator = match contents { b"=" => Operator::Assignment(Assignment::Assign), b"+=" => Operator::Assignment(Assignment::PlusAssign), - b"++=" => Operator::Assignment(Assignment::AppendAssign), + b"++=" => Operator::Assignment(Assignment::ConcatAssign), b"-=" => Operator::Assignment(Assignment::MinusAssign), b"*=" => Operator::Assignment(Assignment::MultiplyAssign), b"/=" => Operator::Assignment(Assignment::DivideAssign), @@ -4959,6 +5042,20 @@ pub fn parse_assignment_expression( // Parse the lhs and operator as usual for a math expression let mut lhs = parse_expression(working_set, lhs_spans); + // make sure that lhs is a mutable variable. + match &lhs.expr { + Expr::FullCellPath(p) => { + if let Expr::Var(var_id) = p.head.expr { + if var_id != nu_protocol::ENV_VARIABLE_ID + && !working_set.get_variable(var_id).mutable + { + working_set.error(ParseError::AssignmentRequiresMutableVar(lhs.span)) + } + } + } + _ => working_set.error(ParseError::AssignmentRequiresVar(lhs.span)), + } + let mut operator = parse_assignment_operator(working_set, op_span); // Re-parse the right-hand side as a subexpression @@ -5034,7 +5131,7 @@ pub fn parse_operator(working_set: &mut StateWorkingSet, span: Span) -> Expressi b"=~" | b"like" => Operator::Comparison(Comparison::RegexMatch), b"!~" | b"not-like" => Operator::Comparison(Comparison::NotRegexMatch), b"+" => Operator::Math(Math::Plus), - b"++" => Operator::Math(Math::Append), + b"++" => Operator::Math(Math::Concat), b"-" => Operator::Math(Math::Minus), b"*" => Operator::Math(Math::Multiply), b"/" => Operator::Math(Math::Divide), @@ -5569,18 +5666,6 @@ pub fn parse_expression(working_set: &mut StateWorkingSet, spans: &[Span]) -> Ex } } -pub fn parse_variable(working_set: &mut StateWorkingSet, span: Span) -> Option { - let bytes = working_set.get_span_contents(span); - - if is_variable(bytes) { - working_set.find_variable(bytes) - } else { - working_set.error(ParseError::Expected("valid variable name", span)); - - None - } -} - pub fn parse_builtin_commands( working_set: &mut StateWorkingSet, lite_command: &LiteCommand, diff --git a/crates/nu-parser/src/type_check.rs b/crates/nu-parser/src/type_check.rs index a7de3800e2..fd57acd5ca 100644 --- a/crates/nu-parser/src/type_check.rs +++ b/crates/nu-parser/src/type_check.rs @@ -29,8 +29,6 @@ pub fn type_compatible(lhs: &Type, rhs: &Type) -> bool { match (lhs, rhs) { (Type::List(c), Type::List(d)) => type_compatible(c, d), - (Type::ListStream, Type::List(_)) => true, - (Type::List(_), Type::ListStream) => true, (Type::List(c), Type::Table(table_fields)) => { if matches!(**c, Type::Any) { return true; @@ -132,7 +130,7 @@ pub fn math_result_type( ) } }, - Operator::Math(Math::Append) => check_append(working_set, lhs, rhs, op), + Operator::Math(Math::Concat) => check_concat(working_set, lhs, rhs, op), Operator::Math(Math::Minus) => match (&lhs.ty, &rhs.ty) { (Type::Int, Type::Int) => (Type::Int, None), (Type::Float, Type::Int) => (Type::Float, None), @@ -935,8 +933,8 @@ pub fn math_result_type( ) } }, - Operator::Assignment(Assignment::AppendAssign) => { - check_append(working_set, lhs, rhs, op) + Operator::Assignment(Assignment::ConcatAssign) => { + check_concat(working_set, lhs, rhs, op) } Operator::Assignment(_) => match (&lhs.ty, &rhs.ty) { (x, y) if x == y => (Type::Nothing, None), @@ -1085,7 +1083,7 @@ pub fn check_block_input_output(working_set: &StateWorkingSet, block: &Block) -> output_errors } -fn check_append( +fn check_concat( working_set: &mut StateWorkingSet, lhs: &Expression, rhs: &Expression, @@ -1099,23 +1097,17 @@ fn check_append( (Type::List(Box::new(Type::Any)), None) } } - (Type::List(a), b) | (b, Type::List(a)) => { - if a == &Box::new(b.clone()) { - (Type::List(a.clone()), None) - } else { - (Type::List(Box::new(Type::Any)), None) - } - } (Type::Table(a), Type::Table(_)) => (Type::Table(a.clone()), None), (Type::String, Type::String) => (Type::String, None), (Type::Binary, Type::Binary) => (Type::Binary, None), (Type::Any, _) | (_, Type::Any) => (Type::Any, None), - (Type::Table(_) | Type::String | Type::Binary, _) => { + (Type::Table(_) | Type::List(_) | Type::String | Type::Binary, _) + | (_, Type::Table(_) | Type::List(_) | Type::String | Type::Binary) => { *op = Expression::garbage(working_set, op.span); ( Type::Any, Some(ParseError::UnsupportedOperationRHS( - "append".into(), + "concatenation".into(), op.span, lhs.span, lhs.ty.clone(), @@ -1129,7 +1121,7 @@ fn check_append( ( Type::Any, Some(ParseError::UnsupportedOperationLHS( - "append".into(), + "concatenation".into(), op.span, lhs.span, lhs.ty.clone(), diff --git a/crates/nu-parser/tests/test_lex.rs b/crates/nu-parser/tests/test_lex.rs index 22fe4c4715..54ff674bb9 100644 --- a/crates/nu-parser/tests/test_lex.rs +++ b/crates/nu-parser/tests/test_lex.rs @@ -1,3 +1,5 @@ +#![allow(clippy::byte_char_slices)] + use nu_parser::{lex, lex_n_tokens, lex_signature, LexState, Token, TokenContents}; use nu_protocol::{ParseError, Span}; @@ -157,6 +159,29 @@ fn lex_comment() { ); } +#[test] +fn lex_not_comment_needs_space_in_front_of_hashtag() { + let file = b"1..10 | each {echo test#testing }"; + + let output = lex(file, 0, &[], &[], false); + + assert!(output.1.is_none()); +} + +#[test] +fn lex_comment_with_space_in_front_of_hashtag() { + let file = b"1..10 | each {echo test #testing }"; + + let output = lex(file, 0, &[], &[], false); + + assert!(output.1.is_some()); + assert!(matches!( + output.1.unwrap(), + ParseError::UnexpectedEof(missing_token, span) if missing_token == "}" + && span == Span::new(33, 34) + )); +} + #[test] fn lex_is_incomplete() { let file = b"let x = 300 | ;"; diff --git a/crates/nu-parser/tests/test_parser.rs b/crates/nu-parser/tests/test_parser.rs index 11cca19d1f..2ceabf8a1a 100644 --- a/crates/nu-parser/tests/test_parser.rs +++ b/crates/nu-parser/tests/test_parser.rs @@ -2460,6 +2460,7 @@ mod input_types { #[rstest] #[case::input_output(b"def q []: int -> int {1}", false)] + #[case::input_output(b"def q [x: bool]: int -> int {2}", false)] #[case::input_output(b"def q []: string -> string {'qwe'}", false)] #[case::input_output(b"def q []: nothing -> nothing {null}", false)] #[case::input_output(b"def q []: list -> list {[]}", false)] @@ -2479,6 +2480,42 @@ mod input_types { #[case::input_output(b"def q []: nothing -> record record {{a: 1}}", true)] #[case::input_output(b"def q []: nothing -> record {{a: {a: 1}}}", true)] + #[case::input_output(b"def q []: int []}", true)] + #[case::input_output(b"def q []: bool {[]", true)] + // Type signature variants with whitespace between inputs and `:` + #[case::input_output(b"def q [] : int -> int {1}", false)] + #[case::input_output(b"def q [x: bool] : int -> int {2}", false)] + #[case::input_output(b"def q []\t : string -> string {'qwe'}", false)] + #[case::input_output(b"def q [] \t : nothing -> nothing {null}", false)] + #[case::input_output(b"def q [] \t: list -> list {[]}", false)] + #[case::input_output( + b"def q []\t: record -> record {{c: 1 e: 1}}", + false + )] + #[case::input_output( + b"def q [] : table -> table {[{c: 1 e: 1}]}", + false + )] + #[case::input_output( + b"def q [] : nothing -> record e: int> {{c: {a: 1 b: 2} e: 1}}", + false + )] + #[case::input_output(b"def q [] : nothing -> list record record {{a: 1}}", true)] + #[case::input_output(b"def q [] : nothing -> record {{a: {a: 1}}}", true)] + #[case::input_output(b"def q [] : int []}", true)] + #[case::input_output(b"def q [] : bool {[]", true)] + // No input-output type signature + #[case::input_output(b"def qq [] {[]}", false)] + #[case::input_output(b"def q [] []}", true)] + #[case::input_output(b"def q [] {", true)] + #[case::input_output(b"def q []: []}", true)] + #[case::input_output(b"def q [] int {}", true)] + #[case::input_output(b"def q [x: string, y: int] {{c: 1 e: 1}}", false)] + #[case::input_output(b"def q [x: string, y: int]: {}", true)] + #[case::input_output(b"def q [x: string, y: int] {a: {a: 1}}", true)] + #[case::input_output(b"def foo {3}", true)] #[case::vardecl(b"let a: int = 1", false)] #[case::vardecl(b"let a: string = 'qwe'", false)] #[case::vardecl(b"let a: nothing = null", false)] diff --git a/crates/nu-path/src/tilde.rs b/crates/nu-path/src/tilde.rs index ac48bdfc3f..2420c8c17e 100644 --- a/crates/nu-path/src/tilde.rs +++ b/crates/nu-path/src/tilde.rs @@ -60,6 +60,7 @@ fn expand_tilde_with_home(path: impl AsRef, home: Option) -> Path } } +#[cfg(not(target_arch = "wasm32"))] fn fallback_home_dir(username: &str) -> PathBuf { PathBuf::from_iter([FALLBACK_USER_HOME_BASE_DIR, username]) } @@ -110,6 +111,13 @@ fn user_home_dir(username: &str) -> PathBuf { } } +#[cfg(target_arch = "wasm32")] +fn user_home_dir(_: &str) -> PathBuf { + // if WASI is used, we try to get a home dir via HOME env, otherwise we don't have a home dir + let home = std::env::var("HOME").unwrap_or_else(|_| "/".to_string()); + PathBuf::from(home) +} + /// Returns true if the shell is running inside the Termux terminal emulator /// app. #[cfg(target_os = "android")] diff --git a/crates/nu-path/src/trailing_slash.rs b/crates/nu-path/src/trailing_slash.rs index 4daf5d45ef..6a2d08ac63 100644 --- a/crates/nu-path/src/trailing_slash.rs +++ b/crates/nu-path/src/trailing_slash.rs @@ -36,6 +36,13 @@ pub fn has_trailing_slash(path: &Path) -> bool { last == Some(&b'/') } +/// `true` if the path has a trailing slash, including if it's the root directory. +#[cfg(target_arch = "wasm32")] +pub fn has_trailing_slash(path: &Path) -> bool { + // in the web paths are often just URLs, they are separated by forward slashes + path.to_str().map_or(false, |s| s.ends_with('/')) +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/nu-plugin-engine/src/context.rs b/crates/nu-plugin-engine/src/context.rs index 4c262fd3ed..5652cf1a5f 100644 --- a/crates/nu-plugin-engine/src/context.rs +++ b/crates/nu-plugin-engine/src/context.rs @@ -126,7 +126,7 @@ impl<'a> PluginExecutionContext for PluginExecutionCommandContext<'a> { } fn get_env_var(&self, name: &str) -> Result, ShellError> { - Ok(self.stack.get_env_var(&self.engine_state, name)) + Ok(self.stack.get_env_var_insensitive(&self.engine_state, name)) } fn get_env_vars(&self) -> Result, ShellError> { diff --git a/crates/nu-plugin-engine/src/interface/tests.rs b/crates/nu-plugin-engine/src/interface/tests.rs index 5058a45695..4761da6824 100644 --- a/crates/nu-plugin-engine/src/interface/tests.rs +++ b/crates/nu-plugin-engine/src/interface/tests.rs @@ -1485,7 +1485,7 @@ fn prepare_plugin_call_custom_value_op() { span, }, CustomValueOp::Operation( - Operator::Math(Math::Append).into_spanned(span), + Operator::Math(Math::Concat).into_spanned(span), cv_ok_val.clone(), ), ), @@ -1498,7 +1498,7 @@ fn prepare_plugin_call_custom_value_op() { span, }, CustomValueOp::Operation( - Operator::Math(Math::Append).into_spanned(span), + Operator::Math(Math::Concat).into_spanned(span), cv_bad_val.clone(), ), ), diff --git a/crates/nu-plugin/Cargo.toml b/crates/nu-plugin/Cargo.toml index 2c43a86c5e..0e6827bb19 100644 --- a/crates/nu-plugin/Cargo.toml +++ b/crates/nu-plugin/Cargo.toml @@ -14,8 +14,8 @@ bench = false workspace = true [dependencies] -nu-engine = { path = "../nu-engine", version = "0.100.1" } -nu-protocol = { path = "../nu-protocol", version = "0.100.1" } +nu-engine = { path = "../nu-engine", version = "0.100.1", features = ["plugin"] } +nu-protocol = { path = "../nu-protocol", version = "0.100.1", features = ["plugin"] } nu-plugin-protocol = { path = "../nu-plugin-protocol", version = "0.100.1" } nu-plugin-core = { path = "../nu-plugin-core", version = "0.100.1", default-features = false } nu-utils = { path = "../nu-utils", version = "0.100.1" } diff --git a/crates/nu-protocol/Cargo.toml b/crates/nu-protocol/Cargo.toml index dfbcb7b307..34af0452f0 100644 --- a/crates/nu-protocol/Cargo.toml +++ b/crates/nu-protocol/Cargo.toml @@ -16,7 +16,7 @@ bench = false workspace = true [dependencies] -nu-utils = { path = "../nu-utils", version = "0.100.1" } +nu-utils = { path = "../nu-utils", version = "0.100.1", default-features = false } nu-path = { path = "../nu-path", version = "0.100.1" } nu-system = { path = "../nu-system", version = "0.100.1" } nu-derive-value = { path = "../nu-derive-value", version = "0.100.1" } @@ -31,14 +31,14 @@ fancy-regex = { workspace = true } heck = { workspace = true } indexmap = { workspace = true } lru = { workspace = true } -miette = { workspace = true, features = ["fancy-no-backtrace"] } +miette = { workspace = true, features = ["fancy-no-backtrace"]} num-format = { workspace = true } rmp-serde = { workspace = true, optional = true } serde = { workspace = true } serde_json = { workspace = true } thiserror = "2.0" typetag = "0.2" -os_pipe = { workspace = true, features = ["io_safety"] } +os_pipe = { workspace = true, optional = true, features = ["io_safety"] } log = { workspace = true } [target.'cfg(unix)'.dependencies] @@ -49,8 +49,15 @@ dirs-sys = { workspace = true } windows-sys = { workspace = true } [features] +default = ["os"] +os = [ + "nu-utils/os", + "os_pipe", +] + plugin = [ "brotli", + "os", "rmp-serde", ] diff --git a/crates/nu-protocol/src/ast/operator.rs b/crates/nu-protocol/src/ast/operator.rs index 8c2e59d93a..bde866498c 100644 --- a/crates/nu-protocol/src/ast/operator.rs +++ b/crates/nu-protocol/src/ast/operator.rs @@ -24,7 +24,7 @@ pub enum Comparison { #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub enum Math { Plus, - Append, + Concat, Minus, Multiply, Divide, @@ -53,7 +53,7 @@ pub enum Bits { pub enum Assignment { Assign, PlusAssign, - AppendAssign, + ConcatAssign, MinusAssign, MultiplyAssign, DivideAssign, @@ -90,7 +90,7 @@ impl Operator { | Self::Comparison(Comparison::NotEqual) | Self::Comparison(Comparison::In) | Self::Comparison(Comparison::NotIn) - | Self::Math(Math::Append) => 80, + | Self::Math(Math::Concat) => 80, Self::Bits(Bits::BitAnd) => 75, Self::Bits(Bits::BitXor) => 70, Self::Bits(Bits::BitOr) => 60, @@ -107,7 +107,7 @@ impl Display for Operator { match self { Operator::Assignment(Assignment::Assign) => write!(f, "="), Operator::Assignment(Assignment::PlusAssign) => write!(f, "+="), - Operator::Assignment(Assignment::AppendAssign) => write!(f, "++="), + Operator::Assignment(Assignment::ConcatAssign) => write!(f, "++="), Operator::Assignment(Assignment::MinusAssign) => write!(f, "-="), Operator::Assignment(Assignment::MultiplyAssign) => write!(f, "*="), Operator::Assignment(Assignment::DivideAssign) => write!(f, "/="), @@ -124,7 +124,7 @@ impl Display for Operator { Operator::Comparison(Comparison::In) => write!(f, "in"), Operator::Comparison(Comparison::NotIn) => write!(f, "not-in"), Operator::Math(Math::Plus) => write!(f, "+"), - Operator::Math(Math::Append) => write!(f, "++"), + Operator::Math(Math::Concat) => write!(f, "++"), Operator::Math(Math::Minus) => write!(f, "-"), Operator::Math(Math::Multiply) => write!(f, "*"), Operator::Math(Math::Divide) => write!(f, "/"), diff --git a/crates/nu-protocol/src/ast/unit.rs b/crates/nu-protocol/src/ast/unit.rs index 0e51d8978f..ecc92806a0 100644 --- a/crates/nu-protocol/src/ast/unit.rs +++ b/crates/nu-protocol/src/ast/unit.rs @@ -1,24 +1,9 @@ -use crate::{ShellError, Span, Value}; +use crate::{Filesize, FilesizeUnit, IntoValue, ShellError, Span, Value}; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum Unit { - // Filesize units: metric - Byte, - Kilobyte, - Megabyte, - Gigabyte, - Terabyte, - Petabyte, - Exabyte, - - // Filesize units: ISO/IEC 80000 - Kibibyte, - Mebibyte, - Gibibyte, - Tebibyte, - Pebibyte, - Exbibyte, + Filesize(FilesizeUnit), // Duration units Nanosecond, @@ -34,33 +19,19 @@ pub enum Unit { impl Unit { pub fn build_value(self, size: i64, span: Span) -> Result { match self { - Unit::Byte => Ok(Value::filesize(size, span)), - Unit::Kilobyte => Ok(Value::filesize(size * 1000, span)), - Unit::Megabyte => Ok(Value::filesize(size * 1000 * 1000, span)), - Unit::Gigabyte => Ok(Value::filesize(size * 1000 * 1000 * 1000, span)), - Unit::Terabyte => Ok(Value::filesize(size * 1000 * 1000 * 1000 * 1000, span)), - Unit::Petabyte => Ok(Value::filesize( - size * 1000 * 1000 * 1000 * 1000 * 1000, - span, - )), - Unit::Exabyte => Ok(Value::filesize( - size * 1000 * 1000 * 1000 * 1000 * 1000 * 1000, - span, - )), - - Unit::Kibibyte => Ok(Value::filesize(size * 1024, span)), - Unit::Mebibyte => Ok(Value::filesize(size * 1024 * 1024, span)), - Unit::Gibibyte => Ok(Value::filesize(size * 1024 * 1024 * 1024, span)), - Unit::Tebibyte => Ok(Value::filesize(size * 1024 * 1024 * 1024 * 1024, span)), - Unit::Pebibyte => Ok(Value::filesize( - size * 1024 * 1024 * 1024 * 1024 * 1024, - span, - )), - Unit::Exbibyte => Ok(Value::filesize( - size * 1024 * 1024 * 1024 * 1024 * 1024 * 1024, - span, - )), - + Unit::Filesize(unit) => { + if let Some(filesize) = Filesize::from_unit(size, unit) { + Ok(filesize.into_value(span)) + } else { + Err(ShellError::GenericError { + error: "filesize too large".into(), + msg: "filesize too large".into(), + span: Some(span), + help: None, + inner: vec![], + }) + } + } Unit::Nanosecond => Ok(Value::duration(size, span)), Unit::Microsecond => Ok(Value::duration(size * 1000, span)), Unit::Millisecond => Ok(Value::duration(size * 1000 * 1000, span)), diff --git a/crates/nu-protocol/src/config/error.rs b/crates/nu-protocol/src/config/error.rs index b13dceeb02..c8fbd3f85a 100644 --- a/crates/nu-protocol/src/config/error.rs +++ b/crates/nu-protocol/src/config/error.rs @@ -65,6 +65,8 @@ impl<'a> ConfigErrors<'a> { }); } + // We'll probably need this again in the future so allow dead code for now + #[allow(dead_code)] pub fn deprecated_option(&mut self, path: &ConfigPath, suggestion: &'static str, span: Span) { self.error(ConfigError::Deprecated { path: path.to_string(), diff --git a/crates/nu-protocol/src/config/hooks.rs b/crates/nu-protocol/src/config/hooks.rs index 374b3818d6..97d4aa759b 100644 --- a/crates/nu-protocol/src/config/hooks.rs +++ b/crates/nu-protocol/src/config/hooks.rs @@ -1,13 +1,13 @@ use super::prelude::*; use crate as nu_protocol; -use crate::Record; +use std::collections::HashMap; /// Definition of a parsed hook from the config object #[derive(Clone, Debug, IntoValue, PartialEq, Serialize, Deserialize)] pub struct Hooks { - pub pre_prompt: Option, - pub pre_execution: Option, - pub env_change: Option, + pub pre_prompt: Vec, + pub pre_execution: Vec, + pub env_change: HashMap>, pub display_output: Option, pub command_not_found: Option, } @@ -15,14 +15,14 @@ pub struct Hooks { impl Hooks { pub fn new() -> Self { Self { - pre_prompt: Some(Value::list(vec![], Span::unknown())), - pre_execution: Some(Value::list(vec![], Span::unknown())), - env_change: Some(Value::record(Record::default(), Span::unknown())), + pre_prompt: Vec::new(), + pre_execution: Vec::new(), + env_change: HashMap::new(), display_output: Some(Value::string( "if (term size).columns >= 100 { table -e } else { table }", Span::unknown(), )), - command_not_found: Some(Value::list(vec![], Span::unknown())), + command_not_found: None, } } } @@ -40,14 +40,6 @@ impl UpdateFromValue for Hooks { path: &mut ConfigPath<'a>, errors: &mut ConfigErrors, ) { - fn update_option(field: &mut Option, value: &Value) { - if value.is_nothing() { - *field = None; - } else { - *field = Some(value.clone()); - } - } - let Value::Record { val: record, .. } = value else { errors.type_mismatch(path, Type::record(), value); return; @@ -56,11 +48,57 @@ impl UpdateFromValue for Hooks { for (col, val) in record.iter() { let path = &mut path.push(col); match col.as_str() { - "pre_prompt" => update_option(&mut self.pre_prompt, val), - "pre_execution" => update_option(&mut self.pre_execution, val), - "env_change" => update_option(&mut self.env_change, val), - "display_output" => update_option(&mut self.display_output, val), - "command_not_found" => update_option(&mut self.command_not_found, val), + "pre_prompt" => { + if let Ok(hooks) = val.as_list() { + self.pre_prompt = hooks.into() + } else { + errors.type_mismatch(path, Type::list(Type::Any), val); + } + } + "pre_execution" => { + if let Ok(hooks) = val.as_list() { + self.pre_execution = hooks.into() + } else { + errors.type_mismatch(path, Type::list(Type::Any), val); + } + } + "env_change" => { + if let Ok(record) = val.as_record() { + self.env_change = record + .iter() + .map(|(key, val)| { + let old = self.env_change.remove(key).unwrap_or_default(); + let new = if let Ok(hooks) = val.as_list() { + hooks.into() + } else { + errors.type_mismatch( + &path.push(key), + Type::list(Type::Any), + val, + ); + old + }; + (key.as_str().into(), new) + }) + .collect(); + } else { + errors.type_mismatch(path, Type::record(), val); + } + } + "display_output" => { + self.display_output = if val.is_nothing() { + None + } else { + Some(val.clone()) + } + } + "command_not_found" => { + self.command_not_found = if val.is_nothing() { + None + } else { + Some(val.clone()) + } + } _ => errors.unknown_option(path, val), } } diff --git a/crates/nu-protocol/src/config/mod.rs b/crates/nu-protocol/src/config/mod.rs index 5f53810315..1f6a859303 100644 --- a/crates/nu-protocol/src/config/mod.rs +++ b/crates/nu-protocol/src/config/mod.rs @@ -156,10 +156,6 @@ impl UpdateFromValue for Config { "filesize" => self.filesize.update(val, path, errors), "explore" => self.explore.update(val, path, errors), "color_config" => self.color_config.update(val, path, errors), - "use_grid_icons" => { - // TODO: delete it after 0.99 - errors.deprecated_option(path, "use `grid -i`", val.span()); - } "footer_mode" => self.footer_mode.update(val, path, errors), "float_precision" => self.float_precision.update(val, path, errors), "use_ansi_coloring" => self.use_ansi_coloring.update(val, path, errors), diff --git a/crates/nu-protocol/src/engine/engine_state.rs b/crates/nu-protocol/src/engine/engine_state.rs index 626033f915..716cb03ca9 100644 --- a/crates/nu-protocol/src/engine/engine_state.rs +++ b/crates/nu-protocol/src/engine/engine_state.rs @@ -14,6 +14,7 @@ use crate::{ use fancy_regex::Regex; use lru::LruCache; use nu_path::AbsolutePathBuf; +use nu_utils::IgnoreCaseExt; use std::{ collections::HashMap, num::NonZeroUsize, @@ -465,20 +466,12 @@ impl EngineState { None } - // Get the path environment variable in a platform agnostic way - pub fn get_path_env_var(&self) -> Option<&Value> { - let env_path_name_windows: &str = "Path"; - let env_path_name_nix: &str = "PATH"; - + pub fn get_env_var_insensitive(&self, name: &str) -> Option<&Value> { for overlay_id in self.scope.active_overlays.iter().rev() { let overlay_name = String::from_utf8_lossy(self.get_overlay_name(*overlay_id)); if let Some(env_vars) = self.env_vars.get(overlay_name.as_ref()) { - if let Some(val) = env_vars.get(env_path_name_nix) { - return Some(val); - } else if let Some(val) = env_vars.get(env_path_name_windows) { - return Some(val); - } else { - return None; + if let Some(v) = env_vars.iter().find(|(k, _)| k.eq_ignore_case(name)) { + return Some(v.1); } } } diff --git a/crates/nu-protocol/src/engine/pattern_match.rs b/crates/nu-protocol/src/engine/pattern_match.rs index 3284527cc6..32c34d22d3 100644 --- a/crates/nu-protocol/src/engine/pattern_match.rs +++ b/crates/nu-protocol/src/engine/pattern_match.rs @@ -128,7 +128,7 @@ impl Matcher for Pattern { false } } - Expr::String(x) => { + Expr::String(x) | Expr::RawString(x) => { if let Value::String { val, .. } = &value { x == val } else { diff --git a/crates/nu-protocol/src/engine/stack.rs b/crates/nu-protocol/src/engine/stack.rs index ddd1169513..ccd38cc5d3 100644 --- a/crates/nu-protocol/src/engine/stack.rs +++ b/crates/nu-protocol/src/engine/stack.rs @@ -5,6 +5,7 @@ use crate::{ }, Config, IntoValue, OutDest, ShellError, Span, Value, VarId, ENV_VARIABLE_ID, NU_VARIABLE_ID, }; +use nu_utils::IgnoreCaseExt; use std::{ collections::{HashMap, HashSet}, fs::File, @@ -479,6 +480,40 @@ impl Stack { None } + // Case-Insensitive version of get_env_var + pub fn get_env_var_insensitive<'a>( + &'a self, + engine_state: &'a EngineState, + name: &str, + ) -> Option<&'a Value> { + for scope in self.env_vars.iter().rev() { + for active_overlay in self.active_overlays.iter().rev() { + if let Some(env_vars) = scope.get(active_overlay) { + if let Some(v) = env_vars.iter().find(|(k, _)| k.eq_ignore_case(name)) { + return Some(v.1); + } + } + } + } + + for active_overlay in self.active_overlays.iter().rev() { + let is_hidden = if let Some(env_hidden) = self.env_hidden.get(active_overlay) { + env_hidden.iter().any(|k| k.eq_ignore_case(name)) + } else { + false + }; + + if !is_hidden { + if let Some(env_vars) = engine_state.env_vars.get(active_overlay) { + if let Some(v) = env_vars.iter().find(|(k, _)| k.eq_ignore_case(name)) { + return Some(v.1); + } + } + } + } + None + } + pub fn has_env_var(&self, engine_state: &EngineState, name: &str) -> bool { for scope in self.env_vars.iter().rev() { for active_overlay in self.active_overlays.iter().rev() { diff --git a/crates/nu-protocol/src/engine/state_working_set.rs b/crates/nu-protocol/src/engine/state_working_set.rs index 0d59068dbf..5cbc959f63 100644 --- a/crates/nu-protocol/src/engine/state_working_set.rs +++ b/crates/nu-protocol/src/engine/state_working_set.rs @@ -459,21 +459,48 @@ impl<'a> StateWorkingSet<'a> { } // check overlay in perma - for overlay_frame in self - .permanent_state - .active_overlays(&removed_overlays) - .rev() - { - visibility.append(&overlay_frame.visibility); + self.permanent_state.find_decl(name, &removed_overlays) + } + + pub fn find_decl_name(&self, decl_id: DeclId) -> Option<&[u8]> { + let mut removed_overlays = vec![]; + + let mut visibility: Visibility = Visibility::new(); + + for scope_frame in self.delta.scope.iter().rev() { + if self.search_predecls { + for (name, id) in scope_frame.predecls.iter() { + if id == &decl_id { + return Some(name); + } + } + } + + // check overlay in delta + for overlay_frame in scope_frame.active_overlays(&mut removed_overlays).rev() { + visibility.append(&overlay_frame.visibility); + + if self.search_predecls { + for (name, id) in overlay_frame.predecls.iter() { + if id == &decl_id { + return Some(name); + } + } + } - if let Some(decl_id) = overlay_frame.get_decl(name) { if visibility.is_decl_id_visible(&decl_id) { - return Some(decl_id); + for (name, id) in overlay_frame.decls.iter() { + if id == &decl_id { + return Some(name); + } + } } } } - None + // check overlay in perma + self.permanent_state + .find_decl_name(decl_id, &removed_overlays) } pub fn find_module(&self, name: &[u8]) -> Option { diff --git a/crates/nu-protocol/src/errors/labeled_error.rs b/crates/nu-protocol/src/errors/labeled_error.rs index a207178415..c76a6c78dd 100644 --- a/crates/nu-protocol/src/errors/labeled_error.rs +++ b/crates/nu-protocol/src/errors/labeled_error.rs @@ -139,23 +139,6 @@ impl LabeledError { self } - pub fn render_error_to_string(diag: impl miette::Diagnostic, fancy_errors: bool) -> String { - let theme = if fancy_errors { - miette::GraphicalTheme::unicode() - } else { - miette::GraphicalTheme::none() - }; - - let mut out = String::new(); - miette::GraphicalReportHandler::new() - .with_width(80) - .with_theme(theme) - .render_report(&mut out, &diag) - .unwrap_or_default(); - - out - } - /// Create a [`LabeledError`] from a type that implements [`miette::Diagnostic`]. /// /// # Example diff --git a/crates/nu-protocol/src/errors/parse_error.rs b/crates/nu-protocol/src/errors/parse_error.rs index ce8522daa1..f7de9f4890 100644 --- a/crates/nu-protocol/src/errors/parse_error.rs +++ b/crates/nu-protocol/src/errors/parse_error.rs @@ -55,6 +55,10 @@ pub enum ParseError { #[diagnostic(code(nu::parser::parse_mismatch_with_full_string_msg))] ExpectedWithStringMsg(String, #[label("expected {0}")] Span), + #[error("Parse mismatch during operation.")] + #[diagnostic(code(nu::parser::parse_mismatch_with_did_you_mean))] + ExpectedWithDidYouMean(&'static str, DidYouMean, #[label("expected {0}. {1}")] Span), + #[error("Command does not support {0} input.")] #[diagnostic(code(nu::parser::input_type_mismatch))] InputMismatch(Type, #[label("command doesn't support {0} input")] Span), @@ -515,6 +519,30 @@ pub enum ParseError { help("To spread arguments, the command needs to define a multi-positional parameter in its signature, such as ...rest") )] UnexpectedSpreadArg(String, #[label = "unexpected spread argument"] Span), + + /// Invalid assignment left-hand side + /// + /// ## Resolution + /// + /// Assignment requires that you assign to a mutable variable or cell path. + #[error("Assignment to an immutable variable.")] + #[diagnostic( + code(nu::parser::assignment_requires_mutable_variable), + help("declare the variable with `mut`, or shadow it again with `let`") + )] + AssignmentRequiresMutableVar(#[label("needs to be a mutable variable")] Span), + + /// Invalid assignment left-hand side + /// + /// ## Resolution + /// + /// Assignment requires that you assign to a variable or variable cell path. + #[error("Assignment operations require a variable.")] + #[diagnostic( + code(nu::parser::assignment_requires_variable), + help("try assigning to a variable or a cell path of a variable") + )] + AssignmentRequiresVar(#[label("needs to be a variable")] Span), } impl ParseError { @@ -527,6 +555,7 @@ impl ParseError { ParseError::Unbalanced(_, _, s) => *s, ParseError::Expected(_, s) => *s, ParseError::ExpectedWithStringMsg(_, s) => *s, + ParseError::ExpectedWithDidYouMean(_, _, s) => *s, ParseError::Mismatch(_, _, s) => *s, ParseError::UnsupportedOperationLHS(_, _, s, _) => *s, ParseError::UnsupportedOperationRHS(_, _, _, _, s, _) => *s, @@ -603,6 +632,8 @@ impl ParseError { ParseError::RedirectingBuiltinCommand(_, s, _) => *s, ParseError::UnexpectedSpreadArg(_, s) => *s, ParseError::ExtraTokensAfterClosingDelimiter(s) => *s, + ParseError::AssignmentRequiresVar(s) => *s, + ParseError::AssignmentRequiresMutableVar(s) => *s, } } } diff --git a/crates/nu-protocol/src/errors/shell_error.rs b/crates/nu-protocol/src/errors/shell_error.rs index b4b6c29cee..61bd6ce11e 100644 --- a/crates/nu-protocol/src/errors/shell_error.rs +++ b/crates/nu-protocol/src/errors/shell_error.rs @@ -1220,10 +1220,10 @@ pub enum ShellError { span: Span, }, - /// Return event, which may become an error if used outside of a function - #[error("Return used outside of function")] + /// Return event, which may become an error if used outside of a custom command or closure + #[error("Return used outside of custom command or closure")] Return { - #[label("used outside of function")] + #[label("used outside of custom command or closure")] span: Span, value: Box, }, @@ -1458,6 +1458,17 @@ On Windows, this would be %USERPROFILE%\AppData\Roaming"# #[label = "while running this code"] span: Option, }, + + #[error("OS feature is disabled: {msg}")] + #[diagnostic( + code(nu::shell::os_disabled), + help("You're probably running outside an OS like a browser, we cannot support this") + )] + DisabledOsSupport { + msg: String, + #[label = "while running this code"] + span: Option, + }, } impl ShellError { @@ -1479,15 +1490,14 @@ impl ShellError { } } - pub fn into_value(self, span: Span, fancy_errors: bool) -> Value { + pub fn into_value(self, working_set: &StateWorkingSet, span: Span) -> Value { let exit_code = self.external_exit_code(); let mut record = record! { "msg" => Value::string(self.to_string(), span), "debug" => Value::string(format!("{self:?}"), span), "raw" => Value::error(self.clone(), span), - // "labeled_error" => Value::string(LabeledError::from_diagnostic_and_render(self.clone()), span), - "rendered" => Value::string(ShellError::render_error_to_string(self.clone(), fancy_errors), span), + "rendered" => Value::string(format_shell_error(working_set, &self), span), "json" => Value::string(serde_json::to_string(&self).expect("Could not serialize error"), span), }; @@ -1507,21 +1517,6 @@ impl ShellError { span, ) } - pub fn render_error_to_string(diag: impl miette::Diagnostic, fancy_errors: bool) -> String { - let theme = if fancy_errors { - miette::GraphicalTheme::unicode() - } else { - miette::GraphicalTheme::none() - }; - let mut out = String::new(); - miette::GraphicalReportHandler::new() - .with_width(80) - .with_theme(theme) - .render_report(&mut out, &diag) - .unwrap_or_default(); - - out - } } impl From for ShellError { @@ -1549,8 +1544,8 @@ impl From for ShellError { impl From> for ShellError { fn from(error: Spanned) -> Self { let Spanned { item: error, span } = error; - if error.kind() == io::ErrorKind::Other { - match error.into_inner() { + match error.kind() { + io::ErrorKind::Other => match error.into_inner() { Some(err) => match err.downcast() { Ok(err) => *err, Err(err) => Self::IOErrorSpanned { @@ -1562,12 +1557,15 @@ impl From> for ShellError { msg: "unknown error".into(), span, }, - } - } else { - Self::IOErrorSpanned { + }, + io::ErrorKind::TimedOut => Self::NetworkFailure { msg: error.to_string(), span, - } + }, + _ => Self::IOErrorSpanned { + msg: error.to_string(), + span, + }, } } } diff --git a/crates/nu-protocol/src/eval_base.rs b/crates/nu-protocol/src/eval_base.rs index 12070f9ca3..0a65c50817 100644 --- a/crates/nu-protocol/src/eval_base.rs +++ b/crates/nu-protocol/src/eval_base.rs @@ -238,7 +238,7 @@ pub trait Eval { Math::Minus => lhs.sub(op_span, &rhs, expr_span), Math::Multiply => lhs.mul(op_span, &rhs, expr_span), Math::Divide => lhs.div(op_span, &rhs, expr_span), - Math::Append => lhs.append(op_span, &rhs, expr_span), + Math::Concat => lhs.concat(op_span, &rhs, expr_span), Math::Modulo => lhs.modulo(op_span, &rhs, expr_span), Math::FloorDivision => lhs.floor_div(op_span, &rhs, expr_span), Math::Pow => lhs.pow(op_span, &rhs, expr_span), diff --git a/crates/nu-protocol/src/ir/mod.rs b/crates/nu-protocol/src/ir/mod.rs index 428a2667e0..9d657ba080 100644 --- a/crates/nu-protocol/src/ir/mod.rs +++ b/crates/nu-protocol/src/ir/mod.rs @@ -1,13 +1,11 @@ -use std::{fmt, sync::Arc}; - use crate::{ ast::{CellPath, Expression, Operator, Pattern, RangeInclusion}, engine::EngineState, - BlockId, DeclId, RegId, Span, Value, VarId, + BlockId, DeclId, Filesize, RegId, Span, Value, VarId, }; - use chrono::{DateTime, FixedOffset}; use serde::{Deserialize, Serialize}; +use std::{fmt, sync::Arc}; mod call; mod display; @@ -397,7 +395,7 @@ pub enum Literal { Bool(bool), Int(i64), Float(f64), - Filesize(i64), + Filesize(Filesize), Duration(i64), Binary(DataSlice), Block(BlockId), diff --git a/crates/nu-protocol/src/lib.rs b/crates/nu-protocol/src/lib.rs index e143a19819..f916bfcf64 100644 --- a/crates/nu-protocol/src/lib.rs +++ b/crates/nu-protocol/src/lib.rs @@ -1,3 +1,4 @@ +#![cfg_attr(not(feature = "os"), allow(unused))] #![doc = include_str!("../README.md")] mod alias; pub mod ast; @@ -17,6 +18,7 @@ pub mod parser_path; mod pipeline; #[cfg(feature = "plugin")] mod plugin; +#[cfg(feature = "os")] pub mod process; mod signature; pub mod span; diff --git a/crates/nu-protocol/src/module.rs b/crates/nu-protocol/src/module.rs index 02bac131dc..0b1a371864 100644 --- a/crates/nu-protocol/src/module.rs +++ b/crates/nu-protocol/src/module.rs @@ -167,7 +167,7 @@ impl Module { vec![] } else { vec![( - final_name.clone(), + normalize_module_name(&final_name), Value::record( const_rows .into_iter() @@ -425,3 +425,32 @@ impl Module { result } } + +/// normalize module names for exporting as record constant +fn normalize_module_name(bytes: &[u8]) -> Vec { + bytes + .iter() + .map(|x| match is_identifier_byte(*x) { + true => *x, + false => b'_', + }) + .collect() +} + +fn is_identifier_byte(b: u8) -> bool { + b != b'.' + && b != b'[' + && b != b'(' + && b != b'{' + && b != b'+' + && b != b'-' + && b != b'*' + && b != b'^' + && b != b'/' + && b != b'=' + && b != b'!' + && b != b'<' + && b != b'>' + && b != b'&' + && b != b'|' +} diff --git a/crates/nu-protocol/src/pipeline/byte_stream.rs b/crates/nu-protocol/src/pipeline/byte_stream.rs index da78b9c836..2b7c93495e 100644 --- a/crates/nu-protocol/src/pipeline/byte_stream.rs +++ b/crates/nu-protocol/src/pipeline/byte_stream.rs @@ -1,8 +1,7 @@ //! Module managing the streaming of raw bytes between pipeline elements -use crate::{ - process::{ChildPipe, ChildProcess}, - ErrSpan, IntoSpanned, PipelineData, ShellError, Signals, Span, Type, Value, -}; +#[cfg(feature = "os")] +use crate::process::{ChildPipe, ChildProcess}; +use crate::{ErrSpan, IntoSpanned, PipelineData, ShellError, Signals, Span, Type, Value}; use serde::{Deserialize, Serialize}; #[cfg(unix)] use std::os::fd::OwnedFd; @@ -24,6 +23,7 @@ use std::{ pub enum ByteStreamSource { Read(Box), File(File), + #[cfg(feature = "os")] Child(Box), } @@ -32,6 +32,7 @@ impl ByteStreamSource { match self { ByteStreamSource::Read(read) => Some(SourceReader::Read(read)), ByteStreamSource::File(file) => Some(SourceReader::File(file)), + #[cfg(feature = "os")] ByteStreamSource::Child(mut child) => child.stdout.take().map(|stdout| match stdout { ChildPipe::Pipe(pipe) => SourceReader::File(convert_file(pipe)), ChildPipe::Tee(tee) => SourceReader::Read(tee), @@ -40,9 +41,16 @@ impl ByteStreamSource { } /// Source is a `Child` or `File`, rather than `Read`. Currently affects trimming - fn is_external(&self) -> bool { + #[cfg(feature = "os")] + pub fn is_external(&self) -> bool { matches!(self, ByteStreamSource::Child(..)) } + + #[cfg(not(feature = "os"))] + pub fn is_external(&self) -> bool { + // without os support we never have externals + false + } } impl Debug for ByteStreamSource { @@ -50,6 +58,7 @@ impl Debug for ByteStreamSource { match self { ByteStreamSource::Read(_) => f.debug_tuple("Read").field(&"..").finish(), ByteStreamSource::File(file) => f.debug_tuple("File").field(file).finish(), + #[cfg(feature = "os")] ByteStreamSource::Child(child) => f.debug_tuple("Child").field(child).finish(), } } @@ -247,6 +256,7 @@ impl ByteStream { /// /// The type is implicitly `Unknown`, as it's not typically known whether child processes will /// return text or binary. + #[cfg(feature = "os")] pub fn child(child: ChildProcess, span: Span) -> Self { Self::new( ByteStreamSource::Child(Box::new(child)), @@ -260,6 +270,7 @@ impl ByteStream { /// /// The type is implicitly `Unknown`, as it's not typically known whether stdin is text or /// binary. + #[cfg(feature = "os")] pub fn stdin(span: Span) -> Result { let stdin = os_pipe::dup_stdin().err_span(span)?; let source = ByteStreamSource::File(convert_file(stdin)); @@ -271,6 +282,14 @@ impl ByteStream { )) } + #[cfg(not(feature = "os"))] + pub fn stdin(span: Span) -> Result { + Err(ShellError::DisabledOsSupport { + msg: "Stdin is not supported".to_string(), + span: Some(span), + }) + } + /// Create a [`ByteStream`] from a generator function that writes data to the given buffer /// when called, and returns `Ok(false)` on end of stream. pub fn from_fn( @@ -432,6 +451,7 @@ impl ByteStream { match self.stream { ByteStreamSource::Read(..) => Err(self), ByteStreamSource::File(file) => Ok(file.into()), + #[cfg(feature = "os")] ByteStreamSource::Child(child) => { if let ChildProcess { stdout: Some(ChildPipe::Pipe(stdout)), @@ -453,6 +473,7 @@ impl ByteStream { /// /// This will only succeed if the [`ByteStreamSource`] of the [`ByteStream`] is [`Child`](ByteStreamSource::Child). /// All other cases return an `Err` with the original [`ByteStream`] in it. + #[cfg(feature = "os")] pub fn into_child(self) -> Result { if let ByteStreamSource::Child(child) = self.stream { Ok(*child) @@ -477,6 +498,7 @@ impl ByteStream { file.read_to_end(&mut buf).err_span(self.span)?; Ok(buf) } + #[cfg(feature = "os")] ByteStreamSource::Child(child) => child.into_bytes(), } } @@ -551,6 +573,7 @@ impl ByteStream { Ok(()) } ByteStreamSource::File(_) => Ok(()), + #[cfg(feature = "os")] ByteStreamSource::Child(child) => child.wait(), } } @@ -575,6 +598,7 @@ impl ByteStream { ByteStreamSource::File(file) => { copy_with_signals(file, dest, span, signals)?; } + #[cfg(feature = "os")] ByteStreamSource::Child(mut child) => { // All `OutDest`s except `OutDest::PipeSeparate` will cause `stderr` to be `None`. // Only `save`, `tee`, and `complete` set the stderr `OutDest` to `OutDest::PipeSeparate`, diff --git a/crates/nu-protocol/src/pipeline/pipeline_data.rs b/crates/nu-protocol/src/pipeline/pipeline_data.rs index 75449f547f..af56533e91 100644 --- a/crates/nu-protocol/src/pipeline/pipeline_data.rs +++ b/crates/nu-protocol/src/pipeline/pipeline_data.rs @@ -109,7 +109,7 @@ impl PipelineData { /// than would be returned by [`Value::get_type()`] on the result of /// [`.into_value()`](Self::into_value). /// - /// Specifically, a `ListStream` results in [`list stream`](Type::ListStream) rather than + /// Specifically, a `ListStream` results in `list` rather than /// the fully complete [`list`](Type::List) type (which would require knowing the contents), /// and a `ByteStream` with [unknown](crate::ByteStreamType::Unknown) type results in /// [`any`](Type::Any) rather than [`string`](Type::String) or [`binary`](Type::Binary). @@ -117,7 +117,7 @@ impl PipelineData { match self { PipelineData::Empty => Type::Nothing, PipelineData::Value(value, _) => value.get_type(), - PipelineData::ListStream(_, _) => Type::ListStream, + PipelineData::ListStream(_, _) => Type::list(Type::Any), PipelineData::ByteStream(stream, _) => stream.type_().into(), } } diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs index 30f8b280d1..549e23743f 100644 --- a/crates/nu-protocol/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -44,6 +44,7 @@ pub enum Category { Date, Debug, Default, + Deprecated, Removed, Env, Experimental, @@ -79,6 +80,7 @@ impl std::fmt::Display for Category { Category::Date => "date", Category::Debug => "debug", Category::Default => "default", + Category::Deprecated => "deprecated", Category::Removed => "removed", Category::Env => "env", Category::Experimental => "experimental", diff --git a/crates/nu-protocol/src/ty.rs b/crates/nu-protocol/src/ty.rs index 55da6422b9..d1b6f7140a 100644 --- a/crates/nu-protocol/src/ty.rs +++ b/crates/nu-protocol/src/ty.rs @@ -23,7 +23,6 @@ pub enum Type { Float, Int, List(Box), - ListStream, #[default] Nothing, Number, @@ -121,7 +120,6 @@ impl Type { Type::Nothing => SyntaxShape::Nothing, Type::Record(entries) => SyntaxShape::Record(mk_shape(entries)), Type::Table(columns) => SyntaxShape::Table(mk_shape(columns)), - Type::ListStream => SyntaxShape::List(Box::new(SyntaxShape::Any)), Type::Any => SyntaxShape::Any, Type::Error => SyntaxShape::Any, Type::Binary => SyntaxShape::Binary, @@ -151,7 +149,6 @@ impl Type { Type::Nothing => String::from("nothing"), Type::Number => String::from("number"), Type::String => String::from("string"), - Type::ListStream => String::from("list-stream"), Type::Any => String::from("any"), Type::Error => String::from("error"), Type::Binary => String::from("binary"), @@ -209,7 +206,6 @@ impl Display for Type { Type::Nothing => write!(f, "nothing"), Type::Number => write!(f, "number"), Type::String => write!(f, "string"), - Type::ListStream => write!(f, "list-stream"), Type::Any => write!(f, "any"), Type::Error => write!(f, "error"), Type::Binary => write!(f, "binary"), diff --git a/crates/nu-protocol/src/value/filesize.rs b/crates/nu-protocol/src/value/filesize.rs index 9d58c103df..9e291db5bb 100644 --- a/crates/nu-protocol/src/value/filesize.rs +++ b/crates/nu-protocol/src/value/filesize.rs @@ -1,13 +1,442 @@ -use crate::Config; +use crate::{Config, FromValue, IntoValue, ShellError, Span, Type, Value}; use byte_unit::UnitType; use nu_utils::get_system_locale; use num_format::ToFormattedString; +use serde::{Deserialize, Serialize}; +use std::{ + fmt, + iter::Sum, + ops::{Add, Mul, Neg, Sub}, + str::FromStr, +}; +use thiserror::Error; -pub fn format_filesize_from_conf(num_bytes: i64, config: &Config) -> String { +/// A signed number of bytes. +/// +/// [`Filesize`] is a wrapper around [`i64`]. Whereas [`i64`] is a dimensionless value, [`Filesize`] represents a +/// numerical value with a dimensional unit (byte). +/// +/// A [`Filesize`] can be created from an [`i64`] using [`Filesize::new`] or the `From` or `Into` trait implementations. +/// To get the underlying [`i64`] value, use [`Filesize::get`] or the `From` or `Into` trait implementations. +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[repr(transparent)] +#[serde(transparent)] +pub struct Filesize(i64); + +impl Filesize { + /// A [`Filesize`] of 0 bytes. + pub const ZERO: Self = Self(0); + + /// The smallest possible [`Filesize`] value. + pub const MIN: Self = Self(i64::MIN); + + /// The largest possible [`Filesize`] value. + pub const MAX: Self = Self(i64::MAX); + + /// Create a new [`Filesize`] from a [`i64`] number of bytes. + pub const fn new(bytes: i64) -> Self { + Self(bytes) + } + + /// Creates a [`Filesize`] from a signed multiple of a [`FilesizeUnit`]. + /// + /// If the resulting number of bytes calculated by `value * unit.as_bytes()` overflows an + /// [`i64`], then `None` is returned. + pub const fn from_unit(value: i64, unit: FilesizeUnit) -> Option { + if let Some(bytes) = value.checked_mul(unit.as_bytes() as i64) { + Some(Self(bytes)) + } else { + None + } + } + + /// Returns the underlying [`i64`] number of bytes in a [`Filesize`]. + pub const fn get(&self) -> i64 { + self.0 + } + + /// Returns true if a [`Filesize`] is positive and false if it is zero or negative. + pub const fn is_positive(self) -> bool { + self.0.is_positive() + } + + /// Returns true if a [`Filesize`] is negative and false if it is zero or positive. + pub const fn is_negative(self) -> bool { + self.0.is_negative() + } + + /// Returns a [`Filesize`] representing the sign of `self`. + /// - 0 if the filesize is zero + /// - 1 if the filesize is positive + /// - -1 if the filesize is negative + pub const fn signum(self) -> Self { + Self(self.0.signum()) + } +} + +impl From for Filesize { + fn from(value: i64) -> Self { + Self(value) + } +} + +impl From for i64 { + fn from(filesize: Filesize) -> Self { + filesize.0 + } +} + +macro_rules! impl_from { + ($($ty:ty),* $(,)?) => { + $( + impl From<$ty> for Filesize { + #[inline] + fn from(value: $ty) -> Self { + Self(value.into()) + } + } + + impl TryFrom for $ty { + type Error = >::Error; + + #[inline] + fn try_from(filesize: Filesize) -> Result { + filesize.0.try_into() + } + } + )* + }; +} + +impl_from!(u8, i8, u16, i16, u32, i32); + +macro_rules! impl_try_from { + ($($ty:ty),* $(,)?) => { + $( + impl TryFrom<$ty> for Filesize { + type Error = <$ty as TryInto>::Error; + + #[inline] + fn try_from(value: $ty) -> Result { + value.try_into().map(Self) + } + } + + impl TryFrom for $ty { + type Error = >::Error; + + #[inline] + fn try_from(filesize: Filesize) -> Result { + filesize.0.try_into() + } + } + )* + }; +} + +impl_try_from!(u64, usize, isize); + +/// The error type returned when a checked conversion from a floating point type fails. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Error)] +pub struct TryFromFloatError(()); + +impl fmt::Display for TryFromFloatError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(fmt, "out of range float type conversion attempted") + } +} + +impl TryFrom for Filesize { + type Error = TryFromFloatError; + + #[inline] + fn try_from(value: f64) -> Result { + if i64::MIN as f64 <= value && value <= i64::MAX as f64 { + Ok(Self(value as i64)) + } else { + Err(TryFromFloatError(())) + } + } +} + +impl TryFrom for Filesize { + type Error = TryFromFloatError; + + #[inline] + fn try_from(value: f32) -> Result { + if i64::MIN as f32 <= value && value <= i64::MAX as f32 { + Ok(Self(value as i64)) + } else { + Err(TryFromFloatError(())) + } + } +} + +impl FromValue for Filesize { + fn from_value(value: Value) -> Result { + value.as_filesize() + } + + fn expected_type() -> Type { + Type::Filesize + } +} + +impl IntoValue for Filesize { + fn into_value(self, span: Span) -> Value { + Value::filesize(self.0, span) + } +} + +impl Add for Filesize { + type Output = Option; + + fn add(self, rhs: Self) -> Self::Output { + self.0.checked_add(rhs.0).map(Self) + } +} + +impl Sub for Filesize { + type Output = Option; + + fn sub(self, rhs: Self) -> Self::Output { + self.0.checked_sub(rhs.0).map(Self) + } +} + +impl Mul for Filesize { + type Output = Option; + + fn mul(self, rhs: i64) -> Self::Output { + self.0.checked_mul(rhs).map(Self) + } +} + +impl Mul for i64 { + type Output = Option; + + fn mul(self, rhs: Filesize) -> Self::Output { + self.checked_mul(rhs.0).map(Filesize::new) + } +} + +impl Mul for Filesize { + type Output = Option; + + fn mul(self, rhs: f64) -> Self::Output { + let bytes = ((self.0 as f64) * rhs).round(); + if i64::MIN as f64 <= bytes && bytes <= i64::MAX as f64 { + Some(Self(bytes as i64)) + } else { + None + } + } +} + +impl Mul for f64 { + type Output = Option; + + fn mul(self, rhs: Filesize) -> Self::Output { + let bytes = (self * (rhs.0 as f64)).round(); + if i64::MIN as f64 <= bytes && bytes <= i64::MAX as f64 { + Some(Filesize(bytes as i64)) + } else { + None + } + } +} + +impl Neg for Filesize { + type Output = Option; + + fn neg(self) -> Self::Output { + self.0.checked_neg().map(Self) + } +} + +impl Sum for Option { + fn sum>(iter: I) -> Self { + let mut sum = Filesize::ZERO; + for filesize in iter { + sum = (sum + filesize)?; + } + Some(sum) + } +} + +impl fmt::Display for Filesize { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + format_filesize(*self, "auto", Some(false)).fmt(f) + } +} + +/// All the possible filesize units for a [`Filesize`]. +/// +/// This type contains both units with metric (SI) decimal prefixes which are powers of 10 (e.g., kB = 1000 bytes) +/// and units with binary prefixes which are powers of 2 (e.g., KiB = 1024 bytes). +/// +/// The number of bytes in a [`FilesizeUnit`] can be obtained using +/// [`as_bytes`](FilesizeUnit::as_bytes). +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub enum FilesizeUnit { + /// One byte + B, + /// Kilobyte = 1000 bytes + KB, + /// Megabyte = 106 bytes + MB, + /// Gigabyte = 109 bytes + GB, + /// Terabyte = 1012 bytes + TB, + /// Petabyte = 1015 bytes + PB, + /// Exabyte = 1018 bytes + EB, + /// Kibibyte = 1024 bytes + KiB, + /// Mebibyte = 220 bytes + MiB, + /// Gibibyte = 230 bytes + GiB, + /// Tebibyte = 240 bytes + TiB, + /// Pebibyte = 250 bytes + PiB, + /// Exbibyte = 260 bytes + EiB, +} + +impl FilesizeUnit { + /// Returns the number of bytes in a [`FilesizeUnit`]. + pub const fn as_bytes(&self) -> u64 { + match self { + Self::B => 1, + Self::KB => 10_u64.pow(3), + Self::MB => 10_u64.pow(6), + Self::GB => 10_u64.pow(9), + Self::TB => 10_u64.pow(12), + Self::PB => 10_u64.pow(15), + Self::EB => 10_u64.pow(18), + Self::KiB => 2_u64.pow(10), + Self::MiB => 2_u64.pow(20), + Self::GiB => 2_u64.pow(30), + Self::TiB => 2_u64.pow(40), + Self::PiB => 2_u64.pow(50), + Self::EiB => 2_u64.pow(60), + } + } + + /// Convert a [`FilesizeUnit`] to a [`Filesize`]. + /// + /// To create a [`Filesize`] from a multiple of a [`FilesizeUnit`] use [`Filesize::from_unit`]. + pub const fn as_filesize(&self) -> Filesize { + Filesize::new(self.as_bytes() as i64) + } + + /// Returns the abbreviated unit for a [`FilesizeUnit`] as a [`str`]. + /// + /// The abbreviated unit is exactly the same as the enum case name in Rust code. + /// + /// # Examples + /// ``` + /// # use nu_protocol::FilesizeUnit; + /// assert_eq!(FilesizeUnit::B.as_str(), "B"); + /// assert_eq!(FilesizeUnit::KB.as_str(), "kB"); + /// assert_eq!(FilesizeUnit::KiB.as_str(), "KiB"); + /// ``` + pub const fn as_str(&self) -> &'static str { + match self { + Self::B => "B", + Self::KB => "kB", + Self::MB => "MB", + Self::GB => "GB", + Self::TB => "TB", + Self::PB => "PB", + Self::EB => "EB", + Self::KiB => "KiB", + Self::MiB => "MiB", + Self::GiB => "GiB", + Self::TiB => "TiB", + Self::PiB => "PiB", + Self::EiB => "EiB", + } + } + + /// Returns `true` if a [`FilesizeUnit`] has a metric (SI) decimal prefix (a power of 10). + /// + /// Note that this returns `true` for [`FilesizeUnit::B`] as well. + pub const fn is_decimal(&self) -> bool { + match self { + Self::B | Self::KB | Self::MB | Self::GB | Self::TB | Self::PB | Self::EB => true, + Self::KiB | Self::MiB | Self::GiB | Self::TiB | Self::PiB | Self::EiB => false, + } + } + + /// Returns `true` if a [`FilesizeUnit`] has a binary prefix (a power of 2). + /// + /// Note that this returns `true` for [`FilesizeUnit::B`] as well. + pub const fn is_binary(&self) -> bool { + match self { + Self::KB | Self::MB | Self::GB | Self::TB | Self::PB | Self::EB => false, + Self::B | Self::KiB | Self::MiB | Self::GiB | Self::TiB | Self::PiB | Self::EiB => true, + } + } +} + +impl From for Filesize { + fn from(unit: FilesizeUnit) -> Self { + unit.as_filesize() + } +} + +/// An error returned when failing to parse a [`FilesizeUnit`]. +/// +/// This occurs when the string being parsed does not exactly match the name of one of the +/// enum cases in [`FilesizeUnit`]. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Error)] +pub struct ParseFilesizeUnitError(()); + +impl fmt::Display for ParseFilesizeUnitError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(fmt, "invalid filesize unit") + } +} + +impl FromStr for FilesizeUnit { + type Err = ParseFilesizeUnitError; + + fn from_str(s: &str) -> Result { + Ok(match s { + "B" => Self::B, + "kB" => Self::KB, + "MB" => Self::MB, + "GB" => Self::GB, + "TB" => Self::TB, + "PB" => Self::PB, + "EB" => Self::EB, + "KiB" => Self::KiB, + "MiB" => Self::MiB, + "GiB" => Self::GiB, + "TiB" => Self::TiB, + "PiB" => Self::PiB, + "EiB" => Self::EiB, + _ => return Err(ParseFilesizeUnitError(())), + }) + } +} + +impl fmt::Display for FilesizeUnit { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.as_str().fmt(f) + } +} + +pub fn format_filesize_from_conf(filesize: Filesize, config: &Config) -> String { // We need to take into account config.filesize_metric so, if someone asks for KB // and filesize_metric is false, return KiB format_filesize( - num_bytes, + filesize, &config.filesize.format, Some(config.filesize.metric), ) @@ -16,7 +445,7 @@ pub fn format_filesize_from_conf(num_bytes: i64, config: &Config) -> String { // filesize_metric is explicit when printed a value according to user config; // other places (such as `format filesize`) don't. pub fn format_filesize( - num_bytes: i64, + filesize: Filesize, format_value: &str, filesize_metric: Option, ) -> String { @@ -25,7 +454,7 @@ pub fn format_filesize( // When format_value is "auto" or an invalid value, the returned ByteUnit doesn't matter // and is always B. let filesize_unit = get_filesize_format(format_value, filesize_metric); - let byte = byte_unit::Byte::from_u64(num_bytes.unsigned_abs()); + let byte = byte_unit::Byte::from_u64(filesize.0.unsigned_abs()); let adj_byte = if let Some(unit) = filesize_unit { byte.get_adjusted_unit(unit) } else { @@ -43,7 +472,7 @@ pub fn format_filesize( let locale = get_system_locale(); let locale_byte = adj_byte.get_value() as u64; let locale_byte_string = locale_byte.to_formatted_string(&locale); - let locale_signed_byte_string = if num_bytes.is_negative() { + let locale_signed_byte_string = if filesize.is_negative() { format!("-{locale_byte_string}") } else { locale_byte_string @@ -56,7 +485,7 @@ pub fn format_filesize( } } _ => { - if num_bytes.is_negative() { + if filesize.is_negative() { format!("-{:.1}", adj_byte) } else { format!("{:.1}", adj_byte) @@ -116,6 +545,9 @@ mod tests { #[case] filesize_format: String, #[case] exp: &str, ) { - assert_eq!(exp, format_filesize(val, &filesize_format, filesize_metric)); + assert_eq!( + exp, + format_filesize(Filesize::new(val), &filesize_format, filesize_metric) + ); } } diff --git a/crates/nu-protocol/src/value/from_value.rs b/crates/nu-protocol/src/value/from_value.rs index 624d7dae39..fb6fc26be5 100644 --- a/crates/nu-protocol/src/value/from_value.rs +++ b/crates/nu-protocol/src/value/from_value.rs @@ -252,9 +252,7 @@ impl FromValue for i64 { fn from_value(v: Value) -> Result { match v { Value::Int { val, .. } => Ok(val), - Value::Filesize { val, .. } => Ok(val), Value::Duration { val, .. } => Ok(val), - v => Err(ShellError::CantConvert { to_type: Self::expected_type().to_string(), from_type: v.get_type().to_string(), @@ -308,9 +306,7 @@ macro_rules! impl_from_value_for_uint { let span = v.span(); const MAX: i64 = $max; match v { - Value::Int { val, .. } - | Value::Filesize { val, .. } - | Value::Duration { val, .. } => { + Value::Int { val, .. } | Value::Duration { val, .. } => { match val { i64::MIN..=-1 => Err(ShellError::NeedsPositiveValue { span }), 0..=MAX => Ok(val as $type), diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index 1a1e8fada2..c12dd3d71d 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -83,7 +83,7 @@ pub enum Value { internal_span: Span, }, Filesize { - val: i64, + val: Filesize, // note: spans are being refactored out of Value // please use .span() instead of matching this span value #[serde(rename = "span")] @@ -301,7 +301,7 @@ impl Value { } /// Returns the inner `i64` filesize value or an error if this `Value` is not a filesize - pub fn as_filesize(&self) -> Result { + pub fn as_filesize(&self) -> Result { if let Value::Filesize { val, .. } = self { Ok(*val) } else { @@ -1819,9 +1819,9 @@ impl Value { } } - pub fn filesize(val: i64, span: Span) -> Value { + pub fn filesize(val: impl Into, span: Span) -> Value { Value::Filesize { - val, + val: val.into(), internal_span: span, } } @@ -1938,7 +1938,7 @@ impl Value { /// Note: Only use this for test data, *not* live data, as it will point into unknown source /// when used in errors. - pub fn test_filesize(val: i64) -> Value { + pub fn test_filesize(val: impl Into) -> Value { Value::filesize(val, Span::test_data()) } @@ -2478,7 +2478,7 @@ impl Value { } } (Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => { - if let Some(val) = lhs.checked_add(*rhs) { + if let Some(val) = *lhs + *rhs { Ok(Value::filesize(val, span)) } else { Err(ShellError::OperatorOverflow { @@ -2503,34 +2503,20 @@ impl Value { } } - pub fn append(&self, op: Span, rhs: &Value, span: Span) -> Result { + pub fn concat(&self, op: Span, rhs: &Value, span: Span) -> Result { match (self, rhs) { (Value::List { vals: lhs, .. }, Value::List { vals: rhs, .. }) => { - let mut lhs = lhs.clone(); - let mut rhs = rhs.clone(); - lhs.append(&mut rhs); - Ok(Value::list(lhs, span)) - } - (Value::List { vals: lhs, .. }, val) => { - let mut lhs = lhs.clone(); - lhs.push(val.clone()); - Ok(Value::list(lhs, span)) - } - (val, Value::List { vals: rhs, .. }) => { - let mut rhs = rhs.clone(); - rhs.insert(0, val.clone()); - Ok(Value::list(rhs, span)) + Ok(Value::list([lhs.as_slice(), rhs.as_slice()].concat(), span)) } (Value::String { val: lhs, .. }, Value::String { val: rhs, .. }) => { - Ok(Value::string(lhs.to_string() + rhs, span)) - } - (Value::Binary { val: lhs, .. }, Value::Binary { val: rhs, .. }) => { - let mut val = lhs.clone(); - val.extend(rhs); - Ok(Value::binary(val, span)) + Ok(Value::string([lhs.as_str(), rhs.as_str()].join(""), span)) } + (Value::Binary { val: lhs, .. }, Value::Binary { val: rhs, .. }) => Ok(Value::binary( + [lhs.as_slice(), rhs.as_slice()].concat(), + span, + )), (Value::Custom { val: lhs, .. }, rhs) => { - lhs.operation(self.span(), Operator::Math(Math::Append), op, rhs) + lhs.operation(self.span(), Operator::Math(Math::Concat), op, rhs) } _ => Err(ShellError::OperatorMismatch { op_span: op, @@ -2599,7 +2585,7 @@ impl Value { } } (Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => { - if let Some(val) = lhs.checked_sub(*rhs) { + if let Some(val) = *lhs - *rhs { Ok(Value::filesize(val, span)) } else { Err(ShellError::OperatorOverflow { @@ -2647,16 +2633,48 @@ impl Value { Ok(Value::float(lhs * rhs, span)) } (Value::Int { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => { - Ok(Value::filesize(*lhs * *rhs, span)) + if let Some(val) = *lhs * *rhs { + Ok(Value::filesize(val, span)) + } else { + Err(ShellError::OperatorOverflow { + msg: "multiply operation overflowed".into(), + span, + help: None, + }) + } } (Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => { - Ok(Value::filesize(*lhs * *rhs, span)) + if let Some(val) = *lhs * *rhs { + Ok(Value::filesize(val, span)) + } else { + Err(ShellError::OperatorOverflow { + msg: "multiply operation overflowed".into(), + span, + help: None, + }) + } } (Value::Float { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => { - Ok(Value::filesize((*lhs * *rhs as f64) as i64, span)) + if let Some(val) = *lhs * *rhs { + Ok(Value::filesize(val, span)) + } else { + Err(ShellError::OperatorOverflow { + msg: "multiply operation overflowed".into(), + span, + help: None, + }) + } } (Value::Filesize { val: lhs, .. }, Value::Float { val: rhs, .. }) => { - Ok(Value::filesize((*lhs as f64 * *rhs) as i64, span)) + if let Some(val) = *lhs * *rhs { + Ok(Value::filesize(val, span)) + } else { + Err(ShellError::OperatorOverflow { + msg: "multiply operation overflowed".into(), + span, + help: None, + }) + } } (Value::Int { val: lhs, .. }, Value::Duration { val: rhs, .. }) => { Ok(Value::duration(*lhs * *rhs, span)) @@ -2714,14 +2732,14 @@ impl Value { } } (Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => { - if *rhs == 0 { + if *rhs == Filesize::ZERO { Err(ShellError::DivisionByZero { span: op }) } else { - Ok(Value::float(*lhs as f64 / *rhs as f64, span)) + Ok(Value::float(lhs.get() as f64 / rhs.get() as f64, span)) } } (Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => { - if let Some(val) = lhs.checked_div(*rhs) { + if let Some(val) = lhs.get().checked_div(*rhs) { Ok(Value::filesize(val, span)) } else if *rhs == 0 { Err(ShellError::DivisionByZero { span: op }) @@ -2735,9 +2753,8 @@ impl Value { } (Value::Filesize { val: lhs, .. }, Value::Float { val: rhs, .. }) => { if *rhs != 0.0 { - let val = *lhs as f64 / rhs; - if i64::MIN as f64 <= val && val <= i64::MAX as f64 { - Ok(Value::filesize(val as i64, span)) + if let Ok(val) = Filesize::try_from(lhs.get() as f64 / rhs) { + Ok(Value::filesize(val, span)) } else { Err(ShellError::OperatorOverflow { msg: "division operation overflowed".into(), @@ -2799,163 +2816,6 @@ impl Value { } } - pub fn modulo(&self, op: Span, rhs: &Value, span: Span) -> Result { - // Based off the unstable `div_floor` function in the std library. - fn checked_mod_i64(dividend: i64, divisor: i64) -> Option { - let remainder = dividend.checked_rem(divisor)?; - if (remainder > 0 && divisor < 0) || (remainder < 0 && divisor > 0) { - // Note that `remainder + divisor` cannot overflow, because `remainder` and - // `divisor` have opposite signs. - Some(remainder + divisor) - } else { - Some(remainder) - } - } - - fn checked_mod_f64(dividend: f64, divisor: f64) -> Option { - if divisor == 0.0 { - None - } else { - let remainder = dividend % divisor; - if (remainder > 0.0 && divisor < 0.0) || (remainder < 0.0 && divisor > 0.0) { - Some(remainder + divisor) - } else { - Some(remainder) - } - } - } - - match (self, rhs) { - (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => { - if let Some(val) = checked_mod_i64(*lhs, *rhs) { - Ok(Value::int(val, span)) - } else if *rhs == 0 { - Err(ShellError::DivisionByZero { span: op }) - } else { - Err(ShellError::OperatorOverflow { - msg: "modulo operation overflowed".into(), - span, - help: None, - }) - } - } - (Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => { - if let Some(val) = checked_mod_f64(*lhs as f64, *rhs) { - Ok(Value::float(val, span)) - } else { - Err(ShellError::DivisionByZero { span: op }) - } - } - (Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => { - if let Some(val) = checked_mod_f64(*lhs, *rhs as f64) { - Ok(Value::float(val, span)) - } else { - Err(ShellError::DivisionByZero { span: op }) - } - } - (Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => { - if let Some(val) = checked_mod_f64(*lhs, *rhs) { - Ok(Value::float(val, span)) - } else { - Err(ShellError::DivisionByZero { span: op }) - } - } - (Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => { - if let Some(val) = checked_mod_i64(*lhs, *rhs) { - Ok(Value::filesize(val, span)) - } else if *rhs == 0 { - Err(ShellError::DivisionByZero { span: op }) - } else { - Err(ShellError::OperatorOverflow { - msg: "modulo operation overflowed".into(), - span, - help: None, - }) - } - } - (Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => { - if let Some(val) = checked_mod_i64(*lhs, *rhs) { - Ok(Value::filesize(val, span)) - } else if *rhs == 0 { - Err(ShellError::DivisionByZero { span: op }) - } else { - Err(ShellError::OperatorOverflow { - msg: "modulo operation overflowed".into(), - span, - help: None, - }) - } - } - (Value::Filesize { val: lhs, .. }, Value::Float { val: rhs, .. }) => { - if let Some(val) = checked_mod_f64(*lhs as f64, *rhs) { - if i64::MIN as f64 <= val && val <= i64::MAX as f64 { - Ok(Value::filesize(val as i64, span)) - } else { - Err(ShellError::OperatorOverflow { - msg: "modulo operation overflowed".into(), - span, - help: None, - }) - } - } else { - Err(ShellError::DivisionByZero { span: op }) - } - } - (Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => { - if let Some(val) = checked_mod_i64(*lhs, *rhs) { - Ok(Value::duration(val, span)) - } else if *rhs == 0 { - Err(ShellError::DivisionByZero { span: op }) - } else { - Err(ShellError::OperatorOverflow { - msg: "division operation overflowed".into(), - span, - help: None, - }) - } - } - (Value::Duration { val: lhs, .. }, Value::Int { val: rhs, .. }) => { - if let Some(val) = checked_mod_i64(*lhs, *rhs) { - Ok(Value::duration(val, span)) - } else if *rhs == 0 { - Err(ShellError::DivisionByZero { span: op }) - } else { - Err(ShellError::OperatorOverflow { - msg: "division operation overflowed".into(), - span, - help: None, - }) - } - } - (Value::Duration { val: lhs, .. }, Value::Float { val: rhs, .. }) => { - if let Some(val) = checked_mod_f64(*lhs as f64, *rhs) { - if i64::MIN as f64 <= val && val <= i64::MAX as f64 { - Ok(Value::duration(val as i64, span)) - } else { - Err(ShellError::OperatorOverflow { - msg: "division operation overflowed".into(), - span, - help: None, - }) - } - } else { - Err(ShellError::DivisionByZero { span: op }) - } - } - (Value::Custom { val: lhs, .. }, rhs) => { - lhs.operation(span, Operator::Math(Math::Modulo), op, rhs) - } - - _ => Err(ShellError::OperatorMismatch { - op_span: op, - lhs_ty: self.get_type().to_string(), - lhs_span: self.span(), - rhs_ty: rhs.get_type().to_string(), - rhs_span: rhs.span(), - }), - } - } - pub fn floor_div(&self, op: Span, rhs: &Value, span: Span) -> Result { // Taken from the unstable `div_floor` function in the std library. fn checked_div_floor_i64(dividend: i64, divisor: i64) -> Option { @@ -3017,9 +2877,9 @@ impl Value { } } (Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => { - if let Some(val) = checked_div_floor_i64(*lhs, *rhs) { + if let Some(val) = checked_div_floor_i64(lhs.get(), rhs.get()) { Ok(Value::int(val, span)) - } else if *rhs == 0 { + } else if *rhs == Filesize::ZERO { Err(ShellError::DivisionByZero { span: op }) } else { Err(ShellError::OperatorOverflow { @@ -3030,7 +2890,7 @@ impl Value { } } (Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => { - if let Some(val) = checked_div_floor_i64(*lhs, *rhs) { + if let Some(val) = checked_div_floor_i64(lhs.get(), *rhs) { Ok(Value::filesize(val, span)) } else if *rhs == 0 { Err(ShellError::DivisionByZero { span: op }) @@ -3043,9 +2903,9 @@ impl Value { } } (Value::Filesize { val: lhs, .. }, Value::Float { val: rhs, .. }) => { - if let Some(val) = checked_div_floor_f64(*lhs as f64, *rhs) { - if i64::MIN as f64 <= val && val <= i64::MAX as f64 { - Ok(Value::filesize(val as i64, span)) + if let Some(val) = checked_div_floor_f64(lhs.get() as f64, *rhs) { + if let Ok(val) = Filesize::try_from(val) { + Ok(Value::filesize(val, span)) } else { Err(ShellError::OperatorOverflow { msg: "division operation overflowed".into(), @@ -3111,6 +2971,163 @@ impl Value { } } + pub fn modulo(&self, op: Span, rhs: &Value, span: Span) -> Result { + // Based off the unstable `div_floor` function in the std library. + fn checked_mod_i64(dividend: i64, divisor: i64) -> Option { + let remainder = dividend.checked_rem(divisor)?; + if (remainder > 0 && divisor < 0) || (remainder < 0 && divisor > 0) { + // Note that `remainder + divisor` cannot overflow, because `remainder` and + // `divisor` have opposite signs. + Some(remainder + divisor) + } else { + Some(remainder) + } + } + + fn checked_mod_f64(dividend: f64, divisor: f64) -> Option { + if divisor == 0.0 { + None + } else { + let remainder = dividend % divisor; + if (remainder > 0.0 && divisor < 0.0) || (remainder < 0.0 && divisor > 0.0) { + Some(remainder + divisor) + } else { + Some(remainder) + } + } + } + + match (self, rhs) { + (Value::Int { val: lhs, .. }, Value::Int { val: rhs, .. }) => { + if let Some(val) = checked_mod_i64(*lhs, *rhs) { + Ok(Value::int(val, span)) + } else if *rhs == 0 { + Err(ShellError::DivisionByZero { span: op }) + } else { + Err(ShellError::OperatorOverflow { + msg: "modulo operation overflowed".into(), + span, + help: None, + }) + } + } + (Value::Int { val: lhs, .. }, Value::Float { val: rhs, .. }) => { + if let Some(val) = checked_mod_f64(*lhs as f64, *rhs) { + Ok(Value::float(val, span)) + } else { + Err(ShellError::DivisionByZero { span: op }) + } + } + (Value::Float { val: lhs, .. }, Value::Int { val: rhs, .. }) => { + if let Some(val) = checked_mod_f64(*lhs, *rhs as f64) { + Ok(Value::float(val, span)) + } else { + Err(ShellError::DivisionByZero { span: op }) + } + } + (Value::Float { val: lhs, .. }, Value::Float { val: rhs, .. }) => { + if let Some(val) = checked_mod_f64(*lhs, *rhs) { + Ok(Value::float(val, span)) + } else { + Err(ShellError::DivisionByZero { span: op }) + } + } + (Value::Filesize { val: lhs, .. }, Value::Filesize { val: rhs, .. }) => { + if let Some(val) = checked_mod_i64(lhs.get(), rhs.get()) { + Ok(Value::filesize(val, span)) + } else if *rhs == Filesize::ZERO { + Err(ShellError::DivisionByZero { span: op }) + } else { + Err(ShellError::OperatorOverflow { + msg: "modulo operation overflowed".into(), + span, + help: None, + }) + } + } + (Value::Filesize { val: lhs, .. }, Value::Int { val: rhs, .. }) => { + if let Some(val) = checked_mod_i64(lhs.get(), *rhs) { + Ok(Value::filesize(val, span)) + } else if *rhs == 0 { + Err(ShellError::DivisionByZero { span: op }) + } else { + Err(ShellError::OperatorOverflow { + msg: "modulo operation overflowed".into(), + span, + help: None, + }) + } + } + (Value::Filesize { val: lhs, .. }, Value::Float { val: rhs, .. }) => { + if let Some(val) = checked_mod_f64(lhs.get() as f64, *rhs) { + if let Ok(val) = Filesize::try_from(val) { + Ok(Value::filesize(val, span)) + } else { + Err(ShellError::OperatorOverflow { + msg: "modulo operation overflowed".into(), + span, + help: None, + }) + } + } else { + Err(ShellError::DivisionByZero { span: op }) + } + } + (Value::Duration { val: lhs, .. }, Value::Duration { val: rhs, .. }) => { + if let Some(val) = checked_mod_i64(*lhs, *rhs) { + Ok(Value::duration(val, span)) + } else if *rhs == 0 { + Err(ShellError::DivisionByZero { span: op }) + } else { + Err(ShellError::OperatorOverflow { + msg: "division operation overflowed".into(), + span, + help: None, + }) + } + } + (Value::Duration { val: lhs, .. }, Value::Int { val: rhs, .. }) => { + if let Some(val) = checked_mod_i64(*lhs, *rhs) { + Ok(Value::duration(val, span)) + } else if *rhs == 0 { + Err(ShellError::DivisionByZero { span: op }) + } else { + Err(ShellError::OperatorOverflow { + msg: "division operation overflowed".into(), + span, + help: None, + }) + } + } + (Value::Duration { val: lhs, .. }, Value::Float { val: rhs, .. }) => { + if let Some(val) = checked_mod_f64(*lhs as f64, *rhs) { + if i64::MIN as f64 <= val && val <= i64::MAX as f64 { + Ok(Value::duration(val as i64, span)) + } else { + Err(ShellError::OperatorOverflow { + msg: "division operation overflowed".into(), + span, + help: None, + }) + } + } else { + Err(ShellError::DivisionByZero { span: op }) + } + } + (Value::Custom { val: lhs, .. }, rhs) => { + lhs.operation(span, Operator::Math(Math::Modulo), op, rhs) + } + + _ => Err(ShellError::OperatorMismatch { + op_span: op, + lhs_ty: self.get_type().to_string(), + lhs_span: self.span(), + rhs_ty: rhs.get_type().to_string(), + rhs_span: rhs.span(), + }), + } + } + pub fn lt(&self, op: Span, rhs: &Value, span: Span) -> Result { if let (Value::Custom { val: lhs, .. }, rhs) = (self, rhs) { return lhs.operation( diff --git a/crates/nu-protocol/tests/test_config.rs b/crates/nu-protocol/tests/test_config.rs index f47dda3121..eb57903685 100644 --- a/crates/nu-protocol/tests/test_config.rs +++ b/crates/nu-protocol/tests/test_config.rs @@ -69,7 +69,7 @@ fn fancy_default_errors() { assert_eq!( actual.err, - "Error: \u{1b}[31m×\u{1b}[0m oh no!\n ╭─[\u{1b}[36;1;4mline1\u{1b}[0m:1:13]\n \u{1b}[2m1\u{1b}[0m │ force_error \"My error\"\n · \u{1b}[35;1m ─────┬────\u{1b}[0m\n · \u{1b}[35;1m╰── \u{1b}[35;1mhere's the error\u{1b}[0m\u{1b}[0m\n ╰────\n\n\n" + "Error: \n \u{1b}[31m×\u{1b}[0m oh no!\n ╭─[\u{1b}[36;1;4mline1:1:13\u{1b}[0m]\n \u{1b}[2m1\u{1b}[0m │ force_error \"My error\"\n · \u{1b}[35;1m ─────┬────\u{1b}[0m\n · \u{1b}[35;1m╰── \u{1b}[35;1mhere's the error\u{1b}[0m\u{1b}[0m\n ╰────\n\n" ); } diff --git a/crates/nu-protocol/tests/test_value.rs b/crates/nu-protocol/tests/test_value.rs index 376bd81f1f..fb26bea0e2 100644 --- a/crates/nu-protocol/tests/test_value.rs +++ b/crates/nu-protocol/tests/test_value.rs @@ -1,4 +1,7 @@ -use nu_protocol::{Config, Span, Value}; +use nu_protocol::{ + engine::{EngineState, Stack}, + Config, Span, Value, +}; use rstest::rstest; #[test] @@ -46,3 +49,27 @@ fn test_duration_to_string(#[case] in_ns: i64, #[case] expected: &str) { "expected != observed" ); } + +#[test] +fn test_case_insensitive_env_var() { + let mut engine_state = EngineState::new(); + let stack = Stack::new(); + + for (name, value) in std::env::vars() { + engine_state.add_env_var(name, Value::test_string(value)); + } + + let path_lower = engine_state.get_env_var_insensitive("path"); + let path_upper = engine_state.get_env_var_insensitive("PATH"); + let path_mixed = engine_state.get_env_var_insensitive("PaTh"); + + assert_eq!(path_lower, path_upper); + assert_eq!(path_lower, path_mixed); + + let stack_path_lower = stack.get_env_var_insensitive(&engine_state, "path"); + let stack_path_upper = stack.get_env_var_insensitive(&engine_state, "PATH"); + let stack_path_mixed = stack.get_env_var_insensitive(&engine_state, "PaTh"); + + assert_eq!(stack_path_lower, stack_path_upper); + assert_eq!(stack_path_lower, stack_path_mixed); +} diff --git a/crates/nu-std/Cargo.toml b/crates/nu-std/Cargo.toml index a6c48cac3c..aa39d215e0 100644 --- a/crates/nu-std/Cargo.toml +++ b/crates/nu-std/Cargo.toml @@ -9,8 +9,8 @@ version = "0.100.1" [dependencies] nu-parser = { version = "0.100.1", path = "../nu-parser" } -nu-protocol = { version = "0.100.1", path = "../nu-protocol" } -nu-engine = { version = "0.100.1", path = "../nu-engine" } +nu-protocol = { version = "0.100.1", path = "../nu-protocol", default-features = false } +nu-engine = { version = "0.100.1", path = "../nu-engine", default-features = false } miette = { workspace = true, features = ["fancy-no-backtrace"] } log = "0.4" diff --git a/crates/nu-std/std/config/mod.nu b/crates/nu-std/std/config/mod.nu index b73964e8d3..9b00db60db 100644 --- a/crates/nu-std/std/config/mod.nu +++ b/crates/nu-std/std/config/mod.nu @@ -26,7 +26,6 @@ export def dark-theme [] { block: white hints: dark_gray search_result: { bg: red fg: white } - shape_and: purple_bold shape_binary: purple_bold shape_block: blue_bold shape_bool: light_cyan @@ -53,7 +52,6 @@ export def dark-theme [] { shape_matching_brackets: { attr: u } shape_nothing: light_cyan shape_operator: yellow - shape_or: purple_bold shape_pipe: purple_bold shape_range: yellow_bold shape_record: cyan_bold @@ -96,7 +94,6 @@ export def light-theme [] { block: dark_gray hints: dark_gray search_result: { fg: white bg: red } - shape_and: purple_bold shape_binary: purple_bold shape_block: blue_bold shape_bool: light_cyan @@ -123,7 +120,6 @@ export def light-theme [] { shape_matching_brackets: { attr: u } shape_nothing: light_cyan shape_operator: yellow - shape_or: purple_bold shape_pipe: purple_bold shape_range: yellow_bold shape_record: cyan_bold @@ -136,4 +132,4 @@ export def light-theme [] { shape_vardecl: purple shape_raw_string: light_purple } -} \ No newline at end of file +} diff --git a/crates/nu-std/std/dirs/mod.nu b/crates/nu-std/std/dirs/mod.nu index a26128a5d7..ab3403f36c 100644 --- a/crates/nu-std/std/dirs/mod.nu +++ b/crates/nu-std/std/dirs/mod.nu @@ -43,7 +43,7 @@ export def --env add [ } # Make the next directory on the list the active directory. -# If the currenta ctive directory is the last in the list, +# If the current active directory is the last in the list, # then cycle to the top of the list. export def --env next [ N:int = 1 # number of positions to move. @@ -52,7 +52,7 @@ export def --env next [ } # Make the previous directory on the list the active directory. -# If the current active directory is the first in the list, +# If the current active directory is the first in the list, # then cycle to the end of the list. export def --env prev [ N:int = 1 # number of positions to move. diff --git a/crates/nu-std/std/dt/mod.nu b/crates/nu-std/std/dt/mod.nu index 7090449994..682fa9587c 100644 --- a/crates/nu-std/std/dt/mod.nu +++ b/crates/nu-std/std/dt/mod.nu @@ -120,8 +120,8 @@ export def datetime-diff [ } } } - let from_expanded = ($later | date to-timezone utc | date to-record) - let to_expanded = ($earlier | date to-timezone utc | date to-record) + let from_expanded = ($later | date to-timezone utc | into record) + let to_expanded = ($earlier | date to-timezone utc | into record) mut result = { year: ($from_expanded.year - $to_expanded.year), month: ($from_expanded.month - $to_expanded.month), day:0, hour:0, minute:0, second:0, millisecond:0, microsecond:0, nanosecond:0} diff --git a/crates/nu-std/std/formats/mod.nu b/crates/nu-std/std/formats/mod.nu index 2f20e81451..a0484e42f4 100644 --- a/crates/nu-std/std/formats/mod.nu +++ b/crates/nu-std/std/formats/mod.nu @@ -28,3 +28,13 @@ export def "to ndjson" []: any -> string { export def "to jsonl" []: any -> string { each { to json --raw } | to text } + +# Convert from NDNUON (newline-delimited NUON), to structured data +export def "from ndnuon" []: [string -> any] { + lines | each { from nuon } +} + +# Convert structured data to NDNUON, i.e. newline-delimited NUON +export def "to ndnuon" []: [any -> string] { + each { to nuon --raw | str replace --all "\n" '\n' } | to text +} diff --git a/crates/nu-std/std/help/mod.nu b/crates/nu-std/std/help/mod.nu index 089bbff7f8..85edd3790d 100644 --- a/crates/nu-std/std/help/mod.nu +++ b/crates/nu-std/std/help/mod.nu @@ -37,7 +37,7 @@ def get-all-operators [] { return [ [Assignment, =, Assign, "Assigns a value to a variable.", 10] [Assignment, +=, PlusAssign, "Adds a value to a variable.", 10] - [Assignment, ++=, AppendAssign, "Appends a list or a value to a variable.", 10] + [Assignment, ++=, ConcatAssign, "Concatenate two lists, two strings, or two binary values.", 10] [Assignment, -=, MinusAssign, "Subtracts a value from a variable.", 10] [Assignment, *=, MultiplyAssign, "Multiplies a variable by a value.", 10] [Assignment, /=, DivideAssign, "Divides a variable by a value.", 10] @@ -55,7 +55,7 @@ def get-all-operators [] { return [ [Comparison, ends-with, EndsWith, "Checks if a string ends with another.", 80] [Comparison, not, UnaryNot, "Negates a value or expression.", 0] [Math, +, Plus, "Adds two values.", 90] - [Math, ++, Append, "Appends two lists or a list and a value.", 80] + [Math, ++, Concat, "Concatenate two lists, two strings, or two binary values.", 80] [Math, -, Minus, "Subtracts two values.", 90] [Math, *, Multiply, "Multiplies two values.", 95] [Math, /, Divide, "Divides two values.", 95] @@ -684,8 +684,7 @@ def build-command-page [command: record] { ] | flatten | str join "\n" } -# Show help on commands. -export def commands [ +def scope-commands [ ...command: string@"nu-complete list-commands" # the name of command to get help on --find (-f): string # string to find in command names and description ] { @@ -699,20 +698,35 @@ export def commands [ let found_command = ($commands | where name == $target_command) if ($found_command | is-empty) { - try { - print $"(ansi default_italic)Help pages from external command ($target_command | pretty-cmd):(ansi reset)" - ^($env.NU_HELPER? | default "man") $target_command - } catch { - command-not-found-error (metadata $command | get span) - } + command-not-found-error (metadata $command | get span) + } else { + build-command-page ($found_command | get 0) } - - build-command-page ($found_command | get 0) } else { $commands | select name category description signatures search_terms } } +def external-commands [ + ...command: string@"nu-complete list-commands", +] { + let target_command = $command | str join " " + print $"(ansi default_italic)Help pages from external command ($target_command | pretty-cmd):(ansi reset)" + ^($env.NU_HELPER? | default "man") $target_command +} + +# Show help on commands. +export def commands [ + ...command: string@"nu-complete list-commands" # the name of command to get help on + --find (-f): string # string to find in command names and description +] { + try { + scope-commands ...$command --find=$find + } catch { + external-commands ...$command + } +} + def pretty-cmd [] { let cmd = $in $"(ansi default_dimmed)(ansi default_italic)($cmd)(ansi reset)" @@ -763,7 +777,7 @@ You can also learn more at (ansi default_italic)(ansi light_cyan_underline)https let target_item = ($item | str join " ") - let commands = (try { commands $target_item --find $find }) + let commands = (try { scope-commands $target_item --find $find }) if not ($commands | is-empty) { return $commands } let aliases = (try { aliases $target_item --find $find }) @@ -776,13 +790,7 @@ You can also learn more at (ansi default_italic)(ansi light_cyan_underline)https print -e $"No help results found mentioning: ($find)" return [] } - - let span = (metadata $item | get span) - error make { - msg: ("std::help::item_not_found" | error-fmt) - label: { - text: "item not found" - span: $span - } - } + # use external tool (e.g: `man`) to search help for $target_item + # the stdout and stderr of external tool will follow `main` call. + external-commands $target_item } diff --git a/crates/nu-std/std/iter/mod.nu b/crates/nu-std/std/iter/mod.nu index 8c25269690..daa62d2dba 100644 --- a/crates/nu-std/std/iter/mod.nu +++ b/crates/nu-std/std/iter/mod.nu @@ -101,8 +101,9 @@ export def intersperse [ # -> list # Returns a list of intermediate steps performed by `reduce` # (`fold`). It takes two arguments, an initial value to seed the # initial state and a closure that takes two arguments, the first -# being the internal state and the second the list element in the -# current iteration. +# being the list element in the current iteration and the second +# the internal state. +# The internal state is also provided as pipeline input. # # # Example # ``` @@ -123,7 +124,8 @@ export def scan [ # -> list --noinit(-n) # remove the initial value from the result ] { reduce --fold [$init] {|it, acc| - $acc ++ [(do $fn ($acc | last) $it)] + let acc_last = $acc | last + $acc ++ [($acc_last | do $fn $it $acc_last)] } | if $noinit { $in | skip diff --git a/crates/nu-std/testing.nu b/crates/nu-std/testing.nu index 7052983a21..e409920569 100644 --- a/crates/nu-std/testing.nu +++ b/crates/nu-std/testing.nu @@ -28,7 +28,7 @@ def valid-annotations [] { # Returns a table containing the list of function names together with their annotations (comments above the declaration) def get-annotated [ file: path -] path -> table { +]: path -> table { let raw_file = ( open $file | lines @@ -59,7 +59,7 @@ def get-annotated [ # Annotations that allow multiple functions are of type list # Other annotations are of type string # Result gets merged with the template record so that the output shape remains consistent regardless of the table content -def create-test-record [] nothing -> record, test-skip: list> { +def create-test-record []: nothing -> record, test-skip: list> { let input = $in let template_record = { @@ -187,7 +187,7 @@ export def ($test_function_name) [] { def run-tests-for-module [ module: record threads: int -] -> table { +]: nothing -> table { let global_context = if not ($module.before-all|is-empty) { log info $"Running before-all for module ($module.name)" run-test { diff --git a/crates/nu-std/tests/test_formats.nu b/crates/nu-std/tests/test_formats.nu index f54598bf08..44a89eeaad 100644 --- a/crates/nu-std/tests/test_formats.nu +++ b/crates/nu-std/tests/test_formats.nu @@ -2,15 +2,26 @@ use std/assert use std/formats * -def test_data_multiline [] { - let lines = [ - "{\"a\":1}", - "{\"a\":2}", - "{\"a\":3}", - "{\"a\":4}", - "{\"a\":5}", - "{\"a\":6}", - ] +def test_data_multiline [--nuon] { + let lines = if $nuon { + [ + "{a: 1}", + "{a: 2}", + "{a: 3}", + "{a: 4}", + "{a: 5}", + "{a: 6}", + ] + } else { + [ + "{\"a\":1}", + "{\"a\":2}", + "{\"a\":3}", + "{\"a\":4}", + "{\"a\":5}", + "{\"a\":6}", + ] + } if $nu.os-info.name == "windows" { $lines | str join "\r\n" @@ -84,3 +95,50 @@ def to_jsonl_single_object [] { let expect = "{\"a\":1}" assert equal $result $expect "could not convert to JSONL" } + +#[test] +def from_ndnuon_multiple_objects [] { + let result = test_data_multiline | from ndnuon + let expect = [{a:1},{a:2},{a:3},{a:4},{a:5},{a:6}] + assert equal $result $expect "could not convert from NDNUON" +} + +#[test] +def from_ndnuon_single_object [] { + let result = '{a: 1}' | from ndnuon + let expect = [{a:1}] + assert equal $result $expect "could not convert from NDNUON" +} + +#[test] +def from_ndnuon_invalid_object [] { + assert error { '{"a":1' | formats from ndnuon } +} + +#[test] +def to_ndnuon_multiple_objects [] { + let result = [{a:1},{a:2},{a:3},{a:4},{a:5},{a:6}] | to ndnuon | str trim + let expect = test_data_multiline --nuon + assert equal $result $expect "could not convert to NDNUON" +} + +#[test] +def to_ndnuon_single_object [] { + let result = [{a:1}] | to ndnuon | str trim + let expect = "{a: 1}" + assert equal $result $expect "could not convert to NDNUON" +} + +#[test] +def to_ndnuon_multiline_strings [] { + let result = "foo\n\\n\nbar" | to ndnuon + let expect = '"foo\n\\n\nbar"' + assert equal $result $expect "could not convert multiline string to NDNUON" +} + +#[test] +def from_ndnuon_multiline_strings [] { + let result = '"foo\n\\n\nbar"' | from ndnuon + let expect = ["foo\n\\n\nbar"] + assert equal $result $expect "could not convert multiline string from NDNUON" +} diff --git a/crates/nu-std/tests/test_iter.nu b/crates/nu-std/tests/test_iter.nu index 2b768c3d9d..3ace342788 100644 --- a/crates/nu-std/tests/test_iter.nu +++ b/crates/nu-std/tests/test_iter.nu @@ -49,7 +49,10 @@ def iter_scan [] { let scanned = ([1 2 3] | iter scan 0 {|x, y| $x + $y}) assert equal $scanned [0, 1, 3, 6] - let scanned = ([a b c d] | iter scan "" {|x, y| [$x, $y] | str join} -n) + let scanned = ([a b c d] | iter scan "" {|it, acc| [$acc, $it] | str join} -n) + assert equal $scanned ["a" "ab" "abc" "abcd"] + + let scanned = ([a b c d] | iter scan "" {|it, acc| append $it | str join} -n) assert equal $scanned ["a" "ab" "abc" "abcd"] } diff --git a/crates/nu-std/tests/test_std_formats.nu b/crates/nu-std/tests/test_std_formats.nu index f23590c5a0..1ca7516c20 100644 --- a/crates/nu-std/tests/test_std_formats.nu +++ b/crates/nu-std/tests/test_std_formats.nu @@ -1,16 +1,27 @@ # Test std/formats when importing `use std *` use std * -def test_data_multiline [] { +def test_data_multiline [--nuon] { use std * - let lines = [ - "{\"a\":1}", - "{\"a\":2}", - "{\"a\":3}", - "{\"a\":4}", - "{\"a\":5}", - "{\"a\":6}", - ] + let lines = if $nuon { + [ + "{a: 1}", + "{a: 2}", + "{a: 3}", + "{a: 4}", + "{a: 5}", + "{a: 6}", + ] + } else { + [ + "{\"a\":1}", + "{\"a\":2}", + "{\"a\":3}", + "{\"a\":4}", + "{\"a\":5}", + "{\"a\":6}", + ] + } if $nu.os-info.name == "windows" { $lines | str join "\r\n" @@ -84,3 +95,50 @@ def to_jsonl_single_object [] { let expect = "{\"a\":1}" assert equal $result $expect "could not convert to JSONL" } + +#[test] +def from_ndnuon_multiple_objects [] { + let result = test_data_multiline | formats from ndnuon + let expect = [{a:1},{a:2},{a:3},{a:4},{a:5},{a:6}] + assert equal $result $expect "could not convert from NDNUON" +} + +#[test] +def from_ndnuon_single_object [] { + let result = '{a: 1}' | formats from ndnuon + let expect = [{a:1}] + assert equal $result $expect "could not convert from NDNUON" +} + +#[test] +def from_ndnuon_invalid_object [] { + assert error { '{"a":1' | formats from ndnuon } +} + +#[test] +def to_ndnuon_multiple_objects [] { + let result = [{a:1},{a:2},{a:3},{a:4},{a:5},{a:6}] | formats to ndnuon | str trim + let expect = test_data_multiline --nuon + assert equal $result $expect "could not convert to NDNUON" +} + +#[test] +def to_ndnuon_single_object [] { + let result = [{a:1}] | formats to ndnuon | str trim + let expect = "{a: 1}" + assert equal $result $expect "could not convert to NDNUON" +} + +#[test] +def to_ndnuon_multiline_strings [] { + let result = "foo\n\\n\nbar" | formats to ndnuon + let expect = '"foo\n\\n\nbar"' + assert equal $result $expect "could not convert multiline string to NDNUON" +} + +#[test] +def from_ndnuon_multiline_strings [] { + let result = '"foo\n\\n\nbar"' | formats from ndnuon + let expect = ["foo\n\\n\nbar"] + assert equal $result $expect "could not convert multiline string from NDNUON" +} diff --git a/crates/nu-table/Cargo.toml b/crates/nu-table/Cargo.toml index c5ddb41f52..ec32249de5 100644 --- a/crates/nu-table/Cargo.toml +++ b/crates/nu-table/Cargo.toml @@ -14,9 +14,9 @@ bench = false workspace = true [dependencies] -nu-protocol = { path = "../nu-protocol", version = "0.100.1" } -nu-utils = { path = "../nu-utils", version = "0.100.1" } -nu-engine = { path = "../nu-engine", version = "0.100.1" } +nu-protocol = { path = "../nu-protocol", version = "0.100.1", default-features = false } +nu-utils = { path = "../nu-utils", version = "0.100.1", default-features = false } +nu-engine = { path = "../nu-engine", version = "0.100.1", default-features = false } nu-color-config = { path = "../nu-color-config", version = "0.100.1" } nu-ansi-term = { workspace = true } fancy-regex = { workspace = true } diff --git a/crates/nu-term-grid/Cargo.toml b/crates/nu-term-grid/Cargo.toml index a9fea5e78b..9ef7414b73 100644 --- a/crates/nu-term-grid/Cargo.toml +++ b/crates/nu-term-grid/Cargo.toml @@ -14,6 +14,6 @@ bench = false workspace = true [dependencies] -nu-utils = { path = "../nu-utils", version = "0.100.1" } +nu-utils = { path = "../nu-utils", version = "0.100.1", default-features = false } unicode-width = { workspace = true } \ No newline at end of file diff --git a/crates/nu-utils/Cargo.toml b/crates/nu-utils/Cargo.toml index 4b590e6831..309f5f3139 100644 --- a/crates/nu-utils/Cargo.toml +++ b/crates/nu-utils/Cargo.toml @@ -17,6 +17,7 @@ bench = false bench = false [dependencies] +crossterm = { workspace = true, optional = true } fancy-regex = { workspace = true } lscolors = { workspace = true, default-features = false, features = ["nu-ansi-term"] } log = { workspace = true } @@ -26,6 +27,10 @@ serde = { workspace = true } sys-locale = "0.3" unicase = "2.8.0" +[features] +default = ["os"] +os = ["crossterm"] + [target.'cfg(windows)'.dependencies] crossterm_winapi = "0.9" diff --git a/crates/nu-utils/src/default_files/README.md b/crates/nu-utils/src/default_files/README.md index 2269771bbd..68a36b865d 100644 --- a/crates/nu-utils/src/default_files/README.md +++ b/crates/nu-utils/src/default_files/README.md @@ -7,10 +7,8 @@ * Will be loaded during any startup where the user's `env.nu` is also loaded. For example: * During normal startup with `nu` * During a startup where the user specifies an alternative `env.nu` via `nu --env-config ` -* Likewise, is never loaded during a startup where the user's `env.nu` would not be loaded. For example: - * `nu -n/--no-config` - * `nu -c "ls"` - * `nu ` + * During a `nu -c ` or `nu