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