ex-cli 1.21.0

Command line tool to find, filter, sort and list files.
Documentation
use crate::error::MyResult;
use crate::fs::flags::FileFlags;
#[cfg(windows)]
use once_cell::unsync::Lazy;
#[cfg(windows)]
use std::collections::HashSet;
#[cfg(windows)]
use std::env;
#[cfg(windows)]
use std::ffi::OsStr;
use std::fs;
use std::path::Path;
use std::time::SystemTime;
#[cfg(unix)]
use uzers::{gid_t, uid_t};

#[derive(Clone)]
pub struct Metadata {
    pub file_flags: FileFlags,
    pub file_mode: u32,
    #[cfg(unix)]
    pub owner_uid: uid_t,
    #[cfg(unix)]
    pub owner_gid: gid_t,
    pub file_size: u64,
    pub file_time: SystemTime,
}

impl Metadata {
    pub fn from_path(path: &Path) -> MyResult<Self> {
        let metadata = fs::symlink_metadata(path).map_err(|e| (e, path))?;
        Self::create_metadata(metadata, path)
    }
}

#[cfg(windows)]
impl Metadata {
    fn create_metadata(metadata: fs::Metadata, path: &Path) -> MyResult<Self> {
        use std::os::windows::fs::MetadataExt;
        let file_flags = FileFlags::from_type(metadata.file_type(), false);
        let file_mode = Self::convert_mode(metadata.file_attributes(), path);
        let file_size = metadata.file_size();
        let file_time = metadata.modified().map_err(|e| (e, path))?;
        let metadata = Self {
            file_flags,
            file_mode,
            file_size,
            file_time,
        };
        Ok(metadata)
    }

    fn convert_mode(attributes: u32, path: &Path) -> u32 {
        let extensions = Lazy::new(|| {
            Self::parse_extensions()
        });
        let mut octal = 0x04; // (readable)
        if attributes & 0x01 == 0 {
            octal |= 0x02; // (writable)
        }
        if let Some(ext) = path.extension().and_then(OsStr::to_str) {
            let ext = ext.to_uppercase();
            if extensions.contains(&ext) {
                octal |= 0x01; // (executable)
            }
        }
        octal + (octal << 3) + (octal << 6)
    }

    fn parse_extensions() -> HashSet<String> {
        let mut extensions = HashSet::new();
        if let Ok(variable) = env::var("PATH_EXT") {
            for ext in variable.split(";") {
                if let Some(ext) = ext.strip_prefix(".") {
                    extensions.insert(ext.to_uppercase());
                }
            }
        }
        extensions
    }
}

#[cfg(unix)]
impl Metadata {
    fn create_metadata(metadata: fs::Metadata, path: &Path) -> MyResult<Self> {
        use std::os::unix::fs::MetadataExt;
        let file_flags = FileFlags::from_type(metadata.file_type(), false);
        let file_mode = metadata.mode();
        let owner_uid = metadata.uid();
        let owner_gid = metadata.gid();
        let file_size = metadata.size();
        let file_time = metadata.modified().map_err(|e| (e, path))?;
        let metadata = Self {
            file_flags,
            file_mode,
            owner_uid,
            owner_gid,
            file_size,
            file_time,
        };
        Ok(metadata)
    }
}

impl Default for Metadata {
    fn default() -> Self {
        Self {
            file_flags: FileFlags::Dir,
            file_mode: 0,
            #[cfg(unix)]
            owner_uid: 0,
            #[cfg(unix)]
            owner_gid: 0,
            file_size: 0,
            file_time: SystemTime::UNIX_EPOCH,
        }
    }
}

#[cfg(test)]
pub mod tests {
    use crate::fs::flags::FileFlags;
    use crate::fs::metadata::Metadata;
    use chrono::{DateTime, TimeZone, Utc};
    use std::time::SystemTime;

    impl Metadata {
        #[allow(unused_variables)]
        pub fn from_fields(
            file_type: char,
            file_mode: u32,
            owner_uid: u32, // uid_t
            owner_gid: u32, // gid_t
            file_size: u64,
            file_year: i32,
            file_month: u32,
            file_day: u32,
        ) -> Self {
            let file_flags = FileFlags::from_char(file_type);
            let file_time = create_time(file_year, file_month, file_day);
            let file_time = SystemTime::from(file_time);
            Self {
                file_flags,
                file_mode,
                #[cfg(unix)]
                owner_uid,
                #[cfg(unix)]
                owner_gid,
                file_size,
                file_time,
            }
        }
    }

    fn create_time(year: i32, month: u32, day: u32) -> DateTime<Utc> {
        Utc.with_ymd_and_hms(year, month, day, 0, 0, 0).unwrap()
    }
}