1use crate::ClosureEvalOnce;
2use nu_path::canonicalize_with;
3use nu_protocol::{
4 ast::Expr,
5 engine::{Call, EngineState, Stack, StateWorkingSet},
6 shell_error::io::{ErrorKindExt, IoError, NotFound},
7 ShellError, Span, Type, Value, VarId,
8};
9use std::{
10 collections::HashMap,
11 path::{Path, PathBuf},
12 sync::Arc,
13};
14
15pub const ENV_CONVERSIONS: &str = "ENV_CONVERSIONS";
16
17enum ConversionError {
18 ShellError(ShellError),
19 CellPathError,
20}
21
22impl From<ShellError> for ConversionError {
23 fn from(value: ShellError) -> Self {
24 Self::ShellError(value)
25 }
26}
27
28pub fn convert_env_vars(
30 stack: &mut Stack,
31 engine_state: &EngineState,
32 conversions: &Value,
33) -> Result<(), ShellError> {
34 let conversions = conversions.as_record()?;
35 for (key, conversion) in conversions.into_iter() {
36 if let Some((case_preserve_env_name, val)) =
37 stack.get_env_var_insensitive(engine_state, key)
38 {
39 match val.get_type() {
40 Type::String => {}
41 _ => continue,
42 }
43
44 let conversion = conversion
45 .as_record()?
46 .get("from_string")
47 .ok_or(ShellError::MissingRequiredColumn {
48 column: "from_string",
49 span: conversion.span(),
50 })?
51 .as_closure()?;
52
53 let new_val = ClosureEvalOnce::new(engine_state, stack, conversion.clone())
54 .debug(false)
55 .run_with_value(val.clone())?
56 .into_value(val.span())?;
57
58 stack.add_env_var(case_preserve_env_name.to_string(), new_val);
59 }
60 }
61 Ok(())
62}
63
64pub fn convert_env_values(
71 engine_state: &mut EngineState,
72 stack: &mut Stack,
73) -> Result<(), ShellError> {
74 let mut error = None;
75
76 let mut new_scope = HashMap::new();
77
78 let env_vars = engine_state.render_env_vars();
79
80 for (name, val) in env_vars {
81 if let Value::String { .. } = val {
82 match get_converted_value(engine_state, stack, name, val, "from_string") {
84 Ok(v) => {
85 let _ = new_scope.insert(name.to_string(), v);
86 }
87 Err(ConversionError::ShellError(e)) => error = error.or(Some(e)),
88 Err(ConversionError::CellPathError) => {
89 let _ = new_scope.insert(name.to_string(), val.clone());
90 }
91 }
92 } else {
93 let _ = new_scope.insert(name.to_string(), val.clone());
95 }
96 }
97
98 error = error.or_else(|| ensure_path(engine_state, stack));
99
100 if let Ok(last_overlay_name) = &stack.last_overlay_name() {
101 if let Some(env_vars) = Arc::make_mut(&mut engine_state.env_vars).get_mut(last_overlay_name)
102 {
103 for (k, v) in new_scope {
104 env_vars.insert(k, v);
105 }
106 } else {
107 error = error.or_else(|| {
108 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() })
109 });
110 }
111 } else {
112 error = error.or_else(|| {
113 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() })
114 });
115 }
116
117 if let Some(err) = error {
118 Err(err)
119 } else {
120 Ok(())
121 }
122}
123
124pub fn env_to_string(
128 env_name: &str,
129 value: &Value,
130 engine_state: &EngineState,
131 stack: &Stack,
132) -> Result<String, ShellError> {
133 match get_converted_value(engine_state, stack, env_name, value, "to_string") {
134 Ok(v) => Ok(v.coerce_into_string()?),
135 Err(ConversionError::ShellError(e)) => Err(e),
136 Err(ConversionError::CellPathError) => match value.coerce_string() {
137 Ok(s) => Ok(s),
138 Err(_) => {
139 if env_name.to_lowercase() == "path" {
140 match value {
142 Value::List { vals, .. } => {
143 let paths: Vec<String> = vals
144 .iter()
145 .filter_map(|v| v.coerce_str().ok())
146 .map(|s| nu_path::expand_tilde(&*s).to_string_lossy().into_owned())
147 .collect();
148
149 std::env::join_paths(paths.iter().map(AsRef::<str>::as_ref))
150 .map(|p| p.to_string_lossy().to_string())
151 .map_err(|_| ShellError::EnvVarNotAString {
152 envvar_name: env_name.to_string(),
153 span: value.span(),
154 })
155 }
156 _ => Err(ShellError::EnvVarNotAString {
157 envvar_name: env_name.to_string(),
158 span: value.span(),
159 }),
160 }
161 } else {
162 Err(ShellError::EnvVarNotAString {
163 envvar_name: env_name.to_string(),
164 span: value.span(),
165 })
166 }
167 }
168 },
169 }
170}
171
172pub fn env_to_strings(
174 engine_state: &EngineState,
175 stack: &Stack,
176) -> Result<HashMap<String, String>, ShellError> {
177 let env_vars = stack.get_env_vars(engine_state);
178 let mut env_vars_str = HashMap::new();
179 for (env_name, val) in env_vars {
180 match env_to_string(&env_name, &val, engine_state, stack) {
181 Ok(val_str) => {
182 env_vars_str.insert(env_name, val_str);
183 }
184 Err(ShellError::EnvVarNotAString { .. }) => {} Err(e) => return Err(e),
186 }
187 }
188
189 Ok(env_vars_str)
190}
191
192#[deprecated(since = "0.92.3", note = "please use `EngineState::cwd()` instead")]
197pub fn current_dir_str(engine_state: &EngineState, stack: &Stack) -> Result<String, ShellError> {
198 #[allow(deprecated)]
199 current_dir(engine_state, stack).map(|path| path.to_string_lossy().to_string())
200}
201
202#[deprecated(since = "0.92.3", note = "please use `EngineState::cwd()` instead")]
206pub fn current_dir_str_const(working_set: &StateWorkingSet) -> Result<String, ShellError> {
207 #[allow(deprecated)]
208 current_dir_const(working_set).map(|path| path.to_string_lossy().to_string())
209}
210
211#[deprecated(since = "0.92.3", note = "please use `EngineState::cwd()` instead")]
216pub fn current_dir(engine_state: &EngineState, stack: &Stack) -> Result<PathBuf, ShellError> {
217 let cwd = engine_state.cwd(Some(stack))?;
218 canonicalize_with(&cwd, ".").map_err(|err| {
223 ShellError::Io(IoError::new_internal_with_path(
224 err.kind().not_found_as(NotFound::Directory),
225 "Could not canonicalize current dir",
226 nu_protocol::location!(),
227 PathBuf::from(cwd),
228 ))
229 })
230}
231
232#[deprecated(since = "0.92.3", note = "please use `EngineState::cwd()` instead")]
236pub fn current_dir_const(working_set: &StateWorkingSet) -> Result<PathBuf, ShellError> {
237 let cwd = working_set.permanent_state.cwd(None)?;
238 canonicalize_with(&cwd, ".").map_err(|err| {
243 ShellError::Io(IoError::new_internal_with_path(
244 err.kind().not_found_as(NotFound::Directory),
245 "Could not canonicalize current dir",
246 nu_protocol::location!(),
247 PathBuf::from(cwd),
248 ))
249 })
250}
251
252pub fn path_str(
254 engine_state: &EngineState,
255 stack: &Stack,
256 span: Span,
257) -> Result<String, ShellError> {
258 let (pathname, pathval) = match stack.get_env_var_insensitive(engine_state, "path") {
259 Some((_, v)) => Ok((if cfg!(windows) { "Path" } else { "PATH" }, v)),
260 None => Err(ShellError::EnvVarNotFoundAtRuntime {
261 envvar_name: if cfg!(windows) {
262 "Path".to_string()
263 } else {
264 "PATH".to_string()
265 },
266 span,
267 }),
268 }?;
269
270 env_to_string(pathname, pathval, engine_state, stack)
271}
272
273pub const DIR_VAR_PARSER_INFO: &str = "dirs_var";
274pub fn get_dirs_var_from_call(stack: &Stack, call: &Call) -> Option<VarId> {
275 call.get_parser_info(stack, DIR_VAR_PARSER_INFO)
276 .and_then(|x| {
277 if let Expr::Var(id) = x.expr {
278 Some(id)
279 } else {
280 None
281 }
282 })
283}
284
285pub fn find_in_dirs_env(
297 filename: &str,
298 engine_state: &EngineState,
299 stack: &Stack,
300 dirs_var: Option<VarId>,
301) -> Result<Option<PathBuf>, ShellError> {
302 let cwd = if let Some(pwd) = stack.get_env_var(engine_state, "FILE_PWD") {
304 match env_to_string("FILE_PWD", pwd, engine_state, stack) {
305 Ok(cwd) => {
306 if Path::new(&cwd).is_absolute() {
307 cwd
308 } else {
309 return Err(ShellError::GenericError {
310 error: "Invalid current directory".into(),
311 msg: format!("The 'FILE_PWD' environment variable must be set to an absolute path. Found: '{cwd}'"),
312 span: Some(pwd.span()),
313 help: None,
314 inner: vec![]
315 });
316 }
317 }
318 Err(e) => return Err(e),
319 }
320 } else {
321 engine_state.cwd_as_string(Some(stack))?
322 };
323
324 let check_dir = |lib_dirs: Option<&Value>| -> Option<PathBuf> {
325 if let Ok(p) = canonicalize_with(filename, &cwd) {
326 return Some(p);
327 }
328 let path = Path::new(filename);
329 if !path.is_relative() {
330 return None;
331 }
332
333 lib_dirs?
334 .as_list()
335 .ok()?
336 .iter()
337 .map(|lib_dir| -> Option<PathBuf> {
338 let dir = lib_dir.to_path().ok()?;
339 let dir_abs = canonicalize_with(dir, &cwd).ok()?;
340 canonicalize_with(filename, dir_abs).ok()
341 })
342 .find(Option::is_some)
343 .flatten()
344 };
345
346 let lib_dirs = dirs_var.and_then(|var_id| engine_state.get_var(var_id).const_val.as_ref());
347 let lib_dirs_fallback = stack.get_env_var(engine_state, "NU_LIB_DIRS");
349
350 Ok(check_dir(lib_dirs).or_else(|| check_dir(lib_dirs_fallback)))
351}
352
353fn get_converted_value(
354 engine_state: &EngineState,
355 stack: &Stack,
356 name: &str,
357 orig_val: &Value,
358 direction: &str,
359) -> Result<Value, ConversionError> {
360 let conversion = stack
361 .get_env_var(engine_state, ENV_CONVERSIONS)
362 .ok_or(ConversionError::CellPathError)?
363 .as_record()?
364 .get(name)
365 .ok_or(ConversionError::CellPathError)?
366 .as_record()?
367 .get(direction)
368 .ok_or(ConversionError::CellPathError)?
369 .as_closure()?;
370
371 Ok(
372 ClosureEvalOnce::new(engine_state, stack, conversion.clone())
373 .debug(false)
374 .run_with_value(orig_val.clone())?
375 .into_value(orig_val.span())?,
376 )
377}
378
379fn ensure_path(engine_state: &EngineState, stack: &mut Stack) -> Option<ShellError> {
380 let mut error = None;
381
382 if let Some((preserve_case_name, value)) = stack.get_env_var_insensitive(engine_state, "Path") {
384 let span = value.span();
385 match value {
386 Value::String { val, .. } => {
387 let paths = std::env::split_paths(val)
389 .map(|p| Value::string(p.to_string_lossy().to_string(), span))
390 .collect();
391
392 stack.add_env_var(preserve_case_name.to_string(), Value::list(paths, span));
393 }
394 Value::List { vals, .. } => {
395 if !vals.iter().all(|v| matches!(v, Value::String { .. })) {
397 error = error.or_else(|| {
398 Some(ShellError::GenericError {
399 error: format!(
400 "Incorrect {preserve_case_name} environment variable value"
401 ),
402 msg: format!("{preserve_case_name} must be a list of strings"),
403 span: Some(span),
404 help: None,
405 inner: vec![],
406 })
407 });
408 }
409 }
410
411 val => {
412 let span = val.span();
414
415 error = error.or_else(|| {
416 Some(ShellError::GenericError {
417 error: format!("Incorrect {preserve_case_name} environment variable value"),
418 msg: format!("{preserve_case_name} must be a list of strings"),
419 span: Some(span),
420 help: None,
421 inner: vec![],
422 })
423 });
424 }
425 }
426 }
427
428 error
429}