1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
use std::fs;
use std::io::{self, Read};
use std::marker::PhantomData;
use std::str::FromStr;
use super::{Source, StdinError};
/// Wrapper struct to either read in a file or contents from `stdin`
///
/// `FileOrStdin` can wrap any type that matches the trait bounds for `Arg`: `FromStr` and `Clone`
/// ```rust
/// use std::path::PathBuf;
/// use clap::Parser;
/// use clap_stdin::FileOrStdin;
///
/// #[derive(Debug, Parser)]
/// struct Args {
///     input: FileOrStdin,
/// }
///
/// # fn main() -> anyhow::Result<()> {
/// if let Ok(args) = Args::try_parse() {
///     println!("input={}", args.input.contents()?);
/// }
/// # Ok(())
/// # }
/// ```
///
/// ```sh
/// $ echo "1 2 3 4" > input.txt
/// $ cat input.txt | ./example -
/// 1 2 3 4
///
/// $ ./example input.txt
/// 1 2 3 4
/// ```
#[derive(Debug, Clone)]
pub struct FileOrStdin<T = String> {
    pub source: Source,
    _type: PhantomData<T>,
}
impl<T> FileOrStdin<T> {
    /// Read the entire contents from the input source, returning T::from_str
    pub fn contents(self) -> Result<T, StdinError>
    where
        T: FromStr,
        <T as FromStr>::Err: std::fmt::Display,
    {
        let mut reader = self.into_reader()?;
        let mut input = String::new();
        let _ = reader.read_to_string(&mut input)?;
        T::from_str(input.trim_end()).map_err(|e| StdinError::FromStr(format!("{e}")))
    }
    /// Create a reader from the source, to allow user flexibility of
    /// how to read and parse (e.g. all at once or in chunks)
    ///
    /// ```no_run
    /// use std::io::Read;
    ///
    /// use clap_stdin::FileOrStdin;
    /// use clap::Parser;
    ///
    /// #[derive(Parser)]
    /// struct Args {
    ///   input: FileOrStdin,
    /// }
    ///
    /// # fn main() -> anyhow::Result<()> {
    /// let args = Args::parse();
    /// let mut reader = args.input.into_reader()?;
    /// let mut buf = vec![0;8];
    /// reader.read_exact(&mut buf)?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn into_reader(&self) -> Result<impl io::Read, StdinError> {
        let input: Box<dyn std::io::Read + 'static> = match &self.source {
            Source::Stdin => Box::new(std::io::stdin()),
            Source::Arg(filepath) => {
                let f = fs::File::open(filepath)?;
                Box::new(f)
            }
        };
        Ok(input)
    }
}
impl<T> FromStr for FileOrStdin<T> {
    type Err = StdinError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let source = Source::from_str(s)?;
        Ok(Self {
            source,
            _type: PhantomData,
        })
    }
}