total_space/
claps.rs

1use crate::diagrams::*;
2use crate::models::*;
3use crate::utilities::*;
4
5use clap::App;
6use clap::Arg;
7use clap::ArgMatches;
8use clap::SubCommand;
9use std::io::Write;
10use std::str::FromStr;
11
12/// Add clap commands and flags to a clap application.
13pub fn add_clap<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> {
14    app.arg(
15        Arg::with_name("progress")
16            .short("p")
17            .long("progress-every")
18            .default_value("0")
19            .help("print configurations as they are reached"),
20    )
21    .arg(
22        Arg::with_name("size")
23            .short("s")
24            .long("size")
25            .value_name("COUNT")
26            .help(
27                "pre-allocate arrays to cover this number of configurations, for faster operation",
28            )
29            .default_value("AUTO"),
30    )
31    .arg(
32        Arg::with_name("reachable")
33            .short("r")
34            .long("reachable")
35            .help(
36                "ensure that the initial configuration is reachable from all other configurations",
37            ),
38    )
39    .arg(
40        Arg::with_name("invalid")
41            .short("i")
42            .long("invalid")
43            .help("allow for invalid configurations (but do not explore beyond them)"),
44    )
45    .subcommand(
46        SubCommand::with_name("agents")
47            .about("list the agents of the model (does not compute the model)"),
48    )
49    .subcommand(SubCommand::with_name("conditions").about(
50        "list the conditions which can be used to identify configurations \
51                   (does not compute the model)",
52    ))
53    .subcommand(
54        SubCommand::with_name("compute").about("only compute the model and print basic statistics"),
55    )
56    .subcommand(
57        SubCommand::with_name("configurations").about("list the configurations of the model"),
58    )
59    .subcommand(SubCommand::with_name("transitions").about("list the transitions of the model"))
60    .subcommand(
61        SubCommand::with_name("path")
62            .about("list transitions for a path between configurations")
63            .arg(Arg::with_name("CONDITION").multiple(true).help(
64                "the name of at least two conditions identifying configurations along the path, \
65                          which may be prefixed with ! to negate the condition",
66            )),
67    )
68    .subcommand(
69        SubCommand::with_name("sequence")
70            .about("generate a PlantUML sequence diagram for a path between configurations")
71            .arg(Arg::with_name("CONDITION").multiple(true).help(
72                "the name of at least two conditions identifying configurations along the path, \
73                          which may be prefixed with ! to negate the condition",
74            )),
75    )
76    .subcommand(
77        SubCommand::with_name("states")
78            .about("generate a GraphViz dot diagrams for the states of a specific agent")
79            .arg(
80                Arg::with_name("AGENT")
81                    .help("the name of the agent to generate a diagrams for the states of"),
82            )
83            .arg(
84                Arg::with_name("names-only")
85                    .short("n")
86                    .long("names-only")
87                    .help("condense graph nodes considering only the state & payload names"),
88            )
89            .arg(
90                Arg::with_name("merge-instances")
91                    .short("m")
92                    .long("merge-instances")
93                    .help("condense graph nodes considering only the agent type"),
94            )
95            .arg(
96                Arg::with_name("final-replaced")
97                    .short("f")
98                    .long("final-replaced")
99                    .help("condense graph nodes considering only the final (replaced) payload"),
100            )
101            .arg(
102                Arg::with_name("condensed")
103                    .short("c")
104                    .long("condensed")
105                    .help("most condensed graph (implies --names-only, --merge-instances and --final-replaced)"),
106            ),
107    )
108}
109
110/// Execute operations on a model using clap commands.
111pub trait ClapModel {
112    /// Execute the chosen clap subcommand.
113    ///
114    /// Return whether a command was executed.
115    fn do_clap(&mut self, arg_matches: &ArgMatches, stdout: &mut dyn Write) {
116        let did_clap = self.do_clap_agents(arg_matches, stdout)
117            || self.do_clap_conditions(arg_matches, stdout)
118            || self.do_clap_compute(arg_matches, stdout)
119            || self.do_clap_configurations(arg_matches, stdout)
120            || self.do_clap_transitions(arg_matches, stdout)
121            || self.do_clap_path(arg_matches, stdout)
122            || self.do_clap_sequence(arg_matches, stdout)
123            || self.do_clap_states(arg_matches, stdout);
124        assert!(
125            did_clap,
126            "no command specified; use --help to list the commands"
127        );
128    }
129
130    /// Compute the model.
131    fn do_compute(&mut self, arg_matches: &ArgMatches);
132
133    /// Execute the `agents` clap subcommand, if requested to.
134    ///
135    /// This doesn't compute the model.
136    fn do_clap_agents(&mut self, arg_matches: &ArgMatches, stdout: &mut dyn Write) -> bool;
137
138    /// Execute the `conditions` clap subcommand, if requested to.
139    ///
140    /// This doesn't compute the model.
141    fn do_clap_conditions(&mut self, arg_matches: &ArgMatches, stdout: &mut dyn Write) -> bool;
142
143    /// Only compute the model (no output).
144    fn do_clap_compute(&mut self, arg_matches: &ArgMatches, stdout: &mut dyn Write) -> bool;
145
146    /// Execute the `configurations` clap subcommand, if requested to.
147    ///
148    /// This computes the model.
149    fn do_clap_configurations(&mut self, arg_matches: &ArgMatches, stdout: &mut dyn Write) -> bool;
150
151    /// Execute the `transitions` clap subcommand, if requested to.
152    ///
153    /// This computes the model.
154    fn do_clap_transitions(&mut self, arg_matches: &ArgMatches, stdout: &mut dyn Write) -> bool;
155
156    /// Execute the `path` clap subcommand, if requested to.
157    fn do_clap_path(&mut self, arg_matches: &ArgMatches, stdout: &mut dyn Write) -> bool;
158
159    /// Execute the `sequence` clap subcommand, if requested to.
160    fn do_clap_sequence(&mut self, arg_matches: &ArgMatches, stdout: &mut dyn Write) -> bool;
161
162    /// Execute the `states` clap subcommand, if requested to.
163    fn do_clap_states(&mut self, arg_matches: &ArgMatches, stdout: &mut dyn Write) -> bool;
164}
165
166impl<
167        StateId: IndexLike,
168        MessageId: IndexLike,
169        InvalidId: IndexLike,
170        ConfigurationId: IndexLike,
171        Payload: DataLike,
172        const MAX_AGENTS: usize,
173        const MAX_MESSAGES: usize,
174    > ClapModel
175    for Model<StateId, MessageId, InvalidId, ConfigurationId, Payload, MAX_AGENTS, MAX_MESSAGES>
176{
177    fn do_compute(&mut self, arg_matches: &ArgMatches) {
178        let progress_every = arg_matches.value_of("progress").unwrap();
179        self.print_progress_every = usize::from_str(progress_every).expect("invalid progress rate");
180        self.allow_invalid_configurations = arg_matches.is_present("invalid");
181        self.ensure_init_is_reachable = arg_matches.is_present("reachable");
182
183        self.compute();
184
185        if self.ensure_init_is_reachable {
186            self.assert_init_is_reachable();
187        }
188    }
189
190    fn do_clap_agents(&mut self, arg_matches: &ArgMatches, stdout: &mut dyn Write) -> bool {
191        match arg_matches.subcommand_matches("agents") {
192            Some(_) => {
193                self.print_agents(stdout);
194                true
195            }
196            None => false,
197        }
198    }
199
200    fn do_clap_conditions(&mut self, arg_matches: &ArgMatches, stdout: &mut dyn Write) -> bool {
201        match arg_matches.subcommand_matches("conditions") {
202            Some(_) => {
203                self.print_conditions(stdout);
204                true
205            }
206            None => false,
207        }
208    }
209
210    fn do_clap_compute(&mut self, arg_matches: &ArgMatches, stdout: &mut dyn Write) -> bool {
211        match arg_matches.subcommand_matches("compute") {
212            Some(_) => {
213                self.do_compute(arg_matches);
214                self.print_stats(stdout);
215                true
216            }
217            None => false,
218        }
219    }
220
221    fn do_clap_configurations(&mut self, arg_matches: &ArgMatches, stdout: &mut dyn Write) -> bool {
222        match arg_matches.subcommand_matches("configurations") {
223            Some(_) => {
224                self.do_compute(arg_matches);
225                self.print_configurations(stdout);
226                true
227            }
228            None => false,
229        }
230    }
231
232    fn do_clap_transitions(&mut self, arg_matches: &ArgMatches, stdout: &mut dyn Write) -> bool {
233        match arg_matches.subcommand_matches("transitions") {
234            Some(_) => {
235                self.do_compute(arg_matches);
236                self.print_transitions(stdout);
237                true
238            }
239            None => false,
240        }
241    }
242
243    fn do_clap_path(&mut self, arg_matches: &ArgMatches, stdout: &mut dyn Write) -> bool {
244        match arg_matches.subcommand_matches("path") {
245            Some(matches) => {
246                let steps = self.collect_steps("path", matches);
247                self.do_compute(arg_matches);
248                let path = self.collect_path(steps);
249                self.print_path(&path, stdout);
250                true
251            }
252            None => false,
253        }
254    }
255
256    fn do_clap_sequence(&mut self, arg_matches: &ArgMatches, stdout: &mut dyn Write) -> bool {
257        match arg_matches.subcommand_matches("sequence") {
258            Some(matches) => {
259                let steps = self.collect_steps("sequence", matches);
260                self.do_compute(arg_matches);
261                let path = self.collect_path(steps);
262                let mut sequence_steps = self.collect_sequence_steps(&path[1..]);
263                let first_configuration_id = path[1].from_configuration_id;
264                let last_configuration_id = path.last().unwrap().to_configuration_id;
265                self.print_sequence_diagram(
266                    first_configuration_id,
267                    last_configuration_id,
268                    &mut sequence_steps,
269                    stdout,
270                );
271                true
272            }
273            None => false,
274        }
275    }
276
277    fn do_clap_states(&mut self, arg_matches: &ArgMatches, stdout: &mut dyn Write) -> bool {
278        match arg_matches.subcommand_matches("states") {
279            Some(matches) => {
280                let agent_label = matches
281                    .value_of("AGENT")
282                    .expect("the states command requires a single agent name, none were given");
283                let condense = Condense {
284                    names_only: matches.is_present("names-only") || matches.is_present("condensed"),
285                    merge_instances: matches.is_present("merge-instances")
286                        || matches.is_present("condensed"),
287                    final_replaced: matches.is_present("final-replaced")
288                        || matches.is_present("condensed"),
289                };
290                let agent_index = self
291                    .agent_label_index(agent_label)
292                    .unwrap_or_else(|| panic!("unknown agent {}", agent_label));
293
294                self.do_compute(arg_matches);
295                self.print_states_diagram(&condense, agent_index, stdout);
296
297                true
298            }
299            None => false, // NOT TESTED
300        }
301    }
302}