help_crafter/
builder.rs

1use crate::enums::{Parameter, DASHED};
2use std::collections::HashMap;
3
4/// Unit of help message
5#[derive(Default, Clone, Debug)]
6pub struct Command<'a> {
7    pub long_command: String,
8    pub short_command: String,
9    pub description: String,
10    pub parameter_name: Parameter<'a>,
11    pub dash_status: DASHED,
12}
13
14impl<'a> Command<'a> {
15    pub fn new(
16        long_command: String,
17        short_command: String,
18        description: String,
19        parameter_name: Parameter<'a>,
20        dash_status: DASHED,
21    ) -> Self {
22        Self {
23            long_command,
24            short_command,
25            description,
26            parameter_name,
27            dash_status,
28        }
29    }
30}
31
32/// Structure for building help message
33#[derive(Debug, Default)]
34pub struct HelpMessageBuilder<'a> {
35    commands: Vec<Command<'a>>,
36}
37
38impl<'a> HelpMessageBuilder<'a> {
39    /// Add command to help message.
40    pub fn command(
41        mut self,
42        short_command: &str,
43        long_command: &str,
44        parameter_name: Parameter<'a>,
45        description: &str,
46        dash_status: DASHED,
47    ) -> Self {
48        self.commands.push(Command::new(
49            long_command.to_owned(),
50            short_command.to_owned(),
51            description.to_owned(),
52            parameter_name,
53            dash_status,
54        ));
55        self
56    }
57    fn max_width(commands: &Vec<Command<'a>>) -> HashMap<&'a str, usize> {
58        let mut result = HashMap::new();
59        result.insert("short", 0);
60        result.insert("long", 0);
61        result.insert("description", 0);
62        result.insert("parameter_name", 0);
63
64        for command in commands {
65            if command.short_command.len() > *result.get("short").unwrap() {
66                *result.get_mut("short").unwrap() = command.short_command.len();
67            }
68            if command.long_command.len() > *result.get("long").unwrap() {
69                *result.get_mut("long").unwrap() = command.long_command.len();
70            }
71            if command.description.len() > *result.get("description").unwrap() {
72                *result.get_mut("description").unwrap() = command.description.len();
73            }
74            if command.parameter_name.get_len() > *result.get("parameter_name").unwrap() {
75                *result.get_mut("parameter_name").unwrap() = command.parameter_name.get_len();
76            }
77        }
78        *result.get_mut("short").unwrap() += 1;
79        *result.get_mut("long").unwrap() += 2;
80        *result.get_mut("parameter_name").unwrap() += 2;
81
82        result
83    }
84
85    fn field_wrapper(
86        max_widths: &HashMap<&str, usize>,
87        field: &str,
88        max_character_number: usize,
89    ) -> String {
90        let mut result = String::with_capacity(field.len() + max_character_number * 3);
91        let mut line_limit = max_character_number;
92        let mut previous_index = 0;
93        let mut current_index = line_limit;
94        let spaces = max_widths["short"] + max_widths["long"] + max_widths["parameter_name"] + 8;
95        if line_limit >= field.len() {
96            result.push_str(field);
97        }
98        'outer: while line_limit < field.len() {
99            while current_index >= previous_index {
100                if current_index >= field.len() {
101                    result.push_str(&format!(
102                        "{:<spaces$}{}",
103                        " ",
104                        &field[previous_index..field.len()].trim()
105                    ));
106                    break 'outer;
107                }
108                if &field[current_index..current_index + 1] == " " {
109                    if previous_index == 0 {
110                        result.push_str(&format!(
111                            "{}\n",
112                            &field[previous_index..current_index].trim()
113                        ));
114                    } else {
115                        result.push_str(&format!(
116                            "{:<spaces$}{}\n",
117                            " ",
118                            &field[previous_index..current_index].trim()
119                        ));
120                    }
121                    previous_index = current_index;
122                    line_limit = current_index + max_character_number;
123                    current_index = line_limit;
124                } else {
125                    current_index -= 1;
126                }
127                if current_index == previous_index {
128                    if previous_index == 0 {
129                        result.push_str(&format!(
130                            "{}-\n",
131                            &field[current_index..current_index + max_character_number].trim()
132                        ));
133                    } else {
134                        result.push_str(&format!(
135                            "{:<spaces$}{}-\n",
136                            " ",
137                            &field[current_index..current_index + max_character_number].trim()
138                        ));
139                    }
140                    current_index += max_character_number * 2;
141                    line_limit = current_index;
142                    previous_index = current_index - max_character_number;
143                    if line_limit >= field.len() {
144                        result.push_str(&format!(
145                            "{:<spaces$}{}",
146                            " ",
147                            &field[previous_index..field.len()].trim()
148                        ));
149                        break 'outer;
150                    }
151                }
152            }
153        }
154        result
155    }
156
157    fn craft(command: Command<'a>, max_widths: &HashMap<&str, usize>) -> String {
158        let mut message = String::with_capacity(200);
159        let parameter = match command.parameter_name {
160            Parameter::OPTIONAL(param) => format!("[{}]", param),
161            Parameter::REQUIRED(param) => format!("<{}>", param),
162            Parameter::NO => String::new(),
163        };
164
165        let description_str = Self::field_wrapper(max_widths, &command.description, 40);
166
167        message.push_str(&format!(
168            "{:<short$}   {:<long$}   {:<parameter_name$}  {:<description$}\n",
169            command.short_command,
170            command.long_command,
171            parameter,
172            description_str,
173            short = max_widths.get("short").unwrap(),
174            long = max_widths.get("long").unwrap(),
175            parameter_name = max_widths.get("parameter_name").unwrap(),
176            description = max_widths.get("description").unwrap(),
177        ));
178
179        message
180    }
181    /// Build the help message string.
182    pub fn build(self) -> String {
183        let commands = self.commands;
184        let max_widths = Self::max_width(&commands);
185        let mut result = String::new();
186
187        result.push_str(&format!(
188            "{:<short$}   {:<long$}   {:<parameter_name$}  {:<description$}\n",
189            "",
190            "",
191            "",
192            "",
193            short = max_widths.get("short").unwrap(),
194            long = max_widths.get("long").unwrap(),
195            parameter_name = max_widths.get("parameter_name").unwrap(),
196            description = max_widths.get("description").unwrap(),
197        ));
198
199        for mut command in commands.clone() {
200            match command.dash_status {
201                DASHED::YES => {
202                    if !command.long_command.is_empty() {
203                        let mut long = String::with_capacity(command.long_command.len() + 2);
204                        long.push_str("--");
205                        long.push_str(&command.long_command);
206                        command.long_command = long
207                    }
208                    if !command.short_command.is_empty() {
209                        let mut short = String::with_capacity(command.short_command.len() + 1);
210                        short.push('-');
211                        short.push_str(&command.short_command);
212                        command.short_command = short
213                    }
214                }
215                DASHED::NO => {
216                    command.short_command = format!(" {}", command.short_command);
217                    command.long_command = format!("  {}", command.long_command);
218                }
219            };
220
221            result.push_str(&Self::craft(command, &max_widths));
222        }
223        if *max_widths.get("parameter_name").unwrap() > 2 {
224            result.push_str("\n\nNote: <> parameter is required. [] parameter is optional");
225        }
226        result
227    }
228}