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