forked from extern/nushell
Autoenv rewrite, security and scripting (#2083)
* Add args in .nurc file to environment * Working dummy version * Add add_nurc to sync_env command * Parse .nurc file * Delete env vars after leaving directory * Removing vals not working, strangely * Refactoring, add comment * Debugging * Debug by logging to file * Add and remove env var behavior appears correct However, it does not use existing code that well. * Move work to cli.rs * Parse config directories * I am in a state of distress * Rename .nurc to .nu * Some notes for me * Refactoring * Removing vars works, but not done in a very nice fashion * Refactor env_vars_to_delete * Refactor env_vars_to_add() * Move directory environment code to separate file * Refactor from_config * Restore env values * Working? * Working? * Update comments and change var name * Formatting * Remove vars after leaving dir * Remove notes I made * Rename config function * Clippy * Cleanup and handle errors * cargo fmt * Better error messages, remove last (?) unwrap * FORMAT PLZ * Rename whitelisted_directories to allowed_directories * Add comment to clarify how overwritten values are restored. * Change list of allowed dirs to indexmap * Rewrite starting * rewrite everything * Overwritten env values tracks an indexmap instead of vector * Refactor restore function * Untrack removed vars properly * Performance concerns * Performance concerns * Error handling * Clippy * Add type aliases for String and OsString * Deletion almost works * Working? * Error handling and refactoring * nicer errors * Add TODO file * Move outside of loop * Error handling * Reworking adding of vars * Reworking adding of vars * Ready for testing * Refactoring * Restore overwritten vals code * todo.org * Remove overwritten values tracking, as it is not needed * Cleanup, stop tracking overwritten values as nu takes care of it * Init autoenv command * Initialize autoenv and autoenv trust * autoenv trust toml * toml * Use serde for autoenv * Optional directory arg * Add autoenv untrust command * ... actually add autoenv untrust this time * OsString and paths * Revert "OsString and paths" This reverts commite6eedf8824
. * Fix path * Fix path * Autoenv trust and untrust * Start using autoenv * Check hashes * Use trust functionality when setting vars * Remove unused code * Clippy * Nicer errors for autoenv commands * Non-working errors * Update error description * Satisfy fmt * Errors * Errors print, but not nicely * Nicer errors * fmt * Delete accidentally added todo.org file * Rename direnv to autoenv * Use ShellError instead of Error * Change tests to pass, danger zone? * Clippy and errors * Clippy... again * Replace match with or_else * Use sha2 crate for hashing * parsing and error msg * Refactoring * Only apply vars once * if parent dir * Delete vars * Rework exit code * Adding works * restore * Fix possibility of infinite loop * Refactoring * Non-working * Revert "Non-working" This reverts commite231b85570
. * Revert "Revert "Non-working"" This reverts commit804092e46a
. * Autoenv trust works without restart * Cargo fix * Script vars * Serde * Serde errors * Entry and exitscripts * Clippy * Support windows and handle errors * Formatting * Fix infinite loop on windows * Debugging windows loop * More windows infinite loop debugging * Windows loop debugging #3 * windows loop #4 * Don't return err * Cleanup unused code * Infinite loop debug * Loop debugging * Check if infinite loop is vars_to_add * env_vars_to_add does not terminate, skip loop as test * Hypothesis: std::env::current_dir() is messing with something * Hypothesis: std::env::current_dir() is messing with something * plz * make clippy happy * debugging in env_vars_to_add * Debbuging env_vars_to_add #2 * clippy * clippy.. * Fool clippy * Fix another infinite loop * Binary search for error location x) * Binary search #3 * fmt * Binary search #4 * more searching... * closing in... maybe * PLZ * Cleanup * Restore commented out functionality * Handle case when user gives the directory "." * fmt * Use fs::canonicalize for paths * Create optional script section * fmt * Add exitscripts even if no entryscripts are defined * All sections in .nu-env are now optional * Re-read config file each directory change * Hot reload after autoenv untrust, don't run exitscripts if untrusted * Debugging * Fix issue with recursive adding of vars * Thank you for finding my issues Mr. Azure * use std::env
This commit is contained in:
parent
9e82e5a2fa
commit
ee18f16378
64
Cargo.lock
generated
64
Cargo.lock
generated
@ -322,7 +322,16 @@ dependencies = [
|
|||||||
"block-padding",
|
"block-padding",
|
||||||
"byte-tools",
|
"byte-tools",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"generic-array",
|
"generic-array 0.12.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-buffer"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array 0.14.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -634,6 +643,12 @@ version = "0.7.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac"
|
checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpuid-bool"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6d375c433320f6c5057ae04a04376eef4d04ce2801448cf8863a78da99107be4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc32fast"
|
name = "crc32fast"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@ -866,7 +881,16 @@ version = "0.8.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
|
checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"generic-array",
|
"generic-array 0.12.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array 0.14.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1418,6 +1442,16 @@ dependencies = [
|
|||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "generic-array"
|
||||||
|
version = "0.14.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac746a5f3bbfdadd6106868134545e684693d54d9d44f6e9588a7d54af0bf980"
|
||||||
|
dependencies = [
|
||||||
|
"typenum",
|
||||||
|
"version_check 0.9.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gethostname"
|
name = "gethostname"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@ -2517,6 +2551,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
|
"sha2",
|
||||||
"shellexpand",
|
"shellexpand",
|
||||||
"starship",
|
"starship",
|
||||||
"strip-ansi-escapes",
|
"strip-ansi-escapes",
|
||||||
@ -2969,6 +3004,12 @@ version = "0.2.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
|
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "opaque-debug"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "open"
|
name = "open"
|
||||||
version = "1.4.0"
|
version = "1.4.0"
|
||||||
@ -3892,10 +3933,23 @@ version = "0.8.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
|
checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block-buffer",
|
"block-buffer 0.7.3",
|
||||||
"digest",
|
"digest 0.8.1",
|
||||||
"fake-simd",
|
"fake-simd",
|
||||||
"opaque-debug",
|
"opaque-debug 0.2.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha2"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2933378ddfeda7ea26f48c555bdad8bb446bf8a3d17832dc83e380d444cfb8c1"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer 0.9.0",
|
||||||
|
"cfg-if",
|
||||||
|
"cpuid-bool",
|
||||||
|
"digest 0.9.0",
|
||||||
|
"opaque-debug 0.3.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -75,6 +75,7 @@ serde_ini = "0.2.0"
|
|||||||
serde_json = "1.0.55"
|
serde_json = "1.0.55"
|
||||||
serde_urlencoded = "0.6.1"
|
serde_urlencoded = "0.6.1"
|
||||||
serde_yaml = "0.8"
|
serde_yaml = "0.8"
|
||||||
|
sha2 = "0.9.1"
|
||||||
shellexpand = "2.0.0"
|
shellexpand = "2.0.0"
|
||||||
strip-ansi-escapes = "0.1.0"
|
strip-ansi-escapes = "0.1.0"
|
||||||
tempfile = "3.1.0"
|
tempfile = "3.1.0"
|
||||||
|
@ -352,6 +352,9 @@ pub fn create_default_context(
|
|||||||
whole_stream_command(Headers),
|
whole_stream_command(Headers),
|
||||||
// Data processing
|
// Data processing
|
||||||
whole_stream_command(Histogram),
|
whole_stream_command(Histogram),
|
||||||
|
whole_stream_command(Autoenv),
|
||||||
|
whole_stream_command(AutoenvTrust),
|
||||||
|
whole_stream_command(AutoenvUnTrust),
|
||||||
whole_stream_command(Math),
|
whole_stream_command(Math),
|
||||||
whole_stream_command(MathAverage),
|
whole_stream_command(MathAverage),
|
||||||
whole_stream_command(MathMedian),
|
whole_stream_command(MathMedian),
|
||||||
|
@ -8,6 +8,9 @@ pub(crate) mod alias;
|
|||||||
pub(crate) mod ansi;
|
pub(crate) mod ansi;
|
||||||
pub(crate) mod append;
|
pub(crate) mod append;
|
||||||
pub(crate) mod args;
|
pub(crate) mod args;
|
||||||
|
pub(crate) mod autoenv;
|
||||||
|
pub(crate) mod autoenv_trust;
|
||||||
|
pub(crate) mod autoenv_untrust;
|
||||||
pub(crate) mod autoview;
|
pub(crate) mod autoview;
|
||||||
pub(crate) mod build_string;
|
pub(crate) mod build_string;
|
||||||
pub(crate) mod cal;
|
pub(crate) mod cal;
|
||||||
@ -142,6 +145,9 @@ pub(crate) use command::{
|
|||||||
pub(crate) use alias::Alias;
|
pub(crate) use alias::Alias;
|
||||||
pub(crate) use ansi::Ansi;
|
pub(crate) use ansi::Ansi;
|
||||||
pub(crate) use append::Append;
|
pub(crate) use append::Append;
|
||||||
|
pub(crate) use autoenv::Autoenv;
|
||||||
|
pub(crate) use autoenv_trust::AutoenvTrust;
|
||||||
|
pub(crate) use autoenv_untrust::AutoenvUnTrust;
|
||||||
pub(crate) use build_string::BuildString;
|
pub(crate) use build_string::BuildString;
|
||||||
pub(crate) use cal::Cal;
|
pub(crate) use cal::Cal;
|
||||||
pub(crate) use calc::Calc;
|
pub(crate) use calc::Calc;
|
||||||
|
82
crates/nu-cli/src/commands/autoenv.rs
Normal file
82
crates/nu-cli/src/commands/autoenv.rs
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
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::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: &PathBuf, content: &[u8]) -> Result<bool, ShellError> {
|
||||||
|
let contentdigest = Sha256::digest(&content).as_slice().to_vec();
|
||||||
|
let nufile = nu_env_file.to_str().unwrap_or("");
|
||||||
|
|
||||||
|
let trusted = read_trusted()?;
|
||||||
|
Ok(trusted.files.get(nufile) == 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)
|
||||||
|
.or_else(|_| {
|
||||||
|
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 {
|
||||||
|
"autoenv"
|
||||||
|
}
|
||||||
|
fn 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."
|
||||||
|
"Manage directory specific environments"
|
||||||
|
}
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("autoenv")
|
||||||
|
}
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
_args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let registry = registry.clone();
|
||||||
|
Ok(OutputStream::one(ReturnSuccess::value(
|
||||||
|
UntaggedValue::string(crate::commands::help::get_help(&Autoenv, ®istry))
|
||||||
|
.into_value(Tag::unknown()),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Allow .nu-env file in current directory",
|
||||||
|
example: "autoenv trust",
|
||||||
|
result: None,
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
74
crates/nu-cli/src/commands/autoenv_trust.rs
Normal file
74
crates/nu-cli/src/commands/autoenv_trust.rs
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
use super::autoenv::read_trusted;
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::SyntaxShape;
|
||||||
|
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
|
||||||
|
use sha2::{Digest, Sha256};
|
||||||
|
use std::{fs, path::PathBuf};
|
||||||
|
pub struct AutoenvTrust;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for AutoenvTrust {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"autoenv trust"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("autoenv trust").optional("dir", SyntaxShape::String, "Directory to allow")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Trust a .nu-env file in the current or given directory"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let tag = args.call_info.name_tag.clone();
|
||||||
|
|
||||||
|
let file_to_trust = match args.call_info.evaluate(registry).await?.args.nth(0) {
|
||||||
|
Some(Value {
|
||||||
|
value: UntaggedValue::Primitive(Primitive::String(ref path)),
|
||||||
|
tag: _,
|
||||||
|
}) => {
|
||||||
|
let mut dir = fs::canonicalize(path)?;
|
||||||
|
dir.push(".nu-env");
|
||||||
|
dir
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let mut dir = std::env::current_dir()?;
|
||||||
|
dir.push(".nu-env");
|
||||||
|
dir
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let content = std::fs::read(&file_to_trust)?;
|
||||||
|
|
||||||
|
let filename = file_to_trust.to_string_lossy().to_string();
|
||||||
|
let mut allowed = read_trusted()?;
|
||||||
|
allowed
|
||||||
|
.files
|
||||||
|
.insert(filename, Sha256::digest(&content).as_slice().to_vec());
|
||||||
|
|
||||||
|
let config_path = config::default_path_for(&Some(PathBuf::from("nu-env.toml")))?;
|
||||||
|
let tomlstr = toml::to_string(&allowed).or_else(|_| {
|
||||||
|
Err(ShellError::untagged_runtime_error(
|
||||||
|
"Couldn't serialize allowed dirs to nu-env.toml",
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
fs::write(config_path, tomlstr).expect("Couldn't write to toml file");
|
||||||
|
|
||||||
|
Ok(OutputStream::one(ReturnSuccess::value(
|
||||||
|
UntaggedValue::string(".nu-env trusted!").into_value(tag),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
fn is_binary(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
98
crates/nu-cli/src/commands/autoenv_untrust.rs
Normal file
98
crates/nu-cli/src/commands/autoenv_untrust.rs
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
use super::autoenv::Trusted;
|
||||||
|
use crate::commands::WholeStreamCommand;
|
||||||
|
use crate::prelude::*;
|
||||||
|
use nu_errors::ShellError;
|
||||||
|
use nu_protocol::SyntaxShape;
|
||||||
|
use nu_protocol::{Primitive, ReturnSuccess, Signature, UntaggedValue, Value};
|
||||||
|
use std::io::Read;
|
||||||
|
use std::{fs, path::PathBuf};
|
||||||
|
pub struct AutoenvUnTrust;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl WholeStreamCommand for AutoenvUnTrust {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"autoenv untrust"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("autoenv untrust").optional(
|
||||||
|
"dir",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"Directory to disallow",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Untrust a .nu-env file in the current or given directory"
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run(
|
||||||
|
&self,
|
||||||
|
args: CommandArgs,
|
||||||
|
registry: &CommandRegistry,
|
||||||
|
) -> Result<OutputStream, ShellError> {
|
||||||
|
let tag = args.call_info.name_tag.clone();
|
||||||
|
let file_to_untrust = match args.call_info.evaluate(registry).await?.args.nth(0) {
|
||||||
|
Some(Value {
|
||||||
|
value: UntaggedValue::Primitive(Primitive::String(ref path)),
|
||||||
|
tag: _,
|
||||||
|
}) => {
|
||||||
|
let mut dir = fs::canonicalize(path)?;
|
||||||
|
dir.push(".nu-env");
|
||||||
|
dir
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let mut dir = std::env::current_dir()?;
|
||||||
|
dir.push(".nu-env");
|
||||||
|
dir
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let config_path = config::default_path_for(&Some(PathBuf::from("nu-env.toml")))?;
|
||||||
|
|
||||||
|
let mut file = match std::fs::OpenOptions::new()
|
||||||
|
.read(true)
|
||||||
|
.create(true)
|
||||||
|
.write(true)
|
||||||
|
.open(config_path.clone())
|
||||||
|
{
|
||||||
|
Ok(p) => p,
|
||||||
|
Err(_) => {
|
||||||
|
return Err(ShellError::untagged_runtime_error(
|
||||||
|
"Couldn't open nu-env.toml",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut doc = String::new();
|
||||||
|
file.read_to_string(&mut doc)?;
|
||||||
|
|
||||||
|
let mut allowed: Trusted = toml::from_str(doc.as_str()).unwrap_or_else(|_| Trusted::new());
|
||||||
|
|
||||||
|
let file_to_untrust = file_to_untrust.to_string_lossy().to_string();
|
||||||
|
|
||||||
|
if allowed.files.remove(&file_to_untrust).is_none() {
|
||||||
|
return
|
||||||
|
Err(ShellError::untagged_runtime_error(
|
||||||
|
"No .nu-env file to untrust in the given directory. Is it missing, or already untrusted?",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let tomlstr = toml::to_string(&allowed).or_else(|_| {
|
||||||
|
Err(ShellError::untagged_runtime_error(
|
||||||
|
"Couldn't serialize allowed dirs to nu-env.toml",
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
fs::write(config_path, tomlstr).expect("Couldn't write to toml file");
|
||||||
|
|
||||||
|
Ok(OutputStream::one(ReturnSuccess::value(
|
||||||
|
UntaggedValue::string(".nu-env untrusted!").into_value(tag),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
fn is_binary(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
@ -28,6 +28,10 @@ pub(crate) async fn run_internal_command(
|
|||||||
let objects: InputStream = trace_stream!(target: "nu::trace_stream::internal", "input" = input);
|
let objects: InputStream = trace_stream!(target: "nu::trace_stream::internal", "input" = input);
|
||||||
let internal_command = context.expect_command(&command.name);
|
let internal_command = context.expect_command(&command.name);
|
||||||
|
|
||||||
|
if command.name == "autoenv untrust" {
|
||||||
|
context.user_recently_used_autoenv_untrust = true;
|
||||||
|
}
|
||||||
|
|
||||||
let result = {
|
let result = {
|
||||||
context
|
context
|
||||||
.run_command(
|
.run_command(
|
||||||
|
@ -75,6 +75,7 @@ impl WholeStreamCommand for RunExternalCommand {
|
|||||||
Context {
|
Context {
|
||||||
registry: registry.clone(),
|
registry: registry.clone(),
|
||||||
host: args.host.clone(),
|
host: args.host.clone(),
|
||||||
|
user_recently_used_autoenv_untrust: false,
|
||||||
shell_manager: args.shell_manager.clone(),
|
shell_manager: args.shell_manager.clone(),
|
||||||
ctrl_c: args.ctrl_c.clone(),
|
ctrl_c: args.ctrl_c.clone(),
|
||||||
current_errors: Arc::new(Mutex::new(vec![])),
|
current_errors: Arc::new(Mutex::new(vec![])),
|
||||||
@ -88,6 +89,7 @@ impl WholeStreamCommand for RunExternalCommand {
|
|||||||
{
|
{
|
||||||
Context {
|
Context {
|
||||||
registry: registry.clone(),
|
registry: registry.clone(),
|
||||||
|
user_recently_used_autoenv_untrust: false,
|
||||||
host: args.host.clone(),
|
host: args.host.clone(),
|
||||||
shell_manager: args.shell_manager.clone(),
|
shell_manager: args.shell_manager.clone(),
|
||||||
ctrl_c: args.ctrl_c.clone(),
|
ctrl_c: args.ctrl_c.clone(),
|
||||||
|
@ -76,6 +76,7 @@ pub struct Context {
|
|||||||
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
pub current_errors: Arc<Mutex<Vec<ShellError>>>,
|
||||||
pub ctrl_c: Arc<AtomicBool>,
|
pub ctrl_c: Arc<AtomicBool>,
|
||||||
pub raw_input: String,
|
pub raw_input: String,
|
||||||
|
pub user_recently_used_autoenv_untrust: bool,
|
||||||
pub(crate) shell_manager: ShellManager,
|
pub(crate) shell_manager: ShellManager,
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
@ -96,6 +97,7 @@ impl Context {
|
|||||||
current_errors: raw_args.current_errors.clone(),
|
current_errors: raw_args.current_errors.clone(),
|
||||||
ctrl_c: raw_args.ctrl_c.clone(),
|
ctrl_c: raw_args.ctrl_c.clone(),
|
||||||
shell_manager: raw_args.shell_manager.clone(),
|
shell_manager: raw_args.shell_manager.clone(),
|
||||||
|
user_recently_used_autoenv_untrust: false,
|
||||||
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
|
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
|
||||||
raw_input: String::default(),
|
raw_input: String::default(),
|
||||||
}
|
}
|
||||||
@ -108,6 +110,7 @@ impl Context {
|
|||||||
current_errors: raw_args.current_errors.clone(),
|
current_errors: raw_args.current_errors.clone(),
|
||||||
ctrl_c: raw_args.ctrl_c.clone(),
|
ctrl_c: raw_args.ctrl_c.clone(),
|
||||||
shell_manager: raw_args.shell_manager.clone(),
|
shell_manager: raw_args.shell_manager.clone(),
|
||||||
|
user_recently_used_autoenv_untrust: false,
|
||||||
raw_input: String::default(),
|
raw_input: String::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,6 +125,7 @@ impl Context {
|
|||||||
current_errors: args.current_errors.clone(),
|
current_errors: args.current_errors.clone(),
|
||||||
ctrl_c: args.ctrl_c.clone(),
|
ctrl_c: args.ctrl_c.clone(),
|
||||||
shell_manager: args.shell_manager.clone(),
|
shell_manager: args.shell_manager.clone(),
|
||||||
|
user_recently_used_autoenv_untrust: false,
|
||||||
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
|
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
|
||||||
raw_input: String::default(),
|
raw_input: String::default(),
|
||||||
}
|
}
|
||||||
@ -133,6 +137,7 @@ impl Context {
|
|||||||
host: args.host.clone(),
|
host: args.host.clone(),
|
||||||
current_errors: args.current_errors.clone(),
|
current_errors: args.current_errors.clone(),
|
||||||
ctrl_c: args.ctrl_c.clone(),
|
ctrl_c: args.ctrl_c.clone(),
|
||||||
|
user_recently_used_autoenv_untrust: false,
|
||||||
shell_manager: args.shell_manager.clone(),
|
shell_manager: args.shell_manager.clone(),
|
||||||
raw_input: String::default(),
|
raw_input: String::default(),
|
||||||
}
|
}
|
||||||
@ -151,6 +156,7 @@ impl Context {
|
|||||||
))),
|
))),
|
||||||
current_errors: Arc::new(Mutex::new(vec![])),
|
current_errors: Arc::new(Mutex::new(vec![])),
|
||||||
ctrl_c: Arc::new(AtomicBool::new(false)),
|
ctrl_c: Arc::new(AtomicBool::new(false)),
|
||||||
|
user_recently_used_autoenv_untrust: false,
|
||||||
shell_manager: ShellManager::basic(registry)?,
|
shell_manager: ShellManager::basic(registry)?,
|
||||||
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
|
windows_drives_previous_cwd: Arc::new(Mutex::new(std::collections::HashMap::new())),
|
||||||
raw_input: String::default(),
|
raw_input: String::default(),
|
||||||
@ -166,6 +172,7 @@ impl Context {
|
|||||||
))),
|
))),
|
||||||
current_errors: Arc::new(Mutex::new(vec![])),
|
current_errors: Arc::new(Mutex::new(vec![])),
|
||||||
ctrl_c: Arc::new(AtomicBool::new(false)),
|
ctrl_c: Arc::new(AtomicBool::new(false)),
|
||||||
|
user_recently_used_autoenv_untrust: false,
|
||||||
shell_manager: ShellManager::basic(registry)?,
|
shell_manager: ShellManager::basic(registry)?,
|
||||||
raw_input: String::default(),
|
raw_input: String::default(),
|
||||||
})
|
})
|
||||||
|
@ -4,7 +4,6 @@ use std::fmt::Debug;
|
|||||||
pub trait Conf: Debug + Send {
|
pub trait Conf: Debug + Send {
|
||||||
fn env(&self) -> Option<Value>;
|
fn env(&self) -> Option<Value>;
|
||||||
fn path(&self) -> Option<Value>;
|
fn path(&self) -> Option<Value>;
|
||||||
fn nu_env_dirs(&self) -> Option<Value>;
|
|
||||||
fn reload(&self);
|
fn reload(&self);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -13,10 +12,6 @@ impl Conf for Box<dyn Conf> {
|
|||||||
(**self).env()
|
(**self).env()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn nu_env_dirs(&self) -> Option<Value> {
|
|
||||||
(**self).nu_env_dirs()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn path(&self) -> Option<Value> {
|
fn path(&self) -> Option<Value> {
|
||||||
(**self).path()
|
(**self).path()
|
||||||
}
|
}
|
||||||
|
@ -20,10 +20,6 @@ impl Conf for NuConfig {
|
|||||||
self.path()
|
self.path()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn nu_env_dirs(&self) -> Option<Value> {
|
|
||||||
self.nu_env_dirs()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reload(&self) {
|
fn reload(&self) {
|
||||||
let mut vars = self.vars.lock();
|
let mut vars = self.vars.lock();
|
||||||
|
|
||||||
@ -56,14 +52,6 @@ impl NuConfig {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn nu_env_dirs(&self) -> Option<Value> {
|
|
||||||
let vars = self.vars.lock();
|
|
||||||
if let Some(dirs) = vars.get("nu_env_dirs") {
|
|
||||||
return Some(dirs.clone());
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn path(&self) -> Option<Value> {
|
pub fn path(&self) -> Option<Value> {
|
||||||
let vars = self.vars.lock();
|
let vars = self.vars.lock();
|
||||||
|
|
||||||
|
@ -16,10 +16,6 @@ impl Conf for FakeConfig {
|
|||||||
self.config.env()
|
self.config.env()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn nu_env_dirs(&self) -> Option<Value> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn path(&self) -> Option<Value> {
|
fn path(&self) -> Option<Value> {
|
||||||
self.config.path()
|
self.config.path()
|
||||||
}
|
}
|
||||||
|
@ -1,192 +1,238 @@
|
|||||||
use indexmap::IndexMap;
|
use crate::commands;
|
||||||
use nu_protocol::{Primitive, UntaggedValue, Value};
|
use commands::autoenv;
|
||||||
use std::io::{Error, ErrorKind, Result};
|
use indexmap::{IndexMap, IndexSet};
|
||||||
use std::{ffi::OsString, fmt::Debug, path::PathBuf};
|
use nu_errors::ShellError;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::cmp::Ordering::Less;
|
||||||
|
use std::env::*;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
ffi::OsString,
|
||||||
|
fmt::Debug,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
type EnvKey = String;
|
||||||
|
type EnvVal = OsString;
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct DirectorySpecificEnvironment {
|
pub struct DirectorySpecificEnvironment {
|
||||||
allowed_directories: Vec<PathBuf>,
|
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_env_vars: IndexMap<PathBuf, IndexMap<EnvKey, Option<EnvVal>>>,
|
||||||
|
exitscripts: IndexMap<PathBuf, Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
//Directory -> Env key. 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.
|
#[derive(Deserialize, Debug, Default)]
|
||||||
added_env_vars: IndexMap<PathBuf, Vec<String>>,
|
pub struct NuEnvDoc {
|
||||||
|
pub env: Option<IndexMap<String, String>>,
|
||||||
//Directory -> (env_key, value). If a .nu overwrites some existing environment variables, they are added here so that they can be restored later.
|
pub scriptvars: Option<IndexMap<String, String>>,
|
||||||
overwritten_env_values: IndexMap<PathBuf, Vec<(String, OsString)>>,
|
pub scripts: Option<IndexMap<String, Vec<String>>>,
|
||||||
|
pub entryscripts: Option<Vec<String>>,
|
||||||
|
pub exitscripts: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DirectorySpecificEnvironment {
|
impl DirectorySpecificEnvironment {
|
||||||
pub fn new(allowed_directories: Option<Value>) -> DirectorySpecificEnvironment {
|
pub fn new() -> DirectorySpecificEnvironment {
|
||||||
let mut allowed_directories = if let Some(Value {
|
let root_dir = if cfg!(target_os = "windows") {
|
||||||
value: UntaggedValue::Table(ref wrapped_directories),
|
PathBuf::from("c:\\")
|
||||||
..
|
|
||||||
}) = allowed_directories
|
|
||||||
{
|
|
||||||
wrapped_directories
|
|
||||||
.iter()
|
|
||||||
.filter_map(|dirval| {
|
|
||||||
if let Value {
|
|
||||||
value: UntaggedValue::Primitive(Primitive::String(ref dir)),
|
|
||||||
..
|
|
||||||
} = dirval
|
|
||||||
{
|
|
||||||
return Some(PathBuf::from(&dir));
|
|
||||||
}
|
|
||||||
None
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
} else {
|
} else {
|
||||||
vec![]
|
PathBuf::from("/")
|
||||||
};
|
};
|
||||||
allowed_directories.sort();
|
|
||||||
|
|
||||||
DirectorySpecificEnvironment {
|
DirectorySpecificEnvironment {
|
||||||
allowed_directories,
|
last_seen_directory: root_dir,
|
||||||
added_env_vars: IndexMap::new(),
|
added_env_vars: IndexMap::new(),
|
||||||
overwritten_env_values: IndexMap::new(),
|
exitscripts: IndexMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//If we are no longer in a directory, we restore the values it overwrote.
|
fn toml_if_directory_is_trusted(
|
||||||
pub fn overwritten_values_to_restore(&mut self) -> Result<IndexMap<String, String>> {
|
&mut self,
|
||||||
let current_dir = std::env::current_dir()?;
|
nu_env_file: &PathBuf,
|
||||||
|
) -> Result<NuEnvDoc, ShellError> {
|
||||||
|
let content = std::fs::read(&nu_env_file)?;
|
||||||
|
|
||||||
let mut keyvals_to_restore = IndexMap::new();
|
if autoenv::file_is_trusted(&nu_env_file, &content)? {
|
||||||
let mut new_overwritten = IndexMap::new();
|
let mut doc: NuEnvDoc = toml::de::from_slice(&content)
|
||||||
|
.or_else(|e| Err(ShellError::untagged_runtime_error(format!("{:?}", e))))?;
|
||||||
|
|
||||||
for (directory, keyvals) in &self.overwritten_env_values {
|
if let Some(scripts) = doc.scripts.as_ref() {
|
||||||
let mut working_dir = Some(current_dir.as_path());
|
for (k, v) in scripts {
|
||||||
|
if k == "entryscripts" {
|
||||||
let mut re_add_keyvals = true;
|
doc.entryscripts = Some(v.clone());
|
||||||
while let Some(wdir) = working_dir {
|
} else if k == "exitscripts" {
|
||||||
if wdir == directory.as_path() {
|
doc.exitscripts = Some(v.clone());
|
||||||
re_add_keyvals = false;
|
|
||||||
new_overwritten.insert(directory.clone(), keyvals.clone());
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
working_dir = working_dir //Keep going up in the directory structure with .parent()
|
|
||||||
.ok_or_else(|| {
|
|
||||||
Error::new(ErrorKind::NotFound, "Root directory has no parent")
|
|
||||||
})?
|
|
||||||
.parent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if re_add_keyvals {
|
|
||||||
for (k, v) in keyvals {
|
|
||||||
keyvals_to_restore.insert(
|
|
||||||
k.clone(),
|
|
||||||
v.to_str()
|
|
||||||
.ok_or_else(|| {
|
|
||||||
Error::new(
|
|
||||||
ErrorKind::Other,
|
|
||||||
format!("{:?} is not valid unicode", v),
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
.to_string(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.overwritten_env_values = new_overwritten;
|
|
||||||
Ok(keyvals_to_restore)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn env_vars_to_add(&mut self) -> Result<IndexMap<String, String>> {
|
|
||||||
let current_dir = std::env::current_dir()?;
|
|
||||||
|
|
||||||
let mut vars_to_add = IndexMap::new();
|
|
||||||
for dir in &self.allowed_directories {
|
|
||||||
let mut working_dir = Some(current_dir.as_path());
|
|
||||||
|
|
||||||
//Start in the current directory, then traverse towards the root with working_dir to see if we are in a subdirectory of a valid directory.
|
|
||||||
while let Some(wdir) = working_dir {
|
|
||||||
if wdir == dir.as_path() {
|
|
||||||
let toml_doc = std::fs::read_to_string(wdir.join(".nu-env").as_path())?
|
|
||||||
.parse::<toml::Value>()?;
|
|
||||||
|
|
||||||
let vars_in_current_file = toml_doc
|
|
||||||
.get("env")
|
|
||||||
.ok_or_else(|| {
|
|
||||||
std::io::Error::new(
|
|
||||||
std::io::ErrorKind::InvalidData,
|
|
||||||
"No [env] section in .nu-env",
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
.as_table()
|
|
||||||
.ok_or_else(|| {
|
|
||||||
Error::new(ErrorKind::InvalidData, "Malformed [env] section in .nu-env")
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut keys_in_current_nufile = vec![];
|
|
||||||
for (k, v) in vars_in_current_file {
|
|
||||||
vars_to_add.insert(
|
|
||||||
k.clone(),
|
|
||||||
v.as_str()
|
|
||||||
.ok_or_else(|| {
|
|
||||||
Error::new(
|
|
||||||
ErrorKind::InvalidData,
|
|
||||||
format!("Could not read environment variable: {}", v),
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
.to_string(),
|
|
||||||
); //This is used to add variables to the environment
|
|
||||||
keys_in_current_nufile.push(k.clone()); //this is used to keep track of which directory added which variables
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//If we are about to overwrite any environment variables, we save them first so they can be restored later.
|
|
||||||
self.overwritten_env_values.insert(
|
|
||||||
wdir.to_path_buf(),
|
|
||||||
keys_in_current_nufile
|
|
||||||
.iter()
|
|
||||||
.filter_map(|key| {
|
|
||||||
if let Some(val) = std::env::var_os(key) {
|
|
||||||
return Some((key.clone(), val));
|
|
||||||
}
|
|
||||||
None
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
);
|
|
||||||
|
|
||||||
self.added_env_vars
|
|
||||||
.insert(wdir.to_path_buf(), keys_in_current_nufile);
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
working_dir = working_dir //Keep going up in the directory structure with .parent()
|
|
||||||
.ok_or_else(|| {
|
|
||||||
Error::new(ErrorKind::NotFound, "Root directory has no parent")
|
|
||||||
})?
|
|
||||||
.parent();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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 env_vars_to_add(&mut self) -> Result<IndexMap<EnvKey, EnvVal>, ShellError> {
|
||||||
|
let mut dir = current_dir()?;
|
||||||
|
let mut vars_to_add: IndexMap<EnvKey, EnvVal> = IndexMap::new();
|
||||||
|
|
||||||
|
//If we are in the last seen directory, do nothing
|
||||||
|
//If we are in a parent directory to last_seen_directory, just return without applying .nu-env in the parent directory - they were already applied earlier.
|
||||||
|
//parent.cmp(child) = Less
|
||||||
|
let mut popped = true;
|
||||||
|
while self.last_seen_directory.cmp(&dir) == Less && popped {
|
||||||
|
let nu_env_file = dir.join(".nu-env");
|
||||||
|
if nu_env_file.exists() {
|
||||||
|
let nu_env_doc = self.toml_if_directory_is_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.add_key_if_appropriate(&mut vars_to_add, &dir, &env_key, &env_val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add variables that need to evaluate scripts to run, from [scriptvars] section
|
||||||
|
if let Some(scriptvars) = nu_env_doc.scriptvars {
|
||||||
|
for (env_key, dir_val_script) in scriptvars {
|
||||||
|
let command = if cfg!(target_os = "windows") {
|
||||||
|
Command::new("cmd")
|
||||||
|
.args(&["/C", dir_val_script.as_str()])
|
||||||
|
.output()?
|
||||||
|
} else {
|
||||||
|
Command::new("sh").arg("-c").arg(&dir_val_script).output()?
|
||||||
|
};
|
||||||
|
if command.stdout.is_empty() {
|
||||||
|
return Err(ShellError::untagged_runtime_error(format!(
|
||||||
|
"{:?} in {:?} did not return any output",
|
||||||
|
dir_val_script, dir
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
let response =
|
||||||
|
std::str::from_utf8(&command.stdout[..command.stdout.len() - 1])
|
||||||
|
.or_else(|e| {
|
||||||
|
Err(ShellError::untagged_runtime_error(format!(
|
||||||
|
"Couldn't parse stdout from command {:?}: {:?}",
|
||||||
|
command, e
|
||||||
|
)))
|
||||||
|
})?;
|
||||||
|
self.add_key_if_appropriate(
|
||||||
|
&mut vars_to_add,
|
||||||
|
&dir,
|
||||||
|
&env_key,
|
||||||
|
&response.to_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(entryscripts) = nu_env_doc.entryscripts {
|
||||||
|
for script in entryscripts {
|
||||||
|
if cfg!(target_os = "windows") {
|
||||||
|
Command::new("cmd")
|
||||||
|
.args(&["/C", script.as_str()])
|
||||||
|
.output()?;
|
||||||
|
} else {
|
||||||
|
Command::new("sh").arg("-c").arg(script).output()?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(exitscripts) = nu_env_doc.exitscripts {
|
||||||
|
self.exitscripts.insert(dir.clone(), exitscripts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
popped = dir.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(vars_to_add)
|
Ok(vars_to_add)
|
||||||
}
|
}
|
||||||
|
|
||||||
//If the user has left directories which added env vars through .nu, we clear those vars
|
pub fn add_key_if_appropriate(
|
||||||
pub fn env_vars_to_delete(&mut self) -> Result<Vec<String>> {
|
&mut self,
|
||||||
let current_dir = std::env::current_dir()?;
|
vars_to_add: &mut IndexMap<EnvKey, EnvVal>,
|
||||||
|
dir: &PathBuf,
|
||||||
|
env_key: &str,
|
||||||
|
env_val: &str,
|
||||||
|
) {
|
||||||
|
//This condition is to make sure variables in parent directories don't overwrite variables set by subdirectories.
|
||||||
|
if !vars_to_add.contains_key(env_key) {
|
||||||
|
vars_to_add.insert(env_key.to_string(), OsString::from(env_val));
|
||||||
|
self.added_env_vars
|
||||||
|
.entry(dir.clone())
|
||||||
|
.or_insert(IndexMap::new())
|
||||||
|
.insert(env_key.to_string(), var_os(env_key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//Gather up all environment variables that should be deleted.
|
pub fn cleanup_after_dir_exit(
|
||||||
//If we are not in a directory or one of its subdirectories, mark the env_vals it maps to for removal.
|
&mut self,
|
||||||
let vars_to_delete = self.added_env_vars.iter().fold(
|
) -> Result<IndexMap<EnvKey, Option<EnvVal>>, ShellError> {
|
||||||
Vec::new(),
|
let current_dir = current_dir()?;
|
||||||
|mut vars_to_delete, (directory, env_vars)| {
|
let mut vars_to_cleanup = IndexMap::new();
|
||||||
let mut working_dir = Some(current_dir.as_path());
|
|
||||||
|
|
||||||
while let Some(wdir) = working_dir {
|
//If we are in the same directory as last_seen, or a subdirectory to it, do nothing
|
||||||
if wdir == directory {
|
//If we are in a subdirectory to last seen, do nothing
|
||||||
return vars_to_delete;
|
//If we are in a parent directory to last seen, exit .nu-envs from last seen to parent and restore old vals
|
||||||
|
let mut dir = self.last_seen_directory.clone();
|
||||||
|
|
||||||
|
let mut popped = true;
|
||||||
|
while current_dir.cmp(&dir) == Less && popped {
|
||||||
|
if let Some(vars_added_by_this_directory) = self.added_env_vars.get(&dir) {
|
||||||
|
for (k, v) in vars_added_by_this_directory {
|
||||||
|
vars_to_cleanup.insert(k.clone(), v.clone());
|
||||||
|
}
|
||||||
|
self.added_env_vars.remove(&dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(scripts) = self.exitscripts.get(&dir) {
|
||||||
|
for script in scripts {
|
||||||
|
if cfg!(target_os = "windows") {
|
||||||
|
Command::new("cmd")
|
||||||
|
.args(&["/C", script.as_str()])
|
||||||
|
.output()?;
|
||||||
} else {
|
} else {
|
||||||
working_dir = working_dir.expect("Root directory has no parent").parent();
|
Command::new("sh").arg("-c").arg(script).output()?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//only delete vars from directories we are not in
|
}
|
||||||
vars_to_delete.extend(env_vars.clone());
|
popped = dir.pop();
|
||||||
vars_to_delete
|
}
|
||||||
},
|
Ok(vars_to_cleanup)
|
||||||
);
|
}
|
||||||
|
|
||||||
Ok(vars_to_delete)
|
// 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_env_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_env_vars.get(&path) {
|
||||||
|
for (key, _) in added_keys {
|
||||||
|
remove_var(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.exitscripts.remove(&path);
|
||||||
|
self.added_env_vars.remove(&path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
65
crates/nu-cli/src/env/environment.rs
vendored
65
crates/nu-cli/src/env/environment.rs
vendored
@ -1,15 +1,18 @@
|
|||||||
use crate::data::config::Conf;
|
use crate::data::config::Conf;
|
||||||
use crate::env::directory_specific_environment::*;
|
use crate::env::directory_specific_environment::*;
|
||||||
use indexmap::{indexmap, IndexSet};
|
use indexmap::{indexmap, IndexSet};
|
||||||
|
use nu_errors::ShellError;
|
||||||
use nu_protocol::{UntaggedValue, Value};
|
use nu_protocol::{UntaggedValue, Value};
|
||||||
|
use std::env::*;
|
||||||
use std::ffi::OsString;
|
use std::ffi::OsString;
|
||||||
|
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
||||||
pub trait Env: Debug + Send {
|
pub trait Env: Debug + Send {
|
||||||
fn env(&self) -> Option<Value>;
|
fn env(&self) -> Option<Value>;
|
||||||
fn path(&self) -> Option<Value>;
|
fn path(&self) -> Option<Value>;
|
||||||
|
|
||||||
fn add_env(&mut self, key: &str, value: &str, overwrite_existing: bool);
|
fn add_env(&mut self, key: &str, value: &str);
|
||||||
fn add_path(&mut self, new_path: OsString);
|
fn add_path(&mut self, new_path: OsString);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,8 +25,8 @@ impl Env for Box<dyn Env> {
|
|||||||
(**self).path()
|
(**self).path()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_env(&mut self, key: &str, value: &str, overwrite_existing: bool) {
|
fn add_env(&mut self, key: &str, value: &str) {
|
||||||
(**self).add_env(key, value, overwrite_existing);
|
(**self).add_env(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_path(&mut self, new_path: OsString) {
|
fn add_path(&mut self, new_path: OsString) {
|
||||||
@ -35,7 +38,7 @@ impl Env for Box<dyn Env> {
|
|||||||
pub struct Environment {
|
pub struct Environment {
|
||||||
environment_vars: Option<Value>,
|
environment_vars: Option<Value>,
|
||||||
path_vars: Option<Value>,
|
path_vars: Option<Value>,
|
||||||
pub direnv: DirectorySpecificEnvironment,
|
pub autoenv: DirectorySpecificEnvironment,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Environment {
|
impl Environment {
|
||||||
@ -43,49 +46,41 @@ impl Environment {
|
|||||||
Environment {
|
Environment {
|
||||||
environment_vars: None,
|
environment_vars: None,
|
||||||
path_vars: None,
|
path_vars: None,
|
||||||
direnv: DirectorySpecificEnvironment::new(None),
|
autoenv: DirectorySpecificEnvironment::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_config<T: Conf>(configuration: &T) -> Environment {
|
pub fn from_config<T: Conf>(configuration: &T) -> Environment {
|
||||||
let env = configuration.env();
|
let env = configuration.env();
|
||||||
let path = configuration.path();
|
let path = configuration.path();
|
||||||
|
|
||||||
Environment {
|
Environment {
|
||||||
environment_vars: env,
|
environment_vars: env,
|
||||||
path_vars: path,
|
path_vars: path,
|
||||||
direnv: DirectorySpecificEnvironment::new(configuration.nu_env_dirs()),
|
autoenv: DirectorySpecificEnvironment::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn maintain_directory_environment(&mut self) -> std::io::Result<()> {
|
pub fn autoenv(&mut self, reload_trusted: bool) -> Result<(), ShellError> {
|
||||||
self.direnv.env_vars_to_delete()?.iter().for_each(|k| {
|
for (k, v) in self.autoenv.env_vars_to_add()? {
|
||||||
self.remove_env(&k);
|
set_var(&k, OsString::from(v.to_string_lossy().to_string()));
|
||||||
});
|
}
|
||||||
self.direnv.env_vars_to_add()?.iter().for_each(|(k, v)| {
|
|
||||||
self.add_env(&k, &v, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
self.direnv
|
for (k, v) in self.autoenv.cleanup_after_dir_exit()? {
|
||||||
.overwritten_values_to_restore()?
|
if let Some(v) = v {
|
||||||
.iter()
|
set_var(k, v);
|
||||||
.for_each(|(k, v)| {
|
} else {
|
||||||
self.add_env(&k, &v, true);
|
remove_var(k);
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if reload_trusted {
|
||||||
|
self.autoenv.clear_recently_untrusted_file()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.autoenv.last_seen_directory = current_dir()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_env(&mut self, key: &str) {
|
|
||||||
if let Some(Value {
|
|
||||||
value: UntaggedValue::Row(ref mut envs),
|
|
||||||
..
|
|
||||||
}) = self.environment_vars
|
|
||||||
{
|
|
||||||
envs.entries.remove(key);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn morph<T: Conf>(&mut self, configuration: &T) {
|
pub fn morph<T: Conf>(&mut self, configuration: &T) {
|
||||||
self.environment_vars = configuration.env();
|
self.environment_vars = configuration.env();
|
||||||
self.path_vars = configuration.path();
|
self.path_vars = configuration.path();
|
||||||
@ -109,7 +104,7 @@ impl Env for Environment {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_env(&mut self, key: &str, value: &str, overwrite_existing: bool) {
|
fn add_env(&mut self, key: &str, value: &str) {
|
||||||
let value = UntaggedValue::string(value);
|
let value = UntaggedValue::string(value);
|
||||||
|
|
||||||
let new_envs = {
|
let new_envs = {
|
||||||
@ -120,7 +115,7 @@ impl Env for Environment {
|
|||||||
{
|
{
|
||||||
let mut new_envs = envs.clone();
|
let mut new_envs = envs.clone();
|
||||||
|
|
||||||
if !new_envs.contains_key(key) || overwrite_existing {
|
if !new_envs.contains_key(key) {
|
||||||
new_envs.insert_data_at_key(key, value.into_value(tag.clone()));
|
new_envs.insert_data_at_key(key, value.into_value(tag.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,7 +141,7 @@ impl Env for Environment {
|
|||||||
{
|
{
|
||||||
let mut new_paths = current_paths.clone();
|
let mut new_paths = current_paths.clone();
|
||||||
|
|
||||||
let new_path_candidates = std::env::split_paths(&paths).map(|path| {
|
let new_path_candidates = split_paths(&paths).map(|path| {
|
||||||
UntaggedValue::string(path.to_string_lossy()).into_value(tag.clone())
|
UntaggedValue::string(path.to_string_lossy()).into_value(tag.clone())
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -238,7 +233,7 @@ mod tests {
|
|||||||
let fake_config = FakeConfig::new(&file);
|
let fake_config = FakeConfig::new(&file);
|
||||||
let mut actual = Environment::from_config(&fake_config);
|
let mut actual = Environment::from_config(&fake_config);
|
||||||
|
|
||||||
actual.add_env("USER", "NUNO", false);
|
actual.add_env("USER", "NUNO");
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actual.env(),
|
actual.env(),
|
||||||
@ -271,7 +266,7 @@ mod tests {
|
|||||||
let fake_config = FakeConfig::new(&file);
|
let fake_config = FakeConfig::new(&file);
|
||||||
let mut actual = Environment::from_config(&fake_config);
|
let mut actual = Environment::from_config(&fake_config);
|
||||||
|
|
||||||
actual.add_env("SHELL", "/usr/bin/sh", false);
|
actual.add_env("SHELL", "/usr/bin/sh");
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
actual.env(),
|
actual.env(),
|
||||||
|
84
crates/nu-cli/src/env/environment_syncer.rs
vendored
84
crates/nu-cli/src/env/environment_syncer.rs
vendored
@ -1,6 +1,8 @@
|
|||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::data::config::{Conf, NuConfig};
|
use crate::data::config::{Conf, NuConfig};
|
||||||
|
|
||||||
use crate::env::environment::{Env, Environment};
|
use crate::env::environment::{Env, Environment};
|
||||||
|
use nu_source::Text;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -44,14 +46,16 @@ impl EnvironmentSyncer {
|
|||||||
pub fn sync_env_vars(&mut self, ctx: &mut Context) {
|
pub fn sync_env_vars(&mut self, ctx: &mut Context) {
|
||||||
let mut environment = self.env.lock();
|
let mut environment = self.env.lock();
|
||||||
|
|
||||||
|
if let Err(e) = environment.autoenv(ctx.user_recently_used_autoenv_untrust) {
|
||||||
|
crate::cli::print_err(e, &Text::from(""));
|
||||||
|
}
|
||||||
|
ctx.user_recently_used_autoenv_untrust = false;
|
||||||
if environment.env().is_some() {
|
if environment.env().is_some() {
|
||||||
for (name, value) in ctx.with_host(|host| host.vars()) {
|
for (name, value) in ctx.with_host(|host| host.vars()) {
|
||||||
if name != "path" && name != "PATH" {
|
if name != "path" && name != "PATH" {
|
||||||
// account for new env vars present in the current session
|
// account for new env vars present in the current session
|
||||||
// that aren't loaded from config.
|
// that aren't loaded from config.
|
||||||
environment.add_env(&name, &value, false);
|
environment.add_env(&name, &value);
|
||||||
|
|
||||||
environment.maintain_directory_environment().ok();
|
|
||||||
|
|
||||||
// clear the env var from the session
|
// clear the env var from the session
|
||||||
// we are about to replace them
|
// we are about to replace them
|
||||||
@ -126,6 +130,7 @@ mod tests {
|
|||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::data::config::tests::FakeConfig;
|
use crate::data::config::tests::FakeConfig;
|
||||||
use crate::env::environment::Env;
|
use crate::env::environment::Env;
|
||||||
|
use indexmap::IndexMap;
|
||||||
use nu_errors::ShellError;
|
use nu_errors::ShellError;
|
||||||
use nu_test_support::fs::Stub::FileWithContent;
|
use nu_test_support::fs::Stub::FileWithContent;
|
||||||
use nu_test_support::playground::Playground;
|
use nu_test_support::playground::Playground;
|
||||||
@ -139,13 +144,12 @@ mod tests {
|
|||||||
let mut ctx = Context::basic()?;
|
let mut ctx = Context::basic()?;
|
||||||
ctx.host = Arc::new(Mutex::new(Box::new(crate::env::host::FakeHost::new())));
|
ctx.host = Arc::new(Mutex::new(Box::new(crate::env::host::FakeHost::new())));
|
||||||
|
|
||||||
let expected = vec![
|
let mut expected = IndexMap::new();
|
||||||
(
|
expected.insert(
|
||||||
"SHELL".to_string(),
|
"SHELL".to_string(),
|
||||||
"/usr/bin/you_already_made_the_nu_choice".to_string(),
|
"/usr/bin/you_already_made_the_nu_choice".to_string(),
|
||||||
),
|
);
|
||||||
("USER".to_string(), "NUNO".to_string()),
|
expected.insert("USER".to_string(), "NUNO".to_string());
|
||||||
];
|
|
||||||
|
|
||||||
Playground::setup("syncs_env_test_1", |dirs, sandbox| {
|
Playground::setup("syncs_env_test_1", |dirs, sandbox| {
|
||||||
sandbox.with_files(vec![FileWithContent(
|
sandbox.with_files(vec![FileWithContent(
|
||||||
@ -203,33 +207,34 @@ mod tests {
|
|||||||
.into_string()
|
.into_string()
|
||||||
.expect("Couldn't convert to string.");
|
.expect("Couldn't convert to string.");
|
||||||
|
|
||||||
let actual = vec![
|
let mut found = IndexMap::new();
|
||||||
("SHELL".to_string(), var_shell),
|
found.insert("SHELL".to_string(), var_shell);
|
||||||
("USER".to_string(), var_user),
|
found.insert("USER".to_string(), var_user);
|
||||||
];
|
|
||||||
|
|
||||||
assert_eq!(actual, expected);
|
for k in found.keys() {
|
||||||
|
assert!(expected.contains_key(k));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Now confirm in-memory environment variables synced appropriately
|
// Now confirm in-memory environment variables synced appropriately
|
||||||
// including the newer one accounted for.
|
// including the newer one accounted for.
|
||||||
let environment = actual.env.lock();
|
let environment = actual.env.lock();
|
||||||
|
|
||||||
let vars = environment
|
let mut vars = IndexMap::new();
|
||||||
|
environment
|
||||||
.env()
|
.env()
|
||||||
.expect("No variables in the environment.")
|
.expect("No variables in the environment.")
|
||||||
.row_entries()
|
.row_entries()
|
||||||
.map(|(name, value)| {
|
.for_each(|(name, value)| {
|
||||||
(
|
vars.insert(
|
||||||
name.to_string(),
|
name.to_string(),
|
||||||
value.as_string().expect("Couldn't convert to string"),
|
value.as_string().expect("Couldn't convert to string"),
|
||||||
)
|
);
|
||||||
})
|
});
|
||||||
.collect::<Vec<_>>();
|
for k in expected.keys() {
|
||||||
|
assert!(vars.contains_key(k));
|
||||||
assert_eq!(vars, expected);
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,10 +243,11 @@ mod tests {
|
|||||||
let mut ctx = Context::basic()?;
|
let mut ctx = Context::basic()?;
|
||||||
ctx.host = Arc::new(Mutex::new(Box::new(crate::env::host::FakeHost::new())));
|
ctx.host = Arc::new(Mutex::new(Box::new(crate::env::host::FakeHost::new())));
|
||||||
|
|
||||||
let expected = vec![(
|
let mut expected = IndexMap::new();
|
||||||
|
expected.insert(
|
||||||
"SHELL".to_string(),
|
"SHELL".to_string(),
|
||||||
"/usr/bin/you_already_made_the_nu_choice".to_string(),
|
"/usr/bin/you_already_made_the_nu_choice".to_string(),
|
||||||
)];
|
);
|
||||||
|
|
||||||
Playground::setup("syncs_env_test_2", |dirs, sandbox| {
|
Playground::setup("syncs_env_test_2", |dirs, sandbox| {
|
||||||
sandbox.with_files(vec![FileWithContent(
|
sandbox.with_files(vec![FileWithContent(
|
||||||
@ -278,26 +284,30 @@ mod tests {
|
|||||||
.into_string()
|
.into_string()
|
||||||
.expect("Couldn't convert to string.");
|
.expect("Couldn't convert to string.");
|
||||||
|
|
||||||
let actual = vec![("SHELL".to_string(), var_shell)];
|
let mut found = IndexMap::new();
|
||||||
|
found.insert("SHELL".to_string(), var_shell);
|
||||||
|
|
||||||
assert_eq!(actual, expected);
|
for k in found.keys() {
|
||||||
|
assert!(expected.contains_key(k));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let environment = actual.env.lock();
|
let environment = actual.env.lock();
|
||||||
|
|
||||||
let vars = environment
|
let mut vars = IndexMap::new();
|
||||||
|
environment
|
||||||
.env()
|
.env()
|
||||||
.expect("No variables in the environment.")
|
.expect("No variables in the environment.")
|
||||||
.row_entries()
|
.row_entries()
|
||||||
.map(|(name, value)| {
|
.for_each(|(name, value)| {
|
||||||
(
|
vars.insert(
|
||||||
name.to_string(),
|
name.to_string(),
|
||||||
value.as_string().expect("Couldn't convert to string"),
|
value.as_string().expect("couldn't convert to string"),
|
||||||
)
|
);
|
||||||
})
|
});
|
||||||
.collect::<Vec<_>>();
|
for k in expected.keys() {
|
||||||
|
assert!(vars.contains_key(k));
|
||||||
assert_eq!(vars, expected);
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
1
crates/nu-cli/tests/commands/autoenv.rs
Normal file
1
crates/nu-cli/tests/commands/autoenv.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
1
crates/nu-cli/tests/commands/autoenv_trust.rs
Normal file
1
crates/nu-cli/tests/commands/autoenv_trust.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
1
crates/nu-cli/tests/commands/autoenv_untrust.rs
Normal file
1
crates/nu-cli/tests/commands/autoenv_untrust.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
@ -1,5 +1,8 @@
|
|||||||
mod alias;
|
mod alias;
|
||||||
mod append;
|
mod append;
|
||||||
|
mod autoenv;
|
||||||
|
mod autoenv_trust;
|
||||||
|
mod autoenv_untrust;
|
||||||
mod cal;
|
mod cal;
|
||||||
mod calc;
|
mod calc;
|
||||||
mod cd;
|
mod cd;
|
||||||
|
Loading…
Reference in New Issue
Block a user