token_read/
reader.rs

1use std::io::{BufRead, BufReader, Lines, Read};
2
3use crate::{FromTokens, LineCount, ReadLineError, ReadTokensError, Take};
4
5#[cfg(doc)]
6use std::io::Stdin;
7
8/// This struct wraps a [`BufReader`] to allow easy parsing of whitespace delimited files.
9#[derive(Debug)]
10pub struct TokenReader<R> {
11    lines: Lines<R>,
12}
13
14impl<R: BufRead> TokenReader<R> {
15    /// Creates a [`TokenReader`] from a type that implements [`BufRead`], such as [`Stdin`].
16    pub fn new(buf_read: R) -> Self {
17        TokenReader {
18            lines: buf_read.lines(),
19        }
20    }
21
22    /// Reads and parses a single line of whitespace delimited tokens.
23    ///
24    /// # Examples
25    ///
26    /// ```
27    /// # use token_read::TokenReader;
28    /// # use anyhow::Result;
29    /// #
30    /// # fn main() -> Result<()> {
31    /// let mut input = TokenReader::new("James 158000 0.58".as_bytes());
32    /// let (name, points, win_chance): (String, u64, f64) = input.line()?;
33    ///
34    /// assert_eq!(name, "James");
35    /// assert_eq!(points, 158000);
36    /// assert_eq!(win_chance, 0.58);
37    /// #
38    /// #   Ok(())
39    /// # }
40    /// ```
41    ///
42    /// ```
43    /// # use token_read::TokenReader;
44    /// # use anyhow::Result;
45    /// #
46    /// # fn main() -> Result<()> {
47    /// let mut input = TokenReader::new("13 8 17".as_bytes());
48    /// let numbers: Vec<i64> = input.line()?;
49    ///
50    /// assert_eq!(numbers, vec![13, 8, 17]);
51    /// #
52    /// #   Ok(())
53    /// # }
54    /// ```
55    pub fn line<T>(&mut self) -> Result<T, ReadTokensError<T::Error>>
56    where
57        T: FromTokens,
58    {
59        let line = self.line_raw()?;
60        let tokens = line.split_whitespace();
61
62        T::from_tokens(tokens).map_err(|source| ReadTokensError::ParseError { source, line })
63    }
64
65    /// Reads a single line, unmodified.
66    ///
67    /// # Example
68    ///
69    /// ```
70    /// # use token_read::TokenReader;
71    /// # use anyhow::Result;
72    /// #
73    /// # fn main() -> Result<()> {
74    /// let mut input = TokenReader::new("1. Write a parsing library in Rust.\n2. ???\n3. Profit!".as_bytes());
75    /// let line = input.line_raw()?;
76    ///
77    /// assert_eq!(line, "1. Write a parsing library in Rust.");
78    /// #
79    /// #   Ok(())
80    /// # }
81    /// ```
82    pub fn line_raw(&mut self) -> Result<String, ReadLineError> {
83        let line = self.lines.next();
84        let line = line.ok_or_else(|| ReadLineError::EndOfFile)?;
85        let line = line.map_err(|source| ReadLineError::IoError { source })?;
86
87        Ok(line)
88    }
89
90    /// Creates an iterator that reads and parses a specific number of lines.
91    ///
92    /// The line count must be an [`usize`].
93    /// You can also use [`TokenReader::take_count`], which allows other types.
94    ///
95    /// # Example
96    ///
97    /// ```
98    /// # use token_read::TokenReader;
99    /// # use anyhow::Result;
100    /// #
101    /// # fn main() -> Result<()> {
102    /// let mut input = TokenReader::new("1 a\n2 b\n3 c".as_bytes());
103    /// let lines: Vec<(u64, char)> = input.take(3).collect::<Result<_, _>>()?;
104    ///
105    /// assert_eq!(lines, vec![(1, 'a'), (2, 'b'), (3, 'c')]);
106    /// #
107    /// #   Ok(())
108    /// # }
109    /// ```
110    pub fn take<T>(&mut self, count: usize) -> Take<'_, T, R, usize>
111    where
112        T: FromTokens,
113    {
114        Take::new(self, count)
115    }
116
117    /// Like [`TokenReader::take`], but can use non-[`usize`] counts.
118    ///
119    /// This method can use any type implementing [`LineCount`] as the element count, like [`u32`].
120    /// It can be used to process more than 2^32 lines on 32-bit systems.
121    ///
122    /// # Example
123    ///
124    /// ```
125    /// # use token_read::TokenReader;
126    /// # use anyhow::Result;
127    /// #
128    /// # fn main() -> Result<()> {
129    /// let mut input = TokenReader::new("1 a\n2 b\n3 c".as_bytes());
130    /// let lines: Vec<(u64, char)> = input.take_count(3u64).collect::<Result<_, _>>()?;
131    ///
132    /// assert_eq!(lines, vec![(1, 'a'), (2, 'b'), (3, 'c')]);
133    /// #
134    /// #   Ok(())
135    /// # }
136    /// ```
137    pub fn take_count<T, S>(&mut self, count: S) -> Take<'_, T, R, S>
138    where
139        T: FromTokens,
140        S: LineCount,
141    {
142        Take::new(self, count)
143    }
144}
145
146impl<R: Read> TokenReader<BufReader<R>> {
147    /// Creates a [`TokenReader`] from a type that implements [`Read`].
148    ///
149    /// This is a convenience method for wrapping the reader with [`BufReader`].
150    pub fn from_read(read: R) -> Self {
151        TokenReader::new(BufReader::new(read))
152    }
153}
154
155impl<R> From<R> for TokenReader<R>
156where
157    R: BufRead,
158{
159    /// Wraps an implementation of [`BufRead`].
160    ///
161    /// Identical to [`TokenReader::new`].
162    fn from(value: R) -> Self {
163        TokenReader::new(value)
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use crate::{ReadLineError, ReadTokensError, TokenReader};
170
171    #[test]
172    fn can_be_constructed_from_bufread() {
173        let mut input = TokenReader::new("Hello".as_bytes());
174        assert_eq!(input.line_raw().unwrap(), "Hello");
175    }
176
177    #[test]
178    fn can_be_constructed_from_read() {
179        let mut input = TokenReader::from_read("Hello".as_bytes());
180        assert_eq!(input.line_raw().unwrap(), "Hello");
181    }
182
183    #[test]
184    fn can_be_constructed_with_from() {
185        let mut input: TokenReader<_> = "Hello".as_bytes().into();
186        assert_eq!(input.line_raw().unwrap(), "Hello");
187    }
188
189    #[test]
190    fn reads_raw_lines() {
191        let mut input = TokenReader::new("First\nSecond\n".as_bytes());
192        assert_eq!(input.line_raw().unwrap(), "First");
193        assert_eq!(input.line_raw().unwrap(), "Second");
194        assert!(matches!(input.line_raw(), Err(ReadLineError::EndOfFile)));
195    }
196
197    #[test]
198    fn reads_single_value() {
199        let mut input = TokenReader::new("13".as_bytes());
200        let value: Vec<i8> = input.line().unwrap();
201        assert_eq!(value, vec![13]);
202    }
203
204    #[test]
205    fn reads_multiple_values() {
206        let mut input = TokenReader::new("40 50 60".as_bytes());
207        let value: Vec<i8> = input.line().unwrap();
208        assert_eq!(value, vec![40, 50, 60]);
209    }
210
211    #[test]
212    fn reads_empty_values() {
213        let mut input = TokenReader::new(" ".as_bytes());
214        let value: Vec<i8> = input.line().unwrap();
215        assert_eq!(value, vec![]);
216    }
217
218    #[test]
219    fn ignores_multiple_whitespace_characters() {
220        let mut input = TokenReader::new("1\t\r    \t  7".as_bytes());
221        let value: Vec<i8> = input.line().unwrap();
222        assert_eq!(value, vec![1, 7]);
223    }
224
225    #[test]
226    fn ignores_start_and_end() {
227        let mut input = TokenReader::new(" \t123 \r".as_bytes());
228        let value: Vec<i8> = input.line().unwrap();
229        assert_eq!(value, vec![123]);
230    }
231
232    #[test]
233    fn returns_end_of_file() {
234        let mut input = TokenReader::new("5\n".as_bytes());
235
236        let _ = input.line::<Vec<i8>>().unwrap();
237        let result = input.line::<Vec<i8>>();
238
239        assert!(matches!(result, Err(ReadTokensError::EndOfFile)));
240    }
241
242    #[test]
243    fn returns_parse_error() {
244        let mut input = TokenReader::new("one\n".as_bytes());
245
246        let result = input.line::<Vec<i8>>();
247
248        match result {
249            Err(ReadTokensError::ParseError { source: _, line }) => {
250                assert_eq!(line, "one");
251            }
252            _ => panic!("expected error, got {result:?}"),
253        }
254    }
255
256    #[test]
257    fn take_gets_multiple_lines() {
258        let mut input = TokenReader::new("0\n1\n2\nx".as_bytes());
259
260        for (i, value) in input.take(3).enumerate() {
261            let (value,): (usize,) = value.unwrap();
262            assert_eq!(value, i);
263        }
264    }
265
266    #[test]
267    fn take_count_gets_multiple_lines() {
268        let mut input = TokenReader::new("0\n1\n2\nx".as_bytes());
269
270        for (i, value) in input.take_count(3u64).enumerate() {
271            let (value,): (usize,) = value.unwrap();
272            assert_eq!(value, i);
273        }
274    }
275}