Refactor nu-cli/env* (#3041)

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

This reverts commit 8fc8fc89aa.

* Add tests

* Refactor .nu-env

* Change logic of Config write to logic of read()

* Fix reload always appends to old vars

* Fix reload always takes last_modified of global config

* Add reload_config in evaluation context

* Reload config after writing to it in cfg set / cfg set_into

* Add --no-history to cli options

* Use --no-history in tests

* Add comment about maybe_print_errors

* Get ctrl_exit var from context.global_config

* Use context.global_config in command "config"

* Add Readme in engine how env vars are now handled

* Update docs from autoenv command

* Move history_path from engine to nu_data

* Move load history out of if

* No let before return

* Add import for indexmap
This commit is contained in:
Leonhard Kipp 2021-03-31 07:52:34 +02:00 committed by GitHub
parent 4faaa5310e
commit c42b588782
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
70 changed files with 1615 additions and 1887 deletions

90
Cargo.lock generated
View file

@ -190,7 +190,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5"
dependencies = [
"quote 1.0.9",
"syn 1.0.62",
"syn 1.0.63",
]
[[package]]
@ -281,7 +281,7 @@ checksum = "d7d78656ba01f1b93024b7c3a0467f1608e4be67d725749fdcd7d2c7678fd7a2"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"syn 1.0.62",
"syn 1.0.63",
]
[[package]]
@ -326,7 +326,7 @@ checksum = "36ea56748e10732c49404c153638a15ec3d6211ec5ff35d9bb20e13b93576adf"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"syn 1.0.62",
"syn 1.0.63",
]
[[package]]
@ -564,9 +564,9 @@ checksum = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40"
[[package]]
name = "byte-unit"
version = "4.0.9"
version = "4.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c8758c32833faaae35b24a73d332e62d0528e89076ae841c63940e37008b153"
checksum = "b9520900471c3a9bbcfe0fd4c7b6bcfeff41b20a76cf91c59b7474b09be1ee27"
dependencies = [
"utf8-width",
]
@ -579,9 +579,9 @@ checksum = "bed57e2090563b83ba8f83366628ce535a7584c9afa4c9fc0612a03925c6df58"
[[package]]
name = "byteorder"
version = "1.4.2"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
@ -780,6 +780,12 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "common-path"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2382f75942f4b3be3690fe4f86365e9c853c1587d6ee58212cebf6e2a9ccd101"
[[package]]
name = "concurrent-queue"
version = "1.2.2"
@ -1104,7 +1110,7 @@ dependencies = [
"proc-macro2",
"quote 1.0.9",
"smallvec 1.6.1",
"syn 1.0.62",
"syn 1.0.63",
]
[[package]]
@ -1114,7 +1120,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e"
dependencies = [
"quote 1.0.9",
"syn 1.0.62",
"syn 1.0.63",
]
[[package]]
@ -1146,7 +1152,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8f45d9ad417bcef4817d614a501ab55cdd96a6fdb24f49aab89a54acfd66b19"
dependencies = [
"quote 1.0.9",
"syn 1.0.62",
"syn 1.0.63",
]
[[package]]
@ -1254,7 +1260,7 @@ checksum = "3418329ca0ad70234b9735dc4ceed10af4df60eff9c8e7b06cb5e520d92c3535"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"syn 1.0.62",
"syn 1.0.63",
]
[[package]]
@ -1276,7 +1282,7 @@ checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"syn 1.0.62",
"syn 1.0.63",
]
[[package]]
@ -1576,7 +1582,7 @@ checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"syn 1.0.62",
"syn 1.0.63",
"synstructure",
]
@ -1855,7 +1861,7 @@ dependencies = [
"proc-macro-hack",
"proc-macro2",
"quote 1.0.9",
"syn 1.0.62",
"syn 1.0.63",
]
[[package]]
@ -2013,7 +2019,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2",
"quote 1.0.9",
"syn 1.0.62",
"syn 1.0.63",
]
[[package]]
@ -2206,7 +2212,7 @@ dependencies = [
"markup5ever",
"proc-macro2",
"quote 1.0.9",
"syn 1.0.62",
"syn 1.0.63",
]
[[package]]
@ -3340,6 +3346,7 @@ dependencies = [
"bigdecimal",
"byte-unit",
"chrono",
"common-path",
"derive-new",
"directories-next",
"dirs-next",
@ -3358,6 +3365,7 @@ dependencies = [
"num-traits 0.2.14",
"query_interface",
"serde 1.0.124",
"sha2 0.9.3",
"toml",
"users",
]
@ -3366,6 +3374,7 @@ dependencies = [
name = "nu-engine"
version = "0.29.0"
dependencies = [
"ansi_term 0.12.1",
"async-recursion",
"async-trait",
"bytes 0.5.6",
@ -3373,6 +3382,7 @@ dependencies = [
"derive-new",
"dirs-next",
"dunce",
"dyn-clone",
"encoding_rs",
"filesize",
"fs_extra",
@ -4015,9 +4025,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "open"
version = "1.5.1"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2033f93630dd4b04768ecf5e16bcd3002a89e1e1dbef375bf290dd67e2b7a4d"
checksum = "a7e9f1bdf15cd1f5a00cc9002a733a6ee6d0ff562491852d59652471c4a389f7"
dependencies = [
"which",
"winapi 0.3.9",
@ -4219,7 +4229,7 @@ dependencies = [
"proc-macro-hack",
"proc-macro2",
"quote 1.0.9",
"syn 1.0.62",
"syn 1.0.63",
]
[[package]]
@ -4257,7 +4267,7 @@ checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"syn 1.0.62",
"syn 1.0.63",
]
[[package]]
@ -4268,7 +4278,7 @@ checksum = "758669ae3558c6f74bd2a18b41f7ac0b5a195aea6639d6a9b5e5d1ad5ba24c0b"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"syn 1.0.62",
"syn 1.0.63",
]
[[package]]
@ -4403,7 +4413,7 @@ dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote 1.0.9",
"syn 1.0.62",
"syn 1.0.63",
"version_check",
]
@ -4517,7 +4527,7 @@ checksum = "b22a693222d716a9587786f37ac3f6b4faedb5b80c23914e7303ff5a1d8016e9"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"syn 1.0.62",
"syn 1.0.63",
]
[[package]]
@ -4939,7 +4949,7 @@ dependencies = [
"proc-macro2",
"quote 1.0.9",
"rust-embed-utils",
"syn 1.0.62",
"syn 1.0.63",
"walkdir",
]
@ -5235,7 +5245,7 @@ checksum = "1800f7693e94e186f5e25a28291ae1570da908aff7d97a095dec1e56ff99069b"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"syn 1.0.62",
"syn 1.0.63",
]
[[package]]
@ -5346,7 +5356,7 @@ checksum = "b2acd6defeddb41eb60bb468f8825d0cfd0c2a76bc03bfd235b6a1dc4f6a1ad5"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"syn 1.0.62",
"syn 1.0.63",
]
[[package]]
@ -5511,7 +5521,7 @@ checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"syn 1.0.62",
"syn 1.0.63",
]
[[package]]
@ -5585,7 +5595,7 @@ dependencies = [
"quote 1.0.9",
"serde 1.0.124",
"serde_derive",
"syn 1.0.62",
"syn 1.0.63",
]
[[package]]
@ -5601,7 +5611,7 @@ dependencies = [
"serde_derive",
"serde_json",
"sha1 0.6.0",
"syn 1.0.62",
"syn 1.0.63",
]
[[package]]
@ -5740,9 +5750,9 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.62"
version = "1.0.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "123a78a3596b24fee53a6464ce52d8ecbf62241e6294c7e7fe12086cd161f512"
checksum = "8fd9bc7ccc2688b3344c2f48b9b546648b25ce0b20fc717ee7fa7981a8ca9717"
dependencies = [
"proc-macro2",
"quote 1.0.9",
@ -5766,7 +5776,7 @@ checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"syn 1.0.62",
"syn 1.0.63",
"unicode-xid 0.2.1",
]
@ -5913,7 +5923,7 @@ checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"syn 1.0.62",
"syn 1.0.63",
]
[[package]]
@ -5991,7 +6001,7 @@ dependencies = [
"proc-macro2",
"quote 1.0.9",
"standback",
"syn 1.0.62",
"syn 1.0.63",
]
[[package]]
@ -6151,7 +6161,7 @@ checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"syn 1.0.62",
"syn 1.0.63",
]
[[package]]
@ -6340,13 +6350,13 @@ dependencies = [
[[package]]
name = "tracing-attributes"
version = "0.1.13"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8a9bd1db7706f2373a190b0d067146caa39350c486f3d455b0e33b431f94c07"
checksum = "41768be5b9f3489491825f56f01f25290aa1d3e7cc97e182d4d34360493ba6fa"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"syn 1.0.62",
"syn 1.0.63",
]
[[package]]
@ -6686,7 +6696,7 @@ dependencies = [
"log 0.4.14",
"proc-macro2",
"quote 1.0.9",
"syn 1.0.62",
"syn 1.0.63",
"wasm-bindgen-shared",
]
@ -6736,7 +6746,7 @@ checksum = "cc053ec74d454df287b9374ee8abb36ffd5acb95ba87da3ba5b7d3fe20eb401e"
dependencies = [
"proc-macro2",
"quote 1.0.9",
"syn 1.0.62",
"syn 1.0.63",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]

View file

