io_arg/
io_arg.rs

1use std::{io, path::PathBuf, str::FromStr};
2
3use crate::{Input, Output};
4
5/// Argument for CLI tools which can either take a file or STDIN/STDOUT.
6///
7/// Caveat: stdin is represented as "-" at the command line. Which means your tool is going to have
8/// a hard time operating on a file named "-".
9///
10/// ```
11/// use clap::Parser;
12/// use io_arg::IoArg;
13///
14/// #[derive(Debug, Parser)]
15/// struct Cli {
16///     /// Path to input file. Set to "-" to use STDIN instead of a file.
17///     input: IoArg,
18///     /// Path to output file. Leave out or set to "-" to use STDOUT instead of a file.
19///     output: IoArg,
20/// }
21/// ```
22#[derive(Debug, PartialEq, Eq, Clone)]
23pub enum IoArg {
24    /// Indicates that the IO is connected to stdin/stdout. Represented as a "-" on the command line.
25    StdStream,
26    /// Indicates that the IO is connected to a file. Contains the file path. Just enter a path
27    /// at the command line.
28    File(PathBuf),
29}
30
31impl IoArg {
32    /// Intended for use in an `if` expression or other situations there a boolean might be more
33    /// convinient than matching against variants.
34    ///
35    /// # Return
36    ///
37    /// `true` if variant is `File`.
38    /// `false` if variant is `StdStream`.
39    pub fn is_file(&self) -> bool {
40        match self {
41            IoArg::StdStream => false,
42            IoArg::File(_) => true,
43        }
44    }
45
46    /// Either calls `stdin` or `File::open` depending on `io_arg`.
47    pub fn open_as_input(self) -> io::Result<Input> {
48        Input::new(self)
49    }
50
51    /// Either calls `stdout` or `File::create` depending on `io_arg`.
52    pub fn open_as_output(self) -> io::Result<Output> {
53        Output::new(self)
54    }
55}
56
57impl FromStr for IoArg {
58    type Err = std::convert::Infallible;
59
60    fn from_str(s: &str) -> Result<Self, Self::Err> {
61        let out = match s {
62            "-" => IoArg::StdStream,
63            other => IoArg::File(other.into()),
64        };
65        Ok(out)
66    }
67}
68
69#[cfg(test)]
70mod tests {
71
72    use super::IoArg;
73
74    #[test]
75    fn parsing() {
76        let actual: IoArg = "-".parse().unwrap();
77        assert_eq!(IoArg::StdStream, actual);
78
79        let actual: IoArg = "filename".parse().unwrap();
80        assert!(matches!(actual, IoArg::File(_)));
81    }
82}