ascetic_cli/
go.rs

1use std::error::Error;
2use ascesis::{Contextual, Runner, StopCondition, Multiplicity};
3use super::{App, Command, Solve, Styled};
4
5pub struct Go {
6    solve_command:  Solve,
7    start_triggers: Vec<(String, Multiplicity)>,
8    stop_triggers:  Vec<(String, Multiplicity)>,
9}
10
11impl Go {
12    fn trigger_parse<S: AsRef<str>>(name: S) -> (String, Multiplicity) {
13        let name = name.as_ref();
14
15        if let Some(pos) = name.rfind(':') {
16            if pos > 0 && pos < name.len() {
17                if let Ok(weight) = name[pos + 1..].parse::<Multiplicity>() {
18                    return (name[..pos].to_owned(), weight)
19                }
20            }
21        }
22
23        (name.to_owned(), Multiplicity::one())
24    }
25
26    pub(crate) fn new(app: &mut App) -> Self {
27        let solve_command = Solve::new(app);
28        let mut start_triggers = Vec::new();
29        let mut stop_triggers = Vec::new();
30
31        if let Some(values) = app.values_of("START") {
32            start_triggers.extend(values.map(Self::trigger_parse));
33        }
34
35        if let Some(values) = app.values_of("GOAL") {
36            stop_triggers.extend(values.map(Self::trigger_parse));
37        }
38
39        app.apply_props(solve_command.get_context());
40        app.accept_selectors(&["SEMANTICS", "MAX_STEPS"]);
41
42        Self { solve_command, start_triggers, stop_triggers }
43    }
44
45    /// Creates a [`Go`] instance and returns it as a [`Command`]
46    /// trait object.
47    ///
48    /// The [`App`] argument is modified, because [`Go`] is a
49    /// top-level [`Command`] which accepts a set of CLI selectors
50    /// (see [`App::accept_selectors()`]) and specifies an application
51    /// mode.
52    pub fn new_command(app: &mut App) -> Box<dyn Command> {
53        app.set_mode("Go");
54
55        Box::new(Self::new(app))
56    }
57}
58
59impl Command for Go {
60    fn name_of_log_file(&self) -> String {
61        self.solve_command.name_of_log_file()
62    }
63
64    fn console_level(&self) -> Option<log::LevelFilter> {
65        self.solve_command.console_level()
66    }
67
68    fn run(&mut self) -> Result<(), Box<dyn Error>> {
69        self.solve_command.run()?;
70
71        let ces = self.solve_command.get_ces();
72
73        if let Some(fset) = ces.get_firing_set() {
74            let mut runner = Runner::new(
75                ces.get_context(),
76                self.start_triggers.iter().map(|(name, mul)| (name, *mul)),
77            );
78
79            if !self.stop_triggers.is_empty() {
80                runner =
81                    runner.with_goal(self.stop_triggers.iter().map(|(name, mul)| (name, *mul)))?;
82            }
83
84            println!("{}", "Go from".bright_green().bold());
85            println!("{} {}", "=>".bright_yellow().bold(), runner.get_initial_state());
86
87            let stop_condition = runner.go(fset)?;
88
89            let fcs = runner.get_firing_sequence();
90            let mut state = runner.get_initial_state().clone();
91
92            for (i, fc) in fcs.iter(fset).enumerate() {
93                if i > 0 {
94                    println!("{} {}", "=>".bright_yellow().bold(), state);
95                }
96                println!(
97                    "{}. {}",
98                    format!("{:4}", (i + 1)).bright_yellow().bold(),
99                    fc.with(ces.get_context())
100                );
101
102                fc.fire(&mut state)?;
103            }
104
105            if let Some(num_steps) = match stop_condition {
106                StopCondition::GoalReached(node_id, num_steps) => {
107                    print!(
108                        "{} reached (node \"{}\")",
109                        "Goal".bright_cyan().bold(),
110                        node_id.with(ces.get_context()),
111                    );
112                    Some(num_steps)
113                }
114                StopCondition::Stalemate(num_steps) => {
115                    print!("{}", "Stuck".bright_red().bold());
116                    Some(num_steps)
117                }
118                StopCondition::Pause(num_steps) => {
119                    print!("{}", "Paused".bright_green().bold());
120                    Some(num_steps)
121                }
122                StopCondition::UnimplementedFeature(feature) => {
123                    println!(
124                        "{}: {} isn't implemented yet.",
125                        "Failed".bright_red().bold(),
126                        feature
127                    );
128                    None
129                }
130            } {
131                println!(" after {} steps at", num_steps);
132                println!("{} {}", "=>".bright_yellow().bold(), state);
133            }
134        } else {
135            println!("{}.", "Structural deadlock".bright_red().bold());
136        }
137
138        Ok(())
139    }
140}