Skip to main content

clash_brush_core/shell/
execution.rs

1//! Execution support for shell.
2
3use std::{io::Read, path::Path};
4
5use crate::{
6    ExecutionControlFlow, ExecutionParameters, ExecutionResult, arithmetic::Evaluatable as _,
7    callstack, error, interp::Execute as _, openfiles, trace_categories,
8};
9
10impl<SE: crate::extensions::ShellExtensions> crate::Shell<SE> {
11    /// Returns the default execution parameters for this shell.
12    pub fn default_exec_params(&self) -> ExecutionParameters {
13        ExecutionParameters::default()
14    }
15
16    pub(super) async fn source_if_exists(
17        &mut self,
18        path: impl AsRef<Path>,
19        params: &ExecutionParameters,
20    ) -> Result<bool, error::Error> {
21        let path = path.as_ref();
22        if path.exists() {
23            self.source_script(path, std::iter::empty::<String>(), params)
24                .await?;
25            Ok(true)
26        } else {
27            tracing::debug!("skipping non-existent file: {}", path.display());
28            Ok(false)
29        }
30    }
31
32    /// Source the given file as a shell script, returning the execution result.
33    ///
34    /// # Arguments
35    ///
36    /// * `path` - The path to the file to source.
37    /// * `args` - The arguments to pass to the script as positional parameters.
38    /// * `params` - Execution parameters.
39    pub async fn source_script<S: Into<String>, P: AsRef<Path>, I: Iterator<Item = S>>(
40        &mut self,
41        path: P,
42        args: I,
43        params: &ExecutionParameters,
44    ) -> Result<ExecutionResult, error::Error> {
45        self.parse_and_execute_script_file(
46            path.as_ref(),
47            args,
48            params,
49            callstack::ScriptCallType::Source,
50        )
51        .await
52    }
53
54    /// Parse and execute the given file as a shell script, returning the execution result.
55    ///
56    /// # Arguments
57    ///
58    /// * `path` - The path to the file to source.
59    /// * `args` - The arguments to pass to the script as positional parameters.
60    /// * `params` - Execution parameters.
61    /// * `call_type` - The type of script call being made.
62    async fn parse_and_execute_script_file<
63        S: Into<String>,
64        P: AsRef<Path>,
65        I: Iterator<Item = S>,
66    >(
67        &mut self,
68        path: P,
69        args: I,
70        params: &ExecutionParameters,
71        call_type: callstack::ScriptCallType,
72    ) -> Result<ExecutionResult, error::Error> {
73        let path = path.as_ref();
74        tracing::debug!("sourcing: {}", path.display());
75
76        let mut options = std::fs::File::options();
77        options.read(true);
78
79        let opened_file: openfiles::OpenFile = self
80            .open_file(&options, path, params)
81            .map_err(|e| error::ErrorKind::FailedSourcingFile(path.to_owned(), e))?;
82
83        if opened_file.is_dir() {
84            return Err(error::ErrorKind::FailedSourcingFile(
85                path.to_owned(),
86                std::io::Error::from(std::io::ErrorKind::IsADirectory),
87            )
88            .into());
89        }
90
91        let source_info = crate::SourceInfo::from(path.to_owned());
92
93        let mut result = self
94            .source_file(opened_file, &source_info, args, params, call_type)
95            .await?;
96
97        // Handle control flow at script execution boundary. If execution completed
98        // with a `return`, we need to clear it since it's already been "used". All
99        // other control flow types are preserved.
100        if matches!(
101            result.next_control_flow,
102            ExecutionControlFlow::ReturnFromFunctionOrScript
103        ) {
104            result.next_control_flow = ExecutionControlFlow::Normal;
105        }
106
107        Ok(result)
108    }
109
110    /// Source the given file as a shell script, returning the execution result.
111    ///
112    /// # Arguments
113    ///
114    /// * `file` - The file to source.
115    /// * `source_info` - Information about the source of the script.
116    /// * `args` - The arguments to pass to the script as positional parameters.
117    /// * `params` - Execution parameters.
118    /// * `call_type` - The type of script call being made.
119    async fn source_file<F: Read, S: Into<String>, I: Iterator<Item = S>>(
120        &mut self,
121        file: F,
122        source_info: &crate::SourceInfo,
123        args: I,
124        params: &ExecutionParameters,
125        call_type: callstack::ScriptCallType,
126    ) -> Result<ExecutionResult, error::Error> {
127        let mut reader = std::io::BufReader::new(file);
128        let mut parser = brush_parser::Parser::new(&mut reader, &self.parser_options());
129
130        tracing::debug!(target: trace_categories::PARSE, "Parsing sourced file: {}", source_info.source);
131        let parse_result = parser.parse_program();
132
133        let script_positional_args = args.map(Into::into);
134
135        self.call_stack
136            .push_script(call_type, source_info, script_positional_args);
137
138        let result = self
139            .run_parsed_result(parse_result, source_info, params)
140            .await;
141
142        self.call_stack.pop();
143
144        result
145    }
146
147    /// Executes the given string as a shell program, returning the resulting exit status.
148    ///
149    /// # Arguments
150    ///
151    /// * `command` - The command to execute.
152    /// * `source_info` - Information about the source of the command text.
153    /// * `params` - Execution parameters.
154    pub async fn run_string<S: Into<String>>(
155        &mut self,
156        command: S,
157        source_info: &crate::SourceInfo,
158        params: &ExecutionParameters,
159    ) -> Result<ExecutionResult, error::Error> {
160        let parse_result = self.parse_string(command);
161        self.run_parsed_result(parse_result, source_info, params)
162            .await
163    }
164
165    /// Executes the given script file, returning the resulting exit status.
166    ///
167    /// # Arguments
168    ///
169    /// * `script_path` - The path to the script file to execute.
170    /// * `args` - The arguments to pass to the script as positional parameters.
171    pub async fn run_script<S: Into<String>, P: AsRef<Path>, I: Iterator<Item = S>>(
172        &mut self,
173        script_path: P,
174        args: I,
175    ) -> Result<ExecutionResult, error::Error> {
176        let params = self.default_exec_params();
177        let result = self
178            .parse_and_execute_script_file(
179                script_path.as_ref(),
180                args,
181                &params,
182                callstack::ScriptCallType::Run,
183            )
184            .await?;
185
186        let _ = self.on_exit().await;
187
188        Ok(result)
189    }
190
191    pub(crate) async fn run_parsed_result(
192        &mut self,
193        parse_result: Result<brush_parser::ast::Program, brush_parser::ParseError>,
194        source_info: &crate::SourceInfo,
195        params: &ExecutionParameters,
196    ) -> Result<ExecutionResult, error::Error> {
197        // If parsing succeeded, run the program. If there's a parse error, it's fatal (per spec).
198        let result = match parse_result {
199            Ok(prog) => self.run_program(prog, params).await,
200            Err(parse_err) => Err(error::Error::from(error::ErrorKind::ParseError(
201                parse_err,
202                source_info.clone(),
203            ))
204            .into_fatal()),
205        };
206
207        // Report any errors.
208        match result {
209            Ok(result) => Ok(result),
210            Err(err) => {
211                let _ = self.display_error(&mut params.stderr(self), &err);
212
213                let result = err.into_result(self);
214                self.set_last_exit_status(result.exit_code.into());
215
216                Ok(result)
217            }
218        }
219    }
220
221    /// Executes the given parsed shell program, returning the resulting exit status.
222    ///
223    /// # Arguments
224    ///
225    /// * `program` - The program to execute.
226    /// * `params` - Execution parameters.
227    pub async fn run_program(
228        &mut self,
229        program: brush_parser::ast::Program,
230        params: &ExecutionParameters,
231    ) -> Result<ExecutionResult, error::Error> {
232        program.execute(self, params).await
233    }
234
235    /// Evaluate the given arithmetic expression, returning the result.
236    pub fn eval_arithmetic(
237        &mut self,
238        expr: &brush_parser::ast::ArithmeticExpr,
239    ) -> Result<i64, error::Error> {
240        Ok(expr.eval(self)?)
241    }
242}