Refactor as much of the html parsing as possible into its own struct, since the pirate/ninja parsing logic is 99% identical (with the exception of class names and skill attributes).

This commit is contained in:
Adrian Malacoda 2017-02-19 01:41:05 -06:00
parent bec5b48601
commit 4ff066b8b7
3 changed files with 88 additions and 58 deletions

View File

@ -5,6 +5,11 @@ extern crate select;
pub mod pirates; pub mod pirates;
pub mod ninjas; pub mod ninjas;
use select::document::Document;
use select::predicate::Class;
use std::collections::BTreeMap;
#[derive(Debug)] #[derive(Debug)]
pub enum Error { pub enum Error {
Http(hyper::error::Error), Http(hyper::error::Error),
@ -41,3 +46,62 @@ pub fn parse_weapons (weapons: &str) -> Vec<String> {
weapons.split(", ").into_iter().map(String::from).collect() weapons.split(", ").into_iter().map(String::from).collect()
} }
pub struct Parser {
fighter_type: &'static str,
document: Document
}
impl Parser {
pub fn parse_name (&self) -> Result<String, Error> {
let name_class = format!("{}-name", self.fighter_type);
self.document.find(Class(&name_class[..])).first()
.ok_or_else(|| Error::Parse(format!("could not find {} element", name_class))).map(|result| result.text())
}
pub fn parse_skills (&self) -> Result<Skills, Error> {
let skill_class = format!("{}-skill", self.fighter_type);
let score_class = format!("{}-score", self.fighter_type);
let map: Result<BTreeMap<String, Skill>, Error> = self.document.find(Class(&skill_class[..])).iter().map(|node| {
let skill = node.text();
let score = node.parent().ok_or_else(|| Error::Parse(format!("could not get {} parent element", skill_class)))?
.find(Class(&score_class[..])).first()
.ok_or_else(|| Error::Parse(format!("could not get {} element", score_class)))?
.text();
Result::Ok((skill, Skill { score: score }))
}).collect();
Result::Ok(Skills {
map: map?
})
}
}
pub struct Skills {
map: BTreeMap<String, Skill>
}
impl Skills {
pub fn get (&self, key: &str) -> Result<&Skill, Error> {
self.map.get(key).ok_or_else(|| Error::Parse(format!("could not get {} value", key)))
}
}
pub struct Skill {
score: String
}
impl Skill {
pub fn as_number (&self) -> Result<u8, std::num::ParseIntError> {
self.score.parse::<u8>()
}
pub fn as_weapons (&self) -> Vec<String> {
if self.score == "None" {
return vec![];
}
self.score.split(", ").into_iter().map(String::from).collect()
}
}

View File

@ -1,11 +1,10 @@
use lru_cache::LruCache; use lru_cache::LruCache;
use hyper::client::Client; use hyper::client::Client;
use select::document::Document; use select::document::Document;
use select::predicate::Class;
use std::io::Read; use std::io::Read;
use {Error, Fighter, parse_weapons}; use {Error, Fighter, Parser};
pub struct Ninjas { pub struct Ninjas {
cache: LruCache<String, Ninja>, cache: LruCache<String, Ninja>,
@ -52,35 +51,19 @@ pub struct Ninja {
impl Ninja { impl Ninja {
pub fn from_html (contents: &str) -> Result<Ninja, Error> { pub fn from_html (contents: &str) -> Result<Ninja, Error> {
let document = Document::from(contents); let parser = Parser {
let full_name = document.find(Class("ninja-name")).first() fighter_type: "ninja",
.ok_or_else(|| Error::Parse(String::from("could not find ninja-name element")))?.text(); document: Document::from(contents)
let mut ninja = Ninja {
name: String::from(full_name),
sneakiness: 0,
pajamas: 0,
pointy_things: 0,
weapons: vec![]
}; };
for node in document.find(Class("ninja-skill")).iter() { let skills = parser.parse_skills()?;
let skill = node.text(); Result::Ok(Ninja {
let score = node.parent().ok_or_else(|| Error::Parse(String::from("could not get ninja-skill parent element")))? name: parser.parse_name()?,
.find(Class("ninja-score")).first() sneakiness: skills.get("Sneakiness:")?.as_number()?,
.ok_or_else(|| Error::Parse(String::from("could not get ninja-score element")))? pajamas: skills.get("Pajamas:")?.as_number()?,
.text(); pointy_things: skills.get("Pointy Things:")?.as_number()?,
weapons: skills.get("Weapons:")?.as_weapons()
match &skill[..] { })
"Sneakiness:" => { ninja.sneakiness = score.parse::<u8>()? }
"Pajamas:" => { ninja.pajamas = score.parse::<u8>()? },
"Pointy Things:" => { ninja.pointy_things = score.parse::<u8>()? },
"Weapons:" => { ninja.weapons = parse_weapons(&score[..]) }
_ => { }
}
}
Result::Ok(ninja)
} }
} }

View File

@ -1,11 +1,10 @@
use lru_cache::LruCache; use lru_cache::LruCache;
use hyper::client::Client; use hyper::client::Client;
use select::document::Document; use select::document::Document;
use select::predicate::Class;
use std::io::Read; use std::io::Read;
use {Error, Fighter, parse_weapons}; use {Error, Fighter, Parser};
pub struct Pirates { pub struct Pirates {
cache: LruCache<String, Pirate>, cache: LruCache<String, Pirate>,
@ -52,35 +51,19 @@ pub struct Pirate {
impl Pirate { impl Pirate {
pub fn from_html (contents: &str) -> Result<Pirate, Error> { pub fn from_html (contents: &str) -> Result<Pirate, Error> {
let document = Document::from(contents); let parser = Parser {
let full_name = document.find(Class("pirate-name")).first() fighter_type: "pirate",
.ok_or_else(|| Error::Parse(String::from("could not find pirate-name element")))?.text(); document: Document::from(contents)
let mut pirate = Pirate {
name: String::from(full_name),
swashbuckling: 0,
drunkenness: 0,
booty: 0,
weapons: vec![]
}; };
for node in document.find(Class("pirate-skill")).iter() { let skills = parser.parse_skills()?;
let skill = node.text(); Result::Ok(Pirate {
let score = node.parent().ok_or_else(|| Error::Parse(String::from("could not get pirate-skill parent element")))? name: parser.parse_name()?,
.find(Class("pirate-score")).first() swashbuckling: skills.get("Swashbuckling:")?.as_number()?,
.ok_or_else(|| Error::Parse(String::from("could not get pirate-score element")))? drunkenness: skills.get("Drunkenness:")?.as_number()?,
.text(); booty: skills.get("Booty:")?.as_number()?,
weapons: skills.get("Weapons:")?.as_weapons()
match &skill[..] { })
"Swashbuckling:" => { pirate.swashbuckling = score.parse::<u8>()? }
"Drunkenness:" => { pirate.drunkenness = score.parse::<u8>()? },
"Booty:" => { pirate.booty = score.parse::<u8>()? },
"Weapons:" => { pirate.weapons = parse_weapons(&score[..]) }
_ => { }
}
}
Result::Ok(pirate)
} }
} }