1use crate::ClosureEvalOnce;
2use nu_path::canonicalize_with;
3use nu_protocol::{
4 ShellError, Span, Type, Value, VarId,
5 ast::Expr,
6 engine::{Call, EngineState, Stack},
7};
8use std::{
9 collections::HashMap,
10 path::{Path, PathBuf},
11 sync::Arc,
12};
13
14pub const ENV_CONVERSIONS: &str = "ENV_CONVERSIONS";
15
16enum ConversionError {
17 ShellError(ShellError),
18 CellPathError,
19}
20
21impl From<ShellError> for ConversionError {
22 fn from(value: ShellError) -> Self {
23 Self::ShellError(value)
24 }
25}
26
27pub fn convert_env_vars(
29 stack: &mut Stack,
30 engine_state: &EngineState,
31 conversions: &Value,
32) -> Result<(), ShellError> {
33 let conversions = conversions.as_record()?;
34 for (key, conversion) in conversions.into_iter() {
35 if let Some((case_preserve_env_name, val)) =
36 stack.get_env_var_insensitive(engine_state, key)
37 {
38 match val.get_type() {
39 Type::String => {}
40 _ => continue,
41 }
42
43 let conversion = conversion
44 .as_record()?
45 .get("from_string")
46 .ok_or(ShellError::MissingRequiredColumn {
47 column: "from_string",
48 span: conversion.span(),
49 })?
50 .as_closure()?;
51
52 let new_val = ClosureEvalOnce::new(engine_state, stack, conversion.clone())
53 .debug(false)
54 .run_with_value(val.clone())?
55 .into_value(val.span())?;
56
57 stack.add_env_var(case_preserve_env_name.to_string(), new_val);
58 }
59 }
60 Ok(())
61}
62
63pub fn convert_env_values(
70 engine_state: &mut EngineState,
71 stack: &mut Stack,
72) -> Result<(), ShellError> {
73 let mut error = None;
74
75 let mut new_scope = HashMap::new();
76
77 let env_vars = engine_state.render_env_vars();
78
79 for (name, val) in env_vars {
80 if let Value::String { .. } = val {
81 match get_converted_value(engine_state, stack, name, val, "from_string") {
83 Ok(v) => {
84 let _ = new_scope.insert(name.to_string(), v);
85 }
86 Err(ConversionError::ShellError(e)) => error = error.or(Some(e)),
87 Err(ConversionError::CellPathError) => {
88 let _ = new_scope.insert(name.to_string(), val.clone());
89 }
90 }
91 } else {
92 let _ = new_scope.insert(name.to_string(), val.clone());
94 }
95 }
96
97 error = error.or_else(|| ensure_path(engine_state, stack));
98
99 if let Ok(last_overlay_name) = &stack.last_overlay_name() {
100 if let Some(env_vars) = Arc::make_mut(&mut engine_state.env_vars).get_mut(last_overlay_name)
101 {
102 for (k, v) in new_scope {
103 env_vars.insert(k, v);
104 }
105 } else {
106 error = error.or_else(|| {
107 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() })
108 });
109 }
110 } else {
111 error = error.or_else(|| {
112 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() })
113 });
114 }
115
116 if let Some(err) = error {
117 Err(err)
118 } else {
119 Ok(())
120 }
121}
122
123pub fn env_to_string(
127 env_name: &str,
128 value: &Value,
129 engine_state: &EngineState,
130 stack: &Stack,
131) -> Result<String, ShellError> {
132 match get_converted_value(engine_state, stack, env_name, value, "to_string") {
133 Ok(v) => Ok(v.coerce_into_string()?),
134 Err(ConversionError::ShellError(e)) => Err(e),
135 Err(ConversionError::CellPathError) => match value.coerce_string() {
136 Ok(s) => Ok(s),
137 Err(_) => {
138 if env_name.to_lowercase() == "path" {
139 match value {
141 Value::List { vals, .. } => {
142 let paths: Vec<String> = vals
143 .iter()
144 .filter_map(|v| v.coerce_str().ok())
145 .map(|s| nu_path::expand_tilde(&*s).to_string_lossy().into_owned())
146 .collect();
147
148 std::env::join_paths(paths.iter().map(AsRef::<str>::as_ref))
149 .map(|p| p.to_string_lossy().to_string())
150 .map_err(|_| ShellError::EnvVarNotAString {
151 envvar_name: env_name.to_string(),
152 span: value.span(),
153 })
154 }
155 _ => Err(ShellError::EnvVarNotAString {
156 envvar_name: env_name.to_string(),
157 span: value.span(),
158 }),
159 }
160 } else {
161 Err(ShellError::EnvVarNotAString {
162 envvar_name: env_name.to_string(),
163 span: value.span(),
164 })
165 }
166 }
167 },
168 }
169}
170
171pub fn env_to_strings(
173 engine_state: &EngineState,
174 stack: &Stack,
175) -> Result<HashMap<String, String>, ShellError> {
176 let env_vars = stack.get_env_vars(engine_state);
177 let mut env_vars_str = HashMap::new();
178 for (env_name, val) in env_vars {
179 match env_to_string(&env_name, &val, engine_state, stack) {
180 Ok(val_str) => {
181 env_vars_str.insert(env_name, val_str);
182 }
183 Err(ShellError::EnvVarNotAString { .. }) => {} Err(e) => return Err(e),
185 }
186 }
187
188 Ok(env_vars_str)
189}
190
191pub fn path_str(
193 engine_state: &EngineState,
194 stack: &Stack,
195 span: Span,
196) -> Result<String, ShellError> {
197 let (pathname, pathval) = match stack.get_env_var_insensitive(engine_state, "path") {
198 Some((_, v)) => Ok((if cfg!(windows) { "Path" } else { "PATH" }, v)),
199 None => Err(ShellError::EnvVarNotFoundAtRuntime {
200 envvar_name: if cfg!(windows) {
201 "Path".to_string()
202 } else {
203 "PATH".to_string()
204 },
205 span,
206 }),
207 }?;
208
209 env_to_string(pathname, pathval, engine_state, stack)
210}
211
212pub const DIR_VAR_PARSER_INFO: &str = "dirs_var";
213pub fn get_dirs_var_from_call(stack: &Stack, call: &Call) -> Option<VarId> {
214 call.get_parser_info(stack, DIR_VAR_PARSER_INFO)
215 .and_then(|x| {
216 if let Expr::Var(id) = x.expr {
217 Some(id)
218 } else {
219 None
220 }
221 })
222}
223
224pub fn find_in_dirs_env(
236 filename: &str,
237 engine_state: &EngineState,
238 stack: &Stack,
239 dirs_var: Option<VarId>,
240) -> Result<Option<PathBuf>, ShellError> {
241 let cwd = if let Some(pwd) = stack.get_env_var(engine_state, "FILE_PWD") {
243 match env_to_string("FILE_PWD", pwd, engine_state, stack) {
244 Ok(cwd) => {
245 if Path::new(&cwd).is_absolute() {
246 cwd
247 } else {
248 return Err(ShellError::GenericError {
249 error: "Invalid current directory".into(),
250 msg: format!(
251 "The 'FILE_PWD' environment variable must be set to an absolute path. Found: '{cwd}'"
252 ),
253 span: Some(pwd.span()),
254 help: None,
255 inner: vec![],
256 });
257 }
258 }
259 Err(e) => return Err(e),
260 }
261 } else {
262 engine_state.cwd_as_string(Some(stack))?
263 };
264
265 let check_dir = |lib_dirs: Option<&Value>| -> Option<PathBuf> {
266 if let Ok(p) = canonicalize_with(filename, &cwd) {
267 return Some(p);
268 }
269 let path = Path::new(filename);
270 if !path.is_relative() {
271 return None;
272 }
273
274 lib_dirs?
275 .as_list()
276 .ok()?
277 .iter()
278 .map(|lib_dir| -> Option<PathBuf> {
279 let dir = lib_dir.to_path().ok()?;
280 let dir_abs = canonicalize_with(dir, &cwd).ok()?;
281 canonicalize_with(filename, dir_abs).ok()
282 })
283 .find(Option::is_some)
284 .flatten()
285 };
286
287 let lib_dirs = dirs_var.and_then(|var_id| engine_state.get_var(var_id).const_val.as_ref());
288 let lib_dirs_fallback = stack.get_env_var(engine_state, "NU_LIB_DIRS");
290
291 Ok(check_dir(lib_dirs).or_else(|| check_dir(lib_dirs_fallback)))
292}
293
294fn get_converted_value(
295 engine_state: &EngineState,
296 stack: &Stack,
297 name: &str,
298 orig_val: &Value,
299 direction: &str,
300) -> Result<Value, ConversionError> {
301 let conversion = stack
302 .get_env_var(engine_state, ENV_CONVERSIONS)
303 .ok_or(ConversionError::CellPathError)?
304 .as_record()?
305 .get(name)
306 .ok_or(ConversionError::CellPathError)?
307 .as_record()?
308 .get(direction)
309 .ok_or(ConversionError::CellPathError)?
310 .as_closure()?;
311
312 Ok(
313 ClosureEvalOnce::new(engine_state, stack, conversion.clone())
314 .debug(false)
315 .run_with_value(orig_val.clone())?
316 .into_value(orig_val.span())?,
317 )
318}
319
320fn ensure_path(engine_state: &EngineState, stack: &mut Stack) -> Option<ShellError> {
321 let mut error = None;
322
323 if let Some((preserve_case_name, value)) = stack.get_env_var_insensitive(engine_state, "Path") {
325 let span = value.span();
326 match value {
327 Value::String { val, .. } => {
328 let paths = std::env::split_paths(val)
330 .map(|p| Value::string(p.to_string_lossy().to_string(), span))
331 .collect();
332
333 stack.add_env_var(preserve_case_name.to_string(), Value::list(paths, span));
334 }
335 Value::List { vals, .. } => {
336 if !vals.iter().all(|v| matches!(v, Value::String { .. })) {
338 error = error.or_else(|| {
339 Some(ShellError::GenericError {
340 error: format!(
341 "Incorrect {preserve_case_name} environment variable value"
342 ),
343 msg: format!("{preserve_case_name} must be a list of strings"),
344 span: Some(span),
345 help: None,
346 inner: vec![],
347 })
348 });
349 }
350 }
351
352 val => {
353 let span = val.span();
355
356 error = error.or_else(|| {
357 Some(ShellError::GenericError {
358 error: format!("Incorrect {preserve_case_name} environment variable value"),
359 msg: format!("{preserve_case_name} must be a list of strings"),
360 span: Some(span),
361 help: None,
362 inner: vec![],
363 })
364 });
365 }
366 }
367 }
368
369 error
370}