From 424efdaafe69300f2e320746ddb55b00e6abeeb4 Mon Sep 17 00:00:00 2001 From: 132ikl <132@ikl.sh> Date: Tue, 3 Dec 2024 16:21:09 -0500 Subject: [PATCH 01/25] Make `glob` stream (#14495) # Description Makes the `glob` command stream # User-Facing Changes The glob command now streams # Tests + Formatting - :green_circle: `toolkit fmt` - :green_circle: `toolkit clippy` - :green_circle: `toolkit test` - :green_circle: `toolkit test stdlib` # After Submitting N/A --- crates/nu-command/src/filesystem/glob.rs | 34 +++++++++++++----------- 1 file changed, 19 insertions(+), 15 deletions(-) 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()) } From da66484578a452e78f7a8b5fa6e07cdbdd918480 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Dec 2024 09:37:19 +0800 Subject: [PATCH 02/25] Bump crate-ci/typos from 1.28.1 to 1.28.2 (#14503) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.28.1 to 1.28.2.
Release notes

Sourced from crate-ci/typos's releases.

v1.28.2

[1.28.2] - 2024-12-02

Fixes

  • Don't correct parametrize variants
Changelog

Sourced from crate-ci/typos's changelog.

[1.28.2] - 2024-12-02

Fixes

  • Don't correct parametrize variants
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=crate-ci/typos&package-manager=github_actions&previous-version=1.28.1&new-version=1.28.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/typos.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml index d21926517e..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.28.1 + uses: crate-ci/typos@v1.28.2 From 08504f6e0695a538902017923bad9b63410e26a4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Dec 2024 09:37:47 +0800 Subject: [PATCH 03/25] Bump bytes from 1.8.0 to 1.9.0 (#14508) Bumps [bytes](https://github.com/tokio-rs/bytes) from 1.8.0 to 1.9.0.
Release notes

Sourced from bytes's releases.

Bytes v1.9.0

1.9.0 (November 27, 2024)

Added

  • Add Bytes::from_owner to enable externally-allocated memory (#742)

Documented

  • Fix typo in Buf::chunk() comment (#744)

Internal changes

  • Replace BufMut::put with BufMut::put_slice in Writer impl (#745)
  • Rename hex_impl! to fmt_impl! and reuse it for fmt::Debug (#743)
Changelog

Sourced from bytes's changelog.

1.9.0 (November 27, 2024)

Added

  • Add Bytes::from_owner to enable externally-allocated memory (#742)

Documented

  • Fix typo in Buf::chunk() comment (#744)

Internal changes

  • Replace BufMut::put with BufMut::put_slice in Writer impl (#745)
  • Rename hex_impl! to fmt_impl! and reuse it for fmt::Debug (#743)
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=bytes&package-manager=cargo&previous-version=1.8.0&new-version=1.9.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6f10da93e9..074ee0439a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -594,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" From a980b9d0a63d7c1828ff82367b5b9191380526e4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Dec 2024 09:37:52 +0800 Subject: [PATCH 04/25] Bump ureq from 2.10.1 to 2.12.0 (#14507) Bumps [ureq](https://github.com/algesten/ureq) from 2.10.1 to 2.12.0.
Changelog

Sourced from ureq's changelog.

2.12.0

  • Bump MSRV 1.67 -> 1.71 because rustls will soon adopt it (#905)
  • Unpin rustls dep (>=0.23.19) (#905)

2.11.0

  • Fixes for changes to cargo-deny (#882)
  • Pin rustls dep on 0.23.19 to keep MSRV 1.67 (#878)
  • Bump MSRV 1.63 -> 1.67 due to time crate (#878)
  • Re-export rustls (#813)
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=ureq&package-manager=cargo&previous-version=2.10.1&new-version=2.12.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 074ee0439a..ca0f0b66f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7331,9 +7331,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "2.10.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a" +checksum = "3193f92e105038f98ae68af40c008e3c94f2f046926e0f95e6c835dc6459bac8" dependencies = [ "base64", "encoding_rs", diff --git a/Cargo.toml b/Cargo.toml index 896fed28b2..57cd11a202 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -165,7 +165,7 @@ trash = "5.2" umask = "2.1" unicode-segmentation = "1.12" unicode-width = "0.2" -ureq = { version = "2.10", default-features = false } +ureq = { version = "2.12", default-features = false } url = "2.2" uu_cp = "0.0.28" uu_mkdir = "0.0.28" From 5f0567f8dfe187b6de7b5bcd337820cb57f3c37e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Dec 2024 09:38:27 +0800 Subject: [PATCH 05/25] Bump multipart-rs from 0.1.11 to 0.1.13 (#14506) Bumps [multipart-rs](https://github.com/feliwir/multipart-rs) from 0.1.11 to 0.1.13.
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=multipart-rs&package-manager=cargo&previous-version=0.1.11&new-version=0.1.13)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ca0f0b66f3..cb04cc9c8e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3040,9 +3040,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", diff --git a/Cargo.toml b/Cargo.toml index 57cd11a202..a6c25a439a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -110,7 +110,7 @@ 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 } From 88a8e986eb1272e974a3337146710ce9e57b778c Mon Sep 17 00:00:00 2001 From: Michel Lind Date: Tue, 3 Dec 2024 17:40:23 -0800 Subject: [PATCH 06/25] Bump titlecase dependency (#14502) # Description v3 drops the dependency on joinery, as well as on lazy_static. The MSRV is bumped to 1.70.0 but that is still way below what nushell requires. # User-Facing Changes N/A # Tests + Formatting All tests pass (including nu-command which is the direct user) # After Submitting N/A Signed-off-by: Michel Lind --- Cargo.lock | 12 ++---------- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cb04cc9c8e..47edc2b3bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2548,12 +2548,6 @@ 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" @@ -6992,12 +6986,10 @@ 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", ] diff --git a/Cargo.toml b/Cargo.toml index a6c25a439a..608d035420 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -159,7 +159,7 @@ sysinfo = "0.32" tabled = { version = "0.16.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" From bf457cd4fca27deb05ef78f29cb9f37b1f4fbb3b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Dec 2024 09:57:05 +0800 Subject: [PATCH 07/25] Bump indexmap from 2.6.0 to 2.7.0 (#14505) Bumps [indexmap](https://github.com/indexmap-rs/indexmap) from 2.6.0 to 2.7.0.
Changelog

Sourced from indexmap's changelog.

2.7.0 (2024-11-30)

  • Added methods Entry::insert_entry and VacantEntry::insert_entry, returning an OccupiedEntry after insertion.
Commits

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=indexmap&package-manager=cargo&previous-version=2.6.0&new-version=2.7.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- crates/nu_plugin_polars/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 47edc2b3bf..f10b9897e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2370,9 +2370,9 @@ 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", diff --git a/Cargo.toml b/Cargo.toml index 608d035420..591947a2b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/crates/nu_plugin_polars/Cargo.toml b/crates/nu_plugin_polars/Cargo.toml index a4f639ca13..f2d64f839a 100644 --- a/crates/nu_plugin_polars/Cargo.toml +++ b/crates/nu_plugin_polars/Cargo.toml @@ -26,7 +26,7 @@ nu-utils = { path = "../nu-utils", version = "0.100.1" } chrono = { workspace = true, features = ["std", "unstable-locales"], default-features = false } chrono-tz = "0.10" fancy-regex = { workspace = true } -indexmap = { version = "2.6" } +indexmap = { version = "2.7" } mimalloc = { version = "0.1.42" } num = {version = "0.4"} serde = { version = "1.0", features = ["derive"] } From 217be249637f45049141a22325a5ade9980a1340 Mon Sep 17 00:00:00 2001 From: RobbingDaHood <16130319+RobbingDaHood@users.noreply.github.com> Date: Wed, 4 Dec 2024 03:39:11 +0100 Subject: [PATCH 08/25] #14238 Now the file completion is triggered on a custom command after the first parameter. (#14481) - this PR should close #14238 # Description Solved as described here (First suggestion): https://github.com/nushell/nushell/issues/14238#issuecomment-2506387012 Below I make the example from the issue, it shows that the completion now works past the first parameter. ``` ~/Projects/nushell> def list [...args] { 11/30/2024 03:21:24 PM ::: $args ::: | each { ::: open $args ::: } ::: } ~/Projects/nushell> cd tests/fixtures/completions/ 11/30/2024 03:25:24 PM ~/Projects/nushell/tests/fixtures/completions| list custom_completion.nu 11/30/2024 03:25:35 PM another/ custom_completion.nu directory_completion/ nushell test_a/ test_b/ .hidden_file .hidden_folder/ ``` # User-Facing Changes The changes introduced to completions in `baadaee0163a5066ae73509ff6052962b3422673` now does not return if it did not find "Operator completions". This could have impact on more than just custom commands, but it could be seemed as making everything a bit more robust. # Tests + Formatting I ran all of: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass (on Windows make sure to [enable developer mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging)) - `cargo run -- -c "use toolkit.nu; toolkit test stdlib"` to run the tests for the standard library # After Submitting I do not think there is any need to update [the documentation](https://github.com/nushell/nushell.github.io), right? --------- Co-authored-by: Daniel Winther Petersen --- crates/nu-cli/src/completions/completer.rs | 5 +- crates/nu-cli/tests/completions/mod.rs | 102 +++++++++++++++++++++ 2 files changed, 106 insertions(+), 1 deletion(-) 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/tests/completions/mod.rs b/crates/nu-cli/tests/completions/mod.rs index 7f6fe30430..78c36cad7e 100644 --- a/crates/nu-cli/tests/completions/mod.rs +++ b/crates/nu-cli/tests/completions/mod.rs @@ -357,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()); @@ -391,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() { From b2d8bd08f876ea6ad5d263547e4e090fdc57407a Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Tue, 3 Dec 2024 20:45:31 -0600 Subject: [PATCH 09/25] allow `select` to stream more (#14492) # Description closes https://github.com/nushell/nushell/issues/14487 This PR tries to allow the `select` to stream better by changing the for loops that collected the output into a `Vec` prior to returning it into a map that returns the data as it is processed. One curiosity, `select` transforms the input into a `PipelineIterator`. If I remove this code, it still passes all tests. I'm not sure all this `PipelineIterator` code is even needed. I left it for someone to tell me if it's necessary. # User-Facing Changes # Tests + Formatting # After Submitting --- crates/nu-command/src/filters/select.rs | 91 ++++++++++------------ crates/nu-command/tests/commands/select.rs | 3 +- 2 files changed, 42 insertions(+), 52 deletions(-) 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/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 "# )); From a3327122755c00ae9384320e519a1257b91d424f Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Tue, 3 Dec 2024 20:47:58 -0600 Subject: [PATCH 10/25] add function to make env vars case-insensitive (#14390) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description This PR adds a new function that allows one to get an env var case-insensitively. I did this so we can hopefully stop having problems when Windows has HKLM as path and HKCU as Path. Instead of just changing every function that used the original one, I chose the ones that I thought were specific to getting the path. I didn't want to go all in and make every env get case insensitive, but maybe we should? ๐Ÿคท๐Ÿปโ€โ™‚๏ธ closes #12676 # User-Facing Changes # Tests + Formatting # After Submitting --- .../src/completions/command_completions.rs | 3 +- crates/nu-cli/tests/completions/mod.rs | 10 ----- crates/nu-engine/src/env.rs | 45 ++++++------------- crates/nu-plugin-engine/src/context.rs | 2 +- crates/nu-protocol/src/engine/engine_state.rs | 15 ++----- crates/nu-protocol/src/engine/stack.rs | 35 +++++++++++++++ crates/nu-protocol/tests/test_value.rs | 29 +++++++++++- 7 files changed, 83 insertions(+), 56 deletions(-) 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/tests/completions/mod.rs b/crates/nu-cli/tests/completions/mod.rs index 78c36cad7e..e32ac8c4f2 100644 --- a/crates/nu-cli/tests/completions/mod.rs +++ b/crates/nu-cli/tests/completions/mod.rs @@ -1759,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-engine/src/env.rs b/crates/nu-engine/src/env.rs index f7aa3cf2e1..dc6c354fce 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 { @@ -53,14 +46,14 @@ pub fn convert_env_values(engine_state: &mut EngineState, stack: &Stack) -> Resu #[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 +100,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, .. } => { @@ -216,31 +209,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-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-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/stack.rs b/crates/nu-protocol/src/engine/stack.rs index ba14e67817..66f59b78f6 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, @@ -494,6 +495,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/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); +} From 05b7c1fffa24d91328414aa09d3560d01be01753 Mon Sep 17 00:00:00 2001 From: Ben Beasley Date: Wed, 4 Dec 2024 15:39:45 -0500 Subject: [PATCH 11/25] Update roxmltree from 0.19 to 0.20, the latest version (#14513) # Description This simply updates `roxmltree` from 0.19.0 to 0.20.0, the latest release, with no code changes required. # User-Facing Changes N/A --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f10b9897e9..54acf0f4c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5830,9 +5830,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" diff --git a/Cargo.toml b/Cargo.toml index 591947a2b3..97bc58c0bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -143,7 +143,7 @@ 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" From 3bd45c005beaa0e006bea0b5e8975b421a6f7342 Mon Sep 17 00:00:00 2001 From: 132ikl <132@ikl.sh> Date: Wed, 4 Dec 2024 20:26:48 -0500 Subject: [PATCH 12/25] Change tests which may invoke externals to use non-conflicting names (#14516) # Description Fixes #14515 Also tweaks the fix from #11261 _just in case_ someone has a `foo` executable # User-Facing Changes N/A # Tests + Formatting # After Submitting --- crates/nu-command/tests/commands/use_.rs | 6 +++--- tests/overlays/mod.rs | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) 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/tests/overlays/mod.rs b/tests/overlays/mod.rs index 15697f467d..a04f44641b 100644 --- a/tests/overlays/mod.rs +++ b/tests/overlays/mod.rs @@ -807,10 +807,10 @@ fn overlay_can_add_renamed_overlay() { #[test] fn overlay_hide_renamed_overlay() { let inp = &[ - r#"module spam { export def foo [] { "foo" } }"#, + r#"module spam { export def foo-command-which-does-not-conflict [] { "foo" } }"#, "overlay use spam as eggs", "overlay hide eggs", - "foo", + "foo-command-which-does-not-conflict", ]; let actual = nu!(&inp.join("; ")); @@ -1243,9 +1243,9 @@ fn overlay_use_main_def_known_external() { #[test] fn overlay_use_main_not_exported() { let inp = &[ - r#"module foo { def main [] { "foo" } }"#, - "overlay use foo", - "foo", + r#"module my-super-cool-and-unique-module-name { def main [] { "hi" } }"#, + "overlay use my-super-cool-and-unique-module-name", + "my-super-cool-and-unique-module-name", ]; let actual = nu!(&inp.join("; ")); @@ -1257,11 +1257,11 @@ fn overlay_use_main_not_exported() { fn alias_overlay_hide() { let inp = &[ "overlay new spam", - "def foo [] { 'foo' }", + "def my-epic-command-name [] { 'foo' }", "overlay new eggs", "alias oh = overlay hide", "oh spam", - "foo", + "my-epic-command-name", ]; let actual = nu!(&inp.join("; ")); From 234484b6f87b2716f7b0a7d17d0fbddba9d7889d Mon Sep 17 00:00:00 2001 From: Solomon Date: Thu, 5 Dec 2024 06:35:15 -0700 Subject: [PATCH 13/25] normalize special characters in module names to allow variable access (#14353) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #14252 # User-Facing Changes - Special characters in module names are replaced with underscores when importing constants, preventing "expected valid variable name": ```nushell > module foo-bar { export const baz = 1 } > use foo-bar > $foo_bar.baz ``` - "expected valid variable name" errors now include a suggestion list: ```nushell > module foo-bar { export const baz = 1 } > use foo-bar > $foo-bar Error: nu::parser::parse_mismatch_with_did_you_mean ร— Parse mismatch during operation. โ•ญโ”€[entry #1:1:1] 1 โ”‚ $foo-bar; ยท โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€ ยท โ•ฐโ”€โ”€ expected valid variable name. Did you mean '$foo_bar'? โ•ฐโ”€โ”€โ”€โ”€ ``` --- crates/nu-parser/src/parser.rs | 32 ++++++++++---------- crates/nu-protocol/src/errors/parse_error.rs | 5 +++ crates/nu-protocol/src/module.rs | 31 ++++++++++++++++++- tests/repl/test_modules.rs | 23 ++++++++++++++ 4 files changed, 74 insertions(+), 17 deletions(-) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 0f8e699729..eecd7863a9 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -2122,7 +2122,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), @@ -2133,9 +2147,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) } } @@ -5612,18 +5624,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-protocol/src/errors/parse_error.rs b/crates/nu-protocol/src/errors/parse_error.rs index 786ce97509..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), @@ -551,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, 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/tests/repl/test_modules.rs b/tests/repl/test_modules.rs index c2fc6b8dc2..d088d4ea30 100644 --- a/tests/repl/test_modules.rs +++ b/tests/repl/test_modules.rs @@ -1,4 +1,5 @@ use crate::repl::tests::{fail_test, run_test, TestResult}; +use rstest::rstest; #[test] fn module_def_imports_1() -> TestResult { @@ -145,6 +146,28 @@ fn export_module_which_defined_const() -> TestResult { ) } +#[rstest] +#[case("spam-mod")] +#[case("spam/mod")] +#[case("spam=mod")] +fn export_module_with_normalized_var_name(#[case] name: &str) -> TestResult { + let def = format!( + "module {name} {{ export const b = 3; export module {name}2 {{ export const c = 4 }} }}" + ); + run_test(&format!("{def}; use {name}; $spam_mod.b"), "3")?; + run_test(&format!("{def}; use {name} *; $spam_mod2.c"), "4") +} + +#[rstest] +#[case("spam-mod")] +#[case("spam/mod")] +fn use_module_with_invalid_var_name(#[case] name: &str) -> TestResult { + fail_test( + &format!("module {name} {{ export const b = 3 }}; use {name}; ${name}"), + "expected valid variable name. Did you mean '$spam_mod'", + ) +} + #[test] fn cannot_export_private_const() -> TestResult { fail_test( From d97562f6e844254610349dfa2daf3cf18470b083 Mon Sep 17 00:00:00 2001 From: Antoine Stevan <44101798+amtoine@users.noreply.github.com> Date: Thu, 5 Dec 2024 14:53:33 +0100 Subject: [PATCH 14/25] fix multiline strings in NDNUON (#14519) - should close https://github.com/nushell/nushell/issues/14517 # Description this will change `to ndnuon` so that newlines are encoded as a literal `\n` which `from ndnuon` is already able to handle # User-Facing Changes users should be able to encode multiline strings in NDNUON # Tests + Formatting new tests have been added: - they don't pass on the first commit - they do pass with the fix # After Submitting --- crates/nu-std/std/formats/mod.nu | 2 +- crates/nu-std/tests/test_formats.nu | 14 ++++++++++++++ crates/nu-std/tests/test_std_formats.nu | 14 ++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/crates/nu-std/std/formats/mod.nu b/crates/nu-std/std/formats/mod.nu index addfa4c1c9..a0484e42f4 100644 --- a/crates/nu-std/std/formats/mod.nu +++ b/crates/nu-std/std/formats/mod.nu @@ -36,5 +36,5 @@ export def "from ndnuon" []: [string -> any] { # Convert structured data to NDNUON, i.e. newline-delimited NUON export def "to ndnuon" []: [any -> string] { - each { to nuon --raw } | to text + each { to nuon --raw | str replace --all "\n" '\n' } | to text } diff --git a/crates/nu-std/tests/test_formats.nu b/crates/nu-std/tests/test_formats.nu index ec1f6adb47..44a89eeaad 100644 --- a/crates/nu-std/tests/test_formats.nu +++ b/crates/nu-std/tests/test_formats.nu @@ -128,3 +128,17 @@ def to_ndnuon_single_object [] { 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_std_formats.nu b/crates/nu-std/tests/test_std_formats.nu index ed6b079d7e..1ca7516c20 100644 --- a/crates/nu-std/tests/test_std_formats.nu +++ b/crates/nu-std/tests/test_std_formats.nu @@ -128,3 +128,17 @@ def to_ndnuon_single_object [] { 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" +} From f51828d049b950c8eee9fd1c5fce2e4f6d784219 Mon Sep 17 00:00:00 2001 From: Stefan Holderbach Date: Thu, 5 Dec 2024 14:54:14 +0100 Subject: [PATCH 15/25] Improve `sleep` example using multiple durations (#14520) It is to cheat our parser and not to repeat yourself. --- crates/nu-command/src/platform/sleep.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 { From 4c9078ccccda3294f4789ec5a71eead0c83acf24 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Thu, 5 Dec 2024 21:36:35 -0600 Subject: [PATCH 16/25] add file column to `scope modules` output (#14524) # Description This PR adds a `file` column to the `scope modules` output table. ![image](https://github.com/user-attachments/assets/d69f3dec-3f9a-4ff9-b971-1fd533520ec7) # User-Facing Changes # Tests + Formatting # After Submitting --- crates/nu-engine/src/scope.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/nu-engine/src/scope.rs b/crates/nu-engine/src/scope.rs index 62acd6a9a7..45f6f983fc 100644 --- a/crates/nu-engine/src/scope.rs +++ b/crates/nu-engine/src/scope.rs @@ -488,6 +488,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, ) From 81d68cd478325170c619f66ff2fa88e12497e144 Mon Sep 17 00:00:00 2001 From: Jack Wright <56345+ayax79@users.noreply.github.com> Date: Fri, 6 Dec 2024 03:17:18 -0800 Subject: [PATCH 17/25] Documentation and error handling around `polars with-column --name` (#14527) The `--name` flag of `polars with-column` only works when used with an eager dataframe. I will not work with lazy dataframes and it will not work when used with expressions (which forces a conversion to a lazyframe). This pull request adds better documentation to the flags and errors messages when used in cases where it will not work. --- .../src/dataframe/command/data/with_column.rs | 25 ++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/crates/nu_plugin_polars/src/dataframe/command/data/with_column.rs b/crates/nu_plugin_polars/src/dataframe/command/data/with_column.rs index 4806450601..637caa1776 100644 --- a/crates/nu_plugin_polars/src/dataframe/command/data/with_column.rs +++ b/crates/nu_plugin_polars/src/dataframe/command/data/with_column.rs @@ -6,8 +6,8 @@ use crate::{ }; use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand}; use nu_protocol::{ - Category, Example, LabeledError, PipelineData, ShellError, Signature, Span, SyntaxShape, Type, - Value, + Category, Example, LabeledError, PipelineData, ShellError, Signature, Span, Spanned, + SyntaxShape, Type, Value, }; #[derive(Clone)] @@ -26,7 +26,7 @@ impl PluginCommand for WithColumn { fn signature(&self) -> Signature { Signature::build(self.name()) - .named("name", SyntaxShape::String, "new column name", Some('n')) + .named("name", SyntaxShape::String, "New column name. For lazy dataframes and expressions syntax, use a `polars as` expression to name a column.", Some('n')) .rest( "series or expressions", SyntaxShape::Any, @@ -138,6 +138,15 @@ fn command_eager( let column_span = new_column.span(); if NuExpression::can_downcast(&new_column) { + if let Some(name) = call.get_flag::>("name")? { + return Err(ShellError::GenericError { + error: "Flag 'name' is unsuppored when used with expressions. Please use the `polars as` expression to name a column".into(), + msg: "".into(), + span: Some(name.span), + help: Some("Use a `polars as` expression to name a column".into()), + inner: vec![], + }); + } let vals: Vec = call.rest(0)?; let value = Value::list(vals, call.head); let expressions = NuExpression::extract_exprs(plugin, value)?; @@ -177,6 +186,16 @@ fn command_lazy( call: &EvaluatedCall, lazy: NuLazyFrame, ) -> Result { + if let Some(name) = call.get_flag::>("name")? { + return Err(ShellError::GenericError { + error: "Flag 'name' is unsuppored for lazy dataframes. Please use the `polars as` expression to name a column".into(), + msg: "".into(), + span: Some(name.span), + help: Some("Use a `polars as` expression to name a column".into()), + inner: vec![], + }); + } + let vals: Vec = call.rest(0)?; let value = Value::list(vals, call.head); let expressions = NuExpression::extract_exprs(plugin, value)?; From cda9ae1e42bdfc487f36d489c574facfea079419 Mon Sep 17 00:00:00 2001 From: Alex Kattathra Johnson Date: Fri, 6 Dec 2024 06:03:13 -0600 Subject: [PATCH 18/25] Shorten --max-time in tests and use a more stable error check (#14494) - fixes flakey tests from solving #14241 # Description This is a preliminary fix for the flaky tests and also shortened the `--max-time` in the tests. # User-Facing Changes --------- Signed-off-by: Alex Kattathra Johnson --- .../tests/commands/network/http/delete.rs | 13 ++++++++++--- .../nu-command/tests/commands/network/http/get.rs | 13 ++++++++++--- .../tests/commands/network/http/options.rs | 13 ++++++++++--- .../tests/commands/network/http/patch.rs | 13 ++++++++++--- .../tests/commands/network/http/post.rs | 13 ++++++++++--- .../nu-command/tests/commands/network/http/put.rs | 13 ++++++++++--- crates/nu-protocol/src/errors/shell_error.rs | 15 +++++++++------ 7 files changed, 69 insertions(+), 24 deletions(-) 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-protocol/src/errors/shell_error.rs b/crates/nu-protocol/src/errors/shell_error.rs index f187dde807..f9ccc73c54 100644 --- a/crates/nu-protocol/src/errors/shell_error.rs +++ b/crates/nu-protocol/src/errors/shell_error.rs @@ -1544,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 { @@ -1557,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, + }, } } } From 8771872d861eeb94eec63051cb4938f6306d1dcd Mon Sep 17 00:00:00 2001 From: Bahex Date: Fri, 6 Dec 2024 17:19:08 +0300 Subject: [PATCH 19/25] Add `path self` command for getting absolute paths to files at parse time (#14303) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Alternative solution to: - #12195 The other approach: - #14305 # Description Adds ~`path const`~ `path self`, a parse-time only command for getting the absolute path of the source file containing it, or any file relative to the source file. - Useful for any script or module that makes use of non nuscript files. - Removes the need for `$env.CURRENT_FILE` and `$env.FILE_PWD`. - Can be used in modules, sourced files or scripts. # Examples ```nushell # ~/.config/nushell/scripts/foo.nu const paths = { self: (path self), dir: (path self .), sibling: (path self sibling), parent_dir: (path self ..), cousin: (path self ../cousin), } export def main [] { $paths } ``` ```nushell > use foo.nu > foo โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ self โ”‚ /home/user/.config/nushell/scripts/foo.nu โ”‚ โ”‚ dir โ”‚ /home/user/.config/nushell/scripts โ”‚ โ”‚ sibling โ”‚ /home/user/.config/nushell/scripts/sibling โ”‚ โ”‚ parent_dir โ”‚ /home/user/.config/nushell โ”‚ โ”‚ cousin โ”‚ /home/user/.config/nushell/cousin โ”‚ โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ ``` Trying to run in a non-const context ```nushell > path self Error: ร— this command can only run during parse-time โ•ญโ”€[entry #1:1:1] 1 โ”‚ path self ยท โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€ ยท โ•ฐโ”€โ”€ can't run after parse-time โ•ฐโ”€โ”€โ”€โ”€ help: try assigning this command's output to a const variable ``` Trying to run in the REPL i.e. not in a file ```nushell > const foo = path self Error: ร— Error: nu::shell::file_not_found โ”‚ โ”‚ ร— File not found โ”‚ โ•ญโ”€[entry #3:1:13] โ”‚ 1 โ”‚ const foo = path self โ”‚ ยท โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€ โ”‚ ยท โ•ฐโ”€โ”€ Couldn't find current file โ”‚ โ•ฐโ”€โ”€โ”€โ”€ โ”‚ โ•ญโ”€[entry #3:1:13] 1 โ”‚ const foo = path self ยท โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€ ยท โ•ฐโ”€โ”€ Encountered error during parse-time evaluation โ•ฐโ”€โ”€โ”€โ”€ ``` # Comparison with #14305 ## Pros - Self contained implementation, does not require changes in the parser. - More concise usage, especially with parent directories. --------- Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com> --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/path/mod.rs | 2 + crates/nu-command/src/path/self_.rs | 129 +++++++++++++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 crates/nu-command/src/path/self_.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 0a4075c532..a4288c685c 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -106,6 +106,7 @@ pub fn add_shell_command_context(mut engine_state: EngineState) -> EngineState { bind_command! { Path, PathBasename, + PathSelf, PathDirname, PathExists, PathExpand, 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 {}) + } +} From 941145868928524695c9ca244888c37dad2e4c45 Mon Sep 17 00:00:00 2001 From: Maxim Uvarov Date: Fri, 6 Dec 2024 21:09:11 -0300 Subject: [PATCH 20/25] rewrite error message to not use the word `function` (#14533) # Description After [the discussion on discord](https://discord.com/channels/601130461678272522/601130461678272524/1314600410882904125) I propose to rephrase the error message to avoid using the word `function`. From `Return used outside of function` to `Return used outside of custom command or closure` ![image](https://github.com/user-attachments/assets/d14331c8-e381-4b04-8709-bd6064e0791e) # User-Facing Changes None # Tests + Formatting toolkit.nu fmt is good --- crates/nu-protocol/src/errors/shell_error.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/nu-protocol/src/errors/shell_error.rs b/crates/nu-protocol/src/errors/shell_error.rs index f9ccc73c54..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, }, From c16f49cf190ac0646e4283472dd5fa221cdad5ae Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Sat, 7 Dec 2024 07:20:46 -0600 Subject: [PATCH 21/25] add coreutils to search terms --- crates/nu-command/src/filesystem/utouch.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/nu-command/src/filesystem/utouch.rs b/crates/nu-command/src/filesystem/utouch.rs index 51338b20e7..b3d827f7b2 100644 --- a/crates/nu-command/src/filesystem/utouch.rs +++ b/crates/nu-command/src/filesystem/utouch.rs @@ -15,7 +15,7 @@ impl Command for UTouch { } fn search_terms(&self) -> Vec<&str> { - vec!["create", "file"] + vec!["create", "file", "coreutils"] } fn signature(&self) -> Signature { From f0ecaabd7dea79aaf1c8e116d95c1df793c97fba Mon Sep 17 00:00:00 2001 From: Piepmatz Date: Sat, 7 Dec 2024 14:28:14 +0100 Subject: [PATCH 22/25] Expose "to html" command (#14536) # Description In this PR I exposed the struct `ToHtml` that comes from `nu-cmd-extra`. I know this command isn't in a best state and should be changed in some way in the future but having the struct exposed makes transforming data to html way more simple for external tools as the `PipelineData` can easily be placed in the `ToHtml::run` method. # User-Facing Changes None. # Tests + Formatting I did `fmt` and `check` but not `test`, shouldn't break any tests regardless. # After Submitting For the demo page or my jupyter kernel would this make my life easiert. --- crates/nu-cmd-extra/src/extra/formats/mod.rs | 2 +- crates/nu-cmd-extra/src/extra/mod.rs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) 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, From 69fbfb939f1ed902efb0c3e7600de61ef0cc494f Mon Sep 17 00:00:00 2001 From: Bahex Date: Sat, 7 Dec 2024 18:46:52 +0300 Subject: [PATCH 23/25] lsp and --ide-check fix for `path self` related diagnostics (#14538) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description fixes [this](https://github.com/nushell/nushell/pull/14303#issuecomment-2525100480) where lsp and ide integration would produce the following error --- ```sh nu --ide-check 100 "/path/to/env.nu" ``` with ```nu const const_env = path self ``` would lead to ``` Error: nu::shell::file_not_found ร— File not found โ•ญโ”€[/path/to/env.nu:1:19] 1 โ”‚ const const_env = path self ยท โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€ ยท โ•ฐโ”€โ”€ Couldn't find current file โ•ฐโ”€โ”€โ”€โ”€ ``` # Tests + Formatting - :green_circle: `cargo fmt --all` - :green_circle: `cargo clippy --workspace` --- crates/nu-lsp/src/diagnostics.rs | 3 ++- crates/nu-lsp/src/lib.rs | 3 +++ src/ide.rs | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) 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 d5a4938615..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); diff --git a/src/ide.rs b/src/ide.rs index bf582a8287..85023745fa 100644 --- a/src/ide.rs +++ b/src/ide.rs @@ -24,6 +24,7 @@ fn find_id( ) -> Option<(Id, usize, Span)> { let file_id = working_set.add_file(file_path.to_string(), file); let offset = working_set.get_span_for_file(file_id).start; + let _ = working_set.files.push(file_path.into(), Span::unknown()); let block = parse(working_set, Some(file_path), file, false); let flattened = flatten_block(working_set, &block); @@ -88,6 +89,7 @@ pub fn check(engine_state: &mut EngineState, file_path: &str, max_errors: &Value if let Ok(contents) = file { let offset = working_set.next_span_start(); + let _ = working_set.files.push(file_path.into(), Span::unknown()); let block = parse(&mut working_set, Some(file_path), &contents, false); for (idx, err) in working_set.parse_errors.iter().enumerate() { @@ -631,6 +633,7 @@ pub fn ast(engine_state: &mut EngineState, file_path: &str) { if let Ok(contents) = file { let offset = working_set.next_span_start(); + let _ = working_set.files.push(file_path.into(), Span::unknown()); let parsed_block = parse(&mut working_set, Some(file_path), &contents, false); let flat = flatten_block(&working_set, &parsed_block); From 9daa5f9177cf76864f9918760eaf6d15fa61da06 Mon Sep 17 00:00:00 2001 From: Jess <34199683+ratherforky@users.noreply.github.com> Date: Sat, 7 Dec 2024 15:55:15 +0000 Subject: [PATCH 24/25] Fix silent failure of parsing input output types (#14510) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - This PR should fix/close: - #11266 - #12893 - #13736 - #13748 - #14170 - It doesn't fix #13736 though unfortunately. The issue there is at a different level to this fix (I think probably in the lexing somewhere, which I haven't touched). # The Problem The linked issues have many examples of the problem and the related confusion it causes, but I'll give some more examples here for illustration. It boils down to the following: This doesn't type check (good): ```nu def foo []: string -> int { false } ``` This does (bad): ```nu def foo [] : string -> int { false } ``` Because the parser is completely ignoring all the characters. This also compiles in 0.100.0: ```nu def blue [] Da ba Dee da Ba da { false } ``` And this also means commands which have a completely fine type, but an extra space before `:`, lose that type information and end up as `any -> any`, e.g. ```nu def foo [] : int -> int {$in + 3} ``` ```bash $ foo --help Input/output types: โ•ญโ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ โ”‚ # โ”‚ input โ”‚ output โ”‚ โ”œโ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚ 0 โ”‚ any โ”‚ any โ”‚ โ•ฐโ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ ``` # The Fix Special thank you to @texastoland whose draft PR (#12358) I referenced heavily while making this fix. That PR seeks to fix the invalid parsing by disallowing whitespace between `[]` and `:` in declarations, e.g. `def foo [] : int -> any {}` This PR instead allows the whitespace while properly parsing the type signature. I think this is the better choice for a few reasons: - The parsing is still straightforward and the information is all there anyway, - It's more consistent with type annotations in other places, e.g. `do {|nums : list| $nums | describe} [ 1 2 3 ]` from the [Type Signatures doc page](https://www.nushell.sh/lang-guide/chapters/types/type_signatures.html) - It's more consistent with the new nu parser, which allows `let x : bool = false` (current nu doesn't, but this PR doesn't change that) - It will be less disruptive and should only break code where the types are actually wrong (if your types were correct, but you had a space before the `:`, those declarations will still compile and now have more type information vs. throwing an error in all cases and requiring spaces to be deleted) - It's the more intuitive syntax for most functional programmers like myself (haskell/lean/coq/agda and many more either allow or require whitespace for type annotations) I don't use Rust a lot, so I tried to keep most things the same and the rest I wrote as if it was Haskell (if you squint a bit). Code review/suggestions very welcome. I added all the tests I could think of and `toolkit check pr` gives it the all-clear. # User-Facing Changes This PR meets part of the goal of #13849, but doesn't do anything about parsing signatures twice and doesn't do much to improve error messages, it just enforces the existing errors and error messages. This will no doubt be a breaking change, mostly because the code is already broken and users don't realise yet (one of my personal scripts stopped compiling after this fix because I thought `def foo [] -> string {}` was valid syntax). It shouldn't break any type-correct code though. --- crates/nu-parser/src/parser.rs | 78 ++++++++++++++++++++------- crates/nu-parser/tests/test_parser.rs | 37 +++++++++++++ crates/nu-std/testing.nu | 6 +-- 3 files changed, 99 insertions(+), 22 deletions(-) diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index eecd7863a9..07ab35dc36 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -3361,26 +3361,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]) } } 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-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 { From 685dc78739016d4bb92459678b75646768a690b2 Mon Sep 17 00:00:00 2001 From: Darren Schroeder <343840+fdncred@users.noreply.github.com> Date: Sun, 8 Dec 2024 08:43:36 -0600 Subject: [PATCH 25/25] update to reedline 9eb3c2d (#14541) # Description This PR updates nushell to the latest commit of reedline that fixes some rendering issues on window resize. # User-Facing Changes # Tests + Formatting # After Submitting --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 54acf0f4c2..4a50d71724 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5604,7 +5604,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.37.0" -source = "git+https://github.com/nushell/reedline?branch=main#3c46dc2c0c69476a625611a556e67ddb8439629c" +source = "git+https://github.com/nushell/reedline?branch=main#9eb3c2dd1375119c7f6bb8ecac07b715e72fe692" dependencies = [ "arboard", "chrono",