use colored::Colorize;
use std::{cmp::max, fmt::Display};
use regex::Regex;
use crate::args::split::{len_of_chars, split_into_lines};
#[derive(Clone)]
pub enum CommandLineOptionCondition {
Standard,
Format,
Flavor,
Debug,
Additional,
}
impl Display for CommandLineOptionCondition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", Into::<&str>::into(self.clone()))
}
}
impl Into<&str> for CommandLineOptionCondition {
fn into(self) -> &'static str {
match self {
Self::Standard => "标准选项",
Self::Format => "格式化选项",
Self::Flavor => "定制风格",
Self::Debug => "调试选项",
Self::Additional => "附加选项",
}
}
}
impl PartialEq for CommandLineOptionCondition {
fn eq(&self, other: &Self) -> bool {
core::mem::discriminant(self) == core::mem::discriminant(other)
}
}
#[derive(Clone)]
pub struct CommandLineOption {
pub condition: CommandLineOptionCondition,
short: Option<String>,
pub long: String,
pub description: String,
pub explaination: String,
}
impl CommandLineOption {
pub fn new(long: &str, desp: &str, exp: &str) -> Self {
Self {
condition: CommandLineOptionCondition::Standard,
short: None,
long: long.to_string(),
description: desp.to_string(),
explaination: exp.to_string(),
}
}
pub fn set_short(mut self, short: &str) -> Self {
if Regex::new(r"^-[a-zA-Z]$").unwrap().is_match(short) {
self.short = Some(short.to_string());
return self;
} else {
panic!("Short command must be like `-h` or `-H`");
}
}
pub fn short(&self) -> Option<String> {
self.short.clone()
}
pub fn set_condition(mut self, cond: CommandLineOptionCondition) -> Self {
self.condition = cond;
self
}
}
pub fn print_help(mut commands: Vec<CommandLineOption>, line_width: Option<usize>) {
let mut command_max_length = 0;
let len_des_per_line = line_width.unwrap_or(120);
commands.sort_by(|cmd1, cmd2| {
if cmd1.condition == cmd2.condition {
cmd1.long.cmp(&cmd2.long)
} else {
Into::<&str>::into(cmd1.condition.clone())
.cmp(&Into::<&str>::into(cmd2.condition.clone()))
}
});
let mut output_tuples: Vec<(String, String, CommandLineOptionCondition)> = vec![];
commands.iter().for_each(|cmd| {
let command = match cmd.short.clone() {
None => format!(" {}", cmd.long.clone()),
Some(s) => format!("{} | {}", s, cmd.long.clone()),
};
command_max_length = max(command.len(), command_max_length);
output_tuples.push((command, cmd.description.clone(), cmd.condition.clone()));
});
let mut current_condition = None;
output_tuples.iter().for_each(|cmd| {
if let Some(condition) = current_condition.clone() {
if condition != cmd.2 {
current_condition = Some(cmd.2.clone());
println!("\n{}", cmd.2);
}
} else {
println!("{}", cmd.2);
current_condition = Some(cmd.2.clone());
}
let description = if cmd.1.len() > 0 {
let mut temp: Vec<String> = vec![];
if len_of_chars(&cmd.1) > len_des_per_line - command_max_length || cmd.1.contains("\n")
{
for line in split_into_lines(cmd.1.clone(), len_des_per_line - command_max_length) {
temp.push(format!("{} {}", " ".repeat(command_max_length), line));
}
if let Some(first_line) = temp.first_mut() {
*first_line = first_line.trim().to_string();
}
} else {
temp.push(format!("{}", cmd.1));
}
temp.join("\n")
} else {
"".to_string()
};
println!(
"{:cmd_width$} {}",
cmd.0.bold(),
description.italic(),
cmd_width = command_max_length
);
});
}
pub fn print_doc(
mut commands: Vec<CommandLineOption>,
target: Option<Vec<String>>,
line_width: Option<usize>,
) {
let mut command_max_length = 0;
commands.sort_by(|cmd1, cmd2| {
if cmd1.condition == cmd2.condition {
cmd1.long.cmp(&cmd2.long)
} else {
Into::<&str>::into(cmd1.condition.clone())
.cmp(&Into::<&str>::into(cmd2.condition.clone()))
}
});
let len_des_per_line = line_width.unwrap_or(120) - 7;
let len_exp_per_line = line_width.unwrap_or(120) - 9;
let mut output_tuples: Vec<(String, String, String, CommandLineOptionCondition)> = vec![];
commands.iter().for_each(|cmd| {
let command = match cmd.short.clone() {
None => format!(" {}", cmd.long.clone()),
Some(s) => format!("{} | {}", s, cmd.long.clone()),
};
match &target {
Some(target_command_vec) => {
if target_command_vec.len() > 0
&& !target_command_vec.contains(&cmd.short().unwrap_or(String::new()))
&& !target_command_vec.contains(&cmd.long)
{
return;
}
}
_ => (),
};
command_max_length = max(command.len(), command_max_length);
output_tuples.push((
command,
cmd.description.clone(),
cmd.explaination.clone(),
cmd.condition.clone(),
));
});
let mut current_condition = None;
output_tuples.iter().for_each(|cmd| {
if let Some(condition) = current_condition.clone() {
if condition != cmd.3 {
current_condition = Some(cmd.3.clone());
println!("\n{}", cmd.3);
}
} else {
println!("{}", cmd.3);
current_condition = Some(cmd.3.clone());
}
let explanation = if cmd.2.len() > 0 {
let mut temp: Vec<String> = vec![];
if len_of_chars(&cmd.2) > len_exp_per_line || cmd.2.contains("\n") {
for line in split_into_lines(cmd.2.clone(), len_exp_per_line) {
temp.push(format!("{} {}", " ".repeat(command_max_length), line));
}
} else {
temp.push(format!("{} {}", " ".repeat(command_max_length), cmd.2));
}
format!("\n{}", temp.join("\n"))
} else {
"".to_string()
};
let description = if cmd.1.len() > 0 {
let mut temp: Vec<String> = vec![];
if len_of_chars(&cmd.1) > len_des_per_line - command_max_length || cmd.1.contains("\n")
{
for line in split_into_lines(cmd.1.clone(), len_des_per_line - command_max_length) {
temp.push(format!("{} {}", " ".repeat(command_max_length), line));
}
if let Some(first_line) = temp.first_mut() {
*first_line = first_line.trim().to_string();
}
} else {
temp.push(format!("{}", cmd.1));
}
temp.join("\n")
} else {
"".to_string()
};
println!(
"{:cmd_width$} {}{}\n",
cmd.0.bold(),
description.italic(),
explanation,
cmd_width = command_max_length
);
});
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_print_doc() {
let mut commands = vec![];
commands.push(
CommandLineOption::new("--help", "长名字的命令".repeat(8).as_str(), "")
.set_condition(CommandLineOptionCondition::Debug),
);
commands.push(CommandLineOption::new(
"--hugging-face-wechat",
"Hello",
"这是一段很长的介绍".repeat(10).as_str(),
));
commands.push(
CommandLineOption::new(
"--flavor-hugging-face-wechat",
"Hello",
"This is a very long string line that repeats. "
.repeat(5)
.as_str(),
)
.set_short("-f"),
);
print_help(commands.clone(), Some(80));
print_doc(commands, None, Some(80));
}
}