diff --git a/crates/nu-command/src/filesystem/mod.rs b/crates/nu-command/src/filesystem/mod.rs index 241e2c81a9..dd09ca7bad 100644 --- a/crates/nu-command/src/filesystem/mod.rs +++ b/crates/nu-command/src/filesystem/mod.rs @@ -14,7 +14,7 @@ pub use cp::Cp; pub use ls::Ls; pub use mkdir::Mkdir; pub use mv::Mv; -pub use open::Open; +pub use open::{BufferedReader, Open}; pub use rm::Rm; pub use save::Save; pub use touch::Touch; diff --git a/crates/nu-command/src/filesystem/open.rs b/crates/nu-command/src/filesystem/open.rs index f0c6466d8b..082efdf02e 100644 --- a/crates/nu-command/src/filesystem/open.rs +++ b/crates/nu-command/src/filesystem/open.rs @@ -184,6 +184,12 @@ pub struct BufferedReader { input: BufReader, } +impl BufferedReader { + pub fn new(input: BufReader) -> Self { + Self { input } + } +} + impl Iterator for BufferedReader { type Item = Result, ShellError>; diff --git a/crates/nu-engine/src/documentation.rs b/crates/nu-engine/src/documentation.rs index 2f32fd3d3b..9e2f17fc1b 100644 --- a/crates/nu-engine/src/documentation.rs +++ b/crates/nu-engine/src/documentation.rs @@ -186,6 +186,10 @@ pub fn get_documentation( long_desc.push('\n'); } + if !sig.named.is_empty() { + long_desc.push_str(&get_flags_section(sig)) + } + if !sig.required_positional.is_empty() || !sig.optional_positional.is_empty() || sig.rest_positional.is_some() @@ -205,13 +209,11 @@ pub fn get_documentation( long_desc.push_str(&format!(" ...args: {}\n", rest_positional.desc)); } } - if !sig.named.is_empty() { - long_desc.push_str(&get_flags_section(sig)) - } if !examples.is_empty() { long_desc.push_str("\nExamples:"); } + for example in examples { long_desc.push('\n'); long_desc.push_str(" "); @@ -222,7 +224,7 @@ pub fn get_documentation( } else if let Some(highlighter) = engine_state.find_decl(b"nu-highlight") { let decl = engine_state.get_decl(highlighter); - if let Ok(output) = decl.run( + match decl.run( engine_state, stack, &Call::new(), @@ -232,16 +234,23 @@ pub fn get_documentation( } .into_pipeline_data(), ) { - let result = output.into_value(Span { start: 0, end: 0 }); - match result.as_string() { - Ok(s) => { - long_desc.push_str(&format!("\n > {}\n", s)); - } - _ => { - long_desc.push_str(&format!("\n > {}\n", example.example)); + Ok(output) => { + let result = output.into_value(Span { start: 0, end: 0 }); + match result.as_string() { + Ok(s) => { + long_desc.push_str(&format!("\n > {}\n", s)); + } + _ => { + long_desc.push_str(&format!("\n > {}\n", example.example)); + } } } + Err(_) => { + long_desc.push_str(&format!("\n > {}\n", example.example)); + } } + } else { + long_desc.push_str(&format!("\n > {}\n", example.example)); } } diff --git a/crates/nu-parser/src/lib.rs b/crates/nu-parser/src/lib.rs index 0bdd0ec117..363aed5f0c 100644 --- a/crates/nu-parser/src/lib.rs +++ b/crates/nu-parser/src/lib.rs @@ -12,9 +12,7 @@ pub use flatten::{ }; pub use lex::{lex, Token, TokenContents}; pub use lite_parse::{lite_parse, LiteBlock}; -pub use parse_keywords::{ - parse_alias, parse_def, parse_def_predecl, parse_let, parse_module, parse_use, -}; + pub use parser::{find_captures_in_expr, parse, trim_quotes, Import}; #[cfg(feature = "plugin")] diff --git a/crates/nu-protocol/src/example.rs b/crates/nu-protocol/src/example.rs index 1abaca1774..9d2c0b5e89 100644 --- a/crates/nu-protocol/src/example.rs +++ b/crates/nu-protocol/src/example.rs @@ -1,5 +1,6 @@ use crate::Value; +#[derive(Debug)] pub struct Example { pub example: &'static str, pub description: &'static str, diff --git a/crates/nu-protocol/src/signature.rs b/crates/nu-protocol/src/signature.rs index a3406d472d..6ea31f6b4c 100644 --- a/crates/nu-protocol/src/signature.rs +++ b/crates/nu-protocol/src/signature.rs @@ -279,6 +279,14 @@ impl Signature { one_liner.push_str(&self.name); one_liner.push(' '); + // Note: the call signature needs flags first because on the nu commandline, + // flags will precede the script file name. Flags for internal commands can come + // either before or after (or around) positional parameters, so there isn't a strong + // preference, so we default to the more constrained example. + if self.named.len() > 1 { + one_liner.push_str("{flags} "); + } + for positional in &self.required_positional { one_liner.push_str(&get_positional_short_name(positional, true)); } @@ -286,18 +294,14 @@ impl Signature { one_liner.push_str(&get_positional_short_name(positional, false)); } - if self.rest_positional.is_some() { - one_liner.push_str("...args "); + if let Some(rest) = &self.rest_positional { + one_liner.push_str(&format!("...{}", get_positional_short_name(rest, false))); } // if !self.subcommands.is_empty() { // one_liner.push_str(" "); // } - if !self.named.is_empty() { - one_liner.push_str("{flags} "); - } - one_liner } diff --git a/src/eval_file.rs b/src/eval_file.rs index 54ff9c11df..10490debdf 100644 --- a/src/eval_file.rs +++ b/src/eval_file.rs @@ -13,8 +13,10 @@ use crate::utils::{gather_parent_env_vars, report_error}; /// Main function used when a file path is found as argument for nu pub(crate) fn evaluate( path: String, + args: &[String], init_cwd: PathBuf, engine_state: &mut EngineState, + input: PipelineData, ) -> Result<()> { // First, set up env vars as strings only gather_parent_env_vars(engine_state); @@ -82,84 +84,71 @@ pub(crate) fn evaluate( std::process::exit(1); } - match eval_block( - engine_state, - &mut stack, - &block, - PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored - ) { - Ok(pipeline_data) => { - for item in pipeline_data { - if let Value::Error { error } = item { - let working_set = StateWorkingSet::new(engine_state); + // Next, let's check if there are any flags we want to pass to the main function + if args.is_empty() && engine_state.find_decl(b"main").is_none() { + // We don't have a main, so evaluate the whole file + 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); + report_error(&working_set, &error); - std::process::exit(1); - } - println!("{}", item.into_string("\n", &config)); - } - - // Next, let's check if there are any flags we want to pass to the main function - let args: Vec = std::env::args().skip(2).collect(); - - if args.is_empty() && engine_state.find_decl(b"main").is_none() { - return Ok(()); - } - - let args = format!("main {}", args.join(" ")).as_bytes().to_vec(); - - let (block, delta) = { - let mut working_set = StateWorkingSet::new(engine_state); - let (output, err) = parse(&mut working_set, Some(""), &args, false); - if let Some(err) = err { - report_error(&working_set, &err); - - std::process::exit(1); - } - (output, working_set.render()) - }; - - let cwd = nu_engine::env::current_dir_str(engine_state, &stack)?; - - if let Err(err) = engine_state.merge_delta(delta, Some(&mut stack), &cwd) { - let working_set = StateWorkingSet::new(engine_state); - report_error(&working_set, &err); - } - - match eval_block( - engine_state, - &mut stack, - &block, - PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored - ) { - 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)); + std::process::exit(1); } + println!("{}", item.into_string("\n", &config)); } - Err(err) => { - let working_set = StateWorkingSet::new(engine_state); + } + Err(err) => { + let working_set = StateWorkingSet::new(engine_state); - report_error(&working_set, &err); + report_error(&working_set, &err); - std::process::exit(1); - } + std::process::exit(1); } } - Err(err) => { + } else { + let args = format!("main {}", args.join(" ")).as_bytes().to_vec(); + + let (block, delta) = { + let mut working_set = StateWorkingSet::new(engine_state); + let (output, err) = parse(&mut working_set, Some(""), &args, false); + if let Some(err) = err { + report_error(&working_set, &err); + + std::process::exit(1); + } + (output, working_set.render()) + }; + + let cwd = nu_engine::env::current_dir_str(engine_state, &stack)?; + + if let Err(err) = engine_state.merge_delta(delta, Some(&mut stack), &cwd) { let working_set = StateWorkingSet::new(engine_state); - report_error(&working_set, &err); + } - 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); + } } } diff --git a/src/main.rs b/src/main.rs index 77be11b1ff..28a5c6bbbd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,11 +10,24 @@ mod utils; mod tests; use miette::Result; -use nu_command::create_default_context; -use std::sync::{ - atomic::{AtomicBool, Ordering}, - Arc, +use nu_command::{create_default_context, BufferedReader}; +use nu_engine::get_full_help; +use nu_parser::parse; +use nu_protocol::{ + ast::{Call, Expr, Expression, Pipeline, Statement}, + engine::{Command, EngineState, Stack, StateWorkingSet}, + ByteStream, Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, + Spanned, SyntaxShape, Value, CONFIG_VARIABLE_ID, }; +use std::{ + io::BufReader, + path::Path, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, +}; +use utils::report_error; fn main() -> Result<()> { // miette::set_panic_hook(); @@ -51,9 +64,199 @@ fn main() -> Result<()> { engine_state.ctrlc = Some(engine_state_ctrlc); // End ctrl-c protection section - if let Some(path) = std::env::args().nth(1) { - eval_file::evaluate(path, init_cwd, &mut engine_state) - } else { - repl::evaluate(ctrlc, &mut engine_state) + let mut args_to_nushell = vec![]; + let mut script_name = String::new(); + let mut args_to_script = 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 collect_arg_nushell = false; + for arg in std::env::args().skip(1) { + if !script_name.is_empty() { + args_to_script.push(arg); + } else if collect_arg_nushell { + args_to_nushell.push(arg); + collect_arg_nushell = false; + } else if arg.starts_with('-') { + // Cool, it's a flag + if arg == "-c" + || arg == "--commands" + || arg == "--develop" + || arg == "--debug" + || arg == "--loglevel" + || arg == "--config-file" + { + collect_arg_nushell = true; + } + args_to_nushell.push(arg); + } else { + // Our script file + script_name = arg; + } + } + + args_to_nushell.insert(0, "nu".into()); + + let nushell_commandline_args = args_to_nushell.join(" "); + + let nushell_config = + parse_commandline_args(&nushell_commandline_args, &init_cwd, &mut engine_state); + + 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); + + PipelineData::ByteStream( + ByteStream { + stream: Box::new(BufferedReader::new(buf_reader)), + ctrlc: Some(ctrlc), + }, + redirect_stdin.span, + None, + ) + } else { + PipelineData::new(Span::new(0, 0)) + }; + + eval_file::evaluate( + script_name, + &args_to_script, + init_cwd, + &mut engine_state, + input, + ) + } else { + repl::evaluate(ctrlc, &mut engine_state) + } + } + Err(_) => std::process::exit(1), + } +} + +fn parse_commandline_args( + commandline_args: &str, + init_cwd: &Path, + 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()) + }; + + let _ = engine_state.merge_delta(delta, None, init_cwd); + + let mut stack = Stack::new(); + stack.add_var( + CONFIG_VARIABLE_ID, + Value::Record { + cols: vec![], + vals: vec![], + span: Span::new(0, 0), + }, + ); + + // We should have a successful parse now + if let Some(Statement::Pipeline(Pipeline { expressions })) = block.stmts.get(0) { + if let Some(Expression { + expr: Expr::Call(call), + .. + }) = expressions.get(0) + { + let redirect_stdin = call.get_named_arg("stdin"); + + let help = call.has_flag("help"); + + if help { + let full_help = + get_full_help(&Nu.signature(), &Nu.examples(), engine_state, &mut stack); + print!("{}", full_help); + std::process::exit(1); + } + + return Ok(NushellConfig { redirect_stdin }); + } + } + + // Just give the help and exit if the above fails + let full_help = get_full_help(&Nu.signature(), &Nu.examples(), engine_state, &mut stack); + print!("{}", full_help); + std::process::exit(1); +} + +struct NushellConfig { + redirect_stdin: Option>, +} + +#[derive(Clone)] +struct Nu; + +impl Command for Nu { + fn name(&self) -> &str { + "nu" + } + + fn signature(&self) -> Signature { + Signature::build("nu") + .desc("The nushell language and shell.") + .switch("stdin", "redirect the stdin", 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) + } + + 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), + 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, + }, + ] } } diff --git a/src/utils.rs b/src/utils.rs index 79e5c8441c..f5b72ca32c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -317,7 +317,7 @@ pub(crate) fn eval_source( engine_state, stack, &block, - PipelineData::new(Span::new(0, 0)), // Don't try this at home, 0 span is ignored + PipelineData::new(Span::new(0, 0)), ) { Ok(pipeline_data) => { if let Err(err) = print_pipeline_data(pipeline_data, engine_state, stack) {