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