1use crate::util::{eval_source, print_pipeline};
2use log::{info, trace};
3use nu_engine::eval_block;
4use nu_parser::parse;
5use nu_path::canonicalize_with;
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,
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 = canonicalize_with(&path, cwd).map_err(|err| {
30 IoError::new_internal_with_path(
31 err.not_found_as(NotFound::File),
32 "Could not access file",
33 nu_protocol::location!(),
34 PathBuf::from(&path),
35 )
36 })?;
37
38 let file_path_str = file_path
39 .to_str()
40 .ok_or_else(|| ShellError::NonUtf8Custom {
41 msg: format!(
42 "Input file name '{}' is not valid UTF8",
43 file_path.to_string_lossy()
44 ),
45 span: Span::unknown(),
46 })?;
47
48 let file = std::fs::read(&file_path).map_err(|err| {
49 IoError::new_internal_with_path(
50 err.not_found_as(NotFound::File),
51 "Could not read file",
52 nu_protocol::location!(),
53 file_path.clone(),
54 )
55 })?;
56 engine_state.file = Some(file_path.clone());
57
58 let parent = file_path.parent().ok_or_else(|| {
59 IoError::new_internal_with_path(
60 ErrorKind::DirectoryNotFound,
61 "The file path does not have a parent",
62 nu_protocol::location!(),
63 file_path.clone(),
64 )
65 })?;
66
67 stack.add_env_var(
68 "FILE_PWD".to_string(),
69 Value::string(parent.to_string_lossy(), Span::unknown()),
70 );
71 stack.add_env_var(
72 "CURRENT_FILE".to_string(),
73 Value::string(file_path.to_string_lossy(), Span::unknown()),
74 );
75 stack.add_env_var(
76 "PROCESS_PATH".to_string(),
77 Value::string(path, Span::unknown()),
78 );
79
80 let source_filename = file_path
81 .file_name()
82 .expect("internal error: missing filename");
83
84 let mut working_set = StateWorkingSet::new(engine_state);
85 trace!("parsing file: {}", file_path_str);
86 let block = parse(&mut working_set, Some(file_path_str), &file, false);
87
88 if let Some(warning) = working_set.parse_warnings.first() {
89 report_parse_warning(&working_set, warning);
90 }
91
92 if let Some(err) = working_set.parse_errors.first() {
94 report_parse_error(&working_set, err);
95 std::process::exit(1);
96 }
97
98 if let Some(err) = working_set.compile_errors.first() {
99 report_compile_error(&working_set, err);
100 std::process::exit(1);
101 }
102
103 for block in working_set.delta.blocks.iter_mut().map(Arc::make_mut) {
105 if block.signature.name == "main" {
106 block.signature.name = source_filename.to_string_lossy().to_string();
107 } else if block.signature.name.starts_with("main ") {
108 block.signature.name =
109 source_filename.to_string_lossy().to_string() + " " + &block.signature.name[5..];
110 }
111 }
112
113 engine_state.merge_delta(working_set.delta)?;
115
116 let exit_code = if engine_state.find_decl(b"main", &[]).is_some() {
118 let pipeline =
120 match eval_block::<WithoutDebug>(engine_state, stack, &block, PipelineData::empty()) {
121 Ok(data) => data,
122 Err(ShellError::Return { .. }) => {
123 return Ok(());
125 }
126 Err(err) => return Err(err),
127 };
128
129 print_pipeline(engine_state, stack, pipeline, true)?;
131
132 let args = format!("main {}", args.join(" "));
135 eval_source(
136 engine_state,
137 stack,
138 args.as_bytes(),
139 "<commandline>",
140 input,
141 true,
142 )
143 } else {
144 eval_source(engine_state, stack, &file, file_path_str, input, true)
145 };
146
147 if exit_code != 0 {
148 std::process::exit(exit_code);
149 }
150
151 info!("evaluate {}:{}:{}", file!(), line!(), column!());
152
153 Ok(())
154}