Rollup merge of #33480 - birkenfeld:issue-33422, r=steveklabnik
book: fixup code in error handling tutorial A few oversights happened while porting the example from docopt to getopts. I retraced all the steps, fixing code and description as necessary. Fixes: #33422
This commit is contained in:
commit
35cc6b0c2c
1 changed files with 59 additions and 57 deletions
|
@ -1573,8 +1573,9 @@ detail on Getopts, but there is [some good documentation][15]
|
||||||
describing it. The short story is that Getopts generates an argument
|
describing it. The short story is that Getopts generates an argument
|
||||||
parser and a help message from a vector of options (The fact that it
|
parser and a help message from a vector of options (The fact that it
|
||||||
is a vector is hidden behind a struct and a set of methods). Once the
|
is a vector is hidden behind a struct and a set of methods). Once the
|
||||||
parsing is done, we can decode the program arguments into a Rust
|
parsing is done, the parser returns a struct that records matches
|
||||||
struct. From there, we can get information about the flags, for
|
for defined options, and remaining "free" arguments.
|
||||||
|
From there, we can get information about the flags, for
|
||||||
instance, whether they were passed in, and what arguments they
|
instance, whether they were passed in, and what arguments they
|
||||||
had. Here's our program with the appropriate `extern crate`
|
had. Here's our program with the appropriate `extern crate`
|
||||||
statements, and the basic argument setup for Getopts:
|
statements, and the basic argument setup for Getopts:
|
||||||
|
@ -1605,8 +1606,8 @@ fn main() {
|
||||||
print_usage(&program, opts);
|
print_usage(&program, opts);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let data_path = &args[1];
|
let data_path = &matches.free[0];
|
||||||
let city = &args[2];
|
let city: &str = &matches.free[1];
|
||||||
|
|
||||||
// Do stuff with information
|
// Do stuff with information
|
||||||
}
|
}
|
||||||
|
@ -1680,8 +1681,8 @@ fn main() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let data_path = &args[1];
|
let data_path = &matches.free[0];
|
||||||
let city: &str = &args[2];
|
let city: &str = &matches.free[1];
|
||||||
|
|
||||||
let file = File::open(data_path).unwrap();
|
let file = File::open(data_path).unwrap();
|
||||||
let mut rdr = csv::Reader::from_reader(file);
|
let mut rdr = csv::Reader::from_reader(file);
|
||||||
|
@ -1792,13 +1793,15 @@ fn main() {
|
||||||
Ok(m) => { m }
|
Ok(m) => { m }
|
||||||
Err(e) => { panic!(e.to_string()) }
|
Err(e) => { panic!(e.to_string()) }
|
||||||
};
|
};
|
||||||
|
|
||||||
if matches.opt_present("h") {
|
if matches.opt_present("h") {
|
||||||
print_usage(&program, opts);
|
print_usage(&program, opts);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let data_path = &args[1];
|
let data_path = &matches.free[0];
|
||||||
let city = &args[2];
|
let city: &str = &matches.free[1];
|
||||||
|
|
||||||
for pop in search(data_path, city) {
|
for pop in search(data_path, city) {
|
||||||
println!("{}, {}: {:?}", pop.city, pop.country, pop.count);
|
println!("{}, {}: {:?}", pop.city, pop.country, pop.count);
|
||||||
}
|
}
|
||||||
|
@ -1876,14 +1879,14 @@ when calling `search`:
|
||||||
|
|
||||||
```rust,ignore
|
```rust,ignore
|
||||||
...
|
...
|
||||||
match search(&data_file, &city) {
|
match search(data_path, city) {
|
||||||
Ok(pops) => {
|
Ok(pops) => {
|
||||||
for pop in pops {
|
for pop in pops {
|
||||||
println!("{}, {}: {:?}", pop.city, pop.country, pop.count);
|
println!("{}, {}: {:?}", pop.city, pop.country, pop.count);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Err(err) => println!("{}", err)
|
||||||
}
|
}
|
||||||
Err(err) => println!("{}", err)
|
|
||||||
}
|
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1914,43 +1917,37 @@ fn print_usage(program: &str, opts: Options) {
|
||||||
println!("{}", opts.usage(&format!("Usage: {} [options] <city>", program)));
|
println!("{}", opts.usage(&format!("Usage: {} [options] <city>", program)));
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
The next part is going to be only a little harder:
|
Of course we need to adapt the argument handling code:
|
||||||
|
|
||||||
```rust,ignore
|
```rust,ignore
|
||||||
...
|
...
|
||||||
let mut opts = Options::new();
|
let mut opts = Options::new();
|
||||||
opts.optopt("f", "file", "Choose an input file, instead of using STDIN.", "NAME");
|
opts.optopt("f", "file", "Choose an input file, instead of using STDIN.", "NAME");
|
||||||
opts.optflag("h", "help", "Show this usage message.");
|
opts.optflag("h", "help", "Show this usage message.");
|
||||||
...
|
...
|
||||||
let file = matches.opt_str("f");
|
let data_path = matches.opt_str("f");
|
||||||
let data_file = &file.as_ref().map(Path::new);
|
|
||||||
|
|
||||||
let city = if !matches.free.is_empty() {
|
let city = if !matches.free.is_empty() {
|
||||||
&matches.free[0]
|
&matches.free[0]
|
||||||
} else {
|
} else {
|
||||||
print_usage(&program, opts);
|
print_usage(&program, opts);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
match search(data_file, city) {
|
match search(&data_path, city) {
|
||||||
Ok(pops) => {
|
Ok(pops) => {
|
||||||
for pop in pops {
|
for pop in pops {
|
||||||
println!("{}, {}: {:?}", pop.city, pop.country, pop.count);
|
println!("{}, {}: {:?}", pop.city, pop.country, pop.count);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Err(err) => println!("{}", err)
|
||||||
}
|
}
|
||||||
Err(err) => println!("{}", err)
|
|
||||||
}
|
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
In this piece of code, we take `file` (which has the type
|
We've made the user experience a bit nicer by showing the usage message,
|
||||||
`Option<String>`), and convert it to a type that `search` can use, in
|
instead of a panic from an out-of-bounds index, when `city`, the
|
||||||
this case, `&Option<AsRef<Path>>`. To do this, we take a reference of
|
remaining free argument, is not present.
|
||||||
file, and map `Path::new` onto it. In this case, `as_ref()` converts
|
|
||||||
the `Option<String>` into an `Option<&str>`, and from there, we can
|
|
||||||
execute `Path::new` to the content of the optional, and return the
|
|
||||||
optional of the new value. Once we have that, it is a simple matter of
|
|
||||||
getting the `city` argument and executing `search`.
|
|
||||||
|
|
||||||
Modifying `search` is slightly trickier. The `csv` crate can build a
|
Modifying `search` is slightly trickier. The `csv` crate can build a
|
||||||
parser out of
|
parser out of
|
||||||
|
@ -2000,6 +1997,8 @@ enum CliError {
|
||||||
And now for impls on `Display` and `Error`:
|
And now for impls on `Display` and `Error`:
|
||||||
|
|
||||||
```rust,ignore
|
```rust,ignore
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
impl fmt::Display for CliError {
|
impl fmt::Display for CliError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
|
@ -2020,13 +2019,13 @@ impl Error for CliError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cause(&self) -> Option<&error::Error> {
|
fn cause(&self) -> Option<&Error> {
|
||||||
match *self {
|
match *self {
|
||||||
CliError::Io(ref err) => Some(err),
|
CliError::Io(ref err) => Some(err),
|
||||||
CliError::Parse(ref err) => Some(err),
|
CliError::Csv(ref err) => Some(err),
|
||||||
// Our custom error doesn't have an underlying cause, but we could
|
// Our custom error doesn't have an underlying cause,
|
||||||
// modify it so that it does.
|
// but we could modify it so that it does.
|
||||||
CliError::NotFound() => None,
|
CliError::NotFound => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2122,10 +2121,10 @@ string and add a flag to the Option variable. Once we've done that, Getopts does
|
||||||
|
|
||||||
```rust,ignore
|
```rust,ignore
|
||||||
...
|
...
|
||||||
let mut opts = Options::new();
|
let mut opts = Options::new();
|
||||||
opts.optopt("f", "file", "Choose an input file, instead of using STDIN.", "NAME");
|
opts.optopt("f", "file", "Choose an input file, instead of using STDIN.", "NAME");
|
||||||
opts.optflag("h", "help", "Show this usage message.");
|
opts.optflag("h", "help", "Show this usage message.");
|
||||||
opts.optflag("q", "quiet", "Silences errors and warnings.");
|
opts.optflag("q", "quiet", "Silences errors and warnings.");
|
||||||
...
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -2133,13 +2132,16 @@ Now we only need to implement our “quiet” functionality. This requires us to
|
||||||
tweak the case analysis in `main`:
|
tweak the case analysis in `main`:
|
||||||
|
|
||||||
```rust,ignore
|
```rust,ignore
|
||||||
match search(&args.arg_data_path, &args.arg_city) {
|
use std::process;
|
||||||
Err(CliError::NotFound) if args.flag_quiet => process::exit(1),
|
...
|
||||||
Err(err) => panic!("{}", err),
|
match search(&data_path, city) {
|
||||||
Ok(pops) => for pop in pops {
|
Err(CliError::NotFound) if matches.opt_present("q") => process::exit(1),
|
||||||
println!("{}, {}: {:?}", pop.city, pop.country, pop.count);
|
Err(err) => panic!("{}", err),
|
||||||
|
Ok(pops) => for pop in pops {
|
||||||
|
println!("{}, {}: {:?}", pop.city, pop.country, pop.count);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
Certainly, we don't want to be quiet if there was an IO error or if the data
|
Certainly, we don't want to be quiet if there was an IO error or if the data
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue