cfun 0.2.11

Tidy up common functions
Documentation
#[cfg(feature = "cmd")]
use anstyle::{
    AnsiColor::{BrightBlue, BrightCyan, BrightGreen, Green, Red},
    Color::Ansi,
    Style,
};
#[cfg(feature = "cmd")]
use chardet::detect;
#[cfg(feature = "cmd")]
use encoding_rs::{Encoding, GBK, UTF_8, WINDOWS_1252};
#[cfg(feature = "cmd")]
use serde::{Deserialize, Serialize};

#[cfg(feature = "cmd")]
fn detect_encoding(buf: &[u8]) -> &'static Encoding {
    let result = detect(buf);
    let encoding_name = result.0.to_lowercase();
    let encoding_label = encoding_name.as_bytes();

    // 检测 BOM 编码
    if let Some((bom_encoding, _)) = Encoding::for_bom(buf) {
        return bom_encoding;
    }

    // 尝试使用 chardet 检测的编码
    if let Some(encoding) = Encoding::for_label(encoding_label) {
        return encoding;
    }

    // 如果无法确定编码,尝试 UTF-8、GBK 和 ISO-8859-1
    let (_, _, utf8_had_errors) = UTF_8.decode(buf);
    if !utf8_had_errors {
        return UTF_8;
    }

    let (_, _, gbk_had_errors) = GBK.decode(buf);
    if !gbk_had_errors {
        return GBK;
    }

    WINDOWS_1252
}
/// config the style of clap lib help info
/// # Example
/// ```
/// #[derive(Parser, Debug)]
/// #[clap(author, version, about)]
/// #[command(styles=clap_help_styles())]
/// pub struct CFunArgs {
///
/// }
/// ```
#[cfg(feature = "cmd")]
pub fn clap_help_styles() -> clap::builder::Styles {
    clap::builder::Styles::styled()
        .usage(Style::new().fg_color(Some(Ansi(BrightBlue))))
        .header(Style::new().fg_color(Some(Ansi(BrightBlue))))
        .literal(Style::new().fg_color(Some(Ansi(BrightGreen))))
        .invalid(Style::new().bold().fg_color(Some(Ansi(Red))))
        .error(Style::new().bold().fg_color(Some(Ansi(Red))))
        .valid(Style::new().fg_color(Some(Ansi(Green))))
        .placeholder(Style::new().fg_color(Some(Ansi(BrightCyan))))
}

#[cfg(feature = "cmd")]
#[derive(Debug, Deserialize, Serialize)]
pub struct CmdResult {
    pub code: i32,
    pub stdout: String,
    pub stderr: String,
}
/// exec a command and get the result,if get the command's exit code failed, default to -1
#[cfg(feature = "cmd")]
pub fn exec(cmd: &str, args: Option<&Vec<&str>>) -> Result<CmdResult, std::io::Error> {
    use std::process::Command;
    let mut cmd = Command::new(cmd);
    if args.is_some_and(|a| !a.is_empty()) {
        cmd.args(args.unwrap());
    }
    let ret = cmd.output();
    let Ok(ret) = ret else {
        return Err(ret.unwrap_err());
    };

    let code = ret.status.code().unwrap_or(-1);
    let coding = detect_encoding(&ret.stdout);
    let stdout = coding.decode(&ret.stdout).0.to_string();
    let coding = detect_encoding(&ret.stderr);
    let stderr = coding.decode(&ret.stderr).0.to_string();

    Ok(CmdResult {
        code,
        stdout,
        stderr,
    })
}

/// exec a command and get the result,The parameter will be passed in as a pure string,if get the command's exit code failed, default to -1
#[cfg(all(feature = "cmd", target_os = "windows"))]
pub fn exec_raw(cmd: &str, args: Option<&str>) -> Result<CmdResult, std::io::Error> {
    use std::{os::windows::process::CommandExt, process::Command};
    let mut cmd = Command::new(cmd);
    if let Some(args) = args {
        cmd.raw_arg(args);
    };
    let ret = cmd.output();
    let Ok(ret) = ret else {
        return Err(ret.unwrap_err());
    };

    let code = ret.status.code().unwrap_or(-1);
    let coding = detect_encoding(&ret.stdout);
    let stdout = coding.decode(&ret.stdout).0.to_string();
    let coding = detect_encoding(&ret.stderr);
    let stderr = coding.decode(&ret.stderr).0.to_string();

    Ok(CmdResult {
        code,
        stdout,
        stderr,
    })
}

#[test]
#[cfg(feature = "cmd")]
fn test_exec() {
    let ret = exec("cmd", Some(&vec!["dir"])).unwrap();
    println!("{:?}", ret);
}