'Accept optional file on command line, default to stdin [closed]
I'm building a CLI program that takes as an optional final argument the name of a file to read from, which may be left off to read from standard input instead, as with cat and similar UNIX programs.
Is there any way I could have clap populate a member of my Cli struct with something like a Box<dyn BufRead> initialized from either the file or stdin, or is this just something I'm going to have to handle manually?
Not sure why this was closed as opinion-based, but it seems that the answer to my question is "no". There's no simple way to prepopulate a struct with an open reader on the right thing using only clap's built-in parsing, and I have to do it manually. Case closed.
I wasn't looking for how best to do it manually, just asking if there was a feature I'd overlooked that would let me avoid having to.
Solution 1:[1]
This solution: Doesn't use dynamic dispatch, is verbose, use default_value_t, require nightly see #93965 (It should be possible to not require nightly but it's was simpler for me)
use clap; // 3.1.6
use std::fmt::{self, Display, Formatter};
use std::fs::File;
use std::io::{self, stdin, BufRead, BufReader, Read, StdinLock};
use std::str::FromStr;
enum Input {
Stdin(StdinLock<'static>),
File(BufReader<File>),
}
impl Display for Input {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> {
write!(fmt, "Input")
}
}
impl Default for Input {
fn default() -> Self {
Self::Stdin(stdin().lock())
}
}
impl FromStr for Input {
type Err = io::Error;
fn from_str(path: &str) -> Result<Self, <Self as FromStr>::Err> {
File::open(path).map(BufReader::new).map(Input::File)
}
}
impl BufRead for Input {
fn fill_buf(&mut self) -> Result<&[u8], io::Error> {
match self {
Self::Stdin(stdin) => stdin.fill_buf(),
Self::File(file) => file.fill_buf(),
}
}
fn consume(&mut self, amt: usize) {
match self {
Self::Stdin(stdin) => stdin.consume(amt),
Self::File(file) => file.consume(amt),
}
}
}
impl Read for Input {
fn read(&mut self, buf: &mut [u8]) -> Result<usize, io::Error> {
match self {
Self::Stdin(stdin) => stdin.read(buf),
Self::File(file) => file.read(buf),
}
}
}
#[derive(clap::Parser)]
struct Reader {
#[clap(default_value_t)]
input: Input,
}
Solution 2:[2]
You can use an Option<PathBuf> and use stdin when None, everything wrapped into a method (yes, you have to implement it yourself):
use std::io::BufReader;
use clap;
use std::io::BufRead;
use std::path::PathBuf; // 3.1.6
#[derive(clap::Parser)]
struct Reader {
input: Option<PathBuf>,
}
impl Reader {
fn reader(&self) -> Box<dyn BufRead> {
self.input.as_ref()
.map(|path| {
Box::new(BufReader::new(std::fs::File::open(path).unwrap())) as Box<dyn BufRead>
})
.unwrap_or_else(|| Box::new(BufReader::new(std::io::stdin())) as Box<dyn BufRead>)
}
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Stargateur |
| Solution 2 |
