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