dx-cli 0.3.1

faster dx with obj c ffi bindings
use bytesize::ByteSize;
use color_print::{cformat, cprintln};
use dx_cli::fffs::{get_finder_fast_folder_size, process_path, PathResult};
use dx_cli::DataSource;
use itertools::Itertools;
use std::{
    borrow::Borrow,
    fs::{self, File},
    path::{Path, PathBuf},
    process::{Command, CommandArgs},
    time::{Duration, Instant},
};

use regex::Regex;

use dx_cli::config::{self, Strategy};

extern crate libc;

pub fn pad_end(s: &str, target_length: usize, pad_char: char) -> String {
    if s.len() >= target_length {
        return s.to_string();
    }
    let padding = pad_char.to_string().repeat(target_length - s.len());
    format!("{}{}", s, padding)
}

pub fn pad_start(s: &str, target_length: usize, pad_char: char) -> String {
    if s.len() >= target_length {
        return s.to_string();
    }
    let padding = pad_char.to_string().repeat(target_length - s.len());
    format!("{}{}", padding, s)
}

fn format_size(size: i64) -> String {
    if (config::ArgOpts.bytes) {
        return size.to_string();
    }
    if (size < 0) {
        return "KO".to_string();
    }
    let size_formatted = ByteSize::b(size as u64).to_string();
    let mut rtn = size_formatted.replace('B', "").replace(' ', "");
    if rtn.len() >= 5 {
        let re = Regex::new(r"\.[0-9]+").unwrap();
        rtn = re.replace(&rtn, "").into_owned();
    }
    if rtn.parse::<f64>().is_ok() {
        if (rtn.len() <= 3) {
            rtn = format!("{}B", rtn);
        }
    }
    return rtn;
}

fn format_path(abs_path: &PathBuf, index: usize) -> String {
    if (config::ArgOpts.list) {
        let path_arg = &config::ArgOpts.paths[0];
        let file_name = abs_path.file_name().unwrap();
        let joined = path_arg.join(file_name);

        return joined.to_string_lossy().into_owned();
    }
    let current_path_arg = &config::ArgOpts.paths[index];
    if (current_path_arg.is_relative()) {
        let file_name: &std::ffi::OsStr = abs_path.file_name().unwrap();
        if (current_path_arg.ends_with(file_name)) {
            return current_path_arg.to_string_lossy().into_owned();
        }
        let joined = current_path_arg.join(file_name);
        return joined.to_string_lossy().into_owned();
    }
    return abs_path.to_string_lossy().into_owned();
}

fn format_timing(duration: Duration) -> String {
    if (!config::ArgOpts.perf) {
        return format!("");
    }
    if duration.as_secs_f32() > 1.0 {
        return cformat!(" <red>{:.1}s</red>", duration.as_secs_f32());
    }
    if duration.as_secs_f32() > 0.1 {
        return cformat!(" <yellow>{:.3}μ</yellow>", duration.subsec_millis());
    }
    if duration.as_secs_f32() > 0.01 {
        return format!(" {:3}μ", duration.subsec_millis());
    }
    return format!("     ");
}

fn pretty_print(path_index: usize, path_result: &PathResult) {
    let PathResult {
        path,
        size,
        strategy,
        duration,
        error_message,
        ..
    } = path_result;

    let size_formatted = format_size(*size);
    let home_dir = std::env::var("HOME").unwrap();
    let path_formatted = format_path(path, path_index).replace(home_dir.as_str(), "~");
    let pad_size = pad_start(&size_formatted, 5, ' ');
    let perf_timing = format_timing(*duration);

    let strat_indicator = match (config::ArgOpts.verbose, strategy) {
        (false, _) => "".to_string(),
        (true, Some(strategy)) => strategy.to_colored_short_name(),
        (true, None) => format!(
            "<red>{}</>",
            error_message.clone().unwrap_or("err".to_string())
        ),
    };
    cprintln!(
        "{}{} <bold>{}</> {}",
        strat_indicator,
        perf_timing,
        pad_size,
        path_formatted
    );
}

pub fn process_paths(paths: Vec<PathBuf>) {
    let vecstrats = config::ArgOpts.strategy.to_vec();

    let mapped_results = paths
        .iter()
        .filter(|path| !config::ArgOpts.dironly || path.is_dir())
        .map(|path| {
            if path.is_file() {
                process_path(path, vec![Strategy::Live])
            } else {
                process_path(path, vecstrats.clone())
            }
        })
        .inspect(|path_result| {
            if (!config::ArgOpts.sort) {
                pretty_print(0, path_result);
            }
        })
        .collect::<Vec<PathResult>>();
    if (config::ArgOpts.sort) {
        mapped_results.iter()
            .enumerate()
            .sorted_by_key(|&(_, p)| p.size)
            .for_each(|(index, path_result)| pretty_print(index, path_result));
    }
}