forked from extern/nushell
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:
parent
342584e5f8
commit
6a0f404558
@ -8,7 +8,6 @@ use {
|
||||
/// Nushell prompt definition
|
||||
#[derive(Clone)]
|
||||
pub struct NushellPrompt {
|
||||
prompt_command: String,
|
||||
prompt_string: String,
|
||||
// These are part of the struct definition in case we want to allow
|
||||
// further customization to the shell status
|
||||
@ -27,7 +26,6 @@ impl Default for NushellPrompt {
|
||||
impl NushellPrompt {
|
||||
pub fn new() -> NushellPrompt {
|
||||
NushellPrompt {
|
||||
prompt_command: "".to_string(),
|
||||
prompt_string: "".to_string(),
|
||||
default_prompt_indicator: "〉".to_string(),
|
||||
default_vi_insert_prompt_indicator: ": ".to_string(),
|
||||
@ -36,12 +34,7 @@ impl NushellPrompt {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_new_prompt(&self, prompt_command: &str) -> bool {
|
||||
self.prompt_command != prompt_command
|
||||
}
|
||||
|
||||
pub fn update_prompt(&mut self, prompt_command: String, prompt_string: String) {
|
||||
self.prompt_command = prompt_command;
|
||||
pub fn update_prompt(&mut self, prompt_string: String) {
|
||||
self.prompt_string = prompt_string;
|
||||
}
|
||||
|
||||
|
@ -88,15 +88,8 @@ impl Command for Use {
|
||||
|
||||
// TODO: Add string conversions (e.g. int to string)
|
||||
// TODO: Later expand env to take all Values
|
||||
let val = if let Ok(s) =
|
||||
eval_block(engine_state, stack, block, PipelineData::new(call.head))?
|
||||
.into_value(Span::unknown())
|
||||
.as_string()
|
||||
{
|
||||
s
|
||||
} else {
|
||||
return Err(ShellError::EnvVarNotAString(import_pattern.span()));
|
||||
};
|
||||
let val = eval_block(engine_state, stack, block, PipelineData::new(call.head))?
|
||||
.into_value(Span::unknown());
|
||||
|
||||
stack.add_env_var(name, val);
|
||||
}
|
||||
|
@ -226,6 +226,7 @@ pub fn create_default_context() -> EngineState {
|
||||
bind_command! {
|
||||
LetEnv,
|
||||
WithEnv,
|
||||
Env,
|
||||
};
|
||||
|
||||
// Math
|
||||
|
61
crates/nu-command/src/env/env_command.rs
vendored
Normal file
61
crates/nu-command/src/env/env_command.rs
vendored
Normal 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())
|
||||
}
|
||||
}
|
5
crates/nu-command/src/env/let_env.rs
vendored
5
crates/nu-command/src/env/let_env.rs
vendored
@ -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))
|
||||
|
2
crates/nu-command/src/env/mod.rs
vendored
2
crates/nu-command/src/env/mod.rs
vendored
@ -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;
|
||||
|
51
crates/nu-command/src/env/with_env.rs
vendored
51
crates/nu-command/src/env/with_env.rs
vendored
@ -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)
|
||||
|
@ -1,7 +1,7 @@
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{Category, PipelineData, Signature, SyntaxShape};
|
||||
use nu_protocol::{Category, PipelineData, Signature, SyntaxShape, Value};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Cd;
|
||||
@ -28,23 +28,23 @@ impl Command for Cd {
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
let path: Option<String> = call.opt(engine_state, stack, 0)?;
|
||||
let path_val: Option<Value> = call.opt(engine_state, stack, 0)?;
|
||||
|
||||
let path = match path {
|
||||
Some(path) => {
|
||||
let path = nu_path::expand_path(path);
|
||||
path.to_string_lossy().to_string()
|
||||
let (path, span) = match path_val {
|
||||
Some(v) => {
|
||||
let path = nu_path::expand_path(v.as_string()?);
|
||||
(path.to_string_lossy().to_string(), v.span()?)
|
||||
}
|
||||
None => {
|
||||
let path = nu_path::expand_tilde("~");
|
||||
path.to_string_lossy().to_string()
|
||||
(path.to_string_lossy().to_string(), call.head)
|
||||
}
|
||||
};
|
||||
let _ = std::env::set_current_dir(&path);
|
||||
|
||||
//FIXME: this only changes the current scope, but instead this environment variable
|
||||
//should probably be a block that loads the information from the state in the overlay
|
||||
stack.add_env_var("PWD".into(), path);
|
||||
stack.add_env_var("PWD".into(), Value::String { val: path, span });
|
||||
Ok(PipelineData::new(call.head))
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ use std::process::{Command as CommandSys, Stdio};
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::mpsc;
|
||||
|
||||
use nu_engine::env_to_strings;
|
||||
use nu_protocol::engine::{EngineState, Stack};
|
||||
use nu_protocol::{ast::Call, engine::Command, ShellError, Signature, SyntaxShape, Value};
|
||||
use nu_protocol::{Category, Config, IntoInterruptiblePipelineData, PipelineData, Span, Spanned};
|
||||
@ -51,9 +52,10 @@ impl Command for External {
|
||||
let mut name: Spanned<String> = call.req(engine_state, stack, 0)?;
|
||||
let args: Vec<String> = call.rest(engine_state, stack, 1)?;
|
||||
let last_expression = call.has_flag("last_expression");
|
||||
let env_vars = stack.get_env_vars();
|
||||
|
||||
// Translate environment variables from Values to Strings
|
||||
let config = stack.get_config().unwrap_or_default();
|
||||
let env_vars_str = env_to_strings(engine_state, stack, &config)?;
|
||||
|
||||
// Check if this is a single call to a directory, if so auto-cd
|
||||
let path = nu_path::expand_path(&name.item);
|
||||
@ -73,7 +75,13 @@ impl Command for External {
|
||||
|
||||
//FIXME: this only changes the current scope, but instead this environment variable
|
||||
//should probably be a block that loads the information from the state in the overlay
|
||||
stack.add_env_var("PWD".into(), name.item.clone());
|
||||
stack.add_env_var(
|
||||
"PWD".into(),
|
||||
Value::String {
|
||||
val: name.item.clone(),
|
||||
span: Span::unknown(),
|
||||
},
|
||||
);
|
||||
return Ok(PipelineData::new(call.head));
|
||||
}
|
||||
|
||||
@ -81,7 +89,7 @@ impl Command for External {
|
||||
name,
|
||||
args,
|
||||
last_expression,
|
||||
env_vars,
|
||||
env_vars: env_vars_str,
|
||||
call,
|
||||
};
|
||||
command.run_with_input(engine_state, input, config)
|
||||
|
@ -1,6 +1,7 @@
|
||||
// use super::icons::{icon_for_file, iconify_style_ansi_to_nu};
|
||||
use super::icons::icon_for_file;
|
||||
use lscolors::{LsColors, Style};
|
||||
use nu_engine::env_to_string;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::{
|
||||
ast::{Call, PathMember},
|
||||
@ -61,7 +62,10 @@ prints out the list properly."#
|
||||
let color_param: bool = call.has_flag("color");
|
||||
let separator_param: Option<String> = call.get_flag(engine_state, stack, "separator")?;
|
||||
let config = stack.get_config().unwrap_or_default();
|
||||
let env_str = stack.get_env_var("LS_COLORS");
|
||||
let env_str = match stack.get_env_var("LS_COLORS") {
|
||||
Some(v) => Some(env_to_string("LS_COLORS", v, engine_state, stack, &config)?),
|
||||
None => None,
|
||||
};
|
||||
let use_grid_icons = config.use_grid_icons;
|
||||
|
||||
match input {
|
||||
|
@ -1,5 +1,6 @@
|
||||
use lscolors::{LsColors, Style};
|
||||
use nu_color_config::{get_color_config, style_primitive};
|
||||
use nu_engine::env_to_string;
|
||||
use nu_protocol::ast::{Call, PathMember};
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
@ -74,7 +75,13 @@ impl Command for Table {
|
||||
let ctrlc = ctrlc.clone();
|
||||
|
||||
let ls_colors = match stack.get_env_var("LS_COLORS") {
|
||||
Some(s) => LsColors::from_string(&s),
|
||||
Some(v) => LsColors::from_string(&env_to_string(
|
||||
"LS_COLORS",
|
||||
v,
|
||||
engine_state,
|
||||
stack,
|
||||
&config,
|
||||
)?),
|
||||
None => LsColors::default(),
|
||||
};
|
||||
|
||||
|
131
crates/nu-engine/src/env.rs
Normal file
131
crates/nu-engine/src/env.rs
Normal file
@ -0,0 +1,131 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use nu_protocol::engine::{EngineState, Stack};
|
||||
use nu_protocol::{Config, PipelineData, ShellError, Value};
|
||||
|
||||
use crate::eval_block;
|
||||
|
||||
#[cfg(windows)]
|
||||
const ENV_SEP: &str = ";";
|
||||
#[cfg(not(windows))]
|
||||
const ENV_SEP: &str = ":";
|
||||
|
||||
/// Translate environment variables from Strings to Values. Requires config to be already set up in
|
||||
/// case the user defined custom env conversions in config.nu.
|
||||
///
|
||||
/// It returns Option instead of Result since we do want to translate all the values we can and
|
||||
/// skip errors. This function is called in the main() so we want to keep running, we cannot just
|
||||
/// exit.
|
||||
pub fn env_to_values(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
config: &Config,
|
||||
) -> Option<ShellError> {
|
||||
let mut new_env_vars = vec![];
|
||||
let mut error = None;
|
||||
|
||||
for scope in &stack.env_vars {
|
||||
let mut new_scope = HashMap::new();
|
||||
|
||||
for (name, val) in scope {
|
||||
if let Some(conv) = config.env_conversions.get(name) {
|
||||
let span = match val.span() {
|
||||
Ok(sp) => sp,
|
||||
Err(e) => {
|
||||
error = error.or(Some(e));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let block = engine_state.get_block(conv.from_string.0);
|
||||
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
let mut stack = stack.collect_captures(&block.captures);
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(*var_id, val.clone());
|
||||
}
|
||||
|
||||
let result =
|
||||
eval_block(engine_state, &mut stack, block, PipelineData::new(span));
|
||||
|
||||
match result {
|
||||
Ok(data) => {
|
||||
let val = data.into_value(span);
|
||||
new_scope.insert(name.to_string(), val);
|
||||
}
|
||||
Err(e) => error = error.or(Some(e)),
|
||||
}
|
||||
} else {
|
||||
error = error.or_else(|| {
|
||||
Some(ShellError::MissingParameter(
|
||||
"block input".into(),
|
||||
conv.from_string.1,
|
||||
))
|
||||
});
|
||||
}
|
||||
} else {
|
||||
new_scope.insert(name.to_string(), val.clone());
|
||||
}
|
||||
}
|
||||
|
||||
new_env_vars.push(new_scope);
|
||||
}
|
||||
|
||||
stack.env_vars = new_env_vars;
|
||||
|
||||
error
|
||||
}
|
||||
|
||||
/// Translate one environment variable from Value to String
|
||||
pub fn env_to_string(
|
||||
env_name: &str,
|
||||
value: Value,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
config: &Config,
|
||||
) -> Result<String, ShellError> {
|
||||
if let Some(conv) = config.env_conversions.get(env_name) {
|
||||
let block = engine_state.get_block(conv.to_string.0);
|
||||
|
||||
if let Some(var) = block.signature.get_positional(0) {
|
||||
let span = value.span()?;
|
||||
let mut stack = stack.collect_captures(&block.captures);
|
||||
|
||||
if let Some(var_id) = &var.var_id {
|
||||
stack.add_var(*var_id, value);
|
||||
}
|
||||
|
||||
Ok(
|
||||
// This one is OK to fail: We want to know if custom conversion is working
|
||||
eval_block(engine_state, &mut stack, block, PipelineData::new(span))?
|
||||
.into_value(span)
|
||||
.as_string()?,
|
||||
)
|
||||
} else {
|
||||
Err(ShellError::MissingParameter(
|
||||
"block input".into(),
|
||||
conv.to_string.1,
|
||||
))
|
||||
}
|
||||
} else {
|
||||
// Do not fail here. Must sicceed, otherwise setting a non-string env var would constantly
|
||||
// throw errors when running externals etc.
|
||||
Ok(value.into_string(ENV_SEP, config))
|
||||
}
|
||||
}
|
||||
|
||||
/// Translate all environment variables from Values to Strings
|
||||
pub fn env_to_strings(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
config: &Config,
|
||||
) -> Result<HashMap<String, String>, ShellError> {
|
||||
let env_vars = stack.get_env_vars();
|
||||
let mut env_vars_str = HashMap::new();
|
||||
for (env_name, val) in env_vars {
|
||||
let val_str = env_to_string(&env_name, val, engine_state, stack, config)?;
|
||||
env_vars_str.insert(env_name, val_str);
|
||||
}
|
||||
|
||||
Ok(env_vars_str)
|
||||
}
|
@ -472,13 +472,7 @@ pub fn eval_variable(
|
||||
|
||||
let env_vars = stack.get_env_vars();
|
||||
let env_columns: Vec<_> = env_vars.keys().map(|x| x.to_string()).collect();
|
||||
let env_values: Vec<_> = env_vars
|
||||
.values()
|
||||
.map(|x| Value::String {
|
||||
val: x.to_string(),
|
||||
span,
|
||||
})
|
||||
.collect();
|
||||
let env_values: Vec<_> = env_vars.values().cloned().collect();
|
||||
|
||||
output_cols.push("env".into());
|
||||
output_vals.push(Value::Record {
|
||||
@ -852,7 +846,7 @@ pub fn eval_variable(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compute(size: i64, unit: Unit, span: Span) -> Value {
|
||||
fn compute(size: i64, unit: Unit, span: Span) -> Value {
|
||||
match unit {
|
||||
Unit::Byte => Value::Filesize { val: size, span },
|
||||
Unit::Kilobyte => Value::Filesize {
|
||||
|
@ -1,7 +1,9 @@
|
||||
mod call_ext;
|
||||
mod documentation;
|
||||
mod env;
|
||||
mod eval;
|
||||
|
||||
pub use call_ext::CallExt;
|
||||
pub use documentation::{generate_docs, get_brief_help, get_documentation, get_full_help};
|
||||
pub use env::*;
|
||||
pub use eval::{eval_block, eval_expression, eval_operator};
|
||||
|
@ -1,9 +1,50 @@
|
||||
use crate::{ShellError, Value};
|
||||
use crate::{BlockId, ShellError, Span, Value};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
const ANIMATE_PROMPT_DEFAULT: bool = false;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct EnvConversion {
|
||||
pub from_string: (BlockId, Span),
|
||||
pub to_string: (BlockId, Span),
|
||||
}
|
||||
|
||||
impl EnvConversion {
|
||||
pub fn from_record(value: &Value) -> Result<Self, ShellError> {
|
||||
let record = value.as_record()?;
|
||||
|
||||
let mut conv_map = HashMap::new();
|
||||
|
||||
for (k, v) in record.0.iter().zip(record.1) {
|
||||
if (k == "from_string") || (k == "to_string") {
|
||||
conv_map.insert(k.as_str(), (v.as_block()?, v.span()?));
|
||||
} else {
|
||||
return Err(ShellError::UnsupportedConfigValue(
|
||||
"'from_string' and 'to_string' fields".into(),
|
||||
k.into(),
|
||||
value.span()?,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
match (conv_map.get("from_string"), conv_map.get("to_string")) {
|
||||
(None, _) => Err(ShellError::MissingConfigValue(
|
||||
"'from_string' field".into(),
|
||||
value.span()?,
|
||||
)),
|
||||
(_, None) => Err(ShellError::MissingConfigValue(
|
||||
"'to_string' field".into(),
|
||||
value.span()?,
|
||||
)),
|
||||
(Some(from), Some(to)) => Ok(EnvConversion {
|
||||
from_string: *from,
|
||||
to_string: *to,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct Config {
|
||||
pub filesize_metric: bool,
|
||||
@ -16,6 +57,7 @@ pub struct Config {
|
||||
pub float_precision: i64,
|
||||
pub filesize_format: String,
|
||||
pub use_ansi_coloring: bool,
|
||||
pub env_conversions: HashMap<String, EnvConversion>,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@ -31,6 +73,7 @@ impl Default for Config {
|
||||
float_precision: 4,
|
||||
filesize_format: "auto".into(),
|
||||
use_ansi_coloring: true,
|
||||
env_conversions: HashMap::new(), // TODO: Add default conversoins
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -129,6 +172,16 @@ impl Value {
|
||||
"filesize_format" => {
|
||||
config.filesize_format = value.as_string()?.to_lowercase();
|
||||
}
|
||||
"env_conversions" => {
|
||||
let (env_vars, conversions) = value.as_record()?;
|
||||
let mut env_conversions = HashMap::new();
|
||||
|
||||
for (env_var, record) in env_vars.iter().zip(conversions) {
|
||||
env_conversions.insert(env_var.into(), EnvConversion::from_record(record)?);
|
||||
}
|
||||
|
||||
config.env_conversions = env_conversions;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -489,8 +489,7 @@ impl EngineState {
|
||||
"<unknown>".into()
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) fn add_file(&mut self, filename: String, contents: Vec<u8>) -> usize {
|
||||
pub fn add_file(&mut self, filename: String, contents: Vec<u8>) -> usize {
|
||||
let next_span_start = self.next_span_start();
|
||||
let next_span_end = next_span_start + contents.len();
|
||||
|
||||
|
@ -24,7 +24,7 @@ pub struct Stack {
|
||||
/// Variables
|
||||
pub vars: HashMap<VarId, Value>,
|
||||
/// Environment variables arranged as a stack to be able to recover values from parent scopes
|
||||
pub env_vars: Vec<HashMap<String, String>>,
|
||||
pub env_vars: Vec<HashMap<String, Value>>,
|
||||
}
|
||||
|
||||
impl Default for Stack {
|
||||
@ -53,7 +53,7 @@ impl Stack {
|
||||
self.vars.insert(var_id, value);
|
||||
}
|
||||
|
||||
pub fn add_env_var(&mut self, var: String, value: String) {
|
||||
pub fn add_env_var(&mut self, var: String, value: Value) {
|
||||
if let Some(scope) = self.env_vars.last_mut() {
|
||||
scope.insert(var, value);
|
||||
} else {
|
||||
@ -85,7 +85,7 @@ impl Stack {
|
||||
}
|
||||
|
||||
/// Flatten the env var scope frames into one frame
|
||||
pub fn get_env_vars(&self) -> HashMap<String, String> {
|
||||
pub fn get_env_vars(&self) -> HashMap<String, Value> {
|
||||
let mut result = HashMap::new();
|
||||
|
||||
for scope in &self.env_vars {
|
||||
@ -95,17 +95,17 @@ impl Stack {
|
||||
result
|
||||
}
|
||||
|
||||
pub fn get_env_var(&self, name: &str) -> Option<String> {
|
||||
pub fn get_env_var(&self, name: &str) -> Option<Value> {
|
||||
for scope in self.env_vars.iter().rev() {
|
||||
if let Some(v) = scope.get(name) {
|
||||
return Some(v.to_string());
|
||||
return Some(v.clone());
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn remove_env_var(&mut self, name: &str) -> Option<String> {
|
||||
pub fn remove_env_var(&mut self, name: &str) -> Option<Value> {
|
||||
for scope in self.env_vars.iter_mut().rev() {
|
||||
if let Some(v) = scope.remove(name) {
|
||||
return Some(v);
|
||||
@ -135,7 +135,7 @@ impl Stack {
|
||||
for (i, scope) in self.env_vars.iter().rev().enumerate() {
|
||||
println!("env vars, scope {} (from the last);", i);
|
||||
for (var, val) in scope {
|
||||
println!(" {}: {:?}", var, val);
|
||||
println!(" {}: {:?}", var, val.clone().debug_value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -99,10 +99,9 @@ pub enum ShellError {
|
||||
#[diagnostic(code(nu::shell::variable_not_found), url(docsrs))]
|
||||
EnvVarNotFoundAtRuntime(#[label = "environment variable not found"] Span),
|
||||
|
||||
#[error("Environment variable is not a string")]
|
||||
#[diagnostic(code(nu::shell::variable_not_found), url(docsrs))]
|
||||
EnvVarNotAString(#[label = "does not evaluate to a string"] Span),
|
||||
|
||||
// #[error("Environment variable is not a string")]
|
||||
// #[diagnostic(code(nu::shell::variable_not_found), url(docsrs))]
|
||||
// EnvVarNotAString(#[label = "does not evaluate to a string"] Span),
|
||||
#[error("Not found.")]
|
||||
#[diagnostic(code(nu::parser::not_found), url(docsrs))]
|
||||
NotFound(#[label = "did not find anything under this name"] Span),
|
||||
@ -235,6 +234,14 @@ pub enum ShellError {
|
||||
#[diagnostic(code(nu::shell::downcast_not_possible), url(docsrs))]
|
||||
DowncastNotPossible(String, #[label("{0}")] Span),
|
||||
|
||||
#[error("Unsupported config value")]
|
||||
#[diagnostic(code(nu::shell::unsupported_config_value), url(docsrs))]
|
||||
UnsupportedConfigValue(String, String, #[label = "expected {0}, got {1}"] Span),
|
||||
|
||||
#[error("Missing config value")]
|
||||
#[diagnostic(code(nu::shell::missing_config_value), url(docsrs))]
|
||||
MissingConfigValue(String, #[label = "missing {0}"] Span),
|
||||
|
||||
#[error("{0}")]
|
||||
#[diagnostic()]
|
||||
SpannedLabeledError(String, String, #[label("{1}")] Span),
|
||||
|
126
src/main.rs
126
src/main.rs
@ -8,8 +8,8 @@ use dialoguer::{
|
||||
use miette::{IntoDiagnostic, Result};
|
||||
use nu_cli::{CliError, NuCompleter, NuHighlighter, NuValidator, NushellPrompt};
|
||||
use nu_command::create_default_context;
|
||||
use nu_engine::eval_block;
|
||||
use nu_parser::parse;
|
||||
use nu_engine::{env_to_values, eval_block};
|
||||
use nu_parser::{lex, parse, Token, TokenContents};
|
||||
use nu_protocol::{
|
||||
ast::Call,
|
||||
engine::{EngineState, Stack, StateWorkingSet},
|
||||
@ -126,9 +126,8 @@ fn main() -> Result<()> {
|
||||
|
||||
let mut stack = nu_protocol::engine::Stack::new();
|
||||
|
||||
for (k, v) in std::env::vars() {
|
||||
stack.add_env_var(k, v);
|
||||
}
|
||||
// First, set up env vars as strings only
|
||||
gather_parent_env_vars(&mut engine_state, &mut stack);
|
||||
|
||||
// Set up our initial config to start from
|
||||
stack.vars.insert(
|
||||
@ -150,6 +149,13 @@ fn main() -> Result<()> {
|
||||
}
|
||||
};
|
||||
|
||||
// Translate environment variables from Strings to Values
|
||||
if let Some(e) = env_to_values(&engine_state, &mut stack, &config) {
|
||||
let working_set = StateWorkingSet::new(&engine_state);
|
||||
report_error(&working_set, &e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
match eval_block(
|
||||
&engine_state,
|
||||
&mut stack,
|
||||
@ -239,9 +245,8 @@ fn main() -> Result<()> {
|
||||
let mut nu_prompt = NushellPrompt::new();
|
||||
let mut stack = nu_protocol::engine::Stack::new();
|
||||
|
||||
for (k, v) in std::env::vars() {
|
||||
stack.add_env_var(k, v);
|
||||
}
|
||||
// First, set up env vars as strings only
|
||||
gather_parent_env_vars(&mut engine_state, &mut stack);
|
||||
|
||||
// Set up our initial config to start from
|
||||
stack.vars.insert(
|
||||
@ -269,6 +274,23 @@ fn main() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
// Get the config
|
||||
let config = match stack.get_config() {
|
||||
Ok(config) => config,
|
||||
Err(e) => {
|
||||
let working_set = StateWorkingSet::new(&engine_state);
|
||||
|
||||
report_error(&working_set, &e);
|
||||
Config::default()
|
||||
}
|
||||
};
|
||||
|
||||
// Translate environment variables from Strings to Values
|
||||
if let Some(e) = env_to_values(&engine_state, &mut stack, &config) {
|
||||
let working_set = StateWorkingSet::new(&engine_state);
|
||||
report_error(&working_set, &e);
|
||||
}
|
||||
|
||||
let history_path = if let Some(mut history_path) = nu_path::config_dir() {
|
||||
history_path.push("nushell");
|
||||
history_path.push("history.txt");
|
||||
@ -385,6 +407,67 @@ fn main() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
// This fill collect environment variables from std::env and adds them to a stack.
|
||||
//
|
||||
// In order to ensure the values have spans, it first creates a dummy file, writes the collected
|
||||
// env vars into it (in a NAME=value format, similar to the output of the Unix 'env' tool), then
|
||||
// uses the file to get the spans. The file stays in memory, no filesystem IO is done.
|
||||
fn gather_parent_env_vars(engine_state: &mut EngineState, stack: &mut Stack) {
|
||||
let mut fake_env_file = String::new();
|
||||
for (name, val) in std::env::vars() {
|
||||
fake_env_file.push_str(&name);
|
||||
fake_env_file.push('=');
|
||||
fake_env_file.push_str(&val);
|
||||
fake_env_file.push('\n');
|
||||
}
|
||||
|
||||
let span_offset = engine_state.next_span_start();
|
||||
engine_state.add_file(
|
||||
"Host Environment Variables".to_string(),
|
||||
fake_env_file.as_bytes().to_vec(),
|
||||
);
|
||||
let (tokens, _) = lex(fake_env_file.as_bytes(), span_offset, &[], &[], true);
|
||||
for token in tokens {
|
||||
if let Token {
|
||||
contents: TokenContents::Item,
|
||||
span: full_span,
|
||||
} = token
|
||||
{
|
||||
let contents = engine_state.get_span_contents(&full_span);
|
||||
let (parts, _) = lex(contents, full_span.start, &[], &[b'='], true);
|
||||
|
||||
let name = if let Some(Token {
|
||||
contents: TokenContents::Item,
|
||||
span,
|
||||
}) = parts.get(0)
|
||||
{
|
||||
String::from_utf8_lossy(engine_state.get_span_contents(span)).to_string()
|
||||
} else {
|
||||
// Skip this env var if it does not have a name
|
||||
continue;
|
||||
};
|
||||
|
||||
let value = if let Some(Token {
|
||||
contents: TokenContents::Item,
|
||||
span,
|
||||
}) = parts.get(2)
|
||||
{
|
||||
Value::String {
|
||||
val: String::from_utf8_lossy(engine_state.get_span_contents(span)).to_string(),
|
||||
span: *span,
|
||||
}
|
||||
} else {
|
||||
Value::String {
|
||||
val: "".to_string(),
|
||||
span: Span::new(full_span.end, full_span.end),
|
||||
}
|
||||
};
|
||||
|
||||
stack.add_env_var(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_pipeline_data(
|
||||
input: PipelineData,
|
||||
engine_state: &EngineState,
|
||||
@ -447,33 +530,22 @@ fn update_prompt<'prompt>(
|
||||
nu_prompt: &'prompt mut NushellPrompt,
|
||||
default_prompt: &'prompt DefaultPrompt,
|
||||
) -> &'prompt dyn Prompt {
|
||||
let prompt_command = match stack.get_env_var(env_variable) {
|
||||
Some(prompt) => prompt,
|
||||
let block_id = match stack.get_env_var(env_variable) {
|
||||
Some(v) => match v.as_block() {
|
||||
Ok(b) => b,
|
||||
Err(_) => return default_prompt as &dyn Prompt,
|
||||
},
|
||||
None => return default_prompt as &dyn Prompt,
|
||||
};
|
||||
|
||||
// Checking if the PROMPT_COMMAND is the same to avoid evaluating constantly
|
||||
// the same command, thus saturating the contents in the EngineState
|
||||
if !nu_prompt.is_new_prompt(prompt_command.as_str()) {
|
||||
return nu_prompt as &dyn Prompt;
|
||||
}
|
||||
|
||||
let block = {
|
||||
let mut working_set = StateWorkingSet::new(engine_state);
|
||||
let (output, err) = parse(&mut working_set, None, prompt_command.as_bytes(), false);
|
||||
if let Some(err) = err {
|
||||
report_error(&working_set, &err);
|
||||
return default_prompt as &dyn Prompt;
|
||||
}
|
||||
output
|
||||
};
|
||||
let block = engine_state.get_block(block_id);
|
||||
|
||||
let mut stack = stack.clone();
|
||||
|
||||
let evaluated_prompt = match eval_block(
|
||||
engine_state,
|
||||
&mut stack,
|
||||
&block,
|
||||
block,
|
||||
PipelineData::new(Span::unknown()),
|
||||
) {
|
||||
Ok(pipeline_data) => {
|
||||
@ -486,7 +558,7 @@ fn update_prompt<'prompt>(
|
||||
}
|
||||
};
|
||||
|
||||
nu_prompt.update_prompt(prompt_command, evaluated_prompt);
|
||||
nu_prompt.update_prompt(evaluated_prompt);
|
||||
|
||||
nu_prompt as &dyn Prompt
|
||||
}
|
||||
|
@ -1066,11 +1066,6 @@ fn shorthand_env_3() -> TestResult {
|
||||
run_test(r#"FOO=BAZ BAR=MOO $nu.env.FOO"#, "BAZ")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shorthand_env_4() -> TestResult {
|
||||
fail_test(r#"FOO=BAZ FOO= $nu.env.FOO"#, "did you mean")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_cell_path_1() -> TestResult {
|
||||
run_test(
|
||||
|
Loading…
Reference in New Issue
Block a user