1#![allow(clippy::byte_char_slices)]
2
3use nu_cmd_base::hook::eval_hook;
4use nu_engine::{eval_block, eval_block_with_early_return};
5use nu_parser::{Token, TokenContents, lex, parse, unescape_unquote_string};
6use nu_protocol::{
7 PipelineData, ShellError, Span, Value,
8 debugger::WithoutDebug,
9 engine::{EngineState, Stack, StateWorkingSet},
10 process::check_exit_status_future,
11 report_error::report_compile_error,
12 report_parse_error, report_parse_warning, report_shell_error,
13 shell_error::generic::GenericError,
14};
15#[cfg(windows)]
16use nu_utils::enable_vt_processing;
17use nu_utils::time::Instant;
18use nu_utils::{escape_quote_string, perf};
19use std::path::Path;
20
21pub fn gather_parent_env_vars(engine_state: &mut EngineState, init_cwd: &Path) {
40 gather_env_vars(std::env::vars(), engine_state, init_cwd);
41}
42
43fn gather_env_vars(
44 vars: impl Iterator<Item = (String, String)>,
45 engine_state: &mut EngineState,
46 init_cwd: &Path,
47) {
48 fn report_capture_error(engine_state: &EngineState, env_str: &str, msg: &str) {
49 report_shell_error(
50 None,
51 engine_state,
52 &ShellError::Generic(
53 GenericError::new_internal(
54 format!("Environment variable was not captured: {env_str}"),
55 "",
56 )
57 .with_help(msg.to_string()),
58 ),
59 );
60 }
61
62 fn put_env_to_fake_file(name: &str, val: &str, fake_env_file: &mut String) {
63 fake_env_file.push_str(&escape_quote_string(name));
64 fake_env_file.push('=');
65 fake_env_file.push_str(&escape_quote_string(val));
66 fake_env_file.push('\n');
67 }
68
69 let mut fake_env_file = String::new();
70 for (name, val) in vars {
72 put_env_to_fake_file(&name, &val, &mut fake_env_file);
73 }
74
75 match init_cwd.to_str() {
76 Some(cwd) => {
77 put_env_to_fake_file("PWD", cwd, &mut fake_env_file);
78 }
79 None => {
80 report_shell_error(
82 None,
83 engine_state,
84 &ShellError::Generic(
85 GenericError::new_internal("Current directory is not a valid utf-8 path", "")
86 .with_help(format!(
87 "Retrieving current directory failed: {init_cwd:?} not a valid utf-8 path"
88 )),
89 ),
90 );
91 }
92 }
93
94 let span_offset = engine_state.next_span_start();
97
98 engine_state.add_file(
99 "Host Environment Variables".into(),
100 fake_env_file.as_bytes().into(),
101 );
102
103 let (tokens, _) = lex(fake_env_file.as_bytes(), span_offset, &[], &[], true);
104
105 for token in tokens {
106 if let Token {
107 contents: TokenContents::Item,
108 span: full_span,
109 } = token
110 {
111 let contents = engine_state.get_span_contents(full_span);
112 let (parts, _) = lex(contents, full_span.start, &[], &[b'='], true);
113
114 let name = if let Some(Token {
115 contents: TokenContents::Item,
116 span,
117 }) = parts.first()
118 {
119 let mut working_set = StateWorkingSet::new(engine_state);
120 let bytes = working_set.get_span_contents(*span);
121
122 if bytes.len() < 2 {
123 report_capture_error(
124 engine_state,
125 &String::from_utf8_lossy(contents),
126 "Got empty name.",
127 );
128
129 continue;
130 }
131
132 let (bytes, err) = unescape_unquote_string(bytes, *span);
133 if let Some(err) = err {
134 working_set.error(err);
135 }
136
137 if !working_set.parse_errors.is_empty() {
138 report_capture_error(
139 engine_state,
140 &String::from_utf8_lossy(contents),
141 "Got unparsable name.",
142 );
143
144 continue;
145 }
146
147 bytes
148 } else {
149 report_capture_error(
150 engine_state,
151 &String::from_utf8_lossy(contents),
152 "Got empty name.",
153 );
154
155 continue;
156 };
157
158 let value = if let Some(Token {
159 contents: TokenContents::Item,
160 span,
161 }) = parts.get(2)
162 {
163 let mut working_set = StateWorkingSet::new(engine_state);
164 let bytes = working_set.get_span_contents(*span);
165
166 if bytes.len() < 2 {
167 report_capture_error(
168 engine_state,
169 &String::from_utf8_lossy(contents),
170 "Got empty value.",
171 );
172
173 continue;
174 }
175
176 let (bytes, err) = unescape_unquote_string(bytes, *span);
177 if let Some(err) = err {
178 working_set.error(err);
179 }
180
181 if !working_set.parse_errors.is_empty() {
182 report_capture_error(
183 engine_state,
184 &String::from_utf8_lossy(contents),
185 "Got unparsable value.",
186 );
187
188 continue;
189 }
190
191 Value::string(bytes, *span)
192 } else {
193 report_capture_error(
194 engine_state,
195 &String::from_utf8_lossy(contents),
196 "Got empty value.",
197 );
198
199 continue;
200 };
201
202 engine_state.add_env_var(name, value);
204 }
205 }
206}
207
208pub fn print_pipeline(
216 engine_state: &mut EngineState,
217 stack: &mut Stack,
218 pipeline: PipelineData,
219 no_newline: bool,
220) -> Result<(), ShellError> {
221 let to_stderr = engine_state.is_mcp || engine_state.is_lsp;
222
223 if let Some(hook) = stack.get_config(engine_state).hooks.display_output.clone() {
224 let pipeline = eval_hook(
225 engine_state,
226 stack,
227 Some(pipeline),
228 vec![],
229 &hook,
230 "display_output",
231 )?;
232 pipeline.print_raw(engine_state, no_newline, to_stderr)
233 } else {
234 pipeline.print_table(engine_state, stack, no_newline, to_stderr)
236 }
237}
238
239pub fn eval_source(
240 engine_state: &mut EngineState,
241 stack: &mut Stack,
242 source: &[u8],
243 fname: &str,
244 input: PipelineData,
245 allow_return: bool,
246) -> i32 {
247 let start_time = Instant::now();
248
249 let exit_code = match evaluate_source(engine_state, stack, source, fname, input, allow_return) {
250 Ok(failed) => {
251 let code = failed.into();
252 stack.set_last_exit_code(code, Span::unknown());
254 code
255 }
256 Err(err) => {
257 if let ShellError::Exit { code, .. } = &err {
258 std::process::exit(*code)
259 }
260 report_shell_error(Some(stack), engine_state, &err);
261 let code = err.exit_code();
262 stack.set_last_error(&err);
263 code.unwrap_or(0)
264 }
265 };
266
267 #[cfg(windows)]
269 {
270 let _ = enable_vt_processing();
271 }
272
273 perf!(
274 &format!("eval_source {}", &fname),
275 start_time,
276 engine_state
277 .get_config()
278 .use_ansi_coloring
279 .get(engine_state)
280 );
281
282 exit_code
283}
284
285pub(crate) fn evaluate_source(
286 engine_state: &mut EngineState,
287 stack: &mut Stack,
288 source: &[u8],
289 fname: &str,
290 input: PipelineData,
291 allow_return: bool,
292) -> Result<bool, ShellError> {
293 let (block, delta) = {
294 let mut working_set = StateWorkingSet::new(engine_state);
295 let output = parse(
296 &mut working_set,
297 Some(fname), source,
299 false,
300 );
301 if let Some(warning) = working_set.parse_warnings.first() {
302 report_parse_warning(Some(stack), &working_set, warning);
303 }
304
305 if let Some(err) = working_set.parse_errors.first() {
306 report_parse_error(Some(stack), &working_set, err);
307 return Ok(true);
308 }
309
310 if let Some(err) = working_set.compile_errors.first() {
311 report_compile_error(Some(stack), &working_set, err);
312 return Ok(true);
313 }
314
315 (output, working_set.render())
316 };
317
318 engine_state.merge_delta(delta)?;
319
320 let pipeline = if allow_return {
321 eval_block_with_early_return::<WithoutDebug>(engine_state, stack, &block, input)
322 } else {
323 eval_block::<WithoutDebug>(engine_state, stack, &block, input)
324 }?;
325 let pipeline_data = pipeline.body;
326
327 for var_id in &stack.deletions {
329 if let Some(active_id) = engine_state.scope.active_overlays.last()
330 && let Some((_, overlay)) = engine_state.scope.overlays.get_mut((*active_id).get())
331 {
332 overlay.vars.retain(|_, v| *v != *var_id);
333 }
334 }
335 stack.deletions.clear();
336
337 let no_newline = matches!(&pipeline_data, &PipelineData::ByteStream(..));
338 print_pipeline(engine_state, stack, pipeline_data, no_newline)?;
339
340 let pipefail = nu_experimental::PIPE_FAIL.get();
341 if !pipefail {
342 return Ok(false);
343 }
344 check_exit_status_future(pipeline.exit).map(|_| false)
346}
347
348#[cfg(test)]
349mod test {
350 use super::*;
351
352 #[test]
353 fn test_gather_env_vars() {
354 let mut engine_state = EngineState::new();
355 let symbols = r##" !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"##;
356
357 gather_env_vars(
358 [
359 ("FOO".into(), "foo".into()),
360 ("SYMBOLS".into(), symbols.into()),
361 (symbols.into(), "symbols".into()),
362 ]
363 .into_iter(),
364 &mut engine_state,
365 Path::new("t"),
366 );
367
368 let env = engine_state.render_env_vars();
369
370 assert!(matches!(env.get("FOO"), Some(&Value::String { val, .. }) if val == "foo"));
371 assert!(matches!(env.get("SYMBOLS"), Some(&Value::String { val, .. }) if val == symbols));
372 assert!(matches!(env.get(symbols), Some(&Value::String { val, .. }) if val == "symbols"));
373 assert!(env.contains_key("PWD"));
374 assert_eq!(env.len(), 4);
375 }
376}