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 #[cfg(feature = "sqlite")]
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 record.push("is-lsp", Value::bool(engine_state.is_lsp, span));
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}