1use crate::{
6 BlockId, Config, HistoryFileFormat, HistoryPath, PipelineData, Record, ShellError, Span, Value,
7 VarId,
8 ast::{Assignment, Block, Call, Expr, Expression, ExternalArgument},
9 debugger::{DebugContext, WithoutDebug},
10 engine::{EngineState, StateWorkingSet},
11 eval_base::Eval,
12 record,
13 shell_error::generic::GenericError,
14};
15use nu_system::os_info::{get_kernel_version, get_os_arch, get_os_family, get_os_name};
16use std::{
17 path::{Path, PathBuf},
18 sync::Arc,
19};
20
21pub(crate) fn create_nu_constant(engine_state: &EngineState, span: Span) -> Value {
25 fn canonicalize_path(engine_state: &EngineState, path: &Path) -> PathBuf {
26 #[allow(deprecated)]
27 let cwd = engine_state.current_work_dir();
28
29 if path.exists() {
30 match nu_path::canonicalize_with(path, cwd) {
31 Ok(canon_path) => canon_path,
32 Err(_) => path.to_owned(),
33 }
34 } else {
35 path.to_owned()
36 }
37 }
38
39 let mut record = Record::new();
40
41 let config_path = match nu_path::nu_config_dir() {
42 Some(path) => Ok(canonicalize_path(engine_state, path.as_ref())),
43 None => Err(Value::error(ShellError::ConfigDirNotFound { span }, span)),
44 };
45
46 record.push(
47 "default-config-dir",
48 config_path.as_ref().map_or_else(
49 |e| e.clone(),
50 |path| Value::string(path.to_string_lossy(), span),
51 ),
52 );
53
54 record.push(
55 "config-path",
56 if let Some(path) = engine_state.get_config_path("config-path") {
57 let canon_config_path = canonicalize_path(engine_state, path);
58 Value::string(canon_config_path.to_string_lossy(), span)
59 } else {
60 config_path.clone().map_or_else(
61 |e| e,
62 |mut path| {
63 path.push("config.nu");
64 let canon_config_path = canonicalize_path(engine_state, &path);
65 Value::string(canon_config_path.to_string_lossy(), span)
66 },
67 )
68 },
69 );
70
71 record.push(
72 "env-path",
73 if let Some(path) = engine_state.get_config_path("env-path") {
74 let canon_env_path = canonicalize_path(engine_state, path);
75 Value::string(canon_env_path.to_string_lossy(), span)
76 } else {
77 config_path.clone().map_or_else(
78 |e| e,
79 |mut path| {
80 path.push("env.nu");
81 let canon_env_path = canonicalize_path(engine_state, &path);
82 Value::string(canon_env_path.to_string_lossy(), span)
83 },
84 )
85 },
86 );
87
88 record.push(
89 "history-path",
90 match &engine_state.config.history.path {
91 HistoryPath::Disabled => Value::string("", span),
92 HistoryPath::Custom(custom_path) => {
93 let effective_path = if custom_path.is_dir() {
94 custom_path.join(engine_state.config.history.file_format.default_file_name())
95 } else {
96 custom_path.clone()
97 };
98 let canon_hist_path = canonicalize_path(engine_state, &effective_path);
99 Value::string(canon_hist_path.to_string_lossy(), span)
100 }
101 HistoryPath::Default => config_path.clone().map_or_else(
102 |e| e,
103 |mut path| {
104 match engine_state.config.history.file_format {
105 HistoryFileFormat::Sqlite => {
106 path.push("history.sqlite3");
107 }
108 HistoryFileFormat::Plaintext => {
109 path.push("history.txt");
110 }
111 }
112 let canon_hist_path = canonicalize_path(engine_state, &path);
113 Value::string(canon_hist_path.to_string_lossy(), span)
114 },
115 ),
116 },
117 );
118
119 record.push(
120 "loginshell-path",
121 config_path.clone().map_or_else(
122 |e| e,
123 |mut path| {
124 path.push("login.nu");
125 let canon_login_path = canonicalize_path(engine_state, &path);
126 Value::string(canon_login_path.to_string_lossy(), span)
127 },
128 ),
129 );
130
131 #[cfg(feature = "plugin")]
132 {
133 record.push(
134 "plugin-path",
135 if let Some(path) = &engine_state.plugin_path {
136 let canon_plugin_path = canonicalize_path(engine_state, path);
137 Value::string(canon_plugin_path.to_string_lossy(), span)
138 } else {
139 config_path.clone().map_or_else(
141 |e| e,
142 |mut path| {
143 path.push("plugin.msgpackz");
144 let canonical_plugin_path = canonicalize_path(engine_state, &path);
145 Value::string(canonical_plugin_path.to_string_lossy(), span)
146 },
147 )
148 },
149 );
150 }
151
152 record.push(
153 "home-dir",
154 if let Some(path) = nu_path::home_dir() {
155 let canon_home_path = canonicalize_path(engine_state, path.as_ref());
156 Value::string(canon_home_path.to_string_lossy(), span)
157 } else {
158 Value::error(
159 ShellError::Generic(GenericError::new(
160 "setting $nu.home-dir failed",
161 "Could not get home directory",
162 span,
163 )),
164 span,
165 )
166 },
167 );
168
169 record.push(
170 "data-dir",
171 if let Some(path) = nu_path::data_dir() {
172 let mut canon_data_path = canonicalize_path(engine_state, path.as_ref());
173 canon_data_path.push("nushell");
174 Value::string(canon_data_path.to_string_lossy(), span)
175 } else {
176 Value::error(
177 ShellError::Generic(GenericError::new(
178 "setting $nu.data-dir failed",
179 "Could not get data path",
180 span,
181 )),
182 span,
183 )
184 },
185 );
186
187 record.push(
188 "cache-dir",
189 if let Some(path) = nu_path::cache_dir() {
190 let mut canon_cache_path = canonicalize_path(engine_state, path.as_ref());
191 canon_cache_path.push("nushell");
192 Value::string(canon_cache_path.to_string_lossy(), span)
193 } else {
194 Value::error(
195 ShellError::Generic(GenericError::new(
196 "setting $nu.cache-dir failed",
197 "Could not get cache path",
198 span,
199 )),
200 span,
201 )
202 },
203 );
204
205 record.push(
206 "vendor-autoload-dirs",
207 Value::list(
208 get_vendor_autoload_dirs(engine_state)
209 .iter()
210 .map(|path| Value::string(path.to_string_lossy(), span))
211 .collect(),
212 span,
213 ),
214 );
215
216 record.push(
217 "user-autoload-dirs",
218 Value::list(
219 get_user_autoload_dirs(engine_state)
220 .iter()
221 .map(|path| Value::string(path.to_string_lossy(), span))
222 .collect(),
223 span,
224 ),
225 );
226
227 record.push("temp-dir", {
228 let canon_temp_path = canonicalize_path(engine_state, &std::env::temp_dir());
229 Value::string(canon_temp_path.to_string_lossy(), span)
230 });
231
232 record.push("pid", Value::int(std::process::id().into(), span));
233
234 record.push("os-info", {
235 let ver = get_kernel_version();
236 Value::record(
237 record! {
238 "name" => Value::string(get_os_name(), span),
239 "arch" => Value::string(get_os_arch(), span),
240 "family" => Value::string(get_os_family(), span),
241 "kernel_version" => Value::string(ver, span),
242 },
243 span,
244 )
245 });
246
247 record.push(
248 "startup-time",
249 Value::duration(engine_state.get_startup_time(), span),
250 );
251
252 record.push(
253 "is-interactive",
254 Value::bool(engine_state.is_interactive, span),
255 );
256
257 record.push("is-login", Value::bool(engine_state.is_login, span));
258
259 record.push(
260 "history-enabled",
261 Value::bool(engine_state.history_enabled, span),
262 );
263
264 record.push(
265 "current-exe",
266 if let Ok(current_exe) = std::env::current_exe() {
267 Value::string(current_exe.to_string_lossy(), span)
268 } else {
269 Value::error(
270 ShellError::Generic(GenericError::new(
271 "setting $nu.current-exe failed",
272 "Could not get current executable path",
273 span,
274 )),
275 span,
276 )
277 },
278 );
279
280 record.push("is-lsp", Value::bool(engine_state.is_lsp, span));
281 record.push("is-mcp", Value::bool(engine_state.is_mcp, span));
282
283 Value::record(record, span)
284}
285
286pub fn get_vendor_autoload_dirs(_engine_state: &EngineState) -> Vec<PathBuf> {
287 let into_autoload_path_fn = |mut path: PathBuf| {
296 path.push("nushell");
297 path.push("vendor");
298 path.push("autoload");
299 path
300 };
301
302 let mut dirs = Vec::new();
303
304 let mut append_fn = |path: PathBuf| {
305 if !dirs.contains(&path) {
306 dirs.push(path)
307 }
308 };
309
310 #[cfg(target_os = "macos")]
311 std::iter::once("/Library/Application Support")
312 .map(PathBuf::from)
313 .map(into_autoload_path_fn)
314 .for_each(&mut append_fn);
315 #[cfg(unix)]
316 {
317 use std::os::unix::ffi::OsStrExt;
318
319 std::env::var_os("XDG_DATA_DIRS")
320 .or_else(|| {
321 option_env!("PREFIX").map(|prefix| {
322 if prefix.ends_with("local") {
323 std::ffi::OsString::from(format!("{prefix}/share"))
324 } else {
325 std::ffi::OsString::from(format!("{prefix}/local/share:{prefix}/share"))
326 }
327 })
328 })
329 .unwrap_or_else(|| std::ffi::OsString::from("/usr/local/share/:/usr/share/"))
330 .as_encoded_bytes()
331 .split(|b| *b == b':')
332 .map(|split| into_autoload_path_fn(PathBuf::from(std::ffi::OsStr::from_bytes(split))))
333 .rev()
334 .for_each(&mut append_fn);
335 }
336
337 #[cfg(target_os = "windows")]
338 dirs_sys::known_folder(windows_sys::Win32::UI::Shell::FOLDERID_ProgramData)
339 .into_iter()
340 .map(into_autoload_path_fn)
341 .for_each(&mut append_fn);
342
343 if let Some(path) = option_env!("NU_VENDOR_AUTOLOAD_DIR") {
344 append_fn(PathBuf::from(path));
345 }
346
347 if let Some(data_dir) = nu_path::data_dir() {
348 append_fn(into_autoload_path_fn(PathBuf::from(data_dir)));
349 }
350
351 if let Some(path) = std::env::var_os("NU_VENDOR_AUTOLOAD_DIR") {
352 append_fn(PathBuf::from(path));
353 }
354
355 dirs
356}
357
358pub fn get_user_autoload_dirs(_engine_state: &EngineState) -> Vec<PathBuf> {
359 let mut dirs = Vec::new();
362
363 let mut append_fn = |path: PathBuf| {
364 if !dirs.contains(&path) {
365 dirs.push(path)
366 }
367 };
368
369 if let Some(config_dir) = nu_path::nu_config_dir() {
370 append_fn(config_dir.join("autoload").into());
371 }
372
373 dirs
374}
375
376fn eval_const_call(
377 working_set: &StateWorkingSet,
378 call: &Call,
379 input: PipelineData,
380) -> Result<PipelineData, ShellError> {
381 let decl = working_set.get_decl(call.decl_id);
382
383 if !decl.is_const() {
384 return Err(ShellError::NotAConstCommand { span: call.head });
385 }
386
387 if !decl.is_known_external() && call.named_iter().any(|(flag, _, _)| flag.item == "help") {
388 return Err(ShellError::NotAConstHelp { span: call.head });
391 }
392
393 decl.run_const(working_set, &call.into(), input)
394}
395
396pub fn eval_const_subexpression(
397 working_set: &StateWorkingSet,
398 block: &Block,
399 mut input: PipelineData,
400 span: Span,
401) -> Result<PipelineData, ShellError> {
402 for pipeline in block.pipelines.iter() {
403 for element in pipeline.elements.iter() {
404 if element.redirection.is_some() {
405 return Err(ShellError::NotAConstant { span });
406 }
407
408 input = eval_constant_with_input(working_set, &element.expr, input)?
409 }
410 }
411
412 Ok(input)
413}
414
415pub fn eval_constant_with_input(
416 working_set: &StateWorkingSet,
417 expr: &Expression,
418 input: PipelineData,
419) -> Result<PipelineData, ShellError> {
420 match &expr.expr {
421 Expr::Call(call) => eval_const_call(working_set, call, input),
422 Expr::Subexpression(block_id) => {
423 let block = working_set.get_block(*block_id);
424 eval_const_subexpression(working_set, block, input, expr.span(&working_set))
425 }
426 _ => eval_constant(working_set, expr).map(|v| PipelineData::value(v, None)),
427 }
428}
429
430pub fn eval_constant(
432 working_set: &StateWorkingSet,
433 expr: &Expression,
434) -> Result<Value, ShellError> {
435 <EvalConst as Eval>::eval::<WithoutDebug>(working_set, &mut (), expr)
437}
438
439struct EvalConst;
440
441impl Eval for EvalConst {
442 type State<'a> = &'a StateWorkingSet<'a>;
443
444 type MutState = ();
445
446 fn get_config(state: Self::State<'_>, _: &mut ()) -> Arc<Config> {
447 state.get_config().clone()
448 }
449
450 fn eval_var(
451 working_set: &StateWorkingSet,
452 _: &mut (),
453 var_id: VarId,
454 span: Span,
455 ) -> Result<Value, ShellError> {
456 match working_set.get_variable(var_id).const_val.as_ref() {
457 Some(val) => Ok(val.clone()),
458 None => Err(ShellError::NotAConstant { span }),
459 }
460 }
461
462 fn eval_call<D: DebugContext>(
463 working_set: &StateWorkingSet,
464 _: &mut (),
465 call: &Call,
466 span: Span,
467 ) -> Result<Value, ShellError> {
468 eval_const_call(working_set, call, PipelineData::empty())?.into_value(span)
471 }
472
473 fn eval_external_call(
474 _: &StateWorkingSet,
475 _: &mut (),
476 _: &Expression,
477 _: &[ExternalArgument],
478 span: Span,
479 ) -> Result<Value, ShellError> {
480 Err(ShellError::NotAConstant { span })
482 }
483
484 fn eval_collect<D: DebugContext>(
485 _: &StateWorkingSet,
486 _: &mut (),
487 _var_id: VarId,
488 expr: &Expression,
489 ) -> Result<Value, ShellError> {
490 Err(ShellError::NotAConstant { span: expr.span })
491 }
492
493 fn eval_subexpression<D: DebugContext>(
494 working_set: &StateWorkingSet,
495 _: &mut (),
496 block_id: BlockId,
497 span: Span,
498 ) -> Result<Value, ShellError> {
499 if working_set
501 .parse_errors
502 .iter()
503 .any(|error| span.contains_span(error.span()))
504 {
505 return Err(ShellError::ParseErrorInConstant { span });
506 }
507 let block = working_set.get_block(block_id);
509 eval_const_subexpression(working_set, block, PipelineData::empty(), span)?.into_value(span)
510 }
511
512 fn regex_match(
513 _: &StateWorkingSet,
514 _op_span: Span,
515 _: &Value,
516 _: &Value,
517 _: bool,
518 expr_span: Span,
519 ) -> Result<Value, ShellError> {
520 Err(ShellError::NotAConstant { span: expr_span })
521 }
522
523 fn eval_assignment<D: DebugContext>(
524 _: &StateWorkingSet,
525 _: &mut (),
526 _: &Expression,
527 _: &Expression,
528 _: Assignment,
529 _op_span: Span,
530 expr_span: Span,
531 ) -> Result<Value, ShellError> {
532 Err(ShellError::NotAConstant { span: expr_span })
534 }
535
536 fn eval_row_condition_or_closure(
537 _: &StateWorkingSet,
538 _: &mut (),
539 _: BlockId,
540 span: Span,
541 ) -> Result<Value, ShellError> {
542 Err(ShellError::NotAConstant { span })
543 }
544
545 fn eval_overlay(_: &StateWorkingSet, span: Span) -> Result<Value, ShellError> {
546 Err(ShellError::NotAConstant { span })
547 }
548
549 fn unreachable(working_set: &StateWorkingSet, expr: &Expression) -> Result<Value, ShellError> {
550 Err(ShellError::NotAConstant {
551 span: expr.span(&working_set),
552 })
553 }
554}