use nom::{
branch::alt,
bytes::complete::tag,
character::complete::{char, digit0, digit1, multispace0, one_of},
combinator::{all_consuming, complete, cut, map, opt, recognize},
error::{ErrorKind::TooLarge, ParseError},
multi::many1,
sequence::{delimited, tuple},
Err::Failure,
Finish, IResult,
};
use crate::{
duration::{Container, Duration},
error,
};
#[derive(Copy, Clone, Debug)]
enum DurationUnit {
Year,
Month,
Week,
Day,
Hour,
Minute,
Second,
Millisecond,
Microsecond,
Nanosecond,
}
fn float(input: &str) -> IResult<&str, f64> {
map(
recognize(tuple((
opt(one_of("+-")),
opt(tuple((digit0, char('.')))),
digit1,
))),
|s: &str| s.parse::<f64>().unwrap(),
)(input)
}
fn timespan_word(input: &str) -> IResult<&str, &str> {
recognize(many1(one_of("Macdehiklmnorstuwyµ")))(input)
}
fn all_consuming_tag<'a, E: ParseError<&'a str>>(
t: &'a str,
) -> impl FnMut(&'a str) -> IResult<&'a str, &'a str, E>
where
{
all_consuming(tag(t))
}
fn timespan_period_years(input: &str) -> IResult<&str, DurationUnit> {
map(
alt((
all_consuming_tag("years"),
all_consuming_tag("year"),
all_consuming_tag("yrs"),
all_consuming_tag("yr"),
all_consuming_tag("y"),
)),
|_| DurationUnit::Year,
)(input)
}
fn timespan_period_months(input: &str) -> IResult<&str, DurationUnit> {
map(
alt((
all_consuming_tag("months"),
all_consuming_tag("month"),
all_consuming_tag("mos"),
all_consuming_tag("mo"),
all_consuming_tag("M"),
)),
|_| DurationUnit::Month,
)(input)
}
fn timespan_period_weeks(input: &str) -> IResult<&str, DurationUnit> {
map(
alt((
all_consuming_tag("weeks"),
all_consuming_tag("week"),
all_consuming_tag("wks"),
all_consuming_tag("wk"),
all_consuming_tag("w"),
)),
|_| DurationUnit::Week,
)(input)
}
fn timespan_period_days(input: &str) -> IResult<&str, DurationUnit> {
map(
alt((
all_consuming_tag("days"),
all_consuming_tag("day"),
all_consuming_tag("d"),
)),
|_| DurationUnit::Day,
)(input)
}
fn timespan_period_hours(input: &str) -> IResult<&str, DurationUnit> {
map(
alt((
all_consuming_tag("hours"),
all_consuming_tag("hour"),
all_consuming_tag("hrs"),
all_consuming_tag("hr"),
all_consuming_tag("h"),
)),
|_| DurationUnit::Hour,
)(input)
}
fn timespan_period_minutes(input: &str) -> IResult<&str, DurationUnit> {
map(
alt((
all_consuming_tag("minutes"),
all_consuming_tag("minute"),
all_consuming_tag("mins"),
all_consuming_tag("min"),
all_consuming_tag("m"),
)),
|_| DurationUnit::Minute,
)(input)
}
fn timespan_period_seconds(input: &str) -> IResult<&str, DurationUnit> {
map(
alt((
all_consuming_tag("seconds"),
all_consuming_tag("second"),
all_consuming_tag("secs"),
all_consuming_tag("sec"),
all_consuming_tag("s"),
)),
|_| DurationUnit::Second,
)(input)
}
fn timespan_period_milliseconds(input: &str) -> IResult<&str, DurationUnit> {
map(
alt((
all_consuming_tag("milliseconds"),
all_consuming_tag("millisecond"),
all_consuming_tag("msecs"),
all_consuming_tag("msec"),
all_consuming_tag("ms"),
)),
|_| DurationUnit::Millisecond,
)(input)
}
fn timespan_period_microseconds(input: &str) -> IResult<&str, DurationUnit> {
map(
alt((
all_consuming_tag("microseconds"),
all_consuming_tag("microsecond"),
all_consuming_tag("µsecs"),
all_consuming_tag("µsec"),
all_consuming_tag("µs"),
all_consuming_tag("µ"),
all_consuming_tag("usecs"),
all_consuming_tag("usec"),
all_consuming_tag("us"),
)),
|_| DurationUnit::Microsecond,
)(input)
}
fn timespan_period_nanoseconds(input: &str) -> IResult<&str, DurationUnit> {
map(
alt((
all_consuming_tag("nanoseconds"),
all_consuming_tag("nanosecond"),
all_consuming_tag("nsecs"),
all_consuming_tag("nsec"),
all_consuming_tag("ns"),
)),
|_| DurationUnit::Nanosecond,
)(input)
}
fn timespan_period(input: &str) -> IResult<&str, DurationUnit> {
let (input, unit) = timespan_word(input)?;
let (_, result) = all_consuming(alt((
timespan_period_years,
timespan_period_months,
timespan_period_weeks,
timespan_period_days,
timespan_period_hours,
timespan_period_minutes,
timespan_period_seconds,
timespan_period_milliseconds,
timespan_period_microseconds,
timespan_period_nanoseconds,
)))(unit)?;
Ok((input, result))
}
#[inline(never)]
fn duration_fragment(input: &str) -> IResult<&str, Duration> {
let (input, count) = delimited(multispace0, float, multispace0)(input)?;
let (input, unit) = timespan_period(input)?;
let val = match unit {
DurationUnit::Year => Duration::Year(count),
DurationUnit::Month => Duration::Month(count),
DurationUnit::Week => Duration::Week(count),
DurationUnit::Day => Duration::Day(count),
DurationUnit::Hour => Duration::Hour(count),
DurationUnit::Minute => Duration::Minute(count),
DurationUnit::Second => Duration::Second(count),
DurationUnit::Millisecond => Duration::Millisecond(count),
DurationUnit::Microsecond => Duration::Microsecond(count),
DurationUnit::Nanosecond => {
#[allow(clippy::cast_precision_loss)]
if count < i64::MIN as f64 || count > i64::MAX as f64 {
return Err(Failure(ParseError::from_error_kind(input, TooLarge)));
}
#[allow(clippy::cast_possible_truncation)]
Duration::Nanosecond(count as i64)
}
};
Ok((input, val))
}
fn raw_seconds(input: &str) -> IResult<&str, Duration> {
let (input, seconds) = all_consuming(delimited(multispace0, float, multispace0))(input)?;
Ok((input, Duration::Second(seconds)))
}
fn full_duration(input: &str) -> IResult<&str, Vec<Duration>> {
all_consuming(many1(duration_fragment))(input)
}
fn duration(input: &str) -> IResult<&str, Container> {
complete(cut(alt((
map(raw_seconds, |v| Container::new(vec![v])),
map(full_duration, Container::new),
))))(input)
}
macro_rules! impl_parse {
($modname:ident, $typename:ident) => {
impl_parse!($modname, $typename, ::$modname::$typename);
};
($modname:ident, $typename:ident, $type:ty) => {
#[doc = concat!("Parsing systemd-style durations into structs used by [`", stringify!($typename), "`][", stringify!($type), "]")]
pub mod $modname {
use super::*;
#[doc = concat!("Parse a duration string into a [`", stringify!($typename), "`][", stringify!($type), "]")]
pub fn parse(input: &str) -> Result<$type, error::Error> {
let dur = duration(input).map_err(|e| e.to_owned()).finish()?;
let ret = dur.1.try_into()?;
Ok(ret)
}
}
};
}
impl_parse!(stdtime, Duration, std::time::Duration);
#[cfg(feature = "with-chrono")]
impl_parse!(chrono, TimeDelta);
#[cfg(feature = "with-time")]
impl_parse!(time, Duration);