diff --git a/city-pop/.DS_Store b/city-pop/.DS_Store new file mode 100644 index 0000000..7d69590 Binary files /dev/null and b/city-pop/.DS_Store differ diff --git a/city-pop/Cargo.lock b/city-pop/Cargo.lock new file mode 100644 index 0000000..88162d0 --- /dev/null +++ b/city-pop/Cargo.lock @@ -0,0 +1,33 @@ +[root] +name = "city-pop" +version = "0.1.0" +dependencies = [ + "csv 0.14.3 (registry+https://github.com/rust-lang/crates.io-index)", + "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "byteorder" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "csv" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-serialize 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "getopts" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rustc-serialize" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" + diff --git a/city-pop/Cargo.toml b/city-pop/Cargo.toml new file mode 100644 index 0000000..3f28397 --- /dev/null +++ b/city-pop/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "city-pop" +version = "0.1.0" +authors = ["Tim Warren "] + +[[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