Add broken error test case
This commit is contained in:
parent
f635b26671
commit
4b413c9e54
BIN
city-pop/.DS_Store
vendored
Normal file
BIN
city-pop/.DS_Store
vendored
Normal file
Binary file not shown.
33
city-pop/Cargo.lock
generated
Normal file
33
city-pop/Cargo.lock
generated
Normal file
@ -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"
|
||||||
|
|
12
city-pop/Cargo.toml
Normal file
12
city-pop/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
[package]
|
||||||
|
name = "city-pop"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Tim Warren <twarren@nexient.com>"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "city-pop"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
csv = "0.*"
|
||||||
|
rustc-serialize = "0.*"
|
||||||
|
getopts = "0.*"
|
146
city-pop/src/main.rs
Normal file
146
city-pop/src/main.rs
Normal file
@ -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<u64>,
|
||||||
|
latitude: Option<f64>,
|
||||||
|
longitude: Option<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
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<io::Error> for CliError {
|
||||||
|
fn from(err: io::Error) -> CliError {
|
||||||
|
CliError::Io(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<csv::Error> for CliError {
|
||||||
|
fn from(err: csv::Error) -> CliError {
|
||||||
|
CliError::Csv(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_usage(program: &str, opts: Options) {
|
||||||
|
println!("{}", opts.usage(&format!("Usage: {} [options] <city>", program)));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search<P: AsRef<Path>>
|
||||||
|
(file_path: &Option<P>, city: &str)
|
||||||
|
-> Result<Vec<PopulationCount>, CliError> {
|
||||||
|
let mut found = vec![];
|
||||||
|
let input: Box<io::Read> = 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::<Row>() {
|
||||||
|
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<String> = 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user