1use crate::{
6 BlockId, Config, HistoryPath, 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 shell_error::generic::GenericError,
13};
14use nu_system::os_info::{get_kernel_version, get_os_arch, get_os_family, get_os_name};
15use std::{
16 path::{Path, PathBuf},
17 sync::Arc,
18};
19
20pub(crate) fn create_nu_constant(engine_state: &EngineState, span: Span) -> Value {
24 fn canonicalize_path(engine_state: &EngineState, path: &Path) -> PathBuf {
25 #[allow(deprecated)]
26 let cwd = engine_state.current_work_dir();
27
28 if path.exists() {
29 match nu_path::canonicalize_with(path, cwd) {
30 Ok(canon_path) => canon_path,
31 Err(_) => path.to_owned(),
32 }
33 } else {
34 path.to_owned()
35 }
36 }
37
38 let mut record = Record::new();
39
40 let config_home = &engine_state.config_dirs.config_home;
41 let canon_config_home = canonicalize_path(engine_state, config_home);
42
43 record.push(
44 "default-config-dir",
45 Value::string(canon_config_home.to_string_lossy(), span),
46 );
47
48 record.push(
49 "config-path",
50 Value::string(
51 canonicalize_path(engine_state, engine_state.config_dirs.config_file.as_path())
52 .to_string_lossy(),
53 span,
54 ),
55 );
56
57 record.push(
58 "env-path",
59 Value::string(
60 canonicalize_path(engine_state, engine_state.config_dirs.env_file.as_path())
61 .to_string_lossy(),
62 span,
63 ),
64 );
65
66 record.push(
67 "history-path",
68 match &engine_state.config.history.path {
69 HistoryPath::Disabled => Value::string("", span),
70 HistoryPath::Custom(custom_path) => {
71 let effective_path = if custom_path.is_dir() {
72 custom_path.join(engine_state.config.history.file_format.default_file_name())
73 } else {
74 custom_path.clone()
75 };
76 let canon_hist_path = canonicalize_path(engine_state, &effective_path);
77 Value::string(canon_hist_path.to_string_lossy(), span)
78 }
79 HistoryPath::Default => {
80 let hist_path = engine_state
83 .config
84 .history
85 .file_path(config_home)
86 .unwrap_or_else(|| {
87 config_home
88 .join(engine_state.config.history.file_format.default_file_name())
89 });
90 let canon_hist_path = canonicalize_path(engine_state, &hist_path);
91 Value::string(canon_hist_path.to_string_lossy(), span)
92 }
93 },
94 );
95
96 record.push(
97 "loginshell-path",
98 Value::string(
99 canonicalize_path(engine_state, &config_home.join("login.nu")).to_string_lossy(),
100 span,
101 ),
102 );
103
104 #[cfg(feature = "plugin")]
105 {
106 let plugin_path = engine_state
108 .plugin_path
109 .as_deref()
110 .unwrap_or_else(|| engine_state.config_dirs.plugin_file.as_path());
111 record.push(
112 "plugin-path",
113 Value::string(
114 canonicalize_path(engine_state, plugin_path).to_string_lossy(),
115 span,
116 ),
117 );
118 }
119
120 record.push(
121 "home-dir",
122 Value::string(
123 canonicalize_path(engine_state, &engine_state.config_dirs.home_dir).to_string_lossy(),
124 span,
125 ),
126 );
127
128 record.push(
129 "data-dir",
130 Value::string(
131 canonicalize_path(engine_state, &engine_state.config_dirs.data_home).to_string_lossy(),
132 span,
133 ),
134 );
135
136 record.push(
137 "cache-dir",
138 Value::string(
139 canonicalize_path(engine_state, &engine_state.config_dirs.cache_home).to_string_lossy(),
140 span,
141 ),
142 );
143
144 record.push(
145 "vendor-autoload-dirs",
146 Value::list(
147 engine_state
148 .config_dirs
149 .vendor_autoload_dirs
150 .iter()
151 .map(|path| Value::string(path.to_string_lossy(), span))
152 .collect(),
153 span,
154 ),
155 );
156
157 record.push(
158 "user-autoload-dirs",
159 Value::list(
160 engine_state
161 .config_dirs
162 .user_autoload_dirs
163 .iter()
164 .map(|path| Value::string(path.to_string_lossy(), span))
165 .collect(),
166 span,
167 ),
168 );
169
170 record.push("temp-dir", {
171 let canon_temp_path = canonicalize_path(engine_state, &std::env::temp_dir());
172 Value::string(canon_temp_path.to_string_lossy(), span)
173 });
174
175 record.push("pid", Value::int(std::process::id().into(), span));
176
177 record.push("os-info", {
178 let ver = get_kernel_version();
179 Value::record(
180 record! {
181 "name" => Value::string(get_os_name(), span),
182 "arch" => Value::string(get_os_arch(), span),
183 "family" => Value::string(get_os_family(), span),
184 "kernel_version" => Value::string(ver, span),
185 },
186 span,
187 )
188 });
189
190 record.push(
191 "startup-time",
192 Value::duration(engine_state.get_startup_time(), span),
193 );
194
195 record.push(
196 "is-interactive",
197 Value::bool(engine_state.is_interactive, span),
198 );
199
200 record.push("is-login", Value::bool(engine_state.is_login, span));
201
202 record.push(
203 "history-enabled",
204 Value::bool(engine_state.history_enabled, span),
205 );
206
207 record.push(
208 "current-exe",
209 if let Ok(current_exe) = std::env::current_exe() {
210 Value::string(current_exe.to_string_lossy(), span)
211 } else {
212 Value::error(
213 ShellError::Generic(GenericError::new(
214 "setting $nu.current-exe failed",
215 "Could not get current executable path",
216 span,
217 )),
218 span,
219 )
220 },
221 );
222
223 record.push("is-lsp", Value::bool(engine_state.is_lsp, span));
224 record.push("is-mcp", Value::bool(engine_state.is_mcp, span));
225
226 Value::record(record, span)
227}
228
229fn eval_const_call(
230 working_set: &StateWorkingSet,
231 call: &Call,
232 input: PipelineData,
233) -> Result<PipelineData, ShellError> {
234 let decl = working_set.get_decl(call.decl_id);
235
236 if !decl.is_const() {
237 return Err(ShellError::NotAConstCommand { span: call.head });
238 }
239
240 if !decl.is_known_external() && call.named_iter().any(|(flag, _, _)| flag.item == "help") {
241 return Err(ShellError::NotAConstHelp { span: call.head });
244 }
245
246 decl.run_const(working_set, &call.into(), input)
247}
248
249pub fn eval_const_subexpression(
250 working_set: &StateWorkingSet,
251 block: &Block,
252 mut input: PipelineData,
253 span: Span,
254) -> Result<PipelineData, ShellError> {
255 for pipeline in block.pipelines.iter() {
256 for element in pipeline.elements.iter() {
257 if element.redirection.is_some() {
258 return Err(ShellError::NotAConstant { span });
259 }
260
261 input = eval_constant_with_input(working_set, &element.expr, input)?
262 }
263 }
264
265 Ok(input)
266}
267
268pub fn eval_constant_with_input(
269 working_set: &StateWorkingSet,
270 expr: &Expression,
271 input: PipelineData,
272) -> Result<PipelineData, ShellError> {
273 match &expr.expr {
274 Expr::Call(call) => eval_const_call(working_set, call, input),
275 Expr::Subexpression(block_id) => {
276 let block = working_set.get_block(*block_id);
277 eval_const_subexpression(working_set, block, input, expr.span(&working_set))
278 }
279 _ => eval_constant(working_set, expr).map(|v| PipelineData::value(v, None)),
280 }
281}
282
283pub fn eval_constant(
285 working_set: &StateWorkingSet,
286 expr: &Expression,
287) -> Result<Value, ShellError> {
288 <EvalConst as Eval>::eval::<WithoutDebug>(working_set, &mut (), expr)
290}
291
292struct EvalConst;
293
294impl Eval for EvalConst {
295 type State<'a> = &'a StateWorkingSet<'a>;
296
297 type MutState = ();
298
299 fn get_config(state: Self::State<'_>, _: &mut ()) -> Arc<Config> {
300 state.get_config().clone()
301 }
302
303 fn eval_var(
304 working_set: &StateWorkingSet,
305 _: &mut (),
306 var_id: VarId,
307 span: Span,
308 ) -> Result<Value, ShellError> {
309 match working_set.get_variable(var_id).const_val.as_ref() {
310 Some(val) => Ok(val.clone()),
311 None => Err(ShellError::NotAConstant { span }),
312 }
313 }
314
315 fn eval_call<D: DebugContext>(
316 working_set: &StateWorkingSet,
317 _: &mut (),
318 call: &Call,
319 span: Span,
320 ) -> Result<Value, ShellError> {
321 eval_const_call(working_set, call, PipelineData::empty())?.into_value(span)
324 }
325
326 fn eval_external_call(
327 _: &StateWorkingSet,
328 _: &mut (),
329 _: &Expression,
330 _: &[ExternalArgument],
331 span: Span,
332 ) -> Result<Value, ShellError> {
333 Err(ShellError::NotAConstant { span })
335 }
336
337 fn eval_collect<D: DebugContext>(
338 _: &StateWorkingSet,
339 _: &mut (),
340 _var_id: VarId,
341 expr: &Expression,
342 ) -> Result<Value, ShellError> {
343 Err(ShellError::NotAConstant { span: expr.span })
344 }
345
346 fn eval_subexpression<D: DebugContext>(
347 working_set: &StateWorkingSet,
348 _: &mut (),
349 block_id: BlockId,
350 span: Span,
351 ) -> Result<Value, ShellError> {
352 if working_set
354 .parse_errors
355 .iter()
356 .any(|error| span.contains_span(error.span()))
357 {
358 return Err(ShellError::ParseErrorInConstant { span });
359 }
360 let block = working_set.get_block(block_id);
362 eval_const_subexpression(working_set, block, PipelineData::empty(), span)?.into_value(span)
363 }
364
365 fn regex_match(
366 _: &StateWorkingSet,
367 _op_span: Span,
368 _: &Value,
369 _: &Value,
370 _: bool,
371 expr_span: Span,
372 ) -> Result<Value, ShellError> {
373 Err(ShellError::NotAConstant { span: expr_span })
374 }
375
376 fn eval_assignment<D: DebugContext>(
377 _: &StateWorkingSet,
378 _: &mut (),
379 _: &Expression,
380 _: &Expression,
381 _: Assignment,
382 _op_span: Span,
383 expr_span: Span,
384 ) -> Result<Value, ShellError> {
385 Err(ShellError::NotAConstant { span: expr_span })
387 }
388
389 fn eval_row_condition_or_closure(
390 _: &StateWorkingSet,
391 _: &mut (),
392 _: BlockId,
393 span: Span,
394 ) -> Result<Value, ShellError> {
395 Err(ShellError::NotAConstant { span })
396 }
397
398 fn eval_overlay(_: &StateWorkingSet, span: Span) -> Result<Value, ShellError> {
399 Err(ShellError::NotAConstant { span })
400 }
401
402 fn unreachable(working_set: &StateWorkingSet, expr: &Expression) -> Result<Value, ShellError> {
403 Err(ShellError::NotAConstant {
404 span: expr.span(&working_set),
405 })
406 }
407}