1use std::error::Error;
3
4use crate::error::*;
5use chrono::{NaiveDate, NaiveDateTime};
6use optional::{none, some, Optioned};
7
8pub(crate) const MISSING_I32: i32 = -9999;
10pub(crate) const MISSING_F64: f64 = -9999.0;
11pub(crate) const MISSING_F64_INDEX: f64 = 999.0;
12
13pub(crate) fn check_missing(val: f64) -> Optioned<f64> {
14 if val == MISSING_F64 {
15 none()
16 } else {
17 some(val)
18 }
19}
20
21pub(crate) fn check_missing_i32(val: i32) -> Option<i32> {
22 if val == MISSING_I32 {
23 None
24 } else {
25 Some(val)
26 }
27}
28
29pub fn parse_kv<'a, 'b, FS, FE>(
37 src: &'a str,
38 key: &'b str,
39 start_val: FS,
40 end_val: FE,
41) -> Result<(&'a str, &'a str), BufkitFileError>
42where
43 FS: Fn(char) -> bool,
44 FE: Fn(char) -> bool,
45{
46 let mut idx = src.find(key).ok_or_else(BufkitFileError::new)?;
47 let head = &src[idx..];
48 let mut head = head.trim_start_matches(key);
49 idx = head.find(start_val).ok_or_else(BufkitFileError::new)?;
50 head = &head[idx..];
51 let tail_idx = head.find(end_val).or_else(|| Some(head.len())).unwrap();
54 Ok((head[..tail_idx].trim(), &head[tail_idx..]))
55}
56
57#[test]
58#[rustfmt::skip]
59fn test_parse_kv() {
60 let test_data =
61 "STID = STNM = 727730 TIME = 170401/0000 \
62 SLAT = 46.92 SLON = -114.08 SELV = 972.0 \
63 STIM = 0";
64
65 if let Ok((value_to_parse, head)) =
66 parse_kv(test_data,
67 "STID",
68 |c| char::is_alphanumeric(c),
69 |c| !char::is_alphanumeric(c)) {
70 assert_eq!(value_to_parse, "STNM");
71 assert_eq!(head, " = 727730 TIME = 170401/0000 SLAT = 46.92 SLON = -114.08 SELV = 972.0 STIM = 0");
72 } else {
73 assert!(false, "There was an error parsing.");
74 }
75
76 if let Ok((value_to_parse, head)) =
77 parse_kv(test_data,
78 "STNM",
79 |c| char::is_digit(c, 10),
80 |c| !char::is_digit(c, 10)) {
81 assert_eq!(value_to_parse, "727730");
82 assert_eq!(head, " TIME = 170401/0000 SLAT = 46.92 SLON = -114.08 SELV = 972.0 STIM = 0");
83 } else {
84 assert!(false, "There was an error parsing.");
85 }
86
87 if let Ok((val_to_parse, head)) =
88 parse_kv(test_data,
89 "TIME",
90 |c| char::is_digit(c, 10),
91 |c| !(char::is_digit(c, 10) || c == '/')) {
92 assert_eq!(val_to_parse, "170401/0000");
93 assert_eq!(head, " SLAT = 46.92 SLON = -114.08 SELV = 972.0 STIM = 0");
94 } else {
95 assert!(false, "There was an error parsing.");
96 }
97
98 if let Ok((val_to_parse, head)) =
99 parse_kv(test_data,
100 "STIM",
101 |c| char::is_digit(c, 10),
102 |c| !char::is_digit(c, 10)) {
103 assert_eq!(val_to_parse, "0");
104 assert_eq!(head, "");
105 } else {
106 assert!(false, "There was an error parsing the very last element.");
107 }
108}
109
110pub fn parse_f64<'a, 'b>(
112 src: &'a str,
113 key: &'b str,
114) -> Result<(Optioned<f64>, &'a str), Box<dyn Error>> {
115 use std::str::FromStr;
116
117 let (val_to_parse, head) = parse_kv(
118 src,
119 key,
120 |c| char::is_digit(c, 10) || c == '-',
121 |c| !(char::is_digit(c, 10) || c == '.' || c == '-'),
122 )?;
123 let val = check_missing(f64::from_str(val_to_parse)?);
124 Ok((val, head))
125}
126
127#[test]
128#[rustfmt::skip]
129fn test_parse_f64() {
130 let test_data =
131 "STID = STNM = 727730 TIME = 170401/0000 \
132 SLAT = 46.92 SLON = -114.08 SELV = 972.0 \
133 STIM = 0";
134
135 if let Ok((lat, head)) = parse_f64(test_data, "SLAT") {
136 assert_eq!(lat, some(46.92));
137 assert_eq!(head, " SLON = -114.08 SELV = 972.0 STIM = 0");
138 } else {
139 assert!(false, "There was an error parsing.");
140 }
141
142 if let Ok((lon, head)) = parse_f64(test_data, "SLON") {
143 assert_eq!(lon, some(-114.08));
144 assert_eq!(head, " SELV = 972.0 STIM = 0");
145 } else {
146 assert!(false, "There was an error parsing.");
147 }
148}
149
150pub fn parse_i32<'a, 'b>(src: &'a str, key: &'b str) -> Result<(i32, &'a str), Box<dyn Error>> {
152 use std::str::FromStr;
153
154 let (val_to_parse, head) = parse_kv(
155 src,
156 key,
157 |c| char::is_digit(c, 10),
158 |c| !char::is_digit(c, 10),
159 )?;
160 let val = i32::from_str(val_to_parse)?;
161 Ok((val, head))
162}
163
164#[test]
165#[rustfmt::skip]
166fn test_parse_i32() {
167 let test_data =
168 "STID = STNM = 727730 TIME = 170401/0000 \
169 SLAT = 46.92 SLON = -114.08 SELV = 972.0 \
170 STIM = 0";
171
172 if let Ok((stnm, head)) = parse_i32(test_data, "STNM") {
173 assert_eq!(stnm, 727730);
174 assert_eq!(head, " TIME = 170401/0000 SLAT = 46.92 SLON = -114.08 SELV = 972.0 STIM = 0");
175 } else {
176 assert!(false, "There was an error parsing.");
177 }
178
179 if let Ok((ymd, head)) = parse_i32(test_data, "TIME") {
180 assert_eq!(ymd, 170401);
181 assert_eq!(head, "/0000 SLAT = 46.92 SLON = -114.08 SELV = 972.0 STIM = 0");
182 } else {
183 assert!(false, "There was an error parsing.");
184 }
185}
186
187pub fn parse_naive_date_time(src: &str) -> Result<NaiveDateTime, Box<dyn Error>> {
189 use std::str::FromStr;
190
191 let val_to_parse = src.trim();
192
193 let year = i32::from_str(&val_to_parse[..2])? + 2000;
194 let month = u32::from_str(&val_to_parse[2..4])?;
195 let day = u32::from_str(&val_to_parse[4..6])?;
196 let hour = u32::from_str(&val_to_parse[7..9])?;
197 let minute = u32::from_str(&val_to_parse[9..11])?;
198
199 NaiveDate::from_ymd_opt(year, month, day)
200 .and_then(|dt| dt.and_hms_opt(hour, minute, 0))
201 .ok_or(Box::new(crate::error::BufkitFileError {}))
202}
203
204#[test]
205fn test_parse_naive_date_time() {
206 let test_data = " 170401/0000 ";
207
208 let test_value = parse_naive_date_time(test_data).unwrap();
209 assert_eq!(test_value, NaiveDate::from_ymd_opt(2017, 4, 1).unwrap().and_hms_opt(0, 0, 0).unwrap());
210}
211
212pub fn find_blank_line(src: &str) -> Option<usize> {
217 let mut first_newline = false;
218
219 let mut iter = src.char_indices().peekable();
220 loop {
221 let (_, c) = iter.next()?;
222
223 if c == '\n' && !first_newline {
224 first_newline = true;
225 } else if c.is_alphanumeric() {
226 first_newline = false;
228 } else if c == '\n' && first_newline {
229 if let Some(&(next_index, _)) = iter.peek() {
231 return Some(next_index);
232 } else {
233 return None;
234 }
235 }
236 }
237}
238
239#[test]
240fn test_find_blank_line() {
241 let test_string = "STID = STNM = 727730 TIME = 170401/0300
242 SLAT = 46.92 SLON = -114.08 SELV = 972.0
243 STIM = 3
244
245 SHOW = 9.67 LIFT = 9.84 SWET = 33.41 KINX = 3.88
246 LCLP = 822.95 PWAT = 9.52 TOTL = 37.25 CAPE = 0.00
247 LCLT = 273.49 CINS = 0.00 EQLV = -9999.00 LFCT = -9999.00
248 BRCH = 0.00
249
250 PRES TMPC TMWC DWPC THTE DRCT SKNT OMEG
251 HGHT
252 906.90 8.04 4.99 1.70 303.11 250.71 4.12 -2.00";
253
254 let (station_info, the_rest) = test_string.split_at(find_blank_line(test_string).unwrap());
255 let (indexes, the_rest) = the_rest.split_at(find_blank_line(the_rest).unwrap());
256
257 assert!(station_info.trim().starts_with("STID = STNM = 727730"));
258 assert!(station_info.trim().ends_with("STIM = 3"));
259
260 assert!(indexes.trim().starts_with("SHOW = 9.67"));
261 assert!(indexes.trim().ends_with("BRCH = 0.00"));
262
263 assert!(the_rest.trim().starts_with("PRES TMPC TMWC"));
264 assert!(find_blank_line(the_rest).is_none());
265}
266
267pub fn find_next_n_tokens(src: &str, n: usize) -> Result<Option<usize>, BufkitFileError> {
269 if src.trim().is_empty() {
270 return Ok(None);
271 }
272
273 let mut started = false;
274 let mut token_count = 0;
275 let mut in_white_space = src.starts_with(char::is_whitespace);
276
277 for (i, c) in src.char_indices() {
278 if !started && (c.is_numeric() || c == '-' || c == '.') {
279 started = true;
280 } else if !in_white_space && c.is_whitespace() {
281 token_count += 1;
283 in_white_space = true;
284 } else if in_white_space && !c.is_whitespace() {
285 in_white_space = false;
287 }
288
289 if token_count == n {
290 return Ok(Some(i));
291 }
292 }
293
294 if !in_white_space && token_count == n - 1 {
296 return Ok(Some(src.len()));
297 }
298
299 if token_count > 0 {
301 return Err(BufkitFileError::new());
302 }
303 Ok(None)
305}
306
307#[test]
308fn test_find_next_n_tokens() {
309 let test_data = "
310 727730 170401/0700 1021.50 869.80 0.14 275.50 0.00 74.00
311 0.00 0.00 277.40 0.00 0.00 0.00
312 0.00 1.00 0.70 0.00 0.07 1.44
313 3.73 0.00 0.00 0.00 0.00 -4.60
314 -4.80 30.30 0.01 999.00 -9999.00 20.00
315 -2.30
316 727730 170401/0800 1022.00 869.70 -0.36 274.90 0.00 74.00
317 0.00 0.00 277.20 0.00 0.00 0.00
318 0.00 1.00 0.50 0.00 0.07 0.34
319 3.60 0.00 0.00 0.00 0.00 -3.70
320 -5.30 35.40 0.01 999.00 -9999.00 20.00
321 -2.78
322 727730 170401/0900 1022.80 869.80 -0.46 274.80 0.00 74.00
323 0.00 0.00 277.10 0.00 0.00 0.00
324 0.00 0.90 0.80 0.00 0.07 -0.56
325 3.50 0.00 0.00 0.00 0.00 -2.70
326 -6.70 31.90 0.01 999.00 -9999.00 20.00
327 -3.15";
328
329 let brk = find_next_n_tokens(test_data, 33).unwrap().unwrap();
330 let (substr, remaining) = test_data.split_at(brk);
331
332 println!("First: {}", substr);
333 assert_eq!(
334 substr,
335 "
336 727730 170401/0700 1021.50 869.80 0.14 275.50 0.00 74.00
337 0.00 0.00 277.40 0.00 0.00 0.00
338 0.00 1.00 0.70 0.00 0.07 1.44
339 3.73 0.00 0.00 0.00 0.00 -4.60
340 -4.80 30.30 0.01 999.00 -9999.00 20.00
341 -2.30"
342 );
343
344 let brk = find_next_n_tokens(remaining, 33).unwrap().unwrap();
345 let (substr, remaining) = remaining.split_at(brk);
346 println!("Second: {}", substr);
347 assert_eq!(
348 substr,
349 "
350 727730 170401/0800 1022.00 869.70 -0.36 274.90 0.00 74.00
351 0.00 0.00 277.20 0.00 0.00 0.00
352 0.00 1.00 0.50 0.00 0.07 0.34
353 3.60 0.00 0.00 0.00 0.00 -3.70
354 -5.30 35.40 0.01 999.00 -9999.00 20.00
355 -2.78"
356 );
357
358 let brk = find_next_n_tokens(remaining, 33).unwrap().unwrap();
359 let (substr, remaining) = remaining.split_at(brk);
360 println!("Third: {}", substr.trim());
361 assert_eq!(
362 substr,
363 "
364 727730 170401/0900 1022.80 869.80 -0.46 274.80 0.00 74.00
365 0.00 0.00 277.10 0.00 0.00 0.00
366 0.00 0.90 0.80 0.00 0.07 -0.56
367 3.50 0.00 0.00 0.00 0.00 -2.70
368 -6.70 31.90 0.01 999.00 -9999.00 20.00
369 -3.15"
370 );
371
372 assert_eq!(find_next_n_tokens(remaining, 33).unwrap(), None);
373}