ezlua 0.5.4

Ergonomic, efficient and Zero-cost rust bindings to Lua5.4
Documentation
use crate::prelude::*;
use ::chrono::prelude::*;
use alloc::string::ToString;
use chrono::{Days, Duration, Months};
use core::{fmt::Display, str::FromStr};

impl UserData for Duration {
    fn getter(fields: UserdataRegistry<Self>) -> LuaResult<()> {
        fields.set_closure("weeks", Self::num_weeks)?;
        fields.set_closure("days", Self::num_days)?;
        fields.set_closure("hours", Self::num_hours)?;
        fields.set_closure("minutes", Self::num_minutes)?;
        fields.set_closure("seconds", Self::num_seconds)?;
        fields.set_closure("milliseconds", Self::num_milliseconds)?;
        fields.set_closure("microseconds", Self::num_microseconds)?;
        fields.set_closure("nanoseconds", Self::num_nanoseconds)?;

        fields.set_closure("is_zero", Self::is_zero)?;
        Ok(())
    }

    fn methods(methods: UserdataRegistry<Self>) -> LuaResult<()> {
        methods.set_function("add", |lua, (this, val): (&Self, ValRef)| {
            if let Ok(d) = <&Self as FromLua>::from_lua(val.state(), val) {
                Ok(*this + *d)
            } else {
                Err("invalid arg").lua_result()
            }
        })?;
        methods.set_function("sub", |lua, (this, val): (&Self, ValRef)| {
            if let Ok(d) = <&Self as FromLua>::from_lua(val.state(), val) {
                Ok(*this - *d)
            } else {
                Err("invalid arg").lua_result()
            }
        })?;
        Ok(())
    }

    fn metatable(mt: UserdataRegistry<Self>) -> LuaResult<()> {
        mt.set_closure("weeks", Self::weeks)?;
        mt.set_closure("days", Self::days)?;
        mt.set_closure("hours", Self::hours)?;
        mt.set_closure("minutes", Self::minutes)?;
        mt.set_closure("seconds", Self::seconds)?;
        mt.set_closure("milliseconds", Self::milliseconds)?;
        mt.set_closure("microseconds", Self::microseconds)?;
        mt.set_closure("nanoseconds", Self::nanoseconds)?;

        mt.set_closure("zero", Self::zero)?;
        mt.set_closure("min", Self::min_value)?;
        mt.set_closure("max", Self::max_value)?;

        let methods = mt.get("__method")?;
        mt.set("__add", methods.get("add")?)?;
        mt.set("__sub", methods.get("sub")?)?;

        Ok(())
    }
}

impl FromLua<'_> for Days {
    fn from_lua(lua: &LuaState, val: ValRef) -> LuaResult<Self> {
        FromLua::from_lua(lua, val).map(Days::new)
    }
}

impl FromLua<'_> for Months {
    fn from_lua(lua: &LuaState, val: ValRef) -> LuaResult<Self> {
        FromLua::from_lua(lua, val).map(Months::new)
    }
}

impl UserData for NaiveDate {
    fn getter(fields: UserdataRegistry<Self>) -> LuaResult<()> {
        Ok(())
    }

    fn metatable(mt: UserdataRegistry<Self>) -> LuaResult<()> {
        mt.set_closure(
            "from_ymd",
            move |y: Option<i32>, m: Option<u32>, d: Option<u32>| {
                Self::from_ymd_opt(y.unwrap_or(0), m.unwrap_or(0), d.unwrap_or(0))
            },
        )?;
        Ok(())
    }
}

impl UserData for NaiveTime {
    fn metatable(mt: UserdataRegistry<Self>) -> LuaResult<()> {
        mt.set_closure(
            "from_hms",
            move |h: Option<u32>, min: Option<u32>, s: Option<u32>| {
                Self::from_hms_opt(h.unwrap_or(0), min.unwrap_or(0), s.unwrap_or(0))
            },
        )?;
        Ok(())
    }
}

impl UserData for NaiveDateTime {
    fn metatable(mt: UserdataRegistry<Self>) -> LuaResult<()> {
        mt.set_closure("parse", |s, fmt: Option<&str>| {
            if let Some(fmt) = fmt {
                NaiveDateTime::parse_from_str(s, fmt)
            } else {
                NaiveDateTime::from_str(s)
            }
        })?;
        mt.set_closure("new", |d: &NaiveDate, t: &NaiveTime| {
            NaiveDateTime::new(*d, *t)
        })?;
        mt.set_closure("__tostring", Self::to_string)?;

        Ok(())
    }

