1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
use crate::util::eval_source;
use log::{info, trace};
use miette::{IntoDiagnostic, Result};
use nu_engine::{convert_env_values, current_dir, eval_block};
use nu_parser::parse;
use nu_path::canonicalize_with;
use nu_protocol::{
    ast::Call,
    debugger::WithoutDebug,
    engine::{EngineState, Stack, StateWorkingSet},
    report_error, Config, PipelineData, ShellError, Span, Value,
};
use nu_utils::stdout_write_all_and_flush;
use std::sync::Arc;

/// Main function used when a file path is found as argument for nu
pub fn evaluate_file(
    path: String,
    args: &[String],
    engine_state: &mut EngineState,
    stack: &mut Stack,
    input: PipelineData,
) -> Result<()> {
    // Translate environment variables from Strings to Values
    if let Some(e) = convert_env_values(engine_state, stack) {
        let working_set = StateWorkingSet::new(engine_state);
        report_error(&working_set, &e);
        std::process::exit(1);
    }

    let cwd = current_dir(engine_state, stack)?;

    let file_path = canonicalize_with(&path, cwd).unwrap_or_else(|e| {
        let working_set = StateWorkingSet::new(engine_state);
        report_error(
            &working_set,
            &ShellError::FileNotFoundCustom {
                msg: format!("Could not access file '{}': {:?}", path, e.to_string()),
                span: Span::unknown(),
            },
        );
        std::process::exit(1);
    });

    let file_path_str = file_path.to_str().unwrap_or_else(|| {
        let working_set = StateWorkingSet::new(engine_state);
        report_error(
            &working_set,
            &ShellError::NonUtf8Custom {
                msg: format!(
                    "Input file name '{}' is not valid UTF8",
                    file_path.to_string_lossy()
                ),
                span: Span::unknown(),
            },
        );
        std::process::exit(1);
    });

    let file = std::fs::read(&file_path)
        .into_diagnostic()
        .unwrap_or_else(|e| {
            let working_set = StateWorkingSet::new(engine_state);
            report_error(
                &working_set,
                &ShellError::FileNotFoundCustom {
                    msg: format!(
                        "Could not read file '{}': {:?}",
                        file_path_str,
                        e.to_string()
                    ),
                    span: Span::unknown(),
                },
            );
            std::process::exit(1);
        });

    engine_state.start_in_file(Some(file_path_str));

    let parent = file_path.parent().unwrap_or_else(|| {
        let working_set = StateWorkingSet::new(engine_state);
        report_error(
            &working_set,
            &ShellError::FileNotFoundCustom {
                msg: format!("The file path '{file_path_str}' does not have a parent"),
                span: Span::unknown(),
            },
        );
        std::process::exit(1);
    });

    stack.add_env_var(
        "FILE_PWD".to_string(),
        Value::string(parent.to_string_lossy(), Span::unknown()),
    );
    stack.add_env_var(
        "CURRENT_FILE".to_string(),
        Value::string(file_path.to_string_lossy(), Span::unknown()),
    );
    stack.add_env_var(
        "PROCESS_PATH".to_string(),
        Value::string(path, Span::unknown()),
    );

    let source_filename = file_path
        .file_name()
        .expect("internal error: script missing filename");

    let mut working_set = StateWorkingSet::new(engine_state);
    trace!("parsing file: {}", file_path_str);
    let block = parse(&mut working_set, Some(file_path_str), &file, false);

    if let Some(err) = working_set.parse_errors.first() {
        report_error(&working_set, err);
        std::process::exit(1);
    }

    for block in working_set.delta.blocks.iter_mut().map(Arc::make_mut) {
        if block.signature.name == "main" {
            block.signature.name = source_filename.to_string_lossy().to_string();
        } else if block.signature.name.starts_with("main ") {
            block.signature.name =
                source_filename.to_string_lossy().to_string() + " " + &block.signature.name[5..];
        }
    }

    let _ = engine_state.merge_delta(working_set.delta);

    if engine_state.find_decl(b"main", &[]).is_some() {
        let args = format!("main {}", args.join(" "));

        let pipeline_data =
            eval_block::<WithoutDebug>(engine_state, stack, &block, PipelineData::empty());
        let pipeline_data = match pipeline_data {
            Err(ShellError::Return { .. }) => {
                // allows early exists before `main` is run.
                return Ok(());
            }

            x => x,
        }
        .unwrap_or_else(|e| {
            let working_set = StateWorkingSet::new(engine_state);
            report_error(&working_set, &e);
            std::process::exit(1);
        });

        let result = pipeline_data.print(engine_state, stack, true, false);

        match result {
            Err(err) => {
                let working_set = StateWorkingSet::new(engine_state);

                report_error(&working_set, &err);
                std::process::exit(1);
            }
            Ok(exit_code) => {
                if exit_code != 0 {
                    std::process::exit(exit_code as i32);
                }
            }
        }

        if !eval_source(
            engine_state,
            stack,
            args.as_bytes(),
            "<commandline>",
            input,
            true,
        ) {
            std::process::exit(1);
        }
    } else if !eval_source(engine_state, stack, &file, file_path_str, input, true) {
        std::process::exit(1);
    }

    info!("evaluate {}:{}:{}", file!(), line!(), column!());

    Ok(())
}

pub(crate) fn print_table_or_error(
    engine_state: &mut EngineState,
    stack: &mut Stack,
    mut pipeline_data: PipelineData,
    config: &mut Config,
) -> Option<i64> {
    let exit_code = match &mut pipeline_data {
        PipelineData::ExternalStream { exit_code, .. } => exit_code.take(),
        _ => None,
    };

    // Change the engine_state config to use the passed in configuration
    engine_state.set_config(config.clone());

    if let PipelineData::Value(Value::Error { error, .. }, ..) = &pipeline_data {
        let working_set = StateWorkingSet::new(engine_state);
        report_error(&working_set, &**error);
        std::process::exit(1);
    }

    if let Some(decl_id) = engine_state.find_decl("table".as_bytes(), &[]) {
        let command = engine_state.get_decl(decl_id);
        if command.get_block_id().is_some() {
            print_or_exit(pipeline_data, engine_state, config);
        } else {
            // The final call on table command, it's ok to set redirect_output to false.
            let call = Call::new(Span::new(0, 0));
            let table = command.run(engine_state, stack, &call, pipeline_data);

            match table {
                Ok(table) => {
                    print_or_exit(table, engine_state, config);
                }
                Err(error) => {
                    let working_set = StateWorkingSet::new(engine_state);
                    report_error(&working_set, &error);
                    std::process::exit(1);
                }
            }
        }
    } else {
        print_or_exit(pipeline_data, engine_state, config);
    }

    // Make sure everything has finished
    if let Some(exit_code) = exit_code {
        let mut exit_code: Vec<_> = exit_code.into_iter().collect();
        exit_code
            .pop()
            .and_then(|last_exit_code| match last_exit_code {
                Value::Int { val: code, .. } => Some(code),
                _ => None,
            })
    } else {
        None
    }
}

fn print_or_exit(pipeline_data: PipelineData, engine_state: &mut EngineState, config: &Config) {
    for item in pipeline_data {
        if let Value::Error { error, .. } = item {
            let working_set = StateWorkingSet::new(engine_state);

            report_error(&working_set, &*error);

            std::process::exit(1);
        }

        let out = item.to_expanded_string("\n", config) + "\n";
        let _ = stdout_write_all_and_flush(out).map_err(|err| eprintln!("{err}"));
    }
}