mod commands; mod config_files; mod eval_file; mod logger; mod prompt_update; mod reedline_config; mod repl; mod utils; #[cfg(test)] mod tests; use miette::Result; 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}, Category, Example, IntoPipelineData, PipelineData, RawStream, 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(); let miette_hook = std::panic::take_hook(); std::panic::set_hook(Box::new(move |x| { crossterm::terminal::disable_raw_mode().expect("unable to disable raw mode"); miette_hook(x); })); // Get initial current working directory. let init_cwd = utils::get_init_cwd(); let mut engine_state = create_default_context(&init_cwd); // Custom additions let delta = { let mut working_set = nu_protocol::engine::StateWorkingSet::new(&engine_state); working_set.add_decl(Box::new(nu_cli::NuHighlight)); working_set.render() }; let _ = engine_state.merge_delta(delta, None, &init_cwd); // TODO: make this conditional in the future // Ctrl-c protection section let ctrlc = Arc::new(AtomicBool::new(false)); let handler_ctrlc = ctrlc.clone(); let engine_state_ctrlc = ctrlc.clone(); ctrlc::set_handler(move || { handler_ctrlc.store(true, Ordering::SeqCst); }) .expect("Error setting Ctrl-C handler"); engine_state.ctrlc = Some(engine_state_ctrlc); // End ctrl-c protection section 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(if arg.contains(' ') { format!("'{}'", arg) } else { arg }); } else if collect_arg_nushell { 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 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) => { let input = if let Some(redirect_stdin) = &nushell_config.redirect_stdin { let stdin = std::io::stdin(); let buf_reader = BufReader::new(stdin); PipelineData::RawStream( RawStream::new(Box::new(BufferedReader::new(buf_reader)), 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, init_cwd, &mut engine_state, input, ) } else { repl::evaluate(&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 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"); 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, login_shell, interactive_shell, commands, }); } } // 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>, #[allow(dead_code)] login_shell: Option>, interactive_shell: Option>, commands: 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) .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, "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, }, ] } }