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_var(
445 working_set: &StateWorkingSet,
446 _: &mut (),
447 var_id: VarId,
448 span: Span,
449 ) -> Result<Value, ShellError> {
450 match working_set.get_variable(var_id).const_val.as_ref() {
451 Some(val) => Ok(val.clone()),
452 None => Err(ShellError::NotAConstant { span }),
453 }
454 }
455
456 fn eval_call<D: DebugContext>(
457 working_set: &StateWorkingSet,
458 _: &mut (),
459 call: &Call,
460 span: Span,
461 ) -> Result<Value, ShellError> {
462 eval_const_call(working_set, call, PipelineData::empty())?.into_value(span)
465 }
466
467 fn eval_external_call(
468 _: &StateWorkingSet,
469 _: &mut (),
470 _: &Expression,
471 _: &[ExternalArgument],
472 span: Span,
473 ) -> Result<Value, ShellError> {
474 Err(ShellError::NotAConstant { span })
476 }
477
478 fn eval_collect<D: DebugContext>(
479 _: &StateWorkingSet,
480 _: &mut (),
481 _var_id: VarId,
482 expr: &Expression,
483 ) -> Result<Value, ShellError> {
484 Err(ShellError::NotAConstant { span: expr.span })
485 }
486
487 fn eval_subexpression<D: DebugContext>(
488 working_set: &StateWorkingSet,
489 _: &mut (),
490 block_id: BlockId,
491 span: Span,
492 ) -> Result<Value, ShellError> {
493 if working_set
495 .parse_errors
496 .iter()
497 .any(|error| span.contains_span(error.span()))
498 {
499 return Err(ShellError::ParseErrorInConstant { span });
500 }
501 let block = working_set.get_block(block_id);
503 eval_const_subexpression(working_set, block, PipelineData::empty(), span)?.into_value(span)
504 }
505
506 fn regex_match(
507 _: &StateWorkingSet,
508 _op_span: Span,
509 _: &Value,
510 _: &Value,
511 _: bool,
512 expr_span: Span,
513 ) -> Result<Value, ShellError> {
514 Err(ShellError::NotAConstant { span: expr_span })
515 }
516
517 fn eval_assignment<D: DebugContext>(
518 _: &StateWorkingSet,
519 _: &mut (),
520 _: &Expression,
521 _: &Expression,
522 _: Assignment,
523 _op_span: Span,
524 expr_span: Span,
525 ) -> Result<Value, ShellError> {
526 Err(ShellError::NotAConstant { span: expr_span })
528 }
529
530 fn eval_row_condition_or_closure(
531 _: &StateWorkingSet,
532 _: &mut (),
533 _: BlockId,
534 span: Span,
535 ) -> Result<Value, ShellError> {
536 Err(ShellError::NotAConstant { span })
537 }
538
539 fn eval_overlay(_: &StateWorkingSet, span: Span) -> Result<Value, ShellError> {
540 Err(ShellError::NotAConstant { span })
541 }
542
543 fn unreachable(working_set: &StateWorkingSet, expr: &Expression) -> Result<Value, ShellError> {
544 Err(ShellError::NotAConstant {
545 span: expr.span(&working_set),
546 })
547 }
548}