ex-cli 1.22.0

Command line tool to find, filter, sort and list files.
Documentation
use crate::error::{MyError, MyResult};
use crate::util::calendar::Calendar;
use chrono::{DateTime, Duration, TimeZone, Utc};
use regex::Regex;
use std::ops::Sub;
use termcolor::WriteColor;

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum PeriodKind {
    Sec(i64),
    Min(i64),
    Hour(i64),
    Day(i64),
    Week(i64),
    Month(i64),
    Year(i64),
}

impl PeriodKind {
    pub fn print_time<W: WriteColor>(&self, writer: &mut W, padding: bool) -> MyResult<()> {
        match self {
            PeriodKind::Sec(count) => write!(writer, "{:2} sec", count)?,
            PeriodKind::Min(count) => write!(writer, "{:2} min", count)?,
            PeriodKind::Hour(count) => write!(writer, "{:2} hour", count)?,
            PeriodKind::Day(count) => write!(writer, "{:2} day", count)?,
            PeriodKind::Week(count) => write!(writer, "{:2} week", count)?,
            PeriodKind::Month(count) => write!(writer, "{:2} month", count)?,
            PeriodKind::Year(count) => write!(writer, "{:2} year", count)?,
        }
        if padding {
            match self {
                PeriodKind::Sec(_) => write!(writer, "  ")?,
                PeriodKind::Min(_) => write!(writer, "  ")?,
                PeriodKind::Hour(_) => write!(writer, " ")?,
                PeriodKind::Day(_) => write!(writer, "  ")?,
                PeriodKind::Week(_) => write!(writer, " ")?,
                PeriodKind::Month(_) => (),
                PeriodKind::Year(_) => write!(writer, " ")?,
            }
        }
        Ok(())
    }

    fn subtract_from<Tz: TimeZone>(&self, time: &DateTime<Utc>, zone: &Tz) -> DateTime<Utc> {
        match self {
            Self::Sec(count) => time.sub(Duration::seconds(*count)),
            Self::Min(count) => time.sub(Duration::minutes(*count)),
            Self::Hour(count) => time.sub(Duration::hours(*count)),
            Self::Day(count) => time.sub(Duration::days(*count)),
            Self::Week(count) => time.sub(Duration::weeks(*count)),
            Self::Month(count) => Calendar::from_time(time, zone).subtract_month(*count, zone),
            Self::Year(count) => Calendar::from_time(time, zone).subtract_year(*count, zone),
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub enum RecentKind<T> {
    None,
    Before(T),
    After(T),
}

impl<T> RecentKind<T> {
    pub fn to_option(self) -> Option<T> {
        match self {
            Self::None => None,
            Self::Before(value) => Some(value),
            Self::After(value) => Some(value),
        }
    }
}

impl RecentKind<PeriodKind> {
    pub fn from_str(value: &str) -> MyResult<Self> {
        let re = Regex::new(r"^([ymwdhHMS])(\d+)?([+-])?$")?;
        match re.captures(value) {
            Some(captures) => {
                let count = captures
                    .get(2)
                    .map(|x| x.as_str())
                    .map(|x| x.parse())
                    .unwrap_or(Ok(1))?;
                let before = captures
                    .get(3)
                    .map(|x| x.as_str())
                    .map(|x| x == "-")
                    .unwrap_or(false);
                let recent = captures
                    .get(1)
                    .map(|x| x.as_str())
                    .map(|x| Self::from_tuple(x, count, before))
                    .unwrap_or(Self::None);
                Ok(recent)
            }
            None => Err(MyError::create_clap("recent", value)),
        }
    }

    fn from_tuple(period: &str, count: i64, before: bool) -> Self {
        if let Some(period) = Self::from_period(period, count) {
            if before {
                Self::Before(period)
            } else {
                Self::After(period)
            }
        } else {
            Self::None
        }
    }

    fn from_period(period: &str, count: i64) -> Option<PeriodKind> {
        match period {
            "S" => Some(PeriodKind::Sec(count)),
            "M" => Some(PeriodKind::Min(count)),
            "H" | "h" => Some(PeriodKind::Hour(count)),
            "d" => Some(PeriodKind::Day(count)),
            "w" => Some(PeriodKind::Week(count)),
            "m" => Some(PeriodKind::Month(count)),
            "y" => Some(PeriodKind::Year(count)),
            _ => None,
        }
    }

    pub fn from_times<Tz: TimeZone>(
        file_time: &DateTime<Utc>,
        curr_time: &DateTime<Utc>,
        curr_calendar: &Calendar,
        zone: &Tz,
    ) -> Self {
        let delta = curr_time.signed_duration_since(file_time);
        let count = delta.num_days();
        if count > 0 {
            let file_calendar = Calendar::from_time(file_time, zone);
            if let Some(count) = file_calendar.num_years_to(&curr_calendar) {
                return Self::After(PeriodKind::Year(count));
            }
            if let Some(count) = file_calendar.num_months_to(&curr_calendar) {
                return Self::After(PeriodKind::Month(count));
            }
            return Self::After(PeriodKind::Day(count));
        }
        let count = delta.num_hours();
        if count > 0 {
            return Self::After(PeriodKind::Hour(count));
        }
        let count = delta.num_minutes();
        if count > 0 {
            return Self::After(PeriodKind::Min(count));
        }
        let count = delta.num_seconds();
        if count >= 0 {
            return Self::After(PeriodKind::Sec(count));
        }
        Self::None
    }

    pub fn subtract_from<Tz: TimeZone>(
        &self,
        time: &DateTime<Utc>,
        zone: &Tz,
    ) -> RecentKind<DateTime<Utc>> {
        match self {
            Self::None => RecentKind::None,
            Self::Before(period) => RecentKind::Before(period.subtract_from(time, zone)),
            Self::After(period) => RecentKind::After(period.subtract_from(time, zone)),
        }
    }
}

impl<T> Default for RecentKind<T> {
    fn default() -> Self {
        Self::None
    }
}