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}