1use std::io::{self, BufRead, BufReader, Read, Write};
7use std::time::{Duration, Instant};
8
9use bytes::BytesMut;
10use miette::{IntoDiagnostic, Result, WrapErr};
11
12use crate::defaults::io::*;
13use crate::defaults::memory::*;
14use crate::defaults::processing::BLOCK_SIZE;
15use crate::{config, process_line, strip_line_ending};
16
17pub fn handle_stdin() -> Result<()> {
19 let offset = config::offset();
20 let offset_unit = config::offset_unit();
21
22 let mut processor = StdinProcessor::new();
23
24 if offset == 0 {
25 return processor.tail();
26 }
27
28 match (offset.is_positive(), offset_unit) {
29 (true, config::OffsetUnit::Lines) => processor.skip_lines(offset as u64),
31 (true, config::OffsetUnit::Bytes) => processor.skip_bytes(offset as u64),
32 (true, config::OffsetUnit::Blocks) => {
33 let bytes_to_skip = (offset as u64) * BLOCK_SIZE;
34 processor.skip_bytes(bytes_to_skip)
35 }
36
37 (false, config::OffsetUnit::Lines) => processor.backtrack_lines((-offset) as u64),
39 (false, config::OffsetUnit::Bytes) => processor.backtrack_bytes((-offset) as u64),
40 (false, config::OffsetUnit::Blocks) => processor.backtrack_bytes(((-offset) as u64) * BLOCK_SIZE),
41 }
42}
43
44pub struct StdinProcessor<'a> {
47 inlock: io::StdinLock<'a>,
48 outlock: io::StdoutLock<'a>,
49 buffer: BytesMut,
50 line: String,
51 count: u16,
52}
53
54impl<'a> Default for StdinProcessor<'a> {
55 fn default() -> Self {
56 Self::new()
57 }
58}
59
60impl<'a> StdinProcessor<'a> {
61 pub fn new() -> Self {
63 Self {
64 inlock: io::stdin().lock(),
65 outlock: io::stdout().lock(),
66 buffer: BytesMut::with_capacity(OUTPUT_BUFFER_CAPACITY),
67 line: String::with_capacity(LINE_CAPACITY),
68 count: 0,
69 }
70 }
71
72 pub fn process_line(&mut self, line: &str) -> Result<()> {
74 process_line(line, &mut self.buffer, &mut self.outlock).with_context(|| "Failed to process line")?;
75 self.count += 1;
76 self.flush_if_needed()
77 }
78
79 pub fn flush_if_needed(&mut self) -> Result<()> {
81 if self.count >= FLUSH_LINE_COUNT {
82 self.outlock.flush().into_diagnostic()?;
83 self.count = 0;
84 }
85 Ok(())
86 }
87
88 pub fn flush(&mut self) -> Result<()> {
90 self.outlock.flush().into_diagnostic()?;
91 self.count = 0;
92 Ok(())
93 }
94
95 pub fn read_line(&mut self) -> Result<usize> {
97 self.line.clear();
98 let bytes_read = self.inlock.read_line(&mut self.line).into_diagnostic()?;
99 if bytes_read > 0 {
100 strip_line_ending(&mut self.line);
101 }
102 Ok(bytes_read)
103 }
104
105 pub fn line(&self) -> &str {
107 &self.line
108 }
109
110 pub fn process_to_end(&mut self) -> Result<()> {
112 while self.read_line()? != 0 {
113 let line = self.line().to_string();
114 self.process_line(&line)?;
115 }
116 self.flush()
117 }
118
119 pub fn handle_overshoot(&mut self, overshoot: &[u8]) -> Result<()> {
122 let mut start = 0;
124 for (i, &byte) in overshoot.iter().enumerate() {
125 if byte == b'\n' {
126 let line_bytes = &overshoot[start..i];
128 let line = String::from_utf8_lossy(line_bytes);
129 self.process_line(&line)?;
130 start = i + 1;
131 }
132 }
133
134 if start < overshoot.len() {
136 let remaining_bytes = &overshoot[start..];
137 let remaining_str = String::from_utf8_lossy(remaining_bytes);
138 self.line.push_str(&remaining_str);
139 }
140
141 if !self.line.is_empty() && self.inlock.read_line(&mut self.line).into_diagnostic()? > 0 {
143 strip_line_ending(&mut self.line);
144 let line = self.line().to_string();
145 self.process_line(&line)?;
146 }
147
148 self.tail()
150 }
151
152 pub fn tail(&mut self) -> Result<()> {
155 self.process_to_end()?;
156 if !config::tailing() {
157 return Ok(());
158 }
159
160 let mut last_flush = Instant::now();
161 loop {
162 std::thread::sleep(Duration::from_millis(100));
163
164 match self.read_line()? {
165 0 => continue, _ => {
167 let line = self.line().to_string();
168 self.process_line(&line)?;
169 if last_flush.elapsed() >= TAIL_FLUSH_INTERVAL {
170 self.flush()?;
171 last_flush = Instant::now();
172 }
173 }
174 }
175 }
176 }
177
178 pub fn skip_lines(&mut self, count: u64) -> Result<()> {
179 let mut lines_skipped = 0u64;
181 while lines_skipped < count {
182 match self.read_line()? {
183 0 => {
184 return Ok(());
186 }
187 _ => {
188 lines_skipped += 1;
189 }
190 }
191 }
192 self.tail()
193 }
194
195 pub fn skip_bytes(&mut self, to_skip: u64) -> Result<()> {
197 let mut buffer = [0u8; READ_BUFFER_SIZE];
198 let mut bytes_skipped = 0u64;
199
200 while bytes_skipped < to_skip {
201 let bytes_read = self.inlock.read(&mut buffer).into_diagnostic()?;
202 if bytes_read == 0 {
203 return Ok(());
205 }
206
207 let bytes_to_consume = std::cmp::min(bytes_read as u64, to_skip - bytes_skipped);
208 bytes_skipped += bytes_to_consume;
209
210 if bytes_skipped == to_skip && bytes_to_consume < bytes_read as u64 {
212 let overshoot_start = bytes_to_consume as usize;
213 let overshoot = &buffer[overshoot_start..bytes_read];
214 return self.handle_overshoot(overshoot);
215 }
216 }
217
218 self.tail()
220 }
221
222 pub fn backtrack_bytes(&mut self, bytes_to_show: u64) -> Result<()> {
223 let mut circular_buffer = CircularByteBuffer::new(bytes_to_show as usize);
224
225 loop {
227 let bytes_read = self.inlock.read(&mut self.buffer).into_diagnostic()?;
228 if bytes_read == 0 {
229 break; }
231 circular_buffer.write(&self.buffer[..bytes_read]);
232 }
233
234 if circular_buffer.is_empty() {
236 return Ok(()); }
238
239 let mut overshoot: Vec<u8> = Vec::new();
240 let output_bytes = circular_buffer.extract_last_bytes();
241 let process_this = match find_last_char(output_bytes.as_slice(), b'\n') {
242 Some(last_line_ending) => {
243 overshoot = output_bytes[last_line_ending + 1..].to_vec();
244 output_bytes[..last_line_ending].to_vec()
245 }
246 None => output_bytes[..].to_vec(),
247 };
248
249 let output_str = String::from_utf8_lossy(&process_this);
251 for line in output_str.lines() {
252 self.process_line(line)?;
253 }
254 if !overshoot.is_empty() {
256 self.handle_overshoot(overshoot.as_slice())
257 } else {
258 self.tail()
259 }
260 }
261
262 pub fn backtrack_lines(&mut self, lines_to_show: u64) -> Result<()> {
264 use std::collections::VecDeque;
265
266 use tempfile::NamedTempFile;
267
268 let mut line_buffer: VecDeque<String> = VecDeque::with_capacity(lines_to_show as usize);
269 let mut memory_used = 0usize;
270 let mut temp_file: Option<NamedTempFile> = None;
271
272 loop {
274 let bytes_read = self.read_line()?;
275 if bytes_read == 0 {
276 break; }
278
279 if memory_used > MEMORY_LIMIT_BYTES && temp_file.is_none() {
281 let mut temp = NamedTempFile::new()
283 .into_diagnostic()
284 .wrap_err("Failed to create temporary file for large stdin backtrack")?;
285
286 for line in &line_buffer {
288 writeln!(temp, "{}", line)
289 .into_diagnostic()
290 .wrap_err("Failed to write to temporary file")?;
291 }
292
293 temp_file = Some(temp);
294
295 line_buffer.clear();
297 memory_used = 0;
298 }
299
300 match &mut temp_file {
301 Some(temp) => {
302 writeln!(temp, "{}", self.line)
304 .into_diagnostic()
305 .wrap_err("Failed to write to temporary file")?;
306 }
307 None => {
308 if line_buffer.len() >= lines_to_show as usize {
310 if let Some(old_line) = line_buffer.pop_front() {
312 memory_used -= old_line.len();
313 }
314 }
315 memory_used += self.line.len();
316 line_buffer.push_back(self.line.clone());
317 }
318 }
319 }
320
321 match temp_file {
323 Some(mut temp) => {
324 temp.flush()
326 .into_diagnostic()
327 .wrap_err("Failed to flush temporary file")?;
328 self.read_last_n_lines_from_temp_file(temp, lines_to_show)?;
329 }
330 None => {
331 for buffered_line in line_buffer {
333 self.process_line(&buffered_line)?;
334 }
335 }
336 }
337
338 self.flush()?;
339 Ok(())
340 }
341
342 fn read_last_n_lines_from_temp_file(
344 &mut self,
345 temp_file: tempfile::NamedTempFile,
346 lines_to_show: u64,
347 ) -> Result<()> {
348 use std::collections::VecDeque;
349 use std::fs::File;
350
351 let file = File::open(temp_file.path())
353 .into_diagnostic()
354 .wrap_err("Failed to open temporary file for reading")?;
355 let reader = BufReader::new(file);
356
357 let mut line_buffer: VecDeque<String> = VecDeque::with_capacity(lines_to_show as usize);
358
359 for line_result in reader.lines() {
361 let line = line_result
362 .into_diagnostic()
363 .wrap_err("Failed to read line from temporary file")?;
364
365 if line_buffer.len() >= lines_to_show as usize {
366 line_buffer.pop_front();
367 }
368 line_buffer.push_back(line);
369 }
370
371 for line in line_buffer {
373 self.process_line(&line)?;
374 }
375
376 Ok(())
378 }
379}
380
381pub struct CircularByteBuffer {
383 buffer: Vec<u8>,
384 pos: usize,
385 total_read: u64,
386 capacity: usize,
387}
388
389impl CircularByteBuffer {
390 pub fn new(capacity: usize) -> Self {
392 Self {
393 buffer: vec![0u8; capacity],
394 pos: 0,
395 total_read: 0,
396 capacity,
397 }
398 }
399
400 pub fn write(&mut self, data: &[u8]) {
402 for &byte in data {
403 self.buffer[self.pos % self.capacity] = byte;
404 self.pos += 1;
405 self.total_read += 1;
406 }
407 }
408
409 pub fn extract_last_bytes(&self) -> Vec<u8> {
411 if self.total_read == 0 {
412 return Vec::new();
413 }
414
415 let bytes_to_output = std::cmp::min(self.total_read, self.capacity as u64) as usize;
416
417 if self.total_read >= self.capacity as u64 {
418 let start_pos = self.pos % self.capacity;
420 let mut result = Vec::with_capacity(bytes_to_output);
421 for i in 0..bytes_to_output {
422 result.push(self.buffer[(start_pos + i) % self.capacity]);
423 }
424 result
425 } else {
426 self.buffer[..bytes_to_output].to_vec()
428 }
429 }
430
431 pub fn is_empty(&self) -> bool {
433 self.total_read == 0
434 }
435
436 pub fn total_written(&self) -> u64 {
438 self.total_read
439 }
440}
441
442fn find_last_char(buffer: &[u8], c: u8) -> Option<usize> {
443 buffer.iter().rposition(|&b| b == c)
444}
445
446#[cfg(test)]
447mod tests {
448 use super::*;
449
450 #[test]
451 fn can_find_last_char() {
452 assert_eq!(find_last_char(b"hello\nworld\n", b'\n'), Some(11));
454 assert_eq!(find_last_char(b"hello\nworld", b'\n'), Some(5));
455
456 assert_eq!(find_last_char(b"hello world", b'\n'), None);
458
459 assert_eq!(find_last_char(b"", b'\n'), None);
461
462 assert_eq!(find_last_char(b"\n", b'\n'), Some(0));
464 assert_eq!(find_last_char(b"a", b'\n'), None);
465
466 assert_eq!(find_last_char(b"\n\n\n", b'\n'), Some(2));
468 }
469
470 #[test]
471 fn circular_buffer_edge_cases() {
472 let mut buffer = CircularByteBuffer::new(5);
474 buffer.write(b"12345");
475 assert_eq!(buffer.extract_last_bytes(), b"12345");
476
477 buffer.write(b"67890");
479 assert_eq!(buffer.extract_last_bytes(), b"67890");
480
481 let mut buffer2 = CircularByteBuffer::new(10);
483 buffer2.write(b"abc");
484 assert_eq!(buffer2.extract_last_bytes(), b"abc");
485
486 buffer2.write(b"def");
488 buffer2.write(b"ghi");
489 assert_eq!(buffer2.extract_last_bytes(), b"abcdefghi");
490 }
491
492 #[test]
493 fn backtracking_with_partial_lines() {
494 let buffer_with_newline = b"line1\nline2\nline3\n";
499 assert_eq!(find_last_char(buffer_with_newline, b'\n'), Some(17));
500
501 let buffer_without_newline = b"line1\nline2\nline3";
503 assert_eq!(find_last_char(buffer_without_newline, b'\n'), Some(11));
504
505 let buffer_no_newlines = b"single long line without newlines";
507 assert_eq!(find_last_char(buffer_no_newlines, b'\n'), None);
508 }
509}