@@ -1573,9 +1573,8 @@ detail on Getopts, but there is [some good documentation][15]
15731573describing it. The short story is that Getopts generates an argument
15741574parser and a help message from a vector of options (The fact that it
15751575is a vector is hidden behind a struct and a set of methods). Once the
1576- parsing is done, the parser returns a struct that records matches
1577- for defined options, and remaining "free" arguments.
1578- From there, we can get information about the flags, for
1576+ parsing is done, we can decode the program arguments into a Rust
1577+ struct. From there, we can get information about the flags, for
15791578instance, whether they were passed in, and what arguments they
15801579had. Here's our program with the appropriate ` extern crate `
15811580statements, and the basic argument setup for Getopts:
@@ -1606,8 +1605,8 @@ fn main() {
16061605 print_usage(&program, opts);
16071606 return;
16081607 }
1609- let data_path = &matches.free[0 ];
1610- let city: &str = &matches.free[1 ];
1608+ let data_path = &args[1 ];
1609+ let city = &args[2 ];
16111610
16121611 // Do stuff with information
16131612}
@@ -1681,8 +1680,8 @@ fn main() {
16811680 return;
16821681 }
16831682
1684- let data_path = &matches.free[0 ];
1685- let city: &str = &matches.free[1 ];
1683+ let data_path = &args[1 ];
1684+ let city: &str = &args[2 ];
16861685
16871686 let file = File::open(data_path).unwrap();
16881687 let mut rdr = csv::Reader::from_reader(file);
@@ -1793,15 +1792,13 @@ fn main() {
17931792 Ok(m) => { m }
17941793 Err(e) => { panic!(e.to_string()) }
17951794 };
1796-
17971795 if matches.opt_present("h") {
17981796 print_usage(&program, opts);
17991797 return;
18001798 }
18011799
1802- let data_path = &matches.free[0];
1803- let city: &str = &matches.free[1];
1804-
1800+ let data_path = &args[1];
1801+ let city = &args[2];
18051802 for pop in search(data_path, city) {
18061803 println!("{}, {}: {:?}", pop.city, pop.country, pop.count);
18071804 }
@@ -1879,14 +1876,14 @@ when calling `search`:
18791876
18801877``` rust,ignore
18811878...
1882- match search(data_path, city) {
1883- Ok(pops) => {
1884- for pop in pops {
1885- println!("{}, {}: {:?}", pop.city, pop.country, pop.count);
1886- }
1879+ match search(&data_file, &city) {
1880+ Ok(pops) => {
1881+ for pop in pops {
1882+ println!("{}, {}: {:?}", pop.city, pop.country, pop.count);
18871883 }
1888- Err(err) => println!("{}", err)
18891884 }
1885+ Err(err) => println!("{}", err)
1886+ }
18901887...
18911888```
18921889
@@ -1917,37 +1914,43 @@ fn print_usage(program: &str, opts: Options) {
19171914 println!("{}", opts.usage(&format!("Usage: {} [options] <city>", program)));
19181915}
19191916```
1920- Of course we need to adapt the argument handling code :
1917+ The next part is going to be only a little harder :
19211918
19221919``` rust,ignore
19231920...
1924- let mut opts = Options::new();
1925- opts.optopt("f", "file", "Choose an input file, instead of using STDIN.", "NAME");
1926- opts.optflag("h", "help", "Show this usage message.");
1927- ...
1928- let data_path = matches.opt_str("f");
1929-
1930- let city = if !matches.free.is_empty() {
1931- & matches.free[0]
1932- } else {
1933- print_usage(&program, opts);
1934- return ;
1935- } ;
1936-
1937- match search(&data_path, city) {
1938- Ok(pops) => {
1939- for pop in pops {
1940- println!("{}, {}: {:?}", pop.city, pop.country, pop.count);
1941- }
1921+ let mut opts = Options::new();
1922+ opts.optopt("f", "file", "Choose an input file, instead of using STDIN.", "NAME");
1923+ opts.optflag("h", "help", "Show this usage message.");
1924+ ...
1925+ let file = matches.opt_str("f");
1926+ let data_file = &file.as_ref().map(Path::new);
1927+
1928+ let city = if ! matches.free.is_empty() {
1929+ &matches.free[0]
1930+ } else {
1931+ print_usage(&program, opts) ;
1932+ return ;
1933+ };
1934+
1935+ match search(data_file, city) {
1936+ Ok( pops) => {
1937+ for pop in pops {
1938+ println!("{}, {}: {:?}", pop.city, pop.country, pop.count);
19421939 }
1943- Err(err) => println!("{}", err)
19441940 }
1941+ Err(err) => println!("{}", err)
1942+ }
19451943...
19461944```
19471945
1948- We've made the user experience a bit nicer by showing the usage message,
1949- instead of a panic from an out-of-bounds index, when ` city ` , the
1950- remaining free argument, is not present.
1946+ In this piece of code, we take ` file ` (which has the type
1947+ ` Option<String> ` ), and convert it to a type that ` search ` can use, in
1948+ this case, ` &Option<AsRef<Path>> ` . To do this, we take a reference of
1949+ file, and map ` Path::new ` onto it. In this case, ` as_ref() ` converts
1950+ the ` Option<String> ` into an ` Option<&str> ` , and from there, we can
1951+ execute ` Path::new ` to the content of the optional, and return the
1952+ optional of the new value. Once we have that, it is a simple matter of
1953+ getting the ` city ` argument and executing ` search ` .
19511954
19521955Modifying ` search ` is slightly trickier. The ` csv ` crate can build a
19531956parser out of
@@ -1997,8 +2000,6 @@ enum CliError {
19972000And now for impls on ` Display ` and ` Error ` :
19982001
19992002``` rust,ignore
2000- use std::fmt;
2001-
20022003impl fmt::Display for CliError {
20032004 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
20042005 match *self {
@@ -2019,13 +2020,13 @@ impl Error for CliError {
20192020 }
20202021 }
20212022
2022- fn cause(&self) -> Option<&Error> {
2023- match *self {
2023+ fn cause(&self) -> Option<&error:: Error> {
2024+ match *self {
20242025 CliError::Io(ref err) => Some(err),
2025- CliError::Csv (ref err) => Some(err),
2026- // Our custom error doesn't have an underlying cause,
2027- // but we could modify it so that it does.
2028- CliError::NotFound => None,
2026+ CliError::Parse (ref err) => Some(err),
2027+ // Our custom error doesn't have an underlying cause, but we could
2028+ // modify it so that it does.
2029+ CliError::NotFound() => None,
20292030 }
20302031 }
20312032}
@@ -2121,27 +2122,24 @@ string and add a flag to the Option variable. Once we've done that, Getopts does
21212122
21222123``` rust,ignore
21232124...
2124- let mut opts = Options::new();
2125- opts.optopt("f", "file", "Choose an input file, instead of using STDIN.", "NAME");
2126- opts.optflag("h", "help", "Show this usage message.");
2127- opts.optflag("q", "quiet", "Silences errors and warnings.");
2125+ let mut opts = Options::new();
2126+ opts.optopt("f", "file", "Choose an input file, instead of using STDIN.", "NAME");
2127+ opts.optflag("h", "help", "Show this usage message.");
2128+ opts.optflag("q", "quiet", "Silences errors and warnings.");
21282129...
21292130```
21302131
21312132Now we only need to implement our “quiet” functionality. This requires us to
21322133tweak the case analysis in ` main ` :
21332134
21342135``` rust,ignore
2135- use std::process;
2136- ...
2137- match search(&data_path, city) {
2138- Err(CliError::NotFound) if matches.opt_present("q") => process::exit(1),
2139- Err(err) => panic!("{}", err),
2140- Ok(pops) => for pop in pops {
2141- println!("{}, {}: {:?}", pop.city, pop.country, pop.count);
2142- }
2136+ match search(&args.arg_data_path, &args.arg_city) {
2137+ Err(CliError::NotFound) if args.flag_quiet => process::exit(1),
2138+ Err(err) => panic!("{}", err),
2139+ Ok(pops) => for pop in pops {
2140+ println!("{}, {}: {:?}", pop.city, pop.country, pop.count);
21432141 }
2144- ...
2142+ }
21452143```
21462144
21472145Certainly, we don't want to be quiet if there was an IO error or if the data
0 commit comments