Skip to content

Commit

Permalink
Merge pull request #4352 from epage/typed
Browse files Browse the repository at this point in the history
docs(cookbook): PossibleValues with custom types
  • Loading branch information
epage committed Oct 6, 2022
2 parents af1234a + e467cc7 commit 93648df
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 11 deletions.
72 changes: 61 additions & 11 deletions examples/typed-derive.md
Expand Up @@ -6,19 +6,21 @@ $ typed-derive --help
Usage: typed-derive[EXE] [OPTIONS]

Options:
-O <OPTIMIZATION> Implicitly using `std::str::FromStr`
-I <DIR> Allow invalid UTF-8 paths
--bind <BIND> Handle IP addresses
--sleep <SLEEP> Allow human-readable durations
-D <DEFINES> Hand-written parser for tuples
-h, --help Print help information
-O <OPTIMIZATION> Implicitly using `std::str::FromStr`
-I <DIR> Allow invalid UTF-8 paths
--bind <BIND> Handle IP addresses
--sleep <SLEEP> Allow human-readable durations
-D <DEFINES> Hand-written parser for tuples
--port <PORT> Support for discrete numbers [default: 22] [possible values: 22, 80]
--log-level <LOG_LEVEL> Support enums from a foreign crate that don't implement `ValueEnum` [default: info] [possible values: info, debug, info, warn, error]
-h, --help Print help information

```

Optimization-level (number)
```console
$ typed-derive -O 1
Args { optimization: Some(1), include: None, bind: None, sleep: None, defines: [] }
Args { optimization: Some(1), include: None, bind: None, sleep: None, defines: [], port: 22, log_level: Info }

$ typed-derive -O plaid
? failed
Expand All @@ -31,14 +33,14 @@ For more information try '--help'
Include (path)
```console
$ typed-derive -I../hello
Args { optimization: None, include: Some("../hello"), bind: None, sleep: None, defines: [] }
Args { optimization: None, include: Some("../hello"), bind: None, sleep: None, defines: [], port: 22, log_level: Info }

```

IP Address
```console
$ typed-derive --bind 192.0.0.1
Args { optimization: None, include: None, bind: Some(192.0.0.1), sleep: None, defines: [] }
Args { optimization: None, include: None, bind: Some(192.0.0.1), sleep: None, defines: [], port: 22, log_level: Info }

$ typed-derive --bind localhost
? failed
Expand All @@ -51,7 +53,7 @@ For more information try '--help'
Time
```console
$ typed-derive --sleep 10s
Args { optimization: None, include: None, bind: None, sleep: Some(Duration(10s)), defines: [] }
Args { optimization: None, include: None, bind: None, sleep: Some(Duration(10s)), defines: [], port: 22, log_level: Info }

$ typed-derive --sleep forever
? failed
Expand All @@ -64,7 +66,7 @@ For more information try '--help'
Defines (key-value pairs)
```console
$ typed-derive -D Foo=10 -D Alice=30
Args { optimization: None, include: None, bind: None, sleep: None, defines: [("Foo", 10), ("Alice", 30)] }
Args { optimization: None, include: None, bind: None, sleep: None, defines: [("Foo", 10), ("Alice", 30)], port: 22, log_level: Info }

$ typed-derive -D Foo
? failed
Expand All @@ -79,3 +81,51 @@ error: Invalid value "Foo=Bar" for '-D <DEFINES>': invalid digit found in string
For more information try '--help'

```

Discrete numbers
```console
$ typed-derive --port 22
Args { optimization: None, include: None, bind: None, sleep: None, defines: [], port: 22, log_level: Info }

$ typed-derive --port 80
Args { optimization: None, include: None, bind: None, sleep: None, defines: [], port: 80, log_level: Info }

$ typed-derive --port
? failed
error: The argument '--port <PORT>' requires a value but none was supplied
[possible values: 22, 80]

For more information try '--help'

$ typed-derive --port 3000
? failed
error: "3000" isn't a valid value for '--port <PORT>'
[possible values: 22, 80]

For more information try '--help'

```

Enums from crates that can't implement `ValueEnum`
```console
$ typed-derive --log-level debug
Args { optimization: None, include: None, bind: None, sleep: None, defines: [], port: 22, log_level: Debug }

$ typed-derive --log-level error
Args { optimization: None, include: None, bind: None, sleep: None, defines: [], port: 22, log_level: Error }

$ typed-derive --log-level
? failed
error: The argument '--log-level <LOG_LEVEL>' requires a value but none was supplied
[possible values: info, debug, info, warn, error]

For more information try '--help'

$ typed-derive --log-level critical
? failed
error: "critical" isn't a valid value for '--log-level <LOG_LEVEL>'
[possible values: info, debug, info, warn, error]

For more information try '--help'

```
58 changes: 58 additions & 0 deletions examples/typed-derive.rs
@@ -1,7 +1,9 @@
use clap::builder::TypedValueParser as _;
use clap::Parser;
use std::error::Error;

#[derive(Parser, Debug)] // requires `derive` feature
#[command(term_width = 0)] // Just to make testing across clap features easier
struct Args {
/// Implicitly using `std::str::FromStr`
#[arg(short = 'O')]
Expand All @@ -22,6 +24,24 @@ struct Args {
/// Hand-written parser for tuples
#[arg(short = 'D', value_parser = parse_key_val::<String, i32>)]
defines: Vec<(String, i32)>,

/// Support for discrete numbers
#[arg(
long,
default_value_t = 22,
value_parser = clap::builder::PossibleValuesParser::new(["22", "80"])
.map(|s| s.parse::<usize>().unwrap()),
)]
port: usize,

/// Support enums from a foreign crate that don't implement `ValueEnum`
#[arg(
long,
default_value_t = foreign_crate::LogLevel::Info,
value_parser = clap::builder::PossibleValuesParser::new(["info", "debug", "info", "warn", "error"])
.map(|s| s.parse::<foreign_crate::LogLevel>().unwrap()),
)]
log_level: foreign_crate::LogLevel,
}

/// Parse a single key-value pair
Expand All @@ -38,6 +58,44 @@ where
Ok((s[..pos].parse()?, s[pos + 1..].parse()?))
}

mod foreign_crate {
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum LogLevel {
Trace,
Debug,
Info,
Warn,
Error,
}

impl std::fmt::Display for LogLevel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
Self::Trace => "trace",
Self::Debug => "debug",
Self::Info => "info",
Self::Warn => "warn",
Self::Error => "error",
};
s.fmt(f)
}
}
impl std::str::FromStr for LogLevel {
type Err = String;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"trace" => Ok(Self::Trace),
"debug" => Ok(Self::Debug),
"info" => Ok(Self::Info),
"warn" => Ok(Self::Warn),
"error" => Ok(Self::Error),
_ => Err(format!("Unknown log level: {s}")),
}
}
}
}

fn main() {
let args = Args::parse();
println!("{:?}", args);
Expand Down

0 comments on commit 93648df

Please sign in to comment.