    fn methods(methods: UserdataRegistry<Self>) -> LuaResult<()> {
        methods.set_closure("timestamp", |this: &Self| this.and_utc().timestamp())?;
        methods.set_closure("timestamp_millis", |this: &Self| {
            this.and_utc().timestamp_millis()
        })?;
        methods.set_closure("timestamp_micros", |this: &Self| {
            this.and_utc().timestamp_micros()
        })?;
        methods.set_closure("timestamp_nanos", |this: &Self| {
            this.and_utc().timestamp_nanos_opt()
        })?;
        methods.set_closure("time", Self::time)?;
        methods.set_closure("date", Self::date)?;

        methods.set_closure("with_year", Self::with_year)?;
        methods.set_closure("with_month", Self::with_month)?;
        methods.set_closure("with_day", Self::with_day)?;
        methods.set_closure("with_hour", Self::with_hour)?;
        methods.set_closure("with_minute", Self::with_minute)?;
        methods.set_closure("with_second", Self::with_second)?;
        methods.set_closure("with_nanosecond", Self::with_nanosecond)?;

        methods.set_closure("add_months", |this: &Self, n: i64| {
            if n < 0 {
                this.checked_sub_months(Months::new((-n) as _))
            } else {
                this.checked_add_months(Months::new(n as _))
            }
        })?;
        methods.set_closure("add_days", |this: &Self, n: i64| {
            if n < 0 {
                this.checked_sub_days(Days::new((-n) as _))
            } else {
                this.checked_add_days(Days::new(n as _))
            }
        })?;
        methods.set_function("add", |lua, (this, val): (&Self, ValRef)| {
            if let Ok(d) = <&Duration as FromLua>::from_lua(val.state(), val) {
                Ok(this.clone() + *d)
            } else {
                Err("invalid arg").lua_result()
            }
        })?;
        methods.set_function("sub", |lua, (this, val): (&Self, ValRef)| {
            if let Ok(d) = <&Duration as FromLua>::from_lua(val.state(), val) {
                Ok(this.clone() - *d)
            } else {
                Err("invalid arg").lua_result()
            }
        })?;

        methods.set_closure("format", |this: &Self, fmt| this.format(fmt).to_string())?;
        methods.set_closure("to_utc", |this: &Self| {
            this.and_local_timezone(Utc).single()
        })?;
        methods.set_closure("to_local", |this: &Self| {
            this.and_local_timezone(Local).single()
        })?;
        Ok(())
    }
}

