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