1use nom::{
2 branch::alt,
3 bytes::complete::tag,
4 character::complete::{char, digit0, digit1, multispace0, one_of},
5 combinator::{all_consuming, complete, cut, map, opt, recognize},
6 error::{ErrorKind::TooLarge, ParseError},
7 multi::many1,
8 sequence::{delimited, tuple},
9 Err::Failure,
10 Finish, IResult,
11};
12
13use crate::{
14 duration::{Container, Duration},
15 error,
16};
17
18#[derive(Copy, Clone, Debug)]
20enum DurationUnit {
21 Year,
22 Month,
23 Week,
24 Day,
25 Hour,
26 Minute,
27 Second,
28 Millisecond,
29 Microsecond,
30 Nanosecond,
31}
32
33fn float(input: &str) -> IResult<&str, f64> {
35 map(
36 recognize(tuple((
37 opt(one_of("+-")),
38 opt(tuple((digit0, char('.')))),
39 digit1,
40 ))),
41 |s: &str| s.parse::<f64>().unwrap(),
42 )(input)
43}
44
45fn timespan_word(input: &str) -> IResult<&str, &str> {
46 recognize(many1(one_of("Macdehiklmnorstuwyµ")))(input)
48}
49
50fn all_consuming_tag<'a, E: ParseError<&'a str>>(
52 t: &'a str,
53) -> impl FnMut(&'a str) -> IResult<&'a str, &'a str, E>
54where
55{
56 all_consuming(tag(t))
57}
58
59fn timespan_period_years(input: &str) -> IResult<&str, DurationUnit> {
60 map(
61 alt((
62 all_consuming_tag("years"),
63 all_consuming_tag("year"),
64 all_consuming_tag("yrs"),
65 all_consuming_tag("yr"),
66 all_consuming_tag("y"),
67 )),
68 |_| DurationUnit::Year,
69 )(input)
70}
71
72fn timespan_period_months(input: &str) -> IResult<&str, DurationUnit> {
73 map(
74 alt((
75 all_consuming_tag("months"),
76 all_consuming_tag("month"),
77 all_consuming_tag("mos"),
78 all_consuming_tag("mo"),
79 all_consuming_tag("M"),
80 )),
81 |_| DurationUnit::Month,
82 )(input)
83}
84
85fn timespan_period_weeks(input: &str) -> IResult<&str, DurationUnit> {
86 map(
87 alt((
88 all_consuming_tag("weeks"),
89 all_consuming_tag("week"),
90 all_consuming_tag("wks"),
91 all_consuming_tag("wk"),
92 all_consuming_tag("w"),
93 )),
94 |_| DurationUnit::Week,
95 )(input)
96}
97
98fn timespan_period_days(input: &str) -> IResult<&str, DurationUnit> {
99 map(
100 alt((
101 all_consuming_tag("days"),
102 all_consuming_tag("day"),
103 all_consuming_tag("d"),
104 )),
105 |_| DurationUnit::Day,
106 )(input)
107}
108
109fn timespan_period_hours(input: &str) -> IResult<&str, DurationUnit> {
110 map(
111 alt((
112 all_consuming_tag("hours"),
113 all_consuming_tag("hour"),
114 all_consuming_tag("hrs"),
115 all_consuming_tag("hr"),
116 all_consuming_tag("h"),
117 )),
118 |_| DurationUnit::Hour,
119 )(input)
120}
121
122fn timespan_period_minutes(input: &str) -> IResult<&str, DurationUnit> {
123 map(
124 alt((
125 all_consuming_tag("minutes"),
126 all_consuming_tag("minute"),
127 all_consuming_tag("mins"),
128 all_consuming_tag("min"),
129 all_consuming_tag("m"),
130 )),
131 |_| DurationUnit::Minute,
132 )(input)
133}
134
135fn timespan_period_seconds(input: &str) -> IResult<&str, DurationUnit> {
136 map(
137 alt((
138 all_consuming_tag("seconds"),
139 all_consuming_tag("second"),
140 all_consuming_tag("secs"),
141 all_consuming_tag("sec"),
142 all_consuming_tag("s"),
143 )),
144 |_| DurationUnit::Second,
145 )(input)
146}
147
148fn timespan_period_milliseconds(input: &str) -> IResult<&str, DurationUnit> {
149 map(
150 alt((
151 all_consuming_tag("milliseconds"),
152 all_consuming_tag("millisecond"),
153 all_consuming_tag("msecs"),
154 all_consuming_tag("msec"),
155 all_consuming_tag("ms"),
156 )),
157 |_| DurationUnit::Millisecond,
158 )(input)
159}
160
161fn timespan_period_microseconds(input: &str) -> IResult<&str, DurationUnit> {
162 map(
163 alt((
164 all_consuming_tag("microseconds"),
165 all_consuming_tag("microsecond"),
166 all_consuming_tag("µsecs"),
167 all_consuming_tag("µsec"),
168 all_consuming_tag("µs"),
169 all_consuming_tag("µ"),
170 all_consuming_tag("usecs"),
171 all_consuming_tag("usec"),
172 all_consuming_tag("us"),
173 )),
174 |_| DurationUnit::Microsecond,
175 )(input)
176}
177
178fn timespan_period_nanoseconds(input: &str) -> IResult<&str, DurationUnit> {
179 map(
180 alt((
181 all_consuming_tag("nanoseconds"),
182 all_consuming_tag("nanosecond"),
183 all_consuming_tag("nsecs"),
184 all_consuming_tag("nsec"),
185 all_consuming_tag("ns"),
186 )),
187 |_| DurationUnit::Nanosecond,
188 )(input)
189}
190
191fn timespan_period(input: &str) -> IResult<&str, DurationUnit> {
194 let (input, unit) = timespan_word(input)?;
195 let (_, result) = all_consuming(alt((
196 timespan_period_years,
197 timespan_period_months,
198 timespan_period_weeks,
199 timespan_period_days,
200 timespan_period_hours,
201 timespan_period_minutes,
202 timespan_period_seconds,
203 timespan_period_milliseconds,
204 timespan_period_microseconds,
205 timespan_period_nanoseconds,
206 )))(unit)?;
207
208 Ok((input, result))
209}
210
211#[inline(never)]
213fn duration_fragment(input: &str) -> IResult<&str, Duration> {
214 let (input, count) = delimited(multispace0, float, multispace0)(input)?;
215 let (input, unit) = timespan_period(input)?;
216 let val = match unit {
217 DurationUnit::Year => Duration::Year(count),
218 DurationUnit::Month => Duration::Month(count),
219 DurationUnit::Week => Duration::Week(count),
220 DurationUnit::Day => Duration::Day(count),
221 DurationUnit::Hour => Duration::Hour(count),
222 DurationUnit::Minute => Duration::Minute(count),
223 DurationUnit::Second => Duration::Second(count),
224 DurationUnit::Millisecond => Duration::Millisecond(count),
225 DurationUnit::Microsecond => Duration::Microsecond(count),
226 DurationUnit::Nanosecond => {
227 #[allow(clippy::cast_precision_loss)]
230 if count < i64::MIN as f64 || count > i64::MAX as f64 {
231 return Err(Failure(ParseError::from_error_kind(input, TooLarge)));
232 }
233 #[allow(clippy::cast_possible_truncation)]
234 Duration::Nanosecond(count as i64)
235 }
236 };
237
238 Ok((input, val))
239}
240
241fn raw_seconds(input: &str) -> IResult<&str, Duration> {
243 let (input, seconds) = all_consuming(delimited(multispace0, float, multispace0))(input)?;
244 Ok((input, Duration::Second(seconds)))
245}
246
247fn full_duration(input: &str) -> IResult<&str, Vec<Duration>> {
248 all_consuming(many1(duration_fragment))(input)
249}
250
251fn duration(input: &str) -> IResult<&str, Container> {
253 complete(cut(alt((
254 map(raw_seconds, |v| Container::new(vec![v])),
255 map(full_duration, Container::new),
256 ))))(input)
257}
258
259macro_rules! impl_parse {
260 ($modname:ident, $typename:ident) => {
261 impl_parse!($modname, $typename, ::$modname::$typename);
262 };
263 ($modname:ident, $typename:ident, $type:ty) => {
264 #[doc = concat!("Parsing systemd-style durations into structs used by [`", stringify!($typename), "`][", stringify!($type), "]")]
265 pub mod $modname {
266 use super::*;
267
268 #[doc = concat!("Parse a duration string into a [`", stringify!($typename), "`][", stringify!($type), "]")]
269 pub fn parse(input: &str) -> Result<$type, error::Error> {
270 let dur = duration(input).map_err(|e| e.to_owned()).finish()?;
271 let ret = dur.1.try_into()?;
272 Ok(ret)
273 }
274 }
275 };
276}
277
278impl_parse!(stdtime, Duration, std::time::Duration);
279
280#[cfg(feature = "with-chrono")]
281impl_parse!(chrono, TimeDelta);
282
283#[cfg(feature = "with-time")]
284impl_parse!(time, Duration);