From e467cc70aed9f53168a9638a3b723afee9e892c0 Mon Sep 17 00:00:00 2001 From: Ed Page Date: Wed, 5 Oct 2022 18:56:37 -0500 Subject: [PATCH] docs(cookbook): PossibleValues with custom types This used to be more magical in clap 3 and people are having a hard time discovering how to get this in clap 4, so let's add examples: - discrete numbers - foreign enums --- examples/typed-derive.md | 72 ++++++++++++++++++++++++++++++++++------ examples/typed-derive.rs | 58 ++++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+), 11 deletions(-) diff --git a/examples/typed-derive.md b/examples/typed-derive.md index 4418fe5ba65..d6aa71012a7 100644 --- a/examples/typed-derive.md +++ b/examples/typed-derive.md @@ -6,19 +6,21 @@ $ typed-derive --help Usage: typed-derive[EXE] [OPTIONS] Options: - -O Implicitly using `std::str::FromStr` - -I Allow invalid UTF-8 paths - --bind Handle IP addresses - --sleep Allow human-readable durations - -D Hand-written parser for tuples - -h, --help Print help information + -O Implicitly using `std::str::FromStr` + -I Allow invalid UTF-8 paths + --bind Handle IP addresses + --sleep Allow human-readable durations + -D Hand-written parser for tuples + --port Support for discrete numbers [default: 22] [possible values: 22, 80] + --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 @@ -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 @@ -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 @@ -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 @@ -79,3 +81,51 @@ error: Invalid value "Foo=Bar" for '-D ': 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 ' 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 ' + [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 ' 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 ' + [possible values: info, debug, info, warn, error] + +For more information try '--help' + +``` diff --git a/examples/typed-derive.rs b/examples/typed-derive.rs index 1bd03fcf52c..bdf8a580911 100644 --- a/examples/typed-derive.rs +++ b/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')] @@ -22,6 +24,24 @@ struct Args { /// Hand-written parser for tuples #[arg(short = 'D', value_parser = parse_key_val::)] 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::().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::().unwrap()), + )] + log_level: foreign_crate::LogLevel, } /// Parse a single key-value pair @@ -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 { + 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);