markdown_formatter/args/
builder.rs

1use colored::Colorize;
2use std::{cmp::max, fmt::Display};
3
4use regex::Regex;
5
6use crate::args::split::{len_of_chars, split_into_lines};
7
8#[derive(Clone)]
9pub enum CommandLineOptionCondition {
10    Standard,
11    Format,
12    Flavor,
13    Debug,
14    Additional,
15}
16impl Display for CommandLineOptionCondition {
17    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18        write!(f, "{}", Into::<&str>::into(self.clone()))
19    }
20}
21
22impl Into<&str> for CommandLineOptionCondition {
23    fn into(self) -> &'static str {
24        match self {
25            Self::Standard => "标准选项",
26            Self::Format => "格式化选项",
27            Self::Flavor => "定制风格",
28            Self::Debug => "调试选项",
29            Self::Additional => "附加选项",
30        }
31    }
32}
33
34impl PartialEq for CommandLineOptionCondition {
35    fn eq(&self, other: &Self) -> bool {
36        core::mem::discriminant(self) == core::mem::discriminant(other)
37    }
38}
39#[derive(Clone)]
40pub struct CommandLineOption {
41    pub condition: CommandLineOptionCondition,
42    short: Option<String>,
43    pub long: String,
44    pub description: String,
45    pub explaination: String,
46}
47
48impl CommandLineOption {
49    pub fn new(long: &str, desp: &str, exp: &str) -> Self {
50        Self {
51            condition: CommandLineOptionCondition::Standard,
52            short: None,
53            long: long.to_string(),
54            description: desp.to_string(),
55            explaination: exp.to_string(),
56        }
57    }
58
59    pub fn set_short(mut self, short: &str) -> Self {
60        if Regex::new(r"^-[a-zA-Z]$").unwrap().is_match(short) {
61            self.short = Some(short.to_string());
62            return self;
63        } else {
64            panic!("Short command must be like `-h` or `-H`");
65        }
66    }
67
68    pub fn short(&self) -> Option<String> {
69        self.short.clone()
70    }
71
72    pub fn set_condition(mut self, cond: CommandLineOptionCondition) -> Self {
73        self.condition = cond;
74        self
75    }
76}
77
78pub fn print_help(mut commands: Vec<CommandLineOption>, line_width: Option<usize>) {
79    let mut command_max_length = 0;
80
81    let len_des_per_line = line_width.unwrap_or(120);
82    commands.sort_by(|cmd1, cmd2| {
83        if cmd1.condition == cmd2.condition {
84            cmd1.long.cmp(&cmd2.long)
85        } else {
86            Into::<&str>::into(cmd1.condition.clone())
87                .cmp(&Into::<&str>::into(cmd2.condition.clone()))
88        }
89    });
90
91    let mut output_tuples: Vec<(String, String, CommandLineOptionCondition)> = vec![];
92
93    commands.iter().for_each(|cmd| {
94        let command = match cmd.short.clone() {
95            None => format!("     {}", cmd.long.clone()),
96            Some(s) => format!("{} | {}", s, cmd.long.clone()),
97        };
98        command_max_length = max(command.len(), command_max_length);
99        output_tuples.push((command, cmd.description.clone(), cmd.condition.clone()));
100    });
101    let mut current_condition = None;
102    output_tuples.iter().for_each(|cmd| {
103        if let Some(condition) = current_condition.clone() {
104            if condition != cmd.2 {
105                current_condition = Some(cmd.2.clone());
106                println!("\n{}", cmd.2);
107            }
108        } else {
109            println!("{}", cmd.2);
110            current_condition = Some(cmd.2.clone());
111        }
112        let description = if cmd.1.len() > 0 {
113            let mut temp: Vec<String> = vec![];
114            if len_of_chars(&cmd.1) > len_des_per_line - command_max_length || cmd.1.contains("\n")
115            {
116                for line in split_into_lines(cmd.1.clone(), len_des_per_line - command_max_length) {
117                    temp.push(format!("{} {}", " ".repeat(command_max_length), line));
118                }
119                if let Some(first_line) = temp.first_mut() {
120                    *first_line = first_line.trim().to_string();
121                }
122            } else {
123                temp.push(format!("{}", cmd.1));
124            }
125            temp.join("\n")
126        } else {
127            "".to_string()
128        };
129
130        println!(
131            "{:cmd_width$} {}",
132            cmd.0.bold(),
133            description.italic(),
134            cmd_width = command_max_length
135        );
136    });
137}
138pub fn print_doc(
139    mut commands: Vec<CommandLineOption>,
140    target: Option<Vec<String>>,
141    line_width: Option<usize>,
142) {
143    let mut command_max_length = 0;
144    commands.sort_by(|cmd1, cmd2| {
145        if cmd1.condition == cmd2.condition {
146            cmd1.long.cmp(&cmd2.long)
147        } else {
148            Into::<&str>::into(cmd1.condition.clone())
149                .cmp(&Into::<&str>::into(cmd2.condition.clone()))
150        }
151    });
152
153    let len_des_per_line = line_width.unwrap_or(120) - 7;
154    let len_exp_per_line = line_width.unwrap_or(120) - 9;
155
156    let mut output_tuples: Vec<(String, String, String, CommandLineOptionCondition)> = vec![];
157
158    commands.iter().for_each(|cmd| {
159        let command = match cmd.short.clone() {
160            None => format!("     {}", cmd.long.clone()),
161            Some(s) => format!("{} | {}", s, cmd.long.clone()),
162        };
163
164        match &target {
165            Some(target_command_vec) => {
166                if target_command_vec.len() > 0
167                    && !target_command_vec.contains(&cmd.short().unwrap_or(String::new()))
168                    && !target_command_vec.contains(&cmd.long)
169                {
170                    return;
171                }
172            }
173            _ => (),
174        };
175
176        command_max_length = max(command.len(), command_max_length);
177
178        output_tuples.push((
179            command,
180            cmd.description.clone(),
181            cmd.explaination.clone(),
182            cmd.condition.clone(),
183        ));
184    });
185
186    let mut current_condition = None;
187    output_tuples.iter().for_each(|cmd| {
188        if let Some(condition) = current_condition.clone() {
189            if condition != cmd.3 {
190                current_condition = Some(cmd.3.clone());
191                println!("\n{}", cmd.3);
192            }
193        } else {
194            println!("{}", cmd.3);
195            current_condition = Some(cmd.3.clone());
196        }
197        let explanation = if cmd.2.len() > 0 {
198            let mut temp: Vec<String> = vec![];
199            if len_of_chars(&cmd.2) > len_exp_per_line || cmd.2.contains("\n") {
200                for line in split_into_lines(cmd.2.clone(), len_exp_per_line) {
201                    temp.push(format!("{} {}", " ".repeat(command_max_length), line));
202                }
203            } else {
204                temp.push(format!("{} {}", " ".repeat(command_max_length), cmd.2));
205            }
206            format!("\n{}", temp.join("\n"))
207        } else {
208            "".to_string()
209        };
210        let description = if cmd.1.len() > 0 {
211            let mut temp: Vec<String> = vec![];
212            if len_of_chars(&cmd.1) > len_des_per_line - command_max_length || cmd.1.contains("\n")
213            {
214                for line in split_into_lines(cmd.1.clone(), len_des_per_line - command_max_length) {
215                    temp.push(format!("{} {}", " ".repeat(command_max_length), line));
216                }
217                if let Some(first_line) = temp.first_mut() {
218                    *first_line = first_line.trim().to_string();
219                }
220            } else {
221                temp.push(format!("{}", cmd.1));
222            }
223            temp.join("\n")
224        } else {
225            "".to_string()
226        };
227        println!(
228            "{:cmd_width$} {}{}\n",
229            cmd.0.bold(),
230            description.italic(),
231            explanation,
232            cmd_width = command_max_length
233        );
234    });
235}
236
237#[cfg(test)]
238mod test {
239    use super::*;
240
241    #[test]
242    fn test_print_doc() {
243        let mut commands = vec![];
244        commands.push(
245            CommandLineOption::new("--help", "长名字的命令".repeat(8).as_str(), "")
246                .set_condition(CommandLineOptionCondition::Debug),
247        );
248
249        commands.push(CommandLineOption::new(
250            "--hugging-face-wechat",
251            "Hello",
252            "这是一段很长的介绍".repeat(10).as_str(),
253        ));
254
255        commands.push(
256            CommandLineOption::new(
257                "--flavor-hugging-face-wechat",
258                "Hello",
259                "This is a very long string line that repeats. "
260                    .repeat(5)
261                    .as_str(),
262            )
263            .set_short("-f"),
264        );
265
266        print_help(commands.clone(), Some(80));
267        print_doc(commands, None, Some(80));
268    }
269}