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