1use clap::Parser;
2use rustutils_runnable::Runnable;
3use std::error::Error;
4use std::fs::File;
5use std::io::{Read, Write};
6use std::path::{Path, PathBuf};
7
8pub const BUFFER_SIZE: usize = 4 * 1024;
9
10#[derive(Parser, Clone, Debug)]
12#[clap(author, version, about, long_about = None)]
13pub struct Cat {
14 files: Vec<PathBuf>,
16}
17
18impl Cat {
19 pub fn iter(&self) -> CatIterator {
20 CatIterator::new(self.files.iter().map(|p| p.as_path()))
21 }
22}
23
24#[derive(thiserror::Error, Debug)]
25pub enum CatIteratorError {
26 #[error("Opening file {0:?}: {1:}")]
27 OpeningFile(PathBuf, std::io::Error),
28 #[error("Reading from file {0:?}: {1:}")]
29 ReadingFile(PathBuf, std::io::Error),
30}
31
32pub struct CatIterator<'f> {
33 current: Option<(&'f Path, Box<dyn Read>)>,
35 files: Box<dyn Iterator<Item = &'f Path> + 'f>,
37}
38
39impl<'f> CatIterator<'f> {
40 pub fn new(files: impl Iterator<Item = &'f Path> + 'f) -> Self {
41 CatIterator {
42 current: None,
43 files: Box::new(files),
44 }
45 }
46}
47
48impl<'f> Iterator for CatIterator<'f> {
49 type Item = Result<Vec<u8>, CatIteratorError>;
50 fn next(&mut self) -> Option<Self::Item> {
51 if self.current.is_none() {
52 if let Some(next) = self.files.next() {
53 let reader: Box<dyn Read> = if next.as_os_str() == "-" {
54 Box::new(std::io::stdin())
55 } else {
56 match File::open(next) {
57 Err(error) => {
58 return Some(Err(CatIteratorError::OpeningFile(
59 next.to_path_buf(),
60 error,
61 )))
62 }
63 Ok(file) => Box::new(file),
64 }
65 };
66 self.current = Some((next, reader));
67 } else {
68 return None;
69 }
70 }
71 let (next, reader) = match &mut self.current {
72 Some((next, reader)) => (next, reader),
73 None => unreachable!(),
74 };
75 let mut buffer = vec![0; BUFFER_SIZE];
76 let length = match reader.read(&mut buffer[..]) {
77 Err(error) => {
78 return Some(Err(CatIteratorError::ReadingFile(
79 next.to_path_buf(),
80 error,
81 )))
82 }
83 Ok(length) => length,
84 };
85 buffer.truncate(length);
86 if length == 0 {
87 self.current = None;
88 }
89 Some(Ok(buffer))
90 }
91}
92
93impl Runnable for Cat {
94 fn run(&self) -> Result<(), Box<dyn Error>> {
95 let mut stdout = std::io::stdout();
96 for data in self.iter() {
97 stdout.write_all(&data?)?;
98 }
99 Ok(())
100 }
101}