@@ -1573,8 +1573,9 @@ detail on Getopts, but there is [some good documentation][15]
1573
1573
describing it. The short story is that Getopts generates an argument
1574
1574
parser and a help message from a vector of options (The fact that it
1575
1575
is 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
1578
1579
instance, whether they were passed in, and what arguments they
1579
1580
had. Here's our program with the appropriate ` extern crate `
1580
1581
statements, and the basic argument setup for Getopts:
@@ -1605,8 +1606,8 @@ fn main() {
1605
1606
print_usage(&program, opts);
1606
1607
return;
1607
1608
}
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 ];
1610
1611
1611
1612
// Do stuff with information
1612
1613
}
@@ -1680,8 +1681,8 @@ fn main() {
1680
1681
return;
1681
1682
}
1682
1683
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 ];
1685
1686
1686
1687
let file = File::open(data_path).unwrap();
1687
1688
let mut rdr = csv::Reader::from_reader(file);
@@ -1792,13 +1793,15 @@ fn main() {
1792
1793
Ok(m) => { m }
1793
1794
Err(e) => { panic!(e.to_string()) }
1794
1795
};
1796
+
1795
1797
if matches.opt_present("h") {
1796
1798
print_usage(&program, opts);
1797
1799
return;
1798
1800
}
1799
1801
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
+
1802
1805
for pop in search(data_path, city) {
1803
1806
println!("{}, {}: {:?}", pop.city, pop.country, pop.count);
1804
1807
}
@@ -1876,14 +1879,14 @@ when calling `search`:
1876
1879
1877
1880
``` rust,ignore
1878
1881
...
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
+ }
1883
1887
}
1888
+ Err(err) => println!("{}", err)
1884
1889
}
1885
- Err(err) => println!("{}", err)
1886
- }
1887
1890
...
1888
1891
```
1889
1892
@@ -1914,43 +1917,37 @@ fn print_usage(program: &str, opts: Options) {
1914
1917
println!("{}", opts.usage(&format!("Usage: {} [options] <city>", program)));
1915
1918
}
1916
1919
```
1917
- The next part is going to be only a little harder :
1920
+ Of course we need to adapt the argument handling code :
1918
1921
1919
1922
``` rust,ignore
1920
1923
...
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
+ }
1939
1942
}
1943
+ Err(err) => println!("{}", err)
1940
1944
}
1941
- Err(err) => println!("{}", err)
1942
- }
1943
1945
...
1944
1946
```
1945
1947
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.
1954
1951
1955
1952
Modifying ` search ` is slightly trickier. The ` csv ` crate can build a
1956
1953
parser out of
@@ -2000,6 +1997,8 @@ enum CliError {
2000
1997
And now for impls on ` Display ` and ` Error ` :
2001
1998
2002
1999
``` rust,ignore
2000
+ use std::fmt;
2001
+
2003
2002
impl fmt::Display for CliError {
2004
2003
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2005
2004
match *self {
@@ -2020,13 +2019,13 @@ impl Error for CliError {
2020
2019
}
2021
2020
}
2022
2021
2023
- fn cause(&self) -> Option<&error:: Error> {
2024
- match *self {
2022
+ fn cause(&self) -> Option<&Error> {
2023
+ match *self {
2025
2024
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,
2030
2029
}
2031
2030
}
2032
2031
}
@@ -2122,24 +2121,27 @@ string and add a flag to the Option variable. Once we've done that, Getopts does
2122
2121
2123
2122
``` rust,ignore
2124
2123
...
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.");
2129
2128
...
2130
2129
```
2131
2130
2132
2131
Now we only need to implement our “quiet” functionality. This requires us to
2133
2132
tweak the case analysis in ` main ` :
2134
2133
2135
2134
``` 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
+ }
2141
2143
}
2142
- }
2144
+ ...
2143
2145
```
2144
2146
2145
2147
Certainly, we don't want to be quiet if there was an IO error or if the data
0 commit comments