1use crate::util::{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(),
48 })?;
49
50 let file = std::fs::read(&file_path).map_err(|err| {
51 let cmdline = format!("nu {path} {}", args.join(" "));
52 let mut working_set = StateWorkingSet::new(engine_state);
53 let file_id = working_set.add_file("<commandline>".into(), cmdline.as_bytes());
54 let span = working_set
55 .get_span_for_file(file_id)
56 .subspan(3, path.len() + 3)
57 .expect("<commandline> to contain script path");
58 if let Err(err) = engine_state.merge_delta(working_set.render()) {
59 err
60 } else {
61 IoError::new(err.not_found_as(NotFound::File), span, PathBuf::from(&path)).into()
62 }
63 })?;
64 engine_state.file = Some(file_path.clone());
65
66 let parent = file_path.parent().ok_or_else(|| {
67 IoError::new_internal_with_path(
68 ErrorKind::DirectoryNotFound,
69 "The file path does not have a parent",
70 file_path.clone(),
71 )
72 })?;
73
74 stack.add_env_var(
75 "FILE_PWD".to_string(),
76 Value::string(parent.to_string_lossy(), Span::unknown()),
77 );
78 stack.add_env_var(
79 "CURRENT_FILE".to_string(),
80 Value::string(file_path.to_string_lossy(), Span::unknown()),
81 );
82 stack.add_env_var(
83 "PROCESS_PATH".to_string(),
84 Value::string(path, Span::unknown()),
85 );
86
87 let source_filename = file_path
88 .file_name()
89 .expect("internal error: missing filename");
90
91 let script_name = source_filename.to_string_lossy().to_string();
93 let script_name_bytes = script_name.as_bytes().to_vec();
94
95 let mut working_set = StateWorkingSet::new(engine_state);
96 trace!("parsing file: {file_path_str}");
97 let block = parse(&mut working_set, Some(file_path_str), &file, false);
98
99 if let Some(warning) = working_set.parse_warnings.first() {
100 report_parse_warning(None, &working_set, warning);
101 }
102
103 if let Some(err) = working_set.parse_errors.first() {
105 report_parse_error(None, &working_set, err);
106 std::process::exit(1);
107 }
108
109 if let Some(err) = working_set.compile_errors.first() {
110 report_compile_error(None, &working_set, err);
111 std::process::exit(1);
112 }
113
114 let mut file_has_main = false;
120 for block in working_set.delta.blocks.iter_mut().map(Arc::make_mut) {
121 if block.signature.name == "main" {
122 file_has_main = true;
123 block.signature.name = script_name.clone();
124 } else if block.signature.name.starts_with("main ") {
125 file_has_main = true;
126 block.signature.name = script_name.clone() + " " + &block.signature.name[5..];
127 }
128 }
129
130 if file_has_main && let Some(overlay) = working_set.delta.last_overlay_mut() {
133 let mut new_decls = Vec::new();
137 for (name, &decl_id) in &overlay.decls {
138 if name == b"main" || name.starts_with(b"main ") {
139 let mut new_name = script_name_bytes.clone();
140 if name.len() > 4 {
141 new_name.extend_from_slice(&name[4..]);
142 }
143 new_decls.push((new_name, decl_id));
144 }
145 }
146 for (n, id) in new_decls {
147 overlay.decls.insert(n, id);
148 }
149
150 let mut new_predecls = Vec::new();
151 for (name, &decl_id) in &overlay.predecls {
152 if name == b"main" || name.starts_with(b"main ") {
153 let mut new_name = script_name_bytes.clone();
154 if name.len() > 4 {
155 new_name.extend_from_slice(&name[4..]);
156 }
157 new_predecls.push((new_name, decl_id));
158 }
159 }
160 for (n, id) in new_predecls {
161 overlay.predecls.insert(n, id);
162 }
163 }
164
165 engine_state.merge_delta(working_set.delta)?;
167
168 let exit_code = if file_has_main && engine_state.find_decl(&script_name_bytes, &[]).is_some() {
172 let pipeline =
174 match eval_block::<WithoutDebug>(engine_state, stack, &block, PipelineData::empty())
175 .map(|p| p.body)
176 {
177 Ok(data) => data,
178 Err(ShellError::Return { .. }) => {
179 return Ok(());
181 }
182 Err(err) => return Err(err),
183 };
184
185 print_pipeline(engine_state, stack, pipeline, true)?;
187
188 let args = format!("main {}", args.join(" "));
194 eval_source(
195 engine_state,
196 stack,
197 args.as_bytes(),
198 "<commandline>",
199 input,
200 true,
201 )
202 } else {
203 eval_source(engine_state, stack, &file, file_path_str, input, true)
204 };
205
206 if exit_code != 0 {
207 std::process::exit(exit_code);
208 }
209
210 info!("evaluate {}:{}:{}", file!(), line!(), column!());
211
212 Ok(())
213}