1use 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
9pub 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
31pub trait WithReadLines<R: Read> {
33 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
54pub trait WithAsRecords {
56 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
70pub trait MultilineFromStr {
73 type Err;
75
76 fn new() -> Self;
78
79 fn indicates_new_record(&self, line: &str) -> bool;
81
82 fn parse(&mut self, line: &str) -> Result<(), Self::Err>;
84}
85
86pub 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
144pub trait WithReadMultiLines<R: Read> {
146 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
177pub trait WithAsMultilineRecords {
179 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}