forked from extern/nushell
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:
parent
b243b3ee1d
commit
8fc8fc89aa
@ -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,27 +167,51 @@ 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 {
|
||||
let location = Some(AnchorLocation::File(
|
||||
config_file.to_string_lossy().to_string(),
|
||||
));
|
||||
let tag = Tag::unknown().anchored(location);
|
||||
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(),
|
||||
));
|
||||
|
||||
context.scope.add_var(
|
||||
"config-path",
|
||||
UntaggedValue::filepath(PathBuf::from(&config_file)).into_value(tag),
|
||||
);
|
||||
let tag = Tag::unknown().anchored(location);
|
||||
|
||||
EnvironmentSyncer::with_config(Box::new(NuConfig::with(Some(config_file.into()))))
|
||||
} else {
|
||||
EnvironmentSyncer::new()
|
||||
}
|
||||
context.scope.add_var(
|
||||
"config-path",
|
||||
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")]
|
||||
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) => {
|
||||
rl.add_history_entry(&line);
|
||||
let _ = rl.save_history(&history_path);
|
||||
maybe_print_errors(&context, Text::from(session_text.clone()));
|
||||
options.history(|file| {
|
||||
rl.add_history_entry(&line);
|
||||
let _ = rl.save_history(&file);
|
||||
});
|
||||
|
||||
evaluation_context::maybe_print_errors(&context, Text::from(session_text.clone()));
|
||||
}
|
||||
|
||||
LineResult::ClearHistory => {
|
||||
rl.clear_history();
|
||||
let _ = rl.save_history(&history_path);
|
||||
options.history(|file| {
|
||||
rl.clear_history();
|
||||
let _ = rl.save_history(&file);
|
||||
});
|
||||
}
|
||||
|
||||
LineResult::Error(line, err) => {
|
||||
rl.add_history_entry(&line);
|
||||
let _ = rl.save_history(&history_path);
|
||||
LineResult::Error(line, reason) => {
|
||||
options.history(|file| {
|
||||
rl.add_history_entry(&line);
|
||||
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
|
||||
|
12
crates/nu-cli/src/env/environment_syncer.rs
vendored
12
crates/nu-cli/src/env/environment_syncer.rs
vendored
@ -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![
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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.");
|
||||
|
@ -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),
|
||||
|
@ -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::*;
|
||||
|
@ -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?;
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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,15 +104,17 @@ 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) {
|
||||
(Status::LastModified(left), Status::LastModified(right)) => {
|
||||
let left = left.duration_since(std::time::UNIX_EPOCH)?;
|
||||
let right = (*right).duration_since(std::time::UNIX_EPOCH)?;
|
||||
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)?;
|
||||
|
||||
left != right
|
||||
}
|
||||
(_, _) => false,
|
||||
})
|
||||
left != right
|
||||
}
|
||||
(_, _) => false,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn var(&self, key: &str) -> Option<Value> {
|
||||
|
32
crates/nu-data/src/config/path.rs
Normal file
32
crates/nu-data/src/config/path.rs
Normal 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))
|
||||
}
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
@ -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())),
|
||||
})
|
||||
}
|
@ -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()?)])),
|
||||
})
|
||||
}
|
28
crates/nu-engine/src/env/basic_host.rs
vendored
28
crates/nu-engine/src/env/basic_host.rs
vendored
@ -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)]
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
})
|
||||
}
|
@ -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};
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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", "")
|
||||
}
|
||||
|
@ -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};
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
113
tests/shell/environment/in_sync.rs
Normal file
113
tests/shell/environment/in_sync.rs
Normal 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)
|
||||
);
|
||||
});
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
mod configuration;
|
||||
mod in_sync;
|
||||
mod nu_env;
|
||||
|
||||
pub mod support {
|
||||
|
Loading…
Reference in New Issue
Block a user