mihomo-rs 2.0.0

A Rust SDK and CLI tool for mihomo proxy management with service lifecycle management, configuration handling, and real-time monitoring
Documentation
use crossterm::style::{Color, Print, ResetColor, SetForegroundColor};
use crossterm::ExecutableCommand;
use std::io::stdout;
use unicode_width::UnicodeWidthStr;

pub fn print_success(msg: &str) {
    let mut stdout = stdout();
    let _ = stdout.execute(SetForegroundColor(Color::Green));
    let _ = stdout.execute(Print(""));
    let _ = stdout.execute(ResetColor);
    println!("{}", msg);
}

pub fn print_error(msg: &str) {
    let mut stdout = stdout();
    let _ = stdout.execute(SetForegroundColor(Color::Red));
    let _ = stdout.execute(Print(""));
    let _ = stdout.execute(ResetColor);
    eprintln!("{}", msg);
}

pub fn print_info(msg: &str) {
    let mut stdout = stdout();
    let _ = stdout.execute(SetForegroundColor(Color::Blue));
    let _ = stdout.execute(Print(""));
    let _ = stdout.execute(ResetColor);
    println!("{}", msg);
}

pub fn print_table(headers: &[&str], rows: Vec<Vec<String>>) {
    if rows.is_empty() {
        return;
    }

    let mut col_widths = headers.iter().map(|h| display_width(h)).collect::<Vec<_>>();

    for row in &rows {
        for (i, cell) in row.iter().enumerate() {
            if i < col_widths.len() {
                col_widths[i] = col_widths[i].max(display_width(cell));
            }
        }
    }

    print!("");
    for (i, header) in headers.iter().enumerate() {
        print_padded(header, col_widths[i]);
        if i < headers.len() - 1 {
            print!("");
        }
    }
    println!("");

    print!("");
    for (i, width) in col_widths.iter().enumerate() {
        print!("{}", "".repeat(width + 2));
        if i < col_widths.len() - 1 {
            print!("");
        }
    }
    println!("");

    for row in rows {
        print!("");
        for (i, cell) in row.iter().enumerate() {
            if i < col_widths.len() {
                print_padded(cell, col_widths[i]);
                if i < row.len() - 1 {
                    print!("");
                }
            }
        }
        println!("");
    }
}

fn display_width(input: &str) -> usize {
    UnicodeWidthStr::width(input)
}

fn print_padded(input: &str, width: usize) {
    print!("{}", input);
    let current = display_width(input);
    if width > current {
        print!("{}", " ".repeat(width - current));
    }
}

#[cfg(test)]
mod tests {
    use super::{display_width, print_error, print_info, print_success, print_table};

    #[test]
    fn test_display_width_mixed_language() {
        assert_eq!(display_width("abc"), 3);
        assert_eq!(display_width("测试"), 4);
        assert_eq!(display_width("a测b"), 4);
    }

    #[test]
    fn test_print_colored_messages_do_not_panic() {
        print_success("operation ok");
        print_info("some info");
        print_error("some error");
    }

    #[test]
    fn test_print_table_empty_rows_returns_early() {
        print_table(&["name", "value"], vec![]);
    }

    #[test]
    fn test_print_table_with_mixed_content() {
        let headers = vec!["name", "status", "note"];
        let rows = vec![
            vec![
                "alpha".to_string(),
                "running".to_string(),
                "plain ascii".to_string(),
            ],
            vec![
                "测试节点".to_string(),
                "ok".to_string(),
                "mixed 中文 + english".to_string(),
            ],
        ];
        print_table(&headers, rows);
    }
}