mithril_aggregator/commands/
config_association.rs

1/// Call `extract_config` on each SubCommand so it could not be forget to implement it.
2/// All variant must be listed otherwise there is a compilation error.
3/// The associated command to the variant must be the right one otherwise there is a compilation error.
4///
5/// # Example
6///
7/// ```ignore
8/// # use std::collections::HashMap;
9/// # use mithril_doc::StructDoc;
10/// # use mithril_aggregator::extract_all;
11/// fn extract_config(command_path: String) -> HashMap<String, StructDoc> {
12///     extract_all!(
13///         command_path,
14///         PseudoCommand,
15///         CommandA = { PseudoCommandA },
16///         CommandB = { pseudo_module::PseudoCommandB },
17///     )
18/// }
19/// ```
20#[macro_export]
21macro_rules! extract_all {
22    (@check_type $variant_val:ident, { $type:ident }) => {
23        {let _:$type = $variant_val;}
24    };
25    (@check_type $variant_val:ident, { $module:ident::$type:ident }) => {
26        {let _:$module::$type = $variant_val;}
27    };
28    (@check_type $variant_val:ident, {}) => {
29        let _ = $variant_val;
30    };
31
32    (@extract_config $config_id:ident, { $type:ident }) => {
33        $type::extract_config($config_id)
34    };
35    (@extract_config $config_id:ident, { $module:ident::$type:ident }) => {
36        $module::$type::extract_config($config_id)
37    };
38    (@extract_config $config_id:ident, {}) => {
39        {
40            let _ = $config_id;
41            std::collections::HashMap::new()
42        }
43    };
44
45    ($command_path: ident, $E:path, $($variant:ident = $cmd:tt,)*) => {
46        {
47            // Check that all enum variants are used and the associated variant value
48            use $E as E;
49            let _ = |dummy: E| {
50                match dummy {
51                    $(E::$variant(x) => {
52                        extract_all!(@check_type x, $cmd);
53                    }),*
54                }
55            };
56
57            // Build config HashMap
58            let mut configs = HashMap::new();
59            $(
60                let config_id = format!(
61                    "{} {}",
62                    $command_path, stringify!($variant).to_lowercase()
63                );
64                configs.extend(extract_all!(@extract_config config_id, $cmd));
65            )*
66            configs
67        }
68
69    };
70}
71
72#[cfg(test)]
73mod tests {
74    use std::collections::HashMap;
75
76    use mithril_common::assert_equivalent_macro;
77    use mithril_doc::StructDoc;
78
79    #[allow(dead_code)]
80    #[derive(Debug, Clone)]
81    enum PseudoCommand {
82        CommandA(PseudoCommandA),
83        CommandB(pseudo_module::PseudoCommandB),
84        CommandWithoutConfig(PseudoCommandWithoutConfig),
85        CommandConfigUnwanted(PseudoCommandConfigUnwanted),
86        CommandE(EnumCommandE),
87    }
88
89    #[derive(Debug, Clone)]
90    struct PseudoCommandA {}
91    impl PseudoCommandA {
92        pub fn extract_config(command_path: String) -> HashMap<String, StructDoc> {
93            let mut struct_doc = StructDoc::default();
94            struct_doc.add_param("field_command_a", "", None, None, None, true);
95            HashMap::from([(command_path, struct_doc)])
96        }
97    }
98
99    #[derive(Debug, Clone)]
100    struct PseudoCommandWithoutConfig {}
101    impl PseudoCommandWithoutConfig {
102        pub fn extract_config(_command_path: String) -> HashMap<String, StructDoc> {
103            HashMap::new()
104        }
105    }
106
107    #[derive(Debug, Clone)]
108    struct PseudoCommandConfigUnwanted {}
109
110    #[allow(dead_code)]
111    #[derive(Debug, Clone)]
112    enum EnumCommandE {
113        SubCommandE(PseudoSubCommandE),
114    }
115
116    impl EnumCommandE {
117        pub fn extract_config(command_path: String) -> HashMap<String, StructDoc> {
118            extract_all!(
119                command_path,
120                EnumCommandE,
121                SubCommandE = { PseudoSubCommandE },
122            )
123        }
124    }
125
126    #[derive(Debug, Clone)]
127    struct PseudoSubCommandE {}
128    impl PseudoSubCommandE {
129        pub fn extract_config(command_path: String) -> HashMap<String, StructDoc> {
130            let mut struct_doc = StructDoc::default();
131            struct_doc.add_param("field_sub_command_e", "", None, None, None, true);
132            HashMap::from([(command_path, struct_doc)])
133        }
134    }
135
136    mod pseudo_module {
137        use super::*;
138
139        #[derive(Debug, Clone)]
140        pub struct PseudoCommandB {}
141        impl PseudoCommandB {
142            pub fn extract_config(command_path: String) -> HashMap<String, StructDoc> {
143                let mut struct_doc = StructDoc::default();
144                struct_doc.add_param("field_command_b", "", None, None, None, true);
145                HashMap::from([(command_path, struct_doc)])
146            }
147        }
148    }
149
150    #[test]
151    fn test_extract_all_should_construct_hashmap_with_subcommand() {
152        let command_path = "mithril".to_string();
153        let configs = extract_all!(
154            command_path,
155            PseudoCommand,
156            CommandA = { PseudoCommandA },
157            CommandB = { pseudo_module::PseudoCommandB },
158            CommandWithoutConfig = { PseudoCommandWithoutConfig },
159            CommandConfigUnwanted = {},
160            CommandE = { EnumCommandE },
161        );
162
163        let keys: Vec<String> = configs.clone().into_keys().collect();
164        let expected = vec![
165            "mithril commanda".to_string(),
166            "mithril commandb".to_string(),
167            "mithril commande subcommande".to_string(),
168        ];
169
170        assert_equivalent_macro!(expected, keys);
171    }
172
173    #[test]
174    fn test_extract_all_should_associate_struct_doc() {
175        let command_path = "mithril".to_string();
176        let configs = extract_all!(
177            command_path,
178            PseudoCommand,
179            CommandA = { PseudoCommandA },
180            CommandB = { pseudo_module::PseudoCommandB },
181            CommandWithoutConfig = { PseudoCommandWithoutConfig },
182            CommandConfigUnwanted = {},
183            CommandE = { EnumCommandE },
184        );
185        let doc_command_b = configs.get("mithril commandb").unwrap();
186        assert!(doc_command_b.get_field("field_command_b").is_some());
187        assert_eq!(1, doc_command_b.get_ordered_data().len());
188
189        let doc_sub_command_e = configs.get("mithril commande subcommande").unwrap();
190        assert!(doc_sub_command_e.get_field("field_sub_command_e").is_some());
191        assert_eq!(1, doc_sub_command_e.get_ordered_data().len());
192    }
193}