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