1use clap::Parser;
2use rustutils_runnable::Runnable;
3use std::error::Error;
4use std::fs::{File, OpenOptions};
5use std::io::{Read, Write};
6use std::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 Tee {
14 #[clap(short, long)]
15 append: bool,
16 file: Vec<PathBuf>,
17}
18
19#[derive(thiserror::Error, Debug)]
20pub enum TeeError {
21 #[error("Opening file {0:?}: {1:}")]
22 OpeningFile(PathBuf, std::io::Error),
23 #[error("Writing to file {0:?}: {1:}")]
24 WritingFile(PathBuf, std::io::Error),
25 #[error("Reading from standard input: {0:}")]
26 ReadingStdin(std::io::Error),
27 #[error("Writing to standard output: {0:}")]
28 WritingStdout(std::io::Error),
29}
30
31impl Runnable for Tee {
32 fn run(&self) -> Result<(), Box<dyn Error>> {
33 let mut stdin = std::io::stdin();
34 let mut stdout = std::io::stdout();
35 let mut files = self
36 .file
37 .iter()
38 .map(|path| {
39 let mut options = OpenOptions::new();
40 options.create(true);
41 options.append(self.append);
42 options.write(true);
43 let file = options
44 .open(&path)
45 .map_err(|e| TeeError::OpeningFile(path.clone(), e))?;
46 Ok((path, file))
47 })
48 .collect::<Result<Vec<(&PathBuf, File)>, TeeError>>()?;
49 let mut buffer = vec![0; BUFFER_SIZE];
50 loop {
51 let length = stdin
52 .read(&mut buffer[..])
53 .map_err(|e| TeeError::ReadingStdin(e))?;
54 let data = &buffer[0..length];
55
56 if data.len() == 0 {
58 break;
59 }
60
61 stdout
62 .write_all(&data)
63 .map_err(|e| TeeError::WritingStdout(e))?;
64 for (path, file) in &mut files {
65 file.write_all(&data)
66 .map_err(|e| TeeError::WritingFile(path.clone(), e))?;
67 }
68 }
69 Ok(())
70 }
71}