rdcl_aoc_helpers/
input.rs

1//! I/O operations.
2use std::fs::File;
3use std::io::{BufRead, BufReader, Lines, Read};
4use std::str::FromStr;
5use std::{fmt, io, marker, mem};
6
7use crate::error::WithOrExit;
8
9/// Iterator over the lines that have been mapped to the requested type.
10pub struct MappedLines<T: FromStr, R: Read> {
11    exit_code_on_fail: i32,
12    lines: Lines<BufReader<R>>,
13    _marker: marker::PhantomData<T>,
14}
15
16impl<T: FromStr, R: Read> Iterator for MappedLines<T, R>
17where
18    <T as FromStr>::Err: fmt::Debug,
19{
20    type Item = T;
21
22    fn next(&mut self) -> Option<Self::Item> {
23        self.lines
24            .next()
25            .map(|line| line.or_exit_with(self.exit_code_on_fail))
26            .map(|line| line.parse::<T>())
27            .map(|record| record.or_exit_with(self.exit_code_on_fail))
28    }
29}
30
31/// This trait allows you to easily read lines from a Readable object.
32pub trait WithReadLines<R: Read> {
33    /// Read lines from a given Readable object, and parse each line to the requested type.
34    fn read_lines<T: FromStr>(self, exit_code_on_fail: i32) -> MappedLines<T, R>;
35}
36
37impl WithReadLines<File> for File {
38    fn read_lines<T: FromStr>(self, exit_code_on_fail: i32) -> MappedLines<T, File> {
39        MappedLines {
40            exit_code_on_fail,
41            lines: BufReader::new(self).lines(),
42            _marker: Default::default(),
43        }
44    }
45}
46
47impl WithReadLines<File> for io::Result<File> {
48    fn read_lines<T: FromStr>(self, exit_code_on_fail: i32) -> MappedLines<T, File> {
49        self.or_exit_with(exit_code_on_fail)
50            .read_lines(exit_code_on_fail)
51    }
52}
53
54/// This trait allows you to easily convert an object to a vec of items of the required type.
55pub trait WithAsRecords {
56    /// Convert to a list of records of the requested type.
57    fn as_records<T: FromStr>(&self) -> Result<Vec<T>, <T as FromStr>::Err>;
58}
59
60impl<'a> WithAsRecords for Vec<&'a str> {
61    fn as_records<T: FromStr>(&self) -> Result<Vec<T>, <T as FromStr>::Err> {
62        let mut records = Vec::new();
63        for line in self {
64            records.push(line.parse::<T>()?);
65        }
66        Ok(records)
67    }
68}
69
70/// Inspired by `std::str::FromStr`, so you can read input files where a record spans multiple
71/// lines.
72pub trait MultilineFromStr {
73    /// The associated error which can be returned from parsing.
74    type Err;
75
76    /// Create a new initial record.
77    fn new() -> Self;
78
79    /// Test for whether a line indicates that a new record starts.
80    fn indicates_new_record(&self, line: &str) -> bool;
81
82    /// Parses a line.
83    fn parse(&mut self, line: &str) -> Result<(), Self::Err>;
84}
85
86/// Iterator over the lines that have been mapped to the requested type.
87pub struct MappedMultiLines<T, U>
88where
89    T: MultilineFromStr,
90    U: Iterator<Item = io::Result<String>>,
91{
92    exit_code_on_fail: i32,
93    lines: U,
94    current: T,
95    exhausted: bool,
96}
97
98impl<T, U> MappedMultiLines<T, U>
99where
100    T: MultilineFromStr + Default,
101    U: Iterator<Item = io::Result<String>>,
102{
103    pub fn from_iterator(iter: U, exit_code_on_fail: i32) -> MappedMultiLines<T, U> {
104        MappedMultiLines {
105            exit_code_on_fail,
106            lines: iter,
107            current: T::default(),
108            exhausted: false,
109        }
110    }
111}
112
113impl<T, U> Iterator for MappedMultiLines<T, U>
114where
115    T: MultilineFromStr,
116    U: Iterator<Item = io::Result<String>>,
117    <T as MultilineFromStr>::Err: fmt::Debug,
118{
119    type Item = T;
120
121    fn next(&mut self) -> Option<Self::Item> {
122        if self.exhausted {
123            None
124        } else {
125            let mut record = mem::replace(&mut self.current, T::new());
126            self.exhausted = true;
127            for line in &mut self.lines {
128                let line = line.or_exit_with(self.exit_code_on_fail);
129                if self.current.indicates_new_record(&line) {
130                    self.exhausted = false;
131                    self.current
132                        .parse(&line)
133                        .or_exit_with(self.exit_code_on_fail);
134                    break;
135                }
136                record.parse(&line).or_exit_with(self.exit_code_on_fail);
137            }
138
139            Some(record)
140        }
141    }
142}
143
144/// This trait allows you to easily read lines from a Readable object.
145pub trait WithReadMultiLines<R: Read> {
146    /// Read lines from a given Readable object, and parse its lines to the requested type.
147    fn read_multi_lines<T: MultilineFromStr>(
148        self,
149        exit_code_on_fail: i32,
150    ) -> MappedMultiLines<T, Lines<BufReader<R>>>;
151}
152
153impl WithReadMultiLines<File> for File {
154    fn read_multi_lines<T: MultilineFromStr>(
155        self,
156        exit_code_on_fail: i32,
157    ) -> MappedMultiLines<T, Lines<BufReader<File>>> {
158        MappedMultiLines {
159            exit_code_on_fail,
160            lines: BufReader::new(self).lines(),
161            current: T::new(),
162            exhausted: false,
163        }
164    }
165}
166
167impl WithReadMultiLines<File> for io::Result<File> {
168    fn read_multi_lines<T: MultilineFromStr>(
169        self,
170        exit_code_on_fail: i32,
171    ) -> MappedMultiLines<T, Lines<BufReader<File>>> {
172        self.or_exit_with(exit_code_on_fail)
173            .read_multi_lines(exit_code_on_fail)
174    }
175}
176
177/// This trait allows you to easily convert an object to a vec of items of the required type.
178pub trait WithAsMultilineRecords {
179    /// Convert to a list of records of the requested type.
180    fn as_multiline_records<T: MultilineFromStr>(
181        &self,
182    ) -> Result<Vec<T>, <T as MultilineFromStr>::Err>;
183}
184
185impl<'a> WithAsMultilineRecords for Vec<&'a str> {
186    fn as_multiline_records<T: MultilineFromStr>(
187        &self,
188    ) -> Result<Vec<T>, <T as MultilineFromStr>::Err> {
189        let mut records = Vec::new();
190        let mut record = T::new();
191        for line in self {
192            if record.indicates_new_record(line) {
193                records.push(record);
194                record = T::new();
195            }
196            record.parse(line)?;
197        }
198        records.push(record);
199        Ok(records)
200    }
201}
202
203#[cfg(test)]
204mod tests {
205    use crate::error::ParseError;
206
207    use super::*;
208
209    #[test]
210    fn test_simple() {
211        let input = vec!["1", "2", "3", "4", "5"].as_records::<u8>().unwrap();
212
213        assert_eq!(input, vec![1, 2, 3, 4, 5]);
214    }
215
216    #[test]
217    fn test_multiline() {
218        let input = vec!["1", "2", "", "3", "4", "", "5"]
219            .as_multiline_records::<Record>()
220            .unwrap();
221
222        assert_eq!(
223            input,
224            vec![
225                Record { items: vec![1, 2] },
226                Record { items: vec![3, 4] },
227                Record { items: vec![5] },
228            ]
229        );
230    }
231
232    #[derive(Debug, Eq, PartialEq)]
233    pub struct Record {
234        items: Vec<u8>,
235    }
236
237    impl MultilineFromStr for Record {
238        type Err = ParseError;
239
240        fn new() -> Self {
241            Record { items: Vec::new() }
242        }
243
244        fn indicates_new_record(&self, line: &str) -> bool {
245            line.is_empty()
246        }
247
248        fn parse(&mut self, line: &str) -> Result<(), Self::Err> {
249            if !line.is_empty() {
250                self.items.push(line.parse::<u8>()?);
251            }
252
253            Ok(())
254        }
255    }
256}