• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Parser example for ISO8601 dates. This does not handle the entire specification but it should
2 //! show the gist of it and be easy to extend to parse additional forms.
3 
4 use std::{
5     env, fmt,
6     fs::File,
7     io::{self, Read},
8 };
9 
10 use combine::{
11     choice,
12     error::ParseError,
13     many, optional,
14     parser::char::{char, digit},
15     stream::position,
16     Parser, Stream,
17 };
18 
19 #[cfg(feature = "std")]
20 use combine::{
21     stream::{easy, position::SourcePosition},
22     EasyParser,
23 };
24 
25 enum Error<E> {
26     Io(io::Error),
27     Parse(E),
28 }
29 
30 impl<E> fmt::Display for Error<E>
31 where
32     E: fmt::Display,
33 {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result34     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35         match *self {
36             Error::Io(ref err) => write!(f, "{}", err),
37             Error::Parse(ref err) => write!(f, "{}", err),
38         }
39     }
40 }
41 
42 #[derive(PartialEq, Debug)]
43 pub struct Date {
44     pub year: i32,
45     pub month: i32,
46     pub day: i32,
47 }
48 
49 #[derive(PartialEq, Debug)]
50 pub struct Time {
51     pub hour: i32,
52     pub minute: i32,
53     pub second: i32,
54     pub time_zone: i32,
55 }
56 
57 #[derive(PartialEq, Debug)]
58 pub struct DateTime {
59     pub date: Date,
60     pub time: Time,
61 }
62 
two_digits<Input>() -> impl Parser<Input, Output = i32> where Input: Stream<Token = char>, Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,63 fn two_digits<Input>() -> impl Parser<Input, Output = i32>
64 where
65     Input: Stream<Token = char>,
66     // Necessary due to rust-lang/rust#24159
67     Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
68 {
69     (digit(), digit()).map(|(x, y): (char, char)| {
70         let x = x.to_digit(10).expect("digit");
71         let y = y.to_digit(10).expect("digit");
72         (x * 10 + y) as i32
73     })
74 }
75 
76 /// Parses a time zone
77 /// +0012
78 /// -06:30
79 /// -01
80 /// Z
time_zone<Input>() -> impl Parser<Input, Output = i32> where Input: Stream<Token = char>, Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,81 fn time_zone<Input>() -> impl Parser<Input, Output = i32>
82 where
83     Input: Stream<Token = char>,
84     Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
85 {
86     let utc = char('Z').map(|_| 0);
87     let offset = (
88         choice([char('-'), char('+')]),
89         two_digits(),
90         optional(optional(char(':')).with(two_digits())),
91     )
92         .map(|(sign, hour, minute)| {
93             let offset = hour * 60 + minute.unwrap_or(0);
94             if sign == '-' {
95                 -offset
96             } else {
97                 offset
98             }
99         });
100 
101     utc.or(offset)
102 }
103 
104 /// Parses a date
105 /// 2010-01-30
date<Input>() -> impl Parser<Input, Output = Date> where Input: Stream<Token = char>, Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,106 fn date<Input>() -> impl Parser<Input, Output = Date>
107 where
108     Input: Stream<Token = char>,
109     Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
110 {
111     (
112         many::<String, _, _>(digit()),
113         char('-'),
114         two_digits(),
115         char('-'),
116         two_digits(),
117     )
118         .map(|(year, _, month, _, day)| {
119             // Its ok to just unwrap since we only parsed digits
120             Date {
121                 year: year.parse().unwrap(),
122                 month,
123                 day,
124             }
125         })
126 }
127 
128 /// Parses a time
129 /// 12:30:02
time<Input>() -> impl Parser<Input, Output = Time> where Input: Stream<Token = char>, Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,130 fn time<Input>() -> impl Parser<Input, Output = Time>
131 where
132     Input: Stream<Token = char>,
133     Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
134 {
135     (
136         two_digits(),
137         char(':'),
138         two_digits(),
139         char(':'),
140         two_digits(),
141         time_zone(),
142     )
143         .map(|(hour, _, minute, _, second, time_zone)| {
144             // Its ok to just unwrap since we only parsed digits
145             Time {
146                 hour,
147                 minute,
148                 second,
149                 time_zone,
150             }
151         })
152 }
153 
154 /// Parses a date time according to ISO8601
155 /// 2015-08-02T18:54:42+02
date_time<Input>() -> impl Parser<Input, Output = DateTime> where Input: Stream<Token = char>, Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,156 fn date_time<Input>() -> impl Parser<Input, Output = DateTime>
157 where
158     Input: Stream<Token = char>,
159     Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
160 {
161     (date(), char('T'), time()).map(|(date, _, time)| DateTime { date, time })
162 }
163 
164 #[test]
test()165 fn test() {
166     // A parser for
167     let result = date_time().parse("2015-08-02T18:54:42+02");
168     let d = DateTime {
169         date: Date {
170             year: 2015,
171             month: 8,
172             day: 2,
173         },
174         time: Time {
175             hour: 18,
176             minute: 54,
177             second: 42,
178             time_zone: 2 * 60,
179         },
180     };
181     assert_eq!(result, Ok((d, "")));
182 
183     let result = date_time().parse("50015-12-30T08:54:42Z");
184     let d = DateTime {
185         date: Date {
186             year: 50015,
187             month: 12,
188             day: 30,
189         },
190         time: Time {
191             hour: 8,
192             minute: 54,
193             second: 42,
194             time_zone: 0,
195         },
196     };
197     assert_eq!(result, Ok((d, "")));
198 }
199 
main()200 fn main() {
201     let result = match env::args().nth(1) {
202         Some(file) => File::open(file).map_err(Error::Io).and_then(main_),
203         None => main_(io::stdin()),
204     };
205     match result {
206         Ok(_) => println!("OK"),
207         Err(err) => println!("{}", err),
208     }
209 }
210 
211 #[cfg(feature = "std")]
main_<R>(mut read: R) -> Result<(), Error<easy::Errors<char, String, SourcePosition>>> where R: Read,212 fn main_<R>(mut read: R) -> Result<(), Error<easy::Errors<char, String, SourcePosition>>>
213 where
214     R: Read,
215 {
216     let mut text = String::new();
217     read.read_to_string(&mut text).map_err(Error::Io)?;
218     date_time()
219         .easy_parse(position::Stream::new(&*text))
220         .map_err(|err| Error::Parse(err.map_range(|s| s.to_string())))?;
221     Ok(())
222 }
223 
224 #[cfg(not(feature = "std"))]
main_<R>(mut read: R) -> Result<(), Error<::combine::error::StringStreamError>> where R: Read,225 fn main_<R>(mut read: R) -> Result<(), Error<::combine::error::StringStreamError>>
226 where
227     R: Read,
228 {
229     let mut text = String::new();
230     read.read_to_string(&mut text).map_err(Error::Io)?;
231     date_time()
232         .parse(position::Stream::new(&*text))
233         .map_err(Error::Parse)?;
234     Ok(())
235 }
236