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
12pub 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
110pub trait ClapModel {
112 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 fn do_compute(&mut self, arg_matches: &ArgMatches);
132
133 fn do_clap_agents(&mut self, arg_matches: &ArgMatches, stdout: &mut dyn Write) -> bool;
137
138 fn do_clap_conditions(&mut self, arg_matches: &ArgMatches, stdout: &mut dyn Write) -> bool;
142
143 fn do_clap_compute(&mut self, arg_matches: &ArgMatches, stdout: &mut dyn Write) -> bool;
145
146 fn do_clap_configurations(&mut self, arg_matches: &ArgMatches, stdout: &mut dyn Write) -> bool;
150
151 fn do_clap_transitions(&mut self, arg_matches: &ArgMatches, stdout: &mut dyn Write) -> bool;
155
156 fn do_clap_path(&mut self, arg_matches: &ArgMatches, stdout: &mut dyn Write) -> bool;
158
159 fn do_clap_sequence(&mut self, arg_matches: &ArgMatches, stdout: &mut dyn Write) -> bool;
161
162 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, }
301 }
302}