kodegen_bash_shell/core/
results.rs1use tokio_util::sync::CancellationToken;
4
5use super::{error, processes};
6
7#[derive(Default)]
9pub struct ExecutionResult {
10 pub next_control_flow: ExecutionControlFlow,
12 pub exit_code: ExecutionExitCode,
14}
15
16impl ExecutionResult {
17 pub fn new(exit_code: u8) -> Self {
23 Self {
24 exit_code: exit_code.into(),
25 ..Self::default()
26 }
27 }
28
29 pub fn stopped() -> Self {
31 const SIGTSTP: std::os::raw::c_int = 20;
33
34 #[expect(clippy::cast_possible_truncation)]
35 Self::new(128 + SIGTSTP as u8)
36 }
37
38 pub const fn success() -> Self {
40 Self {
41 next_control_flow: ExecutionControlFlow::Normal,
42 exit_code: ExecutionExitCode::Success,
43 }
44 }
45
46 pub const fn general_error() -> Self {
48 Self {
49 next_control_flow: ExecutionControlFlow::Normal,
50 exit_code: ExecutionExitCode::GeneralError,
51 }
52 }
53
54 pub const fn is_success(&self) -> bool {
56 self.exit_code.is_success()
57 }
58
59 pub const fn is_normal_flow(&self) -> bool {
62 matches!(self.next_control_flow, ExecutionControlFlow::Normal)
63 }
64
65 pub const fn is_break(&self) -> bool {
67 matches!(
68 self.next_control_flow,
69 ExecutionControlFlow::BreakLoop { .. }
70 )
71 }
72
73 pub const fn is_continue(&self) -> bool {
75 matches!(
76 self.next_control_flow,
77 ExecutionControlFlow::ContinueLoop { .. }
78 )
79 }
80
81 pub const fn is_return_or_exit(&self) -> bool {
85 matches!(
86 self.next_control_flow,
87 ExecutionControlFlow::ReturnFromFunctionOrScript | ExecutionControlFlow::ExitShell
88 )
89 }
90
91 pub fn is_cancelled(&self) -> bool {
93 matches!(self.exit_code, ExecutionExitCode::Interrupted)
94 }
95}
96
97impl From<ExecutionExitCode> for ExecutionResult {
98 fn from(exit_code: ExecutionExitCode) -> Self {
99 Self {
100 next_control_flow: ExecutionControlFlow::Normal,
101 exit_code,
102 }
103 }
104}
105
106#[derive(Clone, Copy, Default)]
108pub enum ExecutionExitCode {
109 #[default]
111 Success,
112 GeneralError,
114 InvalidUsage,
116 CannotExecute,
118 NotFound,
120 Interrupted,
122 Unimplemented,
124 Custom(u8),
126}
127
128impl ExecutionExitCode {
129 pub const fn is_success(&self) -> bool {
131 matches!(self, Self::Success)
132 }
133}
134
135impl From<u8> for ExecutionExitCode {
136 fn from(code: u8) -> Self {
137 match code {
138 0 => Self::Success,
139 1 => Self::GeneralError,
140 2 => Self::InvalidUsage,
141 99 => Self::Unimplemented,
142 126 => Self::CannotExecute,
143 127 => Self::NotFound,
144 130 => Self::Interrupted,
145 code => Self::Custom(code),
146 }
147 }
148}
149
150impl From<ExecutionExitCode> for u8 {
151 fn from(code: ExecutionExitCode) -> Self {
152 Self::from(&code)
153 }
154}
155
156impl From<&ExecutionExitCode> for u8 {
157 fn from(code: &ExecutionExitCode) -> Self {
158 match code {
159 ExecutionExitCode::Success => 0,
160 ExecutionExitCode::GeneralError => 1,
161 ExecutionExitCode::InvalidUsage => 2,
162 ExecutionExitCode::Unimplemented => 99,
163 ExecutionExitCode::CannotExecute => 126,
164 ExecutionExitCode::NotFound => 127,
165 ExecutionExitCode::Interrupted => 130,
166 ExecutionExitCode::Custom(code) => *code,
167 }
168 }
169}
170
171#[derive(Clone, Copy, Default)]
173pub enum ExecutionControlFlow {
174 #[default]
176 Normal,
177 BreakLoop {
179 levels: usize,
182 },
183 ContinueLoop {
185 levels: usize,
188 },
189 ReturnFromFunctionOrScript,
191 ExitShell,
193}
194
195impl ExecutionControlFlow {
196 #[must_use]
200 pub const fn try_decrement_loop_levels(&self) -> Self {
201 match self {
202 Self::BreakLoop { levels: 0 } | Self::ContinueLoop { levels: 0 } => Self::Normal,
203 Self::BreakLoop { levels } => Self::BreakLoop {
204 levels: *levels - 1,
205 },
206 Self::ContinueLoop { levels } => Self::ContinueLoop {
207 levels: *levels - 1,
208 },
209 control_flow => *control_flow,
210 }
211 }
212}
213
214pub enum ExecutionSpawnResult {
218 Completed(ExecutionResult),
220 StartedProcess(processes::ChildProcess),
222}
223
224impl From<ExecutionResult> for ExecutionSpawnResult {
225 fn from(result: ExecutionResult) -> Self {
226 Self::Completed(result)
227 }
228}
229
230impl ExecutionSpawnResult {
231 pub async fn wait(
238 self,
239 no_wait: bool,
240 cancellation_token: Option<&CancellationToken>,
241 ) -> Result<ExecutionWaitResult, error::Error> {
242 match self {
243 Self::StartedProcess(mut child) => {
244 let process_wait_result = if !no_wait {
245 child.wait(cancellation_token).await?
248 } else {
249 processes::ProcessWaitResult::Stopped
250 };
251
252 let wait_result = match process_wait_result {
253 processes::ProcessWaitResult::Completed(output) => {
254 ExecutionWaitResult::Completed(ExecutionResult::from(output))
255 }
256 processes::ProcessWaitResult::Stopped => ExecutionWaitResult::Stopped(child),
257 processes::ProcessWaitResult::Cancelled => {
258 ExecutionWaitResult::Cancelled(child)
259 }
260 };
261
262 Ok(wait_result)
263 }
264 Self::Completed(result) => Ok(ExecutionWaitResult::Completed(result)),
265 }
266 }
267}
268
269pub enum ExecutionWaitResult {
271 Completed(ExecutionResult),
273 Stopped(processes::ChildProcess),
275 Cancelled(processes::ChildProcess),
277}
278
279#[derive(Clone, Copy, Debug, PartialEq, Eq)]
285pub enum OutputStreamType {
286 Stdout,
287 Stderr,
288}
289
290#[derive(Clone, Debug)]
292pub struct StreamingOutput {
293 pub stream: OutputStreamType,
294 pub data: Vec<u8>,
295}
296
297impl StreamingOutput {
298 pub fn stdout(data: Vec<u8>) -> Self {
299 Self { stream: OutputStreamType::Stdout, data }
300 }
301
302 pub fn stderr(data: Vec<u8>) -> Self {
303 Self { stream: OutputStreamType::Stderr, data }
304 }
305
306 pub fn is_stdout(&self) -> bool {
307 matches!(self.stream, OutputStreamType::Stdout)
308 }
309
310 pub fn is_stderr(&self) -> bool {
311 matches!(self.stream, OutputStreamType::Stderr)
312 }
313
314 pub fn as_str_lossy(&self) -> std::borrow::Cow<'_, str> {
315 String::from_utf8_lossy(&self.data)
316 }
317}