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 nu_protocol::location!(),
36 PathBuf::from(&path),
37 )),
38 }
39 }?;
40
41 let file_path_str = file_path
42 .to_str()
43 .ok_or_else(|| ShellError::NonUtf8Custom {
44 msg: format!(
45 "Input file name '{}' is not valid UTF8",
46 file_path.to_string_lossy()
47 ),
48 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>".into(), 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 nu_protocol::location!(),
72 file_path.clone(),
73 )
74 })?;
75
76 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 .map(|p| p.body)
178 {
179 Ok(data) => data,
180 Err(ShellError::Return { .. }) => {
181 return Ok(());
183 }
184 Err(err) => return Err(err),
185 };
186
187 print_pipeline(engine_state, stack, pipeline, true)?;
189
190 let args = format!("main {}", args.join(" "));
196 eval_source(
197 engine_state,
198 stack,
199 args.as_bytes(),
200 "<commandline>",
201 input,
202 true,
203 )
204 } else {
205 eval_source(engine_state, stack, &file, file_path_str, input, true)
206 };
207
208 if exit_code != 0 {
209 std::process::exit(exit_code);
210 }
211
212 info!("evaluate {}:{}:{}", file!(), line!(), column!());
213
214 Ok(())
215}