1use crate::error::{NounVerbError, Result};
4use crate::noun::NounCommand;
5use crate::verb::{VerbArgs, VerbContext};
6use clap::{ArgMatches, Command};
7use std::collections::HashMap;
8
9pub struct CommandRouter {
11 nouns: HashMap<String, Box<dyn NounCommand>>,
12}
13
14impl CommandRouter {
15 pub fn new() -> Self {
17 Self { nouns: HashMap::new() }
18 }
19
20 pub fn register_noun(&mut self, noun: Box<dyn NounCommand>) {
22 self.nouns.insert(noun.name().to_string(), noun);
23 }
24
25 pub fn route(&self, matches: &ArgMatches) -> Result<()> {
27 let (noun_name, noun_matches) = matches
29 .subcommand()
30 .ok_or_else(|| NounVerbError::invalid_structure("No subcommand found"))?;
31
32 let noun =
34 self.nouns.get(noun_name).ok_or_else(|| NounVerbError::command_not_found(noun_name))?;
35
36 self.route_recursive(noun.as_ref(), noun_name, noun_matches, matches)
38 }
39
40 #[allow(clippy::only_used_in_recursion)]
42 fn route_recursive(
43 &self,
44 noun: &dyn NounCommand,
45 noun_name: &str,
46 matches: &ArgMatches,
47 root_matches: &ArgMatches,
48 ) -> Result<()> {
49 if let Some((sub_name, sub_matches)) = matches.subcommand() {
51 if let Some(verb) = noun.verbs().iter().find(|v| v.name() == sub_name) {
53 let context = VerbContext::new(sub_name).with_noun(noun_name);
55 let args = VerbArgs::new(sub_matches.clone())
56 .with_parent(root_matches.clone())
57 .with_context(context);
58
59 verb.run(&args)
60 } else if let Some(sub_noun) = noun.sub_nouns().iter().find(|n| n.name() == sub_name) {
61 self.route_recursive(sub_noun.as_ref(), sub_name, sub_matches, root_matches)
63 } else {
64 Err(NounVerbError::verb_not_found(noun_name, sub_name))
66 }
67 } else {
68 let context = VerbContext::new("").with_noun(noun_name);
70 let args = VerbArgs::new(matches.clone()).with_context(context);
71
72 noun.handle_direct(&args)
73 }
74 }
75
76 pub fn build_command(&self, app_name: &'static str, about: &'static str) -> Command {
78 let mut cmd = Command::new(app_name).about(about);
79
80 for noun in self.nouns.values() {
81 cmd = cmd.subcommand(noun.build_command());
82 }
83
84 cmd
85 }
86
87 pub fn noun_names(&self) -> Vec<&str> {
89 self.nouns.keys().map(|s| s.as_str()).collect()
90 }
91
92 pub fn get_verbs(&self, noun_name: &str) -> Result<Vec<String>> {
94 let noun =
95 self.nouns.get(noun_name).ok_or_else(|| NounVerbError::command_not_found(noun_name))?;
96
97 Ok(noun.verbs().iter().map(|v| v.name().to_string()).collect())
98 }
99}
100
101impl Default for CommandRouter {
102 fn default() -> Self {
103 Self::new()
104 }
105}