Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs(cookbook): PossibleValues with custom types #4352

Merged
merged 1 commit into from Oct 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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