mirror of
https://github.com/nushell/nushell.git
synced 2025-01-05 05:50:14 +01:00
Run ENV_CONVERSIONS whenever it's modified (#14591)
- this PR should close #14514 # Description Makes updates to `$env.ENV_CONVERSIONS` take effect immediately. # User-Facing Changes No breaking change, `$env.ENV_CONVERSIONS` can be set and its effect used in the same file. # Tests + Formatting - 🟢 toolkit fmt - 🟢 toolkit clippy - 🟢 toolkit test - 🟢 toolkit test stdlib # After Submitting N/A
This commit is contained in:
parent
45ff964cbd
commit
1b01598840
@ -331,43 +331,43 @@ pub(crate) fn compile_load_env(
|
|||||||
path: &[PathMember],
|
path: &[PathMember],
|
||||||
out_reg: RegId,
|
out_reg: RegId,
|
||||||
) -> Result<(), CompileError> {
|
) -> Result<(), CompileError> {
|
||||||
if path.is_empty() {
|
match path {
|
||||||
builder.push(
|
[] => builder.push(
|
||||||
Instruction::LoadVariable {
|
Instruction::LoadVariable {
|
||||||
dst: out_reg,
|
dst: out_reg,
|
||||||
var_id: ENV_VARIABLE_ID,
|
var_id: ENV_VARIABLE_ID,
|
||||||
}
|
}
|
||||||
.into_spanned(span),
|
.into_spanned(span),
|
||||||
)?;
|
)?,
|
||||||
} else {
|
[PathMember::Int { span, .. }, ..] => {
|
||||||
let (key, optional) = match &path[0] {
|
return Err(CompileError::AccessEnvByInt { span: *span })
|
||||||
PathMember::String { val, optional, .. } => (builder.data(val)?, *optional),
|
|
||||||
PathMember::Int { span, .. } => {
|
|
||||||
return Err(CompileError::AccessEnvByInt { span: *span })
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let tail = &path[1..];
|
|
||||||
|
|
||||||
if optional {
|
|
||||||
builder.push(Instruction::LoadEnvOpt { dst: out_reg, key }.into_spanned(span))?;
|
|
||||||
} else {
|
|
||||||
builder.push(Instruction::LoadEnv { dst: out_reg, key }.into_spanned(span))?;
|
|
||||||
}
|
}
|
||||||
|
[PathMember::String {
|
||||||
|
val: key, optional, ..
|
||||||
|
}, tail @ ..] => {
|
||||||
|
let key = builder.data(key)?;
|
||||||
|
|
||||||
if !tail.is_empty() {
|
builder.push(if *optional {
|
||||||
let path = builder.literal(
|
Instruction::LoadEnvOpt { dst: out_reg, key }.into_spanned(span)
|
||||||
Literal::CellPath(Box::new(CellPath {
|
} else {
|
||||||
members: tail.to_vec(),
|
Instruction::LoadEnv { dst: out_reg, key }.into_spanned(span)
|
||||||
}))
|
})?;
|
||||||
.into_spanned(span),
|
|
||||||
)?;
|
if !tail.is_empty() {
|
||||||
builder.push(
|
let path = builder.literal(
|
||||||
Instruction::FollowCellPath {
|
Literal::CellPath(Box::new(CellPath {
|
||||||
src_dst: out_reg,
|
members: tail.to_vec(),
|
||||||
path,
|
}))
|
||||||
}
|
.into_spanned(span),
|
||||||
.into_spanned(span),
|
)?;
|
||||||
)?;
|
builder.push(
|
||||||
|
Instruction::FollowCellPath {
|
||||||
|
src_dst: out_reg,
|
||||||
|
path,
|
||||||
|
}
|
||||||
|
.into_spanned(span),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -3,7 +3,7 @@ use nu_path::canonicalize_with;
|
|||||||
use nu_protocol::{
|
use nu_protocol::{
|
||||||
ast::Expr,
|
ast::Expr,
|
||||||
engine::{Call, EngineState, Stack, StateWorkingSet},
|
engine::{Call, EngineState, Stack, StateWorkingSet},
|
||||||
ShellError, Span, Value, VarId,
|
ShellError, Span, Type, Value, VarId,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
@ -11,12 +11,51 @@ use std::{
|
|||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
const ENV_CONVERSIONS: &str = "ENV_CONVERSIONS";
|
pub const ENV_CONVERSIONS: &str = "ENV_CONVERSIONS";
|
||||||
|
|
||||||
enum ConversionResult {
|
enum ConversionError {
|
||||||
Ok(Value),
|
ShellError(ShellError),
|
||||||
ConversionError(ShellError), // Failure during the conversion itself
|
CellPathError,
|
||||||
CellPathError, // Error looking up the ENV_VAR.to_/from_string fields in $env.ENV_CONVERSIONS
|
}
|
||||||
|
|
||||||
|
impl From<ShellError> for ConversionError {
|
||||||
|
fn from(value: ShellError) -> Self {
|
||||||
|
Self::ShellError(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Translate environment variables from Strings to Values.
|
||||||
|
pub fn convert_env_vars(
|
||||||
|
stack: &mut Stack,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
conversions: &Value,
|
||||||
|
) -> Result<(), ShellError> {
|
||||||
|
let conversions = conversions.as_record()?;
|
||||||
|
for (key, conversion) in conversions.into_iter() {
|
||||||
|
if let Some(val) = stack.get_env_var_insensitive(engine_state, key) {
|
||||||
|
match val.get_type() {
|
||||||
|
Type::String => {}
|
||||||
|
_ => continue,
|
||||||
|
}
|
||||||
|
|
||||||
|
let conversion = conversion
|
||||||
|
.as_record()?
|
||||||
|
.get("from_string")
|
||||||
|
.ok_or(ShellError::MissingRequiredColumn {
|
||||||
|
column: "from_string",
|
||||||
|
span: conversion.span(),
|
||||||
|
})?
|
||||||
|
.as_closure()?;
|
||||||
|
|
||||||
|
let new_val = ClosureEvalOnce::new(engine_state, stack, conversion.clone())
|
||||||
|
.debug(false)
|
||||||
|
.run_with_value(val.clone())?
|
||||||
|
.into_value(val.span())?;
|
||||||
|
|
||||||
|
stack.add_env_var(key.clone(), new_val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Translate environment variables from Strings to Values. Requires config to be already set up in
|
/// Translate environment variables from Strings to Values. Requires config to be already set up in
|
||||||
@ -36,11 +75,11 @@ pub fn convert_env_values(engine_state: &mut EngineState, stack: &Stack) -> Resu
|
|||||||
if let Value::String { .. } = val {
|
if let Value::String { .. } = val {
|
||||||
// Only run from_string on string values
|
// Only run from_string on string values
|
||||||
match get_converted_value(engine_state, stack, name, val, "from_string") {
|
match get_converted_value(engine_state, stack, name, val, "from_string") {
|
||||||
ConversionResult::Ok(v) => {
|
Ok(v) => {
|
||||||
let _ = new_scope.insert(name.to_string(), v);
|
let _ = new_scope.insert(name.to_string(), v);
|
||||||
}
|
}
|
||||||
ConversionResult::ConversionError(e) => error = error.or(Some(e)),
|
Err(ConversionError::ShellError(e)) => error = error.or(Some(e)),
|
||||||
ConversionResult::CellPathError => {
|
Err(ConversionError::CellPathError) => {
|
||||||
let _ = new_scope.insert(name.to_string(), val.clone());
|
let _ = new_scope.insert(name.to_string(), val.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -101,9 +140,9 @@ pub fn env_to_string(
|
|||||||
stack: &Stack,
|
stack: &Stack,
|
||||||
) -> Result<String, ShellError> {
|
) -> Result<String, ShellError> {
|
||||||
match get_converted_value(engine_state, stack, env_name, value, "to_string") {
|
match get_converted_value(engine_state, stack, env_name, value, "to_string") {
|
||||||
ConversionResult::Ok(v) => Ok(v.coerce_into_string()?),
|
Ok(v) => Ok(v.coerce_into_string()?),
|
||||||
ConversionResult::ConversionError(e) => Err(e),
|
Err(ConversionError::ShellError(e)) => Err(e),
|
||||||
ConversionResult::CellPathError => match value.coerce_string() {
|
Err(ConversionError::CellPathError) => match value.coerce_string() {
|
||||||
Ok(s) => Ok(s),
|
Ok(s) => Ok(s),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
if env_name.to_lowercase() == "path" {
|
if env_name.to_lowercase() == "path" {
|
||||||
@ -318,28 +357,24 @@ fn get_converted_value(
|
|||||||
name: &str,
|
name: &str,
|
||||||
orig_val: &Value,
|
orig_val: &Value,
|
||||||
direction: &str,
|
direction: &str,
|
||||||
) -> ConversionResult {
|
) -> Result<Value, ConversionError> {
|
||||||
let conversions = stack.get_env_var(engine_state, ENV_CONVERSIONS);
|
let conversion = stack
|
||||||
let conversion = conversions
|
.get_env_var(engine_state, ENV_CONVERSIONS)
|
||||||
.as_ref()
|
.ok_or(ConversionError::CellPathError)?
|
||||||
.and_then(|val| val.as_record().ok())
|
.as_record()?
|
||||||
.and_then(|record| record.get(name))
|
.get(name)
|
||||||
.and_then(|val| val.as_record().ok())
|
.ok_or(ConversionError::CellPathError)?
|
||||||
.and_then(|record| record.get(direction));
|
.as_record()?
|
||||||
|
.get(direction)
|
||||||
|
.ok_or(ConversionError::CellPathError)?
|
||||||
|
.as_closure()?;
|
||||||
|
|
||||||
if let Some(conversion) = conversion {
|
Ok(
|
||||||
conversion
|
ClosureEvalOnce::new(engine_state, stack, conversion.clone())
|
||||||
.as_closure()
|
.debug(false)
|
||||||
.and_then(|closure| {
|
.run_with_value(orig_val.clone())?
|
||||||
ClosureEvalOnce::new(engine_state, stack, closure.clone())
|
.into_value(orig_val.span())?,
|
||||||
.debug(false)
|
)
|
||||||
.run_with_value(orig_val.clone())
|
|
||||||
})
|
|
||||||
.and_then(|data| data.into_value(orig_val.span()))
|
|
||||||
.map_or_else(ConversionResult::ConversionError, ConversionResult::Ok)
|
|
||||||
} else {
|
|
||||||
ConversionResult::CellPathError
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ensure_path(scope: &mut HashMap<String, Value>, env_path_name: &str) -> Option<ShellError> {
|
fn ensure_path(scope: &mut HashMap<String, Value>, env_path_name: &str) -> Option<ShellError> {
|
||||||
|
@ -14,7 +14,9 @@ use nu_protocol::{
|
|||||||
};
|
};
|
||||||
use nu_utils::IgnoreCaseExt;
|
use nu_utils::IgnoreCaseExt;
|
||||||
|
|
||||||
use crate::{eval::is_automatic_env_var, eval_block_with_early_return};
|
use crate::{
|
||||||
|
convert_env_vars, eval::is_automatic_env_var, eval_block_with_early_return, ENV_CONVERSIONS,
|
||||||
|
};
|
||||||
|
|
||||||
/// Evaluate the compiled representation of a [`Block`].
|
/// Evaluate the compiled representation of a [`Block`].
|
||||||
pub fn eval_ir_block<D: DebugContext>(
|
pub fn eval_ir_block<D: DebugContext>(
|
||||||
@ -384,10 +386,16 @@ fn eval_instruction<D: DebugContext>(
|
|||||||
|
|
||||||
if !is_automatic_env_var(&key) {
|
if !is_automatic_env_var(&key) {
|
||||||
let is_config = key == "config";
|
let is_config = key == "config";
|
||||||
ctx.stack.add_env_var(key.into_owned(), value);
|
let update_conversions = key == ENV_CONVERSIONS;
|
||||||
|
|
||||||
|
ctx.stack.add_env_var(key.into_owned(), value.clone());
|
||||||
|
|
||||||
if is_config {
|
if is_config {
|
||||||
ctx.stack.update_config(ctx.engine_state)?;
|
ctx.stack.update_config(ctx.engine_state)?;
|
||||||
}
|
}
|
||||||
|
if update_conversions {
|
||||||
|
convert_env_vars(ctx.stack, ctx.engine_state, &value)?;
|
||||||
|
}
|
||||||
Ok(Continue)
|
Ok(Continue)
|
||||||
} else {
|
} else {
|
||||||
Err(ShellError::AutomaticEnvVarSetManually {
|
Err(ShellError::AutomaticEnvVarSetManually {
|
||||||
|
@ -190,6 +190,16 @@ fn env_var_case_insensitive() {
|
|||||||
assert!(actual.out.contains("222"));
|
assert!(actual.out.contains("222"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn env_conversion_on_assignment() {
|
||||||
|
let actual = nu!(r#"
|
||||||
|
$env.FOO = "bar:baz:quox"
|
||||||
|
$env.ENV_CONVERSIONS = { FOO: { from_string: {|| split row ":"} } }
|
||||||
|
$env.FOO | to nuon
|
||||||
|
"#);
|
||||||
|
assert_eq!(actual.out, "[bar, baz, quox]");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn std_log_env_vars_are_not_overridden() {
|
fn std_log_env_vars_are_not_overridden() {
|
||||||
let actual = nu_with_std!(
|
let actual = nu_with_std!(
|
||||||
|
Loading…
Reference in New Issue
Block a user