clap_stdin/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use std::io::{self, Read};
4use std::str::FromStr;
5use std::sync::atomic::AtomicBool;
6
7mod maybe_stdin;
8pub use maybe_stdin::MaybeStdin;
9mod file_or_stdin;
10pub use file_or_stdin::FileOrStdin;
11mod file_or_stdout;
12pub use file_or_stdout::FileOrStdout;
13
14static STDIN_HAS_BEEN_READ: AtomicBool = AtomicBool::new(false);
15
16#[derive(Debug, thiserror::Error)]
17pub enum StdinError {
18    #[error("stdin read from more than once")]
19    StdInRepeatedUse,
20    #[error(transparent)]
21    StdIn(#[from] io::Error),
22    #[error("unable to parse from_str: {0}")]
23    FromStr(String),
24}
25
26/// Source of the value contents will be either from `stdin` or a CLI arg provided value
27#[derive(Clone)]
28pub(crate) enum Source {
29    Stdin,
30    Arg(String),
31}
32
33impl Source {
34    pub(crate) fn into_reader(self) -> Result<impl std::io::Read, StdinError> {
35        let input: Box<dyn std::io::Read + 'static> = match self {
36            Source::Stdin => {
37                if STDIN_HAS_BEEN_READ.load(std::sync::atomic::Ordering::Acquire) {
38                    return Err(StdinError::StdInRepeatedUse);
39                }
40                STDIN_HAS_BEEN_READ.store(true, std::sync::atomic::Ordering::SeqCst);
41                Box::new(std::io::stdin())
42            }
43            Source::Arg(filepath) => {
44                let f = std::fs::File::open(filepath)?;
45                Box::new(f)
46            }
47        };
48        Ok(input)
49    }
50
51    pub(crate) fn get_value(self) -> Result<String, StdinError> {
52        match self {
53            Source::Stdin => {
54                if STDIN_HAS_BEEN_READ.load(std::sync::atomic::Ordering::Acquire) {
55                    return Err(StdinError::StdInRepeatedUse);
56                }
57                STDIN_HAS_BEEN_READ.store(true, std::sync::atomic::Ordering::SeqCst);
58                let stdin = io::stdin();
59                let mut input = String::new();
60                stdin.lock().read_to_string(&mut input)?;
61                Ok(input)
62            }
63            Source::Arg(value) => Ok(value),
64        }
65    }
66}
67
68impl FromStr for Source {
69    type Err = StdinError;
70
71    fn from_str(s: &str) -> Result<Self, Self::Err> {
72        match s {
73            "-" => Ok(Self::Stdin),
74            arg => Ok(Self::Arg(arg.to_owned())),
75        }
76    }
77}
78
79impl std::fmt::Debug for Source {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        match self {
82            Source::Stdin => write!(f, "stdin"),
83            Source::Arg(v) => v.fmt(f),
84        }
85    }
86}
87
88/// Destination of the value contents will be either `stdout` or a CLI arg provided filepath
89#[derive(Clone)]
90pub(crate) enum Dest {
91    Stdout,
92    Arg(String),
93}
94
95impl Dest {
96    pub(crate) fn into_writer(self) -> std::io::Result<impl std::io::Write> {
97        let input: Box<dyn std::io::Write + 'static> = match self {
98            Dest::Stdout => Box::new(std::io::stdout()),
99            Dest::Arg(filepath) => {
100                let f = std::fs::OpenOptions::new()
101                    .create(true)
102                    .write(true)
103                    .truncate(false)
104                    .open(filepath)?;
105                Box::new(f)
106            }
107        };
108        Ok(input)
109    }
110}
111
112impl FromStr for Dest {
113    type Err = std::io::Error;
114
115    fn from_str(s: &str) -> Result<Self, Self::Err> {
116        match s {
117            "-" => Ok(Self::Stdout),
118            arg => Ok(Self::Arg(arg.to_owned())),
119        }
120    }
121}
122
123impl std::fmt::Debug for Dest {
124    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
125        match self {
126            Dest::Stdout => write!(f, "stdout"),
127            Dest::Arg(path) => path.fmt(f),
128        }
129    }
130}