Skip to content

Commit

Permalink
book: fixup code in error handling tutorial
Browse files Browse the repository at this point in the history
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
  • Loading branch information
birkenfeld committed May 7, 2016
1 parent 6478583 commit d75c079
Showing 1 changed file with 61 additions and 59 deletions.
120 changes: 61 additions & 59 deletions src/doc/book/error-handling.md
Expand Up @@ -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
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
parsing is done, we can decode the program arguments into a Rust
struct. From there, we can get information about the flags, for
parsing is done, the parser returns a struct that records matches
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
had. Here's our program with the appropriate `extern crate`
statements, and the basic argument setup for Getopts:
Expand Down Expand Up @@ -1605,8 +1606,8 @@ fn main() {
print_usage(&program, opts);
return;
}
let data_path = &args[1];
let city = &args[2];
let data_path = &matches.free[0];
let city: &str = &matches.free[1];
// Do stuff with information
}
Expand Down Expand Up @@ -1680,8 +1681,8 @@ fn main() {
return;
}
let data_path = &args[1];
let city: &str = &args[2];
let data_path = &matches.free[0];
let city: &str = &matches.free[1];
let file = File::open(data_path).unwrap();
let mut rdr = csv::Reader::from_reader(file);
Expand Down Expand Up @@ -1792,13 +1793,15 @@ fn main() {
Ok(m) => { m }
Err(e) => { panic!(e.to_string()) }
};
if matches.opt_present("h") {
print_usage(&program, opts);
return;
}
let data_path = &args[1];
let city = &args[2];
let data_path = &matches.free[0];
let city: &str = &matches.free[1];
for pop in search(data_path, city) {
println!("{}, {}: {:?}", pop.city, pop.country, pop.count);
}
Expand Down Expand Up @@ -1876,14 +1879,14 @@ when calling `search`:

```rust,ignore
...
match search(&data_file, &city) {
Ok(pops) => {
for pop in pops {
println!("{}, {}: {:?}", pop.city, pop.country, pop.count);
match search(data_path, city) {
Ok(pops) => {
for pop in pops {
println!("{}, {}: {:?}", pop.city, pop.country, pop.count);
}
}
Err(err) => println!("{}", err)
}
Err(err) => println!("{}", err)
}
...
```

Expand Down Expand Up @@ -1914,43 +1917,37 @@ fn print_usage(program: &str, opts: Options) {
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
...
let mut opts = Options::new();
opts.optopt("f", "file", "Choose an input file, instead of using STDIN.", "NAME");
opts.optflag("h", "help", "Show this usage message.");
...
let file = matches.opt_str("f");
let data_file = &file.as_ref().map(Path::new);
let city = if !matches.free.is_empty() {
&matches.free[0]
} else {
print_usage(&program, opts);
return;
};
match search(data_file, city) {
Ok(pops) => {
for pop in pops {
println!("{}, {}: {:?}", pop.city, pop.country, pop.count);
let mut opts = Options::new();
opts.optopt("f", "file", "Choose an input file, instead of using STDIN.", "NAME");
opts.optflag("h", "help", "Show this usage message.");
...
let data_path = matches.opt_str("f");
let city = if !matches.free.is_empty() {
&matches.free[0]
} else {
print_usage(&program, opts);
return;
};
match search(&data_path, city) {
Ok(pops) => {
for pop in pops {
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
`Option<String>`), and convert it to a type that `search` can use, in
this case, `&Option<AsRef<Path>>`. To do this, we take a reference of
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`.
We've made the user experience a bit nicer by showing the usage message,
instead of a panic from an out-of-bounds index, when `city`, the
remaining free argument, is not present.

Modifying `search` is slightly trickier. The `csv` crate can build a
parser out of
Expand Down Expand Up @@ -2000,6 +1997,8 @@ enum CliError {
And now for impls on `Display` and `Error`:

```rust,ignore
use std::fmt;
impl fmt::Display for CliError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Expand All @@ -2020,13 +2019,13 @@ impl Error for CliError {
}
}
fn cause(&self) -> Option<&error::Error> {
match *self {
fn cause(&self) -> Option<&Error> {
match *self {
CliError::Io(ref err) => Some(err),
CliError::Parse(ref err) => Some(err),
// Our custom error doesn't have an underlying cause, but we could
// modify it so that it does.
CliError::NotFound() => None,
CliError::Csv(ref err) => Some(err),
// Our custom error doesn't have an underlying cause,
// but we could modify it so that it does.
CliError::NotFound => None,
}
}
}
Expand Down Expand Up @@ -2122,24 +2121,27 @@ string and add a flag to the Option variable. Once we've done that, Getopts does

```rust,ignore
...
let mut opts = Options::new();
opts.optopt("f", "file", "Choose an input file, instead of using STDIN.", "NAME");
opts.optflag("h", "help", "Show this usage message.");
opts.optflag("q", "quiet", "Silences errors and warnings.");
let mut opts = Options::new();
opts.optopt("f", "file", "Choose an input file, instead of using STDIN.", "NAME");
opts.optflag("h", "help", "Show this usage message.");
opts.optflag("q", "quiet", "Silences errors and warnings.");
...
```

Now we only need to implement our “quiet” functionality. This requires us to
tweak the case analysis in `main`:

```rust,ignore
match search(&args.arg_data_path, &args.arg_city) {
Err(CliError::NotFound) if args.flag_quiet => process::exit(1),
Err(err) => panic!("{}", err),
Ok(pops) => for pop in pops {
println!("{}, {}: {:?}", pop.city, pop.country, pop.count);
use std::process;
...
match search(&data_path, city) {
Err(CliError::NotFound) if matches.opt_present("q") => process::exit(1),
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
Expand Down

0 comments on commit d75c079

Please sign in to comment.