From 4ff066b8b7412cbfd937acf62af7d611a519097e Mon Sep 17 00:00:00 2001 From: Adrian Malacoda Date: Sun, 19 Feb 2017 01:41:05 -0600 Subject: [PATCH] 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). --- src/lib.rs | 64 ++++++++++++++++++++++++++++++++++++++++++++++ src/ninjas/mod.rs | 41 +++++++++-------------------- src/pirates/mod.rs | 41 +++++++++-------------------- 3 files changed, 88 insertions(+), 58 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 06a4da9..87ed35d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,11 @@ extern crate select; pub mod pirates; pub mod ninjas; +use select::document::Document; +use select::predicate::Class; + +use std::collections::BTreeMap; + #[derive(Debug)] pub enum Error { Http(hyper::error::Error), @@ -41,3 +46,62 @@ pub fn parse_weapons (weapons: &str) -> Vec { 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 { + 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 { + let skill_class = format!("{}-skill", self.fighter_type); + let score_class = format!("{}-score", self.fighter_type); + let map: Result, 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 +} + +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 { + self.score.parse::() + } + + pub fn as_weapons (&self) -> Vec { + if self.score == "None" { + return vec![]; + } + + self.score.split(", ").into_iter().map(String::from).collect() + } +} diff --git a/src/ninjas/mod.rs b/src/ninjas/mod.rs index 7e7d9bd..4d42e97 100644 --- a/src/ninjas/mod.rs +++ b/src/ninjas/mod.rs @@ -1,11 +1,10 @@ use lru_cache::LruCache; use hyper::client::Client; use select::document::Document; -use select::predicate::Class; use std::io::Read; -use {Error, Fighter, parse_weapons}; +use {Error, Fighter, Parser}; pub struct Ninjas { cache: LruCache, @@ -52,35 +51,19 @@ pub struct Ninja { impl Ninja { pub fn from_html (contents: &str) -> Result { - let document = Document::from(contents); - let full_name = document.find(Class("ninja-name")).first() - .ok_or_else(|| Error::Parse(String::from("could not find ninja-name element")))?.text(); - - let mut ninja = Ninja { - name: String::from(full_name), - sneakiness: 0, - pajamas: 0, - pointy_things: 0, - weapons: vec![] + let parser = Parser { + fighter_type: "ninja", + document: Document::from(contents) }; - for node in document.find(Class("ninja-skill")).iter() { - let skill = node.text(); - let score = node.parent().ok_or_else(|| Error::Parse(String::from("could not get ninja-skill parent element")))? - .find(Class("ninja-score")).first() - .ok_or_else(|| Error::Parse(String::from("could not get ninja-score element")))? - .text(); - - match &skill[..] { - "Sneakiness:" => { ninja.sneakiness = score.parse::()? } - "Pajamas:" => { ninja.pajamas = score.parse::()? }, - "Pointy Things:" => { ninja.pointy_things = score.parse::()? }, - "Weapons:" => { ninja.weapons = parse_weapons(&score[..]) } - _ => { } - } - } - - Result::Ok(ninja) + let skills = parser.parse_skills()?; + Result::Ok(Ninja { + name: parser.parse_name()?, + sneakiness: skills.get("Sneakiness:")?.as_number()?, + pajamas: skills.get("Pajamas:")?.as_number()?, + pointy_things: skills.get("Pointy Things:")?.as_number()?, + weapons: skills.get("Weapons:")?.as_weapons() + }) } } diff --git a/src/pirates/mod.rs b/src/pirates/mod.rs index 02df0a8..cbf154c 100644 --- a/src/pirates/mod.rs +++ b/src/pirates/mod.rs @@ -1,11 +1,10 @@ use lru_cache::LruCache; use hyper::client::Client; use select::document::Document; -use select::predicate::Class; use std::io::Read; -use {Error, Fighter, parse_weapons}; +use {Error, Fighter, Parser}; pub struct Pirates { cache: LruCache, @@ -52,35 +51,19 @@ pub struct Pirate { impl Pirate { pub fn from_html (contents: &str) -> Result { - let document = Document::from(contents); - let full_name = document.find(Class("pirate-name")).first() - .ok_or_else(|| Error::Parse(String::from("could not find pirate-name element")))?.text(); - - let mut pirate = Pirate { - name: String::from(full_name), - swashbuckling: 0, - drunkenness: 0, - booty: 0, - weapons: vec![] + let parser = Parser { + fighter_type: "pirate", + document: Document::from(contents) }; - for node in document.find(Class("pirate-skill")).iter() { - let skill = node.text(); - let score = node.parent().ok_or_else(|| Error::Parse(String::from("could not get pirate-skill parent element")))? - .find(Class("pirate-score")).first() - .ok_or_else(|| Error::Parse(String::from("could not get pirate-score element")))? - .text(); - - match &skill[..] { - "Swashbuckling:" => { pirate.swashbuckling = score.parse::()? } - "Drunkenness:" => { pirate.drunkenness = score.parse::()? }, - "Booty:" => { pirate.booty = score.parse::()? }, - "Weapons:" => { pirate.weapons = parse_weapons(&score[..]) } - _ => { } - } - } - - Result::Ok(pirate) + let skills = parser.parse_skills()?; + Result::Ok(Pirate { + name: parser.parse_name()?, + swashbuckling: skills.get("Swashbuckling:")?.as_number()?, + drunkenness: skills.get("Drunkenness:")?.as_number()?, + booty: skills.get("Booty:")?.as_number()?, + weapons: skills.get("Weapons:")?.as_weapons() + }) } }