ecd 0.1.1

Fast CLI to detect text file encodings (UTF-8, GBK, Shift_JIS, and 35+ more)
Documentation
use std::io::{self, Write};
use std::process::ExitCode;

use clap::{CommandFactory, Parser};
use rayon::prelude::*;

use ecd::cli::{CheckArgs, Cli, Commands};
use ecd::detect::detect_file;
use ecd::output::{FileResult, OutputConfig, print_results, print_stats};
use ecd::walk::collect_paths;

fn main() -> ExitCode {
    match run() {
        Ok(()) => ExitCode::SUCCESS,
        Err(err) => {
            eprintln!("ecd: {err}");
            ExitCode::FAILURE
        }
    }
}

fn run() -> anyhow::Result<()> {
    let cli = Cli::parse();

    let args = match cli.command {
        Some(Commands::Check(args)) => args,
        None => {
            let mut stdout = io::stdout().lock();
            Cli::command().print_help()?;
            writeln!(stdout)?;
            return Ok(());
        }
    };

    execute_check(args)
}

fn execute_check(args: CheckArgs) -> anyhow::Result<()> {
    if let Some(jobs) = args.jobs {
        rayon::ThreadPoolBuilder::new()
            .num_threads(jobs)
            .build_global()
            .map_err(|e| anyhow::anyhow!("failed to configure thread pool: {e}"))?;
    }

    let single_file_mode = args.is_single_file_mode();
    let paths = collect_paths(&args)?;

    let results: Vec<FileResult> = paths
        .par_iter()
        .map(|path| FileResult {
            path: path.clone(),
            detection: detect_file(path),
        })
        .collect();

    let ignore_encoding = args.ignore_encoding.as_deref();
    let config = OutputConfig {
        single_file_mode,
        ignore_encoding,
        quiet: args.quiet,
        verbose: args.verbose,
    };

    let mut stderr = io::stderr().lock();
    let stats = print_results(&results, &config, &mut stderr)?;

    if args.verbose {
        print_stats(&stats, &mut stderr)?;
    }

    Ok(())
}