@ -1,6 +1,6 @@
use crate::line_editor::configure_ctrl_c;
use nu_command::commands::default_context::create_default_context;
use nu_engine::{evaluation_context, run_block, script::run_script_standalone, EvaluationContext};
use nu_engine::{maybe_print_errors, run_block, script::run_script_standalone, EvaluationContext};
#[allow(unused_imports)]
pub(crate) use nu_engine::script::{process_script, LineResult};
@ -13,8 +13,7 @@ use crate::line_editor::{
#[allow(unused_imports)]
use nu_data::config;
use nu_data::config::{Conf, NuConfig};
use nu_source::{AnchorLocation, Tag, Text};
use nu_source::{Tag, Text};
use nu_stream::InputStream;
use std::ffi::{OsStr, OsString};
#[allow(unused_imports)]
@ -23,10 +22,9 @@ use std::sync::atomic::Ordering;
#[cfg(feature = "rustyline-support")]
use rustyline::{self, error::ReadlineError};
use crate::EnvironmentSyncer;
use nu_errors::ShellError;
use nu_parser::ParserScope;
use nu_protocol::{hir::ExternalRedirection, UntaggedValue, Value};
use nu_protocol::{hir::ExternalRedirection, ConfigPath, UntaggedValue, Value};
use log::trace;
use std::error::Error;
@ -35,10 +33,9 @@ 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>,
pub save_history: bool,
}
impl Default for Options {
@ -51,20 +48,9 @@ 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)
save_history: true,
}
}
}
@ -137,25 +123,17 @@ pub fn search_paths() -> Vec<std::path::PathBuf> {
search_paths
}
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, &mut options);
let config = syncer.get_config();
pub async fn run_script_file(options: Options) -> Result<(), Box<dyn Error>> {
let context = create_default_context(false)?;
context.configure(&config, |_, ctx| {
syncer.load_environment();
syncer.sync_env_vars(ctx);
syncer.sync_path_vars(ctx);
if let Err(reason) = syncer.autoenv(ctx) {
ctx.with_host(|host| host.print_err(reason, &Text::from("")));
if let Some(cfg) = options.config {
load_cfg_as_global_cfg(&context, PathBuf::from(cfg)).await;
} else {
load_global_cfg(&context).await;
}
let _ = register_plugins(ctx);
let _ = configure_ctrl_c(ctx);
});
let _ = run_startup_commands(&mut context, &config).await;
let _ = register_plugins(&context);
let _ = configure_ctrl_c(&context);
let script = options
.scripts
@ -167,76 +145,18 @@ pub async fn run_script_file(mut options: Options) -> Result<(), Box<dyn Error>>
Ok(())
}
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(
"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,
mut options: Options,
) -> Result<(), Box<dyn Error>> {
let mut syncer = create_environment_syncer(&context, &mut options);
pub async fn cli(context: EvaluationContext, options: Options) -> Result<(), Box<dyn Error>> {
let _ = configure_ctrl_c(&context);
let configuration = syncer.get_config();
let mut rl = default_rustyline_editor_configuration();
context.configure(&configuration, |config, ctx| {
syncer.load_environment();
syncer.sync_env_vars(ctx);
syncer.sync_path_vars(ctx);
if let Err(reason) = syncer.autoenv(ctx) {
ctx.with_host(|host| host.print_err(reason, &Text::from("")));
}
let _ = configure_ctrl_c(ctx);
let _ = configure_rustyline_editor(&mut rl, config);
let helper = Some(nu_line_editor_helper(ctx, config));
rl.set_helper(helper);
});
// start time for command duration
// start time for running startup scripts (this metric includes loading of the cfg, but w/e)
let startup_commands_start_time = std::time::Instant::now();
// run the startup commands
let _ = run_startup_commands(&mut context, &configuration).await;
if let Some(cfg) = options.config {
load_cfg_as_global_cfg(&context, PathBuf::from(cfg)).await;
} else {
load_global_cfg(&context).await;
}
// Store cmd duration in an env var
context.scope.add_env_var(
"CMD_DURATION",
@ -247,20 +167,43 @@ pub async fn cli(
startup_commands_start_time.elapsed()
);
//Configure rustyline
let mut rl = default_rustyline_editor_configuration();
let history_path = if let Some(cfg) = &context.configs.lock().global_config {
let _ = configure_rustyline_editor(&mut rl, cfg);
let helper = Some(nu_line_editor_helper(&context, cfg));
rl.set_helper(helper);
nu_data::config::path::history_path(cfg)
} else {
nu_data::config::path::default_history_path()
};
// Don't load history if it's not necessary
if options.save_history {
let _ = rl.load_history(&history_path);
}
//set vars from cfg if present
let (skip_welcome_message, prompt) = if let Some(cfg) = &context.configs.lock().global_config {
(
cfg.var("skip_welcome_message")
.map(|x| x.is_true())
.unwrap_or(false),
cfg.var("prompt"),
)
} else {
(false, None)
};
//Check whether dir we start in contains local cfg file and if so load it.
load_local_cfg_if_present(&context).await;
// Give ourselves a scope to work in
context.scope.enter_scope();
options.history(|file| {
let _ = rl.load_history(&file);
});
let mut session_text = String::new();
let mut line_start: usize = 0;
let skip_welcome_message = configuration
.var("skip_welcome_message")
.map(|x| x.is_true())
.unwrap_or(false);
if !skip_welcome_message {
println!(
"Welcome to Nushell {} (type 'help' for more info)",
@ -284,11 +227,10 @@ pub async fn cli(
let cwd = context.shell_manager.path();
let colored_prompt = {
if let Some(prompt) = configuration.var("prompt") {
if let Some(prompt) = &prompt {
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);
@ -305,10 +247,7 @@ pub async fn cli(
Ok(result) => match result.collect_string(Tag::unknown()).await {
Ok(string_result) => {
let errors = context.get_errors();
evaluation_context::maybe_print_errors(
&context,
Text::from(prompt_line),
);
maybe_print_errors(&context, Text::from(prompt_line));
context.clear_errors();
if !errors.is_empty() {
@ -381,54 +320,50 @@ pub async fn cli(
.scope
.add_env_var("CMD_DURATION", format!("{:?}", cmd_start_time.elapsed()));
// Check the config to see if we need to update the path
// TODO: make sure config is cached so we don't path this load every call
// FIXME: we probably want to be a bit more graceful if we can't set the environment
context.configure(&configuration, |config, ctx| {
if syncer.did_config_change() {
syncer.reload();
syncer.sync_env_vars(ctx);
syncer.sync_path_vars(ctx);
}
if let Err(reason) = syncer.autoenv(ctx) {
ctx.with_host(|host| host.print_err(reason, &Text::from("")));
}
let _ = configure_rustyline_editor(&mut rl, config);
});
match line {
LineResult::Success(line) => {
options.history(|file| {
if options.save_history {
rl.add_history_entry(&line);
let _ = rl.save_history(&file);
});
evaluation_context::maybe_print_errors(&context, Text::from(session_text.clone()));
let _ = rl.save_history(&history_path);
}
maybe_print_errors(&context, Text::from(session_text.clone()));
}
LineResult::ClearHistory => {
options.history(|file| {
if options.save_history {
rl.clear_history();
let _ = rl.save_history(&file);
});
let _ = rl.save_history(&history_path);
}
}
LineResult::Error(line, reason) => {
options.history(|file| {
LineResult::Error(line, err) => {
if options.save_history {
rl.add_history_entry(&line);
let _ = rl.save_history(&file);
});
let _ = rl.save_history(&history_path);
}
context.with_host(|host| host.print_err(reason, &Text::from(session_text.clone())));
context
.host
.lock()
.print_err(err, &Text::from(session_text.clone()));
// I am not so sure, we don't need maybe_print_errors here (as we printed an err
// above), because maybe_print_errors also clears the errors.
// TODO Analyze where above err comes from, and whether we need to clear
// context.errors here
// Or just be consistent and return errors always in context.errors...
maybe_print_errors(&context, Text::from(session_text.clone()));
}
LineResult::CtrlC => {
let config_ctrlc_exit = configuration
.var("ctrlc_exit")
.map(|s| s.value.is_true())
let config_ctrlc_exit = context
.configs
.lock()
.global_config
.as_ref()
.map(|cfg| cfg.var("ctrlc_exit"))
.flatten()
.map(|ctrl_c| ctrl_c.is_true())
.unwrap_or(false); // default behavior is to allow CTRL-C spamming similar to other shells
if !config_ctrlc_exit {
@ -436,10 +371,9 @@ pub async fn cli(
}
if ctrlcbreak {
options.history(|file| {
let _ = rl.save_history(&file);
});
if options.save_history {
let _ = rl.save_history(&history_path);
}
std::process::exit(0);
} else {
context.with_host(|host| host.stdout("CTRL-C pressed (again to quit)"));
@ -463,14 +397,56 @@ pub async fn cli(
}
// we are ok if we can not save history
options.history(|file| {
let _ = rl.save_history(&file);
});
if options.save_history {
let _ = rl.save_history(&history_path);
}
Ok(())
}
pub fn register_plugins(context: &mut EvaluationContext) -> Result<(), ShellError> {
pub async fn load_local_cfg_if_present(context: &EvaluationContext) {
trace!("Loading local cfg if present");
match config::loadable_cfg_exists_in_dir(PathBuf::from(context.shell_manager.path())) {
Ok(Some(cfg_path)) => {
if let Err(err) = context.load_config(&ConfigPath::Local(cfg_path)).await {
context.host.lock().print_err(err, &Text::from(""))
}
}
Err(e) => {
//Report error while checking for local cfg file
context.host.lock().print_err(e, &Text::from(""))
}
Ok(None) => {
//No local cfg file present in start dir
}
}
}
async fn load_cfg_as_global_cfg(context: &EvaluationContext, path: PathBuf) {
if let Err(err) = context.load_config(&ConfigPath::Global(path.clone())).await {
context.host.lock().print_err(err, &Text::from(""));
} else {
//TODO current commands assume to find path to global cfg file under config-path
//TODO use newly introduced nuconfig::file_path instead
context.scope.add_var(
"config-path",
UntaggedValue::filepath(path).into_untagged_value(),
);
}
}
pub async fn load_global_cfg(context: &EvaluationContext) {
match config::default_path() {
Ok(path) => {
load_cfg_as_global_cfg(context, path).await;
}
Err(e) => {
context.host.lock().print_err(e, &Text::from(""));
}
}
}
pub fn register_plugins(context: &EvaluationContext) -> Result<(), ShellError> {
if let Ok(plugins) = nu_engine::plugin::build_plugin::scan(search_paths()) {
context.add_commands(
plugins
@ -483,34 +459,6 @@ pub fn register_plugins(context: &mut EvaluationContext) -> Result<(), ShellErro
Ok(())
}
async fn run_startup_commands(
context: &mut EvaluationContext,
config: &dyn nu_data::config::Conf,
) -> Result<(), ShellError> {
if let Some(commands) = config.var("startup") {
match commands {
Value {
value: UntaggedValue::Table(pipelines),
..
} => {
let mut script_file = String::new();
for pipeline in pipelines {
script_file.push_str(&pipeline.as_string()?);
script_file.push('\n');
}
let _ = run_script_standalone(script_file, false, context, false).await;
}
_ => {
return Err(ShellError::untagged_runtime_error(
"expected a table of pipeline strings as startup commands",
));
}
}
}
Ok(())
}
pub async fn parse_and_eval(line: &str, ctx: &EvaluationContext) -> Result<String, ShellError> {
// FIXME: do we still need this?
let line = if let Some(s) = line.strip_suffix('\n') {
@ -528,8 +476,6 @@ pub async fn parse_and_eval(line: &str, ctx: &EvaluationContext) -> Result<Strin
}
let input_stream = InputStream::empty();
let env = ctx.get_env();
ctx.scope.add_env(env);
let result = run_block(&classified_block, ctx, input_stream).await;
ctx.scope.exit_scope();
@ -555,14 +501,14 @@ fn current_branch() -> String {
#[cfg(test)]
mod tests {
use nu_engine::EvaluationContext;
use nu_engine::basic_evaluation_context;
#[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 = EvaluationContext::basic().unwrap();
let context = basic_evaluation_context().unwrap();
let _ = nu_parser::classify_block(&lite_block, &context.scope);
}
true

View file

@ -1,3 +0,0 @@
pub(crate) mod directory_specific_environment;
pub(crate) mod environment;
pub(crate) mod environment_syncer;

View file

@ -1,259 +0,0 @@
use indexmap::{IndexMap, IndexSet};
use nu_command::commands::autoenv;
use nu_errors::ShellError;
use serde::Deserialize;
use std::env::*;
use std::process::Command;
use std::{
ffi::OsString,
fmt::Debug,
path::{Path, PathBuf},
};
//Tests reside in /nushell/tests/shell/pipeline/commands/internal.rs
type EnvKey = String;
type EnvVal = OsString;
#[derive(Debug, Default)]
pub struct DirectorySpecificEnvironment {
pub last_seen_directory: PathBuf,
//If an environment var has been added from a .nu in a directory, we track it here so we can remove it when the user leaves the directory.
//If setting the var overwrote some value, we save the old value in an option so we can restore it later.
added_vars: IndexMap<PathBuf, IndexMap<EnvKey, Option<EnvVal>>>,
//We track directories that we have read .nu-env from. This is different from the keys in added_vars since sometimes a file only wants to run scripts.
visited_dirs: IndexSet<PathBuf>,
exitscripts: IndexMap<PathBuf, Vec<String>>,
}
#[derive(Deserialize, Debug, Default)]
pub struct NuEnvDoc {
pub env: Option<IndexMap<String, String>>,
pub scriptvars: Option<IndexMap<String, String>>,
pub scripts: Option<IndexMap<String, Vec<String>>>,
pub entryscripts: Option<Vec<String>>,
pub exitscripts: Option<Vec<String>>,
}
impl DirectorySpecificEnvironment {
pub fn new() -> DirectorySpecificEnvironment {
let root_dir = if cfg!(target_os = "windows") {
PathBuf::from("c:\\")
} else {
PathBuf::from("/")
};
DirectorySpecificEnvironment {
last_seen_directory: root_dir,
added_vars: IndexMap::new(),
visited_dirs: IndexSet::new(),
exitscripts: IndexMap::new(),
}
}
fn toml_if_trusted(&mut self, nu_env_file: &Path) -> Result<NuEnvDoc, ShellError> {
let content = std::fs::read(&nu_env_file)?;
if autoenv::file_is_trusted(&nu_env_file, &content)? {
let mut doc: NuEnvDoc = toml::de::from_slice(&content)
.map_err(|e| ShellError::untagged_runtime_error(format!("{:?}", e)))?;
if let Some(scripts) = doc.scripts.as_ref() {
for (k, v) in scripts {
if k == "entryscripts" {
doc.entryscripts = Some(v.clone());
} else if k == "exitscripts" {
doc.exitscripts = Some(v.clone());
}
}
}
return Ok(doc);
}
Err(ShellError::untagged_runtime_error(
format!("{:?} is untrusted. Run 'autoenv trust {:?}' to trust it.\nThis needs to be done after each change to the file.", nu_env_file, nu_env_file.parent().unwrap_or_else(|| &Path::new("")))))
}
pub fn maintain_autoenv(&mut self) -> Result<(), ShellError> {
let mut dir = current_dir()?;
if self.last_seen_directory == dir {
return Ok(());
}
//We track which keys we set as we go up the directory hierarchy, so that we don't overwrite a value we set in a subdir.
let mut added_keys = IndexSet::new();
let mut new_visited_dirs = IndexSet::new();
let mut popped = true;
while popped {
let nu_env_file = dir.join(".nu-env");
if nu_env_file.exists() && !self.visited_dirs.contains(&dir) {
let nu_env_doc = self.toml_if_trusted(&nu_env_file)?;
//add regular variables from the [env section]
if let Some(env) = nu_env_doc.env {
for (env_key, env_val) in env {
self.maybe_add_key(&mut added_keys, &dir, &env_key, &env_val);
}
}
//Add variables that need to evaluate scripts to run, from [scriptvars] section
if let Some(sv) = nu_env_doc.scriptvars {
for (key, script) in sv {
self.maybe_add_key(
&mut added_keys,
&dir,
&key,
value_from_script(&script)?.as_str(),
);
}
}
if let Some(es) = nu_env_doc.entryscripts {
for s in es {
run(s.as_str(), None)?;
}
}
if let Some(es) = nu_env_doc.exitscripts {
self.exitscripts.insert(dir.clone(), es);
}
}
new_visited_dirs.insert(dir.clone());
popped = dir.pop();
}
//Time to clear out vars set by directories that we have left.
let mut new_vars = IndexMap::new();
for (dir, dirmap) in self.added_vars.drain(..) {
if new_visited_dirs.contains(&dir) {
new_vars.insert(dir, dirmap);
} else {
for (k, v) in dirmap {
if let Some(v) = v {
std::env::set_var(k, v);
} else {
std::env::remove_var(k);
}
}
}
}
//Run exitscripts, can not be done in same loop as new vars as some files can contain only exitscripts
let mut new_exitscripts = IndexMap::new();
for (dir, scripts) in self.exitscripts.drain(..) {
if new_visited_dirs.contains(&dir) {
new_exitscripts.insert(dir, scripts);
} else {
for s in scripts {
run(s.as_str(), Some(&dir))?;
}
}
}
self.visited_dirs = new_visited_dirs;
self.exitscripts = new_exitscripts;
self.added_vars = new_vars;
self.last_seen_directory = current_dir()?;
Ok(())
}
pub fn maybe_add_key(
&mut self,
seen_vars: &mut IndexSet<EnvKey>,
dir: &Path,
key: &str,
val: &str,
) {
//This condition is to make sure variables in parent directories don't overwrite variables set by subdirectories.
if !seen_vars.contains(key) {
seen_vars.insert(key.to_string());
self.added_vars
.entry(PathBuf::from(dir))
.or_insert(IndexMap::new())
.insert(key.to_string(), var_os(key));
std::env::set_var(key, val);
}
}
// If the user recently ran autoenv untrust on a file, we clear the environment variables it set and make sure to not run any possible exitscripts.
pub fn clear_recently_untrusted_file(&mut self) -> Result<(), ShellError> {
// Figure out which file was untrusted
// Remove all vars set by it
let current_trusted_files: IndexSet<PathBuf> = autoenv::read_trusted()?
.files
.iter()
.map(|(k, _)| PathBuf::from(k))
.collect();
// We figure out which file(s) the user untrusted by taking the set difference of current trusted files in .config/nu/nu-env.toml and the files tracked by self.added_env_vars
// If a file is in self.added_env_vars but not in nu-env.toml, it was just untrusted.
let untrusted_files: IndexSet<PathBuf> = self
.added_vars
.iter()
.filter_map(|(path, _)| {
if !current_trusted_files.contains(path) {
return Some(path.clone());
}
None
})
.collect();
for path in untrusted_files {
if let Some(added_keys) = self.added_vars.get(&path) {
for (key, _) in added_keys {
remove_var(key);
}
}
self.exitscripts.remove(&path);
self.added_vars.remove(&path);
}
Ok(())
}
}
fn run(cmd: &str, dir: Option<&PathBuf>) -> Result<(), ShellError> {
if cfg!(target_os = "windows") {
if let Some(dir) = dir {
let command = format!("cd {} & {}", dir.to_string_lossy(), cmd);
Command::new("cmd")
.args(&["/C", command.as_str()])
.output()?
} else {
Command::new("cmd").args(&["/C", cmd]).output()?
}
} else if let Some(dir) = dir {
// FIXME: When nu scripting is added, cding like might not be a good idea. If nu decides to execute entryscripts when entering the dir this way, it will cause troubles.
// For now only standard shell scripts are used, so this is an issue for the future.
Command::new("sh")
.arg("-c")
.arg(format!("cd {:?}; {}", dir, cmd))
.output()?
} else {
Command::new("sh").arg("-c").arg(&cmd).output()?
};
Ok(())
}
fn value_from_script(cmd: &str) -> Result<String, ShellError> {
let command = if cfg!(target_os = "windows") {
Command::new("cmd").args(&["/C", cmd]).output()?
} else {
Command::new("sh").arg("-c").arg(&cmd).output()?
};
if command.stdout.is_empty() {
return Err(ShellError::untagged_runtime_error(format!(
"{:?} did not return any output",
cmd
)));
}
let response = std::str::from_utf8(&command.stdout[..command.stdout.len()]).map_err(|e| {
ShellError::untagged_runtime_error(format!(
"Couldn't parse stdout from command {:?}: {:?}",
command, e
))
})?;
Ok(response.trim().to_string())
}

View file

@ -1,274 +0,0 @@
use crate::env::directory_specific_environment::*;
use indexmap::{indexmap, IndexSet};
use nu_data::config::Conf;
use nu_engine::Env;
use nu_errors::ShellError;
use nu_protocol::{UntaggedValue, Value};
#[derive(Debug, Default)]
pub struct Environment {
environment_vars: Option<Value>,
path_vars: Option<Value>,
pub autoenv: DirectorySpecificEnvironment,
}
impl Environment {
pub fn new() -> Environment {
Environment {
environment_vars: None,
path_vars: None,
autoenv: DirectorySpecificEnvironment::new(),
}
}
pub fn from_config<T: Conf>(configuration: &T) -> Environment {
let env = configuration.env();
let path = configuration.path();
Environment {
environment_vars: env,
path_vars: path,
autoenv: DirectorySpecificEnvironment::new(),
}
}
pub fn autoenv(&mut self, reload_trusted: bool) -> Result<(), ShellError> {
self.autoenv.maintain_autoenv()?;
if reload_trusted {
self.autoenv.clear_recently_untrusted_file()?;
}
Ok(())
}
pub fn morph<T: Conf>(&mut self, configuration: &T) {
self.environment_vars = configuration.env();
self.path_vars = configuration.path();
}
}
impl Env for Environment {
fn env(&self) -> Option<Value> {
if let Some(vars) = &self.environment_vars {
return Some(vars.clone());
}
None
}
fn path(&self) -> Option<Value> {
if let Some(vars) = &self.path_vars {
return Some(vars.clone());
}
None
}
fn add_env(&mut self, key: &str, value: &str) {
let value = UntaggedValue::string(value);
let new_envs = {
if let Some(Value {
value: UntaggedValue::Row(ref envs),
ref tag,
}) = self.environment_vars
{
let mut new_envs = envs.clone();
if !new_envs.contains_key(key) {
new_envs.insert_data_at_key(key, value.into_value(tag.clone()));
}
Value {
value: UntaggedValue::Row(new_envs),
tag: tag.clone(),
}
} else {
UntaggedValue::Row(indexmap! { key.into() => value.into_untagged_value() }.into())
.into_untagged_value()
}
};
self.environment_vars = Some(new_envs);
}
fn add_path(&mut self, paths: std::ffi::OsString) {
let new_paths = {
if let Some(Value {
value: UntaggedValue::Table(ref current_paths),
ref tag,
}) = self.path_vars
{
let mut new_paths = current_paths.clone();
let new_path_candidates = std::env::split_paths(&paths).map(|path| {
UntaggedValue::string(path.to_string_lossy()).into_value(tag.clone())
});
new_paths.extend(new_path_candidates);
let paths: IndexSet<Value> = new_paths.into_iter().collect();
Value {
value: UntaggedValue::Table(paths.into_iter().collect()),
tag: tag.clone(),
}
} else {
let p = paths.into_string().unwrap_or_else(|_| String::from(""));
let p = UntaggedValue::string(p).into_untagged_value();
UntaggedValue::Table(vec![p]).into_untagged_value()
}
};
self.path_vars = Some(new_paths);
}
}
#[cfg(test)]
mod tests {
use super::{Env, Environment};
use nu_data::config::{tests::FakeConfig, Conf};
use nu_protocol::UntaggedValue;
use nu_test_support::fs::Stub::FileWithContent;
use nu_test_support::playground::Playground;
#[test]
fn picks_up_environment_variables_from_configuration() {
Playground::setup("environment_test_1", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent(
"configuration.toml",
r#"
[env]
mosquetero_1 = "Andrés N. Robalino"
mosquetero_2 = "Jonathan Turner"
mosquetero_3 = "Yehuda katz"
mosquetero_4 = "Jason Gedge"
"#,
)]);
let mut file = dirs.test().clone();
file.push("configuration.toml");
let fake_config = FakeConfig::new(&file);
let actual = Environment::from_config(&fake_config);
assert_eq!(actual.env(), fake_config.env());
});
}
#[test]
fn picks_up_path_variables_from_configuration() {
Playground::setup("environment_test_2", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent(
"configuration.toml",
r#"
path = ["/Users/andresrobalino/.volta/bin", "/users/mosqueteros/bin"]
"#,
)]);
let mut file = dirs.test().clone();
file.push("configuration.toml");
let fake_config = FakeConfig::new(&file);
let actual = Environment::from_config(&fake_config);
assert_eq!(actual.path(), fake_config.path());
});
}
#[test]
fn updates_env_variable() {
Playground::setup("environment_test_3", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent(
"configuration.toml",
r#"
[env]
SHELL = "/usr/bin/you_already_made_the_nu_choice"
"#,
)]);
let mut file = dirs.test().clone();
file.push("configuration.toml");
let fake_config = FakeConfig::new(&file);
let mut actual = Environment::from_config(&fake_config);
actual.add_env("USER", "NUNO");
assert_eq!(
actual.env(),
Some(
UntaggedValue::row(
indexmap! {
"USER".into() => UntaggedValue::string("NUNO").into_untagged_value(),
"SHELL".into() => UntaggedValue::string("/usr/bin/you_already_made_the_nu_choice").into_untagged_value(),
}
).into_untagged_value()
)
);
});
}
#[test]
fn does_not_update_env_variable_if_it_exists() {
Playground::setup("environment_test_4", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent(
"configuration.toml",
r#"
[env]
SHELL = "/usr/bin/you_already_made_the_nu_choice"
"#,
)]);
let mut file = dirs.test().clone();
file.push("configuration.toml");
let fake_config = FakeConfig::new(&file);
let mut actual = Environment::from_config(&fake_config);
actual.add_env("SHELL", "/usr/bin/sh");
assert_eq!(
actual.env(),
Some(
UntaggedValue::row(
indexmap! {
"SHELL".into() => UntaggedValue::string("/usr/bin/you_already_made_the_nu_choice").into_untagged_value(),
}
).into_untagged_value()
)
);
});
}
#[test]
fn updates_path_variable() {
Playground::setup("environment_test_5", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent(
"configuration.toml",
r#"
path = ["/Users/andresrobalino/.volta/bin", "/users/mosqueteros/bin"]
"#,
)]);
let mut file = dirs.test().clone();
file.push("configuration.toml");
let fake_config = FakeConfig::new(&file);
let mut actual = Environment::from_config(&fake_config);
actual.add_path(std::ffi::OsString::from("/path/to/be/added"));
assert_eq!(
actual.path(),
Some(
UntaggedValue::table(&[
UntaggedValue::string("/Users/andresrobalino/.volta/bin")
.into_untagged_value(),
UntaggedValue::string("/users/mosqueteros/bin").into_untagged_value(),
UntaggedValue::string("/path/to/be/added").into_untagged_value(),
])
.into_untagged_value()
)
);
});
}
}

View file

@ -1,617 +0,0 @@
use crate::env::environment::Environment;
use nu_data::config::{Conf, NuConfig};
use nu_engine::Env;
use nu_engine::EvaluationContext;
use nu_errors::ShellError;
use parking_lot::Mutex;
use std::sync::{atomic::Ordering, Arc};
pub struct EnvironmentSyncer {
pub env: Arc<Mutex<Box<Environment>>>,
pub config: Arc<Mutex<Box<dyn Conf>>>,
}
impl Default for EnvironmentSyncer {
fn default() -> Self {
Self::new()
}
}
impl EnvironmentSyncer {
pub fn with_config(config: Box<dyn Conf>) -> Self {
EnvironmentSyncer {
env: Arc::new(Mutex::new(Box::new(Environment::new()))),
config: Arc::new(Mutex::new(config)),
}
}
pub fn new() -> EnvironmentSyncer {
EnvironmentSyncer {
env: Arc::new(Mutex::new(Box::new(Environment::new()))),
config: Arc::new(Mutex::new(Box::new(NuConfig::new()))),
}
}
#[cfg(test)]
pub fn set_config(&mut self, config: Box<dyn Conf>) {
self.config = Arc::new(Mutex::new(config));
}
pub fn get_config(&self) -> Box<dyn Conf> {
let config = self.config.lock();
config.clone_box()
}
pub fn load_environment(&mut self) {
let config = self.config.lock();
self.env = Arc::new(Mutex::new(Box::new(Environment::from_config(&*config))));
}
pub fn did_config_change(&mut self) -> bool {
let config = self.config.lock();
config.is_modified().unwrap_or(false)
}
pub fn reload(&mut self) {
let mut config = self.config.lock();
config.reload();
let mut environment = self.env.lock();
environment.morph(&*config);
}
pub fn autoenv(&self, ctx: &mut EvaluationContext) -> Result<(), ShellError> {
let mut environment = self.env.lock();
let recently_used = ctx
.user_recently_used_autoenv_untrust
.load(Ordering::SeqCst);
let auto = environment.autoenv(recently_used);
ctx.user_recently_used_autoenv_untrust
.store(false, Ordering::SeqCst);
auto
}
pub fn sync_env_vars(&mut self, ctx: &mut EvaluationContext) {
let mut environment = self.env.lock();
if environment.env().is_some() {
for (name, value) in ctx.with_host(|host| host.vars()) {
if name != "path" && name != "PATH" {
// account for new env vars present in the current session
// that aren't loaded from config.
environment.add_env(&name, &value);
// clear the env var from the session
// we are about to replace them
ctx.with_host(|host| host.env_rm(std::ffi::OsString::from(name)));
}
}
if let Some(variables) = environment.env() {
for var in variables.row_entries() {
if let Ok(string) = var.1.as_string() {
ctx.with_host(|host| {
host.env_set(
std::ffi::OsString::from(var.0),
std::ffi::OsString::from(&string),
)
});
ctx.scope.add_env_var_to_base(var.0, string);
}
}
}
}
}
pub fn sync_path_vars(&mut self, ctx: &mut EvaluationContext) {
let mut environment = self.env.lock();
if environment.path().is_some() {
let native_paths = ctx.with_host(|host| host.env_get(std::ffi::OsString::from("PATH")));
if let Some(native_paths) = native_paths {
environment.add_path(native_paths);
ctx.with_host(|host| {
host.env_rm(std::ffi::OsString::from("PATH"));
});
}
if let Some(new_paths) = environment.path() {
let prepared = std::env::join_paths(
new_paths
.table_entries()
.map(|p| p.as_string())
.filter_map(Result::ok),
);
if let Ok(paths_ready) = prepared {
ctx.with_host(|host| {
host.env_set(
std::ffi::OsString::from("PATH"),
std::ffi::OsString::from(&paths_ready),
);
});
ctx.scope
.add_env_var_to_base("PATH", paths_ready.to_string_lossy().to_string());
}
}
}
}
#[cfg(test)]
pub fn clear_env_vars(&mut self, ctx: &mut EvaluationContext) {
for (key, _value) in ctx.with_host(|host| host.vars()) {
if key != "path" && key != "PATH" {
ctx.with_host(|host| host.env_rm(std::ffi::OsString::from(key)));
}
}
}
#[cfg(test)]
pub fn clear_path_var(&mut self, ctx: &mut EvaluationContext) {
ctx.with_host(|host| host.env_rm(std::ffi::OsString::from("PATH")));
}
}
#[cfg(test)]
mod tests {
use super::EnvironmentSyncer;
use indexmap::IndexMap;
use nu_data::config::tests::FakeConfig;
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;
use parking_lot::Mutex;
use std::path::PathBuf;
use std::sync::Arc;
// This test fails on Linux.
// It's possible it has something to do with the fake configuration
// TODO: More tests.
#[cfg(not(target_os = "linux"))]
#[test]
fn syncs_env_if_new_env_entry_is_added_to_an_existing_configuration() -> Result<(), ShellError>
{
let mut ctx = EvaluationContext::basic()?;
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
let mut expected = IndexMap::new();
expected.insert(
"SHELL".to_string(),
"/usr/bin/you_already_made_the_nu_choice".to_string(),
);
Playground::setup("syncs_env_from_config_updated_test_1", |dirs, sandbox| {
sandbox.with_files(vec![
FileWithContent(
"configuration.toml",
r#"
[env]
SHELL = "/usr/bin/you_already_made_the_nu_choice"
"#,
),
FileWithContent(
"updated_configuration.toml",
r#"
[env]
SHELL = "/usr/bin/you_already_made_the_nu_choice"
USER = "NUNO"
"#,
),
]);
let file = dirs.test().join("configuration.toml");
let new_file = dirs.test().join("updated_configuration.toml");
let fake_config = FakeConfig::new(&file);
let mut actual = EnvironmentSyncer::with_config(Box::new(fake_config));
// Here, the environment variables from the current session
// are cleared since we will load and set them from the
// configuration file
actual.clear_env_vars(&mut ctx);
// Nu loads the environment variables from the configuration file
actual.load_environment();
actual.sync_env_vars(&mut ctx);
{
let environment = actual.env.lock();
let mut vars = IndexMap::new();
environment
.env()
.expect("No variables in the environment.")
.row_entries()
.for_each(|(name, value)| {
vars.insert(
name.to_string(),
value.as_string().expect("Couldn't convert to string"),
);
});
for k in expected.keys() {
assert!(vars.contains_key(k));
}
}
assert!(!actual.did_config_change());
// Replacing the newer configuration file to the existing one.
let new_config_contents = std::fs::read_to_string(new_file).expect("Failed");
std::fs::write(&file, &new_config_contents).expect("Failed");
// A change has happened
assert!(actual.did_config_change());
// Syncer should reload and add new envs
actual.reload();
actual.sync_env_vars(&mut ctx);
expected.insert("USER".to_string(), "NUNO".to_string());
{
let environment = actual.env.lock();
let mut vars = IndexMap::new();
environment
.env()
.expect("No variables in the environment.")
.row_entries()
.for_each(|(name, value)| {
vars.insert(
name.to_string(),
value.as_string().expect("Couldn't convert to string"),
);
});
for k in expected.keys() {
assert!(vars.contains_key(k));
}
}
});
Ok(())
}
#[test]
fn syncs_env_if_new_env_entry_in_session_is_not_in_configuration_file() -> Result<(), ShellError>
{
let mut ctx = EvaluationContext::basic()?;
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
let mut expected = IndexMap::new();
expected.insert(
"SHELL".to_string(),
"/usr/bin/you_already_made_the_nu_choice".to_string(),
);
expected.insert("USER".to_string(), "NUNO".to_string());
Playground::setup("syncs_env_test_1", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent(
"configuration.toml",
r#"
[env]
SHELL = "/usr/bin/you_already_made_the_nu_choice"
"#,
)]);
let mut file = dirs.test().clone();
file.push("configuration.toml");
let fake_config = FakeConfig::new(&file);
let mut actual = EnvironmentSyncer::new();
actual.set_config(Box::new(fake_config));
// Here, the environment variables from the current session
// are cleared since we will load and set them from the
// configuration file (if any)
actual.clear_env_vars(&mut ctx);
// We explicitly simulate and add the USER variable to the current
// session's environment variables with the value "NUNO".
ctx.with_host(|test_host| {
test_host.env_set(
std::ffi::OsString::from("USER"),
std::ffi::OsString::from("NUNO"),
)
});
// Nu loads the environment variables from the configuration file (if any)
actual.load_environment();
// By this point, Nu has already loaded the environment variables
// stored in the configuration file. Before continuing we check
// if any new environment variables have been added from the ones loaded
// in the configuration file.
//
// Nu sees the missing "USER" variable and accounts for it.
actual.sync_env_vars(&mut ctx);
// Confirms session environment variables are replaced from Nu configuration file
// including the newer one accounted for.
ctx.with_host(|test_host| {
let var_user = test_host
.env_get(std::ffi::OsString::from("USER"))
.expect("Couldn't get USER var from host.")
.into_string()
.expect("Couldn't convert to string.");
let var_shell = test_host
.env_get(std::ffi::OsString::from("SHELL"))
.expect("Couldn't get SHELL var from host.")
.into_string()
.expect("Couldn't convert to string.");
let mut found = IndexMap::new();
found.insert("SHELL".to_string(), var_shell);
found.insert("USER".to_string(), var_user);
for k in found.keys() {
assert!(expected.contains_key(k));
}
});
// Now confirm in-memory environment variables synced appropriately
// including the newer one accounted for.
let environment = actual.env.lock();
let mut vars = IndexMap::new();
environment
.env()
.expect("No variables in the environment.")
.row_entries()
.for_each(|(name, value)| {
vars.insert(
name.to_string(),
value.as_string().expect("Couldn't convert to string"),
);
});
for k in expected.keys() {
assert!(vars.contains_key(k));
}
});
Ok(())
}
#[test]
fn nu_envs_have_higher_priority_and_does_not_get_overwritten() -> Result<(), ShellError> {
let mut ctx = EvaluationContext::basic()?;
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
let mut expected = IndexMap::new();
expected.insert(
"SHELL".to_string(),
"/usr/bin/you_already_made_the_nu_choice".to_string(),
);
Playground::setup("syncs_env_test_2", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent(
"configuration.toml",
r#"
[env]
SHELL = "/usr/bin/you_already_made_the_nu_choice"
"#,
)]);
let mut file = dirs.test().clone();
file.push("configuration.toml");
let fake_config = FakeConfig::new(&file);
let mut actual = EnvironmentSyncer::new();
actual.set_config(Box::new(fake_config));
actual.clear_env_vars(&mut ctx);
ctx.with_host(|test_host| {
test_host.env_set(
std::ffi::OsString::from("SHELL"),
std::ffi::OsString::from("/usr/bin/sh"),
)
});
actual.load_environment();
actual.sync_env_vars(&mut ctx);
ctx.with_host(|test_host| {
let var_shell = test_host
.env_get(std::ffi::OsString::from("SHELL"))
.expect("Couldn't get SHELL var from host.")
.into_string()
.expect("Couldn't convert to string.");
let mut found = IndexMap::new();
found.insert("SHELL".to_string(), var_shell);
for k in found.keys() {
assert!(expected.contains_key(k));
}
});
let environment = actual.env.lock();
let mut vars = IndexMap::new();
environment
.env()
.expect("No variables in the environment.")
.row_entries()
.for_each(|(name, value)| {
vars.insert(
name.to_string(),
value.as_string().expect("couldn't convert to string"),
);
});
for k in expected.keys() {
assert!(vars.contains_key(k));
}
});
Ok(())
}
#[test]
fn syncs_path_if_new_path_entry_in_session_is_not_in_configuration_file(
) -> Result<(), ShellError> {
let mut ctx = EvaluationContext::basic()?;
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
let expected = std::env::join_paths(vec![
PathBuf::from("/Users/andresrobalino/.volta/bin"),
PathBuf::from("/Users/mosqueteros/bin"),
PathBuf::from("/path/to/be/added"),
])
.expect("Couldn't join paths.")
.into_string()
.expect("Couldn't convert to string.");
Playground::setup("syncs_path_test_1", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent(
"configuration.toml",
r#"
path = ["/Users/andresrobalino/.volta/bin", "/Users/mosqueteros/bin"]
"#,
)]);
let mut file = dirs.test().clone();
file.push("configuration.toml");
let fake_config = FakeConfig::new(&file);
let mut actual = EnvironmentSyncer::new();
actual.set_config(Box::new(fake_config));
// Here, the environment variables from the current session
// are cleared since we will load and set them from the
// configuration file (if any)
actual.clear_path_var(&mut ctx);
// We explicitly simulate and add the PATH variable to the current
// session with the path "/path/to/be/added".
ctx.with_host(|test_host| {
test_host.env_set(
std::ffi::OsString::from("PATH"),
std::env::join_paths(vec![PathBuf::from("/path/to/be/added")])
.expect("Couldn't join paths."),
)
});
// Nu loads the path variables from the configuration file (if any)
actual.load_environment();
// By this point, Nu has already loaded environment path variable
// stored in the configuration file. Before continuing we check
// if any new paths have been added from the ones loaded in the
// configuration file.
//
// Nu sees the missing "/path/to/be/added" and accounts for it.
actual.sync_path_vars(&mut ctx);
ctx.with_host(|test_host| {
let actual = test_host
.env_get(std::ffi::OsString::from("PATH"))
.expect("Couldn't get PATH var from host.")
.into_string()
.expect("Couldn't convert to string.");
assert_eq!(actual, expected);
});
let environment = actual.env.lock();
let paths = std::env::join_paths(
&environment
.path()
.expect("No path variable in the environment.")
.table_entries()
.map(|value| value.as_string().expect("Couldn't convert to string"))
.map(PathBuf::from)
.collect::<Vec<_>>(),
)
.expect("Couldn't join paths.")
.into_string()
.expect("Couldn't convert to string.");
assert_eq!(paths, expected);
});
Ok(())
}
#[test]
fn nu_paths_have_higher_priority_and_new_paths_get_appended_to_the_end(
) -> Result<(), ShellError> {
let mut ctx = EvaluationContext::basic()?;
ctx.host = Arc::new(Mutex::new(Box::new(nu_engine::FakeHost::new())));
let expected = std::env::join_paths(vec![
PathBuf::from("/Users/andresrobalino/.volta/bin"),
PathBuf::from("/Users/mosqueteros/bin"),
PathBuf::from("/path/to/be/added"),
])
.expect("Couldn't join paths.")
.into_string()
.expect("Couldn't convert to string.");
Playground::setup("syncs_path_test_2", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent(
"configuration.toml",
r#"
path = ["/Users/andresrobalino/.volta/bin", "/Users/mosqueteros/bin"]
"#,
)]);
let mut file = dirs.test().clone();
file.push("configuration.toml");
let fake_config = FakeConfig::new(&file);
let mut actual = EnvironmentSyncer::new();
actual.set_config(Box::new(fake_config));
actual.clear_path_var(&mut ctx);
ctx.with_host(|test_host| {
test_host.env_set(
std::ffi::OsString::from("PATH"),
std::env::join_paths(vec![PathBuf::from("/path/to/be/added")])
.expect("Couldn't join paths."),
)
});
actual.load_environment();
actual.sync_path_vars(&mut ctx);
ctx.with_host(|test_host| {
let actual = test_host
.env_get(std::ffi::OsString::from("PATH"))
.expect("Couldn't get PATH var from host.")
.into_string()
.expect("Couldn't convert to string.");
assert_eq!(actual, expected);
});
let environment = actual.env.lock();
let paths = std::env::join_paths(
&environment
.path()
.expect("No path variable in the environment.")
.table_entries()
.map(|value| value.as_string().expect("Couldn't convert to string"))
.map(PathBuf::from)
.collect::<Vec<_>>(),
)
.expect("Couldn't join paths.")
.into_string()
.expect("Couldn't convert to string.");
assert_eq!(paths, expected);
});
Ok(())
}
}

