markdown-formatter 0.0.13

Flavored Markdown (ZH) content formatter
Documentation
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));
    }
}