1use crate::util::{eval_parsed_block_source, eval_source, print_pipeline};
2use log::{info, trace};
3use nu_engine::eval_block;
4use nu_parser::parse;
5use nu_path::absolute_with;
6use nu_protocol::{
7 PipelineData, ShellError, Span, Value,
8 debugger::WithoutDebug,
9 engine::{EngineState, Stack, StateWorkingSet},
10 report_error::report_compile_error,
11 report_parse_error, report_parse_warning,
12 shell_error::io::*,
13};
14use std::{path::PathBuf, sync::Arc};
15
16pub fn evaluate_file(
21 path: String,
22 args: &[String],
23 engine_state: &mut EngineState,
24 stack: &mut Stack,
25 input: PipelineData,
26) -> Result<(), ShellError> {
27 let cwd = engine_state.cwd_as_string(Some(stack))?;
28
29 let file_path = {
30 match absolute_with(&path, cwd) {
31 Ok(t) => Ok(t),
32 Err(err) => Err(IoError::new_internal_with_path(
33 err,
34 "Invalid path",
35 PathBuf::from(&path),
36 )),
37 }
38 }?;
39
40 let file_path_str = file_path
41 .to_str()
42 .ok_or_else(|| ShellError::NonUtf8Custom {
43 msg: format!(
44 "Input file name '{}' is not valid UTF8",
45 file_path.to_string_lossy()
46 ),
47 span: Span::unknown(),
49 })?;
50
51 let file = std::fs::read(&file_path).map_err(|err| {
52 let cmdline = format!("nu {path} {}", args.join(" "));
53 let mut working_set = StateWorkingSet::new(engine_state);
54 let file_id = working_set.add_file("<commandline>", cmdline.as_bytes());
55 let span = working_set
56 .get_span_for_file(file_id)
57 .subspan(3, path.len() + 3)
58 .expect("<commandline> to contain script path");
59 if let Err(err) = engine_state.merge_delta(working_set.render()) {
60 err
61 } else {
62 IoError::new(err.not_found_as(NotFound::File), span, PathBuf::from(&path)).into()
63 }
64 })?;
65 engine_state.file = Some(file_path.clone());
66
67 let parent = file_path.parent().ok_or_else(|| {
68 IoError::new_internal_with_path(
69 ErrorKind::DirectoryNotFound,
70 "The file path does not have a parent",
71 file_path.clone(),
72 )
73 })?;
74
75 stack.add_env_var(
77 "FILE_PWD".to_string(),
78 Value::string(parent.to_string_lossy(), Span::unknown()),
79 );
80 stack.add_env_var(
81 "CURRENT_FILE".to_string(),
82 Value::string(file_path.to_string_lossy(), Span::unknown()),
83 );
84 stack.add_env_var(
85 "PROCESS_PATH".to_string(),
86 Value::string(path, Span::unknown()),
87 );
88
89 let source_filename = file_path
90 .file_name()
91 .expect("internal error: missing filename");
92
93 let script_name = source_filename.to_string_lossy().to_string();
95 let script_name_bytes = script_name.as_bytes().to_vec();
96
97 let mut working_set = StateWorkingSet::new(engine_state);
98 trace!("parsing file: {file_path_str}");
99 let block = parse(&mut working_set, Some(file_path_str), &file, false);
100
101 if let Some(warning) = working_set.parse_warnings.first() {
102 report_parse_warning(None, &working_set, warning);
103 }
104
105 if let Some(err) = working_set.parse_errors.first() {
107 report_parse_error(None, &working_set, err);
108 std::process::exit(1);
109 }
110
111 if let Some(err) = working_set.compile_errors.first() {
112 report_compile_error(None, &working_set, err);
113 std::process::exit(1);
114 }
115
116 let mut file_has_main = false;
122 for block in working_set.delta.blocks.iter_mut().map(Arc::make_mut) {
123 if block.signature.name == "main" {
124 file_has_main = true;
125 block.signature.name = script_name.clone();
126 } else if block.signature.name.starts_with("main ") {
127 file_has_main = true;
128 block.signature.name = script_name.clone() + " " + &block.signature.name[5..];
129 }
130 }
131
132 if file_has_main && let Some(overlay) = working_set.delta.last_overlay_mut() {
135 let mut new_decls = Vec::new();
139 for (name, &decl_id) in &overlay.decls {
140 if name == b"main" || name.starts_with(b"main ") {
141 let mut new_name = script_name_bytes.clone();
142 if name.len() > 4 {
143 new_name.extend_from_slice(&name[4..]);
144 }
145 new_decls.push((new_name, decl_id));
146 }
147 }
148 for (n, id) in new_decls {
149 overlay.decls.insert(n, id);
150 }
151
152 let mut new_predecls = Vec::new();
153 for (name, &decl_id) in &overlay.predecls {
154 if name == b"main" || name.starts_with(b"main ") {
155 let mut new_name = script_name_bytes.clone();
156 if name.len() > 4 {
157 new_name.extend_from_slice(&name[4..]);
158 }
159 new_predecls.push((new_name, decl_id));
160 }
161 }
162 for (n, id) in new_predecls {
163 overlay.predecls.insert(n, id);
164 }
165 }
166
167 engine_state.merge_delta(working_set.delta)?;
169
170 let exit_code = if file_has_main && engine_state.find_decl(&script_name_bytes, &[]).is_some() {
174 let pipeline =
176 match eval_block::<WithoutDebug>(engine_state, stack, &block, PipelineData::empty()) {
177 Ok(data) if data.early_return => {
178 return Ok(());
180 }
181 Ok(data) => data.body,
182 Err(err) => return Err(err),
183 };
184
185 print_pipeline(engine_state, stack, pipeline, true)?;
187
188 let command_line = format!("main {}", args.join(" "));
194 eval_source(
195 engine_state,
196 stack,
197 command_line.as_bytes(),
198 "<commandline>",
199 input,
200 true,
201 )
202 } else {
203 eval_parsed_block_source(engine_state, stack, &block, file_path_str, input, true)
208 };
209
210 if exit_code != 0 {
211 std::process::exit(exit_code);
212 }
213
214 info!("evaluate {}:{}:{}", file!(), line!(), column!());
215
216 Ok(())
217}
218
219#[cfg(test)]
220mod tests {
221 use nu_test_support::fs::Stub::FileWithContent;
222 use nu_test_support::playground::Playground;
223 use nu_test_support::prelude::*;
224
225 #[test]
226 #[deps(NU)]
227 fn evaluate_file_arg_with_various_characters_escape_properly() -> Result {
228 Playground::setup("evaluate_file_various_characters", |dirs, sandbox| {
229 sandbox.with_files(&[FileWithContent(
230 "test.nu",
231 "def main [...args: string] { $args | to json }",
232 )]);
233
234 let args = r#"a "" b "c\nd" "e f" ] "[" "}" "{" "\"" '"'"#;
235 let expected = ["a", "", "b", "c\nd", "e f", "]", "[", "}", "{", "\"", "\""];
236
237 test()
238 .cwd(dirs.test())
239 .run(format!("nu test.nu {args} | from json"))
240 .expect_value_eq(expected)
241 })
242 }
243}