diff --git a/crates/nu-test-support/src/macros.rs b/crates/nu-test-support/src/macros.rs index 7262194046..39cc988025 100644 --- a/crates/nu-test-support/src/macros.rs +++ b/crates/nu-test-support/src/macros.rs @@ -113,6 +113,167 @@ macro_rules! nu { format!($path, $( $part ),*) }}; + // Do the actual work. + (@main $opts:expr, $path:expr) => {{ + pub use std::error::Error; + pub use std::io::prelude::*; + pub use std::process::{Command, Stdio}; + pub use $crate::NATIVE_PATH_ENV_VAR; + + pub fn escape_quote_string(input: String) -> String { + let mut output = String::with_capacity(input.len() + 2); + output.push('"'); + + for c in input.chars() { + if c == '"' || c == '\\' { + output.push('\\'); + } + output.push(c); + } + + output.push('"'); + output + } + + let test_bins = $crate::fs::binaries(); + + let cwd = std::env::current_dir().expect("Could not get current working directory."); + let test_bins = $crate::nu_path::canonicalize_with(&test_bins, cwd).unwrap_or_else(|e| { + panic!( + "Couldn't canonicalize dummy binaries path {}: {:?}", + test_bins.display(), + e + ) + }); + + let mut paths = $crate::shell_os_paths(); + paths.insert(0, test_bins); + + let path = $path.lines().collect::>().join("; "); + + let paths_joined = match std::env::join_paths(paths) { + Ok(all) => all, + Err(_) => panic!("Couldn't join paths for PATH var."), + }; + + let target_cwd = $opts.cwd.unwrap_or(".".to_string()); + let locale = $opts.locale.unwrap_or("en_US.UTF-8".to_string()); + + let mut command = Command::new($crate::fs::executable_path()); + command + .env("PWD", &target_cwd) + .env(nu_utils::locale::LOCALE_OVERRIDE_ENV_VAR, locale) + .current_dir(target_cwd) + .env(NATIVE_PATH_ENV_VAR, paths_joined) + // .arg("--skip-plugins") + // .arg("--no-history") + // .arg("--config-file") + // .arg($crate::fs::DisplayPath::display_path(&$crate::fs::fixtures().join("playground/config/default.toml"))) + .arg("--no-std-lib") + .arg(format!("-c {}", escape_quote_string(path))) + .stdout(Stdio::piped()) + // .stdin(Stdio::piped()) + .stderr(Stdio::piped()); + + let mut process = match command.spawn() + { + Ok(child) => child, + Err(why) => panic!("Can't run test {:?} {}", $crate::fs::executable_path(), why.to_string()), + }; + + // let stdin = process.stdin.as_mut().expect("couldn't open stdin"); + // stdin + // .write_all(b"exit\n") + // .expect("couldn't write to stdin"); + + let output = process + .wait_with_output() + .expect("couldn't read from stdout/stderr"); + + let out = $crate::macros::read_std(&output.stdout); + let err = String::from_utf8_lossy(&output.stderr); + + println!("=== stderr\n{}", err); + + $crate::Outcome::new(out,err.into_owned()) + }}; + + // This is the entrypoint for this macro. + ($($token:tt)*) => {{ + #[derive(Default)] + struct NuOpts { + cwd: Option, + locale: Option, + } + + nu!(@options [ ] $($token)*) + }}; +} + +#[macro_export] +macro_rules! nu_with_std { + // In the `@options` phase, we restucture all the + // `$field_1: $value_1, $field_2: $value_2, ...` + // pairs to a structure like + // `@options[ $field_1 => $value_1 ; $field_2 => $value_2 ; ... ]`. + // We do this to later distinguish the options from the `$path` and `$part`s. + // (See + // https://users.rust-lang.org/t/i-dont-think-this-local-ambiguity-when-calling-macro-is-ambiguous/79401?u=x3ro + // ) + // + // If there is any special treatment needed for the `$value`, we can just + // match for the specific `field` name. + ( + @options [ $($options:tt)* ] + cwd: $value:expr, + $($rest:tt)* + ) => { + nu!(@options [ $($options)* cwd => $crate::fs::in_directory($value) ; ] $($rest)*) + }; + // For all other options, we call `.into()` on the `$value` and hope for the best. ;) + ( + @options [ $($options:tt)* ] + $field:ident : $value:expr, + $($rest:tt)* + ) => { + nu!(@options [ $($options)* $field => $value.into() ; ] $($rest)*) + }; + + // When the `$field: $value,` pairs are all parsed, the next tokens are the `$path` and any + // number of `$part`s, potentially followed by a trailing comma. + ( + @options [ $($options:tt)* ] + $path:expr + $(, $part:expr)* + $(,)* + ) => {{ + // Here we parse the options into a `NuOpts` struct + let opts = nu!(@nu_opts $($options)*); + // and format the `$path` using the `$part`s + let path = nu!(@format_path $path, $($part),*); + // Then finally we go to the `@main` phase, where the actual work is done. + nu!(@main opts, path) + }}; + + // Create the NuOpts struct from the `field => value ;` pairs + (@nu_opts $( $field:ident => $value:expr ; )*) => { + NuOpts{ + $( + $field: Some($value), + )* + ..Default::default() + } + }; + + // Helper to format `$path`. + (@format_path $path:expr $(,)?) => { + // When there are no `$part`s, do not format anything + $path + }; + (@format_path $path:expr, $($part:expr),* $(,)?) => {{ + format!($path, $( $part ),*) + }}; + // Do the actual work. (@main $opts:expr, $path:expr) => {{ pub use std::error::Error; diff --git a/src/command.rs b/src/command.rs index e4e502e8c4..6a2958b59d 100644 --- a/src/command.rs +++ b/src/command.rs @@ -103,6 +103,7 @@ pub(crate) fn parse_commandline_args( #[cfg(feature = "plugin")] let plugin_file: Option = call.get_flag_expr("plugin-config"); let no_config_file = call.get_named_arg("no-config-file"); + let no_std_lib = call.get_named_arg("no-std-lib"); let config_file: Option = call.get_flag_expr("config"); let env_file: Option = call.get_flag_expr("env-config"); let log_level: Option = call.get_flag_expr("log-level"); @@ -176,6 +177,7 @@ pub(crate) fn parse_commandline_args( #[cfg(feature = "plugin")] plugin_file, no_config_file, + no_std_lib, config_file, env_file, log_level, @@ -211,6 +213,7 @@ pub(crate) struct NushellCliArgs { #[cfg(feature = "plugin")] pub(crate) plugin_file: Option>, pub(crate) no_config_file: Option>, + pub(crate) no_std_lib: Option>, pub(crate) config_file: Option>, pub(crate) env_file: Option>, pub(crate) log_level: Option>, @@ -259,6 +262,7 @@ impl Command for Nu { "start with no config file and no env file", Some('n'), ) + .switch("no-std-lib", "start with no standard library", None) .named( "threads", SyntaxShape::Int, diff --git a/src/run.rs b/src/run.rs index 303925bdbf..67ef72c9f1 100644 --- a/src/run.rs +++ b/src/run.rs @@ -22,7 +22,9 @@ pub(crate) fn run_commands( let mut stack = nu_protocol::engine::Stack::new(); let start_time = std::time::Instant::now(); - load_standard_library(engine_state)?; + if parsed_nu_cli_args.no_std_lib.is_none() { + load_standard_library(engine_state)?; + } #[cfg(feature = "plugin")] read_plugin_file( @@ -111,7 +113,9 @@ pub(crate) fn run_file( let mut stack = nu_protocol::engine::Stack::new(); let start_time = std::time::Instant::now(); - load_standard_library(engine_state)?; + if parsed_nu_cli_args.no_std_lib.is_none() { + load_standard_library(engine_state)?; + } #[cfg(feature = "plugin")] read_plugin_file( @@ -210,7 +214,9 @@ pub(crate) fn run_repl( let mut stack = nu_protocol::engine::Stack::new(); let start_time = std::time::Instant::now(); - load_standard_library(engine_state)?; + if parsed_nu_cli_args.no_std_lib.is_none() { + load_standard_library(engine_state)?; + } if parsed_nu_cli_args.no_config_file.is_none() { setup_config(