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#[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#[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}