1#[cfg(unix)]
4use std::os::unix::process::ExitStatusExt;
5
6use crate::{error, processes};
7
8#[derive(Default)]
10pub struct ExecutionResult {
11 pub next_control_flow: ExecutionControlFlow,
13 pub exit_code: ExecutionExitCode,
15}
16
17impl ExecutionResult {
18 pub fn new(exit_code: u8) -> Self {
24 Self {
25 exit_code: exit_code.into(),
26 ..Self::default()
27 }
28 }
29
30 pub fn stopped() -> Self {
32 const SIGTSTP: std::os::raw::c_int = 20;
34
35 #[expect(clippy::cast_possible_truncation)]
36 Self::new(128 + SIGTSTP as u8)
37 }
38
39 pub const fn success() -> Self {
41 Self {
42 next_control_flow: ExecutionControlFlow::Normal,
43 exit_code: ExecutionExitCode::Success,
44 }
45 }
46
47 pub const fn general_error() -> Self {
49 Self {
50 next_control_flow: ExecutionControlFlow::Normal,
51 exit_code: ExecutionExitCode::GeneralError,
52 }
53 }
54
55 pub const fn is_success(&self) -> bool {
57 self.exit_code.is_success()
58 }
59
60 pub const fn is_normal_flow(&self) -> bool {
63 matches!(self.next_control_flow, ExecutionControlFlow::Normal)
64 }
65
66 pub const fn is_break(&self) -> bool {
68 matches!(
69 self.next_control_flow,
70 ExecutionControlFlow::BreakLoop { .. }
71 )
72 }
73
74 pub const fn is_continue(&self) -> bool {
76 matches!(
77 self.next_control_flow,
78 ExecutionControlFlow::ContinueLoop { .. }
79 )
80 }
81
82 pub const fn is_return_or_exit(&self) -> bool {
86 matches!(
87 self.next_control_flow,
88 ExecutionControlFlow::ReturnFromFunctionOrScript | ExecutionControlFlow::ExitShell
89 )
90 }
91}
92
93impl From<ExecutionExitCode> for ExecutionResult {
94 fn from(exit_code: ExecutionExitCode) -> Self {
95 Self {
96 next_control_flow: ExecutionControlFlow::Normal,
97 exit_code,
98 }
99 }
100}
101
102impl From<ExecutionWaitResult> for ExecutionResult {
103 fn from(wait_result: ExecutionWaitResult) -> Self {
104 match wait_result {
105 ExecutionWaitResult::Completed(result) => result,
106 ExecutionWaitResult::Stopped(..) => Self::stopped(),
108 }
109 }
110}
111
112impl From<std::process::Output> for ExecutionResult {
113 fn from(output: std::process::Output) -> Self {
114 if let Some(code) = output.status.code() {
115 #[expect(clippy::cast_sign_loss)]
116 return Self::new((code & 0xFF) as u8);
117 }
118
119 #[cfg(unix)]
120 if let Some(signal) = output.status.signal() {
121 #[expect(clippy::cast_sign_loss)]
122 return Self::new((signal & 0xFF) as u8 + 128);
123 }
124
125 tracing::error!("unhandled process exit");
126 Self::new(127)
127 }
128}
129
130#[derive(Clone, Copy, Default)]
132pub enum ExecutionExitCode {
133 #[default]
135 Success,
136 GeneralError,
138 InvalidUsage,
140 CannotExecute,
142 NotFound,
144 Interrupted,
146 BrokenPipe,
148 Unimplemented,
150 Custom(u8),
152}
153
154impl ExecutionExitCode {
155 pub const fn is_success(&self) -> bool {
157 matches!(self, Self::Success)
158 }
159}
160
161impl From<u8> for ExecutionExitCode {
162 fn from(code: u8) -> Self {
163 match code {
164 0 => Self::Success,
165 1 => Self::GeneralError,
166 2 => Self::InvalidUsage,
167 99 => Self::Unimplemented,
168 126 => Self::CannotExecute,
169 127 => Self::NotFound,
170 130 => Self::Interrupted,
171 141 => Self::BrokenPipe,
172 code => Self::Custom(code),
173 }
174 }
175}
176
177impl From<ExecutionExitCode> for u8 {
178 fn from(code: ExecutionExitCode) -> Self {
179 Self::from(&code)
180 }
181}
182
183impl From<&ExecutionExitCode> for u8 {
184 fn from(code: &ExecutionExitCode) -> Self {
185 match code {
186 ExecutionExitCode::Success => 0,
187 ExecutionExitCode::GeneralError => 1,
188 ExecutionExitCode::InvalidUsage => 2,
189 ExecutionExitCode::Unimplemented => 99,
190 ExecutionExitCode::CannotExecute => 126,
191 ExecutionExitCode::NotFound => 127,
192 ExecutionExitCode::Interrupted => 130,
193 ExecutionExitCode::BrokenPipe => 141,
194 ExecutionExitCode::Custom(code) => *code,
195 }
196 }
197}
198
199#[derive(Clone, Copy, Default)]
201pub enum ExecutionControlFlow {
202 #[default]
204 Normal,
205 BreakLoop {
207 levels: usize,
210 },
211 ContinueLoop {
213 levels: usize,
216 },
217 ReturnFromFunctionOrScript,
219 ExitShell,
221}
222
223impl ExecutionControlFlow {
224 #[must_use]
228 pub const fn try_decrement_loop_levels(&self) -> Self {
229 match self {
230 Self::BreakLoop { levels: 0 } | Self::ContinueLoop { levels: 0 } => Self::Normal,
231 Self::BreakLoop { levels } => Self::BreakLoop {
232 levels: *levels - 1,
233 },
234 Self::ContinueLoop { levels } => Self::ContinueLoop {
235 levels: *levels - 1,
236 },
237 control_flow => *control_flow,
238 }
239 }
240}
241
242pub enum ExecutionSpawnResult {
246 Completed(ExecutionResult),
248 StartedProcess(processes::ChildProcess),
250 StartedTask(tokio::task::JoinHandle<Result<ExecutionResult, error::Error>>),
252}
253
254impl From<ExecutionResult> for ExecutionSpawnResult {
255 fn from(result: ExecutionResult) -> Self {
256 Self::Completed(result)
257 }
258}
259
260impl ExecutionSpawnResult {
261 pub async fn wait(self) -> Result<ExecutionWaitResult, error::Error> {
263 let result = match self {
264 Self::StartedProcess(mut child) => {
265 match child.wait().await? {
268 processes::ProcessWaitResult::Completed(output) => {
269 ExecutionWaitResult::Completed(ExecutionResult::from(output))
270 }
271 processes::ProcessWaitResult::Stopped => ExecutionWaitResult::Stopped(child),
272 }
273 }
274 Self::Completed(result) => ExecutionWaitResult::Completed(result),
275 Self::StartedTask(join_handle) => {
276 let result = join_handle.await?;
277 ExecutionWaitResult::Completed(result?)
278 }
279 };
280
281 Ok(result)
282 }
283
284 pub(crate) async fn poll(self) -> Result<ExecutionWaitResult, error::Error> {
285 let result = match self {
286 Self::StartedProcess(child) => ExecutionWaitResult::Stopped(child),
287 Self::Completed(result) => ExecutionWaitResult::Completed(result),
288 Self::StartedTask(join_handle) => {
289 let result = join_handle.await?;
291 ExecutionWaitResult::Completed(result?)
292 }
293 };
294
295 Ok(result)
296 }
297}
298
299pub enum ExecutionWaitResult {
301 Completed(ExecutionResult),
303 Stopped(processes::ChildProcess),
305}