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;
11
12static STDIN_HAS_BEEN_READ: AtomicBool = AtomicBool::new(false);
13
14#[derive(Debug, thiserror::Error)]
15pub enum StdinError {
16    #[error("stdin read from more than once")]
17    StdInRepeatedUse,
18    #[error(transparent)]
19    StdIn(#[from] io::Error),
20    #[error("unable to parse from_str: {0}")]
21    FromStr(String),
22}
23
24/// Source of the value contents will be either from `stdin` or a CLI arg provided value
25#[derive(Clone)]
26pub(crate) enum Source {
27    Stdin,
28    Arg(String),
29}
30
31impl Source {
32    pub(crate) fn into_reader(self) -> Result<impl std::io::Read, StdinError> {
33        let input: Box<dyn std::io::Read + 'static> = match self {
34            Source::Stdin => {
35                if STDIN_HAS_BEEN_READ.load(std::sync::atomic::Ordering::Acquire) {
36                    return Err(StdinError::StdInRepeatedUse);
37                }
38                STDIN_HAS_BEEN_READ.store(true, std::sync::atomic::Ordering::SeqCst);
39                Box::new(std::io::stdin())
40            }
41            Source::Arg(filepath) => {
42                let f = std::fs::File::open(filepath)?;
43                Box::new(f)
44            }
45        };
46        Ok(input)
47    }
48
49    pub(crate) fn get_value(self) -> Result<String, StdinError> {
50        match self {
51            Source::Stdin => {
52                if STDIN_HAS_BEEN_READ.load(std::sync::atomic::Ordering::Acquire) {
53                    return Err(StdinError::StdInRepeatedUse);
54                }
55                STDIN_HAS_BEEN_READ.store(true, std::sync::atomic::Ordering::SeqCst);
56                let stdin = io::stdin();
57                let mut input = String::new();
58                stdin.lock().read_to_string(&mut input)?;
59                Ok(input)
60            }
61            Source::Arg(value) => Ok(value),
62        }
63    }
64}
65
66impl FromStr for Source {
67    type Err = StdinError;
68
69    fn from_str(s: &str) -> Result<Self, Self::Err> {
70        match s {
71            "-" => Ok(Self::Stdin),
72            arg => Ok(Self::Arg(arg.to_owned())),
73        }
74    }
75}
76
77impl std::fmt::Debug for Source {
78    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
79        match self {
80            Source::Stdin => write!(f, "stdin"),
81            Source::Arg(v) => v.fmt(f),
82        }
83    }
84}