use std::collections::HashMap;
use std::path::{Path, PathBuf};
use nu_protocol::ast::{Call, Expr, PathMember};
use nu_protocol::engine::{EngineState, Stack, StateWorkingSet, PWD_ENV};
use nu_protocol::{Config, PipelineData, ShellError, Span, Value, VarId};
use nu_path::canonicalize_with;
use crate::eval_block;
#[cfg(windows)]
const ENV_PATH_NAME: &str = "Path";
#[cfg(windows)]
const ENV_PATH_NAME_SECONDARY: &str = "PATH";
#[cfg(not(windows))]
const ENV_PATH_NAME: &str = "PATH";
const ENV_CONVERSIONS: &str = "ENV_CONVERSIONS";
#[allow(dead_code)]
enum ConversionResult {
Ok(Value),
ConversionError(ShellError), GeneralError(ShellError), CellPathError, }
pub fn convert_env_values(engine_state: &mut EngineState, stack: &Stack) -> Option<ShellError> {
let mut error = None;
let mut new_scope = HashMap::new();
let env_vars = engine_state.render_env_vars();
for (name, val) in env_vars {
match get_converted_value(engine_state, stack, name, val, "from_string") {
ConversionResult::Ok(v) => {
let _ = new_scope.insert(name.to_string(), v);
}
ConversionResult::ConversionError(e) => error = error.or(Some(e)),
ConversionResult::GeneralError(_) => continue,
ConversionResult::CellPathError => {
let _ = new_scope.insert(name.to_string(), val.clone());
}
}
}
#[cfg(not(windows))]
{
error = error.or_else(|| ensure_path(&mut new_scope, ENV_PATH_NAME));
}
#[cfg(windows)]
{
let first_result = ensure_path(&mut new_scope, ENV_PATH_NAME);
if first_result.is_some() {
let second_result = ensure_path(&mut new_scope, ENV_PATH_NAME_SECONDARY);
if second_result.is_some() {
error = error.or(first_result);
}
}
}
if let Ok(last_overlay_name) = &stack.last_overlay_name() {
if let Some(env_vars) = engine_state.env_vars.get_mut(last_overlay_name) {
for (k, v) in new_scope {
env_vars.insert(k, v);
}
} else {
error = error.or_else(|| {
Some(ShellError::NushellFailedHelp { msg: "Last active overlay not found in permanent state.".into(), help: "This error happened during the conversion of environment variables from strings to Nushell values.".into() })
});
}
} else {
error = error.or_else(|| {
Some(ShellError::NushellFailedHelp { msg: "Last active overlay not found in stack.".into(), help: "This error happened during the conversion of environment variables from strings to Nushell values.".into() })
});
}
error
}
pub fn env_to_string(
env_name: &str,
value: &Value,
engine_state: &EngineState,
stack: &Stack,
) -> Result<String, ShellError> {
match get_converted_value(engine_state, stack, env_name, value, "to_string") {
ConversionResult::Ok(v) => Ok(v.as_string()?),
ConversionResult::ConversionError(e) => Err(e),
ConversionResult::GeneralError(e) => Err(e),
ConversionResult::CellPathError => match value.as_string() {
Ok(s) => Ok(s),
Err(_) => {
if env_name == ENV_PATH_NAME {
match value {
Value::List { vals, .. } => {
let paths = vals
.iter()
.map(|v| v.as_string())
.collect::<Result<Vec<_>, _>>()?;
match std::env::join_paths(paths) {
Ok(p) => Ok(p.to_string_lossy().to_string()),
Err(_) => Err(ShellError::EnvVarNotAString {
envvar_name: env_name.to_string(),
span: value.span(),
}),
}
}
_ => Err(ShellError::EnvVarNotAString {
envvar_name: env_name.to_string(),
span: value.span(),
}),
}
} else {
Err(ShellError::EnvVarNotAString {
envvar_name: env_name.to_string(),
span: value.span(),
})
}
}
},
}
}
pub fn env_to_strings(
engine_state: &EngineState,
stack: &Stack,
) -> Result<HashMap<String, String>, ShellError> {
let env_vars = stack.get_env_vars(engine_state);
let mut env_vars_str = HashMap::new();
for (env_name, val) in env_vars {
match env_to_string(&env_name, &val, engine_state, stack) {
Ok(val_str) => {
env_vars_str.insert(env_name, val_str);
}
Err(ShellError::EnvVarNotAString { .. }) => {} Err(e) => return Err(e),
}
}
Ok(env_vars_str)
}
pub fn current_dir_str(engine_state: &EngineState, stack: &Stack) -> Result<String, ShellError> {
if let Some(pwd) = stack.get_env_var(engine_state, PWD_ENV) {
match env_to_string(PWD_ENV, &pwd, engine_state, stack) {
Ok(cwd) => {
if Path::new(&cwd).is_absolute() {
Ok(cwd)
} else {
Err(ShellError::GenericError(
"Invalid current directory".to_string(),
format!("The 'PWD' environment variable must be set to an absolute path. Found: '{cwd}'"),
Some(pwd.span()),
None,
Vec::new()
))
}
}
Err(e) => Err(e),
}
} else {
Err(ShellError::GenericError(
"Current directory not found".to_string(),
"".to_string(),
None,
Some("The environment variable 'PWD' was not found. It is required to define the current directory.".to_string()),
Vec::new(),
))
}
}
pub fn current_dir_str_const(working_set: &StateWorkingSet) -> Result<String, ShellError> {
if let Some(pwd) = working_set.get_env_var(PWD_ENV) {
let span = pwd.span();
match pwd {
Value::String { val, .. } => {
if Path::new(val).is_absolute() {
Ok(val.clone())
} else {
Err(ShellError::GenericError(
"Invalid current directory".to_string(),
format!("The 'PWD' environment variable must be set to an absolute path. Found: '{val}'"),
Some(span),
None,
Vec::new()
))
}
}
_ => Err(ShellError::GenericError(
"PWD is not a string".to_string(),
"".to_string(),
None,
Some(
"Cusrrent working directory environment variable 'PWD' must be a string."
.to_string(),
),
Vec::new(),
)),
}
} else {
Err(ShellError::GenericError(
"Current directory not found".to_string(),
"".to_string(),
None,
Some("The environment variable 'PWD' was not found. It is required to define the current directory.".to_string()),
Vec::new(),
))
}
}
pub fn current_dir(engine_state: &EngineState, stack: &Stack) -> Result<PathBuf, ShellError> {
current_dir_str(engine_state, stack).map(PathBuf::from)
}
pub fn current_dir_const(working_set: &StateWorkingSet) -> Result<PathBuf, ShellError> {
current_dir_str_const(working_set).map(PathBuf::from)
}
pub fn path_str(
engine_state: &EngineState,
stack: &Stack,
span: Span,
) -> Result<String, ShellError> {
let (pathname, pathval) = match stack.get_env_var(engine_state, ENV_PATH_NAME) {
Some(v) => Ok((ENV_PATH_NAME, v)),
None => {
#[cfg(windows)]
match stack.get_env_var(engine_state, ENV_PATH_NAME_SECONDARY) {
Some(v) => Ok((ENV_PATH_NAME_SECONDARY, v)),
None => Err(ShellError::EnvVarNotFoundAtRuntime {
envvar_name: ENV_PATH_NAME_SECONDARY.to_string(),
span,
}),
}
#[cfg(not(windows))]
Err(ShellError::EnvVarNotFoundAtRuntime {
envvar_name: ENV_PATH_NAME.to_string(),
span,
})
}
}?;
env_to_string(pathname, &pathval, engine_state, stack)
}
pub const DIR_VAR_PARSER_INFO: &str = "dirs_var";
pub fn get_dirs_var_from_call(call: &Call) -> Option<VarId> {
call.get_parser_info(DIR_VAR_PARSER_INFO).and_then(|x| {
if let Expr::Var(id) = x.expr {
Some(id)
} else {
None
}
})
}
pub fn find_in_dirs_env(
filename: &str,
engine_state: &EngineState,
stack: &Stack,
dirs_var: Option<VarId>,
) -> Result<Option<PathBuf>, ShellError> {
let cwd = if let Some(pwd) = stack.get_env_var(engine_state, "FILE_PWD") {
match env_to_string("FILE_PWD", &pwd, engine_state, stack) {
Ok(cwd) => {
if Path::new(&cwd).is_absolute() {
cwd
} else {
return Err(ShellError::GenericError(
"Invalid current directory".to_string(),
format!("The 'FILE_PWD' environment variable must be set to an absolute path. Found: '{cwd}'"),
Some(pwd.span()),
None,
Vec::new()
));
}
}
Err(e) => return Err(e),
}
} else {
current_dir_str(engine_state, stack)?
};
let check_dir = |lib_dirs: Option<Value>| -> Option<PathBuf> {
if let Ok(p) = canonicalize_with(filename, &cwd) {
return Some(p);
}
let path = Path::new(filename);
if !path.is_relative() {
return None;
}
lib_dirs?
.as_list()
.ok()?
.iter()
.map(|lib_dir| -> Option<PathBuf> {
let dir = lib_dir.as_path().ok()?;
let dir_abs = canonicalize_with(dir, &cwd).ok()?;
canonicalize_with(filename, dir_abs).ok()
})
.find(Option::is_some)
.flatten()
};
let lib_dirs = dirs_var.and_then(|var_id| engine_state.get_var(var_id).const_val.clone());
let lib_dirs_fallback = stack.get_env_var(engine_state, "NU_LIB_DIRS");
Ok(check_dir(lib_dirs).or_else(|| check_dir(lib_dirs_fallback)))
}
pub fn get_config(engine_state: &EngineState, stack: &Stack) -> Config {
if let Some(mut config_record) = stack.get_env_var(engine_state, "config") {
config_record.into_config(engine_state.get_config()).0
} else {
engine_state.get_config().clone()
}
}
fn get_converted_value(
engine_state: &EngineState,
stack: &Stack,
name: &str,
orig_val: &Value,
direction: &str,
) -> ConversionResult {
if let Some(env_conversions) = stack.get_env_var(engine_state, ENV_CONVERSIONS) {
let env_span = env_conversions.span();
let val_span = orig_val.span();
let path_members = &[
PathMember::String {
val: name.to_string(),
span: env_span,
optional: false,
},
PathMember::String {
val: direction.to_string(),
span: env_span,
optional: false,
},
];
if let Ok(v) = env_conversions.follow_cell_path_not_from_user_input(path_members, false) {
let from_span = v.span();
match v {
Value::Closure { val: block_id, .. } => {
let block = engine_state.get_block(block_id);
if let Some(var) = block.signature.get_positional(0) {
let mut stack = stack.gather_captures(engine_state, &block.captures);
if let Some(var_id) = &var.var_id {
stack.add_var(*var_id, orig_val.clone());
}
let result = eval_block(
engine_state,
&mut stack,
block,
PipelineData::new_with_metadata(None, val_span),
true,
true,
);
match result {
Ok(data) => ConversionResult::Ok(data.into_value(val_span)),
Err(e) => ConversionResult::ConversionError(e),
}
} else {
ConversionResult::ConversionError(ShellError::MissingParameter {
param_name: "block input".into(),
span: from_span,
})
}
}
_ => ConversionResult::CellPathError,
}
} else {
ConversionResult::CellPathError
}
} else {
ConversionResult::CellPathError
}
}
fn ensure_path(scope: &mut HashMap<String, Value>, env_path_name: &str) -> Option<ShellError> {
let mut error = None;
if let Some(value) = scope.get(env_path_name) {
let span = value.span();
match value {
Value::String { val, .. } => {
let paths = std::env::split_paths(val)
.map(|p| Value::string(p.to_string_lossy().to_string(), span))
.collect();
scope.insert(env_path_name.to_string(), Value::list(paths, span));
}
Value::List { vals, .. } => {
if !vals.iter().all(|v| matches!(v, Value::String { .. })) {
error = error.or_else(|| {
Some(ShellError::GenericError(
format!("Wrong {env_path_name} environment variable value"),
format!("{env_path_name} must be a list of strings"),
Some(span),
None,
Vec::new(),
))
});
}
}
val => {
let span = val.span();
error = error.or_else(|| {
Some(ShellError::GenericError(
format!("Wrong {env_path_name} environment variable value"),
format!("{env_path_name} must be a list of strings"),
Some(span),
None,
Vec::new(),
))
});
}
}
}
error
}