From 4b413c9e54ba181495d2a9e3c4eace674ae3f186 Mon Sep 17 00:00:00 2001 From: Tim Warren Date: Fri, 11 Dec 2015 14:31:33 -0500 Subject: [PATCH] Add broken error test case --- city-pop/.DS_Store | Bin 0 -> 6148 bytes city-pop/Cargo.lock | 33 ++++++++++ city-pop/Cargo.toml | 12 ++++ city-pop/src/main.rs | 146 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 191 insertions(+) create mode 100644 city-pop/.DS_Store create mode 100644 city-pop/Cargo.lock create mode 100644 city-pop/Cargo.toml create mode 100644 city-pop/src/main.rs diff --git a/city-pop/.DS_Store b/city-pop/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..7d6959026f90bf4e0ef86db860f12cb6466df3e4 GIT binary patch literal 6148 zcmeHK!A`?441L)K8gO9Z#4$%sNc=%H28b*C09!YqiK0%Uz_lO92k4w`TUAdct`!nXV_N{&~|6zhoWp}3ezjHynq7%ryM z?=>z{tRu#BIK6y0o!RLP#f8~9z7OGWSwyqOfHBZzU>F}KQva`hzW;ZV?8z7~2L2QS zZZx}}O}Qkkt-Zxbt@YFgs))qZ5!)0tLMdjfl;Trr5c<6|h?!y?ksgZu2m~6;7y~"] + +[[bin]] +name = "city-pop" + +[dependencies] +csv = "0.*" +rustc-serialize = "0.*" +getopts = "0.*" diff --git a/city-pop/src/main.rs b/city-pop/src/main.rs new file mode 100644 index 0000000..ffe5a6f --- /dev/null +++ b/city-pop/src/main.rs @@ -0,0 +1,146 @@ +extern crate getopts; +extern crate rustc_serialize; +extern crate csv; + +use getopts::Options; +use std::{env, io, fmt, fs, process, str}; +use std::path::Path; +use std::error::Error; + +// This struct represents the data in each row of the CSV file. +// Type based decoding absolves us of a lot of the nitty gritty error +// handling, like parsing strings as integers or floats +#[derive(Debug, RustcDecodable)] +struct Row { + country: String, + city: String, + accent_city: String, + region: String, + + // Not every row has data for the population, latitude or logitude! + // So we express them as `Option` types, which admits the possiblity of + // absence. The CSV parser will fill in the correct value for us. + population: Option, + latitude: Option, + longitude: Option, +} + +struct PopulationCount { + city: String, + country: String, + // This is no longer an `Option` because values of this type are only + // constructed if they have a population count. + count: u64, +} + +#[derive(Debug)] +enum CliError { + Io(io::Error), + Csv(csv::Error), + NotFound +} + +impl fmt::Display for CliError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + CliError::Io(ref err) => err.fmt(f), + CliError::Csv(ref err) => err.fmt(f), + CliError::NotFound => write!(f, "No matching cities with a population were found"), + } + } +} + +impl Error for CliError { + fn description(&self) -> &str { + match *self { + CliError::Io(ref err) => err.description(), + CliError::Csv(ref err) => err.description(), + CliError::NotFound => "not found", + } + } +} + +impl From for CliError { + fn from(err: io::Error) -> CliError { + CliError::Io(err) + } +} + +impl From for CliError { + fn from(err: csv::Error) -> CliError { + CliError::Csv(err) + } +} + +fn print_usage(program: &str, opts: Options) { + println!("{}", opts.usage(&format!("Usage: {} [options] ", program))); +} + +fn search> + (file_path: &Option

, city: &str) + -> Result, CliError> { + let mut found = vec![]; + let input: Box = match *file_path { + None => Box::new(io::stdin()), + Some(ref file_path) => Box::new(try!(fs::File::open(file_path))), + }; + let mut rdr = csv::Reader::from_reader(input); + + for row in rdr.decode::() { + let row = try!(row); + match row.population { + None => {} // Skip it + Some(count) => if row.city == city { + found.push(PopulationCount { + city: row.city, + country: row.country, + count: count, + }); + }, + } + } + + if found.is_empty() { + Err(CliError::NotFound) + } else { + Ok(found) + } +} + +fn main() { + let args: Vec = env::args().collect(); + let program = args[0].clone(); + + 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 matches = match opts.parse(&args[1..]) { + Ok(m) => { m } + Err(e) => { panic!(e.to_string()) } + }; + + if matches.opt_present("h") { + print_usage(&program, opts); + return; + } + + 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].clone(); + } else { + print_usage(&program, opts); + return; + }; + + match search(&data_file, &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); + } + } +} \ No newline at end of file