diff --git a/crates/nu-engine/src/documentation.rs b/crates/nu-engine/src/documentation.rs index 9e2f17fc1..f470e6902 100644 --- a/crates/nu-engine/src/documentation.rs +++ b/crates/nu-engine/src/documentation.rs @@ -206,7 +206,10 @@ pub fn get_documentation( } if let Some(rest_positional) = &sig.rest_positional { - long_desc.push_str(&format!(" ...args: {}\n", rest_positional.desc)); + long_desc.push_str(&format!( + " ...{}: {}\n", + rest_positional.name, rest_positional.desc + )); } } @@ -267,7 +270,7 @@ fn get_flags_section(signature: &Signature) -> String { if let Some(short) = flag.short { if flag.required { format!( - " -{}{} (required parameter) {:?} {}\n", + " -{}{} (required parameter) {:?}\n {}\n", short, if !flag.long.is_empty() { format!(", --{}", flag.long) @@ -279,7 +282,7 @@ fn get_flags_section(signature: &Signature) -> String { ) } else { format!( - " -{}{} {:?} {}\n", + " -{}{} <{:?}>\n {}\n", short, if !flag.long.is_empty() { format!(", --{}", flag.long) @@ -292,16 +295,16 @@ fn get_flags_section(signature: &Signature) -> String { } } else if flag.required { format!( - " --{} (required parameter) {:?} {}\n", + " --{} (required parameter) <{:?}>\n {}\n", flag.long, arg, flag.desc ) } else { - format!(" --{} {:?} {}\n", flag.long, arg, flag.desc) + format!(" --{} {:?}\n {}\n", flag.long, arg, flag.desc) } } else if let Some(short) = flag.short { if flag.required { format!( - " -{}{} (required parameter) {}\n", + " -{}{} (required parameter)\n {}\n", short, if !flag.long.is_empty() { format!(", --{}", flag.long) @@ -312,7 +315,7 @@ fn get_flags_section(signature: &Signature) -> String { ) } else { format!( - " -{}{} {}\n", + " -{}{}\n {}\n", short, if !flag.long.is_empty() { format!(", --{}", flag.long) @@ -323,9 +326,12 @@ fn get_flags_section(signature: &Signature) -> String { ) } } else if flag.required { - format!(" --{} (required parameter) {}\n", flag.long, flag.desc) + format!( + " --{} (required parameter)\n {}\n", + flag.long, flag.desc + ) } else { - format!(" --{} {}\n", flag.long, flag.desc) + format!(" --{}\n {}\n", flag.long, flag.desc) }; long_desc.push_str(&msg); } diff --git a/crates/nu-parser/src/lib.rs b/crates/nu-parser/src/lib.rs index 363aed5f0..2346aaed1 100644 --- a/crates/nu-parser/src/lib.rs +++ b/crates/nu-parser/src/lib.rs @@ -13,7 +13,7 @@ pub use flatten::{ pub use lex::{lex, Token, TokenContents}; pub use lite_parse::{lite_parse, LiteBlock}; -pub use parser::{find_captures_in_expr, parse, trim_quotes, Import}; +pub use parser::{find_captures_in_expr, parse, parse_block, trim_quotes, Import}; #[cfg(feature = "plugin")] pub use parse_keywords::parse_register; diff --git a/src/commands.rs b/src/commands.rs new file mode 100644 index 000000000..947bfb8ae --- /dev/null +++ b/src/commands.rs @@ -0,0 +1,136 @@ +use miette::Result; +use nu_engine::{convert_env_values, eval_block}; +use std::path::Path; + +use nu_parser::{lex, lite_parse, parse_block, trim_quotes}; +use nu_protocol::{ + engine::{EngineState, StateDelta, StateWorkingSet}, + Config, PipelineData, Span, Spanned, Value, CONFIG_VARIABLE_ID, +}; + +use crate::utils::{gather_parent_env_vars, report_error}; + +pub(crate) fn evaluate( + commands: &Spanned, + init_cwd: &Path, + engine_state: &mut EngineState, + input: PipelineData, +) -> Result<()> { + // First, set up env vars as strings only + gather_parent_env_vars(engine_state); + + // Run a command (or commands) given to us by the user + let (block, delta) = { + let mut working_set = StateWorkingSet::new(engine_state); + + let (input, span_offset) = + if commands.item.starts_with('\'') || commands.item.starts_with('"') { + ( + trim_quotes(commands.item.as_bytes()), + commands.span.start + 1, + ) + } else { + (commands.item.as_bytes(), commands.span.start) + }; + + let (output, err) = lex(input, span_offset, &[], &[], false); + if let Some(err) = err { + report_error(&working_set, &err); + + std::process::exit(1); + } + + let (output, err) = lite_parse(&output); + if let Some(err) = err { + report_error(&working_set, &err); + + std::process::exit(1); + } + + let (output, err) = parse_block(&mut working_set, &output, false); + if let Some(err) = err { + report_error(&working_set, &err); + + std::process::exit(1); + } + + if let Some(err) = err { + report_error(&working_set, &err); + + std::process::exit(1); + } + (output, working_set.render()) + }; + + if let Err(err) = engine_state.merge_delta(delta, None, init_cwd) { + let working_set = StateWorkingSet::new(engine_state); + report_error(&working_set, &err); + } + + let mut stack = nu_protocol::engine::Stack::new(); + + // Set up our initial config to start from + stack.vars.insert( + CONFIG_VARIABLE_ID, + Value::Record { + cols: vec![], + vals: vec![], + span: Span { start: 0, end: 0 }, + }, + ); + + let config = match stack.get_config() { + Ok(config) => config, + Err(e) => { + let working_set = StateWorkingSet::new(engine_state); + + report_error(&working_set, &e); + Config::default() + } + }; + + // Merge the delta in case env vars changed in the config + match nu_engine::env::current_dir(engine_state, &stack) { + Ok(cwd) => { + if let Err(e) = engine_state.merge_delta(StateDelta::new(), Some(&mut stack), cwd) { + let working_set = StateWorkingSet::new(engine_state); + report_error(&working_set, &e); + } + } + Err(e) => { + let working_set = StateWorkingSet::new(engine_state); + report_error(&working_set, &e); + } + } + + // Translate environment variables from Strings to Values + if let Some(e) = convert_env_values(engine_state, &stack, &config) { + let working_set = StateWorkingSet::new(engine_state); + report_error(&working_set, &e); + std::process::exit(1); + } + + match eval_block(engine_state, &mut stack, &block, input) { + Ok(pipeline_data) => { + for item in pipeline_data { + if let Value::Error { error } = item { + let working_set = StateWorkingSet::new(engine_state); + + report_error(&working_set, &error); + + std::process::exit(1); + } + println!("{}", item.into_string("\n", &config)); + } + } + Err(err) => { + let working_set = StateWorkingSet::new(engine_state); + + report_error(&working_set, &err); + + std::process::exit(1); + } + } + + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 28a5c6bbb..5a9cbacba 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +mod commands; mod config_files; mod eval_file; mod logger; @@ -75,9 +76,17 @@ fn main() -> Result<()> { let mut collect_arg_nushell = false; for arg in std::env::args().skip(1) { if !script_name.is_empty() { - args_to_script.push(arg); + args_to_script.push(if arg.contains(' ') { + format!("'{}'", arg) + } else { + arg + }); } else if collect_arg_nushell { - args_to_nushell.push(arg); + args_to_nushell.push(if arg.contains(' ') { + format!("'{}'", arg) + } else { + arg + }); collect_arg_nushell = false; } else if arg.starts_with('-') { // Cool, it's a flag @@ -106,23 +115,25 @@ fn main() -> Result<()> { match nushell_config { Ok(nushell_config) => { - if !script_name.is_empty() { - let input = if let Some(redirect_stdin) = &nushell_config.redirect_stdin { - let stdin = std::io::stdin(); - let buf_reader = BufReader::new(stdin); + let input = if let Some(redirect_stdin) = &nushell_config.redirect_stdin { + let stdin = std::io::stdin(); + let buf_reader = BufReader::new(stdin); - PipelineData::ByteStream( - ByteStream { - stream: Box::new(BufferedReader::new(buf_reader)), - ctrlc: Some(ctrlc), - }, - redirect_stdin.span, - None, - ) - } else { - PipelineData::new(Span::new(0, 0)) - }; + PipelineData::ByteStream( + ByteStream { + stream: Box::new(BufferedReader::new(buf_reader)), + ctrlc: Some(ctrlc), + }, + redirect_stdin.span, + None, + ) + } else { + PipelineData::new(Span::new(0, 0)) + }; + if let Some(commands) = &nushell_config.commands { + commands::evaluate(commands, &init_cwd, &mut engine_state, input) + } else if !script_name.is_empty() && nushell_config.interactive_shell.is_none() { eval_file::evaluate( script_name, &args_to_script, @@ -131,7 +142,7 @@ fn main() -> Result<()> { input, ) } else { - repl::evaluate(ctrlc, &mut engine_state) + repl::evaluate(&mut engine_state) } } Err(_) => std::process::exit(1), @@ -178,6 +189,20 @@ fn parse_commandline_args( }) = expressions.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 commands = if let Some(expression) = commands { + let contents = engine_state.get_span_contents(&expression.span); + + Some(Spanned { + item: String::from_utf8_lossy(contents).to_string(), + span: expression.span, + }) + } else { + None + }; let help = call.has_flag("help"); @@ -188,7 +213,12 @@ fn parse_commandline_args( std::process::exit(1); } - return Ok(NushellConfig { redirect_stdin }); + return Ok(NushellConfig { + redirect_stdin, + login_shell, + interactive_shell, + commands, + }); } } @@ -200,6 +230,10 @@ fn parse_commandline_args( struct NushellConfig { redirect_stdin: Option>, + #[allow(dead_code)] + login_shell: Option>, + interactive_shell: Option>, + commands: Option>, } #[derive(Clone)] @@ -214,6 +248,14 @@ impl Command for Nu { Signature::build("nu") .desc("The nushell language and shell.") .switch("stdin", "redirect the stdin", None) + .switch("login", "start as a login shell", Some('l')) + .switch("interactive", "start as an interactive shell", Some('i')) + .named( + "commands", + SyntaxShape::String, + "run the given commands and then exit", + Some('c'), + ) .optional( "script file", SyntaxShape::Filepath, diff --git a/src/repl.rs b/src/repl.rs index 3e2c22401..ed04b7a95 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -1,10 +1,4 @@ -use std::{ - sync::{ - atomic::{AtomicBool, Ordering}, - Arc, - }, - time::Instant, -}; +use std::{sync::atomic::Ordering, time::Instant}; use crate::{config_files, prompt_update, reedline_config}; use crate::{ @@ -23,7 +17,7 @@ use nu_protocol::{ }; use reedline::{DefaultHinter, Emacs, Vi}; -pub(crate) fn evaluate(ctrlc: Arc, engine_state: &mut EngineState) -> Result<()> { +pub(crate) fn evaluate(engine_state: &mut EngineState) -> Result<()> { use crate::logger::{configure, logger}; use reedline::{FileBackedHistory, Reedline, Signal}; @@ -97,7 +91,9 @@ pub(crate) fn evaluate(ctrlc: Arc, engine_state: &mut EngineState) - }; //Reset the ctrl-c handler - ctrlc.store(false, Ordering::SeqCst); + if let Some(ctrlc) = &mut engine_state.ctrlc { + ctrlc.store(false, Ordering::SeqCst); + } let line_editor = Reedline::create() .into_diagnostic()? diff --git a/src/tests/test_engine.rs b/src/tests/test_engine.rs index 5c9e1fb04..c5bfa5f9c 100644 --- a/src/tests/test_engine.rs +++ b/src/tests/test_engine.rs @@ -72,7 +72,7 @@ fn in_variable_6() -> TestResult { #[test] fn help_works_with_missing_requirements() -> TestResult { - run_test(r#"each --help | lines | length"#, "15") + run_test(r#"each --help | lines | length"#, "17") } #[test]