From 28e1a7915da78354de51f2ab57d02897b69039c3 Mon Sep 17 00:00:00 2001 From: Leonhard Kipp Date: Sat, 3 Apr 2021 21:31:53 +0200 Subject: [PATCH] Impl one configurable function to run scripts (#3242) * Impl one func to run scripts * Add exit_on_err * Remove run_standalone * Make the compiler happy :) --- crates/nu-cli/src/cli.rs | 86 +++------ crates/nu-cli/src/lib.rs | 2 +- crates/nu-cli/src/shell/helper.rs | 11 +- .../src/commands/classified/external.rs | 6 +- .../src/commands/default_context.rs | 9 +- crates/nu-command/src/commands/source.rs | 33 +--- crates/nu-command/src/examples.rs | 7 +- .../nu-engine/src/basic_evaluation_context.rs | 8 +- crates/nu-engine/src/basic_shell_manager.rs | 6 +- crates/nu-engine/src/evaluation_context.rs | 49 +++-- .../src/filesystem/filesystem_shell.rs | 6 + crates/nu-engine/src/script.rs | 172 ++++++++++-------- crates/nu-engine/src/shell/shell_manager.rs | 7 + crates/nu-protocol/src/lib.rs | 2 + crates/nu-protocol/src/script.rs | 83 +++++++++ src/main.rs | 34 ++-- 16 files changed, 305 insertions(+), 216 deletions(-) create mode 100644 crates/nu-protocol/src/script.rs diff --git a/crates/nu-cli/src/cli.rs b/crates/nu-cli/src/cli.rs index ede076899d..7e3920599e 100644 --- a/crates/nu-cli/src/cli.rs +++ b/crates/nu-cli/src/cli.rs @@ -1,6 +1,10 @@ use crate::line_editor::configure_ctrl_c; use nu_command::commands::default_context::create_default_context; -use nu_engine::{maybe_print_errors, run_block, script::run_script_standalone, EvaluationContext}; +use nu_engine::{ + filesystem::filesystem_shell::FilesystemShellMode, maybe_print_errors, run_block, script, + EvaluationContext, +}; +use std::error::Error; #[allow(unused_imports)] pub(crate) use nu_engine::script::{process_script, LineResult}; @@ -15,7 +19,7 @@ use crate::line_editor::{ use nu_data::config; use nu_source::{Tag, Text}; use nu_stream::InputStream; -use std::ffi::{OsStr, OsString}; +use std::ffi::OsString; #[allow(unused_imports)] use std::sync::atomic::Ordering; @@ -24,10 +28,11 @@ use rustyline::{self, error::ReadlineError}; use nu_errors::ShellError; use nu_parser::ParserScope; -use nu_protocol::{hir::ExternalRedirection, ConfigPath, UntaggedValue, Value}; +use nu_protocol::{ + hir::ExternalRedirection, ConfigPath, NuScript, RunScriptOptions, UntaggedValue, Value, +}; use log::trace; -use std::error::Error; use std::iter::Iterator; use std::path::PathBuf; @@ -55,45 +60,6 @@ impl Options { } } -pub struct NuScript { - pub filepath: Option, - pub contents: String, -} - -impl NuScript { - pub fn code<'a>(content: impl Iterator) -> Result { - let text = content - .map(|x| x.to_string()) - .collect::>() - .join("\n"); - - Ok(Self { - filepath: None, - contents: text, - }) - } - - pub fn get_code(&self) -> &str { - &self.contents - } - - pub fn source_file(path: &OsStr) -> Result { - use std::fs::File; - use std::io::Read; - - let path = path.to_os_string(); - let mut file = File::open(&path)?; - let mut buffer = String::new(); - - file.read_to_string(&mut buffer)?; - - Ok(Self { - filepath: Some(path), - contents: buffer, - }) - } -} - pub fn search_paths() -> Vec { use std::env; @@ -123,8 +89,11 @@ pub fn search_paths() -> Vec { search_paths } -pub async fn run_script_file(options: Options) -> Result<(), Box> { - let context = create_default_context(false)?; +pub async fn run_script_file( + options: Options, + run_options: RunScriptOptions, +) -> Result<(), Box> { + let context = create_default_context(FilesystemShellMode::Script, false)?; if let Some(cfg) = options.config { load_cfg_as_global_cfg(&context, PathBuf::from(cfg)).await; @@ -135,12 +104,9 @@ pub async fn run_script_file(options: Options) -> Result<(), Box> { let _ = register_plugins(&context); let _ = configure_ctrl_c(&context); - let script = options - .scripts - .get(0) - .ok_or_else(|| ShellError::unexpected("Nu source code not available"))?; - - run_script_standalone(script.get_code().to_string(), options.stdin, &context, true).await?; + for script in options.scripts { + script::run_script(script, &run_options, &context).await; + } Ok(()) } @@ -218,6 +184,10 @@ pub async fn cli(context: EvaluationContext, options: Options) -> Result<(), Box let mut ctrlcbreak = false; + let mut run_options = RunScriptOptions::default() + .cli_mode(true) + .redirect_stdin(false); + loop { if context.ctrl_c.load(Ordering::SeqCst) { context.ctrl_c.store(false, Ordering::SeqCst); @@ -303,14 +273,8 @@ pub async fn cli(context: EvaluationContext, options: Options) -> Result<(), Box let line = match convert_rustyline_result_to_string(readline) { LineResult::Success(_) => { - process_script( - &session_text[line_start..], - &context, - false, - line_start, - true, - ) - .await + run_options = run_options.span_offset(line_start); + process_script(&session_text[line_start..], &run_options, &context).await } x => x, }; @@ -501,14 +465,14 @@ fn current_branch() -> String { #[cfg(test)] mod tests { - use nu_engine::basic_evaluation_context; + use nu_engine::{basic_evaluation_context, filesystem::filesystem_shell::FilesystemShellMode}; #[quickcheck] fn quickcheck_parse(data: String) -> bool { let (tokens, err) = nu_parser::lex(&data, 0); let (lite_block, err2) = nu_parser::parse_block(tokens); if err.is_none() && err2.is_none() { - let context = basic_evaluation_context().unwrap(); + let context = basic_evaluation_context(FilesystemShellMode::Cli).unwrap(); let _ = nu_parser::classify_block(&lite_block, &context.scope); } true diff --git a/crates/nu-cli/src/lib.rs b/crates/nu-cli/src/lib.rs index a90039144d..9a3862a1b5 100644 --- a/crates/nu-cli/src/lib.rs +++ b/crates/nu-cli/src/lib.rs @@ -22,8 +22,8 @@ pub mod types; #[cfg(feature = "rustyline-support")] pub use crate::cli::cli; +pub use crate::cli::Options; pub use crate::cli::{parse_and_eval, register_plugins, run_script_file}; -pub use crate::cli::{NuScript, Options}; pub use nu_command::commands::default_context::create_default_context; pub use nu_data::config; diff --git a/crates/nu-cli/src/shell/helper.rs b/crates/nu-cli/src/shell/helper.rs index 2c56915530..639ad627a7 100644 --- a/crates/nu-cli/src/shell/helper.rs +++ b/crates/nu-cli/src/shell/helper.rs @@ -150,6 +150,7 @@ impl rustyline::Helper for Helper {} mod tests { use super::*; use nu_engine::basic_evaluation_context; + use nu_engine::filesystem::filesystem_shell::FilesystemShellMode; use rustyline::completion::Completer; use rustyline::line_buffer::LineBuffer; @@ -163,7 +164,10 @@ mod tests { buffer.insert_str(0, text); buffer.set_pos(text.len() - 1); - let helper = Helper::new(basic_evaluation_context().unwrap(), None); + let helper = Helper::new( + basic_evaluation_context(FilesystemShellMode::Cli).unwrap(), + None, + ); helper.update(&mut buffer, "cd ".len(), &replacement); @@ -183,7 +187,10 @@ mod tests { buffer.insert_str(0, text); buffer.set_pos(text.len() - 30); - let helper = Helper::new(basic_evaluation_context().unwrap(), None); + let helper = Helper::new( + basic_evaluation_context(FilesystemShellMode::Cli).unwrap(), + None, + ); helper.update(&mut buffer, "cd ".len(), &replacement); diff --git a/crates/nu-command/src/commands/classified/external.rs b/crates/nu-command/src/commands/classified/external.rs index 1a648c9ea7..f503503f65 100644 --- a/crates/nu-command/src/commands/classified/external.rs +++ b/crates/nu-command/src/commands/classified/external.rs @@ -505,6 +505,8 @@ mod tests { }; #[cfg(feature = "which")] use super::{run_external_command, InputStream}; + #[cfg(feature = "which")] + use nu_engine::filesystem::filesystem_shell::FilesystemShellMode; #[cfg(feature = "which")] use futures::executor::block_on; @@ -533,8 +535,8 @@ mod tests { let cmd = ExternalBuilder::for_name("i_dont_exist.exe").build(); let input = InputStream::empty(); - let mut ctx = - basic_evaluation_context().expect("There was a problem creating a basic context."); + let mut ctx = basic_evaluation_context(FilesystemShellMode::Cli) + .expect("There was a problem creating a basic context."); assert!( run_external_command(cmd, &mut ctx, input, ExternalRedirection::Stdout) diff --git a/crates/nu-command/src/commands/default_context.rs b/crates/nu-command/src/commands/default_context.rs index e564bf3d32..a514a55438 100644 --- a/crates/nu-command/src/commands/default_context.rs +++ b/crates/nu-command/src/commands/default_context.rs @@ -1,10 +1,13 @@ use crate::prelude::*; -use nu_engine::basic_evaluation_context; use nu_engine::whole_stream_command; +use nu_engine::{basic_evaluation_context, filesystem::filesystem_shell::FilesystemShellMode}; use std::error::Error; -pub fn create_default_context(interactive: bool) -> Result> { - let context = basic_evaluation_context()?; +pub fn create_default_context( + mode: FilesystemShellMode, + interactive: bool, +) -> Result> { + let context = basic_evaluation_context(mode)?; { use crate::commands::*; diff --git a/crates/nu-command/src/commands/source.rs b/crates/nu-command/src/commands/source.rs index 09e7c25153..06906f88ca 100644 --- a/crates/nu-command/src/commands/source.rs +++ b/crates/nu-command/src/commands/source.rs @@ -1,9 +1,11 @@ +use std::path::PathBuf; + use crate::prelude::*; use nu_engine::{script, WholeStreamCommand}; use nu_errors::ShellError; use nu_parser::expand_path; -use nu_protocol::{Signature, SyntaxShape}; +use nu_protocol::{NuScript, RunScriptOptions, Signature, SyntaxShape}; use nu_source::Tagged; pub struct Source; @@ -44,27 +46,12 @@ pub async fn source(args: CommandArgs) -> Result { let ctx = EvaluationContext::from_args(&args); let (SourceArgs { filename }, _) = args.process().await?; - // Note: this is a special case for setting the context from a command - // In this case, if we don't set it now, we'll lose the scope that this - // variable should be set into. - let contents = std::fs::read_to_string(expand_path(&filename.item).into_owned()); - match contents { - Ok(contents) => { - let result = script::run_script_standalone(contents, true, &ctx, false).await; + let script = NuScript::File(PathBuf::from(expand_path(&filename.item).to_string())); + let options = RunScriptOptions::default() + .use_existing_scope(true) + .redirect_stdin(true) + .exit_on_error(false); + script::run_script(script, &options, &ctx).await; - if let Err(err) = result { - ctx.error(err.into()); - } - Ok(OutputStream::empty()) - } - Err(_) => { - ctx.error(ShellError::labeled_error( - "Can't load file to source", - "can't load file", - filename.span(), - )); - - Ok(OutputStream::empty()) - } - } + Ok(OutputStream::empty()) } diff --git a/crates/nu-command/src/examples.rs b/crates/nu-command/src/examples.rs index c44da09467..655be4c6e9 100644 --- a/crates/nu-command/src/examples.rs +++ b/crates/nu-command/src/examples.rs @@ -6,6 +6,7 @@ mod stub_generate; use double_echo::Command as DoubleEcho; use double_ls::Command as DoubleLs; +use nu_engine::filesystem::filesystem_shell::FilesystemShellMode; use stub_generate::{mock_path, Command as StubOpen}; use nu_engine::basic_evaluation_context; @@ -26,7 +27,7 @@ use futures::executor::block_on; pub fn test_examples(cmd: Command) -> Result<(), ShellError> { let examples = cmd.examples(); - let base_context = basic_evaluation_context()?; + let base_context = basic_evaluation_context(FilesystemShellMode::Cli)?; base_context.add_commands(vec![ // Command Doubles @@ -92,7 +93,7 @@ pub fn test_examples(cmd: Command) -> Result<(), ShellError> { pub fn test(cmd: impl WholeStreamCommand + 'static) -> Result<(), ShellError> { let examples = cmd.examples(); - let base_context = basic_evaluation_context()?; + let base_context = basic_evaluation_context(FilesystemShellMode::Cli)?; base_context.add_commands(vec![ whole_stream_command(Echo {}), @@ -149,7 +150,7 @@ pub fn test(cmd: impl WholeStreamCommand + 'static) -> Result<(), ShellError> { pub fn test_anchors(cmd: Command) -> Result<(), ShellError> { let examples = cmd.examples(); - let base_context = basic_evaluation_context()?; + let base_context = basic_evaluation_context(FilesystemShellMode::Cli)?; base_context.add_commands(vec![ // Minimal restricted commands to aid in testing diff --git a/crates/nu-engine/src/basic_evaluation_context.rs b/crates/nu-engine/src/basic_evaluation_context.rs index 133bda09c6..61223a21ae 100644 --- a/crates/nu-engine/src/basic_evaluation_context.rs +++ b/crates/nu-engine/src/basic_evaluation_context.rs @@ -1,14 +1,16 @@ -use crate::EvaluationContext; use crate::Scope; use crate::{basic_shell_manager, config_holder::ConfigHolder}; use crate::{env::basic_host::BasicHost, Host}; +use crate::{filesystem::filesystem_shell::FilesystemShellMode, EvaluationContext}; use indexmap::IndexMap; use parking_lot::Mutex; use std::error::Error; use std::sync::atomic::AtomicBool; use std::sync::Arc; -pub fn basic_evaluation_context() -> Result> { +pub fn basic_evaluation_context( + mode: FilesystemShellMode, +) -> Result> { let scope = Scope::new(); let mut host = BasicHost {}; let env_vars = host.vars().iter().cloned().collect::>(); @@ -20,7 +22,7 @@ pub fn basic_evaluation_context() -> Result> { current_errors: Arc::new(Mutex::new(vec![])), ctrl_c: Arc::new(AtomicBool::new(false)), configs: Arc::new(Mutex::new(ConfigHolder::new())), - shell_manager: basic_shell_manager::basic_shell_manager()?, + shell_manager: basic_shell_manager::basic_shell_manager(mode)?, windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())), }) } diff --git a/crates/nu-engine/src/basic_shell_manager.rs b/crates/nu-engine/src/basic_shell_manager.rs index c0257b2df6..2f39d773a7 100644 --- a/crates/nu-engine/src/basic_shell_manager.rs +++ b/crates/nu-engine/src/basic_shell_manager.rs @@ -6,11 +6,9 @@ use std::error::Error; use std::sync::atomic::AtomicUsize; use std::sync::Arc; -pub fn basic_shell_manager() -> Result> { +pub fn basic_shell_manager(mode: FilesystemShellMode) -> Result> { Ok(ShellManager { current_shell: Arc::new(AtomicUsize::new(0)), - shells: Arc::new(Mutex::new(vec![Box::new(FilesystemShell::basic( - FilesystemShellMode::Cli, - )?)])), + shells: Arc::new(Mutex::new(vec![Box::new(FilesystemShell::basic(mode)?)])), }) } diff --git a/crates/nu-engine/src/evaluation_context.rs b/crates/nu-engine/src/evaluation_context.rs index aa5ca9926c..6edc2dbec0 100644 --- a/crates/nu-engine/src/evaluation_context.rs +++ b/crates/nu-engine/src/evaluation_context.rs @@ -7,12 +7,12 @@ use crate::{command_args::CommandArgs, script}; use log::trace; use nu_data::config::{self, Conf, NuConfig}; use nu_errors::ShellError; -use nu_protocol::{hir, ConfigPath}; +use nu_protocol::{hir, ConfigPath, NuScript, RunScriptOptions}; use nu_source::{Span, Tag}; use nu_stream::{InputStream, OutputStream}; use parking_lot::Mutex; -use std::sync::atomic::AtomicBool; use std::{path::Path, sync::Arc}; +use std::{path::PathBuf, sync::atomic::AtomicBool}; #[derive(Clone)] pub struct EvaluationContext { @@ -177,9 +177,9 @@ impl EvaluationContext { } } - if !startup_scripts.is_empty() { - self.run_scripts(startup_scripts, cfg_path.get_path().parent()) - .await; + let options = exit_entry_script_options(&cfg_path); + for script in startup_scripts { + script::run_script(NuScript::Content(script), &options, self).await; } Ok(()) @@ -270,32 +270,27 @@ impl EvaluationContext { //Run exitscripts with scope frame and cfg still applied if let Some(scripts) = self.scope.get_exitscripts_of_frame_with_tag(&tag) { - self.run_scripts(scripts, cfg_path.get_path().parent()) - .await; + let options = exit_entry_script_options(&cfg_path); + for script in scripts { + script::run_script(NuScript::Content(script), &options, self).await; + } } //Unload config self.configs.lock().remove_cfg(&cfg_path); self.scope.exit_scope_with_tag(&tag); } - - /// Runs scripts with cwd of dir. If dir is None, this method does nothing. - /// Each error is added to `self.current_errors` - pub async fn run_scripts(&self, scripts: Vec, dir: Option<&Path>) { - if let Some(dir) = dir { - for script in scripts { - match script::run_script_in_dir(script.clone(), dir, &self).await { - Ok(_) => {} - Err(e) => { - let err = ShellError::untagged_runtime_error(format!( - "Err while executing exitscript. Err was\n{:?}", - e - )); - let text = script.into(); - self.host.lock().print_err(err, &text); - } - } - } - } - } +} + +fn exit_entry_script_options(cfg_path: &ConfigPath) -> RunScriptOptions { + let root = PathBuf::from("/"); + RunScriptOptions::default() + .with_cwd( + cfg_path + .get_path() + .parent() + .map(Path::to_path_buf) + .unwrap_or(root), + ) + .exit_on_error(false) } diff --git a/crates/nu-engine/src/filesystem/filesystem_shell.rs b/crates/nu-engine/src/filesystem/filesystem_shell.rs index 995c4bfdff..fe0ef4b6aa 100644 --- a/crates/nu-engine/src/filesystem/filesystem_shell.rs +++ b/crates/nu-engine/src/filesystem/filesystem_shell.rs @@ -10,6 +10,7 @@ use futures::stream::BoxStream; use futures::StreamExt; use futures_codec::FramedRead; use futures_util::TryStreamExt; +use log::trace; use nu_data::config::LocalConfigDiff; use nu_protocol::{CommandAction, ConfigPath, TaggedDictBuilder, Value}; use nu_source::{Span, Tag}; @@ -297,6 +298,11 @@ impl Shell for FilesystemShell { if self.mode == FilesystemShellMode::Cli { match dunce::canonicalize(self.path()) { Err(e) => { + trace!( + "Err canonicalize current path: {:?}, err: {:?}", + self.path(), + e + ); let err = ShellError::untagged_runtime_error(format!( "Could not get absolute path from current fs shell. The error was: {:?}", e diff --git a/crates/nu-engine/src/script.rs b/crates/nu-engine/src/script.rs index 29ad593302..3cc4d1b816 100644 --- a/crates/nu-engine/src/script.rs +++ b/crates/nu-engine/src/script.rs @@ -3,19 +3,22 @@ use crate::{MaybeTextCodec, StringOrBinary}; use futures::StreamExt; use futures_codec::FramedRead; use nu_errors::ShellError; -use nu_protocol::hir::{ - Call, ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments, - SpannedExpression, +use nu_parser::ParserScope; +use nu_protocol::{ + hir::{ + Call, ClassifiedCommand, Expression, InternalCommand, Literal, NamedArguments, + SpannedExpression, + }, + NuScript, ReturnSuccess, RunScriptOptions, }; -use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value}; +use nu_protocol::{Primitive, UntaggedValue, Value}; use nu_stream::{InputStream, ToInputStream}; use crate::EvaluationContext; use log::{debug, trace}; use nu_source::{Span, Tag, Text}; -use std::iter::Iterator; use std::path::Path; -use std::{error::Error, sync::atomic::Ordering}; +use std::{iter::Iterator, sync::atomic::Ordering}; #[derive(Debug)] pub enum LineResult { @@ -27,6 +30,7 @@ pub enum LineResult { ClearHistory, } +//TODO is this still needed fn chomp_newline(s: &str) -> &str { if let Some(s) = s.strip_suffix('\n') { s @@ -35,36 +39,106 @@ fn chomp_newline(s: &str) -> &str { } } -pub async fn run_script_in_dir( - script: String, - dir: &Path, - ctx: &EvaluationContext, -) -> Result<(), Box> { - //Save path before to switch back to it after executing script - let path_before = ctx.shell_manager.path(); +/// Runs script `script` configurable by `options` +/// All errors are printed out. +pub async fn run_script(script: NuScript, options: &RunScriptOptions, ctx: &EvaluationContext) { + let code = match script.get_code() { + Ok(code) => code, + Err(e) => { + ctx.host.lock().print_err(e, &Text::from("".to_string())); + return; + } + }; - ctx.shell_manager - .set_path(dir.to_string_lossy().to_string()); - run_script_standalone(script, false, ctx, false).await?; - ctx.shell_manager.set_path(path_before); + if let Err(e) = setup_shell(options, ctx) { + ctx.host.lock().print_err(e, &Text::from("".to_string())); + return; + } + + if !options.use_existing_scope { + ctx.scope.enter_scope() + } + + let line_result = process_script(&code, options, ctx).await; + evaluate_line_result(line_result, options, ctx).await; + + if !options.use_existing_scope { + ctx.scope.exit_scope() + } + + //Leave script shell + ctx.shell_manager.remove_at_current(); +} + +fn setup_shell(options: &RunScriptOptions, ctx: &EvaluationContext) -> Result<(), ShellError> { + //Switch to correct shell + if options.cli_mode { + ctx.shell_manager.enter_cli_mode()?; + } else { + ctx.shell_manager.enter_script_mode()?; + } + + //Switch to cwd if given + if let Some(path) = &options.with_cwd { + ctx.shell_manager + .set_path(path.to_string_lossy().to_string()); + } Ok(()) } +async fn evaluate_line_result( + line_result: LineResult, + options: &RunScriptOptions, + context: &EvaluationContext, +) { + match line_result { + LineResult::Success(line) => { + let error_code = { + let errors = context.current_errors.clone(); + let errors = errors.lock(); + + if errors.len() > 0 { + 1 + } else { + 0 + } + }; + + maybe_print_errors(&context, Text::from(line)); + if error_code != 0 && options.exit_on_error { + std::process::exit(error_code); + } + } + + LineResult::Error(line, err) => { + context + .host + .lock() + .print_err(err, &Text::from(line.clone())); + + maybe_print_errors(&context, Text::from(line)); + if options.exit_on_error { + std::process::exit(1); + } + } + + _ => {} + } +} + /// Process the line by parsing the text to turn it into commands, classify those commands so that we understand what is being called in the pipeline, and then run this pipeline pub async fn process_script( script_text: &str, + options: &RunScriptOptions, ctx: &EvaluationContext, - redirect_stdin: bool, - span_offset: usize, - cli_mode: bool, ) -> LineResult { if script_text.trim() == "" { LineResult::Success(script_text.to_string()) } else { let line = chomp_newline(script_text); - let (block, err) = nu_parser::parse(&line, span_offset, &ctx.scope); + let (block, err) = nu_parser::parse(&line, options.span_offset, &ctx.scope); debug!("{:#?}", block); //println!("{:#?}", pipeline); @@ -79,7 +153,7 @@ pub async fn process_script( // ...and it doesn't have any arguments // ...and we're in the CLI // ...then change to this directory - if cli_mode + if options.cli_mode && block.block.len() == 1 && block.block[0].pipelines.len() == 1 && block.block[0].pipelines[0].list.len() == 1 @@ -160,7 +234,7 @@ pub async fn process_script( } } - let input_stream = if redirect_stdin { + let input_stream = if options.redirect_stdin { let file = futures::io::AllowStdIo::new(std::io::stdin()); let stream = FramedRead::new(file, MaybeTextCodec::default()).map(|line| { if let Ok(line) = line { @@ -235,55 +309,3 @@ pub async fn process_script( } } } - -pub async fn run_script_standalone( - script_text: String, - redirect_stdin: bool, - context: &EvaluationContext, - exit_on_error: bool, -) -> Result<(), Box> { - context - .shell_manager - .enter_script_mode() - .map_err(Box::new)?; - let line = process_script(&script_text, context, redirect_stdin, 0, false).await; - - match line { - LineResult::Success(line) => { - let error_code = { - let errors = context.current_errors.clone(); - let errors = errors.lock(); - - if errors.len() > 0 { - 1 - } else { - 0 - } - }; - - maybe_print_errors(&context, Text::from(line)); - if error_code != 0 && exit_on_error { - std::process::exit(error_code); - } - } - - LineResult::Error(line, err) => { - context - .host - .lock() - .print_err(err, &Text::from(line.clone())); - - maybe_print_errors(&context, Text::from(line)); - if exit_on_error { - std::process::exit(1); - } - } - - _ => {} - } - - //exit script mode shell - context.shell_manager.remove_at_current(); - - Ok(()) -} diff --git a/crates/nu-engine/src/shell/shell_manager.rs b/crates/nu-engine/src/shell/shell_manager.rs index af3e16418b..4d01cc7f32 100644 --- a/crates/nu-engine/src/shell/shell_manager.rs +++ b/crates/nu-engine/src/shell/shell_manager.rs @@ -27,6 +27,13 @@ impl ShellManager { Ok(()) } + pub fn enter_cli_mode(&self) -> Result<(), std::io::Error> { + //New fs_shell starting from current path + let fs_shell = FilesystemShell::with_location(self.path(), FilesystemShellMode::Cli)?; + self.insert_at_current(Box::new(fs_shell)); + Ok(()) + } + pub fn insert_at_current(&self, shell: Box) { self.shells.lock().push(shell); self.current_shell diff --git a/crates/nu-protocol/src/lib.rs b/crates/nu-protocol/src/lib.rs index 33d4ab4fdb..7f33e2d0ff 100644 --- a/crates/nu-protocol/src/lib.rs +++ b/crates/nu-protocol/src/lib.rs @@ -6,6 +6,7 @@ pub mod config_path; pub mod hir; mod maybe_owned; mod return_value; +mod script; mod signature; mod syntax_shape; mod type_name; @@ -16,6 +17,7 @@ pub use crate::call_info::{CallInfo, EvaluatedArgs}; pub use crate::config_path::ConfigPath; pub use crate::maybe_owned::MaybeOwned; pub use crate::return_value::{CommandAction, ReturnSuccess, ReturnValue}; +pub use crate::script::{NuScript, RunScriptOptions}; pub use crate::signature::{NamedType, PositionalType, Signature}; pub use crate::syntax_shape::SyntaxShape; pub use crate::type_name::{PrettyType, ShellTypeName, SpannedTypeName}; diff --git a/crates/nu-protocol/src/script.rs b/crates/nu-protocol/src/script.rs new file mode 100644 index 0000000000..1d0f334279 --- /dev/null +++ b/crates/nu-protocol/src/script.rs @@ -0,0 +1,83 @@ +use std::path::PathBuf; + +use nu_errors::ShellError; + +/// NuScript is either directly some nu code or +/// a file path to a nu-script file. +pub enum NuScript { + Content(String), + File(PathBuf), +} + +impl NuScript { + pub fn get_code(self) -> Result { + match self { + NuScript::Content(code) => Ok(code), + NuScript::File(path) => std::fs::read_to_string(path).map_err(|e| { + ShellError::untagged_runtime_error(format!("Reading of script failed with: {}", e)) + }), + } + } +} + +#[derive(Debug, Clone)] +pub struct RunScriptOptions { + pub with_cwd: Option, + pub with_stdin: bool, + pub redirect_stdin: bool, + pub exit_on_error: bool, + pub cli_mode: bool, + pub span_offset: usize, + pub use_existing_scope: bool, +} + +impl Default for RunScriptOptions { + fn default() -> Self { + Self { + with_cwd: None, + with_stdin: true, + redirect_stdin: false, + exit_on_error: true, + cli_mode: false, + span_offset: 0, + use_existing_scope: false, + } + } +} + +impl RunScriptOptions { + pub fn with_cwd(mut self, path: PathBuf) -> Self { + self.with_cwd = Some(path); + self + } + + pub fn with_stdin(mut self, stdin: bool) -> Self { + self.with_stdin = stdin; + self + } + + pub fn redirect_stdin(mut self, redirect: bool) -> Self { + self.redirect_stdin = redirect; + self + } + + pub fn exit_on_error(mut self, exit_on_error: bool) -> Self { + self.exit_on_error = exit_on_error; + self + } + + pub fn cli_mode(mut self, cli_mode: bool) -> Self { + self.cli_mode = cli_mode; + self + } + + pub fn span_offset(mut self, span_offset: usize) -> Self { + self.span_offset = span_offset; + self + } + + pub fn use_existing_scope(mut self, use_existing_scope: bool) -> Self { + self.use_existing_scope = use_existing_scope; + self + } +} diff --git a/src/main.rs b/src/main.rs index 3f0f8a51fb..62eb991d6c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,10 @@ -use clap::{App, Arg}; +use clap::{App, Arg, ArgMatches}; use log::LevelFilter; -use nu_cli::{create_default_context, NuScript, Options}; +use nu_cli::{create_default_context, Options}; use nu_command::utils::test_bins as binaries; -use std::error::Error; +use nu_engine::filesystem::filesystem_shell::FilesystemShellMode; +use nu_protocol::{NuScript, RunScriptOptions}; +use std::{error::Error, path::PathBuf}; fn main() -> Result<(), Box> { let mut options = Options::new(); @@ -148,25 +150,29 @@ fn main() -> Result<(), Box> { match matches.values_of("commands") { None => {} Some(values) => { - options.scripts = vec![NuScript::code(values)?]; - - futures::executor::block_on(nu_cli::run_script_file(options))?; + options.scripts = values + .map(|cmd| NuScript::Content(cmd.to_string())) + .collect(); + let mut run_options = script_options_from_matches(&matches); + // we always exit on err + run_options.exit_on_error = true; + futures::executor::block_on(nu_cli::run_script_file(options, run_options))?; return Ok(()); } } match matches.value_of("script") { Some(filepath) => { - let filepath = std::ffi::OsString::from(filepath); - - options.scripts = vec![NuScript::source_file(filepath.as_os_str())?]; - - futures::executor::block_on(nu_cli::run_script_file(options))?; + options.scripts = vec![NuScript::File(PathBuf::from(filepath))]; + let mut run_options = script_options_from_matches(&matches); + // we always exit on err + run_options.exit_on_error = true; + futures::executor::block_on(nu_cli::run_script_file(options, run_options))?; return Ok(()); } None => { - let context = create_default_context(true)?; + let context = create_default_context(FilesystemShellMode::Cli, true)?; if !matches.is_present("skip-plugins") { let _ = nu_cli::register_plugins(&context); @@ -186,3 +192,7 @@ fn main() -> Result<(), Box> { Ok(()) } + +fn script_options_from_matches(matches: &ArgMatches) -> RunScriptOptions { + RunScriptOptions::default().with_stdin(matches.is_present("stdin")) +}