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 report_shell_error(Some(stack), engine_state, &err);
258 let code = err.exit_code();
259 stack.set_last_error(&err);
260 code.unwrap_or(0)
261 }
262 };
263
264 #[cfg(windows)]
266 {
267 let _ = enable_vt_processing();
268 }
269
270 perf!(
271 &format!("eval_source {}", &fname),
272 start_time,
273 engine_state
274 .get_config()
275 .use_ansi_coloring
276 .get(engine_state)
277 );
278
279 exit_code
280}
281
282fn evaluate_source(
283 engine_state: &mut EngineState,
284 stack: &mut Stack,
285 source: &[u8],
286 fname: &str,
287 input: PipelineData,
288 allow_return: bool,
289) -> Result<bool, ShellError> {
290 let (block, delta) = {
291 let mut working_set = StateWorkingSet::new(engine_state);
292 let output = parse(
293 &mut working_set,
294 Some(fname), source,
296 false,
297 );
298 if let Some(warning) = working_set.parse_warnings.first() {
299 report_parse_warning(Some(stack), &working_set, warning);
300 }
301
302 if let Some(err) = working_set.parse_errors.first() {
303 report_parse_error(Some(stack), &working_set, err);
304 return Ok(true);
305 }
306
307 if let Some(err) = working_set.compile_errors.first() {
308 report_compile_error(Some(stack), &working_set, err);
309 return Ok(true);
310 }
311
312 (output, working_set.render())
313 };
314
315 engine_state.merge_delta(delta)?;
316
317 let pipeline = if allow_return {
318 eval_block_with_early_return::<WithoutDebug>(engine_state, stack, &block, input)
319 } else {
320 eval_block::<WithoutDebug>(engine_state, stack, &block, input)
321 }?;
322 let pipeline_data = pipeline.body;
323
324 for var_id in &stack.deletions {
326 if let Some(active_id) = engine_state.scope.active_overlays.last()
327 && let Some((_, overlay)) = engine_state.scope.overlays.get_mut((*active_id).get())
328 {
329 overlay.vars.retain(|_, v| *v != *var_id);
330 }
331 }
332 stack.deletions.clear();
333
334 let no_newline = matches!(&pipeline_data, &PipelineData::ByteStream(..));
335 print_pipeline(engine_state, stack, pipeline_data, no_newline)?;
336
337 let pipefail = nu_experimental::PIPE_FAIL.get();
338 if !pipefail {
339 return Ok(false);
340 }
341 check_exit_status_future(pipeline.exit).map(|_| false)
343}
344
345#[cfg(test)]
346mod test {
347 use super::*;
348
349 #[test]
350 fn test_gather_env_vars() {
351 let mut engine_state = EngineState::new();
352 let symbols = r##" !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~"##;
353
354 gather_env_vars(
355 [
356 ("FOO".into(), "foo".into()),
357 ("SYMBOLS".into(), symbols.into()),
358 (symbols.into(), "symbols".into()),
359 ]
360 .into_iter(),
361 &mut engine_state,
362 Path::new("t"),
363 );
364
365 let env = engine_state.render_env_vars();
366
367 assert!(matches!(env.get("FOO"), Some(&Value::String { val, .. }) if val == "foo"));
368 assert!(matches!(env.get("SYMBOLS"), Some(&Value::String { val, .. }) if val == symbols));
369 assert!(matches!(env.get(symbols), Some(&Value::String { val, .. }) if val == "symbols"));
370 assert!(env.contains_key("PWD"));
371 assert_eq!(env.len(), 4);
372 }
373}