@@ -1573,8 +1573,9 @@ 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, we can decode the program arguments into a Rust
1577- struct. From there, we can get information about the flags, for
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
15781579instance, whether they were passed in, and what arguments they
15791580had. Here's our program with the appropriate ` extern crate `
15801581statements, and the basic argument setup for Getopts:
@@ -1605,8 +1606,8 @@ fn main() {
16051606 print_usage(&program, opts);
16061607 return;
16071608 }
1608- let data_path = &args[1 ];
1609- let city = &args[2 ];
1609+ let data_path = &matches.free[0 ];
1610+ let city: &str = &matches.free[1 ];
16101611
16111612 // Do stuff with information
16121613}
@@ -1680,8 +1681,8 @@ fn main() {
16801681 return;
16811682 }
16821683
1683- let data_path = &args[1 ];
1684- let city: &str = &args[2 ];
1684+ let data_path = &matches.free[0 ];
1685+ let city: &str = &matches.free[1 ];
16851686
16861687 let file = File::open(data_path).unwrap();
16871688 let mut rdr = csv::Reader::from_reader(file);
@@ -1792,13 +1793,15 @@ fn main() {
17921793 Ok(m) => { m }
17931794 Err(e) => { panic!(e.to_string()) }
17941795 };
1796+
17951797 if matches.opt_present("h") {
17961798 print_usage(&program, opts);
17971799 return;
17981800 }
17991801
1800- let data_path = &args[1];
1801- let city = &args[2];
1802+ let data_path = &matches.free[0];
1803+ let city: &str = &matches.free[1];
1804+
18021805 for pop in search(data_path, city) {
18031806 println!("{}, {}: {:?}", pop.city, pop.country, pop.count);
18041807 }
@@ -1876,14 +1879,14 @@ when calling `search`:
18761879
18771880``` rust,ignore
18781881...
1879- match search(&data_file, &city) {
1880- Ok(pops) => {
1881- for pop in pops {
1882- println!("{}, {}: {:?}", pop.city, pop.country, pop.count);
1882+ match search(data_path, city) {
1883+ Ok(pops) => {
1884+ for pop in pops {
1885+ println!("{}, {}: {:?}", pop.city, pop.country, pop.count);
1886+ }
18831887 }
1888+ Err(err) => println!("{}", err)
18841889 }
1885- Err(err) => println!("{}", err)
1886- }
18871890...
18881891```
18891892
@@ -1914,43 +1917,37 @@ fn print_usage(program: &str, opts: Options) {
19141917 println!("{}", opts.usage(&format!("Usage: {} [options] <city>", program)));
19151918}
19161919```
1917- The next part is going to be only a little harder :
1920+ Of course we need to adapt the argument handling code :
19181921
19191922``` rust,ignore
19201923...
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);
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+ }
19391942 }
1943+ Err(err) => println!("{}", err)
19401944 }
1941- Err(err) => println!("{}", err)
1942- }
19431945...
19441946```
19451947
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 ` .
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.
19541951
19551952Modifying ` search ` is slightly trickier. The ` csv ` crate can build a
19561953parser out of
@@ -2000,6 +1997,8 @@ enum CliError {
20001997And now for impls on ` Display ` and ` Error ` :
20011998
20021999``` rust,ignore
2000+ use std::fmt;
2001+
20032002impl fmt::Display for CliError {
20042003 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
20052004 match *self {
@@ -2020,13 +2019,13 @@ impl Error for CliError {
20202019 }
20212020 }
20222021
2023- fn cause(&self) -> Option<&error:: Error> {
2024- match *self {
2022+ fn cause(&self) -> Option<&Error> {
2023+ match *self {
20252024 CliError::Io(ref err) => Some(err),
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,
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,
20302029 }
20312030 }
20322031}
@@ -2122,24 +2121,27 @@ string and add a flag to the Option variable. Once we've done that, Getopts does
21222121
21232122``` rust,ignore
21242123...
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.");
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.");
21292128...
21302129```
21312130
21322131Now we only need to implement our “quiet” functionality. This requires us to
21332132tweak the case analysis in ` main ` :
21342133
21352134``` rust,ignore
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);
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+ }
21412143 }
2142- }
2144+ ...
21432145```
21442146
21452147Certainly, we don't want to be quiet if there was an IO error or if the data
0 commit comments