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}