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 nu_command::commands::default_context::create_default_context;
use nu_engine::{
print::maybe_print_errors, run_block, script::run_script_standalone, EvaluationContext,
};
use nu_engine::{evaluation_context, run_block, script::run_script_standalone, EvaluationContext};
#[allow(unused_imports)]
pub(crate) use nu_engine::script::{process_script, LineResult};
@ -37,6 +35,8 @@ use std::path::PathBuf;
pub struct Options {
pub config: Option<OsString>,
pub history: Option<PathBuf>,
pub save_history: bool,
pub stdin: bool,
pub scripts: Vec<NuScript>,
}
@ -51,10 +51,22 @@ impl Options {
pub fn new() -> Self {
Self {
config: None,
history: None,
save_history: true,
stdin: false,
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 {
@ -125,9 +137,9 @@ pub fn search_paths() -> Vec<std::path::PathBuf> {
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 syncer = create_environment_syncer(&context, &options);
let mut syncer = create_environment_syncer(&context, &mut options);
let config = syncer.get_config();
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);
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);
@ -155,11 +167,16 @@ pub async fn run_script_file(options: Options) -> Result<(), Box<dyn Error>> {
Ok(())
}
fn create_environment_syncer(context: &EvaluationContext, options: &Options) -> EnvironmentSyncer {
if let Some(config_file) = &options.config {
fn create_environment_syncer(
context: &EvaluationContext,
options: &mut Options,
) -> EnvironmentSyncer {
let configuration = match &options.config {
Some(config_file) => {
let location = Some(AnchorLocation::File(
config_file.to_string_lossy().to_string(),
));
let tag = Tag::unknown().anchored(location);
context.scope.add_var(
@ -167,15 +184,34 @@ fn create_environment_syncer(context: &EvaluationContext, options: &Options) ->
UntaggedValue::filepath(PathBuf::from(&config_file)).into_value(tag),
);
EnvironmentSyncer::with_config(Box::new(NuConfig::with(Some(config_file.into()))))
} else {
EnvironmentSyncer::new()
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")]
pub async fn cli(mut context: EvaluationContext, options: Options) -> Result<(), Box<dyn Error>> {
let mut syncer = create_environment_syncer(&context, &options);
pub async fn cli(
mut context: EvaluationContext,
mut options: Options,
) -> Result<(), Box<dyn Error>> {
let mut syncer = create_environment_syncer(&context, &mut options);
let configuration = syncer.get_config();
@ -187,7 +223,7 @@ pub async fn cli(mut context: EvaluationContext, options: Options) -> Result<(),
syncer.sync_path_vars(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);
@ -214,11 +250,9 @@ pub async fn cli(mut context: EvaluationContext, options: Options) -> Result<(),
// Give ourselves a scope to work in
context.scope.enter_scope();
let env = context.get_env();
context.scope.add_env_to_base(env);
let history_path = nu_engine::history_path(&configuration);
let _ = rl.load_history(&history_path);
options.history(|file| {
let _ = rl.load_history(&file);
});
let mut session_text = String::new();
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()?;
context.scope.enter_scope();
let (mut prompt_block, err) = nu_parser::parse(&prompt_line, 0, &context.scope);
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())
} else {
// let env = context.get_env();
let run_result = run_block(&prompt_block, &context, InputStream::empty()).await;
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(string_result) => {
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();
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) {
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);
@ -365,31 +401,33 @@ pub async fn cli(mut context: EvaluationContext, options: Options) -> Result<(),
match line {
LineResult::Success(line) => {
options.history(|file| {
rl.add_history_entry(&line);
let _ = rl.save_history(&history_path);
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 => {
options.history(|file| {
rl.clear_history();
let _ = rl.save_history(&history_path);
let _ = rl.save_history(&file);
});
}
LineResult::Error(line, err) => {
LineResult::Error(line, reason) => {
options.history(|file| {
rl.add_history_entry(&line);
let _ = rl.save_history(&history_path);
let _ = rl.save_history(&file);
});
context
.host
.lock()
.print_err(err, &Text::from(session_text.clone()));
maybe_print_errors(&context, Text::from(session_text.clone()));
context.with_host(|host| host.print_err(reason, &Text::from(session_text.clone())));
}
LineResult::CtrlC => {
let config_ctrlc_exit = config::config(Tag::unknown())?
.get("ctrlc_exit")
let config_ctrlc_exit = configuration
.var("ctrlc_exit")
.map(|s| s.value.is_true())
.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 {
let _ = rl.save_history(&history_path);
options.history(|file| {
let _ = rl.save_history(&file);
});
std::process::exit(0);
} else {
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
let _ = rl.save_history(&history_path);
options.history(|file| {
let _ = rl.save_history(&file);
});
Ok(())
}
@ -512,14 +555,14 @@ fn current_branch() -> String {
#[cfg(test)]
mod tests {
use nu_engine::basic_evaluation_context;
use nu_engine::EvaluationContext;
#[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 = EvaluationContext::basic().unwrap();
let _ = nu_parser::classify_block(&lite_block, &context.scope);
}
true

View File

@ -163,8 +163,8 @@ mod tests {
use super::EnvironmentSyncer;
use indexmap::IndexMap;
use nu_data::config::tests::FakeConfig;
use nu_engine::basic_evaluation_context;
use nu_engine::Env;
use nu_engine::EvaluationContext;
use nu_errors::ShellError;
use nu_test_support::fs::Stub::FileWithContent;
use nu_test_support::playground::Playground;
@ -179,7 +179,7 @@ mod tests {
#[test]
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())));
let mut expected = IndexMap::new();
@ -282,7 +282,7 @@ mod tests {
#[test]
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())));
let mut expected = IndexMap::new();
@ -381,7 +381,7 @@ mod tests {
#[test]
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())));
let mut expected = IndexMap::new();
@ -457,7 +457,7 @@ mod tests {
#[test]
fn syncs_path_if_new_path_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())));
let expected = std::env::join_paths(vec![
@ -544,7 +544,7 @@ mod tests {
#[test]
fn nu_paths_have_higher_priority_and_new_paths_get_appended_to_the_end(
) -> 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())));
let expected = std::env::join_paths(vec![

View File

@ -149,7 +149,6 @@ impl rustyline::Helper for Helper {}
#[cfg(test)]
mod tests {
use super::*;
use nu_engine::basic_evaluation_context;
use rustyline::completion::Completer;
use rustyline::line_buffer::LineBuffer;
@ -163,7 +162,7 @@ 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(EvaluationContext::basic().unwrap(), None);
helper.update(&mut buffer, "cd ".len(), &replacement);
@ -183,7 +182,7 @@ 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(EvaluationContext::basic().unwrap(), None);
helper.update(&mut buffer, "cd ".len(), &replacement);

View File

@ -509,7 +509,7 @@ mod tests {
#[cfg(feature = "which")]
use futures::executor::block_on;
#[cfg(feature = "which")]
use nu_engine::basic_evaluation_context;
use nu_engine::EvaluationContext;
#[cfg(feature = "which")]
use nu_errors::ShellError;
#[cfg(feature = "which")]
@ -534,7 +534,7 @@ mod tests {
let input = InputStream::empty();
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!(
run_external_command(cmd, &mut ctx, input, ExternalRedirection::Stdout)
@ -548,7 +548,7 @@ mod tests {
// async fn failure_run() -> Result<(), ShellError> {
// 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)
// .await?
// .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::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
use nu_stream::OutputStream;
pub struct Command;
@ -22,9 +22,16 @@ impl WholeStreamCommand for Command {
}
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;
let result = nu_data::config::read(name_span, &None)?;
let name = args.call_info.name_tag.clone();
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(
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::EvaluationContext;
use std::error::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::*;

View File

@ -1,6 +1,5 @@
use crate::prelude::*;
use nu_data::config::{Conf, NuConfig};
use nu_engine::history_path;
use nu_data::config::{path::history as history_path, NuConfig};
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
@ -34,7 +33,7 @@ impl WholeStreamCommand for History {
}
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 (Arguments { clear }, _) = args.process().await?;

View File

@ -6,14 +6,12 @@ mod stub_generate;
use double_echo::Command as DoubleEcho;
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_parser::ParserScope;
use nu_protocol::hir::ClassifiedBlock;
use nu_protocol::{ShellTypeName, Value};
use nu_source::AnchorLocation;
use stub_generate::{mock_path, Command as StubOpen};
use crate::commands::{
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> {
let examples = cmd.examples();
let base_context = basic_evaluation_context()?;
let base_context = EvaluationContext::basic()?;
base_context.add_commands(vec![
// Command Doubles
@ -92,7 +90,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 = EvaluationContext::basic()?;
base_context.add_commands(vec![
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> {
let examples = cmd.examples();
let base_context = basic_evaluation_context()?;
let base_context = EvaluationContext::basic()?;
base_context.add_commands(vec![
// Minimal restricted commands to aid in testing

View File

@ -1,5 +1,6 @@
mod conf;
mod nuconfig;
pub mod path;
pub mod tests;
@ -185,7 +186,7 @@ pub fn default_path_for(file: &Option<PathBuf>) -> Result<PathBuf, ShellError> {
let file: &Path = file
.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);
Ok(filename)

View File

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

View File

@ -2,15 +2,22 @@ use crate::config::{last_modified, read, Conf, Status};
use indexmap::IndexMap;
use nu_protocol::Value;
use nu_source::Tag;
use std::any::Any;
use std::fmt::Debug;
use std::path::PathBuf;
#[derive(Debug, Clone, Default)]
pub struct NuConfig {
pub source_file: Option<std::path::PathBuf>,
pub vars: IndexMap<String, Value>,
pub modified_at: Status,
}
impl Conf for NuConfig {
fn as_any(&self) -> &dyn Any {
self
}
fn is_modified(&self) -> Result<bool, Box<dyn std::error::Error>> {
self.is_modified()
}
@ -30,7 +37,7 @@ impl Conf for NuConfig {
fn reload(&mut self) {
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);
self.modified_at = if let Ok(status) = last_modified(&None) {
@ -60,6 +67,7 @@ impl NuConfig {
};
NuConfig {
source_file: source_file.clone(),
vars,
modified_at: NuConfig::get_last_modified(&source_file),
}
@ -75,11 +83,16 @@ impl NuConfig {
};
NuConfig {
source_file: None,
vars,
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 {
if let Ok(status) = last_modified(config_file) {
status
@ -91,7 +104,8 @@ impl NuConfig {
pub fn is_modified(&self) -> Result<bool, Box<dyn std::error::Error>> {
let modified_at = &self.modified_at;
Ok(match (NuConfig::get_last_modified(&None), modified_at) {
Ok(
match (NuConfig::get_last_modified(&self.source_file), modified_at) {
(Status::LastModified(left), Status::LastModified(right)) => {
let left = left.duration_since(std::time::UNIX_EPOCH)?;
let right = (*right).duration_since(std::time::UNIX_EPOCH)?;
@ -99,7 +113,8 @@ impl NuConfig {
left != right
}
(_, _) => false,
})
},
)
}
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 nu_protocol::Value;
use std::any::Any;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
@ -9,6 +10,10 @@ pub struct FakeConfig {
}
impl Conf for FakeConfig {
fn as_any(&self) -> &dyn Any {
self
}
fn is_modified(&self) -> Result<bool, Box<dyn std::error::Error>> {
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)]
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 {
fn stdout(&mut self, out: &str) {
match out {
@ -23,18 +38,7 @@ impl Host for BasicHost {
}
fn print_err(&mut self, 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);
});
}
print_err(err, source);
}
#[allow(unused_variables)]

View File

@ -1,13 +1,24 @@
use crate::evaluate::scope::Scope;
use crate::history_path::history_path;
use nu_data::config::NuConfig;
use nu_errors::ShellError;
use nu_protocol::{Primitive, TaggedDictBuilder, UntaggedValue, Value};
use nu_source::Tag;
pub fn nu(scope: &Scope, tag: impl Into<Tag>) -> Result<Value, ShellError> {
let env = &scope.get_env_vars();
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 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());
let config_file = match scope.get_var("config-path") {
Some(Value {
value: UntaggedValue::Primitive(Primitive::FilePath(path)),
..
}) => Some(path),
_ => None,
};
let config = nu_data::config::read(&tag, &config_file)?;
nu_dict.insert_value("config", UntaggedValue::row(config).into_value(&tag));
nu_dict.insert_value(
"config",
UntaggedValue::row(config.vars.clone()).into_value(&tag),
);
let mut table = vec![];
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();
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(
"config-path",
UntaggedValue::filepath(config).into_value(&tag),
UntaggedValue::filepath(nu_data::config::path::source_file(&config)).into_value(&tag),
);
#[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(
"history-path",
UntaggedValue::filepath(history).into_value(&tag),
UntaggedValue::filepath(nu_data::config::path::history(&config)).into_value(&tag),
);
Ok(nu_dict.into_value())

View File

@ -1,13 +1,13 @@
use crate::call_info::UnevaluatedCallInfo;
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::shell::shell_manager::ShellManager;
use crate::whole_stream_command::Command;
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_protocol::hir;
use nu_source::Tag;
use nu_source::{Tag, Text};
use nu_stream::{InputStream, OutputStream};
use parking_lot::Mutex;
use std::sync::atomic::AtomicBool;
@ -27,6 +27,18 @@ pub struct 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 {
EvaluationContext {
scope: args.scope.clone(),
@ -121,3 +133,18 @@ impl EvaluationContext {
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 command_args;
pub mod deserializer;
pub mod documentation;
mod env;
mod evaluate;
mod evaluation_context;
pub mod evaluation_context;
mod example;
pub mod filesystem;
mod history_path;
mod maybe_text_codec;
pub mod plugin;
pub mod print;
mod runnable_context;
pub mod script;
pub mod shell;
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::command_args::{
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::filesystem_shell::FilesystemShell;
pub use crate::filesystem::path;
pub use crate::history_path::history_path;
pub use crate::maybe_text_codec::{MaybeTextCodec, StringOrBinary};
pub use crate::runnable_context::RunnableContext;
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::{path::canonicalize, print::maybe_print_errors};
use crate::{MaybeTextCodec, StringOrBinary};
use futures::StreamExt;
use futures_codec::FramedRead;
@ -11,7 +11,7 @@ use nu_protocol::hir::{
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value};
use nu_stream::{InputStream, ToInputStream};
use crate::EvaluationContext;
use crate::{evaluation_context, EvaluationContext};
use log::{debug, trace};
use nu_source::{Span, Tag, Text};
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 {
std::process::exit(error_code);
}
}
LineResult::Error(line, err) => {
context
.host
.lock()
.print_err(err, &Text::from(line.clone()));
context.with_host(|host| host.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 {
std::process::exit(1);
}

View File

@ -1,10 +1,12 @@
use crate::command_args::EvaluatedWholeStreamCommandArgs;
use crate::maybe_text_codec::StringOrBinary;
use crate::shell::Shell;
use futures::Stream;
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;
use encoding_rs::Encoding;
use nu_errors::ShellError;
use nu_source::{Span, Tag};
@ -20,6 +22,13 @@ pub struct 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>) {
self.shells.lock().push(shell);
self.current_shell

View File

@ -48,6 +48,9 @@ macro_rules! nu {
let mut process = match Command::new($crate::fs::executable_path())
.env("PATH", paths_joined)
.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())
.stdin(Stdio::piped())
.stderr(Stdio::piped())
@ -153,7 +156,7 @@ macro_rules! nu_with_plugins {
pub fn read_std(std: &[u8]) -> String {
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", "");
out.replace("\n", "")
}

View File

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

View File

@ -1,12 +1,12 @@
use crate::fs::executable_path;
use std::collections::HashMap;
use super::EnvironmentVariable;
use crate::fs::{binaries as test_bins_path, executable_path};
use std::ffi::{OsStr, OsString};
use std::fmt;
use std::path::Path;
use std::process::{Command, ExitStatus};
pub trait Executable {
fn execute(&self) -> NuResult;
fn execute(&mut self) -> NuResult;
}
#[derive(Clone, Debug)]
@ -36,7 +36,7 @@ pub struct NuError {
#[derive(Clone, Debug)]
pub struct NuProcess {
pub arguments: Vec<OsString>,
pub environment_vars: HashMap<String, Option<OsString>>,
pub environment_vars: Vec<EnvironmentVariable>,
pub cwd: Option<OsString>,
}
@ -56,7 +56,7 @@ impl Default for NuProcess {
fn default() -> Self {
Self {
arguments: vec![],
environment_vars: HashMap::default(),
environment_vars: Vec::default(),
cwd: None,
}
}
@ -90,6 +90,21 @@ impl NuProcess {
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 {
command.arg(arg);
}

View File

@ -7,11 +7,27 @@ use std::path::{Path, PathBuf};
use std::str;
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> {
root: TempDir,
tests: String,
cwd: PathBuf,
config: PathBuf,
environment_vars: Vec<EnvironmentVariable>,
dirs: &'a Dirs,
}
@ -71,6 +87,7 @@ impl<'a> Playground<'a> {
tests: topic.to_string(),
cwd: nuplay_dir,
config: fixtures.join("playground/config/default.toml"),
environment_vars: Vec::default(),
dirs: &Dirs::default(),
};
@ -108,6 +125,12 @@ impl<'a> Playground<'a> {
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 {
self.config.to_str().expect("could not convert path.")
}
@ -116,6 +139,7 @@ impl<'a> Playground<'a> {
Director {
cwd: Some(self.dirs.test().into()),
config: Some(self.config.clone().into()),
environment_vars: self.environment_vars.clone(),
..Default::default()
}
}

View File

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