From b78924c77712ecfbac4047ade89beae505ed4621 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Sat, 15 Jan 2022 18:50:11 -0500 Subject: [PATCH] Add support for load-env (#752) --- crates/nu-command/src/default_context.rs | 7 +- crates/nu-command/src/env/load_env.rs | 109 +++++++++++++++++++++ crates/nu-command/src/env/mod.rs | 2 + crates/nu-command/src/filesystem/open.rs | 1 - crates/nu-command/src/filesystem/save.rs | 1 - crates/nu-parser/src/parser.rs | 2 + crates/nu-protocol/src/syntax_shape.rs | 5 + crates/nu-protocol/src/value/from_value.rs | 14 +++ 8 files changed, 136 insertions(+), 5 deletions(-) create mode 100644 crates/nu-command/src/env/load_env.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 8c3cc1cbb..e68b6dc79 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -234,6 +234,7 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { // Conversions bind_command! { + Fmt, Into, IntoBool, IntoBinary, @@ -242,14 +243,14 @@ pub fn create_default_context(cwd: impl AsRef) -> EngineState { IntoFilesize, IntoInt, IntoString, - Fmt, }; // Env bind_command! { - LetEnv, - WithEnv, Env, + LetEnv, + LoadEnv, + WithEnv, }; // Math diff --git a/crates/nu-command/src/env/load_env.rs b/crates/nu-command/src/env/load_env.rs new file mode 100644 index 000000000..c4edfebc8 --- /dev/null +++ b/crates/nu-command/src/env/load_env.rs @@ -0,0 +1,109 @@ +use nu_engine::{current_dir, CallExt}; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Value}; + +#[derive(Clone)] +pub struct LoadEnv; + +impl Command for LoadEnv { + fn name(&self) -> &str { + "load-env" + } + + fn usage(&self) -> &str { + "Loads an environment update from a record." + } + + fn signature(&self) -> nu_protocol::Signature { + Signature::build("load-env") + .optional( + "update", + SyntaxShape::Record, + "the record to use for updates", + ) + .category(Category::FileSystem) + } + + fn run( + &self, + engine_state: &EngineState, + stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let arg: Option<(Vec, Vec)> = call.opt(engine_state, stack, 0)?; + let span = call.head; + + match arg { + Some((cols, vals)) => { + for (env_var, rhs) in cols.into_iter().zip(vals) { + if env_var == "PWD" { + let cwd = current_dir(engine_state, stack)?; + let rhs = rhs.as_string()?; + let rhs = nu_path::expand_path_with(rhs, cwd); + stack.add_env_var( + env_var, + Value::String { + val: rhs.to_string_lossy().to_string(), + span: call.head, + }, + ); + } else { + stack.add_env_var(env_var, rhs); + } + } + Ok(PipelineData::new(call.head)) + } + None => match input { + PipelineData::Value(Value::Record { cols, vals, .. }, ..) => { + for (env_var, rhs) in cols.into_iter().zip(vals) { + if env_var == "PWD" { + let cwd = current_dir(engine_state, stack)?; + let rhs = rhs.as_string()?; + let rhs = nu_path::expand_path_with(rhs, cwd); + stack.add_env_var( + env_var, + Value::String { + val: rhs.to_string_lossy().to_string(), + span: call.head, + }, + ); + } else { + stack.add_env_var(env_var, rhs); + } + } + Ok(PipelineData::new(call.head)) + } + _ => Err(ShellError::UnsupportedInput("Record".into(), span)), + }, + } + } + + fn examples(&self) -> Vec { + vec![ + Example { + description: "Load variables from an input stream", + example: r#"{NAME: ABE, AGE: UNKNOWN} | load-env; echo $env.NAME"#, + result: Some(Value::test_string("ABE")), + }, + Example { + description: "Load variables from an argument", + example: r#"load-env {NAME: ABE, AGE: UNKNOWN}; echo $env.NAME"#, + result: Some(Value::test_string("ABE")), + }, + ] + } +} + +#[cfg(test)] +mod tests { + use super::LoadEnv; + + #[test] + fn examples_work_as_expected() { + use crate::test_examples; + + test_examples(LoadEnv {}) + } +} diff --git a/crates/nu-command/src/env/mod.rs b/crates/nu-command/src/env/mod.rs index 55f1c90bd..3b40657fb 100644 --- a/crates/nu-command/src/env/mod.rs +++ b/crates/nu-command/src/env/mod.rs @@ -1,7 +1,9 @@ mod env_command; mod let_env; +mod load_env; mod with_env; pub use env_command::Env; pub use let_env::LetEnv; +pub use load_env::LoadEnv; pub use with_env::WithEnv; diff --git a/crates/nu-command/src/filesystem/open.rs b/crates/nu-command/src/filesystem/open.rs index 37852ecf7..56ef50613 100644 --- a/crates/nu-command/src/filesystem/open.rs +++ b/crates/nu-command/src/filesystem/open.rs @@ -13,7 +13,6 @@ use std::path::Path; #[derive(Clone)] pub struct Open; -//NOTE: this is not a real implementation :D. It's just a simple one to test with until we port the real one. impl Command for Open { fn name(&self) -> &str { "open" diff --git a/crates/nu-command/src/filesystem/save.rs b/crates/nu-command/src/filesystem/save.rs index 1c7066cde..5b2efcd78 100644 --- a/crates/nu-command/src/filesystem/save.rs +++ b/crates/nu-command/src/filesystem/save.rs @@ -9,7 +9,6 @@ use std::path::Path; #[derive(Clone)] pub struct Save; -//NOTE: this is not a real implementation :D. It's just a simple one to test with until we port the real one. impl Command for Save { fn name(&self) -> &str { "save" diff --git a/crates/nu-parser/src/parser.rs b/crates/nu-parser/src/parser.rs index 85ffeec58..8b88395d1 100644 --- a/crates/nu-parser/src/parser.rs +++ b/crates/nu-parser/src/parser.rs @@ -2970,6 +2970,8 @@ pub fn parse_value( } if matches!(shape, SyntaxShape::Block(_)) || matches!(shape, SyntaxShape::Any) { return parse_block_expression(working_set, shape, span); + } else if matches!(shape, SyntaxShape::Record) { + return parse_record(working_set, span); } else { return ( Expression::garbage(span), diff --git a/crates/nu-protocol/src/syntax_shape.rs b/crates/nu-protocol/src/syntax_shape.rs index 8e92369eb..90b0dd201 100644 --- a/crates/nu-protocol/src/syntax_shape.rs +++ b/crates/nu-protocol/src/syntax_shape.rs @@ -80,6 +80,9 @@ pub enum SyntaxShape { /// A boolean value Boolean, + /// A record value + Record, + /// A custom shape with custom completion logic Custom(Box, String), } @@ -108,6 +111,7 @@ impl SyntaxShape { SyntaxShape::Number => Type::Number, SyntaxShape::Operator => Type::Unknown, SyntaxShape::Range => Type::Unknown, + SyntaxShape::Record => Type::Record(vec![]), // FIXME: Add actual record type SyntaxShape::RowCondition => Type::Bool, SyntaxShape::Boolean => Type::Bool, SyntaxShape::Signature => Type::Signature, @@ -138,6 +142,7 @@ impl Display for SyntaxShape { SyntaxShape::Block(_) => write!(f, "block"), SyntaxShape::Table => write!(f, "table"), SyntaxShape::List(x) => write!(f, "list<{}>", x), + SyntaxShape::Record => write!(f, "record"), SyntaxShape::Filesize => write!(f, "filesize"), SyntaxShape::Duration => write!(f, "duration"), SyntaxShape::Operator => write!(f, "operator"), diff --git a/crates/nu-protocol/src/value/from_value.rs b/crates/nu-protocol/src/value/from_value.rs index ea3fd7b0f..268310f72 100644 --- a/crates/nu-protocol/src/value/from_value.rs +++ b/crates/nu-protocol/src/value/from_value.rs @@ -353,6 +353,20 @@ impl FromValue for Vec { } } +// A record +impl FromValue for (Vec, Vec) { + fn from_value(v: &Value) -> Result { + match v { + Value::Record { cols, vals, .. } => Ok((cols.clone(), vals.clone())), + v => Err(ShellError::CantConvert( + "Record".into(), + v.get_type().to_string(), + v.span()?, + )), + } + } +} + impl FromValue for CaptureBlock { fn from_value(v: &Value) -> Result { match v {