| 1 | use clap::builder::TypedValueParser as _; |
| 2 | use clap::Parser; |
| 3 | use std::error::Error; |
| 4 | |
| 5 | #[derive(Parser, Debug)] // requires `derive` feature |
| 6 | #[command(term_width = 0)] // Just to make testing across clap features easier |
| 7 | struct Args { |
| 8 | /// Implicitly using `std::str::FromStr` |
| 9 | #[arg(short = 'O' )] |
| 10 | optimization: Option<usize>, |
| 11 | |
| 12 | /// Allow invalid UTF-8 paths |
| 13 | #[arg(short = 'I' , value_name = "DIR" , value_hint = clap::ValueHint::DirPath)] |
| 14 | include: Option<std::path::PathBuf>, |
| 15 | |
| 16 | /// Handle IP addresses |
| 17 | #[arg(long)] |
| 18 | bind: Option<std::net::IpAddr>, |
| 19 | |
| 20 | /// Allow human-readable durations |
| 21 | #[arg(long)] |
| 22 | sleep: Option<humantime::Duration>, |
| 23 | |
| 24 | /// Hand-written parser for tuples |
| 25 | #[arg(short = 'D' , value_parser = parse_key_val::<String, i32>)] |
| 26 | defines: Vec<(String, i32)>, |
| 27 | |
| 28 | /// Support for discrete numbers |
| 29 | #[arg( |
| 30 | long, |
| 31 | default_value_t = 22, |
| 32 | value_parser = clap::builder::PossibleValuesParser::new(["22" , "80" ]) |
| 33 | .map(|s| s.parse::<usize>().unwrap()), |
| 34 | )] |
| 35 | port: usize, |
| 36 | |
| 37 | /// Support enums from a foreign crate that don't implement `ValueEnum` |
| 38 | #[arg( |
| 39 | long, |
| 40 | default_value_t = foreign_crate::LogLevel::Info, |
| 41 | value_parser = clap::builder::PossibleValuesParser::new(["trace" , "debug" , "info" , "warn" , "error" ]) |
| 42 | .map(|s| s.parse::<foreign_crate::LogLevel>().unwrap()), |
| 43 | )] |
| 44 | log_level: foreign_crate::LogLevel, |
| 45 | } |
| 46 | |
| 47 | /// Parse a single key-value pair |
| 48 | fn parse_key_val<T, U>(s: &str) -> Result<(T, U), Box<dyn Error + Send + Sync + 'static>> |
| 49 | where |
| 50 | T: std::str::FromStr, |
| 51 | T::Err: Error + Send + Sync + 'static, |
| 52 | U: std::str::FromStr, |
| 53 | U::Err: Error + Send + Sync + 'static, |
| 54 | { |
| 55 | let pos = s |
| 56 | .find('=' ) |
| 57 | .ok_or_else(|| format!("invalid KEY=value: no `=` found in `{s}`" ))?; |
| 58 | Ok((s[..pos].parse()?, s[pos + 1..].parse()?)) |
| 59 | } |
| 60 | |
| 61 | mod foreign_crate { |
| 62 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] |
| 63 | pub enum LogLevel { |
| 64 | Trace, |
| 65 | Debug, |
| 66 | Info, |
| 67 | Warn, |
| 68 | Error, |
| 69 | } |
| 70 | |
| 71 | impl std::fmt::Display for LogLevel { |
| 72 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 73 | let s = match self { |
| 74 | Self::Trace => "trace" , |
| 75 | Self::Debug => "debug" , |
| 76 | Self::Info => "info" , |
| 77 | Self::Warn => "warn" , |
| 78 | Self::Error => "error" , |
| 79 | }; |
| 80 | s.fmt(f) |
| 81 | } |
| 82 | } |
| 83 | impl std::str::FromStr for LogLevel { |
| 84 | type Err = String; |
| 85 | |
| 86 | fn from_str(s: &str) -> Result<Self, Self::Err> { |
| 87 | match s { |
| 88 | "trace" => Ok(Self::Trace), |
| 89 | "debug" => Ok(Self::Debug), |
| 90 | "info" => Ok(Self::Info), |
| 91 | "warn" => Ok(Self::Warn), |
| 92 | "error" => Ok(Self::Error), |
| 93 | _ => Err(format!("Unknown log level: {s}" )), |
| 94 | } |
| 95 | } |
| 96 | } |
| 97 | } |
| 98 | |
| 99 | fn main() { |
| 100 | let args = Args::parse(); |
| 101 | println!("{args:?}" ); |
| 102 | } |
| 103 | |