use nu_command::util::report_error; use nu_engine::{get_full_help, CallExt}; use nu_parser::parse; use nu_parser::{escape_for_script_arg, escape_quote_string}; use nu_protocol::{ ast::{Call, Expr, Expression, PipelineElement}, engine::{Command, EngineState, Stack, StateWorkingSet}, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Value, }; use nu_utils::stdout_write_all_and_flush; pub(crate) fn gather_commandline_args() -> (Vec, String, Vec) { // Would be nice if we had a way to parse this. The first flags we see will be going to nushell // then it'll be the script name // then the args to the script let mut args_to_nushell = Vec::from(["nu".into()]); let mut script_name = String::new(); let mut args = std::env::args(); // Mimic the behaviour of bash/zsh if let Some(argv0) = args.next() { if argv0.starts_with('-') { args_to_nushell.push("--login".into()); } } while let Some(arg) = args.next() { if !arg.starts_with('-') { script_name = arg; break; } let flag_value = match arg.as_ref() { "--commands" | "-c" | "--table-mode" | "-m" | "-e" | "--execute" | "--config" | "--env-config" => args.next().map(|a| escape_quote_string(&a)), #[cfg(feature = "plugin")] "--plugin-config" => args.next().map(|a| escape_quote_string(&a)), "--log-level" | "--log-target" | "--testbin" | "--threads" | "-t" => args.next(), _ => None, }; args_to_nushell.push(arg); if let Some(flag_value) = flag_value { args_to_nushell.push(flag_value); } } let args_to_script = if !script_name.is_empty() { args.map(|arg| escape_for_script_arg(&arg)).collect() } else { Vec::default() }; (args_to_nushell, script_name, args_to_script) } pub(crate) fn parse_commandline_args( commandline_args: &str, engine_state: &mut EngineState, ) -> Result { let (block, delta) = { let mut working_set = StateWorkingSet::new(engine_state); working_set.add_decl(Box::new(Nu)); let (output, err) = parse( &mut working_set, None, commandline_args.as_bytes(), false, &[], ); if let Some(err) = err { report_error(&working_set, &err); std::process::exit(1); } working_set.hide_decl(b"nu"); (output, working_set.render()) }; engine_state.merge_delta(delta)?; let mut stack = Stack::new(); // We should have a successful parse now if let Some(pipeline) = block.pipelines.get(0) { if let Some(PipelineElement::Expression( _, Expression { expr: Expr::Call(call), .. }, )) = pipeline.elements.get(0) { let redirect_stdin = call.get_named_arg("stdin"); let login_shell = call.get_named_arg("login"); let interactive_shell = call.get_named_arg("interactive"); let commands: Option = call.get_flag_expr("commands"); let testbin: Option = call.get_flag_expr("testbin"); #[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 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"); let log_target: Option = call.get_flag_expr("log-target"); let execute: Option = call.get_flag_expr("execute"); let table_mode: Option = call.get_flag(engine_state, &mut stack, "table-mode")?; fn extract_contents( expression: Option, ) -> Result>, ShellError> { if let Some(expr) = expression { let str = expr.as_string(); if let Some(str) = str { Ok(Some(Spanned { item: str, span: expr.span, })) } else { Err(ShellError::TypeMismatch { err_message: "string".into(), span: expr.span, }) } } else { Ok(None) } } let commands = extract_contents(commands)?; let testbin = extract_contents(testbin)?; #[cfg(feature = "plugin")] let plugin_file = extract_contents(plugin_file)?; let config_file = extract_contents(config_file)?; let env_file = extract_contents(env_file)?; let log_level = extract_contents(log_level)?; let log_target = extract_contents(log_target)?; let execute = extract_contents(execute)?; let help = call.has_flag("help"); if help { let full_help = get_full_help( &Nu.signature(), &Nu.examples(), engine_state, &mut stack, true, ); let _ = std::panic::catch_unwind(move || stdout_write_all_and_flush(full_help)); std::process::exit(0); } if call.has_flag("version") { let version = env!("CARGO_PKG_VERSION").to_string(); let _ = std::panic::catch_unwind(move || { stdout_write_all_and_flush(format!("{version}\n")) }); std::process::exit(0); } return Ok(NushellCliArgs { redirect_stdin, login_shell, interactive_shell, commands, testbin, #[cfg(feature = "plugin")] plugin_file, no_config_file, config_file, env_file, log_level, log_target, execute, table_mode, }); } } // Just give the help and exit if the above fails let full_help = get_full_help( &Nu.signature(), &Nu.examples(), engine_state, &mut stack, true, ); print!("{full_help}"); std::process::exit(1); } pub(crate) struct NushellCliArgs { pub(crate) redirect_stdin: Option>, pub(crate) login_shell: Option>, pub(crate) interactive_shell: Option>, pub(crate) commands: Option>, pub(crate) testbin: Option>, #[cfg(feature = "plugin")] pub(crate) plugin_file: Option>, pub(crate) no_config_file: Option>, pub(crate) config_file: Option>, pub(crate) env_file: Option>, pub(crate) log_level: Option>, pub(crate) log_target: Option>, pub(crate) execute: Option>, pub(crate) table_mode: Option, } #[derive(Clone)] struct Nu; impl Command for Nu { fn name(&self) -> &str { "nu" } fn signature(&self) -> Signature { let mut signature = Signature::build("nu") .usage("The nushell language and shell.") .named( "commands", SyntaxShape::String, "run the given commands and then exit", Some('c'), ) .named( "execute", SyntaxShape::String, "run the given commands and then enter an interactive shell", Some('e'), ) .switch("interactive", "start as an interactive shell", Some('i')) .switch("login", "start as a login shell", Some('l')) .named( "table-mode", SyntaxShape::String, "the table mode to use. rounded is default.", Some('m'), ) .switch( "no-config-file", "start with no config file and no env file", Some('n'), ) .named( "threads", SyntaxShape::Int, "threads to use for parallel commands", Some('t'), ) .switch("version", "print the version", Some('v')) .named( "config", SyntaxShape::String, "start with an alternate config file", None, ) .named( "env-config", SyntaxShape::String, "start with an alternate environment config file", None, ); #[cfg(feature = "plugin")] { signature = signature.named( "plugin-config", SyntaxShape::String, "start with an alternate plugin signature file", None, ); } signature = signature .named( "log-level", SyntaxShape::String, "log level for diagnostic logs (error, warn, info, debug, trace). Off by default", None, ) .named( "log-target", SyntaxShape::String, "set the target for the log to output. stdout, stderr(default), mixed or file", None, ) .switch( "stdin", "redirect standard input to a command (with `-c`) or a script file", None, ) .named( "testbin", SyntaxShape::String, "run internal test binary", None, ) .optional( "script file", SyntaxShape::Filepath, "name of the optional script file to run", ) .rest( "script args", SyntaxShape::String, "parameters to the script file", ) .category(Category::System); signature } fn usage(&self) -> &str { "The nushell language and shell." } fn run( &self, engine_state: &EngineState, stack: &mut Stack, call: &Call, _input: PipelineData, ) -> Result { Ok(Value::String { val: get_full_help(&Nu.signature(), &Nu.examples(), engine_state, stack, true), span: call.head, } .into_pipeline_data()) } fn examples(&self) -> Vec { vec![ Example { description: "Run a script", example: "nu myfile.nu", result: None, }, Example { description: "Run nushell interactively (as a shell or REPL)", example: "nu", result: None, }, ] } }