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