1mod extract_clap_info;
8mod markdown_formatter;
9mod test_doc_macro;
10
11use clap::{Command, Parser};
12use std::collections::BTreeMap;
13use std::fs::File;
14use std::io::Write;
15
16pub use mithril_doc_derive::{self, *};
17
18const DEFAULT_OUTPUT_FILE_TEMPLATE: &str = "[PROGRAM NAME]-command-line.md";
19
20#[derive(Clone, Default, Debug)]
22pub struct FieldDoc {
23 pub parameter: String,
25 pub command_line_long: String,
27 pub command_line_short: String,
29 pub environment_variable: Option<String>,
31 pub description: String,
33 pub default_value: Option<String>,
35 pub example: Option<String>,
37 pub is_mandatory: bool,
39}
40
41#[derive(Clone, Default, Debug)]
43pub struct StructDoc {
44 pub data: Vec<FieldDoc>,
46}
47
48impl StructDoc {
49 pub fn new() -> StructDoc {
51 StructDoc { data: vec![] }
52 }
53
54 pub fn add_param(
56 &mut self,
57 name: &str,
58 description: &str,
59 environment_variable: Option<String>,
60 default: Option<String>,
61 example: Option<String>,
62 ) {
63 let field_doc = FieldDoc {
64 parameter: name.to_string(),
65 command_line_long: "".to_string(),
66 command_line_short: "".to_string(),
67 environment_variable,
68 description: description.to_string(),
69 default_value: default,
70 example,
71 is_mandatory: false,
72 };
73 self.data.push(field_doc);
74 }
75
76 pub fn merge_struct_doc(&self, s2: &StructDoc) -> StructDoc {
78 let mut data_map1 = self
79 .data
80 .iter()
81 .map(|field_doc| (field_doc.parameter.clone(), field_doc.clone()))
82 .collect::<BTreeMap<_, _>>();
83
84 for field_doc in s2.data.iter() {
85 if !data_map1.contains_key(&field_doc.parameter) {
86 data_map1.insert(field_doc.parameter.clone(), field_doc.clone());
87 } else {
88 let mut d = data_map1.get(&field_doc.parameter).unwrap().clone();
89 if d.default_value.is_none() {
90 d.default_value.clone_from(&field_doc.default_value);
91 }
92 if d.example.is_none() {
93 d.example.clone_from(&field_doc.example);
94 }
95 if d.environment_variable.is_none() {
96 d.environment_variable
97 .clone_from(&field_doc.environment_variable);
98 }
99 data_map1.insert(field_doc.parameter.clone(), d);
100 }
101 }
102 let result = StructDoc {
103 data: data_map1.values().cloned().collect(),
104 };
105 result
106 }
107
108 pub fn get_field(&self, name: &str) -> &FieldDoc {
110 let mut fields = self.data.iter().filter(|f| f.parameter == name);
111
112 assert_eq!(1, fields.clone().count());
113 fields.next().unwrap()
114 }
115}
116
117pub trait Documenter {
119 fn extract() -> StructDoc;
121}
122
123pub trait DocumenterDefault {
125 fn extract() -> StructDoc;
127}
128
129#[derive(Parser, Debug, PartialEq, Clone)]
131pub struct GenerateDocCommands {
132 #[clap(long, default_value = DEFAULT_OUTPUT_FILE_TEMPLATE)]
134 output: String,
135}
136
137impl GenerateDocCommands {
138 fn save_doc(&self, cmd_name: &str, doc: &str) -> Result<(), String> {
139 let output = if self.output.as_str() == DEFAULT_OUTPUT_FILE_TEMPLATE {
140 format!("{}-command-line.md", cmd_name)
141 } else {
142 self.output.clone()
143 };
144
145 match File::create(&output) {
146 Ok(mut buffer) => {
147 if write!(buffer, "\n{}", doc).is_err() {
148 return Err(format!("Error writing in {}", output));
149 }
150 println!("Documentation generated in file `{}`", &output);
151 }
152 _ => return Err(format!("Could not create {}", output)),
153 };
154 Ok(())
155 }
156
157 pub fn execute(&self, cmd_to_document: &mut Command) -> Result<(), String> {
159 self.execute_with_configurations(cmd_to_document, &[])
160 }
161
162 pub fn execute_with_configurations(
164 &self,
165 cmd_to_document: &mut Command,
166 configs_info: &[StructDoc],
167 ) -> Result<(), String> {
168 let mut iter_config = configs_info.iter();
169 let mut merged_struct_doc = StructDoc::new();
170 for next_config in &mut iter_config {
171 merged_struct_doc = merged_struct_doc.merge_struct_doc(next_config);
172 }
173
174 let doc =
175 markdown_formatter::doc_markdown_with_config(cmd_to_document, Some(&merged_struct_doc));
176 let cmd_name = cmd_to_document.get_name();
177
178 println!("Please note: the documentation generated is not able to indicate the environment variables used by the commands.");
179 self.save_doc(cmd_name, format!("\n{}", doc).as_str())
180 }
181}
182
183#[cfg(test)]
184mod tests {
185
186 use std::collections::HashMap;
187
188 use super::*;
189
190 #[test]
191 fn test_merge_struct_doc() {
192 let s1 = {
193 let mut s = StructDoc::default();
194 s.add_param(
195 "A",
196 "Param first A",
197 Some("env A".to_string()),
198 Some("default A".to_string()),
199 Some("example A".to_string()),
200 );
201 s.add_param("B", "Param first B", None, None, None);
202 s.add_param(
203 "C",
204 "Param first C",
205 Some("env C".to_string()),
206 Some("default C".to_string()),
207 Some("example C".to_string()),
208 );
209 s.add_param("D", "Param first D", None, None, None);
210 s
211 };
212
213 let s2 = {
214 let mut s = StructDoc::default();
215 s.add_param("A", "Param second A", None, None, None);
216 s.add_param(
217 "B",
218 "Param second B",
219 Some("env B".to_string()),
220 Some("default B".to_string()),
221 Some("example B".to_string()),
222 );
223 s.add_param("E", "Param second E", None, None, None);
224 s.add_param(
225 "F",
226 "Param second F",
227 Some("env F".to_string()),
228 Some("default F".to_string()),
229 Some("example F".to_string()),
230 );
231 s
232 };
233
234 let result = s1.merge_struct_doc(&s2);
235
236 let data = result.data;
237 let data_map = data
238 .into_iter()
239 .map(|field_doc| (field_doc.parameter.clone(), field_doc))
240 .collect::<HashMap<_, _>>();
241
242 assert_eq!(6, data_map.len());
243 assert_eq!("Param first A", data_map.get("A").unwrap().description);
244 assert_eq!("Param first B", data_map.get("B").unwrap().description);
245 assert_eq!("Param first C", data_map.get("C").unwrap().description);
246 assert_eq!("Param first D", data_map.get("D").unwrap().description);
247 assert_eq!("Param second E", data_map.get("E").unwrap().description);
248 assert_eq!("Param second F", data_map.get("F").unwrap().description);
249
250 assert_eq!(
251 Some("default A".to_string()),
252 data_map.get("A").unwrap().default_value
253 );
254 assert_eq!(
255 Some("default B".to_string()),
256 data_map.get("B").unwrap().default_value
257 );
258 assert_eq!(
259 Some("default C".to_string()),
260 data_map.get("C").unwrap().default_value
261 );
262 assert_eq!(None, data_map.get("D").unwrap().default_value);
263 assert_eq!(None, data_map.get("E").unwrap().default_value);
264 assert_eq!(
265 Some("default F".to_string()),
266 data_map.get("F").unwrap().default_value
267 );
268
269 assert_eq!(
270 Some("example A".to_string()),
271 data_map.get("A").unwrap().example
272 );
273 assert_eq!(
274 Some("example B".to_string()),
275 data_map.get("B").unwrap().example
276 );
277 assert_eq!(
278 Some("example C".to_string()),
279 data_map.get("C").unwrap().example
280 );
281 assert_eq!(None, data_map.get("D").unwrap().example);
282 assert_eq!(None, data_map.get("E").unwrap().example);
283 assert_eq!(
284 Some("example F".to_string()),
285 data_map.get("F").unwrap().example
286 );
287
288 assert_eq!(
289 Some("env A".to_string()),
290 data_map.get("A").unwrap().environment_variable
291 );
292 assert_eq!(
293 Some("env B".to_string()),
294 data_map.get("B").unwrap().environment_variable
295 );
296 assert_eq!(
297 Some("env C".to_string()),
298 data_map.get("C").unwrap().environment_variable
299 );
300 assert_eq!(None, data_map.get("D").unwrap().environment_variable);
301 assert_eq!(None, data_map.get("E").unwrap().environment_variable);
302 assert_eq!(
303 Some("env F".to_string()),
304 data_map.get("F").unwrap().environment_variable
305 );
306 }
307
308 #[test]
309 fn test_merge_struct_doc_should_keep_the_order() {
310 let values = ["A", "B", "C", "D", "E", "F", "G"];
311 let s1 = {
312 let mut s = StructDoc::default();
313 for value in values.iter() {
314 s.add_param(value, value, None, None, None);
315 }
316 s
317 };
318
319 let s2 = s1.clone();
320
321 for (index, value) in values.iter().enumerate() {
322 assert_eq!(value, &s1.data[index].parameter);
323 assert_eq!(value, &s2.data[index].parameter);
324 }
325
326 let result = s1.merge_struct_doc(&s2);
327 for (index, value) in values.iter().enumerate() {
328 assert_eq!(value, &result.data[index].parameter);
329 }
330 }
331}