impl<Tz: TimeZone + Clone + 'static> UserData for DateTime<Tz>
where
    Tz::Offset: Display,
{
    fn getter(fields: UserdataRegistry<Self>) -> LuaResult<()> {
        fields.add("year", Self::year)?;
        fields.add("month", Self::month)?;
        fields.add("day", Self::day)?;
        fields.add("hour", Self::hour)?;
        fields.add("minute", Self::minute)?;
        fields.add("second", Self::second)?;
        fields.add("nanosecond", Self::nanosecond)?;
        Ok(())
    }

    fn methods(methods: UserdataRegistry<Self>) -> LuaResult<()> {
        methods.set_closure("timestamp", Self::timestamp)?;
        methods.set_closure("timestamp_millis", Self::timestamp_millis)?;
        methods.set_closure("timestamp_micros", Self::timestamp_micros)?;
        methods.set_closure("timestamp_nanos", Self::timestamp_nanos_opt)?;
        methods.set_closure("time", Self::time)?;
        methods.set_closure("date", Self::date_naive)?;
        methods.add("years_since", |this: &Self, base: &Self| {
            this.years_since(base.clone())
        })?;

        methods.set_closure("with_year", Self::with_year)?;
        methods.set_closure("with_month", Self::with_month)?;
        methods.set_closure("with_day", Self::with_day)?;
        methods.set_closure("with_hour", Self::with_hour)?;
        methods.set_closure("with_minute", Self::with_minute)?;
        methods.set_closure("with_second", Self::with_second)?;
        methods.set_closure("with_nanosecond", Self::with_nanosecond)?;

        methods.set_closure("add_months", |this: &Self, n: i64| {
            if n < 0 {
                this.clone().checked_sub_months(Months::new((-n) as _))
            } else {
                this.clone().checked_add_months(Months::new(n as _))
            }
        })?;
        methods.set_closure("add_days", |this: &Self, n: i64| {
            if n < 0 {
                this.clone().checked_sub_days(Days::new((-n) as _))
            } else {
                this.clone().checked_add_days(Days::new(n as _))
            }
        })?;
        methods.set_function("add", |lua, (this, val): (&Self, ValRef)| {
            if let Ok(d) = <&Duration as FromLua>::from_lua(val.state(), val) {
                Ok(this.clone() + *d)
            } else {
                Err("invalid arg").lua_result()
            }
        })?;
        methods.set_function("sub", |lua, (this, val): (&Self, ValRef)| {
            if let Ok(d) = <&Duration as FromLua>::from_lua(val.state(), val) {
                Ok(this.clone() - *d)
            } else {
                Err("invalid arg").lua_result()
            }
        })?;

        methods.set_closure("format", |this: &Self, fmt| this.format(fmt).to_string())?;
        methods.set_closure("to_rfc2822", Self::to_rfc2822)?;
        methods.set_closure("to_rfc3339", Self::to_rfc3339)?;

        methods.set_closure("to_utc", |this: &Self| this.with_timezone(&Utc))?;
        methods.set_closure("to_local", |this: &Self| this.with_timezone(&Local))?;
        Ok(())
    }

    fn metatable(mt: UserdataRegistry<Self>) -> LuaResult<()> {
        mt.set_closure("__tostring", Self::to_string)?;
        mt.set_closure("parse", |s, fmt| DateTime::parse_from_str(s, fmt))?;

        Ok(())
    }
}

pub fn open(lua: &LuaState) -> LuaResult<LuaTable> {
    let m = lua.new_table()?;

    let utc = lua.register_usertype::<DateTime<Utc>>()?;
    utc.set_closure("now", Utc::now)?;
    utc.set_closure("parse", |FromStr::<DateTime<Utc>>(dt)| dt)?;
    init_timezone(Utc, &utc)?;
    m.set("DateTimeUtc", utc)?;

    let local = lua.register_usertype::<DateTime<Local>>()?;
    local.set_closure("now", Local::now)?;
    local.set_closure("parse", |FromStr::<DateTime<Local>>(dt)| dt)?;
    init_timezone(Local, &local)?;
    m.set("DateTimeLocal", local)?;

    m.set("Duration", lua.register_usertype::<Duration>()?)?;
    m.set("NaiveDate", lua.register_usertype::<NaiveDate>()?)?;
    m.set("NaiveTime", lua.register_usertype::<NaiveTime>()?)?;
    m.set("NaiveDateTime", lua.register_usertype::<NaiveDateTime>()?)?;

    Ok(m)
}

fn init_timezone<Tz: TimeZone + Copy + 'static>(tz: Tz, t: &LuaTable) -> LuaResult<()>
where
    Tz::Offset: Display,
{
    t.set_closure(
        "from_ymd_hms",
        move |y: Option<i32>,
              m: Option<u32>,
              d: Option<u32>,
              h: Option<u32>,
              min: Option<u32>,
              s: Option<u32>| {
            tz.with_ymd_and_hms(
                y.unwrap_or(0),
                m.unwrap_or(0),
                d.unwrap_or(0),
                h.unwrap_or(0),
                min.unwrap_or(0),
                s.unwrap_or(0),
            )
            .single()
        },
    )?;
    t.set_closure(
        "from_timestamp",
        move |secs: Option<_>, nsecs: Option<_>| {
            tz.timestamp_opt(secs.unwrap_or(0), nsecs.unwrap_or(0))
                .single()
        },
    )?;
    t.set_closure("from_timestamp_millis", move |millis| {
        tz.timestamp_millis_opt(millis).single()
    })?;
    t.set_closure("from_timestamp_nanos", move |nanos| {
        tz.timestamp_nanos(nanos)
    })?;
    t.set_closure("from_timestamp_micros", move |micros| {
        tz.timestamp_micros(micros).single()
    })?;

    Ok(())
}