1use ascii::{AsciiChar, AsciiStr, AsciiString};
2use std::io;
3use std::io::{BufRead, BufReader, Read, StdinLock};
4use thiserror::Error;
5
6#[derive(Error, Debug)]
9pub enum AsciiReadError {
10 #[error("io error")]
11 Io(#[from] io::Error),
12 #[error("ascii decoding error")]
13 Ascii(#[from] ascii::AsAsciiStrError),
14}
15
16pub trait AsciiBufRead: BufRead {
17 fn read_ascii_line(&mut self, buf: &mut AsciiString) -> Result<usize, AsciiReadError> {
28 let mut s = String::new();
29 let n = self.read_line(&mut s)?;
30 buf.push_str(AsciiStr::from_ascii(&s)?);
31 Ok(n)
32 }
33
34 fn ascii_lines(self) -> AsciiLines<Self>
39 where
40 Self: Sized,
41 {
42 AsciiLines { buf: self }
43 }
44}
45
46impl<R: Read> AsciiBufRead for BufReader<R> {}
47impl AsciiBufRead for StdinLock<'_> {}
48
49#[derive(Debug)]
56pub struct AsciiLines<B> {
57 buf: B,
58}
59
60impl<B: AsciiBufRead> Iterator for AsciiLines<B> {
61 type Item = Result<AsciiString, AsciiReadError>;
62
63 fn next(&mut self) -> Option<Self::Item> {
64 let mut buf = AsciiString::new();
65 match self.buf.read_ascii_line(&mut buf) {
66 Ok(0) => None,
67 Ok(_n) => {
68 if buf[buf.len() - 1] == AsciiChar::LineFeed {
69 let _ = buf.pop();
70 if !buf.is_empty() && buf[buf.len() - 1] == AsciiChar::CarriageReturn {
71 let _ = buf.pop();
72 }
73 }
74 Some(Ok(buf))
75 }
76 Err(e) => Some(Err(e)),
77 }
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84 use std::env;
85 use std::fs::File;
86 use std::path::Path;
87
88 #[test]
93 fn test_read_ascii_line_bufreader() {
94 let manifest_path = Path::new(
95 &env::var("CARGO_MANIFEST_DIR")
96 .expect("Environment variable CARGO_MANIFEST_DIR not set."),
97 )
98 .join("resources/test/file.txt");
99 let file = File::open(&manifest_path).expect("Failed to open {manifest_path:?}");
100 let mut reader = BufReader::new(file);
101 let mut asciistring = AsciiString::new();
102 loop {
103 let n = reader
104 .read_ascii_line(&mut asciistring)
105 .expect("Panic at read_ascii_line()");
106 if n == 0 {
107 break;
108 }
109 }
110 assert_eq!(asciistring, "this is a test file\nsecond line\nhola\n");
111 }
112}