clap_stdin/
file_or_stdout.rs

1use std::str::FromStr;
2
3use super::Dest;
4
5/// `FileOrStdout` can be used as a proxy output writer to write to whichever destination
6/// was specified by the CLI args, a file or `stdout`.
7///
8/// ```rust
9/// use std::path::PathBuf;
10/// use std::io::Write;
11/// use clap::Parser;
12/// use clap_stdin::FileOrStdout;
13///
14/// #[derive(Debug, Parser)]
15/// struct Args {
16///     output: FileOrStdout,
17/// }
18///
19/// # fn main() -> anyhow::Result<()> {
20/// if let Ok(args) = Args::try_parse() {
21///     let mut writer = args.output.into_writer()?;
22///     write!(&mut writer, "1 2 3 4");
23/// }
24/// # Ok(())
25/// # }
26/// ```
27///
28/// ```sh
29/// $ ./example output.txt
30/// 1 2 3 4
31/// $ cat output.txt | ./example -
32/// 1 2 3 4
33/// ```
34#[derive(Debug, Clone)]
35pub struct FileOrStdout {
36    dest: Dest,
37}
38
39impl FileOrStdout {
40    /// Was this value read from stdout
41    pub fn is_stdout(&self) -> bool {
42        matches!(self.dest, Dest::Stdout)
43    }
44
45    /// Was this value read from a file (path passed in from argument values)
46    pub fn is_file(&self) -> bool {
47        !self.is_stdout()
48    }
49
50    /// The value passed to this arg (Either "-" for stdout or a filepath)
51    pub fn filename(&self) -> &str {
52        match &self.dest {
53            Dest::Stdout => "-",
54            Dest::Arg(path) => path,
55        }
56    }
57
58    /// Create a writer for the dest, to allow user flexibility of
59    /// how to write output (e.g. all at once or in chunks)
60    ///
61    /// ```no_run
62    /// use std::io::Write;
63    ///
64    /// use clap_stdin::FileOrStdout;
65    /// use clap::Parser;
66    ///
67    /// #[derive(Parser)]
68    /// struct Args {
69    ///   output: FileOrStdout,
70    /// }
71    ///
72    /// # fn main() -> anyhow::Result<()> {
73    /// let args = Args::parse();
74    /// let mut writer = args.output.into_writer()?;
75    /// let mut buf = vec![0;8];
76    /// writer.write_all(&mut buf)?;
77    /// # Ok(())
78    /// # }
79    /// ```
80    pub fn into_writer(self) -> Result<impl std::io::Write, std::io::Error> {
81        self.dest.into_writer()
82    }
83
84    #[cfg(feature = "tokio")]
85    /// Create a reader from the source, to allow user flexibility of
86    /// how to read and parse (e.g. all at once or in chunks)
87    ///
88    /// ```no_run
89    /// use std::io::Write;
90    /// use tokio::io::AsyncWriteExt;
91    ///
92    /// use clap_stdin::FileOrStdout;
93    /// use clap::Parser;
94    ///
95    /// #[derive(Parser)]
96    /// struct Args {
97    ///   output: FileOrStdout,
98    /// }
99    ///
100    /// # #[tokio::main(flavor = "current_thread")]
101    /// # async fn main() -> anyhow::Result<()> {
102    /// let args = Args::parse();
103    /// let mut writer = args.output.into_async_writer().await?;
104    /// let mut buf = vec![0;8];
105    /// writer.write_all(&mut buf).await?;
106    /// # Ok(())
107    /// # }
108    /// ```
109    pub async fn into_async_writer(&self) -> std::io::Result<impl tokio::io::AsyncWrite> {
110        let output: std::pin::Pin<Box<dyn tokio::io::AsyncWrite + 'static>> = match &self.dest {
111            Dest::Stdout => Box::pin(tokio::io::stdout()),
112            Dest::Arg(filepath) => {
113                let f = tokio::fs::File::open(filepath).await?;
114                Box::pin(f)
115            }
116        };
117        Ok(output)
118    }
119}
120
121impl FromStr for FileOrStdout {
122    type Err = std::io::Error;
123
124    fn from_str(s: &str) -> Result<Self, Self::Err> {
125        let dest = Dest::from_str(s)?;
126        Ok(Self { dest })
127    }
128}
129
130#[test]
131fn test_source_methods() {
132    let val: FileOrStdout = "-".parse().unwrap();
133    assert!(val.is_stdout());
134    assert!(!val.is_file());
135    assert_eq!(val.filename(), "-");
136
137    let val: FileOrStdout = "/path/to/something".parse().unwrap();
138    assert!(val.is_file());
139    assert!(!val.is_stdout());
140    assert_eq!(val.filename(), "/path/to/something");
141}