1#![allow(dead_code)]
2use std::io::{self, BufRead, BufReader, Read, Seek, SeekFrom};
13
14pub const STOP_REASON_BOF: &str = "bof";
16pub const STOP_REASON_EOF: &str = "eof";
18pub const STOP_REASON_READ_LIMIT: &str = "read_limit";
20
21pub fn read_lines<R: Read>(
29 reader: R,
30 line_count: usize,
31 skip_lines: usize,
32 read_limit: usize,
33) -> io::Result<(Vec<String>, String)> {
34 let mut buf_reader = BufReader::new(reader);
35 let mut lines = Vec::new();
36 let mut bytes_read = 0usize;
37 let mut skipped = 0usize;
38
39 loop {
40 let mut line = String::new();
41 match buf_reader.read_line(&mut line) {
42 Ok(0) => return Ok((lines, STOP_REASON_EOF.to_string())),
43 Ok(n) => {
44 bytes_read += n;
45
46 if skipped < skip_lines {
47 skipped += 1;
48 } else {
49 lines.push(line.clone());
50 if line_count > 0 && lines.len() >= line_count {
51 return Ok((lines, String::new()));
52 }
53 }
54
55 if read_limit > 0 && bytes_read >= read_limit {
56 return Ok((lines, STOP_REASON_READ_LIMIT.to_string()));
57 }
58 }
59 Err(e) => return Err(e),
60 }
61 }
62}
63
64pub fn read_last_n_line_offsets<R: Read + Seek>(
69 rs: &mut R,
70 max_lines: usize,
71 keep_first: bool,
72) -> io::Result<(Vec<i64>, usize)> {
73 rs.seek(SeekFrom::Start(0))?;
74
75 let mut offsets: Vec<i64> = Vec::new();
76 let mut reader = BufReader::new(rs);
77 let mut current_pos: i64 = 0;
78 let mut total_lines = 0;
79
80 if keep_first {
81 offsets.push(0);
82 total_lines = 1;
83 }
84
85 loop {
86 let mut line = Vec::new();
87 match reader.read_until(b'\n', &mut line) {
88 Ok(0) => break,
89 Ok(n) => {
90 current_pos += n as i64;
91 offsets.push(current_pos);
92 total_lines += 1;
93 if offsets.len() > max_lines + 1 {
94 offsets.remove(0);
95 }
96 }
97 Err(e) => return Err(e),
98 }
99 }
100
101 if !offsets.is_empty() {
102 offsets.pop();
103 total_lines -= 1;
104 }
105
106 Ok((offsets, total_lines))
107}
108
109fn read_tail_lines_internal<R: Read + Seek>(
114 rs: &mut R,
115 line_count: usize,
116 line_offset: usize,
117 keep_first: bool,
118) -> io::Result<(Vec<String>, bool)> {
119 let max_offsets = line_count + line_offset;
120 let (offsets, total_lines) = read_last_n_line_offsets(rs, max_offsets, keep_first)?;
121
122 if total_lines <= line_offset {
123 return Ok((Vec::new(), false));
124 }
125
126 let lines_to_read = line_count.min(total_lines - line_offset);
127 let start_idx = offsets.len().saturating_sub(line_offset + lines_to_read);
128 let has_more = total_lines > line_count + line_offset;
129
130 rs.seek(SeekFrom::Start(offsets[start_idx] as u64))?;
131
132 let (lines, _) = read_lines(rs, lines_to_read, 0, 0)?;
133 Ok((lines, has_more))
134}
135
136pub fn read_tail_lines<R: Read + Seek>(
143 rs: &mut R,
144 total_size: u64,
145 line_count: usize,
146 line_offset: usize,
147 read_limit: u64,
148) -> io::Result<(Vec<String>, String)> {
149 if read_limit == 0 {
150 return Err(io::Error::new(
151 io::ErrorKind::InvalidInput,
152 format!("read_limit must be positive, got {}", read_limit),
153 ));
154 }
155
156 let mut read_bytes: u64 = (1024 * 1024).min(read_limit);
157
158 loop {
159 let start_pos = if total_size > read_bytes {
160 total_size - read_bytes
161 } else {
162 read_bytes = total_size;
163 0
164 };
165
166 rs.seek(SeekFrom::Start(start_pos))?;
167 let keep_first = start_pos == 0;
168
169 let mut section = vec![0u8; read_bytes as usize];
171 let actually_read = rs.read(&mut section)?;
172 section.truncate(actually_read);
173
174 let mut cursor = io::Cursor::new(section);
175 let (lines, has_more_in_window) =
176 read_tail_lines_internal(&mut cursor, line_count, line_offset, keep_first)?;
177
178 if lines.len() == line_count {
179 let has_more = start_pos > 0 || has_more_in_window;
180 if !has_more {
181 return Ok((lines, STOP_REASON_BOF.to_string()));
182 }
183 return Ok((lines, String::new()));
184 }
185
186 if read_bytes >= read_limit || read_bytes >= total_size {
187 if start_pos > 0 {
188 return Ok((lines, STOP_REASON_READ_LIMIT.to_string()));
189 }
190 return Ok((lines, STOP_REASON_BOF.to_string()));
191 }
192
193 read_bytes = (read_bytes * 2).min(read_limit);
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200
201 #[test]
204 fn test_read_lines_basic() {
205 let data = b"line1\nline2\nline3\n";
206 let (lines, reason) = read_lines(&data[..], 0, 0, 0).unwrap();
207 assert_eq!(lines.len(), 3);
208 assert_eq!(reason, STOP_REASON_EOF);
209 }
210
211 #[test]
212 fn test_read_lines_with_limit() {
213 let data = b"line1\nline2\nline3\n";
214 let (lines, reason) = read_lines(&data[..], 2, 0, 0).unwrap();
215 assert_eq!(lines.len(), 2);
216 assert_eq!(reason, ""); }
218
219 #[test]
220 fn test_read_lines_with_skip() {
221 let data = b"skip1\nskip2\nkeep1\nkeep2\n";
222 let (lines, reason) = read_lines(&data[..], 0, 2, 0).unwrap();
223 assert_eq!(lines.len(), 2);
224 assert!(lines[0].contains("keep1"));
225 assert_eq!(reason, STOP_REASON_EOF);
226 }
227
228 #[test]
229 fn test_read_lines_byte_limit() {
230 let data = b"line1\nline2\nline3\n";
231 let (lines, reason) = read_lines(&data[..], 0, 0, 12).unwrap();
232 assert_eq!(reason, STOP_REASON_READ_LIMIT);
233 assert!(lines.len() >= 1);
234 }
235
236 #[test]
237 fn test_read_lines_empty() {
238 let data = b"";
239 let (lines, reason) = read_lines(&data[..], 0, 0, 0).unwrap();
240 assert!(lines.is_empty());
241 assert_eq!(reason, STOP_REASON_EOF);
242 }
243
244 #[test]
247 fn test_line_offsets_basic() {
248 let data = b"line1\nline2\nline3\n";
249 let mut cursor = io::Cursor::new(data.as_slice());
250 let (offsets, total) = read_last_n_line_offsets(&mut cursor, 10, false).unwrap();
251 assert_eq!(total, 2);
254 assert_eq!(offsets.len(), 2);
255 assert_eq!(offsets[0], 6); assert_eq!(offsets[1], 12); }
258
259 #[test]
260 fn test_line_offsets_keep_first() {
261 let data = b"line1\nline2\nline3\n";
262 let mut cursor = io::Cursor::new(data.as_slice());
263 let (offsets, total) = read_last_n_line_offsets(&mut cursor, 10, true).unwrap();
264 assert_eq!(total, 3);
267 assert_eq!(offsets[0], 0); assert_eq!(offsets[1], 6);
269 assert_eq!(offsets[2], 12);
270 }
271
272 #[test]
273 fn test_line_offsets_max_lines() {
274 let data = b"1\n2\n3\n4\n5\n";
275 let mut cursor = io::Cursor::new(data.as_slice());
276 let (offsets, total) = read_last_n_line_offsets(&mut cursor, 2, false).unwrap();
277 assert_eq!(total, 4);
279 assert_eq!(offsets.len(), 2); }
281
282 #[test]
285 fn test_tail_lines_basic() {
286 let data = b"line1\nline2\nline3\nline4\nline5\n";
287 let mut cursor = io::Cursor::new(data.as_slice());
288 let size = data.len() as u64;
289 let (lines, reason) = read_tail_lines(&mut cursor, size, 3, 0, 1024 * 1024).unwrap();
290 assert_eq!(lines.len(), 3);
291 assert!(lines[0].contains("line3"));
292 assert!(lines[1].contains("line4"));
293 assert!(lines[2].contains("line5"));
294 assert_eq!(reason, ""); }
296
297 #[test]
298 fn test_tail_lines_all() {
299 let data = b"line1\nline2\n";
300 let mut cursor = io::Cursor::new(data.as_slice());
301 let size = data.len() as u64;
302 let (lines, reason) = read_tail_lines(&mut cursor, size, 10, 0, 1024 * 1024).unwrap();
303 assert_eq!(lines.len(), 2);
304 assert_eq!(reason, STOP_REASON_BOF);
305 }
306
307 #[test]
308 fn test_tail_lines_with_offset() {
309 let data = b"line1\nline2\nline3\nline4\nline5\n";
310 let mut cursor = io::Cursor::new(data.as_slice());
311 let size = data.len() as u64;
312 let (lines, _) = read_tail_lines(&mut cursor, size, 2, 1, 1024 * 1024).unwrap();
313 assert_eq!(lines.len(), 2);
314 assert!(lines[0].contains("line3"));
316 assert!(lines[1].contains("line4"));
317 }
318
319 #[test]
320 fn test_tail_lines_invalid_limit() {
321 let data = b"line1\n";
322 let mut cursor = io::Cursor::new(data.as_slice());
323 let result = read_tail_lines(&mut cursor, data.len() as u64, 1, 0, 0);
324 assert!(result.is_err());
325 }
326
327 #[test]
328 fn test_tail_lines_empty() {
329 let data = b"";
330 let mut cursor = io::Cursor::new(data.as_slice());
331 let (lines, reason) = read_tail_lines(&mut cursor, 0, 10, 0, 1024 * 1024).unwrap();
332 assert!(lines.is_empty());
333 assert_eq!(reason, STOP_REASON_BOF);
334 }
335}