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