View file

@ -1,9 +1,5 @@
#![recursion_limit = "2048"]
#[cfg(test)]
#[macro_use]
extern crate indexmap;
#[macro_use]
mod prelude;
@ -16,7 +12,6 @@ extern crate quickcheck_macros;
mod cli;
#[cfg(feature = "rustyline-support")]
mod completion;
mod env;
mod format;
#[cfg(feature = "rustyline-support")]
mod keybinding;
@ -30,7 +25,6 @@ pub use crate::cli::cli;
pub use crate::cli::{parse_and_eval, register_plugins, run_script_file};
pub use crate::cli::{NuScript, Options};
pub use crate::env::environment_syncer::EnvironmentSyncer;
pub use nu_command::commands::default_context::create_default_context;
pub use nu_data::config;
pub use nu_data::dict::TaggedListBuilder;

View file

@ -202,7 +202,7 @@ pub fn configure_rustyline_editor(
#[cfg(feature = "rustyline-support")]
pub fn nu_line_editor_helper(
context: &mut EvaluationContext,
context: &EvaluationContext,
config: &dyn nu_data::config::Conf,
) -> crate::shell::Helper {
let hinter = rustyline_hinter(config);
@ -224,7 +224,7 @@ pub fn rustyline_hinter(
Some(rustyline::hint::HistoryHinter {})
}
pub fn configure_ctrl_c(_context: &mut EvaluationContext) -> Result<(), Box<dyn Error>> {
pub fn configure_ctrl_c(_context: &EvaluationContext) -> Result<(), Box<dyn Error>> {
#[cfg(feature = "ctrlc")]
{
let cc = _context.ctrl_c.clone();

View file

@ -149,6 +149,7 @@ 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;
@ -162,7 +163,7 @@ mod tests {
buffer.insert_str(0, text);
buffer.set_pos(text.len() - 1);
let helper = Helper::new(EvaluationContext::basic().unwrap(), None);
let helper = Helper::new(basic_evaluation_context().unwrap(), None);
helper.update(&mut buffer, "cd ".len(), &replacement);
@ -182,7 +183,7 @@ mod tests {
buffer.insert_str(0, text);
buffer.set_pos(text.len() - 30);
let helper = Helper::new(EvaluationContext::basic().unwrap(), None);
let helper = Helper::new(basic_evaluation_context().unwrap(), None);
helper.update(&mut buffer, "cd ".len(), &replacement);

View file

@ -2,49 +2,8 @@ use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
use serde::Deserialize;
use serde::Serialize;
use sha2::{Digest, Sha256};
use std::io::Read;
use std::path::{Path, PathBuf};
pub struct Autoenv;
#[derive(Deserialize, Serialize, Debug, Default)]
pub struct Trusted {
pub files: IndexMap<String, Vec<u8>>,
}
impl Trusted {
pub fn new() -> Self {
Trusted {
files: IndexMap::new(),
}
}
}
pub fn file_is_trusted(nu_env_file: &Path, content: &[u8]) -> Result<bool, ShellError> {
let contentdigest = Sha256::digest(&content).as_slice().to_vec();
let nufile = std::fs::canonicalize(nu_env_file)?;
let trusted = read_trusted()?;
Ok(trusted.files.get(&nufile.to_string_lossy().to_string()) == Some(&contentdigest))
}
pub fn read_trusted() -> Result<Trusted, ShellError> {
let config_path = config::default_path_for(&Some(PathBuf::from("nu-env.toml")))?;
let mut file = std::fs::OpenOptions::new()
.read(true)
.create(true)
.write(true)
.open(config_path)
.map_err(|_| ShellError::untagged_runtime_error("Couldn't open nu-env.toml"))?;
let mut doc = String::new();
file.read_to_string(&mut doc)?;
let allowed = toml::de::from_str(doc.as_str()).unwrap_or_else(|_| Trusted::new());
Ok(allowed)
}
#[async_trait]
impl WholeStreamCommand for Autoenv {
fn name(&self) -> &str {
@ -56,11 +15,12 @@ impl WholeStreamCommand for Autoenv {
fn extra_usage(&self) -> &str {
// "Mark a .nu-env file in a directory as trusted. Needs to be re-run after each change to the file or its filepath."
r#"Create a file called .nu-env in any directory and run 'autoenv trust' to let nushell read it when entering the directory.
The file can contain several optional sections:
env: environment variables to set when visiting the directory. The variables are unset after leaving the directory and any overwritten values are restored.
scriptvars: environment variables that should be set to the return value of a script. After they have been set, they behave in the same way as variables set in the env section.
scripts: scripts to run when entering the directory or leaving it."#
r#"Create a file called .nu-env in any directory and run 'autoenv trust' to let nushell load it when entering the directory.
The .nu-env file has the same format as your $HOME/nu/config.toml file. By loading a .nu-env file the following applies:
- environment variables (section \"[env]\") are loaded from the .nu-env file. Those env variables are only existend in this directory (and children directories)
- the \"startup\" commands are run when entering the directory
- the \"on_exit\" commands are run when leaving the directory
"#
}
fn signature(&self) -> Signature {
@ -76,15 +36,12 @@ The file can contain several optional sections:
vec![Example {
description: "Example .nu-env file",
example: r#"cat .nu-env
startup = ["echo ...entering the directory", "echo 1 2 3"]
on_exit = ["echo ...leaving the directory"]
[env]
mykey = "myvalue"
[scriptvars]
myscript = "echo myval"
[scripts]
entryscripts = ["touch hello.txt", "touch hello2.txt"]
exitscripts = ["touch bye.txt"]"#,
"#,
result: None,
}]
}

View file

@ -1,5 +1,5 @@
use super::autoenv::read_trusted;
use crate::prelude::*;
use nu_data::config::read_trusted;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::SyntaxShape;

View file

@ -1,5 +1,5 @@
use super::autoenv::Trusted;
use crate::prelude::*;
use nu_data::config::Trusted;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::SyntaxShape;

View file

@ -2,7 +2,7 @@ use crate::commands::autoview::options::{ConfigExtensions, NuConfig as AutoViewC
use crate::prelude::*;
use crate::primitive::get_color_config;
use nu_data::value::format_leaf;
use nu_engine::{UnevaluatedCallInfo, WholeStreamCommand};
use nu_engine::{ConfigHolder, RunnableContext, UnevaluatedCallInfo, WholeStreamCommand};
use nu_errors::ShellError;
use nu_protocol::hir::{self, Expression, ExternalRedirection, Literal, SpannedExpression};
use nu_protocol::{Primitive, Signature, UntaggedValue, Value};
@ -27,16 +27,7 @@ impl WholeStreamCommand for Command {
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
autoview(RunnableContext {
input: args.input,
scope: args.scope.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag,
})
.await
autoview(RunnableContext::from_command_args(args)).await
}
fn examples(&self) -> Vec<Example> {
@ -60,6 +51,7 @@ pub struct RunnableContextWithoutInput {
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
pub ctrl_c: Arc<AtomicBool>,
pub configs: Arc<Mutex<ConfigHolder>>,
pub scope: Scope,
pub name: Tag,
}
@ -70,6 +62,7 @@ impl RunnableContextWithoutInput {
shell_manager: context.shell_manager,
host: context.host,
ctrl_c: context.ctrl_c,
configs: context.configs,
current_errors: context.current_errors,
scope: context.scope,
name: context.name,
@ -288,6 +281,7 @@ fn create_default_command_args(context: &RunnableContextWithoutInput) -> RawComm
RawCommandArgs {
host: context.host.clone(),
ctrl_c: context.ctrl_c.clone(),
configs: context.configs.clone(),
current_errors: context.current_errors.clone(),
shell_manager: context.shell_manager.clone(),
call_info: UnevaluatedCallInfo {

View file

@ -509,7 +509,7 @@ mod tests {
#[cfg(feature = "which")]
use futures::executor::block_on;
#[cfg(feature = "which")]
use nu_engine::EvaluationContext;
use nu_engine::basic_evaluation_context;
#[cfg(feature = "which")]
use nu_errors::ShellError;
#[cfg(feature = "which")]
@ -534,7 +534,7 @@ mod tests {
let input = InputStream::empty();
let mut ctx =
EvaluationContext::basic().expect("There was a problem creating a basic context.");
basic_evaluation_context().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::EvaluationContext::basic().expect("There was a problem creating a basic context.");
// let mut ctx = crate::cli::basic_evaluation_context().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::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
use nu_protocol::{ReturnSuccess, Signature, UntaggedValue};
use nu_stream::OutputStream;
pub struct Command;
@ -22,20 +22,21 @@ impl WholeStreamCommand for Command {
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
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)?;
let name = args.call_info.name_tag;
if let Some(global_cfg) = &args.configs.lock().global_config {
let result = global_cfg.vars.clone();
Ok(futures::stream::iter(vec![ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(name),
)])
.to_output_stream())
} else {
Ok(
futures::stream::iter(vec![ReturnSuccess::value(UntaggedValue::Error(
ShellError::untagged_runtime_error("No global config found!"),
))])
.to_output_stream(),
)
}
}
}

View file

@ -2,7 +2,7 @@ use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{
ColumnPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
ColumnPath, ConfigPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
pub struct SubCommand;
@ -61,6 +61,7 @@ impl WholeStreamCommand for SubCommand {
pub async fn set(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone();
let ctx = EvaluationContext::from_args(&args);
let scope = args.scope.clone();
let (
Arguments {
@ -93,6 +94,10 @@ pub async fn set(args: CommandArgs) -> Result<OutputStream, ShellError> {
..
}) => {
config::write(&changes.entries, &path)?;
ctx.reload_config(&ConfigPath::Global(
path.expect("Global config path is always some"),
))
.await?;
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::Row(changes).into_value(name),

View file

@ -1,7 +1,9 @@
use crate::prelude::*;
use nu_engine::WholeStreamCommand;
use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
use nu_protocol::{
ConfigPath, Primitive, ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value,
};
use nu_source::Tagged;
pub struct SubCommand;
@ -44,6 +46,7 @@ impl WholeStreamCommand for SubCommand {
pub async fn set_into(args: CommandArgs) -> Result<OutputStream, ShellError> {
let name = args.call_info.name_tag.clone();
let ctx = EvaluationContext::from_args(&args);
let scope = args.scope.clone();
let (Arguments { set_into: v }, input) = args.process().await?;
@ -73,6 +76,10 @@ pub async fn set_into(args: CommandArgs) -> Result<OutputStream, ShellError> {
result.insert(key, value.clone());
config::write(&result, &path)?;
ctx.reload_config(&ConfigPath::Global(
path.expect("Global config path is always some"),
))
.await?;
OutputStream::one(ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(name),
@ -84,6 +91,10 @@ pub async fn set_into(args: CommandArgs) -> Result<OutputStream, ShellError> {
result.insert(key, value);
config::write(&result, &path)?;
ctx.reload_config(&ConfigPath::Global(
path.expect("Global config path is always some"),
))
.await?;
OutputStream::one(ReturnSuccess::value(
UntaggedValue::Row(result.into()).into_value(name),

View file

@ -1,9 +1,10 @@
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 = EvaluationContext::basic()?;
let context = basic_evaluation_context()?;
{
use crate::commands::*;

View file

@ -81,6 +81,7 @@ async fn enter(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
let shell_manager = raw_args.shell_manager.clone();
let head = raw_args.call_info.args.head.clone();
let ctrl_c = raw_args.ctrl_c.clone();
let configs = raw_args.configs.clone();
let current_errors = raw_args.current_errors.clone();
let host = raw_args.host.clone();
let tag = raw_args.call_info.name_tag.clone();
@ -132,6 +133,7 @@ async fn enter(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
let new_args = RawCommandArgs {
host,
ctrl_c,
configs,
current_errors,
shell_manager,
call_info: UnevaluatedCallInfo {

View file

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

View file

@ -29,19 +29,7 @@ impl WholeStreamCommand for SubCommand {
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
run_with_function(
RunnableContext {
input: args.input,
scope: args.scope.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag,
},
average,
)
.await
run_with_function(RunnableContext::from_command_args(args), average).await
}
fn examples(&self) -> Vec<Example> {

View file

@ -23,15 +23,7 @@ impl WholeStreamCommand for SubCommand {
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
run_with_numerical_functions_on_stream(
RunnableContext {
input: args.input,
scope: args.scope.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag,
},
RunnableContext::from_command_args(args),
ceil_big_int,
ceil_big_decimal,
ceil_default,

View file

@ -23,15 +23,7 @@ impl WholeStreamCommand for SubCommand {
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
run_with_numerical_functions_on_stream(
RunnableContext {
input: args.input,
scope: args.scope.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag,
},
RunnableContext::from_command_args(args),
floor_big_int,
floor_big_decimal,
floor_default,

View file

@ -22,19 +22,7 @@ impl WholeStreamCommand for SubCommand {
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
run_with_function(
RunnableContext {
input: args.input,
scope: args.scope.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag,
},
maximum,
)
.await
run_with_function(RunnableContext::from_command_args(args), maximum).await
}
fn examples(&self) -> Vec<Example> {

View file

@ -26,19 +26,7 @@ impl WholeStreamCommand for SubCommand {
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
run_with_function(
RunnableContext {
input: args.input,
scope: args.scope.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag,
},
median,
)
.await
run_with_function(RunnableContext::from_command_args(args), median).await
}
fn examples(&self) -> Vec<Example> {

View file

@ -22,19 +22,7 @@ impl WholeStreamCommand for SubCommand {
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
run_with_function(
RunnableContext {
input: args.input,
scope: args.scope.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag,
},
minimum,
)
.await
run_with_function(RunnableContext::from_command_args(args), minimum).await
}
fn examples(&self) -> Vec<Example> {

View file

@ -22,19 +22,7 @@ impl WholeStreamCommand for SubCommand {
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
run_with_function(
RunnableContext {
input: args.input,
scope: args.scope.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag,
},
mode,
)
.await
run_with_function(RunnableContext::from_command_args(args), mode).await
}
fn examples(&self) -> Vec<Example> {

View file

@ -22,19 +22,7 @@ impl WholeStreamCommand for SubCommand {
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
run_with_function(
RunnableContext {
input: args.input,
scope: args.scope.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag,
},
product,
)
.await
run_with_function(RunnableContext::from_command_args(args), product).await
}
fn examples(&self) -> Vec<Example> {

View file

@ -23,19 +23,7 @@ impl WholeStreamCommand for SubCommand {
}
async fn run(&self, args: CommandArgs) -> Result<OutputStream, ShellError> {
run_with_function(
RunnableContext {
input: args.input,
scope: args.scope.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
current_errors: args.current_errors,
name: args.call_info.name_tag,
},
summation,
)
.await
run_with_function(RunnableContext::from_command_args(args), summation).await
}
fn examples(&self) -> Vec<Example> {

View file

@ -83,9 +83,9 @@ impl WholeStreamCommand for RunExternalCommand {
EvaluationContext {
scope: args.scope.clone(),
host: args.host.clone(),
user_recently_used_autoenv_untrust: Arc::new(AtomicBool::new(false)),
shell_manager: args.shell_manager.clone(),
ctrl_c: args.ctrl_c.clone(),
configs: args.configs.clone(),
current_errors: Arc::new(Mutex::new(vec![])),
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
}

View file

@ -168,6 +168,7 @@ async fn save(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
let scope = raw_args.scope.clone();
let host = raw_args.host.clone();
let ctrl_c = raw_args.ctrl_c.clone();
let configs = raw_args.configs.clone();
let current_errors = raw_args.current_errors.clone();
let shell_manager = raw_args.shell_manager.clone();
@ -215,6 +216,7 @@ async fn save(raw_args: CommandArgs) -> Result<OutputStream, ShellError> {
let new_args = RawCommandArgs {
host,
ctrl_c,
configs,
current_errors,
shell_manager: shell_manager.clone(),
call_info: UnevaluatedCallInfo {

View file

@ -6,12 +6,14 @@ 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,
@ -24,7 +26,7 @@ use futures::executor::block_on;
pub fn test_examples(cmd: Command) -> Result<(), ShellError> {
let examples = cmd.examples();
let base_context = EvaluationContext::basic()?;
let base_context = basic_evaluation_context()?;
base_context.add_commands(vec![
// Command Doubles
@ -90,7 +92,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 = EvaluationContext::basic()?;
let base_context = basic_evaluation_context()?;
base_context.add_commands(vec![
whole_stream_command(Echo {}),
@ -147,7 +149,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 = EvaluationContext::basic()?;
let base_context = basic_evaluation_context()?;
base_context.add_commands(vec![
// Minimal restricted commands to aid in testing
@ -228,10 +230,8 @@ async fn evaluate_block(
ctx: &mut EvaluationContext,
) -> Result<Vec<Value>, ShellError> {
let input_stream = InputStream::empty();
let env = ctx.get_env();
ctx.scope.enter_scope();
ctx.scope.add_env(env);
let result = run_block(&block.block, ctx, input_stream).await;

View file

@ -1,5 +1,17 @@
use std::io::{self, BufRead, Write};
/// Echo's value of env keys from args
/// Example: nu --testbin env_echo FOO BAR
/// If it it's not present echo's nothing
pub fn echo_env() {
let args = args();
for arg in args {
if let Ok(v) = std::env::var(arg) {
println!("{}", v);
}
}
}
pub fn cococo() {
let args: Vec<String> = args();

View file

@ -26,6 +26,8 @@ num-traits = "0.2.14"
query_interface = "0.3.5"
serde = { version = "1.0.123", features = ["derive"] }
toml = "0.5.8"
sha2 = "0.9.3"
common-path = "1.0.0"
nu-errors = { version = "0.29.0", path = "../nu-errors" }
nu-protocol = { version = "0.29.0", path = "../nu-protocol" }

View file

@ -1,18 +1,23 @@
mod conf;
mod config_trust;
mod local_config;
mod nuconfig;
pub mod path;
pub mod tests;
pub use conf::Conf;
pub use config_trust::is_file_trusted;
pub use config_trust::read_trusted;
pub use config_trust::Trusted;
pub use local_config::loadable_cfg_exists_in_dir;
pub use local_config::LocalConfigDiff;
pub use nuconfig::NuConfig;
use indexmap::IndexMap;
use log::trace;
use nu_errors::{CoerceInto, ShellError};
use nu_protocol::{
Dictionary, Primitive, ShellTypeName, TaggedDictBuilder, UnspannedPathMember, UntaggedValue,
Value,
ConfigPath, Dictionary, Primitive, ShellTypeName, TaggedDictBuilder, UnspannedPathMember,
UntaggedValue, Value,
};
use nu_source::{SpannedItem, Tag, TaggedItem};
use std::fs::{self, OpenOptions};
@ -186,7 +191,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(|| self::path::DEFAULT_CONFIG_LOCATION.as_ref());
.unwrap_or_else(|| "config.toml".as_ref());
filename.push(file);
Ok(filename)
@ -297,14 +302,11 @@ pub fn config(tag: impl Into<Tag>) -> Result<IndexMap<String, Value>, ShellError
}
pub fn write(config: &IndexMap<String, Value>, at: &Option<PathBuf>) -> Result<(), ShellError> {
let filename = &mut default_path()?;
let filename = default_path()?;
let filename = match at {
None => filename,
Some(file) => {
filename.pop();
filename.push(file);
filename
}
Some(ref file) => file.clone(),
};
let contents = value_to_toml_value(
@ -325,3 +327,7 @@ fn touch(path: &Path) -> io::Result<()> {
Err(e) => Err(e),
}
}
pub fn cfg_path_to_scope_tag(cfg_path: &ConfigPath) -> String {
cfg_path.get_path().to_string_lossy().to_string()
}

View file

@ -1,22 +1,17 @@
use nu_errors::ShellError;
use nu_protocol::Value;
use std::any::Any;
use std::fmt::Debug;
use std::{fmt::Debug, path::PathBuf};
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>;
fn path(&self) -> Option<Value>;
fn reload(&mut self);
fn path(&self) -> Result<Option<Vec<PathBuf>>, ShellError>;
fn clone_box(&self) -> Box<dyn Conf>;
fn reload(&mut self);
}
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()
}
@ -29,10 +24,6 @@ impl Conf for Box<dyn Conf> {
(**self).env()
}
fn path(&self) -> Option<Value> {
(**self).path()
}
fn reload(&mut self) {
(**self).reload();
}
@ -40,4 +31,8 @@ impl Conf for Box<dyn Conf> {
fn clone_box(&self) -> Box<dyn Conf> {
(**self).clone_box()
}
fn path(&self) -> Result<Option<Vec<PathBuf>>, ShellError> {
(**self).path()
}
}

View file

@ -0,0 +1,44 @@
use serde::Deserialize;
use serde::Serialize;
use sha2::{Digest, Sha256};
use std::{io::Read, path::Path, path::PathBuf};
use indexmap::IndexMap;
use nu_errors::ShellError;
#[derive(Deserialize, Serialize, Debug, Default)]
pub struct Trusted {
pub files: IndexMap<String, Vec<u8>>,
}
impl Trusted {
pub fn new() -> Self {
Trusted {
files: IndexMap::new(),
}
}
}
pub fn is_file_trusted(nu_env_file: &Path, content: &[u8]) -> Result<bool, ShellError> {
let contentdigest = Sha256::digest(&content).as_slice().to_vec();
let nufile = std::fs::canonicalize(nu_env_file)?;
let trusted = read_trusted()?;
Ok(trusted.files.get(&nufile.to_string_lossy().to_string()) == Some(&contentdigest))
}
pub fn read_trusted() -> Result<Trusted, ShellError> {
let config_path = crate::config::default_path_for(&Some(PathBuf::from("nu-env.toml")))?;
let mut file = std::fs::OpenOptions::new()
.read(true)
.create(true)
.write(true)
.open(config_path)
.map_err(|_| ShellError::untagged_runtime_error("Couldn't open nu-env.toml"))?;
let mut doc = String::new();
file.read_to_string(&mut doc)?;
let allowed = toml::de::from_str(doc.as_str()).unwrap_or_else(|_| Trusted::new());
Ok(allowed)
}

View file

@ -0,0 +1,150 @@
use nu_errors::ShellError;
use std::path::{Path, PathBuf};
static LOCAL_CFG_FILE_NAME: &str = ".nu-env";
pub struct LocalConfigDiff {
pub cfgs_to_load: Vec<PathBuf>,
pub cfgs_to_unload: Vec<PathBuf>,
}
/// Finds all local configs between `from` up to `to`.
/// Every config seen while going up the filesystem (e.G. from `/foo` to `/foo/bar`) is returned
/// as a config to load
/// Every config seen while going down the filesystem (e.G. from `/foo/bar` to `/foo/bar`) is
/// returned as a config to unload
/// If both paths are unrelated to each other, (e.G. windows paths as: `C:/foo` and `D:/bar`)
/// this function first walks `from` completly down the filesystem and then it walks up until `to`.
///
/// Both paths are required to be absolute.
impl LocalConfigDiff {
pub fn between(from: PathBuf, to: PathBuf) -> (LocalConfigDiff, Vec<ShellError>) {
let common_prefix = common_path::common_path(&from, &to);
let (cfgs_to_unload, err_down) = walk_down(&from, &common_prefix);
let (cfgs_to_load, err_up) = walk_up(&common_prefix, &to);
(
LocalConfigDiff {
cfgs_to_load,
cfgs_to_unload,
},
err_down.into_iter().chain(err_up).collect(),
)
}
}
///Walks from the first parameter down the filesystem to the second parameter. Marking all
///configs found in directories on the way as to remove.
///If to is None, this method walks from the first paramter down to the beginning of the
///filesystem
///Returns tuple of (configs to remove, errors from io).
fn walk_down(
from_inclusive: &Path,
to_exclusive: &Option<PathBuf>,
) -> (Vec<PathBuf>, Vec<ShellError>) {
let mut all_err = vec![];
let mut all_cfgs_to_unload = vec![];
for dir in from_inclusive.ancestors().take_while(|cur_path| {
if let Some(until_path) = to_exclusive {
//Stop before `to_exclusive`
*cur_path != until_path
} else {
//No end, walk all the way down
true
}
}) {
match local_cfg_should_be_unloaded(dir.to_path_buf()) {
Ok(Some(cfg)) => all_cfgs_to_unload.push(cfg),
Err(e) => all_err.push(e),
_ => {}
}
}
(all_cfgs_to_unload, all_err)
}
///Walks from the first parameter up the filesystem to the second parameter, returns all configs
///found in directories on the way to load.
///Returns combined errors from checking directories on the way
///If from is None, this method walks from the beginning of the second parameter up to the
///second parameter
fn walk_up(
from_exclusive: &Option<PathBuf>,
to_inclusive: &Path,
) -> (Vec<PathBuf>, Vec<ShellError>) {
let mut all_err = vec![];
let mut all_cfgs_to_load = vec![];
//skip all paths until (inclusive) from (or 0 if from is None)
let skip_ahead = from_exclusive
.as_ref()
.map(|p| p.ancestors().count())
.unwrap_or(0);
//We have to traverse ancestors in reverse order (apply lower directories first)
//ancestors() does not yield iter with .rev() method. So we store all ancestors
//and then iterate over the vec
let dirs: Vec<_> = to_inclusive.ancestors().map(Path::to_path_buf).collect();
for dir in dirs.iter().rev().skip(skip_ahead) {
match loadable_cfg_exists_in_dir(dir.clone()) {
Ok(Some(cfg)) => all_cfgs_to_load.push(cfg),
Err(e) => all_err.push(e),
_ => {}
}
}
(all_cfgs_to_load, all_err)
}
fn is_existent_local_cfg(cfg_file_path: &Path) -> Result<bool, ShellError> {
if !cfg_file_path.exists() || cfg_file_path.parent() == super::default_path()?.parent() {
//Don't treat global cfg as local one
Ok(false)
} else {
Ok(true)
}
}
fn is_trusted_local_cfg_content(cfg_file_path: &Path, content: &[u8]) -> Result<bool, ShellError> {
//This checks whether user used `autoenv trust` to mark this cfg as secure
if !super::is_file_trusted(&cfg_file_path, &content)? {
//Notify user about present config, but not trusted
Err(ShellError::untagged_runtime_error(
format!("{:?} is untrusted. Run 'autoenv trust {:?}' to trust it.\nThis needs to be done after each change to the file.",
cfg_file_path, cfg_file_path.parent().unwrap_or_else(|| &Path::new("")))))
} else {
Ok(true)
}
}
fn local_cfg_should_be_unloaded<P: AsRef<Path>>(cfg_dir: P) -> Result<Option<PathBuf>, ShellError> {
let mut cfg = cfg_dir.as_ref().to_path_buf();
cfg.push(LOCAL_CFG_FILE_NAME);
if is_existent_local_cfg(&cfg)? {
//No need to compute whether content is good. If it is not loaded before, unloading does
//nothing
Ok(Some(cfg))
} else {
Ok(None)
}
}
/// Checks whether a local_cfg exists in cfg_dir and returns:
/// Ok(Some(cfg_path)) if cfg exists and is good to load
/// Ok(None) if no cfg exists
/// Err(error) if cfg exits, but is not good to load
pub fn loadable_cfg_exists_in_dir(mut cfg_dir: PathBuf) -> Result<Option<PathBuf>, ShellError> {
cfg_dir.push(LOCAL_CFG_FILE_NAME);
let cfg_path = cfg_dir;
if !is_existent_local_cfg(&cfg_path)? {
return Ok(None);
}
let content = std::fs::read(&cfg_path)?;
if !is_trusted_local_cfg_content(&cfg_path, &content)? {
return Ok(None);
}
Ok(Some(cfg_path))
}

View file

@ -1,23 +1,18 @@
use crate::config::{last_modified, read, Conf, Status};
use indexmap::IndexMap;
use nu_errors::ShellError;
use nu_protocol::Value;
use nu_source::Tag;
use std::any::Any;
use std::fmt::Debug;
use std::path::PathBuf;
use std::{fmt::Debug, path::PathBuf};
#[derive(Debug, Clone, Default)]
pub struct NuConfig {
pub source_file: Option<std::path::PathBuf>,
pub vars: IndexMap<String, Value>,
pub file_path: PathBuf,
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,17 +25,15 @@ impl Conf for NuConfig {
self.env()
}
fn path(&self) -> Option<Value> {
fn path(&self) -> Result<Option<Vec<PathBuf>>, ShellError> {
self.path()
}
fn reload(&mut self) {
let vars = &mut self.vars;
if let Ok(variables) = read(Tag::unknown(), &Some(self.file_path.clone())) {
self.vars = variables;
if let Ok(variables) = read(Tag::unknown(), &self.source_file) {
vars.extend(variables);
self.modified_at = if let Ok(status) = last_modified(&None) {
self.modified_at = if let Ok(status) = last_modified(&Some(self.file_path.clone())) {
status
} else {
Status::Unavailable
@ -54,25 +47,20 @@ impl Conf for NuConfig {
}
impl NuConfig {
pub fn with(config_file: Option<std::ffi::OsString>) -> NuConfig {
match &config_file {
None => NuConfig::new(),
Some(_) => {
let source_file = config_file.map(std::path::PathBuf::from);
let vars = if let Ok(variables) = read(Tag::unknown(), &source_file) {
variables
pub fn load(cfg_file_path: Option<PathBuf>) -> Result<NuConfig, ShellError> {
let vars = read(Tag::unknown(), &cfg_file_path)?;
let modified_at = NuConfig::get_last_modified(&cfg_file_path);
let file_path = if let Some(file_path) = cfg_file_path {
file_path
} else {
IndexMap::default()
crate::config::default_path()?
};
NuConfig {
source_file: source_file.clone(),
Ok(NuConfig {
file_path,
vars,
modified_at: NuConfig::get_last_modified(&source_file),
}
}
}
modified_at,
})
}
pub fn new() -> NuConfig {
@ -81,18 +69,19 @@ impl NuConfig {
} else {
IndexMap::default()
};
let path = if let Ok(path) = crate::config::default_path() {
path
} else {
PathBuf::new()
};
NuConfig {
source_file: None,
vars,
modified_at: NuConfig::get_last_modified(&None),
file_path: path,
}
}
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
@ -104,8 +93,7 @@ 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(&self.source_file), 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)?;
@ -113,8 +101,7 @@ impl NuConfig {
left != right
}
(_, _) => false,
},
)
})
}
pub fn var(&self, key: &str) -> Option<Value> {
@ -127,6 +114,19 @@ impl NuConfig {
None
}
/// Return environment variables as map
pub fn env_map(&self) -> IndexMap<String, String> {
let mut result = IndexMap::new();
if let Some(variables) = self.env() {
for var in variables.row_entries() {
if let Ok(value) = var.1.as_string() {
result.insert(var.0.clone(), value);
}
}
}
result
}
pub fn env(&self) -> Option<Value> {
let vars = &self.vars;
@ -137,17 +137,43 @@ impl NuConfig {
None
}
pub fn path(&self) -> Option<Value> {
pub fn path(&self) -> Result<Option<Vec<PathBuf>>, ShellError> {
let vars = &self.vars;
if let Some(env_vars) = vars.get("path") {
return Some(env_vars.clone());
if let Some(path) = vars.get("path").or_else(|| vars.get("PATH")) {
path
.table_entries()
.map(|p| {
p.as_string().map(PathBuf::from).map_err(|_| {
ShellError::untagged_runtime_error("Could not format path entry as string!\nPath entry from config won't be added")
})
})
.collect::<Result<Vec<PathBuf>, ShellError>>().map(Some)
} else {
Ok(None)
}
}
if let Some(env_vars) = vars.get("PATH") {
return Some(env_vars.clone());
fn load_scripts_if_present(&self, scripts_name: &str) -> Result<Vec<String>, ShellError> {
if let Some(array) = self.var(scripts_name) {
if !array.is_table() {
Err(ShellError::untagged_runtime_error(format!(
"expected an array of strings as {} commands",
scripts_name
)))
} else {
array.table_entries().map(Value::as_string).collect()
}
} else {
Ok(vec![])
}
}
None
pub fn exit_scripts(&self) -> Result<Vec<String>, ShellError> {
self.load_scripts_if_present("on_exit")
}
pub fn startup_scripts(&self) -> Result<Vec<String>, ShellError> {
self.load_scripts_if_present("startup")
}
}

View file

@ -1,32 +1,25 @@
use crate::config::NuConfig;
use crate::config::Conf;
use std::path::PathBuf;
pub const DEFAULT_CONFIG_LOCATION: &str = "config.toml";
const DEFAULT_HISTORY_LOCATION: &str = "history.txt";
const DEFAULT_LOCATION: &str = "history.txt";
pub fn history(config: &NuConfig) -> PathBuf {
let default_path = crate::config::user_data()
pub fn default_history_path() -> PathBuf {
crate::config::user_data()
.map(|mut p| {
p.push(DEFAULT_HISTORY_LOCATION);
p.push(DEFAULT_LOCATION);
p
})
.unwrap_or_else(|_| PathBuf::from(DEFAULT_HISTORY_LOCATION));
.unwrap_or_else(|_| PathBuf::from(DEFAULT_LOCATION))
}
let path = &config.var("history-path");
pub fn history_path(config: &dyn Conf) -> PathBuf {
let default_history_path = default_history_path();
path.as_ref().map_or(default_path.clone(), |custom_path| {
match custom_path.as_string() {
config.var("history-path").map_or(
default_history_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))
}
}
Err(_) => default_history_path,
},
)
}

View file

@ -1,6 +1,5 @@
use crate::config::{Conf, NuConfig, Status};
use nu_protocol::Value;
use std::any::Any;
use std::path::{Path, PathBuf};
#[derive(Debug, Clone)]
@ -10,10 +9,6 @@ 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

@ -20,6 +20,8 @@ nu-ansi-term = { version = "0.29.0", path = "../nu-ansi-term" }
trash = { version = "1.3.0", optional = true }
which = { version = "4.0.2", optional = true }
codespan-reporting = "0.11.0"
dyn-clone = "1.0.4"
ansi_term = "0.12.1"
async-recursion = "0.3.2"
async-trait = "0.1.42"
bytes = "0.5.6"

View file

@ -0,0 +1,16 @@
# Nu-Engine
Nu-engine handles most of the core logic of nushell. For example, engine handles:
- Passing of data between commands
- Evaluating a commands return values
- Loading of user configurations
## Top level introduction
The following topics shall give the reader a top level understanding how various topics are handled in nushell.
### How are environment variables handled?
Environment variables (or short envs) are stored in the `Scope` of the `EvaluationContext`. That means that environment variables are scoped by default and we don't use `std::env` to store envs (but make exceptions where convenient).
Nushell handles environment variables and their lifetime the following:
- At startup all existing environment variables are read and put into `Scope`. (Nushell reads existing environment variables platform independent by asking the `Host`. They will most likly come from `std::env::*`)
- Envs can also be loaded from config files. Each loaded config produces a new `ScopeFrame` with the envs of the loaded config.
- Nu-Script files and internal commands read and write env variables from / to the `Scope`. External scripts and binaries can't interact with the `Scope`. Therefore all env variables are read from the `Scope` and put into the external binaries environment-variables-memory area.

View file

@ -0,0 +1,26 @@
use crate::EvaluationContext;
use crate::Scope;
use crate::{basic_shell_manager, config_holder::ConfigHolder};
use crate::{env::basic_host::BasicHost, Host};
use indexmap::IndexMap;
use parking_lot::Mutex;
use std::error::Error;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
pub fn basic_evaluation_context() -> Result<EvaluationContext, Box<dyn Error>> {
let scope = Scope::new();
let mut host = BasicHost {};
let env_vars = host.vars().iter().cloned().collect::<IndexMap<_, _>>();
scope.add_env(env_vars);
Ok(EvaluationContext {
scope,
host: Arc::new(parking_lot::Mutex::new(Box::new(host))),
current_errors: Arc::new(Mutex::new(vec![])),
ctrl_c: Arc::new(AtomicBool::new(false)),
configs: Arc::new(Mutex::new(ConfigHolder::new())),
shell_manager: basic_shell_manager::basic_shell_manager()?,
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
})
}

View file

@ -0,0 +1,16 @@
use crate::filesystem::filesystem_shell::{FilesystemShell, FilesystemShellMode};
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(
FilesystemShellMode::Cli,
)?)])),
})
}

View file

@ -1,9 +1,9 @@
use crate::call_info::UnevaluatedCallInfo;
use crate::deserializer::ConfigDeserializer;
use crate::env::host::Host;
use crate::evaluate::scope::Scope;
use crate::evaluation_context::EvaluationContext;
use crate::shell::shell_manager::ShellManager;
use crate::{call_info::UnevaluatedCallInfo, config_holder::ConfigHolder};
use derive_new::new;
use getset::Getters;
use nu_errors::ShellError;
@ -22,6 +22,7 @@ use std::sync::Arc;
pub struct CommandArgs {
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
pub ctrl_c: Arc<AtomicBool>,
pub configs: Arc<Mutex<ConfigHolder>>,
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
pub shell_manager: ShellManager,
pub call_info: UnevaluatedCallInfo,
@ -35,6 +36,7 @@ pub struct RawCommandArgs {
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
pub ctrl_c: Arc<AtomicBool>,
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
pub configs: Arc<Mutex<ConfigHolder>>,
pub shell_manager: ShellManager,
pub scope: Scope,
pub call_info: UnevaluatedCallInfo,
@ -45,6 +47,7 @@ impl RawCommandArgs {
CommandArgs {
host: self.host,
ctrl_c: self.ctrl_c,
configs: self.configs,
current_errors: self.current_errors,
shell_manager: self.shell_manager,
call_info: self.call_info,
@ -65,6 +68,7 @@ impl CommandArgs {
let ctx = EvaluationContext::from_args(&self);
let host = self.host.clone();
let ctrl_c = self.ctrl_c.clone();
let configs = self.configs.clone();
let shell_manager = self.shell_manager.clone();
let input = self.input;
let call_info = self.call_info.evaluate(&ctx).await?;
@ -73,6 +77,7 @@ impl CommandArgs {
Ok(EvaluatedWholeStreamCommandArgs::new(
host,
ctrl_c,
configs,
shell_manager,
call_info,
input,
@ -106,6 +111,7 @@ impl EvaluatedWholeStreamCommandArgs {
pub fn new(
host: Arc<parking_lot::Mutex<dyn Host>>,
ctrl_c: Arc<AtomicBool>,
configs: Arc<Mutex<ConfigHolder>>,
shell_manager: ShellManager,
call_info: CallInfo,
input: impl Into<InputStream>,
@ -115,6 +121,7 @@ impl EvaluatedWholeStreamCommandArgs {
args: EvaluatedCommandArgs {
host,
ctrl_c,
configs,
shell_manager,
call_info,
scope,
@ -145,6 +152,7 @@ impl EvaluatedWholeStreamCommandArgs {
pub struct EvaluatedCommandArgs {
pub host: Arc<parking_lot::Mutex<dyn Host>>,
pub ctrl_c: Arc<AtomicBool>,
pub configs: Arc<Mutex<ConfigHolder>>,
pub shell_manager: ShellManager,
pub call_info: CallInfo,
pub scope: Scope,

View file

@ -0,0 +1,53 @@
use std::path::Path;
use nu_data::config::NuConfig;
use nu_protocol::ConfigPath;
/// ConfigHolder holds information which configs have been loaded.
#[derive(Clone)]
pub struct ConfigHolder {
pub global_config: Option<NuConfig>,
pub local_configs: Vec<NuConfig>,
}
impl Default for ConfigHolder {
fn default() -> Self {
Self::new()
}
}
impl ConfigHolder {
pub fn new() -> ConfigHolder {
ConfigHolder {
global_config: None,
local_configs: vec![],
}
}
pub fn add_local_cfg(&mut self, cfg: NuConfig) {
self.local_configs.push(cfg);
}
pub fn set_global_cfg(&mut self, cfg: NuConfig) {
self.global_config = Some(cfg);
}
pub fn remove_cfg(&mut self, cfg_path: &ConfigPath) {
match cfg_path {
ConfigPath::Global(_) => self.global_config = None,
ConfigPath::Local(p) => self.remove_local_cfg(p),
}
}
fn remove_local_cfg<P: AsRef<Path>>(&mut self, cfg_path: P) {
// Remove the first loaded local config with specified cfg_path
if let Some(index) = self
.local_configs
.iter()
.rev()
.position(|cfg| cfg.file_path == cfg_path.as_ref())
{
self.local_configs.remove(index);
}
}
}

View file

@ -7,21 +7,6 @@ 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 {
@ -38,7 +23,18 @@ impl Host for BasicHost {
}
fn print_err(&mut self, err: ShellError, source: &Text) {
print_err(err, source);
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);
});
}
}
#[allow(unused_variables)]

View file

@ -1,30 +0,0 @@
use nu_protocol::Value;
use std::ffi::OsString;
use std::fmt::Debug;
pub trait Env: Debug + Send {
fn env(&self) -> Option<Value>;
fn path(&self) -> Option<Value>;
fn add_env(&mut self, key: &str, value: &str);
fn add_path(&mut self, new_path: OsString);
}
impl Env for Box<dyn Env> {
fn env(&self) -> Option<Value> {
(**self).env()
}
fn path(&self) -> Option<Value> {
(**self).path()
}
fn add_env(&mut self, key: &str, value: &str) {
(**self).add_env(key, value);
}
fn add_path(&mut self, new_path: OsString) {
(**self).add_path(new_path);
}
}

View file

@ -1,3 +1,2 @@
pub(crate) mod basic_host;
pub(crate) mod environment;
pub(crate) mod host;

View file

@ -1,7 +1,7 @@
use crate::call_info::UnevaluatedCallInfo;
use crate::command_args::RawCommandArgs;
use crate::evaluation_context::EvaluationContext;
use crate::filesystem::filesystem_shell::FilesystemShell;
use crate::filesystem::filesystem_shell::{FilesystemShell, FilesystemShellMode};
use crate::shell::help_shell::HelpShell;
use crate::shell::value_shell::ValueShell;
use futures::StreamExt;
@ -11,7 +11,6 @@ use nu_protocol::hir::{ExternalRedirection, InternalCommand};
use nu_protocol::{CommandAction, Primitive, ReturnSuccess, UntaggedValue, Value};
use nu_source::{PrettyDebug, Span, Tag};
use nu_stream::{trace_stream, InputStream, ToInputStream};
use std::sync::atomic::Ordering;
use std::sync::Arc;
pub(crate) async fn run_internal_command(
@ -28,12 +27,6 @@ pub(crate) async fn run_internal_command(
let internal_command = context.scope.expect_command(&command.name);
if command.name == "autoenv untrust" {
context
.user_recently_used_autoenv_untrust
.store(true, Ordering::SeqCst);
}
let result = {
context
.run_command(
@ -75,6 +68,7 @@ pub(crate) async fn run_internal_command(
let new_args = RawCommandArgs {
host: context.host.clone(),
ctrl_c: context.ctrl_c.clone(),
configs: context.configs.clone(),
current_errors: context.current_errors.clone(),
shell_manager: context.shell_manager.clone(),
call_info: UnevaluatedCallInfo {
@ -172,8 +166,13 @@ pub(crate) async fn run_internal_command(
InputStream::from_stream(futures::stream::iter(vec![]))
}
CommandAction::EnterShell(location) => {
let mode = if context.shell_manager.is_interactive() {
FilesystemShellMode::Cli
} else {
FilesystemShellMode::Script
};
context.shell_manager.insert_at_current(Box::new(
match FilesystemShell::with_location(location) {
match FilesystemShell::with_location(location, mode) {
Ok(v) => v,
Err(err) => {
context.error(err.into());
@ -220,6 +219,17 @@ pub(crate) async fn run_internal_command(
}
InputStream::empty()
}
CommandAction::UnloadConfig(cfg_path) => {
context.unload_config(&cfg_path).await;
InputStream::empty()
}
CommandAction::LoadConfig(cfg_path) => {
if let Err(e) = context.load_config(&cfg_path).await {
InputStream::one(UntaggedValue::Error(e).into_untagged_value())
} else {
InputStream::empty()
}
}
},
Ok(ReturnSuccess::Value(Value {

View file

@ -177,6 +177,16 @@ impl Scope {
output
}
pub fn get_env(&self, name: &str) -> Option<String> {
for frame in self.frames.lock().iter().rev() {
if let Some(v) = frame.env.get(name) {
return Some(v.clone());
}
}
None
}
pub fn get_var(&self, name: &str) -> Option<Value> {
for frame in self.frames.lock().iter().rev() {
if let Some(v) = frame.vars.get(name) {
@ -224,6 +234,66 @@ impl Scope {
frame.env.insert(name.into(), value);
}
}
pub fn set_exit_scripts(&self, scripts: Vec<String>) {
if let Some(frame) = self.frames.lock().last_mut() {
frame.exitscripts = scripts
}
}
pub fn enter_scope_with_tag(&self, tag: String) {
self.frames.lock().push(ScopeFrame::with_tag(tag));
}
//Removes the scopeframe with tag.
pub fn exit_scope_with_tag(&self, tag: &str) {
let mut frames = self.frames.lock();
let tag = Some(tag);
if let Some(i) = frames.iter().rposition(|f| f.tag.as_deref() == tag) {
frames.remove(i);
}
}
pub fn get_exitscripts_of_frame_with_tag(&self, tag: &str) -> Option<Vec<String>> {
let frames = self.frames.lock();
let tag = Some(tag);
frames.iter().find_map(|f| {
if f.tag.as_deref() == tag {
Some(f.exitscripts.clone())
} else {
None
}
})
}
pub fn get_frame_with_tag(&self, tag: &str) -> Option<ScopeFrame> {
let frames = self.frames.lock();
let tag = Some(tag);
frames.iter().rev().find_map(|f| {
if f.tag.as_deref() == tag {
Some(f.clone())
} else {
None
}
})
}
pub fn update_frame_with_tag(&self, frame: ScopeFrame, tag: &str) -> Result<(), ShellError> {
let mut frames = self.frames.lock();
let tag = Some(tag);
for f in frames.iter_mut().rev() {
if f.tag.as_deref() == tag {
*f = frame;
return Ok(());
}
}
// Frame not found, return err
Err(ShellError::untagged_runtime_error(format!(
"Can't update frame with tag {:?}. No such frame present!",
tag
)))
}
}
impl ParserScope for Scope {
@ -286,6 +356,15 @@ pub struct ScopeFrame {
pub commands: IndexMap<String, Command>,
pub custom_commands: IndexMap<String, Block>,
pub aliases: IndexMap<String, Vec<Spanned<String>>>,
///Optional tag to better identify this scope frame later
pub tag: Option<String>,
pub exitscripts: Vec<String>,
}
impl Default for ScopeFrame {
fn default() -> Self {
ScopeFrame::new()
}
}
impl ScopeFrame {
@ -324,6 +403,15 @@ impl ScopeFrame {
commands: IndexMap::new(),
custom_commands: IndexMap::new(),
aliases: IndexMap::new(),
tag: None,
exitscripts: Vec::new(),
}
}
pub fn with_tag(tag: String) -> ScopeFrame {
let mut scope = ScopeFrame::new();
scope.tag = Some(tag);
scope
}
}

View file

@ -1,24 +1,13 @@
use crate::evaluate::scope::Scope;
use indexmap::IndexMap;
use nu_data::config::NuConfig;
use nu_data::config::path::history_path;
use nu_errors::ShellError;
use nu_protocol::{Primitive, TaggedDictBuilder, UntaggedValue, Value};
use nu_source::{Spanned, Tag};
pub fn nu(scope: &Scope, tag: impl Into<Tag>) -> Result<Value, ShellError> {
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 tag = tag.into();
let mut nu_dict = TaggedDictBuilder::new(&tag);
@ -30,10 +19,16 @@ pub fn nu(scope: &Scope, tag: impl Into<Tag>) -> Result<Value, ShellError> {
}
nu_dict.insert_value("env", dict.into_value());
nu_dict.insert_value(
"config",
UntaggedValue::row(config.vars.clone()).into_value(&tag),
);
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));
let mut table = vec![];
for v in env.iter() {
@ -55,9 +50,15 @@ 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(nu_data::config::path::source_file(&config)).into_value(&tag),
UntaggedValue::filepath(config).into_value(&tag),
);
#[cfg(feature = "rustyline-support")]
@ -69,9 +70,11 @@ 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(nu_data::config::path::history(&config)).into_value(&tag),
UntaggedValue::filepath(history).into_value(&tag),
);
Ok(nu_dict.into_value())

View file

@ -1,17 +1,18 @@
use crate::call_info::UnevaluatedCallInfo;
use crate::command_args::CommandArgs;
use crate::env::{basic_host::BasicHost, host::Host};
use crate::evaluate::scope::Scope;
use crate::env::host::Host;
use crate::evaluate::scope::{Scope, ScopeFrame};
use crate::shell::shell_manager::ShellManager;
use crate::whole_stream_command::Command;
use indexmap::IndexMap;
use crate::{call_info::UnevaluatedCallInfo, config_holder::ConfigHolder};
use crate::{command_args::CommandArgs, script};
use log::trace;
use nu_data::config::{self, Conf, NuConfig};
use nu_errors::ShellError;
use nu_protocol::hir;
use nu_source::{Tag, Text};
use nu_protocol::{hir, ConfigPath};
use nu_source::{Span, Tag};
use nu_stream::{InputStream, OutputStream};
use parking_lot::Mutex;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use std::{path::Path, sync::Arc};
#[derive(Clone)]
pub struct EvaluationContext {
@ -19,7 +20,7 @@ pub struct EvaluationContext {
pub host: Arc<parking_lot::Mutex<Box<dyn Host>>>,
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
pub ctrl_c: Arc<AtomicBool>,
pub user_recently_used_autoenv_untrust: Arc<AtomicBool>,
pub configs: Arc<Mutex<ConfigHolder>>,
pub shell_manager: ShellManager,
/// Windows-specific: keep track of previous cwd on each drive
@ -27,26 +28,14 @@ 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(),
host: args.host.clone(),
current_errors: args.current_errors.clone(),
ctrl_c: args.ctrl_c.clone(),
configs: args.configs.clone(),
shell_manager: args.shell_manager.clone(),
user_recently_used_autoenv_untrust: Arc::new(AtomicBool::new(false)),
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
}
}
@ -117,6 +106,7 @@ impl EvaluationContext {
CommandArgs {
host: self.host.clone(),
ctrl_c: self.ctrl_c.clone(),
configs: self.configs.clone(),
current_errors: self.current_errors.clone(),
shell_manager: self.shell_manager.clone(),
call_info: self.call_info(args, name_tag),
@ -125,26 +115,187 @@ impl EvaluationContext {
}
}
pub fn get_env(&self) -> IndexMap<String, String> {
let mut output = IndexMap::new();
for (var, value) in self.host.lock().vars() {
output.insert(var, value);
/// Loads config under cfg_path.
/// If an error occurs while loading the config:
/// The config is not loaded
/// The error is returned
/// After successfull loading of the config the startup scripts are run
/// as normal scripts (Errors are printed out, ...)
/// After executing the startup scripts, true is returned to indicate successfull loading
/// of the config
//
// The rational here is that, we should not partially load any config
// that might be damaged. However, startup scripts might fail for various reasons.
// A failure there is not as crucial as wrong config files.
pub async fn load_config(&self, cfg_path: &ConfigPath) -> Result<(), ShellError> {
trace!("Loading cfg {:?}", cfg_path);
let cfg = NuConfig::load(Some(cfg_path.get_path().clone()))?;
let exit_scripts = cfg.exit_scripts()?;
let startup_scripts = cfg.startup_scripts()?;
let cfg_paths = cfg.path()?;
let joined_paths = cfg_paths
.map(|mut cfg_paths| {
//existing paths are prepended to path
if let Some(env_paths) = self.scope.get_env("PATH") {
let mut env_paths = std::env::split_paths(&env_paths).collect::<Vec<_>>();
//No duplicates! Remove env_paths already existing in cfg_paths
env_paths.retain(|env_path| !cfg_paths.contains(env_path));
//env_paths entries are appended at the end
//nu config paths have a higher priority
cfg_paths.extend(env_paths);
}
output
cfg_paths
})
.map(|paths| {
std::env::join_paths(paths)
.map(|s| s.to_string_lossy().to_string())
.map_err(|e| {
ShellError::labeled_error(
&format!("Error while joining paths from config: {:?}", e),
"Config path error",
Span::unknown(),
)
})
})
.transpose()?;
let tag = config::cfg_path_to_scope_tag(cfg_path);
self.scope.enter_scope_with_tag(tag);
self.scope.add_env(cfg.env_map());
if let Some(path) = joined_paths {
self.scope.add_env_var("PATH", path);
}
self.scope.set_exit_scripts(exit_scripts);
match cfg_path {
ConfigPath::Global(_) => self.configs.lock().set_global_cfg(cfg),
ConfigPath::Local(_) => {
self.configs.lock().add_local_cfg(cfg);
}
}
pub fn maybe_print_errors(context: &EvaluationContext, source: Text) -> bool {
let errors = context.current_errors.clone();
let mut errors = errors.lock();
if !startup_scripts.is_empty() {
self.run_scripts(startup_scripts, cfg_path.get_path().parent())
.await;
}
if errors.len() > 0 {
let error = errors[0].clone();
*errors = vec![];
Ok(())
}
context.host.lock().print_err(error, &source);
true
} else {
false
/// Reloads config with a path of cfg_path.
/// If an error occurs while reloading the config:
/// The config is not reloaded
/// The error is returned
pub async fn reload_config(&self, cfg_path: &ConfigPath) -> Result<(), ShellError> {
trace!("Reloading cfg {:?}", cfg_path);
let mut configs = self.configs.lock();
let cfg = match cfg_path {
ConfigPath::Global(path) => {
configs.global_config.iter_mut().find(|cfg| &cfg.file_path == path).ok_or_else(||
ShellError::labeled_error(
&format!("Error reloading global config with path of {}. No such global config present.", path.display()),
"Config path error",
Span::unknown(),
)
)?
}
ConfigPath::Local(path) => {
configs.local_configs.iter_mut().find(|cfg| &cfg.file_path == path).ok_or_else(||
ShellError::labeled_error(
&format!("Error reloading local config with path of {}. No such local config present.", path.display()),
"Config path error",
Span::unknown(),
)
)?
}
};
cfg.reload();
let exit_scripts = cfg.exit_scripts()?;
let cfg_paths = cfg.path()?;
let joined_paths = cfg_paths
.map(|mut cfg_paths| {
//existing paths are prepended to path
if let Some(env_paths) = self.scope.get_env("PATH") {
let mut env_paths = std::env::split_paths(&env_paths).collect::<Vec<_>>();
//No duplicates! Remove env_paths already existing in cfg_paths
env_paths.retain(|env_path| !cfg_paths.contains(env_path));
//env_paths entries are appended at the end
//nu config paths have a higher priority
cfg_paths.extend(env_paths);
}
cfg_paths
})
.map(|paths| {
std::env::join_paths(paths)
.map(|s| s.to_string_lossy().to_string())
.map_err(|e| {
ShellError::labeled_error(
&format!("Error while joining paths from config: {:?}", e),
"Config path error",
Span::unknown(),
)
})
})
.transpose()?;
let tag = config::cfg_path_to_scope_tag(cfg_path);
let mut frame = ScopeFrame::new();
frame.env = cfg.env_map();
if let Some(path) = joined_paths {
frame.env.insert("PATH".to_string(), path);
}
frame.exitscripts = exit_scripts;
self.scope.update_frame_with_tag(frame, &tag)?;
Ok(())
}
/// Runs all exit_scripts before unloading the config with path of cfg_path
/// If an error occurs while running exit scripts:
/// The error is added to `self.current_errors`
/// If no config with path of `cfg_path` is present, this method does nothing
pub async fn unload_config(&self, cfg_path: &ConfigPath) {
trace!("UnLoading cfg {:?}", cfg_path);
let tag = config::cfg_path_to_scope_tag(cfg_path);
//Run exitscripts with scope frame and cfg still applied
if let Some(scripts) = self.scope.get_exitscripts_of_frame_with_tag(&tag) {
self.run_scripts(scripts, cfg_path.get_path().parent())
.await;
}
//Unload config
self.configs.lock().remove_cfg(&cfg_path);
self.scope.exit_scope_with_tag(&tag);
}
/// Runs scripts with cwd of dir. If dir is None, this method does nothing.
/// Each error is added to `self.current_errors`
pub async fn run_scripts(&self, scripts: Vec<String>, dir: Option<&Path>) {
if let Some(dir) = dir {
for script in scripts {
match script::run_script_in_dir(script.clone(), dir, &self).await {
Ok(_) => {}
Err(e) => {
let err = ShellError::untagged_runtime_error(format!(
"Err while executing exitscript. Err was\n{:?}",
e
));
let text = script.into();
self.host.lock().print_err(err, &text);
}
}
}
}
}
}

View file

@ -10,7 +10,8 @@ use futures::stream::BoxStream;
use futures::StreamExt;
use futures_codec::FramedRead;
use futures_util::TryStreamExt;
use nu_protocol::{TaggedDictBuilder, Value};
use nu_data::config::LocalConfigDiff;
use nu_protocol::{CommandAction, ConfigPath, TaggedDictBuilder, Value};
use nu_source::{Span, Tag};
use nu_stream::{Interruptible, OutputStream, ToOutputStream};
use std::collections::HashMap;
@ -27,9 +28,16 @@ use nu_errors::ShellError;
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue};
use nu_source::Tagged;
#[derive(Eq, PartialEq, Clone, Copy)]
pub enum FilesystemShellMode {
Cli,
Script,
}
pub struct FilesystemShell {
pub(crate) path: String,
pub(crate) last_path: String,
pub(crate) mode: FilesystemShellMode,
}
impl std::fmt::Debug for FilesystemShell {
@ -43,12 +51,13 @@ impl Clone for FilesystemShell {
FilesystemShell {
path: self.path.clone(),
last_path: self.path.clone(),
mode: self.mode,
}
}
}
impl FilesystemShell {
pub fn basic() -> Result<FilesystemShell, Error> {
pub fn basic(mode: FilesystemShellMode) -> Result<FilesystemShell, Error> {
let path = match std::env::current_dir() {
Ok(path) => path,
Err(_) => PathBuf::from("/"),
@ -57,15 +66,23 @@ impl FilesystemShell {
Ok(FilesystemShell {
path: path.to_string_lossy().to_string(),
last_path: path.to_string_lossy().to_string(),
mode,
})
}
pub fn with_location(path: String) -> Result<FilesystemShell, std::io::Error> {
pub fn with_location(
path: String,
mode: FilesystemShellMode,
) -> Result<FilesystemShell, std::io::Error> {
let path = canonicalize(std::env::current_dir()?, &path)?;
let path = path.display().to_string();
let last_path = path.clone();
Ok(FilesystemShell { path, last_path })
Ok(FilesystemShell {
path,
last_path,
mode,
})
}
}
@ -255,6 +272,43 @@ impl Shell for FilesystemShell {
path.to_string_lossy().to_string(),
));
//Loading local configs in script mode, makes scripts behave different on different
//filesystems and might therefore surprise users. That's why we only load them in cli mode.
if self.mode == FilesystemShellMode::Cli {
match dunce::canonicalize(self.path()) {
Err(e) => {
let err = ShellError::untagged_runtime_error(format!(
"Could not get absolute path from current fs shell. The error was: {:?}",
e
));
stream.push_back(ReturnSuccess::value(
UntaggedValue::Error(err).into_value(Tag::unknown()),
));
}
Ok(current_pwd) => {
let (changes, errs) = LocalConfigDiff::between(current_pwd, path);
for err in errs {
stream.push_back(ReturnSuccess::value(
UntaggedValue::Error(err).into_value(Tag::unknown()),
));
}
for unload_cfg in changes.cfgs_to_unload {
stream.push_back(ReturnSuccess::action(CommandAction::UnloadConfig(
ConfigPath::Local(unload_cfg),
)));
}
for load_cfg in changes.cfgs_to_load {
stream.push_back(ReturnSuccess::action(CommandAction::LoadConfig(
ConfigPath::Local(load_cfg),
)));
}
}
};
}
Ok(stream.into())
}
@ -777,6 +831,10 @@ impl Shell for FilesystemShell {
)),
}
}
fn is_interactive(&self) -> bool {
self.mode == FilesystemShellMode::Cli
}
}
struct TaggedPathBuf<'a>(&'a PathBuf, &'a Tag);

View file

@ -1,25 +1,31 @@
pub mod basic_evaluation_context;
pub mod basic_shell_manager;
mod call_info;
mod command_args;
mod config_holder;
pub mod deserializer;
pub mod documentation;
mod env;
mod evaluate;
pub mod evaluation_context;
mod evaluation_context;
mod example;
pub mod filesystem;
mod maybe_text_codec;
pub mod plugin;
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,
};
pub use crate::config_holder::ConfigHolder;
pub use crate::documentation::{generate_docs, get_brief_help, get_documentation, get_full_help};
pub use crate::env::environment::Env;
pub use crate::env::host::FakeHost;
pub use crate::env::host::Host;
pub use crate::evaluate::block::run_block;
@ -31,6 +37,7 @@ pub use crate::filesystem::dir_info::{DirBuilder, DirInfo, FileInfo};
pub use crate::filesystem::filesystem_shell::FilesystemShell;
pub use crate::filesystem::path;
pub use crate::maybe_text_codec::{MaybeTextCodec, StringOrBinary};
pub use crate::print::maybe_print_errors;
pub use crate::runnable_context::RunnableContext;
pub use crate::shell::help_shell::{command_dict, HelpShell};
pub use crate::shell::painter::Painter;

View file

@ -0,0 +1,18 @@
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,4 +1,5 @@
use crate::{Command, Host, Scope, ShellManager};
use crate::{Command, CommandArgs, EvaluationContext};
use crate::{ConfigHolder, Host, Scope, ShellManager};
use nu_errors::ShellError;
use nu_source::Tag;
use nu_stream::InputStream;
@ -10,12 +11,39 @@ pub struct RunnableContext {
pub shell_manager: ShellManager,
pub host: Arc<Mutex<Box<dyn Host>>>,
pub ctrl_c: Arc<AtomicBool>,
pub configs: Arc<Mutex<ConfigHolder>>,
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
pub scope: Scope,
pub name: Tag,
}
impl RunnableContext {
pub fn from_command_args(args: CommandArgs) -> Self {
RunnableContext {
input: args.input,
scope: args.scope.clone(),
shell_manager: args.shell_manager,
host: args.host,
ctrl_c: args.ctrl_c,
configs: args.configs,
current_errors: args.current_errors,
name: args.call_info.name_tag,
}
}
pub fn from_evaluation_context(input: InputStream, ctx: &EvaluationContext) -> Self {
RunnableContext {
input,
shell_manager: ctx.shell_manager.clone(),
host: ctx.host.clone(),
ctrl_c: ctx.ctrl_c.clone(),
configs: ctx.configs.clone(),
current_errors: ctx.current_errors.clone(),
scope: ctx.scope.clone(),
name: Tag::unknown(),
}
}
pub fn get_command(&self, name: &str) -> Option<Command> {
self.scope.get_command(name)
}

View file

@ -1,5 +1,4 @@
use crate::path::canonicalize;
use crate::run_block;
use crate::{maybe_print_errors, path::canonicalize, run_block};
use crate::{MaybeTextCodec, StringOrBinary};
use futures::StreamExt;
use futures_codec::FramedRead;
@ -11,7 +10,7 @@ use nu_protocol::hir::{
use nu_protocol::{Primitive, ReturnSuccess, UntaggedValue, Value};
use nu_stream::{InputStream, ToInputStream};
use crate::{evaluation_context, EvaluationContext};
use crate::EvaluationContext;
use log::{debug, trace};
use nu_source::{Span, Tag, Text};
use std::iter::Iterator;
@ -36,6 +35,22 @@ fn chomp_newline(s: &str) -> &str {
}
}
pub async fn run_script_in_dir(
script: String,
dir: &Path,
ctx: &EvaluationContext,
) -> Result<(), Box<dyn Error>> {
//Save path before to switch back to it after executing script
let path_before = ctx.shell_manager.path();
ctx.shell_manager
.set_path(dir.to_string_lossy().to_string());
run_script_standalone(script, false, ctx, false).await?;
ctx.shell_manager.set_path(path_before);
Ok(())
}
/// Process the line by parsing the text to turn it into commands, classify those commands so that we understand what is being called in the pipeline, and then run this pipeline
pub async fn process_script(
script_text: &str,
@ -168,9 +183,7 @@ pub async fn process_script(
};
trace!("{:#?}", block);
let env = ctx.get_env();
ctx.scope.add_env_to_base(env);
let result = run_block(&block, ctx, input_stream).await;
match result {
@ -229,6 +242,10 @@ pub async fn run_script_standalone(
context: &EvaluationContext,
exit_on_error: bool,
) -> Result<(), Box<dyn Error>> {
context
.shell_manager
.enter_script_mode()
.map_err(Box::new)?;
let line = process_script(&script_text, context, redirect_stdin, 0, false).await;
match line {
@ -244,16 +261,19 @@ pub async fn run_script_standalone(
}
};
evaluation_context::maybe_print_errors(&context, Text::from(line));
maybe_print_errors(&context, Text::from(line));
if error_code != 0 && exit_on_error {
std::process::exit(error_code);
}
}
LineResult::Error(line, err) => {
context.with_host(|host| host.print_err(err, &Text::from(line.clone())));
context
.host
.lock()
.print_err(err, &Text::from(line.clone()));
evaluation_context::maybe_print_errors(&context, Text::from(line));
maybe_print_errors(&context, Text::from(line));
if exit_on_error {
std::process::exit(1);
}
@ -261,5 +281,9 @@ pub async fn run_script_standalone(
_ => {}
}
//exit script mode shell
context.shell_manager.remove_at_current();
Ok(())
}

View file

@ -244,4 +244,9 @@ impl Shell for HelpShell {
"save on help shell is not supported",
))
}
fn is_interactive(&self) -> bool {
//Help shell is always interactive
true
}
}

View file

@ -19,6 +19,7 @@ pub(crate) mod shell_manager;
pub(crate) mod value_shell;
pub trait Shell: std::fmt::Debug {
fn is_interactive(&self) -> bool;
fn name(&self) -> String;
fn homedir(&self) -> Option<PathBuf>;

View file

@ -1,12 +1,10 @@
use crate::command_args::EvaluatedWholeStreamCommandArgs;
use crate::maybe_text_codec::StringOrBinary;
use crate::shell::Shell;
use crate::{command_args::EvaluatedWholeStreamCommandArgs, FilesystemShell};
use crate::{filesystem::filesystem_shell::FilesystemShellMode, maybe_text_codec::StringOrBinary};
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};
@ -22,11 +20,11 @@ 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 enter_script_mode(&self) -> Result<(), std::io::Error> {
//New fs_shell starting from current path
let fs_shell = FilesystemShell::with_location(self.path(), FilesystemShellMode::Script)?;
self.insert_at_current(Box::new(fs_shell));
Ok(())
}
pub fn insert_at_current(&self, shell: Box<dyn Shell + Send>) {
@ -64,6 +62,10 @@ impl ShellManager {
self.shells.lock().is_empty()
}
pub fn is_interactive(&self) -> bool {
self.shells.lock()[self.current_shell()].is_interactive()
}
pub fn path(&self) -> String {
self.shells.lock()[self.current_shell()].path()
}

View file

@ -254,4 +254,9 @@ impl Shell for ValueShell {
"save on help shell is not supported",
))
}
fn is_interactive(&self) -> bool {
//Value shell is always interactive
true
}
}

View file

@ -0,0 +1,19 @@
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
/// Specifies a path to a configuration file and its type
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum ConfigPath {
/// Path to the global configuration file
Global(PathBuf),
/// Path to a local configuration file
Local(PathBuf),
}
impl ConfigPath {
pub fn get_path(&self) -> &PathBuf {
match self {
ConfigPath::Global(p) | ConfigPath::Local(p) => p,
}
}
}

View file

@ -2,6 +2,7 @@
mod macros;
mod call_info;
pub mod config_path;
pub mod hir;
mod maybe_owned;
mod return_value;
@ -12,6 +13,7 @@ mod type_shape;
pub mod value;
pub use crate::call_info::{CallInfo, EvaluatedArgs};
pub use crate::config_path::ConfigPath;
pub use crate::maybe_owned::MaybeOwned;
pub use crate::return_value::{CommandAction, ReturnSuccess, ReturnValue};
pub use crate::signature::{NamedType, PositionalType, Signature};

View file

@ -1,4 +1,4 @@
use crate::value::Value;
use crate::{value::Value, ConfigPath};
use nu_errors::ShellError;
use nu_source::{DbgDocBldr, DebugDocBuilder, PrettyDebug};
use serde::{Deserialize, Serialize};
@ -22,6 +22,10 @@ pub enum CommandAction {
EnterHelpShell(Value),
/// Add plugins from path given
AddPlugins(String),
/// Unload the config specified by PathBuf if present
UnloadConfig(ConfigPath),
/// Load the config specified by PathBuf
LoadConfig(ConfigPath),
/// Go to the previous shell in the shell ring buffer
PreviousShell,
/// Go to the next shell in the shell ring buffer
@ -51,6 +55,12 @@ impl PrettyDebug for CommandAction {
CommandAction::PreviousShell => DbgDocBldr::description("previous shell"),
CommandAction::NextShell => DbgDocBldr::description("next shell"),
CommandAction::LeaveShell(_) => DbgDocBldr::description("leave shell"),
CommandAction::UnloadConfig(cfg) => {
DbgDocBldr::description(format!("unload config {:?}", cfg))
}
CommandAction::LoadConfig(cfg) => {
DbgDocBldr::description(format!("load config {:?}", cfg))
}
}
}
}

View file

@ -16,6 +16,13 @@ fn main() -> Result<(), Box<dyn Error>> {
.hidden(true)
.takes_value(true),
)
.arg(
Arg::with_name("no-history")
.hidden(true)
.long("no-history")
.multiple(false)
.takes_value(false),
)
.arg(
Arg::with_name("loglevel")
.short("l")
@ -36,7 +43,9 @@ fn main() -> Result<(), Box<dyn Error>> {
.hidden(true)
.long("testbin")
.value_name("TESTBIN")
.possible_values(&["cococo", "iecho", "fail", "nonu", "chop", "repeater"])
.possible_values(&[
"echo_env", "cococo", "iecho", "fail", "nonu", "chop", "repeater",
])
.takes_value(true),
)
.arg(
@ -64,13 +73,6 @@ 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")
@ -86,6 +88,7 @@ fn main() -> Result<(), Box<dyn Error>> {
if let Some(bin) = matches.value_of("testbin") {
match bin {
"echo_env" => binaries::echo_env(),
"cococo" => binaries::cococo(),
"iecho" => binaries::iecho(),
"fail" => binaries::fail(),
@ -163,10 +166,10 @@ fn main() -> Result<(), Box<dyn Error>> {
}
None => {
let mut context = create_default_context(true)?;
let context = create_default_context(true)?;
if !matches.is_present("skip-plugins") {
let _ = nu_cli::register_plugins(&mut context);
let _ = nu_cli::register_plugins(&context);
}
#[cfg(feature = "rustyline-support")]

View file

@ -0,0 +1,45 @@
use super::support::Trusted;
use nu_test_support::fs::Stub::FileWithContent;
use nu_test_support::nu;
use nu_test_support::playground::Playground;
use serial_test::serial;
#[test]
fn passes_let_env_env_var_to_external_process() {
let actual = nu!(cwd: ".", r#"
let-env FOO = foo
nu --testbin echo_env FOO
"#);
assert_eq!(actual.out, "foo");
}
#[test]
fn passes_with_env_env_var_to_external_process() {
let actual = nu!(cwd: ".", r#"
with-env [FOO foo] {nu --testbin echo_env FOO}
"#);
assert_eq!(actual.out, "foo");
}
#[test]
#[serial]
fn passes_env_from_local_cfg_to_external_process() {
Playground::setup("autoenv_dir", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent(
".nu-env",
r#"[env]
FOO = "foo"
"#,
)]);
let actual = Trusted::in_path(&dirs, || {
nu!(cwd: dirs.test(), r#"
nu --testbin echo_env FOO
"#)
});
assert_eq!(actual.out, "foo");
})
}

View file

@ -1,4 +1,5 @@
mod configuration;
mod env;
mod in_sync;
mod nu_env;

View file

@ -6,17 +6,8 @@ use nu_test_support::{nu, pipeline};
use serial_test::serial;
// Windows uses a different command to create an empty file
// so we need to have different content on windows.
const SCRIPTS: &str = if cfg!(target_os = "windows") {
r#"[scripts]
entryscripts = ["echo nul > hello.txt"]
exitscripts = ["echo nul > bye.txt"]"#
} else {
r#"[scripts]
entryscripts = ["touch hello.txt"]
exitscripts = ["touch bye.txt"]"#
};
const SCRIPTS: &str = r#"startup = ["touch hello.txt"]
on_exit = ["touch bye.txt"]"#;
#[test]
#[serial]
@ -26,13 +17,13 @@ fn picks_up_env_keys_when_entering_trusted_directory() {
".nu-env",
&format!(
"{}\n{}",
SCRIPTS,
r#"[env]
testkey = "testvalue"
[scriptvars]
myscript = "echo myval"
"#,
SCRIPTS
"#
),
)]);
@ -46,19 +37,20 @@ fn picks_up_env_keys_when_entering_trusted_directory() {
#[test]
#[serial]
#[ignore]
fn picks_up_script_vars_when_entering_trusted_directory() {
Playground::setup("autoenv_test_2", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent(
".nu-env",
&format!(
"{}\n{}",
SCRIPTS,
r#"[env]
testkey = "testvalue"
[scriptvars]
myscript = "echo myval"
"#,
SCRIPTS
"#
),
)]);
@ -66,6 +58,8 @@ fn picks_up_script_vars_when_entering_trusted_directory() {
let actual = Trusted::in_path(&dirs, || nu!(cwd: dirs.test(), "echo $nu.env.myscript"));
// scriptvars are not supported
// and why is myval expected when myscript is "echo myval"
assert_eq!(actual.out, expected);
})
}
@ -102,13 +96,10 @@ fn entering_a_trusted_directory_runs_entry_scripts() {
".nu-env",
&format!(
"{}\n{}",
SCRIPTS,
r#"[env]
testkey = "testvalue"
[scriptvars]
myscript = "echo myval"
"#,
SCRIPTS
"#
),
)]);
@ -132,13 +123,13 @@ fn leaving_a_trusted_directory_runs_exit_scripts() {
".nu-env",
&format!(
"{}\n{}",
SCRIPTS,
r#"[env]
testkey = "testvalue"
[scriptvars]
myscript = "echo myval"
"#,
SCRIPTS
"#
),
)]);
@ -161,13 +152,13 @@ fn entry_scripts_are_called_when_revisiting_a_trusted_directory() {
".nu-env",
&format!(
"{}\n{}",
SCRIPTS,
r#"[env]
testkey = "testvalue"
[scriptvars]
myscript = "echo myval"
"#,
SCRIPTS
"#
),
)]);
@ -194,13 +185,13 @@ fn given_a_trusted_directory_with_entry_scripts_when_entering_a_subdirectory_ent
".nu-env",
&format!(
"{}\n{}",
SCRIPTS,
r#"[env]
testkey = "testvalue"
[scriptvars]
myscript = "echo myval"
"#,
SCRIPTS
"#
),
)]);
@ -225,13 +216,13 @@ fn given_a_trusted_directory_with_exit_scripts_when_entering_a_subdirectory_exit
".nu-env",
&format!(
"{}\n{}",
SCRIPTS,
r#"[env]
testkey = "testvalue"
[scriptvars]
myscript = "echo myval"
"#,
SCRIPTS
"#
),
)]);
@ -308,6 +299,40 @@ fn given_a_hierachy_of_trusted_directories_nested_ones_should_overwrite_variable
})
}
#[test]
#[serial]
#[cfg(not(windows))] //TODO figure out why this test doesn't work on windows
fn local_config_should_not_be_added_when_running_scripts() {
Playground::setup("autoenv_test_10", |dirs, sandbox| {
sandbox.mkdir("foo");
sandbox.with_files(vec![
FileWithContent(
".nu-env",
r#"[env]
organization = "nu""#,
),
FileWithContent(
"foo/.nu-env",
r#"[env]
organization = "foo""#,
),
FileWithContent(
"script.nu",
r#"cd foo
echo $nu.env.organization"#,
),
]);
let actual = Trusted::in_path(&dirs, || {
nu!(cwd: dirs.test(), r#"
do { autoenv trust foo ; = $nothing } # Silence autoenv trust message from output
nu script.nu
"#)
});
assert_eq!(actual.out, "nu");
})
}
#[test]
#[serial]
fn given_a_hierachy_of_trusted_directories_going_back_restores_overwritten_variables() {
@ -340,3 +365,246 @@ fn given_a_hierachy_of_trusted_directories_going_back_restores_overwritten_varia
assert_eq!(actual.out, "nushell");
})
}
#[cfg(feature = "directories-support")]
#[cfg(feature = "which-support")]
#[test]
#[serial]
fn local_config_env_var_present_and_removed_correctly() {
use nu_test_support::fs::Stub::FileWithContent;
Playground::setup("autoenv_test", |dirs, sandbox| {
sandbox.mkdir("foo");
sandbox.mkdir("foo/bar");
sandbox.with_files(vec![FileWithContent(
"foo/.nu-env",
r#"[env]
testkey = "testvalue"
"#,
)]);
//Assert testkey is not present before entering directory
let actual = nu!(
cwd: dirs.test(),
r#"autoenv trust foo;
echo $nu.env.testkey"#
);
assert!(actual.err.contains("Unknown"));
//Assert testkey is present in foo
let actual = nu!(
cwd: dirs.test(),
r#"autoenv trust foo; cd foo
echo $nu.env.testkey"#
);
assert_eq!(actual.out, "testvalue");
//Assert testkey is present also in subdirectories
let actual = nu!(
cwd: dirs.test(),
r#"autoenv trust foo; cd foo
cd bar
echo $nu.env.testkey"#
);
assert_eq!(actual.out, "testvalue");
//Assert testkey is present also when jumping over foo
let actual = nu!(
cwd: dirs.test(),
r#"autoenv trust foo; cd foo/bar
echo $nu.env.testkey"#
);
assert_eq!(actual.out, "testvalue");
//Assert testkey removed after leaving foo
let actual = nu!(
cwd: dirs.test(),
r#"autoenv trust foo; cd foo
cd ..
echo $nu.env.testkey"#
);
assert!(actual.err.contains("Unknown"));
});
}
#[cfg(feature = "directories-support")]
#[cfg(feature = "which-support")]
#[test]
#[serial]
fn local_config_env_var_gets_overwritten() {
use nu_test_support::fs::Stub::FileWithContent;
Playground::setup("autoenv_test", |dirs, sandbox| {
sandbox.mkdir("foo");
sandbox.mkdir("foo/bar");
sandbox.with_files(vec![
FileWithContent(
"foo/.nu-env",
r#"[env]
overwrite_me = "foo"
"#,
),
FileWithContent(
"foo/bar/.nu-env",
r#"[env]
overwrite_me = "bar"
"#,
),
]);
//Assert overwrite_me is not present before entering directory
let actual = nu!(
cwd: dirs.test(),
r#"autoenv trust foo;
echo $nu.env.overwrite_me"#
);
assert!(actual.err.contains("Unknown"));
//Assert overwrite_me is foo in foo
let actual = nu!(
cwd: dirs.test(),
r#"autoenv trust foo; cd foo
echo $nu.env.overwrite_me"#
);
assert_eq!(actual.out, "foo");
//Assert overwrite_me is bar in bar
let actual = nu!(
cwd: dirs.test(),
r#"autoenv trust foo | = $nothing
autoenv trust foo/bar | = $nothing
cd foo
cd bar
echo $nu.env.overwrite_me"#
);
assert_eq!(actual.out, "bar");
//Assert overwrite_me is present also when jumping over foo
let actual = nu!(
cwd: dirs.test(),
r#"autoenv trust foo; autoenv trust foo/bar; cd foo/bar
echo $nu.env.overwrite_me
"#
);
assert_eq!(actual.out, "bar");
//Assert overwrite_me removed after leaving bar
let actual = nu!(
cwd: dirs.test(),
r#"autoenv trust foo; autoenv trust foo/bar; cd foo
cd bar
cd ..
echo $nu.env.overwrite_me"#
);
assert_eq!(actual.out, "foo");
});
}
#[cfg(feature = "directories-support")]
#[cfg(feature = "which-support")]
#[test]
#[serial]
fn autoenv_test_entry_scripts() {
use nu_test_support::fs::Stub::FileWithContent;
Playground::setup("autoenv_test", |dirs, sandbox| {
sandbox.mkdir("foo/bar");
// Windows uses a different command to create an empty file so we need to have different content on windows.
let nu_env = if cfg!(target_os = "windows") {
r#"startup = ["echo nul > hello.txt"]"#
} else {
r#"startup = ["touch hello.txt"]"#
};
sandbox.with_files(vec![FileWithContent("foo/.nu-env", nu_env)]);
// Make sure entryscript is run when entering directory
let actual = nu!(
cwd: dirs.test(),
r#"autoenv trust foo
cd foo
ls | where name == "hello.txt" | get name"#
);
assert!(actual.out.contains("hello.txt"));
// Make sure entry scripts are also run when jumping over directory
let actual = nu!(
cwd: dirs.test(),
r#"autoenv trust foo
cd foo/bar
ls .. | where name == "../hello.txt" | get name"#
);
assert!(actual.out.contains("hello.txt"));
// Entryscripts should not run after changing to a subdirectory.
let actual = nu!(
cwd: dirs.test(),
r#"autoenv trust foo | = $nothing
cd foo
rm hello.txt
cd bar
ls .. | where name == "../hello.txt" | length"#
);
assert!(actual.out.contains("0"));
});
}
#[cfg(feature = "directories-support")]
#[cfg(feature = "which-support")]
#[test]
#[serial]
fn autoenv_test_exit_scripts() {
use nu_test_support::fs::Stub::FileWithContent;
Playground::setup("autoenv_test", |dirs, sandbox| {
sandbox.mkdir("foo/bar");
// Windows uses a different command to create an empty file so we need to have different content on windows.
let nu_env = r#"on_exit = ["touch bye.txt"]"#;
sandbox.with_files(vec![FileWithContent("foo/.nu-env", nu_env)]);
// Make sure exitscript is run
let actual = nu!(
cwd: dirs.test(),
r#"autoenv trust foo | = $nothing
cd foo
cd ..
ls foo | where name =~ "bye.txt" | length
rm foo/bye.txt; cd .
"#
);
assert_eq!(actual.out, "1");
// Entering a subdir should not trigger exitscripts
let actual = nu!(
cwd: dirs.test(),
r#"autoenv trust foo | = $nothing
cd foo
cd bar
ls .. | where name =~ "bye.txt" | length"#
);
assert_eq!(actual.out, "0");
// Also run exitscripts when jumping over directory
let actual = nu!(
cwd: dirs.test(),
r#"autoenv trust foo | = $nothing
cd foo/bar
cd ../..
ls foo | where name =~ "bye.txt" | length
rm foo/bye.txt; cd ."#
);
assert_eq!(actual.out, "1");
});
}
#[test]
#[serial]
#[cfg(unix)]
fn prepends_path_from_local_config() {
//If this test fails for you, make sure that your environment from which you start nu
//contains some env vars
Playground::setup("autoenv_test_1", |dirs, sandbox| {
sandbox.with_files(vec![FileWithContent(
".nu-env",
r#"
path = ["/hi", "/nushell"]
"#,
)]);
let expected = "[\"/hi\",\"/nushell\",";
let actual = Trusted::in_path(&dirs, || nu!(cwd: dirs.test(), "echo $nu.path | to json"));
// assert_eq!("", actual.out);
assert!(actual.out.starts_with(expected));
assert!(actual.out.len() > expected.len());
})
}