From 316d2d6af2bad041dafaf73172b2dfe8a9821fad Mon Sep 17 00:00:00 2001 From: 132ikl <132@ikl.sh> Date: Mon, 14 Jul 2025 19:26:40 -0400 Subject: [PATCH] Add `extern` for `nu` command (#16119) # Description Adds an `extern` definition (`KnownExternal`) for the `nu` command. This way you can do `help nu` and get tab completions for flags on the `nu` executable # User-Facing Changes * You can now view the flags for Nushell itself with `help nu`, and tab completion for flags on `nu` works --- .../tests/commands/bytes/ends_with.rs | 18 ++-- .../tests/commands/bytes/starts_with.rs | 18 ++-- .../nu-command/tests/commands/run_external.rs | 87 ++++++++++--------- crates/nu-engine/src/compile/call.rs | 3 +- crates/nu-protocol/src/signature.rs | 8 ++ src/command.rs | 26 +++++- src/main.rs | 9 +- src/test_bins.rs | 34 +++++--- tests/repl/test_known_external.rs | 6 ++ tests/repl/test_parser.rs | 8 +- tests/scope/mod.rs | 2 +- tests/shell/pipeline/commands/external.rs | 12 +-- 12 files changed, 143 insertions(+), 88 deletions(-) diff --git a/crates/nu-cmd-extra/tests/commands/bytes/ends_with.rs b/crates/nu-cmd-extra/tests/commands/bytes/ends_with.rs index b90f936b96..f0971eb223 100644 --- a/crates/nu-cmd-extra/tests/commands/bytes/ends_with.rs +++ b/crates/nu-cmd-extra/tests/commands/bytes/ends_with.rs @@ -22,7 +22,7 @@ fn basic_string_fails() { #[test] fn short_stream_binary() { let actual = nu!(r#" - nu --testbin repeater (0x[01]) 5 | bytes ends-with 0x[010101] + ^nu --testbin repeater (0x[01]) 5 | bytes ends-with 0x[010101] "#); assert_eq!(actual.out, "true"); @@ -31,7 +31,7 @@ fn short_stream_binary() { #[test] fn short_stream_mismatch() { let actual = nu!(r#" - nu --testbin repeater (0x[010203]) 5 | bytes ends-with 0x[010204] + ^nu --testbin repeater (0x[010203]) 5 | bytes ends-with 0x[010204] "#); assert_eq!(actual.out, "false"); @@ -40,7 +40,7 @@ fn short_stream_mismatch() { #[test] fn short_stream_binary_overflow() { let actual = nu!(r#" - nu --testbin repeater (0x[01]) 5 | bytes ends-with 0x[010101010101] + ^nu --testbin repeater (0x[01]) 5 | bytes ends-with 0x[010101010101] "#); assert_eq!(actual.out, "false"); @@ -49,7 +49,7 @@ fn short_stream_binary_overflow() { #[test] fn long_stream_binary() { let actual = nu!(r#" - nu --testbin repeater (0x[01]) 32768 | bytes ends-with 0x[010101] + ^nu --testbin repeater (0x[01]) 32768 | bytes ends-with 0x[010101] "#); assert_eq!(actual.out, "true"); @@ -59,7 +59,7 @@ fn long_stream_binary() { fn long_stream_binary_overflow() { // .. ranges are inclusive..inclusive, so we don't need to +1 to check for an overflow let actual = nu!(r#" - nu --testbin repeater (0x[01]) 32768 | bytes ends-with (0..32768 | each {|| 0x[01] } | bytes collect) + ^nu --testbin repeater (0x[01]) 32768 | bytes ends-with (0..32768 | each {|| 0x[01] } | bytes collect) "#); assert_eq!(actual.out, "false"); @@ -69,7 +69,7 @@ fn long_stream_binary_overflow() { fn long_stream_binary_exact() { // ranges are inclusive..inclusive, so we don't need to +1 to check for an overflow let actual = nu!(r#" - nu --testbin repeater (0x[01020304]) 8192 | bytes ends-with (0..<8192 | each {|| 0x[01020304] } | bytes collect) + ^nu --testbin repeater (0x[01020304]) 8192 | bytes ends-with (0..<8192 | each {|| 0x[01020304] } | bytes collect) "#); assert_eq!(actual.out, "true"); @@ -79,7 +79,7 @@ fn long_stream_binary_exact() { fn long_stream_string_exact() { // ranges are inclusive..inclusive, so we don't need to +1 to check for an overflow let actual = nu!(r#" - nu --testbin repeater hell 8192 | bytes ends-with (0..<8192 | each {|| "hell" | into binary } | bytes collect) + ^nu --testbin repeater hell 8192 | bytes ends-with (0..<8192 | each {|| "hell" | into binary } | bytes collect) "#); assert_eq!(actual.out, "true"); @@ -92,7 +92,7 @@ fn long_stream_mixed_exact() { let binseg = (0..<2048 | each {|| 0x[003d9fbf] } | bytes collect) let strseg = (0..<2048 | each {|| "hell" | into binary } | bytes collect) - nu --testbin repeat_bytes 003d9fbf 2048 68656c6c 2048 | bytes ends-with (bytes build $binseg $strseg) + ^nu --testbin repeat_bytes 003d9fbf 2048 68656c6c 2048 | bytes ends-with (bytes build $binseg $strseg) "#); assert_eq!( @@ -109,7 +109,7 @@ fn long_stream_mixed_overflow() { let binseg = (0..<2048 | each {|| 0x[003d9fbf] } | bytes collect) let strseg = (0..<2048 | each {|| "hell" | into binary } | bytes collect) - nu --testbin repeat_bytes 003d9fbf 2048 68656c6c 2048 | bytes ends-with (bytes build 0x[01] $binseg $strseg) + ^nu --testbin repeat_bytes 003d9fbf 2048 68656c6c 2048 | bytes ends-with (bytes build 0x[01] $binseg $strseg) "#); assert_eq!( diff --git a/crates/nu-cmd-extra/tests/commands/bytes/starts_with.rs b/crates/nu-cmd-extra/tests/commands/bytes/starts_with.rs index e7d57698b5..2cf5bc021a 100644 --- a/crates/nu-cmd-extra/tests/commands/bytes/starts_with.rs +++ b/crates/nu-cmd-extra/tests/commands/bytes/starts_with.rs @@ -22,7 +22,7 @@ fn basic_string_fails() { #[test] fn short_stream_binary() { let actual = nu!(r#" - nu --testbin repeater (0x[01]) 5 | bytes starts-with 0x[010101] + ^nu --testbin repeater (0x[01]) 5 | bytes starts-with 0x[010101] "#); assert_eq!(actual.out, "true"); @@ -31,7 +31,7 @@ fn short_stream_binary() { #[test] fn short_stream_mismatch() { let actual = nu!(r#" - nu --testbin repeater (0x[010203]) 5 | bytes starts-with 0x[010204] + ^nu --testbin repeater (0x[010203]) 5 | bytes starts-with 0x[010204] "#); assert_eq!(actual.out, "false"); @@ -40,7 +40,7 @@ fn short_stream_mismatch() { #[test] fn short_stream_binary_overflow() { let actual = nu!(r#" - nu --testbin repeater (0x[01]) 5 | bytes starts-with 0x[010101010101] + ^nu --testbin repeater (0x[01]) 5 | bytes starts-with 0x[010101010101] "#); assert_eq!(actual.out, "false"); @@ -49,7 +49,7 @@ fn short_stream_binary_overflow() { #[test] fn long_stream_binary() { let actual = nu!(r#" - nu --testbin repeater (0x[01]) 32768 | bytes starts-with 0x[010101] + ^nu --testbin repeater (0x[01]) 32768 | bytes starts-with 0x[010101] "#); assert_eq!(actual.out, "true"); @@ -59,7 +59,7 @@ fn long_stream_binary() { fn long_stream_binary_overflow() { // .. ranges are inclusive..inclusive, so we don't need to +1 to check for an overflow let actual = nu!(r#" - nu --testbin repeater (0x[01]) 32768 | bytes starts-with (0..32768 | each {|| 0x[01] } | bytes collect) + ^nu --testbin repeater (0x[01]) 32768 | bytes starts-with (0..32768 | each {|| 0x[01] } | bytes collect) "#); assert_eq!(actual.out, "false"); @@ -69,7 +69,7 @@ fn long_stream_binary_overflow() { fn long_stream_binary_exact() { // ranges are inclusive..inclusive, so we don't need to +1 to check for an overflow let actual = nu!(r#" - nu --testbin repeater (0x[01020304]) 8192 | bytes starts-with (0..<8192 | each {|| 0x[01020304] } | bytes collect) + ^nu --testbin repeater (0x[01020304]) 8192 | bytes starts-with (0..<8192 | each {|| 0x[01020304] } | bytes collect) "#); assert_eq!(actual.out, "true"); @@ -79,7 +79,7 @@ fn long_stream_binary_exact() { fn long_stream_string_exact() { // ranges are inclusive..inclusive, so we don't need to +1 to check for an overflow let actual = nu!(r#" - nu --testbin repeater hell 8192 | bytes starts-with (0..<8192 | each {|| "hell" | into binary } | bytes collect) + ^nu --testbin repeater hell 8192 | bytes starts-with (0..<8192 | each {|| "hell" | into binary } | bytes collect) "#); assert_eq!(actual.out, "true"); @@ -92,7 +92,7 @@ fn long_stream_mixed_exact() { let binseg = (0..<2048 | each {|| 0x[003d9fbf] } | bytes collect) let strseg = (0..<2048 | each {|| "hell" | into binary } | bytes collect) - nu --testbin repeat_bytes 003d9fbf 2048 68656c6c 2048 | bytes starts-with (bytes build $binseg $strseg) + ^nu --testbin repeat_bytes 003d9fbf 2048 68656c6c 2048 | bytes starts-with (bytes build $binseg $strseg) "#); assert_eq!( @@ -109,7 +109,7 @@ fn long_stream_mixed_overflow() { let binseg = (0..<2048 | each {|| 0x[003d9fbf] } | bytes collect) let strseg = (0..<2048 | each {|| "hell" | into binary } | bytes collect) - nu --testbin repeat_bytes 003d9fbf 2048 68656c6c 2048 | bytes starts-with (bytes build $binseg $strseg 0x[01]) + ^nu --testbin repeat_bytes 003d9fbf 2048 68656c6c 2048 | bytes starts-with (bytes build $binseg $strseg 0x[01]) "#); assert_eq!( diff --git a/crates/nu-command/tests/commands/run_external.rs b/crates/nu-command/tests/commands/run_external.rs index 1c7bcc85a1..21fc8c7fa2 100644 --- a/crates/nu-command/tests/commands/run_external.rs +++ b/crates/nu-command/tests/commands/run_external.rs @@ -4,6 +4,14 @@ use nu_test_support::playground::Playground; use rstest::rstest; use rstest_reuse::*; +// Get full path to nu, so we don't use the nu extern +fn nu_path(prefix: &str) -> String { + let binary = nu_test_support::fs::executable_path() + .to_string_lossy() + .to_string(); + format!("{prefix}{}", binary) +} + // Template for run-external test to ensure tests work when calling // the binary directly, using the caret operator, and when using // the run-external command @@ -18,8 +26,8 @@ fn run_external_prefixes(#[case] prefix: &str) {} fn better_empty_redirection(prefix: &str) { let actual = nu!( cwd: "tests/fixtures/formats", - "ls | each {{ |it| {}nu `--testbin` cococo $it.name }} | ignore", - prefix + "ls | each {{ |it| {} `--testbin` cococo $it.name }} | ignore", + nu_path(prefix) ); eprintln!("out: {}", actual.out); @@ -39,9 +47,9 @@ fn explicit_glob(prefix: &str) { let actual = nu!( cwd: dirs.test(), r#" - {}nu `--testbin` cococo ('*.txt' | into glob) + {} `--testbin` cococo ('*.txt' | into glob) "#, - prefix + nu_path(prefix) ); assert!(actual.out.contains("D&D_volume_1.txt")); @@ -61,9 +69,9 @@ fn bare_word_expand_path_glob(prefix: &str) { let actual = nu!( cwd: dirs.test(), " - {}nu `--testbin` cococo *.txt + {} `--testbin` cococo *.txt ", - prefix + nu_path(prefix) ); assert!(actual.out.contains("D&D_volume_1.txt")); @@ -83,9 +91,9 @@ fn backtick_expand_path_glob(prefix: &str) { let actual = nu!( cwd: dirs.test(), r#" - {}nu `--testbin` cococo `*.txt` + {} `--testbin` cococo `*.txt` "#, - prefix + nu_path(prefix) ); assert!(actual.out.contains("D&D_volume_1.txt")); @@ -105,9 +113,9 @@ fn single_quote_does_not_expand_path_glob(prefix: &str) { let actual = nu!( cwd: dirs.test(), r#" - {}nu `--testbin` cococo '*.txt' + {} `--testbin` cococo '*.txt' "#, - prefix + nu_path(prefix) ); assert_eq!(actual.out, "*.txt"); @@ -126,9 +134,9 @@ fn double_quote_does_not_expand_path_glob(prefix: &str) { let actual = nu!( cwd: dirs.test(), r#" - {}nu `--testbin` cococo "*.txt" + {} `--testbin` cococo "*.txt" "#, - prefix + nu_path(prefix) ); assert_eq!(actual.out, "*.txt"); @@ -141,9 +149,9 @@ fn failed_command_with_semicolon_will_not_execute_following_cmds(prefix: &str) { let actual = nu!( cwd: dirs.test(), " - {}nu `--testbin` fail; echo done + {} `--testbin` fail; echo done ", - prefix + nu_path(prefix) ); assert!(!actual.out.contains("done")); @@ -156,9 +164,9 @@ fn external_args_with_quoted(prefix: &str) { let actual = nu!( cwd: dirs.test(), r#" - {}nu `--testbin` cococo "foo=bar 'hi'" + {} `--testbin` cococo "foo=bar 'hi'" "#, - prefix + nu_path(prefix) ); assert_eq!(actual.out, "foo=bar 'hi'"); @@ -198,9 +206,9 @@ fn external_arg_with_non_option_like_embedded_quotes(#[case] prefix: &str) { let actual = nu!( cwd: dirs.test(), r#" - {}nu `--testbin` cococo foo='bar' 'foo'=bar + {} `--testbin` cococo foo='bar' 'foo'=bar "#, - prefix + nu_path(prefix) ); assert_eq!(actual.out, "foo=bar foo=bar"); @@ -217,9 +225,9 @@ fn external_arg_with_string_interpolation(#[case] prefix: &str) { let actual = nu!( cwd: dirs.test(), r#" - {}nu `--testbin` cococo foo=(2 + 2) $"foo=(2 + 2)" foo=$"(2 + 2)" + {} `--testbin` cococo foo=(2 + 2) $"foo=(2 + 2)" foo=$"(2 + 2)" "#, - prefix + nu_path(prefix) ); assert_eq!(actual.out, "foo=4 foo=4 foo=4"); @@ -233,9 +241,9 @@ fn external_arg_with_variable_name(prefix: &str) { cwd: dirs.test(), r#" let dump_command = "PGPASSWORD='db_secret' pg_dump -Fc -h 'db.host' -p '$db.port' -U postgres -d 'db_name' > '/tmp/dump_name'"; - {}nu `--testbin` nonu $dump_command + {} `--testbin` nonu $dump_command "#, - prefix + nu_path(prefix) ); assert_eq!( @@ -251,9 +259,9 @@ fn external_command_escape_args(prefix: &str) { let actual = nu!( cwd: dirs.test(), r#" - {}nu `--testbin` cococo "\"abcd" + {} `--testbin` cococo "\"abcd" "#, - prefix + nu_path(prefix) ); assert_eq!(actual.out, r#""abcd"#); @@ -264,9 +272,9 @@ fn external_command_escape_args(prefix: &str) { fn external_command_ndots_args(prefix: &str) { let actual = nu!( r#" - {}nu `--testbin` cococo foo/. foo/.. foo/... foo/./bar foo/../bar foo/.../bar ./bar ../bar .../bar + {} `--testbin` cococo foo/. foo/.. foo/... foo/./bar foo/../bar foo/.../bar ./bar ../bar .../bar "#, - prefix + nu_path(prefix) ); assert_eq!( @@ -286,9 +294,9 @@ fn external_command_ndots_leading_dot_slash(prefix: &str) { // Don't expand ndots with a leading `./` let actual = nu!( r#" - {}nu `--testbin` cococo ./... ./.... + {} `--testbin` cococo ./... ./.... "#, - prefix + nu_path(prefix) ); assert_eq!(actual.out, "./... ./...."); @@ -300,9 +308,9 @@ fn external_command_url_args(prefix: &str) { // here let actual = nu!( r#" - {}nu `--testbin` cococo http://example.com http://example.com/.../foo //foo + {} `--testbin` cococo http://example.com http://example.com/.../foo //foo "#, - prefix + nu_path(prefix) ); assert_eq!( @@ -359,9 +367,9 @@ fn external_arg_expand_tilde(#[case] prefix: &str) { let actual = nu!( cwd: dirs.test(), r#" - {}nu `--testbin` cococo ~/foo ~/(2 + 2) + {} `--testbin` cococo ~/foo ~/(2 + 2) "#, - prefix + nu_path(prefix) ); let home = dirs::home_dir().expect("failed to find home dir"); @@ -382,7 +390,7 @@ fn external_command_not_expand_tilde_with_quotes(prefix: &str) { Playground::setup( "external command not expand tilde with quotes", |dirs, _| { - let actual = nu!(cwd: dirs.test(), r#"{}nu `--testbin` nonu "~""#, prefix); + let actual = nu!(cwd: dirs.test(), r#"{} `--testbin` nonu "~""#, nu_path(prefix)); assert_eq!(actual.out, r#"~"#); }, ) @@ -393,7 +401,7 @@ fn external_command_expand_tilde_with_back_quotes(prefix: &str) { Playground::setup( "external command not expand tilde with quotes", |dirs, _| { - let actual = nu!(cwd: dirs.test(), r#"{}nu `--testbin` nonu `~`"#, prefix); + let actual = nu!(cwd: dirs.test(), r#"{} `--testbin` nonu `~`"#, nu_path(prefix)); assert!(!actual.out.contains('~')); }, ) @@ -404,8 +412,8 @@ fn external_command_receives_raw_binary_data(prefix: &str) { Playground::setup("external command receives raw binary data", |dirs, _| { let actual = nu!( cwd: dirs.test(), - "0x[deadbeef] | {}nu `--testbin` input_bytes_length", - prefix + "0x[deadbeef] | {} `--testbin` input_bytes_length", + nu_path(prefix) ); assert_eq!(actual.out, r#"4"#); }) @@ -494,9 +502,9 @@ fn quotes_trimmed_when_shelling_out(prefix: &str) { // regression test for a bug where we weren't trimming quotes around string args before shelling out to cmd.exe let actual = nu!( r#" - {}nu `--testbin` cococo "foo" + {} `--testbin` cococo "foo" "#, - prefix + nu_path(prefix) ); assert_eq!(actual.out, "foo"); @@ -565,8 +573,9 @@ fn expand_command_if_list(#[case] prefix: &str) { let actual = nu!( cwd: dirs.test(), r#" - let cmd = [nu `--testbin`]; {}$cmd meow foo.txt + let cmd = ['{}' `--testbin`]; {}$cmd meow foo.txt "#, + nu_path(""), prefix ); diff --git a/crates/nu-engine/src/compile/call.rs b/crates/nu-engine/src/compile/call.rs index 3729489dd2..0bdef3b664 100644 --- a/crates/nu-engine/src/compile/call.rs +++ b/crates/nu-engine/src/compile/call.rs @@ -19,7 +19,8 @@ pub(crate) fn compile_call( let decl = working_set.get_decl(call.decl_id); // Check if this call has --help - if so, just redirect to `help` - if call.named_iter().any(|(name, _, _)| name.item == "help") { + // Special case the `nu` extern: we want to forward --help to the script + if call.named_iter().any(|(name, _, _)| name.item == "help") && decl.name() != "nu" { let name = working_set .find_decl_name(call.decl_id) // check for name in scope .and_then(|name| std::str::from_utf8(name).ok()) diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs index 80bef3cc66..c774d66ba8 100644 --- a/crates/nu-protocol/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -715,6 +715,14 @@ impl CustomExample { result: self.result.clone(), } } + + pub fn from_example(example: Example<'_>) -> Self { + Self { + example: example.example.to_string(), + description: example.description.to_string(), + result: example.result, + } + } } #[derive(Clone)] diff --git a/src/command.rs b/src/command.rs index 85c7f8006c..0736a968a4 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1,9 +1,10 @@ use nu_engine::{command_prelude::*, get_full_help}; -use nu_parser::{escape_for_script_arg, parse}; +use nu_parser::{KnownExternal, escape_for_script_arg, parse}; use nu_protocol::{ + CustomExample, ast::{Expr, Expression}, engine::StateWorkingSet, - report_parse_error, + report_parse_error, report_shell_error, }; use nu_utils::{escape_quote_string, stdout_write_all_and_flush}; @@ -518,3 +519,24 @@ impl Command for Nu { ] } } + +/// Add the [`Nu`] command as an extern so we get help and flag completions +pub(crate) fn add_nu_extern(engine_state: &mut EngineState) { + let nu_extern = KnownExternal { + signature: Box::new(Nu.signature()), + attributes: vec![], + examples: Nu + .examples() + .into_iter() + .map(CustomExample::from_example) + .collect(), + }; + + let mut working_set = nu_protocol::engine::StateWorkingSet::new(engine_state); + working_set.add_decl(Box::new(nu_extern)); + let delta = working_set.render(); + + if let Err(err) = engine_state.merge_delta(delta) { + report_shell_error(engine_state, &err); + } +} diff --git a/src/main.rs b/src/main.rs index 520c0ba6af..3c17bb193c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,7 @@ use crate::{ config_files::set_config_path, logger::{configure, logger}, }; -use command::gather_commandline_args; +use command::{add_nu_extern, gather_commandline_args}; use log::{Level, trace}; use miette::Result; use nu_cli::gather_parent_env_vars; @@ -79,6 +79,7 @@ fn main() -> Result<()> { let mut working_set = nu_protocol::engine::StateWorkingSet::new(&engine_state); working_set.add_decl(Box::new(nu_cli::NuHighlight)); working_set.add_decl(Box::new(nu_cli::Print)); + working_set.render() }; @@ -202,6 +203,9 @@ fn main() -> Result<()> { std::process::exit(1) }); + // this has to happen after `parse_command_args` since that hides the `Nu` decl + add_nu_extern(&mut engine_state); + experimental_options::load(&engine_state, &parsed_nu_cli_args, !script_name.is_empty()); // keep this condition in sync with the branches at the end @@ -383,9 +387,6 @@ fn main() -> Result<()> { "chop" => test_bins::chop(), "repeater" => test_bins::repeater(), "repeat_bytes" => test_bins::repeat_bytes(), - // Important: nu_repl must be called with `--testbin=nu_repl` - // `--testbin nu_repl` will not work due to argument count logic - // in test_bins.rs "nu_repl" => test_bins::nu_repl(), "input_bytes_length" => test_bins::input_bytes_length(), _ => std::process::exit(1), diff --git a/src/test_bins.rs b/src/test_bins.rs index e0587859f5..16d4ecf3dd 100644 --- a/src/test_bins.rs +++ b/src/test_bins.rs @@ -45,7 +45,6 @@ fn echo_one_env(arg: &str, to_stdout: bool) { /// If it's not present, panic instead pub fn echo_env_mixed() { let args = args(); - let args = &args[1..]; if args.len() != 3 { panic!( @@ -75,11 +74,11 @@ pub fn echo_env_mixed() { pub fn cococo() { let args: Vec = args(); - if args.len() > 1 { + if !args.is_empty() { // Write back out all the arguments passed // if given at least 1 instead of chickens // speaking co co co. - println!("{}", &args[1..].join(" ")); + println!("{}", args.join(" ")); } else { println!("cococo"); } @@ -89,7 +88,7 @@ pub fn cococo() { pub fn meow() { let args: Vec = args(); - for arg in args.iter().skip(1) { + for arg in args.iter() { let contents = std::fs::read_to_string(arg).expect("Expected a filepath"); println!("{contents}"); } @@ -102,7 +101,7 @@ pub fn meowb() { let stdout = io::stdout(); let mut handle = stdout.lock(); - for arg in args.iter().skip(1) { + for arg in args.iter() { let buf = std::fs::read(arg).expect("Expected a filepath"); handle.write_all(&buf).expect("failed to write to stdout"); } @@ -118,7 +117,7 @@ pub fn relay() { /// nu --testbin nonu a b c /// abc pub fn nonu() { - args().iter().skip(1).for_each(|arg| print!("{arg}")); + args().iter().for_each(|arg| print!("{arg}")); } /// Repeat a string or char N times @@ -128,8 +127,7 @@ pub fn nonu() { /// testtesttesttesttest pub fn repeater() { let mut stdout = io::stdout(); - let args = args(); - let mut args = args.iter().skip(1); + let mut args = args().into_iter(); let letter = args.next().expect("needs a character to iterate"); let count = args.next().expect("need the number of times to iterate"); @@ -144,8 +142,7 @@ pub fn repeater() { /// A version of repeater that can output binary data, even null bytes pub fn repeat_bytes() { let mut stdout = io::stdout(); - let args = args(); - let mut args = args.iter().skip(1); + let mut args = args().into_iter(); while let (Some(binary), Some(count)) = (args.next(), args.next()) { let bytes: Vec = (0..binary.len()) @@ -173,7 +170,6 @@ pub fn iecho() { let mut stdout = io::stdout(); let _ = args() .iter() - .skip(1) .cycle() .try_for_each(|v| writeln!(stdout, "{v}")); } @@ -364,6 +360,18 @@ pub fn input_bytes_length() { } fn args() -> Vec { - // skip (--testbin bin_name args) - std::env::args().skip(2).collect() + // skip `nu` path (first argument) + // then skip --testbin=foo OR --testbin foo + std::env::args().skip(1).fold(vec![], |mut acc, it| { + // if we have --testbin foo, skip the previous argument (--testbin) and this argument (foo) + if acc.last().is_some_and(|last| last == "--testbin") { + acc.pop(); + return acc; + } + // if we have --testbin=foo, skip it + if !it.starts_with("--testbin=") { + acc.push(it); + } + acc + }) } diff --git a/tests/repl/test_known_external.rs b/tests/repl/test_known_external.rs index 74bf8815b2..40d30efb7e 100644 --- a/tests/repl/test_known_external.rs +++ b/tests/repl/test_known_external.rs @@ -173,3 +173,9 @@ fn known_external_arg_internally_quoted_options() -> TestResult { "--option=test", ) } + +// Verify that the KnownExternal for the `nu` binary exists +#[test] +fn known_external_nu() -> TestResult { + run_test_contains("help nu", "Usage") +} diff --git a/tests/repl/test_parser.rs b/tests/repl/test_parser.rs index 45b890653c..92fb02520c 100644 --- a/tests/repl/test_parser.rs +++ b/tests/repl/test_parser.rs @@ -422,27 +422,27 @@ fn append_assign_takes_pipeline() -> TestResult { #[test] fn assign_bare_external_fails() { - let result = nu!("$env.FOO = nu --testbin cococo"); + let result = nu!("$env.FOO = cargo --version"); assert!(!result.status.success()); assert!(result.err.contains("must be explicit")); } #[test] fn assign_bare_external_with_caret() { - let result = nu!("$env.FOO = ^nu --testbin cococo"); + let result = nu!("$env.FOO = ^cargo --version"); assert!(result.status.success()); } #[test] fn assign_backtick_quoted_external_fails() { - let result = nu!("$env.FOO = `nu` --testbin cococo"); + let result = nu!("$env.FOO = `cargo` --version"); assert!(!result.status.success()); assert!(result.err.contains("must be explicit")); } #[test] fn assign_backtick_quoted_external_with_caret() { - let result = nu!("$env.FOO = ^`nu` --testbin cococo"); + let result = nu!("$env.FOO = ^`cargo` --version"); assert!(result.status.success()); } diff --git a/tests/scope/mod.rs b/tests/scope/mod.rs index ffcaa80bd7..026b4f4b38 100644 --- a/tests/scope/mod.rs +++ b/tests/scope/mod.rs @@ -290,7 +290,7 @@ fn scope_externs_sorted() { ]; let actual = nu!(&inp.join("; ")); - assert_eq!(actual.out, "abc"); + assert!(actual.out.starts_with("abc")); } #[test] diff --git a/tests/shell/pipeline/commands/external.rs b/tests/shell/pipeline/commands/external.rs index 5b42798ba5..d5f50e5855 100644 --- a/tests/shell/pipeline/commands/external.rs +++ b/tests/shell/pipeline/commands/external.rs @@ -609,15 +609,15 @@ mod external_command_arguments { #[test] fn remove_quotes_in_shell_arguments() { - let actual = nu!("nu --testbin cococo expression='-r -w'"); + let actual = nu!("^nu --testbin cococo expression='-r -w'"); assert_eq!(actual.out, "expression=-r -w"); - let actual = nu!(r#"nu --testbin cococo expression="-r -w""#); + let actual = nu!(r#"^nu --testbin cococo expression="-r -w""#); assert_eq!(actual.out, "expression=-r -w"); - let actual = nu!("nu --testbin cococo expression='-r -w'"); + let actual = nu!("^nu --testbin cococo expression='-r -w'"); assert_eq!(actual.out, "expression=-r -w"); - let actual = nu!(r#"nu --testbin cococo expression="-r\" -w""#); + let actual = nu!(r#"^nu --testbin cococo expression="-r\" -w""#); assert_eq!(actual.out, r#"expression=-r" -w"#); - let actual = nu!(r#"nu --testbin cococo expression='-r\" -w'"#); + let actual = nu!(r#"^nu --testbin cococo expression='-r\" -w'"#); assert_eq!(actual.out, r#"expression=-r\" -w"#); } } @@ -722,7 +722,7 @@ fn external_error_with_backtrace() { #[test] fn sub_external_expression_with_and_op_should_raise_proper_error() { - let actual = nu!("(nu --testbin cococo false) and true"); + let actual = nu!("(^nu --testbin cococo false) and true"); assert!( actual .err