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,
})
}
}