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); } } }