Merge pull request #3555 from epage/parse

docs(examples): More real-world parsing cases
This commit is contained in:
Ed Page 2022-03-14 10:22:40 -05:00 committed by GitHub
commit 38469060db
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 159 additions and 68 deletions

View file

@ -140,6 +140,7 @@ trybuild = "1.0.18"
rustversion = "1"
# Cutting out `filesystem` feature
trycmd = { version = "0.13", default-features = false, features = ["color-auto", "diff", "examples"] }
humantime = "2"
[[example]]
name = "demo"
@ -166,7 +167,7 @@ name = "git-derive"
required-features = ["derive"]
[[example]]
name = "keyvalue-derive"
name = "typed-derive"
required-features = ["derive"]
[[example]]

View file

@ -1,7 +1,7 @@
# Examples
- Basic demo: [derive](demo.md)
- Key-value pair arguments: [derive](keyvalue-derive.md)
- Typed arguments: [derive](typed-derive.md)
- Topics:
- Custom `parse()`
- Custom cargo command: [builder](cargo-example.md), [derive](cargo-example-derive.md)

View file

@ -1,31 +0,0 @@
*Jump to [source](keyvalue-derive.rs)*
**This requires enabling the `derive` feature flag.**
```console
$ keyvalue-derive --help
clap
USAGE:
keyvalue-derive[EXE] [OPTIONS]
OPTIONS:
-D <DEFINES>
-h, --help Print help information
$ keyvalue-derive -D Foo=10 -D Alice=30
Args { defines: [("Foo", 10), ("Alice", 30)] }
$ keyvalue-derive -D Foo
? failed
error: Invalid value "Foo" for '-D <DEFINES>': invalid KEY=value: no `=` found in `Foo`
For more information try --help
$ keyvalue-derive -D Foo=Bar
? failed
error: Invalid value "Foo=Bar" for '-D <DEFINES>': invalid digit found in string
For more information try --help
```

View file

