rustutils_tee/
lib.rs

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/// Copy standard input to each FILE, and also to standard output
11#[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            // end of file
57            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}