ex-cli 1.20.1

Command line tool to find, filter, sort and list files.
Documentation
use chrono::{Local, TimeZone, Utc};
use clap_builder::{ColorChoice, CommandFactory};
use clap_complete::Shell;
use ex_cli::cli::cli::Cli;
use ex_cli::config::Config;
use ex_cli::error::MyResult;
use ex_cli::finder::Finder;
use ex_cli::fs::system::FileSystem;
use ex_cli::printer::{IndentChars, Printer};
use ex_cli::sorter::Sorter;
use ex_cli::util::color::ColorMap;
use std::{env, process};
#[cfg(windows)]
use sysinfo::{Pid, Process, ProcessesToUpdate, System};
use termcolor::StandardStream;

fn main() {
    let result = run_main();
    if let Err(error) = result {
        error.print_error();
    }
}

fn run_main() -> MyResult<()> {
    let args = env::args().collect();
    let piped = atty::isnt(atty::Stream::Stdout);
    let config = Config::new(args, piped, Utc::now)?;
    if let Some(shell) = config.completion() {
        create_completion(shell);
    } else if config.show_utc() {
        run_config(config, &Utc)?;
    } else {
        run_config(config, &Local)?;
    }
    Ok(())
}

fn create_completion(shell: Shell) {
    let name = env!("CARGO_BIN_NAME");
    let mut command = Cli::command();
    let mut stdout = std::io::stdout();
    clap_complete::generate(shell, &mut command, name, &mut stdout);
    process::exit(1);
}

fn run_config<Tz: TimeZone>(config: Config, zone: &Tz) -> MyResult<()> {
    let current = env::current_dir()?;
    let colors = env::var("LS_COLORS").ok();
    let git_bash = test_bash()?;
    let system = FileSystem::new(&config);
    let finder = Finder::new(&config, &system, zone, current, git_bash);
    let sorter = Sorter::new(&config);
    let writer = create_stream(&config);
    let colors = ColorMap::new(colors);
    let chars = IndentChars {
        branching: '\u{251c}',
        terminating: '\u{2514}',
        horizontal: '\u{2500}',
        vertical: '\u{2502}',
    };
    let mut printer = Printer::new(&config, zone, writer, colors, chars, git_bash);
    let mut files = finder.find_files()?;
    let total = finder.create_total(&files);
    sorter.sort_files(&mut files);
    printer.print_files(&files, &total, zone)?;
    Ok(())
}

fn create_stream(config: &Config) -> StandardStream {
    let color = match config.color().unwrap_or(ColorChoice::Auto) {
        ColorChoice::Auto => if atty::is(atty::Stream::Stdout) {
            termcolor::ColorChoice::Auto
        } else {
            termcolor::ColorChoice::Never
        }
        ColorChoice::Always => termcolor::ColorChoice::Always,
        ColorChoice::Never => termcolor::ColorChoice::Never,
    };
    StandardStream::stdout(color)
}

#[cfg(windows)]
fn test_bash() -> MyResult<bool> {
    let mut system = System::new();
    let mut pid = sysinfo::get_current_pid()?;
    while let Some(process) = get_process(&mut system, pid) {
        let name = process.name();
        if name == "bash.exe" {
            return Ok(true);
        } else if let Some(ppid) = process.parent() {
            pid = ppid;
        } else {
            break;
        }
    }
    Ok(false)
}

#[cfg(windows)]
fn get_process(system: &mut System, pid: Pid) -> Option<&Process> {
    system.refresh_processes(ProcessesToUpdate::Some(&[pid]), false);
    system.process(pid)
}

#[cfg(not(windows))]
fn test_bash() -> MyResult<bool> {
    Ok(false)
}