duckling 0.4.0

A Rust port of Facebook's Duckling library for parsing natural language into structured data
Documentation
pub mod ar;
pub mod bg;
pub mod ca;
pub mod de;
pub mod el;
pub mod en;
pub mod es;
pub mod fr;
pub mod ga;
pub mod hi;
pub mod hu;
pub mod ja;
pub mod ka;
pub mod ko;
pub mod mn;
pub mod nb;
pub mod nl;
pub mod pl;
pub mod pt;
pub mod ro;
pub mod ru;
pub mod sv;
pub mod tr;
pub mod uk;
pub mod zh;

use crate::types::DimensionValue;

/// Time grain, ordered from smallest to largest (Second < Minute < ... < Year).
/// Ordering matches Haskell Duckling's derived Ord.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize)]
pub enum Grain {
    /// No grain — used only for `now` to mark it as a reference instant.
    /// Matches Haskell's `NoGrain` variant.
    NoGrain,
    /// Seconds.
    Second,
    /// Minutes.
    Minute,
    /// Hours.
    Hour,
    /// Days.
    Day,
    /// Weeks.
    Week,
    /// Months.
    Month,
    /// Quarters (3 months).
    Quarter,
    /// Years.
    Year,
}

impl Grain {
    /// Returns the grain as a string (e.g. `"hour"`, `"day"`).
    pub fn as_str(&self) -> &'static str {
        match self {
            Grain::NoGrain => "no_grain",
            Grain::Second => "second",
            Grain::Minute => "minute",
            Grain::Hour => "hour",
            Grain::Day => "day",
            Grain::Week => "week",
            Grain::Month => "month",
            Grain::Quarter => "quarter",
            Grain::Year => "year",
        }
    }

    /// Parses a grain from a string.
    #[allow(clippy::should_implement_trait)]
    pub fn from_str(s: &str) -> Grain {
        match s {
            "no_grain" => Grain::NoGrain,
            "second" => Grain::Second,
            "minute" => Grain::Minute,
            "hour" => Grain::Hour,
            "day" => Grain::Day,
            "week" => Grain::Week,
            "month" => Grain::Month,
            "quarter" => Grain::Quarter,
            "year" => Grain::Year,
            _ => Grain::Second,
        }
    }

    fn order(&self) -> u8 {
        match self {
            Grain::NoGrain => 0,
            Grain::Second => 1,
            Grain::Minute => 2,
            Grain::Hour => 3,
            Grain::Day => 4,
            Grain::Week => 5,
            Grain::Month => 6,
            Grain::Quarter => 7,
            Grain::Year => 8,
        }
    }

    /// Returns the next finer grain level, matching Haskell's TG.lower.
    pub fn lower(&self) -> Grain {
        match self {
            Grain::Year => Grain::Month,
            Grain::Quarter => Grain::Month,
            Grain::Month => Grain::Day,
            Grain::Week => Grain::Day,
            Grain::Day => Grain::Hour,
            Grain::Hour => Grain::Minute,
            Grain::Minute => Grain::Second,
            Grain::Second => Grain::Second,
            Grain::NoGrain => Grain::Second,
        }
    }

    /// Number of seconds in `n` units of this grain.
    /// Matches Haskell Duckling's `inSeconds`.
    pub fn in_seconds(&self, n: i64) -> Option<i64> {
        match self {
            Grain::NoGrain => Some(n),
            Grain::Second => Some(n),
            Grain::Minute => n.checked_mul(60),
            Grain::Hour => n.checked_mul(3600),
            Grain::Day => n.checked_mul(86400),
            Grain::Week => n.checked_mul(604800),
            Grain::Month => n.checked_mul(2592000), // 30 days
            Grain::Quarter => n.checked_mul(7776000), // 90 days
            Grain::Year => n.checked_mul(31536000), // 365 days
        }
    }

    /// Number of seconds in one unit of this grain, as f64.
    pub fn one_in_seconds_f64(&self) -> f64 {
        match self {
            Grain::NoGrain => 1.0,
            Grain::Second => 1.0,
            Grain::Minute => 60.0,
            Grain::Hour => 3600.0,
            Grain::Day => 86400.0,
            Grain::Week => 604800.0,
            Grain::Month => 2592000.0,
            Grain::Quarter => 7776000.0,
            Grain::Year => 31536000.0,
        }
    }
}

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

impl Ord for Grain {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        self.order().cmp(&other.order())
    }
}

pub fn resolve(grain: &Grain) -> DimensionValue {
    DimensionValue::TimeGrain(*grain)
}