Treating environment variables as Values (#497)

* Proof of concept treating env vars as Values

* Refactor env var collection and method name

* Remove unnecessary pub

* Move env translations into a new file

* Fix LS_COLORS to support any Value

* Fix spans during env var translation

* Add span to env var in cd

* Improve error diagnostics

* Fix non-string env vars failing string conversion

* Make PROMPT_COMMAND a Block instead of String

* Record host env vars to a fake file

This will give spans to env vars that would otherwise be without one.
Makes errors less confusing.

* Add 'env' command to list env vars

It will list also their values translated to strings

* Sort env command by name; Add env var type

* Remove obsolete test
This commit is contained in:
Jakub Žádník
2021-12-17 03:04:54 +02:00
committed by GitHub
parent 342584e5f8
commit 6a0f404558
20 changed files with 414 additions and 132 deletions

View File

@ -0,0 +1,61 @@
use nu_engine::env_to_string;
use nu_protocol::ast::Call;
use nu_protocol::engine::{Command, EngineState, Stack};
use nu_protocol::{Category, IntoPipelineData, PipelineData, Signature, Value};
#[derive(Clone)]
pub struct Env;
impl Command for Env {
fn name(&self) -> &str {
"env"
}
fn usage(&self) -> &str {
"Display current environment"
}
fn signature(&self) -> nu_protocol::Signature {
Signature::build("env").category(Category::Env)
}
fn run(
&self,
engine_state: &EngineState,
stack: &mut Stack,
call: &Call,
_input: PipelineData,
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
let span = call.head;
let config = stack.get_config().unwrap_or_default();
let mut env_vars: Vec<(String, Value)> = stack.get_env_vars().into_iter().collect();
env_vars.sort_by(|(name1, _), (name2, _)| name1.cmp(name2));
let mut values = vec![];
for (name, val) in env_vars {
let mut cols = vec![];
let mut vals = vec![];
let raw = env_to_string(&name, val.clone(), engine_state, stack, &config)?;
let val_type = val.get_type();
cols.push("name".into());
vals.push(Value::string(name, span));
cols.push("type".into());
vals.push(Value::string(format!("{}", val_type), span));
cols.push("value".into());
vals.push(val);
cols.push("raw".into());
vals.push(Value::string(raw, span));
values.push(Value::Record { cols, vals, span });
}
Ok(Value::List { vals: values, span }.into_pipeline_data())
}
}

View File

@ -20,7 +20,7 @@ impl Command for LetEnv {
.required("var_name", SyntaxShape::String, "variable name")
.required(
"initial_value",
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::String)),
SyntaxShape::Keyword(b"=".to_vec(), Box::new(SyntaxShape::Any)),
"equals sign followed by value",
)
.category(Category::Env)
@ -42,9 +42,6 @@ impl Command for LetEnv {
.expect("internal error: missing keyword");
let rhs = eval_expression(engine_state, stack, keyword_expr)?;
let rhs = rhs.as_string()?;
//println!("Adding: {:?} to {}", rhs, var_id);
stack.add_env_var(env_var, rhs);
Ok(PipelineData::new(call.head))

View File

@ -1,5 +1,7 @@
mod env_command;
mod let_env;
mod with_env;
pub use env_command::Env;
pub use let_env::LetEnv;
pub use with_env::WithEnv;

View File

@ -1,7 +1,4 @@
use std::{
collections::HashMap,
convert::{TryFrom, TryInto},
};
use std::collections::HashMap;
use nu_engine::{eval_block, CallExt};
use nu_protocol::{
@ -73,34 +70,6 @@ impl Command for WithEnv {
}
}
#[derive(Debug, Clone)]
pub enum EnvVar {
Proper(String),
Nothing,
}
impl TryFrom<&Value> for EnvVar {
type Error = ShellError;
fn try_from(value: &Value) -> Result<Self, Self::Error> {
if matches!(value, Value::Nothing { .. }) {
Ok(EnvVar::Nothing)
} else if let Ok(s) = value.as_string() {
if s.is_empty() {
Ok(EnvVar::Nothing)
} else {
Ok(EnvVar::Proper(s))
}
} else {
Err(ShellError::CantConvert(
"string".into(),
value.get_type().to_string(),
value.span()?,
))
}
}
}
fn with_env(
engine_state: &EngineState,
stack: &mut Stack,
@ -116,7 +85,7 @@ fn with_env(
let block = engine_state.get_block(block_id).clone();
let mut stack = stack.collect_captures(&block.captures);
let mut env: HashMap<String, EnvVar> = HashMap::new();
let mut env: HashMap<String, Value> = HashMap::new();
match &variable {
Value::List { vals: table, .. } => {
@ -125,7 +94,7 @@ fn with_env(
match &table[0] {
Value::Record { cols, vals, .. } => {
for (k, v) in cols.iter().zip(vals.iter()) {
env.insert(k.to_string(), v.try_into()?);
env.insert(k.to_string(), v.clone());
}
}
x => {
@ -140,15 +109,16 @@ fn with_env(
// primitive values([X Y W Z])
for row in table.chunks(2) {
if row.len() == 2 {
env.insert(row[0].as_string()?, (&row[1]).try_into()?);
env.insert(row[0].as_string()?, (&row[1]).clone());
}
// TODO: else error?
}
}
}
// when get object by `open x.json` or `from json`
Value::Record { cols, vals, .. } => {
for (k, v) in cols.iter().zip(vals) {
env.insert(k.clone(), v.try_into()?);
env.insert(k.clone(), v.clone());
}
}
x => {
@ -161,14 +131,7 @@ fn with_env(
};
for (k, v) in env {
match v {
EnvVar::Nothing => {
stack.remove_env_var(&k);
}
EnvVar::Proper(s) => {
stack.add_env_var(k, s);
}
}
stack.add_env_var(k, v);
}
eval_block(engine_state, &mut stack, &block, input)