Skip to main content

kwik/file/text/
reader.rs

1/*
2 * Copyright (c) Kia Shakiba
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8use std::{
9	fs::File,
10	io::{self, BufRead, BufReader, Seek, SeekFrom},
11	path::Path,
12};
13
14use crate::file::FileReader;
15
16/// Reads a text file line-by-line.
17pub struct TextReader {
18	file:  BufReader<File>,
19	buf:   String,
20	count: u64,
21}
22
23pub struct Iter<'a> {
24	reader: &'a mut TextReader,
25}
26
27pub struct IntoIter {
28	reader: TextReader,
29}
30
31impl FileReader for TextReader {
32	fn from_path<P>(path: P) -> io::Result<Self>
33	where
34		Self: Sized,
35		P: AsRef<Path>,
36	{
37		TextReader::from_file(File::open(path)?)
38	}
39
40	fn from_file(file: File) -> io::Result<Self>
41	where
42		Self: Sized,
43	{
44		let reader = TextReader {
45			file:  BufReader::new(file),
46			buf:   String::new(),
47			count: 0,
48		};
49
50		Ok(reader)
51	}
52
53	#[inline]
54	fn size(&self) -> u64 {
55		let metadata = self
56			.file
57			.get_ref()
58			.metadata()
59			.expect("Could not get text file's size");
60
61		metadata.len()
62	}
63}
64
65impl TextReader {
66	/// Reads one line of the text file and returns a `Result` containing
67	/// the line. If the end of the file is reached, an `io::Error` is returned.
68	///
69	/// # Examples
70	/// ```no_run
71	/// use std::io;
72	///
73	/// use kwik::file::{
74	///     FileReader,
75	///     text::TextReader,
76	/// };
77	///
78	/// let mut reader = TextReader::from_path("/path/to/file").unwrap();
79	///
80	/// while let Ok(line) = reader.read_line() {
81	///     // do something with the line
82	/// }
83	/// ```
84	///
85	/// # Errors
86	///
87	/// This function will return an error if the line could not be read.
88	#[inline]
89	pub fn read_line(&mut self) -> io::Result<String> {
90		self.buf.clear();
91
92		self.file
93			.read_line(&mut self.buf)
94			.and_then(|buf_size| {
95				if buf_size == 0 {
96					return Err(io::Error::new(
97						io::ErrorKind::UnexpectedEof,
98						"The end of the file has been reached",
99					));
100				}
101
102				self.count += 1;
103
104				if self.buf.ends_with('\n') {
105					self.buf.pop();
106
107					if self.buf.ends_with('\r') {
108						self.buf.pop();
109					}
110				}
111
112				Ok(self.buf.clone())
113			})
114	}
115
116	/// Returns an iterator over the text file. The iterator takes a mutable
117	/// reference to `self` as it is iterating over a stream. This means
118	/// performing the iteration modifies the reader's position in the file.
119	///
120	/// # Examples
121	/// ```no_run
122	/// use std::io;
123	///
124	/// use kwik::file::{
125	///     FileReader,
126	///     text::TextReader,
127	/// };
128	///
129	/// let mut reader = TextReader::from_path("/path/to/file").unwrap();
130	///
131	/// for line in reader.iter() {
132	///     // do something with the line
133	/// }
134	/// ```
135	#[inline]
136	pub fn iter(&mut self) -> Iter<'_> {
137		Iter {
138			reader: self
139		}
140	}
141}
142
143impl Seek for TextReader {
144	fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
145		self.file.seek(pos)
146	}
147}
148
149impl Iterator for Iter<'_> {
150	type Item = String;
151
152	fn next(&mut self) -> Option<Self::Item> {
153		match self.reader.read_line() {
154			Ok(line) => Some(line),
155			Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => None,
156
157			Err(_) => panic!(
158				"An error occurred on line {} when reading text file",
159				self.reader.count + 1,
160			),
161		}
162	}
163}
164
165impl IntoIterator for TextReader {
166	type Item = String;
167	type IntoIter = IntoIter;
168
169	fn into_iter(self) -> Self::IntoIter {
170		IntoIter {
171			reader: self
172		}
173	}
174}
175
176impl Iterator for IntoIter {
177	type Item = String;
178
179	fn next(&mut self) -> Option<Self::Item> {
180		match self.reader.read_line() {
181			Ok(line) => Some(line),
182			Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => None,
183
184			Err(_) => panic!(
185				"An error occurred on line {} when reading text file",
186				self.reader.count + 1,
187			),
188		}
189	}
190}