@ -1,26 +1,16 @@
// Note: this requires the `cargo` feature
use clap::{arg, command};
use std::ops::RangeInclusive;
use std::str::FromStr;
const PORT_RANGE: RangeInclusive<usize> = 1..=65535;
use clap::{arg, command};
fn main() {
let matches = command!()
.arg(arg!(<PORT>).help("Network port to use").validator(|s| {
usize::from_str(s)
.map(|port| PORT_RANGE.contains(&port))
.map_err(|e| e.to_string())
.and_then(|result| match result {
true => Ok(()),
false => Err(format!(
"Port not in range {}-{}",
PORT_RANGE.start(),
PORT_RANGE.end()
)),
})
}))
.arg(
arg!(<PORT>)
.help("Network port to use")
.validator(port_in_range),
)
.get_matches();
// Note, it's safe to call unwrap() because the arg is required
@ -29,3 +19,20 @@ fn main() {
.expect("'PORT' is required and parsing will fail if its missing");
println!("PORT = {}", port);
}
const PORT_RANGE: RangeInclusive<usize> = 1..=65535;
fn port_in_range(s: &str) -> Result<(), String> {
let port: usize = s
.parse()
.map_err(|_| format!("`{}` isn't a port number", s))?;
if PORT_RANGE.contains(&port) {
Ok(())
} else {
Err(format!(
"Port not in range {}-{}",
PORT_RANGE.start(),
PORT_RANGE.end()
))
}
}

View file

@ -497,6 +497,12 @@ OPTIONS:
$ 04_02_validate 22
PORT = 22
$ 04_02_validate foobar
? failed
error: Invalid value "foobar" for '<PORT>': `foobar` isn't a port number
For more information try --help
$ 04_02_validate 0
? failed
error: Invalid value "0" for '<PORT>': Port not in range 1-65535

View file

@ -1,14 +1,12 @@
use clap::Parser;
use std::ops::RangeInclusive;
use std::str::FromStr;
const PORT_RANGE: RangeInclusive<usize> = 1..=65535;
use clap::Parser;
#[derive(Parser)]
#[clap(author, version, about, long_about = None)]
struct Cli {
/// Network port to use
#[clap(validator = port_in_range)]
#[clap(parse(try_from_str=port_in_range))]
port: usize,
}
@ -18,16 +16,19 @@ fn main() {
println!("PORT = {}", cli.port);
}
fn port_in_range(s: &str) -> Result<(), String> {
usize::from_str(s)
.map(|port| PORT_RANGE.contains(&port))
.map_err(|e| e.to_string())
.and_then(|result| match result {
true => Ok(()),
false => Err(format!(
"Port not in range {}-{}",
PORT_RANGE.start(),
PORT_RANGE.end()
)),
})
const PORT_RANGE: RangeInclusive<usize> = 1..=65535;
fn port_in_range(s: &str) -> Result<usize, String> {
let port: usize = s
.parse()
.map_err(|_| format!("`{}` isn't a port number", s))?;
if PORT_RANGE.contains(&port) {
Ok(port)
} else {
Err(format!(
"Port not in range {}-{}",
PORT_RANGE.start(),
PORT_RANGE.end()
))
}
}

View file

@ -419,8 +419,6 @@ For more information try --help
More generally, you can validate and parse into any data type.
More generally, you can parse into any data type.
[Example:](04_02_parse.rs)
```console
$ 04_02_parse_derive --help
@ -448,7 +446,7 @@ For more information try --help
```
A custom validator can be used to improve the error messages or provide additional validation:
A custom parser can be used to improve the error messages or provide additional validation:
[Example:](04_02_validate.rs)
```console
@ -469,6 +467,12 @@ OPTIONS:
$ 04_02_validate_derive 22
PORT = 22
$ 04_02_validate_derive foobar
? failed
error: Invalid value "foobar" for '<PORT>': `foobar` isn't a port number
For more information try --help
$ 04_02_validate_derive 0
? failed
error: Invalid value "0" for '<PORT>': Port not in range 1-65535

86
examples/typed-derive.md Normal file
View file

@ -0,0 +1,86 @@
*Jump to [source](typed-derive.rs)*
**This requires enabling the `derive` feature flag.**
Help:
```console
$ typed-derive --help
clap
USAGE:
typed-derive[EXE] [OPTIONS]
OPTIONS:
--bind <BIND> Handle IP addresses
-D <DEFINES> Hand-written parser for tuples
-h, --help Print help information
-I <DIR> Allow invalid UTF-8 paths
-O <OPTIMIZATION> Implicitly using `std::str::FromStr`
--sleep <SLEEP> Allow human-readable durations
```
Optimization-level (number)
```console
$ typed-derive -O 1
Args { optimization: Some(1), include: None, bind: None, sleep: None, defines: [] }
$ typed-derive -O plaid
? failed
error: Invalid value "plaid" for '-O <OPTIMIZATION>': invalid digit found in string
For more information try --help
```
Include (path)
```console
$ typed-derive -I../hello
Args { optimization: None, include: Some("../hello"), bind: None, sleep: None, defines: [] }
```
IP Address
```console
$ typed-derive --bind 192.0.0.1
Args { optimization: None, include: None, bind: Some(192.0.0.1), sleep: None, defines: [] }
$ typed-derive --bind localhost
? failed
error: Invalid value "localhost" for '--bind <BIND>': invalid IP address syntax
For more information try --help
```
Time
```console
$ typed-derive --sleep 10s
Args { optimization: None, include: None, bind: None, sleep: Some(Duration(10s)), defines: [] }
$ typed-derive --sleep forever
? failed
error: Invalid value "forever" for '--sleep <SLEEP>': expected number at 0
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)] }
$ typed-derive -D Foo
? failed
error: Invalid value "Foo" for '-D <DEFINES>': invalid KEY=value: no `=` found in `Foo`
For more information try --help
$ typed-derive -D Foo=Bar
? failed
error: Invalid value "Foo=Bar" for '-D <DEFINES>': invalid digit found in string
For more information try --help
```

View file

@ -5,6 +5,23 @@ use std::error::Error;
#[derive(Parser, Debug)]
struct Args {
/// Implicitly using `std::str::FromStr`
#[clap(short = 'O')]
optimization: Option<usize>,
/// Allow invalid UTF-8 paths
#[clap(short = 'I', parse(from_os_str), value_name = "DIR", value_hint = clap::ValueHint::DirPath)]
include: Option<std::path::PathBuf>,
/// Handle IP addresses
#[clap(long)]
bind: Option<std::net::IpAddr>,
/// Allow human-readable durations
#[clap(long)]
sleep: Option<humantime::Duration>,
/// Hand-written parser for tuples
#[clap(short = 'D', parse(try_from_str = parse_key_val), multiple_occurrences(true))]
defines: Vec<(String, i32)>,
}