2

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.

Mark Reed
  • 91,912
  • 16
  • 138
  • 175

2 Answers2

0

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>)
    }
}

Playground

Netwave
  • 40,134
  • 6
  • 50
  • 93
  • how does this make it automatic ? – Stargateur Apr 15 '22 at 15:52
  • I made it **bold**, so it is not confused... – Netwave Apr 15 '22 at 16:03
  • 1
    well so your answer doesn't answer the question.... The OP obviously would have used similar code "or is this just something I'm going to have to handle manually?" Or your answer is "you can't" – Stargateur Apr 15 '22 at 16:04
  • `Is there any way I could have clap populate a member of my Cli struct with something like a Box initialized from either the file or stdin, or is this just something I'm going to have to handle manually?`, I guess we are understanding different things here. I dont see the point on what do you mean (?) – Netwave Apr 15 '22 at 16:05
  • a lot of obviously not obviously I guess... – Netwave Apr 15 '22 at 16:14
0

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,
}
Stargateur
  • 24,473
  • 8
  • 65
  • 91