History, more test coverage improvements, and refactorings. (#3217)

Improvements overall to Nu. Also among the changes here, we can also be more confident towards incorporating `3041`. End to end tests for checking envs properly exported to externals is not added here (since it's in the other PR)

A few things added in this PR (probably forgetting some too)

* no writes happen to history during test runs.
* environment syncing end to end coverage added.
* clean up / refactorings few areas.
* testing API for finer control (can write tests passing more than one pipeline)
* can pass environment variables in tests that nu will inherit when running.

* No longer needed.

* no longer under a module. No need to use super.
This commit is contained in:
Andrés N. Robalino 2021-03-27 00:08:03 -05:00 committed by GitHub
parent b243b3ee1d
commit 8fc8fc89aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 483 additions and 241 deletions

View File

@ -1,8 +1,6 @@
use crate::line_editor::configure_ctrl_c; use crate::line_editor::configure_ctrl_c;
use nu_command::commands::default_context::create_default_context; use nu_command::commands::default_context::create_default_context;
use nu_engine::{ use nu_engine::{evaluation_context, run_block, script::run_script_standalone, EvaluationContext};
print::maybe_print_errors, run_block, script::run_script_standalone, EvaluationContext,
};
#[allow(unused_imports)] #[allow(unused_imports)]
pub(crate) use nu_engine::script::{process_script, LineResult}; pub(crate) use nu_engine::script::{process_script, LineResult};
@ -37,6 +35,8 @@ use std::path::PathBuf;
pub struct Options { pub struct Options {
pub config: Option<OsString>, pub config: Option<OsString>,
pub history: Option<PathBuf>,
pub save_history: bool,
pub stdin: bool, pub stdin: bool,
pub scripts: Vec<NuScript>, pub scripts: Vec<NuScript>,
} }
@ -51,10 +51,22 @@ impl Options {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
config: None, config: None,
history: None,
save_history: true,
stdin: false, stdin: false,
scripts: vec![], scripts: vec![],
} }
} }
pub fn history(&self, block: impl FnOnce(&std::path::Path)) {
if !self.save_history {
return;
}
if let Some(file) = &self.history {
block(&file)
}
}
} }
pub struct NuScript { pub struct NuScript {
@ -125,9 +137,9 @@ pub fn search_paths() -> Vec<std::path::PathBuf> {
search_paths search_paths
} }
pub async fn run_script_file(options: Options) -> Result<(), Box<dyn Error>> { pub async fn run_script_file(mut options: Options) -> Result<(), Box<dyn Error>> {
let mut context = create_default_context(false)?; let mut context = create_default_context(false)?;
let mut syncer = create_environment_syncer(&context, &options); let mut syncer = create_environment_syncer(&context, &mut options);
let config = syncer.get_config(); let config = syncer.get_config();
context.configure(&config, |_, ctx| { context.configure(&config, |_, ctx| {
@ -136,7 +148,7 @@ pub async fn run_script_file(options: Options) -> Result<(), Box<dyn Error>> {
syncer.sync_path_vars(ctx); syncer.sync_path_vars(ctx);
if let Err(reason) = syncer.autoenv(ctx) { if let Err(reason) = syncer.autoenv(ctx) {
ctx.host.lock().print_err(reason, &Text::from("")); ctx.with_host(|host| host.print_err(reason, &Text::from("")));
} }
let _ = register_plugins(ctx); let _ = register_plugins(ctx);
@ -155,27 +167,51 @@ pub async fn run_script_file(options: Options) -> Result<(), Box<dyn Error>> {
Ok(()) Ok(())
} }
fn create_environment_syncer(context: &EvaluationContext, options: &Options) -> EnvironmentSyncer { fn create_environment_syncer(
if let Some(config_file) = &options.config { context: &EvaluationContext,
let location = Some(AnchorLocation::File( options: &mut Options,
config_file.to_string_lossy().to_string(), ) -> EnvironmentSyncer {
)); let configuration = match &options.config {
let tag = Tag::unknown().anchored(location); Some(config_file) => {
let location = Some(AnchorLocation::File(
config_file.to_string_lossy().to_string(),
));
context.scope.add_var( let tag = Tag::unknown().anchored(location);
"config-path",
UntaggedValue::filepath(PathBuf::from(&config_file)).into_value(tag),
);
EnvironmentSyncer::with_config(Box::new(NuConfig::with(Some(config_file.into())))) context.scope.add_var(
} else { "config-path",
EnvironmentSyncer::new() UntaggedValue::filepath(PathBuf::from(&config_file)).into_value(tag),
} );
NuConfig::with(Some(config_file.into()))
}
None => NuConfig::new(),
};
let history_path = configuration.history_path();
options.history = Some(history_path.clone());
let location = Some(AnchorLocation::File(
history_path.to_string_lossy().to_string(),
));
let tag = Tag::unknown().anchored(location);
context.scope.add_var(
"history-path",
UntaggedValue::filepath(history_path).into_value(tag),
);
EnvironmentSyncer::with_config(Box::new(configuration))
} }
#[cfg(feature = "rustyline-support")] #[cfg(feature = "rustyline-support")]
pub async fn cli(mut context: EvaluationContext, options: Options) -> Result<(), Box<dyn Error>> { pub async fn cli(
let mut syncer = create_environment_syncer(&context, &options); mut context: EvaluationContext,
mut options: Options,
) -> Result<(), Box<dyn Error>> {
let mut syncer = create_environment_syncer(&context, &mut options);
let configuration = syncer.get_config(); let configuration = syncer.get_config();
@ -187,7 +223,7 @@ pub async fn cli(mut context: EvaluationContext, options: Options) -> Result<(),
syncer.sync_path_vars(ctx); syncer.sync_path_vars(ctx);
if let Err(reason) = syncer.autoenv(ctx) { if let Err(reason) = syncer.autoenv(ctx) {
ctx.host.lock().print_err(reason, &Text::from("")); ctx.with_host(|host| host.print_err(reason, &Text::from("")));
} }
let _ = configure_ctrl_c(ctx); let _ = configure_ctrl_c(ctx);
@ -214,11 +250,9 @@ pub async fn cli(mut context: EvaluationContext, options: Options) -> Result<(),
// Give ourselves a scope to work in // Give ourselves a scope to work in
context.scope.enter_scope(); context.scope.enter_scope();
let env = context.get_env(); options.history(|file| {
context.scope.add_env_to_base(env); let _ = rl.load_history(&file);
});
let history_path = nu_engine::history_path(&configuration);
let _ = rl.load_history(&history_path);
let mut session_text = String::new(); let mut session_text = String::new();
let mut line_start: usize = 0; let mut line_start: usize = 0;
@ -254,6 +288,7 @@ pub async fn cli(mut context: EvaluationContext, options: Options) -> Result<(),
let prompt_line = prompt.as_string()?; let prompt_line = prompt.as_string()?;
context.scope.enter_scope(); context.scope.enter_scope();
let (mut prompt_block, err) = nu_parser::parse(&prompt_line, 0, &context.scope); let (mut prompt_block, err) = nu_parser::parse(&prompt_line, 0, &context.scope);
prompt_block.set_redirect(ExternalRedirection::Stdout); prompt_block.set_redirect(ExternalRedirection::Stdout);
@ -263,8 +298,6 @@ pub async fn cli(mut context: EvaluationContext, options: Options) -> Result<(),
format!("\x1b[32m{}{}\x1b[m> ", cwd, current_branch()) format!("\x1b[32m{}{}\x1b[m> ", cwd, current_branch())
} else { } else {
// let env = context.get_env();
let run_result = run_block(&prompt_block, &context, InputStream::empty()).await; let run_result = run_block(&prompt_block, &context, InputStream::empty()).await;
context.scope.exit_scope(); context.scope.exit_scope();
@ -272,7 +305,10 @@ pub async fn cli(mut context: EvaluationContext, options: Options) -> Result<(),
Ok(result) => match result.collect_string(Tag::unknown()).await { Ok(result) => match result.collect_string(Tag::unknown()).await {
Ok(string_result) => { Ok(string_result) => {
let errors = context.get_errors(); let errors = context.get_errors();
maybe_print_errors(&context, Text::from(prompt_line)); evaluation_context::maybe_print_errors(
&context,
Text::from(prompt_line),
);
context.clear_errors(); context.clear_errors();
if !errors.is_empty() { if !errors.is_empty() {
@ -357,7 +393,7 @@ pub async fn cli(mut context: EvaluationContext, options: Options) -> Result<(),
} }
if let Err(reason) = syncer.autoenv(ctx) { if let Err(reason) = syncer.autoenv(ctx) {
ctx.host.lock().print_err(reason, &Text::from("")); ctx.with_host(|host| host.print_err(reason, &Text::from("")));
} }
let _ = configure_rustyline_editor(&mut rl, config); let _ = configure_rustyline_editor(&mut rl, config);
@ -365,31 +401,33 @@ pub async fn cli(mut context: EvaluationContext, options: Options) -> Result<(),
match line { match line {
LineResult::Success(line) => { LineResult::Success(line) => {
rl.add_history_entry(&line); options.history(|file| {
let _ = rl.save_history(&history_path); rl.add_history_entry(&line);
maybe_print_errors(&context, Text::from(session_text.clone())); let _ = rl.save_history(&file);
});
evaluation_context::maybe_print_errors(&context, Text::from(session_text.clone()));
} }
LineResult::ClearHistory => { LineResult::ClearHistory => {
rl.clear_history(); options.history(|file| {
let _ = rl.save_history(&history_path); rl.clear_history();
let _ = rl.save_history(&file);
});
} }
LineResult::Error(line, err) => { LineResult::Error(line, reason) => {
rl.add_history_entry(&line); options.history(|file| {
let _ = rl.save_history(&history_path); rl.add_history_entry(&line);
let _ = rl.save_history(&file);
});
context context.with_host(|host| host.print_err(reason, &Text::from(session_text.clone())));
.host
.lock()
.print_err(err, &Text::from(session_text.clone()));
maybe_print_errors(&context, Text::from(session_text.clone()));
} }
LineResult::CtrlC => { LineResult::CtrlC => {
let config_ctrlc_exit = config::config(Tag::unknown())? let config_ctrlc_exit = configuration
.get("ctrlc_exit") .var("ctrlc_exit")
.map(|s| s.value.is_true()) .map(|s| s.value.is_true())
.unwrap_or(false); // default behavior is to allow CTRL-C spamming similar to other shells .unwrap_or(false); // default behavior is to allow CTRL-C spamming similar to other shells
@ -398,7 +436,10 @@ pub async fn cli(mut context: EvaluationContext, options: Options) -> Result<(),
} }
if ctrlcbreak { if ctrlcbreak {
let _ = rl.save_history(&history_path); options.history(|file| {
let _ = rl.save_history(&file);
});
std::process::exit(0); std::process::exit(0);
} else { } else {
context.with_host(|host| host.stdout("CTRL-C pressed (again to quit)")); context.with_host(|host| host.stdout("CTRL-C pressed (again to quit)"));
@ -422,7 +463,9 @@ pub async fn cli(mut context: EvaluationContext, options: Options) -> Result<(),
} }
// we are ok if we can not save history // we are ok if we can not save history
let _ = rl.save_history(&history_path); options.history(|file| {
let _ = rl.save_history(&file);
});
Ok(()) Ok(())
} }
@ -512,14 +555,14 @@ fn current_branch() -> String {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use nu_engine::basic_evaluation_context; use nu_engine::EvaluationContext;
#[quickcheck] #[quickcheck]
fn quickcheck_parse(data: String) -> bool { fn quickcheck_parse(data: String) -> bool {
let (tokens, err) = nu_parser::lex(&data, 0); let (tokens, err) = nu_parser::lex(&data, 0);
let (lite_block, err2) = nu_parser::parse_block(tokens); let (lite_block, err2) = nu_parser::parse_block(tokens);
if err.is_none() && err2.is_none() { if err.is_none() && err2.is_none() {
let context = basic_evaluation_context().unwrap(); let context = EvaluationContext::basic().unwrap();
let _ = nu_parser::classify_block(&lite_block, &context.scope); let _ = nu_parser::classify_block(&lite_block, &context.scope);
} }
true true

View File

@ -163,8 +163,8 @@ mod tests {
use super::EnvironmentSyncer; use super::EnvironmentSyncer;
use indexmap::IndexMap; use indexmap::IndexMap;
use nu_data::config::tests::FakeConfig; use nu_data::config::tests::FakeConfig;
use nu_engine::basic_evaluation_context;
use nu_engine::Env; use nu_engine::Env;
use nu_engine::EvaluationContext;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_test_support::fs::Stub::FileWithContent; use nu_test_support::fs::Stub::FileWithContent;
use nu_test_support::playground::Playground; use nu_test_support::playground::Playground;
@ -179,7 +179,7 @@ mod tests {
#[test] #[test]
fn syncs_env_if_new_env_entry_is_added_to_an_existing_configuration() -> Result<(), ShellError> fn syncs_env_if_new_env_entry_is_added_to_an_existing_configuration() -> Result<(), ShellError>
{ {
let mut ctx = basic_evaluation_context()?; let mut ctx = EvaluationContext::basic()?;
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new()))); ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
let mut expected = IndexMap::new(); let mut expected = IndexMap::new();
@ -282,7 +282,7 @@ mod tests {
#[test] #[test]
fn syncs_env_if_new_env_entry_in_session_is_not_in_configuration_file() -> Result<(), ShellError> fn syncs_env_if_new_env_entry_in_session_is_not_in_configuration_file() -> Result<(), ShellError>
{ {
let mut ctx = basic_evaluation_context()?; let mut ctx = EvaluationContext::basic()?;
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new()))); ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
let mut expected = IndexMap::new(); let mut expected = IndexMap::new();
@ -381,7 +381,7 @@ mod tests {
#[test] #[test]
fn nu_envs_have_higher_priority_and_does_not_get_overwritten() -> Result<(), ShellError> { fn nu_envs_have_higher_priority_and_does_not_get_overwritten() -> Result<(), ShellError> {
let mut ctx = basic_evaluation_context()?; let mut ctx = EvaluationContext::basic()?;
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new()))); ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
let mut expected = IndexMap::new(); let mut expected = IndexMap::new();
@ -457,7 +457,7 @@ mod tests {
#[test] #[test]
fn syncs_path_if_new_path_entry_in_session_is_not_in_configuration_file( fn syncs_path_if_new_path_entry_in_session_is_not_in_configuration_file(
) -> Result<(), ShellError> { ) -> Result<(), ShellError> {
let mut ctx = basic_evaluation_context()?; let mut ctx = EvaluationContext::basic()?;
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new()))); ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
let expected = std::env::join_paths(vec![ let expected = std::env::join_paths(vec![
@ -544,7 +544,7 @@ mod tests {
#[test] #[test]
fn nu_paths_have_higher_priority_and_new_paths_get_appended_to_the_end( fn nu_paths_have_higher_priority_and_new_paths_get_appended_to_the_end(
) -> Result<(), ShellError> { ) -> Result<(), ShellError> {
let mut ctx = basic_evaluation_context()?; let mut ctx = EvaluationContext::basic()?;
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new()))); ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
let expected = std::env::join_paths(vec![ let expected = std::env::join_paths(vec![

View File

@ -149,7 +149,6 @@ impl rustyline::Helper for Helper {}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use nu_engine::basic_evaluation_context;
use rustyline::completion::Completer; use rustyline::completion::Completer;
use rustyline::line_buffer::LineBuffer; use rustyline::line_buffer::LineBuffer;
@ -163,7 +162,7 @@ mod tests {
buffer.insert_str(0, text); buffer.insert_str(0, text);
buffer.set_pos(text.len() - 1); buffer.set_pos(text.len() - 1);
let helper = Helper::new(basic_evaluation_context().unwrap(), None); let helper = Helper::new(EvaluationContext::basic().unwrap(), None);
helper.update(&mut buffer, "cd ".len(), &replacement); helper.update(&mut buffer, "cd ".len(), &replacement);
@ -183,7 +182,7 @@ mod tests {
buffer.insert_str(0, text); buffer.insert_str(0, text);
buffer.set_pos(text.len() - 30); buffer.set_pos(text.len() - 30);
let helper = Helper::new(basic_evaluation_context().unwrap(), None); let helper = Helper::new(EvaluationContext::basic().unwrap(), None);
helper.update(&mut buffer, "cd ".len(), &replacement); helper.update(&mut buffer, "cd ".len(), &replacement);

View File

@ -509,7 +509,7 @@ mod tests {
#[cfg(feature = "which")] #[cfg(feature = "which")]
use futures::executor::block_on; use futures::executor::block_on;
#[cfg(feature = "which")] #[cfg(feature = "which")]
use nu_engine::basic_evaluation_context; use nu_engine::EvaluationContext;
#[cfg(feature = "which")] #[cfg(feature = "which")]
use nu_errors::ShellError; use nu_errors::ShellError;
#[cfg(feature = "which")] #[cfg(feature = "which")]
@ -534,7 +534,7 @@ mod tests {
let input = InputStream::empty(); let input = InputStream::empty();
let mut ctx = let mut ctx =
basic_evaluation_context().expect("There was a problem creating a basic context."); EvaluationContext::basic().expect("There was a problem creating a basic context.");
assert!( assert!(
run_external_command(cmd, &mut ctx, input, ExternalRedirection::Stdout) run_external_command(cmd, &mut ctx, input, ExternalRedirection::Stdout)
@ -548,7 +548,7 @@ mod tests {
// async fn failure_run() -> Result<(), ShellError> { // async fn failure_run() -> Result<(), ShellError> {
// let cmd = ExternalBuilder::for_name("fail").build(); // let cmd = ExternalBuilder::for_name("fail").build();
// let mut ctx = crate::cli::basic_evaluation_context().expect("There was a problem creating a basic context."); // let mut ctx = crate::cli::EvaluationContext::basic().expect("There was a problem creating a basic context.");
// let stream = run_external_command(cmd, &mut ctx, None, false) // let stream = run_external_command(cmd, &mut ctx, None, false)
// .await? // .await?
// .expect("There was a problem running the external command."); // .expect("There was a problem running the external command.");

View File

@ -2,7 +2,7 @@ use crate::prelude::*;
use nu_engine::CommandArgs; use nu_engine::CommandArgs;
use nu_engine::WholeStreamCommand; use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue}; use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
use nu_stream::OutputStream; use nu_stream::OutputStream;
pub struct Command; pub struct Command;
@ -22,9 +22,16 @@ impl WholeStreamCommand for Command {
} }
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> { async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
let name_span = args.call_info.name_tag.clone(); let name = args.call_info.name_tag.clone();
let name = args.call_info.name_tag;
let result = nu_data::config::read(name_span, &None)?; let path = match args.scope.get_var("config-path") {
Some(Value {
value: UntaggedValue::Primitive(Primitive::FilePath(path)),
..
}) => Some(path),
_ => nu_data::config::default_path().ok(),
};
let result = nu_data::config::read(&name, &path)?;
Ok(futures::stream::iter(vec![ReturnSuccess::value( Ok(futures::stream::iter(vec![ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(name), UntaggedValue::Row(result.into()).into_value(name),

View File

@ -1,10 +1,9 @@
use crate::prelude::*;
use nu_engine::basic_evaluation_context;
use nu_engine::whole_stream_command; use nu_engine::whole_stream_command;
use nu_engine::EvaluationContext;
use std::error::Error; use std::error::Error;
pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Box<dyn Error>> { pub fn create_default_context(interactive: bool) -> Result<EvaluationContext, Box<dyn Error>> {
let context = basic_evaluation_context()?; let context = EvaluationContext::basic()?;
{ {
use crate::commands::*; use crate::commands::*;

View File

@ -1,6 +1,5 @@
use crate::prelude::*; use crate::prelude::*;
use nu_data::config::{Conf, NuConfig}; use nu_data::config::{path::history as history_path, NuConfig};
use nu_engine::history_path;
use nu_engine::WholeStreamCommand; use nu_engine::WholeStreamCommand;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue}; use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
@ -34,7 +33,7 @@ impl WholeStreamCommand for History {
} }
async fn history(args: CommandArgs) -> Result<OutputStream, ShellError> { async fn history(args: CommandArgs) -> Result<OutputStream, ShellError> {
let config: Box<dyn Conf> = Box::new(NuConfig::new()); let config = NuConfig::new();
let tag = args.call_info.name_tag.clone(); let tag = args.call_info.name_tag.clone();
let (Arguments { clear }, _) = args.process().await?; let (Arguments { clear }, _) = args.process().await?;

View File

@ -6,14 +6,12 @@ mod stub_generate;
use double_echo::Command as DoubleEcho; use double_echo::Command as DoubleEcho;
use double_ls::Command as DoubleLs; use double_ls::Command as DoubleLs;
use stub_generate::{mock_path, Command as StubOpen};
use nu_engine::basic_evaluation_context;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_parser::ParserScope; use nu_parser::ParserScope;
use nu_protocol::hir::ClassifiedBlock; use nu_protocol::hir::ClassifiedBlock;
use nu_protocol::{ShellTypeName, Value}; use nu_protocol::{ShellTypeName, Value};
use nu_source::AnchorLocation; use nu_source::AnchorLocation;
use stub_generate::{mock_path, Command as StubOpen};
use crate::commands::{ use crate::commands::{
Append, BuildString, Each, Echo, First, Get, Keep, Last, Let, Nth, Select, StrCollect, Wrap, Append, BuildString, Each, Echo, First, Get, Keep, Last, Let, Nth, Select, StrCollect, Wrap,
@ -26,7 +24,7 @@ use futures::executor::block_on;
pub fn test_examples(cmd: Command) -> Result<(), ShellError> { pub fn test_examples(cmd: Command) -> Result<(), ShellError> {
let examples = cmd.examples(); let examples = cmd.examples();
let base_context = basic_evaluation_context()?; let base_context = EvaluationContext::basic()?;
base_context.add_commands(vec![ base_context.add_commands(vec![
// Command Doubles // Command Doubles
@ -92,7 +90,7 @@ pub fn test_examples(cmd: Command) -> Result<(), ShellError> {
pub fn test(cmd: impl WholeStreamCommand + 'static) -> Result<(), ShellError> { pub fn test(cmd: impl WholeStreamCommand + 'static) -> Result<(), ShellError> {
let examples = cmd.examples(); let examples = cmd.examples();
let base_context = basic_evaluation_context()?; let base_context = EvaluationContext::basic()?;
base_context.add_commands(vec![ base_context.add_commands(vec![
whole_stream_command(Echo {}), whole_stream_command(Echo {}),
@ -149,7 +147,7 @@ pub fn test(cmd: impl WholeStreamCommand + 'static) -> Result<(), ShellError> {
pub fn test_anchors(cmd: Command) -> Result<(), ShellError> { pub fn test_anchors(cmd: Command) -> Result<(), ShellError> {
let examples = cmd.examples(); let examples = cmd.examples();
let base_context = basic_evaluation_context()?; let base_context = EvaluationContext::basic()?;
base_context.add_commands(vec![ base_context.add_commands(vec![
// Minimal restricted commands to aid in testing // Minimal restricted commands to aid in testing

View File

@ -1,5 +1,6 @@
mod conf; mod conf;
mod nuconfig; mod nuconfig;
pub mod path;
pub mod tests; pub mod tests;
@ -185,7 +186,7 @@ pub fn default_path_for(file: &Option<PathBuf>) -> Result<PathBuf, ShellError> {
let file: &Path = file let file: &Path = file
.as_ref() .as_ref()
.map(AsRef::as_ref) .map(AsRef::as_ref)
.unwrap_or_else(|| "config.toml".as_ref()); .unwrap_or_else(|| self::path::DEFAULT_CONFIG_LOCATION.as_ref());
filename.push(file); filename.push(file);
Ok(filename) Ok(filename)

View File

@ -1,7 +1,9 @@
use nu_protocol::Value; use nu_protocol::Value;
use std::any::Any;
use std::fmt::Debug; use std::fmt::Debug;
pub trait Conf: Debug + Send { pub trait Conf: Debug + Send {
fn as_any(&self) -> &dyn Any;
fn is_modified(&self) -> Result<bool, Box<dyn std::error::Error>>; fn is_modified(&self) -> Result<bool, Box<dyn std::error::Error>>;
fn var(&self, key: &str) -> Option<Value>; fn var(&self, key: &str) -> Option<Value>;
fn env(&self) -> Option<Value>; fn env(&self) -> Option<Value>;
@ -11,6 +13,10 @@ pub trait Conf: Debug + Send {
} }
impl Conf for Box<dyn Conf> { impl Conf for Box<dyn Conf> {
fn as_any(&self) -> &dyn Any {
self
}
fn is_modified(&self) -> Result<bool, Box<dyn std::error::Error>> { fn is_modified(&self) -> Result<bool, Box<dyn std::error::Error>> {
(**self).is_modified() (**self).is_modified()
} }

View File

@ -2,15 +2,22 @@ use crate::config::{last_modified, read, Conf, Status};
use indexmap::IndexMap; use indexmap::IndexMap;
use nu_protocol::Value; use nu_protocol::Value;
use nu_source::Tag; use nu_source::Tag;
use std::any::Any;
use std::fmt::Debug; use std::fmt::Debug;
use std::path::PathBuf;
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct NuConfig { pub struct NuConfig {
pub source_file: Option<std::path::PathBuf>,
pub vars: IndexMap<String, Value>, pub vars: IndexMap<String, Value>,
pub modified_at: Status, pub modified_at: Status,
} }
impl Conf for NuConfig { impl Conf for NuConfig {
fn as_any(&self) -> &dyn Any {
self
}
fn is_modified(&self) -> Result<bool, Box<dyn std::error::Error>> { fn is_modified(&self) -> Result<bool, Box<dyn std::error::Error>> {
self.is_modified() self.is_modified()
} }
@ -30,7 +37,7 @@ impl Conf for NuConfig {
fn reload(&mut self) { fn reload(&mut self) {
let vars = &mut self.vars; let vars = &mut self.vars;
if let Ok(variables) = read(Tag::unknown(), &None) { if let Ok(variables) = read(Tag::unknown(), &self.source_file) {
vars.extend(variables); vars.extend(variables);
self.modified_at = if let Ok(status) = last_modified(&None) { self.modified_at = if let Ok(status) = last_modified(&None) {
@ -60,6 +67,7 @@ impl NuConfig {
}; };
NuConfig { NuConfig {
source_file: source_file.clone(),
vars, vars,
modified_at: NuConfig::get_last_modified(&source_file), modified_at: NuConfig::get_last_modified(&source_file),
} }
@ -75,11 +83,16 @@ impl NuConfig {
}; };
NuConfig { NuConfig {
source_file: None,
vars, vars,
modified_at: NuConfig::get_last_modified(&None), modified_at: NuConfig::get_last_modified(&None),
} }
} }
pub fn history_path(&self) -> PathBuf {
super::path::history(self)
}
pub fn get_last_modified(config_file: &Option<std::path::PathBuf>) -> Status { pub fn get_last_modified(config_file: &Option<std::path::PathBuf>) -> Status {
if let Ok(status) = last_modified(config_file) { if let Ok(status) = last_modified(config_file) {
status status
@ -91,15 +104,17 @@ impl NuConfig {
pub fn is_modified(&self) -> Result<bool, Box<dyn std::error::Error>> { pub fn is_modified(&self) -> Result<bool, Box<dyn std::error::Error>> {
let modified_at = &self.modified_at; let modified_at = &self.modified_at;
Ok(match (NuConfig::get_last_modified(&None), modified_at) { Ok(
(Status::LastModified(left), Status::LastModified(right)) => { match (NuConfig::get_last_modified(&self.source_file), modified_at) {
let left = left.duration_since(std::time::UNIX_EPOCH)?; (Status::LastModified(left), Status::LastModified(right)) => {
let right = (*right).duration_since(std::time::UNIX_EPOCH)?; let left = left.duration_since(std::time::UNIX_EPOCH)?;
let right = (*right).duration_since(std::time::UNIX_EPOCH)?;
left != right left != right
} }
(_, _) => false, (_, _) => false,
}) },
)
} }
pub fn var(&self, key: &str) -> Option<Value> { pub fn var(&self, key: &str) -> Option<Value> {

View File

@ -0,0 +1,32 @@
use crate::config::NuConfig;
use std::path::PathBuf;
pub const DEFAULT_CONFIG_LOCATION: &str = "config.toml";
const DEFAULT_HISTORY_LOCATION: &str = "history.txt";
pub fn history(config: &NuConfig) -> PathBuf {
let default_path = crate::config::user_data()
.map(|mut p| {
p.push(DEFAULT_HISTORY_LOCATION);
p
})
.unwrap_or_else(|_| PathBuf::from(DEFAULT_HISTORY_LOCATION));
let path = &config.var("history-path");
path.as_ref().map_or(default_path.clone(), |custom_path| {
match custom_path.as_string() {
Ok(path) => PathBuf::from(path),
Err(_) => default_path,
}
})
}
pub fn source_file(config: &NuConfig) -> PathBuf {
match &config.source_file {
Some(path) => PathBuf::from(path),
None => {
crate::config::default_path().unwrap_or_else(|_| PathBuf::from(DEFAULT_CONFIG_LOCATION))
}
}
}

View File

@ -1,5 +1,6 @@
use crate::config::{Conf, NuConfig, Status}; use crate::config::{Conf, NuConfig, Status};
use nu_protocol::Value; use nu_protocol::Value;
use std::any::Any;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -9,6 +10,10 @@ pub struct FakeConfig {
} }
impl Conf for FakeConfig { impl Conf for FakeConfig {
fn as_any(&self) -> &dyn Any {
self
}
fn is_modified(&self) -> Result<bool, Box<dyn std::error::Error>> { fn is_modified(&self) -> Result<bool, Box<dyn std::error::Error>> {
self.is_modified() self.is_modified()
} }

View File

@ -1,20 +0,0 @@
use crate::basic_shell_manager;
use crate::env::basic_host::BasicHost;
use crate::EvaluationContext;
use crate::Scope;
use parking_lot::Mutex;
use std::error::Error;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
pub fn basic_evaluation_context() -> Result<EvaluationContext, Box<dyn Error>> {
Ok(EvaluationContext {
scope: Scope::new(),
host: Arc::new(parking_lot::Mutex::new(Box::new(BasicHost))),
current_errors: Arc::new(Mutex::new(vec![])),
ctrl_c: Arc::new(AtomicBool::new(false)),
user_recently_used_autoenv_untrust: Arc::new(AtomicBool::new(false)),
shell_manager: basic_shell_manager::basic_shell_manager()?,
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
})
}

View File

@ -1,14 +0,0 @@
use crate::filesystem::filesystem_shell::FilesystemShell;
use crate::shell::shell_manager::ShellManager;
use parking_lot::Mutex;
use std::error::Error;
use std::sync::atomic::AtomicUsize;
use std::sync::Arc;
pub fn basic_shell_manager() -> Result<ShellManager, Box<dyn Error>> {
Ok(ShellManager {
current_shell: Arc::new(AtomicUsize::new(0)),
shells: Arc::new(Mutex::new(vec![Box::new(FilesystemShell::basic()?)])),
})
}

View File

@ -7,6 +7,21 @@ use std::ffi::OsString;
#[derive(Debug)] #[derive(Debug)]
pub struct BasicHost; pub struct BasicHost;
pub fn print_err(err: ShellError, source: &Text) {
if let Some(diag) = err.into_diagnostic() {
let source = source.to_string();
let mut files = codespan_reporting::files::SimpleFiles::new();
files.add("shell", source);
let writer = termcolor::StandardStream::stderr(termcolor::ColorChoice::Auto);
let config = codespan_reporting::term::Config::default();
let _ = std::panic::catch_unwind(move || {
let _ = codespan_reporting::term::emit(&mut writer.lock(), &config, &files, &diag);
});
}
}
impl Host for BasicHost { impl Host for BasicHost {
fn stdout(&mut self, out: &str) { fn stdout(&mut self, out: &str) {
match out { match out {
@ -23,18 +38,7 @@ impl Host for BasicHost {
} }
fn print_err(&mut self, err: ShellError, source: &Text) { fn print_err(&mut self, err: ShellError, source: &Text) {
if let Some(diag) = err.into_diagnostic() { print_err(err, source);
let source = source.to_string();
let mut files = codespan_reporting::files::SimpleFiles::new();
files.add("shell", source);
let writer = termcolor::StandardStream::stderr(termcolor::ColorChoice::Auto);
let config = codespan_reporting::term::Config::default();
let _ = std::panic::catch_unwind(move || {
let _ = codespan_reporting::term::emit(&mut writer.lock(), &config, &files, &diag);
});
}
} }
#[allow(unused_variables)] #[allow(unused_variables)]

View File

@ -1,13 +1,24 @@
use crate::evaluate::scope::Scope; use crate::evaluate::scope::Scope;
use crate::history_path::history_path; use nu_data::config::NuConfig;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::{Primitive, TaggedDictBuilder, UntaggedValue, Value}; use nu_protocol::{Primitive, TaggedDictBuilder, UntaggedValue, Value};
use nu_source::Tag; use nu_source::Tag;
pub fn nu(scope: &Scope, tag: impl Into<Tag>) -> Result<Value, ShellError> { pub fn nu(scope: &Scope, tag: impl Into<Tag>) -> Result<Value, ShellError> {
let env = &scope.get_env_vars();
let tag = tag.into(); let tag = tag.into();
let env = &scope.get_env_vars();
let config = if let Some(Value {
value: UntaggedValue::Primitive(Primitive::FilePath(path)),
..
}) = scope.get_var("config-path")
{
NuConfig::with(Some(path).map(|p| p.into_os_string()))
} else {
NuConfig::new()
};
let mut nu_dict = TaggedDictBuilder::new(&tag); let mut nu_dict = TaggedDictBuilder::new(&tag);
let mut dict = TaggedDictBuilder::new(&tag); let mut dict = TaggedDictBuilder::new(&tag);
@ -18,16 +29,10 @@ pub fn nu(scope: &Scope, tag: impl Into<Tag>) -> Result<Value, ShellError> {
} }
nu_dict.insert_value("env", dict.into_value()); nu_dict.insert_value("env", dict.into_value());
let config_file = match scope.get_var("config-path") { nu_dict.insert_value(
Some(Value { "config",
value: UntaggedValue::Primitive(Primitive::FilePath(path)), UntaggedValue::row(config.vars.clone()).into_value(&tag),
.. );
}) => Some(path),
_ => None,
};
let config = nu_data::config::read(&tag, &config_file)?;
nu_dict.insert_value("config", UntaggedValue::row(config).into_value(&tag));
let mut table = vec![]; let mut table = vec![];
for v in env.iter() { for v in env.iter() {
@ -49,15 +54,9 @@ pub fn nu(scope: &Scope, tag: impl Into<Tag>) -> Result<Value, ShellError> {
let temp = std::env::temp_dir(); let temp = std::env::temp_dir();
nu_dict.insert_value("temp-dir", UntaggedValue::filepath(temp).into_value(&tag)); nu_dict.insert_value("temp-dir", UntaggedValue::filepath(temp).into_value(&tag));
let config = if let Some(path) = config_file {
path
} else {
nu_data::config::default_path()?
};
nu_dict.insert_value( nu_dict.insert_value(
"config-path", "config-path",
UntaggedValue::filepath(config).into_value(&tag), UntaggedValue::filepath(nu_data::config::path::source_file(&config)).into_value(&tag),
); );
#[cfg(feature = "rustyline-support")] #[cfg(feature = "rustyline-support")]
@ -69,11 +68,9 @@ pub fn nu(scope: &Scope, tag: impl Into<Tag>) -> Result<Value, ShellError> {
); );
} }
let config: Box<dyn nu_data::config::Conf> = Box::new(nu_data::config::NuConfig::new());
let history = history_path(&config);
nu_dict.insert_value( nu_dict.insert_value(
"history-path", "history-path",
UntaggedValue::filepath(history).into_value(&tag), UntaggedValue::filepath(nu_data::config::path::history(&config)).into_value(&tag),
); );
Ok(nu_dict.into_value()) Ok(nu_dict.into_value())

View File

@ -1,13 +1,13 @@
use crate::call_info::UnevaluatedCallInfo; use crate::call_info::UnevaluatedCallInfo;
use crate::command_args::CommandArgs; use crate::command_args::CommandArgs;
use crate::env::host::Host; use crate::env::{basic_host::BasicHost, host::Host};
use crate::evaluate::scope::Scope; use crate::evaluate::scope::Scope;
use crate::shell::shell_manager::ShellManager; use crate::shell::shell_manager::ShellManager;
use crate::whole_stream_command::Command; use crate::whole_stream_command::Command;
use indexmap::IndexMap; use indexmap::IndexMap;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_protocol::hir; use nu_protocol::hir;
use nu_source::Tag; use nu_source::{Tag, Text};
use nu_stream::{InputStream, OutputStream}; use nu_stream::{InputStream, OutputStream};
use parking_lot::Mutex; use parking_lot::Mutex;
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
@ -27,6 +27,18 @@ pub struct EvaluationContext {
} }
impl EvaluationContext { impl EvaluationContext {
pub fn basic() -> Result<EvaluationContext, ShellError> {
Ok(EvaluationContext {
scope: Scope::new(),
host: Arc::new(parking_lot::Mutex::new(Box::new(BasicHost))),
current_errors: Arc::new(Mutex::new(vec![])),
ctrl_c: Arc::new(AtomicBool::new(false)),
user_recently_used_autoenv_untrust: Arc::new(AtomicBool::new(false)),
shell_manager: ShellManager::basic()?,
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
})
}
pub fn from_args(args: &CommandArgs) -> EvaluationContext { pub fn from_args(args: &CommandArgs) -> EvaluationContext {
EvaluationContext { EvaluationContext {
scope: args.scope.clone(), scope: args.scope.clone(),
@ -121,3 +133,18 @@ impl EvaluationContext {
output output
} }
} }
pub fn maybe_print_errors(context: &EvaluationContext, source: Text) -> bool {
let errors = context.current_errors.clone();
let mut errors = errors.lock();
if errors.len() > 0 {
let error = errors[0].clone();
*errors = vec![];
context.host.lock().print_err(error, &source);
true
} else {
false
}
}

View File

@ -1,22 +0,0 @@
use nu_data::config::Conf;
use std::path::PathBuf;
const DEFAULT_LOCATION: &str = "history.txt";
pub fn history_path(config: &dyn Conf) -> PathBuf {
let default_path = nu_data::config::user_data()
.map(|mut p| {
p.push(DEFAULT_LOCATION);
p
})
.unwrap_or_else(|_| PathBuf::from(DEFAULT_LOCATION));
config
.var("history-path")
.map_or(default_path.clone(), |custom_path| {
match custom_path.as_string() {
Ok(path) => PathBuf::from(path),
Err(_) => default_path,
}
})
}

View File

@ -1,25 +1,19 @@
pub mod basic_evaluation_context;
pub mod basic_shell_manager;
mod call_info; mod call_info;
mod command_args; mod command_args;
pub mod deserializer; pub mod deserializer;
pub mod documentation; pub mod documentation;
mod env; mod env;
mod evaluate; mod evaluate;
mod evaluation_context; pub mod evaluation_context;
mod example; mod example;
pub mod filesystem; pub mod filesystem;
mod history_path;
mod maybe_text_codec; mod maybe_text_codec;
pub mod plugin; pub mod plugin;
pub mod print;
mod runnable_context; mod runnable_context;
pub mod script; pub mod script;
pub mod shell; pub mod shell;
mod whole_stream_command; mod whole_stream_command;
pub use crate::basic_evaluation_context::basic_evaluation_context;
pub use crate::basic_shell_manager::basic_shell_manager;
pub use crate::call_info::UnevaluatedCallInfo; pub use crate::call_info::UnevaluatedCallInfo;
pub use crate::command_args::{ pub use crate::command_args::{
CommandArgs, EvaluatedCommandArgs, EvaluatedWholeStreamCommandArgs, RawCommandArgs, CommandArgs, EvaluatedCommandArgs, EvaluatedWholeStreamCommandArgs, RawCommandArgs,
@ -36,7 +30,6 @@ pub use crate::example::Example;
pub use crate::filesystem::dir_info::{DirBuilder, DirInfo, FileInfo}; pub use crate::filesystem::dir_info::{DirBuilder, DirInfo, FileInfo};
pub use crate::filesystem::filesystem_shell::FilesystemShell; pub use crate::filesystem::filesystem_shell::FilesystemShell;
pub use crate::filesystem::path; pub use crate::filesystem::path;
pub use crate::history_path::history_path;
pub use crate::maybe_text_codec::{MaybeTextCodec, StringOrBinary}; pub use crate::maybe_text_codec::{MaybeTextCodec, StringOrBinary};
pub use crate::runnable_context::RunnableContext; pub use crate::runnable_context::RunnableContext;
pub use crate::shell::help_shell::{command_dict, HelpShell}; pub use crate::shell::help_shell::{command_dict, HelpShell};

View File

@ -1,18 +0,0 @@
use nu_source::Text;
use crate::EvaluationContext;
pub fn maybe_print_errors(context: &EvaluationContext, source: Text) -> bool {
let errors = context.current_errors.clone();
let mut errors = errors.lock();
if errors.len() > 0 {
let error = errors[0].clone();
*errors = vec![];
context.host.lock().print_err(error, &source);
true
} else {
false
}
}

View File

@ -1,5 +1,5 @@
use crate::path::canonicalize;
use crate::run_block; use crate::run_block;
use crate::{path::canonicalize, print::maybe_print_errors};
use crate::{MaybeTextCodec, StringOrBinary}; use crate::{MaybeTextCodec, StringOrBinary};
use futures::StreamExt; use futures::StreamExt;
use futures_codec::FramedRead; use futures_codec::FramedRead;
@ -11,7 +11,7 @@ use nu_protocol::hir::{
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value}; use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value};
use nu_stream::{InputStream, ToInputStream}; use nu_stream::{InputStream, ToInputStream};
use crate::EvaluationContext; use crate::{evaluation_context, EvaluationContext};
use log::{debug, trace}; use log::{debug, trace};
use nu_source::{Span, Tag, Text}; use nu_source::{Span, Tag, Text};
use std::iter::Iterator; use std::iter::Iterator;
@ -244,19 +244,16 @@ pub async fn run_script_standalone(
} }
}; };
maybe_print_errors(&context, Text::from(line)); evaluation_context::maybe_print_errors(&context, Text::from(line));
if error_code != 0 && exit_on_error { if error_code != 0 && exit_on_error {
std::process::exit(error_code); std::process::exit(error_code);
} }
} }
LineResult::Error(line, err) => { LineResult::Error(line, err) => {
context context.with_host(|host| host.print_err(err, &Text::from(line.clone())));
.host
.lock()
.print_err(err, &Text::from(line.clone()));
maybe_print_errors(&context, Text::from(line)); evaluation_context::maybe_print_errors(&context, Text::from(line));
if exit_on_error { if exit_on_error {
std::process::exit(1); std::process::exit(1);
} }

View File

@ -1,10 +1,12 @@
use crate::command_args::EvaluatedWholeStreamCommandArgs; use crate::command_args::EvaluatedWholeStreamCommandArgs;
use crate::maybe_text_codec::StringOrBinary; use crate::maybe_text_codec::StringOrBinary;
use crate::shell::Shell;
use futures::Stream; use futures::Stream;
use nu_stream::OutputStream; use nu_stream::OutputStream;
use crate::filesystem::filesystem_shell::FilesystemShell;
use crate::shell::shell_args::{CdArgs, CopyArgs, LsArgs, MkdirArgs, MvArgs, RemoveArgs}; use crate::shell::shell_args::{CdArgs, CopyArgs, LsArgs, MkdirArgs, MvArgs, RemoveArgs};
use crate::shell::Shell;
use encoding_rs::Encoding; use encoding_rs::Encoding;
use nu_errors::ShellError; use nu_errors::ShellError;
use nu_source::{Span, Tag}; use nu_source::{Span, Tag};
@ -20,6 +22,13 @@ pub struct ShellManager {
} }
impl ShellManager { impl ShellManager {
pub fn basic() -> Result<Self, ShellError> {
Ok(ShellManager {
current_shell: Arc::new(AtomicUsize::new(0)),
shells: Arc::new(Mutex::new(vec![Box::new(FilesystemShell::basic()?)])),
})
}
pub fn insert_at_current(&self, shell: Box<dyn Shell + Send>) { pub fn insert_at_current(&self, shell: Box<dyn Shell + Send>) {
self.shells.lock().push(shell); self.shells.lock().push(shell);
self.current_shell self.current_shell

View File

@ -48,6 +48,9 @@ macro_rules! nu {
let mut process = match Command::new($crate::fs::executable_path()) let mut process = match Command::new($crate::fs::executable_path())
.env("PATH", paths_joined) .env("PATH", paths_joined)
.arg("--skip-plugins") .arg("--skip-plugins")
.arg("--no-history")
.arg("--config-file")
.arg($crate::fs::DisplayPath::display_path(&$crate::fs::fixtures().join("playground/config/default.toml")))
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stderr(Stdio::piped()) .stderr(Stdio::piped())
@ -153,7 +156,7 @@ macro_rules! nu_with_plugins {
pub fn read_std(std: &[u8]) -> String { pub fn read_std(std: &[u8]) -> String {
let out = String::from_utf8_lossy(std); let out = String::from_utf8_lossy(std);
let out = out.lines().skip(1).collect::<Vec<_>>().join("\n"); let out = out.lines().collect::<Vec<_>>().join("\n");
let out = out.replace("\r\n", ""); let out = out.replace("\r\n", "");
out.replace("\n", "") out.replace("\n", "")
} }

View File

@ -9,4 +9,4 @@ mod tests;
pub use director::Director; pub use director::Director;
pub use matchers::says; pub use matchers::says;
pub use nu_process::{Executable, NuProcess, NuResult, Outcome}; pub use nu_process::{Executable, NuProcess, NuResult, Outcome};
pub use play::{Dirs, Playground}; pub use play::{Dirs, EnvironmentVariable, Playground};

View File

@ -1,48 +1,66 @@
use super::nu_process::*; use super::nu_process::*;
use super::EnvironmentVariable;
use std::ffi::OsString; use std::ffi::OsString;
use std::fmt; use std::fmt;
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Director { pub struct Director {
pub cwd: Option<OsString>, pub cwd: Option<OsString>,
pub environment_vars: Vec<EnvironmentVariable>,
pub config: Option<OsString>, pub config: Option<OsString>,
pub pipeline: Option<String>, pub pipeline: Option<Vec<String>>,
pub executable: Option<NuProcess>, pub executable: Option<NuProcess>,
} }
impl Director { impl Director {
pub fn cococo(&self, arg: &str) -> Self { pub fn cococo(&self, arg: &str) -> Self {
let mut process = NuProcess::default(); let mut process = NuProcess {
environment_vars: self.environment_vars.clone(),
..Default::default()
};
process.args(&["--testbin", "cococo", arg]); process.args(&["--testbin", "cococo", arg]);
Director { Director {
config: self.config.clone(), config: self.config.clone(),
executable: Some(process), executable: Some(process),
environment_vars: self.environment_vars.clone(),
..Default::default() ..Default::default()
} }
} }
pub fn and_then(&mut self, commands: &str) -> &mut Self {
let commands = commands.to_string();
if let Some(ref mut pipeline) = self.pipeline {
pipeline.push(commands);
} else {
self.pipeline = Some(vec![commands]);
}
self
}
pub fn pipeline(&self, commands: &str) -> Self { pub fn pipeline(&self, commands: &str) -> Self {
let mut director = Director { let mut director = Director {
pipeline: if commands.is_empty() { pipeline: if commands.is_empty() {
None None
} else { } else {
Some(format!( Some(vec![commands.to_string()])
"
{}
exit",
commands
))
}, },
..Default::default() ..Default::default()
}; };
let mut process = NuProcess::default(); let mut process = NuProcess {
environment_vars: self.environment_vars.clone(),
..Default::default()
};
if let Some(working_directory) = &self.cwd { if let Some(working_directory) = &self.cwd {
process.cwd(working_directory); process.cwd(working_directory);
} }
process.arg("--skip-plugins"); process.arg("--skip-plugins");
process.arg("--no-history");
if let Some(config_file) = self.config.as_ref() { if let Some(config_file) = self.config.as_ref() {
process.args(&[ process.args(&[
"--config-file", "--config-file",
@ -64,7 +82,7 @@ impl Director {
} }
impl Executable for Director { impl Executable for Director {
fn execute(&self) -> NuResult { fn execute(&mut self) -> NuResult {
use std::io::Write; use std::io::Write;
use std::process::Stdio; use std::process::Stdio;
@ -81,13 +99,16 @@ impl Executable for Director {
Err(why) => panic!("Can't run test {}", why.to_string()), Err(why) => panic!("Can't run test {}", why.to_string()),
}; };
if let Some(pipeline) = &self.pipeline { if let Some(pipelines) = &self.pipeline {
process let child = process.stdin.as_mut().expect("Failed to open stdin");
.stdin
.as_mut() for pipeline in pipelines.iter() {
.expect("couldn't open stdin") child
.write_all(pipeline.as_bytes()) .write_all(format!("{}\n", pipeline).as_bytes())
.expect("couldn't write to stdin"); .expect("Could not write to");
}
child.write_all(b"exit\n").expect("Could not write to");
} }
process process

View File

@ -1,12 +1,12 @@
use crate::fs::executable_path; use super::EnvironmentVariable;
use std::collections::HashMap; use crate::fs::{binaries as test_bins_path, executable_path};
use std::ffi::{OsStr, OsString}; use std::ffi::{OsStr, OsString};
use std::fmt; use std::fmt;
use std::path::Path; use std::path::Path;
use std::process::{Command, ExitStatus}; use std::process::{Command, ExitStatus};
pub trait Executable { pub trait Executable {
fn execute(&self) -> NuResult; fn execute(&mut self) -> NuResult;
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -36,7 +36,7 @@ pub struct NuError {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct NuProcess { pub struct NuProcess {
pub arguments: Vec<OsString>, pub arguments: Vec<OsString>,
pub environment_vars: HashMap<String, Option<OsString>>, pub environment_vars: Vec<EnvironmentVariable>,
pub cwd: Option<OsString>, pub cwd: Option<OsString>,
} }
@ -56,7 +56,7 @@ impl Default for NuProcess {
fn default() -> Self { fn default() -> Self {
Self { Self {
arguments: vec![], arguments: vec![],
environment_vars: HashMap::default(), environment_vars: Vec::default(),
cwd: None, cwd: None,
} }
} }
@ -90,6 +90,21 @@ impl NuProcess {
command.current_dir(cwd); command.current_dir(cwd);
} }
command.env_clear();
let paths = vec![test_bins_path()];
let paths_joined = match std::env::join_paths(paths.iter()) {
Ok(all) => all,
Err(_) => panic!("Couldn't join paths for PATH var."),
};
command.env("PATH", paths_joined);
for env_var in &self.environment_vars {
command.env(&env_var.name, &env_var.value);
}
for arg in &self.arguments { for arg in &self.arguments {
command.arg(arg); command.arg(arg);
} }

View File

@ -7,11 +7,27 @@ use std::path::{Path, PathBuf};
use std::str; use std::str;
use tempfile::{tempdir, TempDir}; use tempfile::{tempdir, TempDir};
#[derive(Default, Clone, Debug)]
pub struct EnvironmentVariable {
pub name: String,
pub value: String,
}
impl EnvironmentVariable {
fn new(name: &str, value: &str) -> Self {
Self {
name: name.to_string(),
value: value.to_string(),
}
}
}
pub struct Playground<'a> { pub struct Playground<'a> {
root: TempDir, root: TempDir,
tests: String, tests: String,
cwd: PathBuf, cwd: PathBuf,
config: PathBuf, config: PathBuf,
environment_vars: Vec<EnvironmentVariable>,
dirs: &'a Dirs, dirs: &'a Dirs,
} }
@ -71,6 +87,7 @@ impl<'a> Playground<'a> {
tests: topic.to_string(), tests: topic.to_string(),
cwd: nuplay_dir, cwd: nuplay_dir,
config: fixtures.join("playground/config/default.toml"), config: fixtures.join("playground/config/default.toml"),
environment_vars: Vec::default(),
dirs: &Dirs::default(), dirs: &Dirs::default(),
}; };
@ -108,6 +125,12 @@ impl<'a> Playground<'a> {
self self
} }
pub fn with_env(&mut self, name: &str, value: &str) -> &mut Self {
self.environment_vars
.push(EnvironmentVariable::new(name, value));
self
}
pub fn get_config(&self) -> &str { pub fn get_config(&self) -> &str {
self.config.to_str().expect("could not convert path.") self.config.to_str().expect("could not convert path.")
} }
@ -116,6 +139,7 @@ impl<'a> Playground<'a> {
Director { Director {
cwd: Some(self.dirs.test().into()), cwd: Some(self.dirs.test().into()),
config: Some(self.config.clone().into()), config: Some(self.config.clone().into()),
environment_vars: self.environment_vars.clone(),
..Default::default() ..Default::default()
} }
} }

View File

@ -64,6 +64,13 @@ fn main() -> Result<(), Box<dyn Error>> {
.multiple(false) .multiple(false)
.takes_value(false), .takes_value(false),
) )
.arg(
Arg::with_name("no-history")
.hidden(true)
.long("no-history")
.multiple(false)
.takes_value(false),
)
.arg( .arg(
Arg::with_name("script") Arg::with_name("script")
.help("the nu script to run") .help("the nu script to run")
@ -95,6 +102,7 @@ fn main() -> Result<(), Box<dyn Error>> {
.value_of("config-file") .value_of("config-file")
.map(std::ffi::OsString::from); .map(std::ffi::OsString::from);
options.stdin = matches.is_present("stdin"); options.stdin = matches.is_present("stdin");
options.save_history = !matches.is_present("no-history");
let loglevel = match matches.value_of("loglevel") { let loglevel = match matches.value_of("loglevel") {
None => LevelFilter::Warn, None => LevelFilter::Warn,

View File

@ -0,0 +1,113 @@
use nu_test_support::fs::Stub::FileWithContent;
use nu_test_support::fs::{AbsolutePath, DisplayPath};
use nu_test_support::playground::{says, Playground};
use std::path::PathBuf;
use hamcrest2::assert_that;
use hamcrest2::prelude::*;
#[test]
fn setting_environment_value_to_configuration_should_pick_up_into_in_memory_environment_on_runtime()
{
Playground::setup("environment_syncing_test_1", |dirs, nu| {
let file = AbsolutePath::new(dirs.test().join("config.toml"));
nu.with_config(&file);
nu.with_files(vec![FileWithContent(
"config.toml",
r#"
skip_welcome_message = true
[env]
SHELL = "/local/nu"
"#,
)]);
assert_that!(
nu.pipeline("config set env.USER NUNO; echo $nothing")
.and_then("echo $nu.env.USER"),
says().to_stdout("NUNO")
);
});
}
#[test]
fn inherited_environment_values_not_present_in_configuration_should_pick_up_into_in_memory_environment(
) {
Playground::setup("environment_syncing_test_2", |dirs, nu| {
let file = AbsolutePath::new(dirs.test().join("config.toml"));
nu.with_files(vec![FileWithContent(
"config.toml",
r#"
skip_welcome_message = true
[env]
SHELL = "/local/nu"
"#,
)])
.with_config(&file)
.with_env("USER", "NUNO");
assert_that!(nu.pipeline("echo $nu.env.USER"), says().to_stdout("NUNO"));
});
}
#[test]
fn environment_values_present_in_configuration_overwrites_inherited_environment_values() {
Playground::setup("environment_syncing_test_3", |dirs, nu| {
let file = AbsolutePath::new(dirs.test().join("config.toml"));
nu.with_files(vec![FileWithContent(
"config.toml",
r#"
skip_welcome_message = true
[env]
SHELL = "/usr/bin/you_already_made_the_nu_choice"
"#,
)])
.with_config(&file)
.with_env("SHELL", "/usr/bin/sh");
assert_that!(
nu.pipeline("echo $nu.env.SHELL"),
says().to_stdout("/usr/bin/you_already_made_the_nu_choice")
);
});
}
#[test]
fn inherited_environment_path_values_not_present_in_configuration_should_pick_up_into_in_memory_environment(
) {
Playground::setup("environment_syncing_test_4", |dirs, nu| {
let file = AbsolutePath::new(dirs.test().join("config.toml"));
let expected_paths = vec![
PathBuf::from("/Users/andresrobalino/.volta/bin"),
PathBuf::from("/Users/mosqueteros/bin"),
PathBuf::from("/path/to/be/added"),
]
.iter()
.map(|p| p.display_path())
.collect::<Vec<_>>()
.join("-");
nu.with_files(vec![FileWithContent(
"config.toml",
r#"
skip_welcome_message = true
path = ["/Users/andresrobalino/.volta/bin", "/Users/mosqueteros/bin"]
"#,
)])
.with_config(&file)
.with_env("PATH", &PathBuf::from("/path/to/be/added").display_path());
assert_that!(
nu.pipeline("echo $nu.path | str collect '-'"),
says().to_stdout(&expected_paths)
);
});
}

View File

@ -1,4 +1,5 @@
mod configuration; mod configuration;
mod in_sync;
mod nu_env; mod nu_env;
pub mod support { pub mod support {