1#[cfg(unix)] use std::os::unix::process::ExitStatusExt;
5
6use crate::{error, processes, tasks};
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;
35
36 #[expect(clippy::cast_possible_truncation)]
37 Self::new(128 + SIGTSTP as u8)
38 }
39
40 pub const fn success() -> Self {
42 Self {
43 next_control_flow: ExecutionControlFlow::Normal,
44 exit_code: ExecutionExitCode::Success,
45 }
46 }
47
48 pub const fn general_error() -> Self {
50 Self {
51 next_control_flow: ExecutionControlFlow::Normal,
52 exit_code: ExecutionExitCode::GeneralError,
53 }
54 }
55
56 pub const fn is_success(&self) -> bool {
58 self.exit_code.is_success()
59 }
60
61 pub const fn is_normal_flow(&self) -> bool {
64 matches!(self.next_control_flow, ExecutionControlFlow::Normal)
65 }
66
67 pub const fn is_break(&self) -> bool {
69 matches!(
70 self.next_control_flow,
71 ExecutionControlFlow::BreakLoop { .. }
72 )
73 }
74
75 pub const fn is_continue(&self) -> bool {
77 matches!(
78 self.next_control_flow,
79 ExecutionControlFlow::ContinueLoop { .. }
80 )
81 }
82
83 pub const fn is_return_or_exit(&self) -> bool {
87 matches!(
88 self.next_control_flow,
89 ExecutionControlFlow::ReturnFromFunctionOrScript | ExecutionControlFlow::ExitShell
90 )
91 }
92
93 pub const fn is_exit(&self) -> bool {
95 matches!(self.next_control_flow, ExecutionControlFlow::ExitShell)
96 }
97}
98
99impl From<ExecutionExitCode> for ExecutionResult {
100 fn from(exit_code: ExecutionExitCode) -> Self {
101 Self {
102 next_control_flow: ExecutionControlFlow::Normal,
103 exit_code,
104 }
105 }
106}
107
108impl From<ExecutionWaitResult> for ExecutionResult {
109 fn from(wait_result: ExecutionWaitResult) -> Self {
110 match wait_result {
111 ExecutionWaitResult::Completed(result) => result,
112 ExecutionWaitResult::Stopped(..) => Self::stopped(),
114 }
115 }
116}
117
118impl From<std::process::Output> for ExecutionResult {
119 fn from(output: std::process::Output) -> Self {
120 if let Some(code) = output.status.code() {
121 #[expect(clippy::cast_sign_loss)]
122 return Self::new((code & 0xff) as u8);
123 }
124
125 #[cfg(unix)] if let Some(signal) = output.status.signal() {
127 #[expect(clippy::cast_sign_loss)]
128 return Self::new((signal & 0xff) as u8 + 128);
129 }
130
131 tracing::error!("unhandled process exit");
132 Self::new(127)
133 }
134}
135
136#[derive(Clone, Copy, Default)]
138pub enum ExecutionExitCode {
139 #[default]
141 Success,
142 GeneralError,
144 InvalidUsage,
146 CannotExecute,
148 NotFound,
150 Interrupted,
152 BrokenPipe,
154 Unimplemented,
156 Custom(u8),
158}
159
160impl ExecutionExitCode {
161 pub const fn is_success(&self) -> bool {
163 matches!(self, Self::Success)
164 }
165}
166
167impl From<u8> for ExecutionExitCode {
168 fn from(code: u8) -> Self {
169 match code {
170 0 => Self::Success,
171 1 => Self::GeneralError,
172 2 => Self::InvalidUsage,
173 99 => Self::Unimplemented,
174 126 => Self::CannotExecute,
175 127 => Self::NotFound,
176 130 => Self::Interrupted,
177 141 => Self::BrokenPipe,
178 code => Self::Custom(code),
179 }
180 }
181}
182
183impl From<ExecutionExitCode> for u8 {
184 fn from(code: ExecutionExitCode) -> Self {
185 Self::from(&code)
186 }
187}
188
189impl From<&ExecutionExitCode> for u8 {
190 fn from(code: &ExecutionExitCode) -> Self {
191 match code {
192 ExecutionExitCode::Success => 0,
193 ExecutionExitCode::GeneralError => 1,
194 ExecutionExitCode::InvalidUsage => 2,
195 ExecutionExitCode::Unimplemented => 99,
196 ExecutionExitCode::CannotExecute => 126,
197 ExecutionExitCode::NotFound => 127,
198 ExecutionExitCode::Interrupted => 130,
199 ExecutionExitCode::BrokenPipe => 141,
200 ExecutionExitCode::Custom(code) => *code,
201 }
202 }
203}
204
205#[derive(Clone, Copy, Default)]
207pub enum ExecutionControlFlow {
208 #[default]
210 Normal,
211 BreakLoop {
213 levels: usize,
217 },
218 ContinueLoop {
220 levels: usize,
223 },
224 ReturnFromFunctionOrScript,
226 ExitShell,
228}
229
230impl ExecutionControlFlow {
231 #[must_use]
235 pub const fn try_decrement_loop_levels(&self) -> Self {
236 match self {
237 Self::BreakLoop { levels: 0 } | Self::ContinueLoop { levels: 0 } => Self::Normal,
238 Self::BreakLoop { levels } => Self::BreakLoop {
239 levels: *levels - 1,
240 },
241 Self::ContinueLoop { levels } => Self::ContinueLoop {
242 levels: *levels - 1,
243 },
244 control_flow => *control_flow,
245 }
246 }
247}
248
249pub enum ExecutionSpawnResult {
253 Completed(ExecutionResult),
255 StartedProcess(processes::ChildProcess),
257 StartedTask(tasks::TaskHandle<Result<ExecutionResult, error::Error>>),
260}
261
262impl From<ExecutionResult> for ExecutionSpawnResult {
263 fn from(result: ExecutionResult) -> Self {
264 Self::Completed(result)
265 }
266}
267
268impl ExecutionSpawnResult {
269 pub async fn wait(self) -> Result<ExecutionWaitResult, error::Error> {
276 let result = match self {
277 Self::StartedProcess(mut child) => {
278 match child.wait().await? {
281 processes::ProcessWaitResult::Completed(output) => {
282 ExecutionWaitResult::Completed(ExecutionResult::from(output))
283 }
284 processes::ProcessWaitResult::Stopped => ExecutionWaitResult::Stopped(child),
285 }
286 }
287 Self::Completed(result) => ExecutionWaitResult::Completed(result),
288 Self::StartedTask(join_handle) => {
289 let result = join_handle.await?;
290 ExecutionWaitResult::Completed(result?)
291 }
292 };
293
294 Ok(result)
295 }
296
297 pub(crate) async fn poll(self) -> Result<ExecutionWaitResult, error::Error> {
298 let result = match self {
299 Self::StartedProcess(child) => ExecutionWaitResult::Stopped(child),
300 Self::Completed(result) => ExecutionWaitResult::Completed(result),
301 Self::StartedTask(join_handle) => {
302 let result = join_handle.await?;
304 ExecutionWaitResult::Completed(result?)
305 }
306 };
307
308 Ok(result)
309 }
310}
311
312pub enum ExecutionWaitResult {
314 Completed(ExecutionResult),
316 Stopped(processes::ChildProcess),
318}