clap_file/
input.rs

1use std::{
2    fs::File,
3    io::{self, BufRead, BufReader, Read},
4    path::{Path, PathBuf},
5    str::FromStr,
6    sync::{Arc, Mutex, MutexGuard},
7};
8
9#[track_caller]
10fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<T> {
11    mutex.lock().unwrap_or_else(|e| e.into_inner())
12}
13
14/// Represents an input source, which can be either standard input or a file.
15///
16/// # Examples
17///
18/// ```rust,no_run
19/// use std::io::{self, BufRead as _};
20///
21/// use clap::Parser as _;
22/// use clap_file::Input;
23///
24/// #[derive(Debug, clap::Parser)]
25/// struct Args {
26///     /// Input file. If not provided, reads from standard input.
27///     input: Input,
28/// }
29///
30/// fn main() -> io::Result<()> {
31///     let args = Args::parse();
32///     let input = args.input.lock();
33///     for line in input.lines() {
34///         let line = line?;
35///         println!("{line}");
36///     }
37///     Ok(())
38/// }
39/// ```
40
41// This struct should not implement `Clone`, but clap-derive requires Clone [1].
42// So, I added `Clone` to the struct and wrap `File` with `Arc` and `Mutex`.
43// This is not the best way to handle this, but it works for now.
44//
45// [1]: https://github.com/clap-rs/clap/issues/4286
46#[derive(Debug, Clone)]
47pub struct Input(InputInner);
48
49#[derive(Debug, Clone)]
50enum InputInner {
51    Stdin,
52    File {
53        path: Arc<PathBuf>,
54        reader: Arc<Mutex<BufReader<File>>>,
55    },
56}
57
58impl Input {
59    /// Creates a new [`Input`] instance that reads from standard input.
60    pub fn stdin() -> Self {
61        Self(InputInner::Stdin)
62    }
63
64    /// Opens a file at the given path and creates a new [`Input`] instance that reads from it.
65    pub fn open(path: PathBuf) -> io::Result<Self> {
66        let path = Arc::new(path);
67        let file = File::open(&*path)?;
68        let reader = Arc::new(Mutex::new(BufReader::new(file)));
69        Ok(Self(InputInner::File { path, reader }))
70    }
71
72    /// Returns `true` if this [`Input`] reads from standard input.
73    pub fn is_stdin(&self) -> bool {
74        matches!(self.0, InputInner::Stdin)
75    }
76
77    /// Returns `true` if this [`Input`] reads from a file.
78
79    pub fn is_file(&self) -> bool {
80        matches!(self.0, InputInner::File { .. })
81    }
82
83    /// Returns the path of the file this [`Input`] reads from.
84    ///
85    /// Returns `None` if this [`Input`] reads from standard input.
86    pub fn path(&self) -> Option<&Path> {
87        match &self.0 {
88            InputInner::Stdin => None,
89            InputInner::File { path, .. } => Some(path),
90        }
91    }
92
93    /// Locks the input source and returns a [`LockedInput`] instance.
94    ///
95    /// This lock is released when the returned [`LockedInput`] instance is dropped.
96    /// The returned `LockedInput` instance implements [`Read`] and [`BufRead`] traits.
97
98    pub fn lock(&self) -> LockedInput<'_> {
99        let inner = match &self.0 {
100            InputInner::Stdin => {
101                let reader = io::stdin().lock();
102                LockedInputInner::Stdin { reader }
103            }
104            InputInner::File { path, reader: file } => {
105                let reader = lock(file);
106                LockedInputInner::File {
107                    path: Arc::clone(path),
108                    reader,
109                }
110            }
111        };
112        LockedInput(inner)
113    }
114}
115
116impl FromStr for Input {
117    type Err = io::Error;
118
119    fn from_str(s: &str) -> Result<Self, Self::Err> {
120        if s == "-" {
121            return Ok(Self::stdin());
122        }
123        Self::open(PathBuf::from(s))
124    }
125}
126
127macro_rules! with_reader {
128    ($inner:expr, $var:ident => $e:expr) => {
129        match $inner {
130            InputInner::Stdin => {
131                let mut $var = io::stdin();
132                $e
133            }
134            InputInner::File { reader, .. } => {
135                let mut $var = lock(reader);
136                $e
137            }
138        }
139    };
140}
141
142impl Read for Input {
143    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
144        with_reader!(&self.0, r => r.read(buf))
145    }
146
147    fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
148        with_reader!(&self.0, r => r.read_vectored(bufs))
149    }
150
151    // this method is not yet stable
152    // fn is_read_vectored(&self) -> bool {
153    //     with_reader!(&self.0, r => r.is_read_vectored())
154    // }
155
156    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
157        with_reader!(&self.0, r => r.read_to_end(buf))
158    }
159
160    fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
161        with_reader!(&self.0, r => r.read_to_string(buf))
162    }
163
164    fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
165        with_reader!(&self.0, r => r.read_exact(buf))
166    }
167
168    // this method is not yet stable
169    // fn read_buf(&mut self, buf: io::BorrowedCursor<'_>) -> io::Result<()> {
170    //     with_reader!(&self.0, r => r.read_buf(buf))
171    // }
172
173    // this method is not yet stable
174    // fn read_buf_exact(&mut self, cursor: io::BorrowedCursor<'_>) -> io::Result<()> {
175    //     with_reader!(&self.0, r => r.read_buf_exact(cursor))
176    // }
177}
178
179/// A locked input source that implements [`Read`] and [`BufRead`] traits.
180#[derive(Debug)]
181pub struct LockedInput<'a>(LockedInputInner<'a>);
182
183impl LockedInput<'_> {
184    /// Returns `true` if this [`LockedInput`] reads from standard input.
185    pub fn is_stdin(&self) -> bool {
186        matches!(self.0, LockedInputInner::Stdin { .. })
187    }
188
189    /// Returns `true` if this [`LockedInput`] reads from a file.
190    pub fn is_file(&self) -> bool {
191        matches!(self.0, LockedInputInner::File { .. })
192    }
193
194    /// Returns the path of the file this [`LockedInput`] reads from.
195    ///
196    /// Returns `None` if this [`LockedInput`] reads from standard input.
197    pub fn path(&self) -> Option<&Path> {
198        match &self.0 {
199            LockedInputInner::Stdin { .. } => None,
200            LockedInputInner::File { path, .. } => Some(path),
201        }
202    }
203}
204
205#[derive(Debug)]
206enum LockedInputInner<'a> {
207    Stdin {
208        reader: io::StdinLock<'a>,
209    },
210    File {
211        path: Arc<PathBuf>,
212        reader: MutexGuard<'a, BufReader<File>>,
213    },
214}
215
216macro_rules! with_locked_reader {
217    ($inner:expr, $var:ident => $e:expr) => {
218        match $inner {
219            LockedInputInner::Stdin { reader } => {
220                let $var = reader;
221                $e
222            }
223            LockedInputInner::File { reader, .. } => {
224                let $var = reader;
225                $e
226            }
227        }
228    };
229}
230
231impl Read for LockedInput<'_> {
232    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
233        with_locked_reader!(&mut self.0, r => r.read(buf))
234    }
235
236    fn read_vectored(&mut self, bufs: &mut [io::IoSliceMut<'_>]) -> io::Result<usize> {
237        with_locked_reader!(&mut self.0, r => r.read_vectored(bufs))
238    }
239
240    // this method is not yet stable
241    // fn is_read_vectored(&self) -> bool {
242    //     with_locked_reader!(&mut self.0, r => r.is_read_vectored())
243    // }
244
245    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> io::Result<usize> {
246        with_locked_reader!(&mut self.0, r => r.read_to_end(buf))
247    }
248
249    fn read_to_string(&mut self, buf: &mut String) -> io::Result<usize> {
250        with_locked_reader!(&mut self.0, r => r.read_to_string(buf))
251    }
252
253    fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
254        with_locked_reader!(&mut self.0, r => r.read_exact(buf))
255    }
256
257    // this method is not yet stable
258    // fn read_buf(&mut self, buf: io::BorrowedCursor<'_>) -> io::Result<()> {
259    //     with_locked_reader!(&mut self.0, r => r.read_buf(buf))
260    // }
261
262    // this method is not yet stable
263    // fn read_buf_exact(&mut self, cursor: io::BorrowedCursor<'_>) -> io::Result<()> {
264    //     with_locked_reader!(&mut self.0, r => r.read_buf_exact(cursor))
265    // }
266}
267
268impl BufRead for LockedInput<'_> {
269    fn fill_buf(&mut self) -> io::Result<&[u8]> {
270        with_locked_reader!(&mut self.0, r => r.fill_buf())
271    }
272
273    fn consume(&mut self, amt: usize) {
274        with_locked_reader!(&mut self.0, r => r.consume(amt))
275    }
276}