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