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> {
312 let into_autoload_path_fn = |mut path: PathBuf| {
313 path.push("nushell");
314 path.push("vendor");
315 path.push("autoload");
316 path
317 };
318
319 let mut dirs = Vec::new();
320
321 let mut append_fn = |path: PathBuf| {
322 if !dirs.contains(&path) {
323 dirs.push(path)
324 }
325 };
326
327 #[cfg(target_os = "macos")]
328 std::iter::once("/Library/Application Support")
329 .map(PathBuf::from)
330 .map(into_autoload_path_fn)
331 .for_each(&mut append_fn);
332 #[cfg(unix)]
333 {
334 use std::os::unix::ffi::OsStrExt;
335
336 std::env::var_os("XDG_DATA_DIRS")
337 .or_else(|| {
338 option_env!("PREFIX").map(|prefix| {
339 if prefix.ends_with("local") {
340 std::ffi::OsString::from(format!("{prefix}/share"))
341 } else {
342 std::ffi::OsString::from(format!("{prefix}/local/share:{prefix}/share"))
343 }
344 })
345 })
346 .unwrap_or_else(|| std::ffi::OsString::from("/usr/local/share/:/usr/share/"))
347 .as_encoded_bytes()
348 .split(|b| *b == b':')
349 .map(|split| into_autoload_path_fn(PathBuf::from(std::ffi::OsStr::from_bytes(split))))
350 .rev()
351 .for_each(&mut append_fn);
352 }
353
354 #[cfg(target_os = "windows")]
355 dirs_sys::known_folder(windows_sys::Win32::UI::Shell::FOLDERID_ProgramData)
356 .into_iter()
357 .map(into_autoload_path_fn)
358 .for_each(&mut append_fn);
359
360 if let Some(path) = option_env!("NU_VENDOR_AUTOLOAD_DIR") {
361 append_fn(PathBuf::from(path));
362 }
363
364 if let Some(data_dir) = nu_path::data_dir() {
365 append_fn(into_autoload_path_fn(PathBuf::from(data_dir)));
366 }
367
368 if let Some(path) = std::env::var_os("NU_VENDOR_AUTOLOAD_DIR") {
369 append_fn(PathBuf::from(path));
370 }
371
372 dirs
373}
374
375pub fn get_user_autoload_dirs(_engine_state: &EngineState) -> Vec<PathBuf> {
376 let mut dirs = Vec::new();
379
380 let mut append_fn = |path: PathBuf| {
381 if !dirs.contains(&path) {
382 dirs.push(path)
383 }
384 };
385
386 if let Some(config_dir) = nu_path::nu_config_dir() {
387 append_fn(config_dir.join("autoload").into());
388 }
389
390 dirs
391}
392
393fn eval_const_call(
394 working_set: &StateWorkingSet,
395 call: &Call,
396 input: PipelineData,
397) -> Result<PipelineData, ShellError> {
398 let decl = working_set.get_decl(call.decl_id);
399
400 if !decl.is_const() {
401 return Err(ShellError::NotAConstCommand { span: call.head });
402 }
403
404 if !decl.is_known_external() && call.named_iter().any(|(flag, _, _)| flag.item == "help") {
405 return Err(ShellError::NotAConstHelp { span: call.head });
408 }
409
410 decl.run_const(working_set, &call.into(), input)
411}
412
413pub fn eval_const_subexpression(
414 working_set: &StateWorkingSet,
415 block: &Block,
416 mut input: PipelineData,
417 span: Span,
418) -> Result<PipelineData, ShellError> {
419 for pipeline in block.pipelines.iter() {
420 for element in pipeline.elements.iter() {
421 if element.redirection.is_some() {
422 return Err(ShellError::NotAConstant { span });
423 }
424
425 input = eval_constant_with_input(working_set, &element.expr, input)?
426 }
427 }
428
429 Ok(input)
430}
431
432pub fn eval_constant_with_input(
433 working_set: &StateWorkingSet,
434 expr: &Expression,
435 input: PipelineData,
436) -> Result<PipelineData, ShellError> {
437 match &expr.expr {
438 Expr::Call(call) => eval_const_call(working_set, call, input),
439 Expr::Subexpression(block_id) => {
440 let block = working_set.get_block(*block_id);
441 eval_const_subexpression(working_set, block, input, expr.span(&working_set))
442 }
443 _ => eval_constant(working_set, expr).map(|v| PipelineData::value(v, None)),
444 }
445}
446
447pub fn eval_constant(
449 working_set: &StateWorkingSet,
450 expr: &Expression,
451) -> Result<Value, ShellError> {
452 <EvalConst as Eval>::eval::<WithoutDebug>(working_set, &mut (), expr)
454}
455
456struct EvalConst;
457
458impl Eval for EvalConst {
459 type State<'a> = &'a StateWorkingSet<'a>;
460
461 type MutState = ();
462
463 fn get_config(state: Self::State<'_>, _: &mut ()) -> Arc<Config> {
464 state.get_config().clone()
465 }
466
467 fn eval_var(
468 working_set: &StateWorkingSet,
469 _: &mut (),
470 var_id: VarId,
471 span: Span,
472 ) -> Result<Value, ShellError> {
473 match working_set.get_variable(var_id).const_val.as_ref() {
474 Some(val) => Ok(val.clone()),
475 None => Err(ShellError::NotAConstant { span }),
476 }
477 }
478
479 fn eval_call<D: DebugContext>(
480 working_set: &StateWorkingSet,
481 _: &mut (),
482 call: &Call,
483 span: Span,
484 ) -> Result<Value, ShellError> {
485 eval_const_call(working_set, call, PipelineData::empty())?.into_value(span)
488 }
489
490 fn eval_external_call(
491 _: &StateWorkingSet,
492 _: &mut (),
493 _: &Expression,
494 _: &[ExternalArgument],
495 span: Span,
496 ) -> Result<Value, ShellError> {
497 Err(ShellError::NotAConstant { span })
499 }
500
501 fn eval_collect<D: DebugContext>(
502 _: &StateWorkingSet,
503 _: &mut (),
504 _var_id: VarId,
505 expr: &Expression,
506 ) -> Result<Value, ShellError> {
507 Err(ShellError::NotAConstant { span: expr.span })
508 }
509
510 fn eval_subexpression<D: DebugContext>(
511 working_set: &StateWorkingSet,
512 _: &mut (),
513 block_id: BlockId,
514 span: Span,
515 ) -> Result<Value, ShellError> {
516 if working_set
518 .parse_errors
519 .iter()
520 .any(|error| span.contains_span(error.span()))
521 {
522 return Err(ShellError::ParseErrorInConstant { span });
523 }
524 let block = working_set.get_block(block_id);
526 eval_const_subexpression(working_set, block, PipelineData::empty(), span)?.into_value(span)
527 }
528
529 fn regex_match(
530 _: &StateWorkingSet,
531 _op_span: Span,
532 _: &Value,
533 _: &Value,
534 _: bool,
535 expr_span: Span,
536 ) -> Result<Value, ShellError> {
537 Err(ShellError::NotAConstant { span: expr_span })
538 }
539
540 fn eval_assignment<D: DebugContext>(
541 _: &StateWorkingSet,
542 _: &mut (),
543 _: &Expression,
544 _: &Expression,
545 _: Assignment,
546 _op_span: Span,
547 expr_span: Span,
548 ) -> Result<Value, ShellError> {
549 Err(ShellError::NotAConstant { span: expr_span })
551 }
552
553 fn eval_row_condition_or_closure(
554 _: &StateWorkingSet,
555 _: &mut (),
556 _: BlockId,
557 span: Span,
558 ) -> Result<Value, ShellError> {
559 Err(ShellError::NotAConstant { span })
560 }
561
562 fn eval_overlay(_: &StateWorkingSet, span: Span) -> Result<Value, ShellError> {
563 Err(ShellError::NotAConstant { span })
564 }
565
566 fn unreachable(working_set: &StateWorkingSet, expr: &Expression) -> Result<Value, ShellError> {
567 Err(ShellError::NotAConstant {
568 span: expr.span(&working_set),
569 })
570 }
571}