ex-cli 1.20.0

Command line tool to find, filter, sort and list files.
Documentation
use crate::cli::file::FileKind;
use crate::git::flags::GitFlags;
use chrono::{DateTime, Utc};
use std::cmp::Ordering;
use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf};
use std::rc::Rc;
use std::time::UNIX_EPOCH;

pub type Signature = [u8; 4];

pub struct File {
    pub abs_dir: PathBuf,
    pub rel_dir: PathBuf,
    pub file_depth: usize,
    pub inner_depth: Option<usize>,
    pub file_name: String,
    pub file_ext: String,
    pub file_type: FileKind,
    pub file_mode: u32,
    pub owner_user: Option<Rc<String>>,
    pub owner_group: Option<Rc<String>>,
    pub file_size: u64,
    pub file_time: DateTime<Utc>,
    pub git_flags: Option<GitFlags>,
    pub file_sig: Signature,
    #[cfg(windows)]
    pub file_ver: Option<String>,
    pub link_data: Option<(PathBuf, FileKind)>,
}

impl File {
    pub fn new(
        abs_dir: PathBuf,
        rel_dir: PathBuf,
        file_depth: usize,
        inner_depth: Option<usize>,
        file_name: String,
        file_ext: String,
        file_type: FileKind,
    ) -> Self {
        Self {
            abs_dir,
            rel_dir,
            file_depth,
            inner_depth,
            file_name,
            file_ext,
            file_type,
            file_mode: 0,
            owner_user: None,
            owner_group: None,
            file_size: 0,
            file_time: DateTime::<Utc>::from(UNIX_EPOCH),
            git_flags: None,
            file_sig: [0; 4],
            #[cfg(windows)]
            file_ver: None,
            link_data: None,
        }
    }

    #[cfg(test)]
    pub fn with_dirs(mut self, abs_dir: PathBuf, rel_dir: PathBuf) -> Self {
        self.abs_dir = abs_dir;
        self.rel_dir = rel_dir;
        self
    }

    #[cfg(test)]
    pub fn with_inner_depth(mut self, inner_depth: Option<usize>) -> Self {
        self.inner_depth = inner_depth;
        self
    }

    #[cfg(test)]
    pub fn with_type(mut self, file_type: FileKind) -> Self {
        self.file_type = file_type;
        self
    }

    pub fn with_mode(mut self, file_mode: u32) -> Self {
        self.file_mode = file_mode;
        self
    }

    pub fn with_owner(
        mut self,
        owner_user: Option<Rc<String>>,
        owner_group: Option<Rc<String>>,
    ) -> Self {
        self.owner_user = owner_user;
        self.owner_group = owner_group;
        self
    }

    #[cfg(test)]
    pub fn with_owner_ref(
        mut self,
        owner_user: &str,
        owner_group: &str,
    ) -> Self {
        self.owner_user = Some(Rc::new(String::from(owner_user)));
        self.owner_group = Some(Rc::new(String::from(owner_group)));
        self
    }

    pub fn with_size(mut self, file_size: u64) -> Self {
        self.file_size = file_size;
        self
    }

    pub fn with_time(mut self, file_time: DateTime<Utc>) -> Self {
        self.file_time = file_time;
        self
    }

    #[cfg(test)]
    pub fn with_date(mut self, year: i32, month: u32, day: u32) -> Self {
        use chrono::TimeZone;
        self.file_time = Utc.with_ymd_and_hms(year, month, day, 0, 0, 0).unwrap();
        self
    }

    pub fn with_git(mut self, git_flags: Option<GitFlags>) -> Self {
        self.git_flags = git_flags;
        self
    }

    pub fn with_sig(mut self, file_sig: Option<Signature>) -> Self {
        if let Some(file_sig) = file_sig {
            self.file_sig = file_sig;
        }
        self
    }

    #[cfg(test)]
    pub fn with_sig_str(mut self, file_sig: &str) -> Self {
        use std::cmp;
        let file_sig = file_sig.as_bytes();
        let len = cmp::min(file_sig.len(), 4);
        self.file_sig[0..len].copy_from_slice(file_sig);
        self
    }

    #[cfg(windows)]
    pub fn with_version(mut self, file_ver: String) -> Self {
        self.file_ver = Some(file_ver);
        self
    }

    pub fn with_link(mut self, link_path: PathBuf, link_type: FileKind) -> Self {
        self.link_data = Some((link_path, link_type));
        self
    }

    pub fn get_path(&self) -> PathBuf {
        self.abs_dir.join(&self.file_name)
    }

    pub fn select_dir(&self, abs_path: bool) -> &Path {
        if abs_path {
            &self.abs_dir
        } else {
            &self.rel_dir
        }
    }

    pub fn select_parent_for_indent(&self) -> Option<PathBuf> {
        if self.file_type == FileKind::Dir {
            self.rel_dir.parent().map(PathBuf::from)
        } else {
            Some(self.rel_dir.clone())
        }
    }

    pub fn group_dir_before_file(&self) -> u8 {
        if self.file_type == FileKind::Dir { 0 } else { 1 }
    }
}

impl PartialEq for File {
    fn eq(&self, other: &Self) -> bool {
        (self.abs_dir == other.abs_dir) && (self.file_name == other.file_name)
    }
}

impl Eq for File {
}

impl PartialOrd for File {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(Ord::cmp(self, other))
    }
}

impl Ord for File {
    fn cmp(&self, other: &Self) -> Ordering {
        match PathBuf::cmp(&self.abs_dir, &other.abs_dir) {
            Ordering::Less => Ordering::Less,
            Ordering::Greater => Ordering::Greater,
            Ordering::Equal => String::cmp(&self.file_name, &other.file_name),
        }
    }
}

impl Hash for File {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.abs_dir.hash(state);
        self.file_name.hash(state);
    }
}