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