Skip to main content

oxihuman_core/
plan_executor.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5/// Simple sequential plan executor with named steps.
6#[allow(dead_code)]
7#[derive(Debug, Clone, PartialEq)]
8pub enum StepState {
9    Pending,
10    Running,
11    Done,
12    Failed(String),
13    Skipped,
14}
15
16#[allow(dead_code)]
17#[derive(Debug, Clone)]
18pub struct PlanStep {
19    pub name: String,
20    pub state: StepState,
21    pub duration_ms: u32,
22}
23
24#[allow(dead_code)]
25pub struct PlanExecutor {
26    steps: Vec<PlanStep>,
27    current: usize,
28    abort_on_failure: bool,
29    aborted: bool,
30}
31
32#[allow(dead_code)]
33impl PlanExecutor {
34    pub fn new(abort_on_failure: bool) -> Self {
35        Self {
36            steps: Vec::new(),
37            current: 0,
38            abort_on_failure,
39            aborted: false,
40        }
41    }
42    pub fn add_step(&mut self, name: &str) {
43        self.steps.push(PlanStep {
44            name: name.to_string(),
45            state: StepState::Pending,
46            duration_ms: 0,
47        });
48    }
49    pub fn complete_current(&mut self, duration_ms: u32) -> bool {
50        if self.current >= self.steps.len() {
51            return false;
52        }
53        self.steps[self.current].state = StepState::Done;
54        self.steps[self.current].duration_ms = duration_ms;
55        self.current += 1;
56        true
57    }
58    pub fn fail_current(&mut self, reason: &str) -> bool {
59        if self.current >= self.steps.len() {
60            return false;
61        }
62        self.steps[self.current].state = StepState::Failed(reason.to_string());
63        self.current += 1;
64        if self.abort_on_failure {
65            self.aborted = true;
66        }
67        true
68    }
69    pub fn skip_current(&mut self) -> bool {
70        if self.current >= self.steps.len() {
71            return false;
72        }
73        self.steps[self.current].state = StepState::Skipped;
74        self.current += 1;
75        true
76    }
77    pub fn is_complete(&self) -> bool {
78        !self.aborted && self.current >= self.steps.len()
79    }
80    pub fn is_aborted(&self) -> bool {
81        self.aborted
82    }
83    pub fn step_count(&self) -> usize {
84        self.steps.len()
85    }
86    pub fn done_count(&self) -> usize {
87        self.steps
88            .iter()
89            .filter(|s| s.state == StepState::Done)
90            .count()
91    }
92    pub fn failed_count(&self) -> usize {
93        self.steps
94            .iter()
95            .filter(|s| matches!(s.state, StepState::Failed(_)))
96            .count()
97    }
98    pub fn pending_count(&self) -> usize {
99        self.steps
100            .iter()
101            .filter(|s| s.state == StepState::Pending)
102            .count()
103    }
104    pub fn current_step(&self) -> Option<&PlanStep> {
105        self.steps.get(self.current)
106    }
107    pub fn steps(&self) -> &[PlanStep] {
108        &self.steps
109    }
110    pub fn total_duration_ms(&self) -> u32 {
111        self.steps.iter().map(|s| s.duration_ms).sum()
112    }
113    pub fn reset(&mut self) {
114        for s in &mut self.steps {
115            s.state = StepState::Pending;
116            s.duration_ms = 0;
117        }
118        self.current = 0;
119        self.aborted = false;
120    }
121}
122
123#[allow(dead_code)]
124pub fn new_plan_executor(abort_on_failure: bool) -> PlanExecutor {
125    PlanExecutor::new(abort_on_failure)
126}
127#[allow(dead_code)]
128pub fn pe_add_step(e: &mut PlanExecutor, name: &str) {
129    e.add_step(name);
130}
131#[allow(dead_code)]
132pub fn pe_complete(e: &mut PlanExecutor, ms: u32) -> bool {
133    e.complete_current(ms)
134}
135#[allow(dead_code)]
136pub fn pe_fail(e: &mut PlanExecutor, reason: &str) -> bool {
137    e.fail_current(reason)
138}
139#[allow(dead_code)]
140pub fn pe_skip(e: &mut PlanExecutor) -> bool {
141    e.skip_current()
142}
143#[allow(dead_code)]
144pub fn pe_is_complete(e: &PlanExecutor) -> bool {
145    e.is_complete()
146}
147#[allow(dead_code)]
148pub fn pe_is_aborted(e: &PlanExecutor) -> bool {
149    e.is_aborted()
150}
151#[allow(dead_code)]
152pub fn pe_done_count(e: &PlanExecutor) -> usize {
153    e.done_count()
154}
155#[allow(dead_code)]
156pub fn pe_failed_count(e: &PlanExecutor) -> usize {
157    e.failed_count()
158}
159#[allow(dead_code)]
160pub fn pe_step_count(e: &PlanExecutor) -> usize {
161    e.step_count()
162}
163#[allow(dead_code)]
164pub fn pe_total_ms(e: &PlanExecutor) -> u32 {
165    e.total_duration_ms()
166}
167#[allow(dead_code)]
168pub fn pe_reset(e: &mut PlanExecutor) {
169    e.reset();
170}
171
172#[cfg(test)]
173mod tests {
174    use super::*;
175    #[test]
176    fn test_complete_all() {
177        let mut e = new_plan_executor(true);
178        pe_add_step(&mut e, "init");
179        pe_add_step(&mut e, "build");
180        pe_complete(&mut e, 10);
181        pe_complete(&mut e, 20);
182        assert!(pe_is_complete(&e));
183        assert_eq!(pe_done_count(&e), 2);
184    }
185    #[test]
186    fn test_fail_aborts() {
187        let mut e = new_plan_executor(true);
188        pe_add_step(&mut e, "step1");
189        pe_fail(&mut e, "error");
190        assert!(pe_is_aborted(&e));
191    }
192    #[test]
193    fn test_fail_no_abort() {
194        let mut e = new_plan_executor(false);
195        pe_add_step(&mut e, "step1");
196        pe_fail(&mut e, "error");
197        assert!(!pe_is_aborted(&e));
198        assert_eq!(pe_failed_count(&e), 1);
199    }
200    #[test]
201    fn test_skip() {
202        let mut e = new_plan_executor(true);
203        pe_add_step(&mut e, "s");
204        pe_skip(&mut e);
205        assert!(pe_is_complete(&e));
206    }
207    #[test]
208    fn test_total_duration() {
209        let mut e = new_plan_executor(true);
210        pe_add_step(&mut e, "a");
211        pe_add_step(&mut e, "b");
212        pe_complete(&mut e, 100);
213        pe_complete(&mut e, 200);
214        assert_eq!(pe_total_ms(&e), 300);
215    }
216    #[test]
217    fn test_current_step() {
218        let mut e = new_plan_executor(true);
219        pe_add_step(&mut e, "first");
220        assert_eq!(e.current_step().map(|s| s.name.as_str()), Some("first"));
221        pe_complete(&mut e, 0);
222        assert!(e.current_step().is_none());
223    }
224    #[test]
225    fn test_pending_count() {
226        let mut e = new_plan_executor(true);
227        pe_add_step(&mut e, "a");
228        pe_add_step(&mut e, "b");
229        pe_add_step(&mut e, "c");
230        pe_complete(&mut e, 0);
231        assert_eq!(e.pending_count(), 2);
232    }
233    #[test]
234    fn test_reset() {
235        let mut e = new_plan_executor(true);
236        pe_add_step(&mut e, "x");
237        pe_complete(&mut e, 5);
238        pe_reset(&mut e);
239        assert_eq!(pe_done_count(&e), 0);
240        assert!(!pe_is_complete(&e));
241    }
242    #[test]
243    fn test_step_count() {
244        let mut e = new_plan_executor(false);
245        pe_add_step(&mut e, "a");
246        pe_add_step(&mut e, "b");
247        assert_eq!(pe_step_count(&e), 2);
248    }
249    #[test]
250    fn test_empty_complete_immediately() {
251        let e = new_plan_executor(true);
252        assert!(pe_is_complete(&e));
253    }
254}