5

I want to accept a std::time::Duration on a command line. I'm using clap with #[derive(Parser)] to generate the parameter parser. Is there any way I can directly accept an input, rather than accepting a number and doing the conversion later?

Something like this:

#[derive(Debug, Parser)]
pub struct Config {
    #[clap( ??? )]
    interval: std::time::Duration,
}
ddulaney
  • 843
  • 7
  • 19

3 Answers3

18

Clap 3.0:

To do custom parsing, you should use #[clap(parse(try_from_str = ...))] and define a custom function to parsing the argument. Here's an example:

use clap::Parser;

#[derive(Debug, Parser)]
pub struct Config {
    #[clap(parse(try_from_str = parse_duration))]
    interval: std::time::Duration,
}

fn parse_duration(arg: &str) -> Result<std::time::Duration, std::num::ParseIntError> {
    let seconds = arg.parse()?;
    Ok(std::time::Duration::from_secs(seconds))
}

Clap 4.0:

Almost same as above; the helper function can stay the same, but the attribute syntax has changed:

use clap::Parser;

#[derive(Debug, Parser)]
pub struct Config {
    #[arg(value_parser = parse_duration)]
    interval: std::time::Duration,
}

fn parse_duration(arg: &str) -> Result<std::time::Duration, std::num::ParseIntError> {
    let seconds = arg.parse()?;
    Ok(std::time::Duration::from_secs(seconds))
}

This parsing is pretty limited (I don't know what format you'd expect the duration to be in), but it shows how you'd do it.

If you want to be flexible with your duration arguments, consider using a crate like humantime; their Duration can be used with clap without special attributes since it implements FromStr.

kmdreko
  • 42,554
  • 6
  • 57
  • 106
  • 1
    I think it might be good to add a code example for how to implement `parse_duration` using `humantime`, like this: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=6053aacfa34b2cd57f36398a36839eb6 I would have added another answer, but it's too close to yours imo. – Finomnis May 20 '22 at 11:21
4

Compact version for Clap 4

use clap::Parser;
use std::time::Duration;
use std::num::ParseIntError;

#[derive(Debug, Parser)]
pub struct Config {
    #[arg(value_parser = |arg: &str| -> Result<Duration, ParseIntError> {Ok(Duration::from_secs(arg.parse()?))})]
    interval_secs: Duration,
}

Docs for value_parser:

https://docs.rs/clap/latest/clap/builder/struct.ValueParser.html#method.new

https://docs.rs/clap/latest/clap/macro.value_parser.html

Bob
  • 689
  • 10
  • 11
-1

I don't think there's a way to differentiate a u8 or whatever from a std::time::Duration on the command line itself. That said, you should be able to do something like this if you implement FromStr for Duration:

struct Config {
    #[structopt(short = "i", long = "interval")]
    interval: std::time::Duration,
}

But it definitely seems easier to take something like a u8 and do a conversion in main() or similar.

t56k
  • 6,769
  • 9
  • 52
  • 115