use std::default::Default; use std::error::Error; use std::str::FromStr; use lazy_static::lazy_static; use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ToSql, ToSqlOutput, ValueRef}; use serde_derive::{Deserialize, Serialize}; use crate::command::ParserError; use crate::result::RudeResult; lazy_static! { /// List of text directions and the associated Direction pub static ref DIRECTION_LIST: Vec<(&'static str, Direction)> = vec![ ("north", Direction::North), ("south", Direction::South), ("east", Direction::East), ("west", Direction::West), ("up", Direction::Up), ("down", Direction::Down), ]; } /// Directions for an exit from a room. #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] pub enum Direction { /// Cardinal north North = 0, /// Cardinal east East, /// Cardinal south South, /// Cardinal west West, /// Up (towards the sky) Up, /// Down (towards the ground) Down, /// Default (internal usage only) Default, } impl Default for Direction { fn default() -> Self { Self::Default } } impl FromSql for Direction { fn column_result(value: ValueRef<'_>) -> FromSqlResult { let s = match value.as_str() { Ok(s) => s, Err(e) => { log::error!("{}({}) :: {} :: {}", file!(), line!(), "value.as_str()", e); return Err(e); } }; match Self::from_str(s) { Ok(direction) => return Ok(direction), Err(e) => Err(FromSqlError::Other(e)), } } } impl ToSql for Direction { fn to_sql(&self) -> rusqlite::Result> { Ok(ToSqlOutput::from(self.long())) } } impl FromStr for Direction { type Err = Box; fn from_str(s: &str) -> std::result::Result { let mut s: String = s.into(); // get rid of extra characters if let Some(pos) = s.find(' ') { s.split_off(pos); } // no command if s.is_empty() { return Err(Box::from(ParserError::Empty)); } // match directions with input let mut matches: Vec<(&str, Self)> = DIRECTION_LIST .iter() .filter_map(|(text, dir)| { if text.starts_with(&s) { Some((text.clone(), dir.clone())) } else { None } }) .collect(); // No matches here means unknown direction if matches.is_empty() { return Err(Box::from(ParserError::Unknown)); } // exact match, do no more if matches.len() == 1 { return Ok(matches[0].1); } // look for directions that match for (text, direction) in DIRECTION_LIST.iter() { let direction: Self = direction.clone(); if *text == s { // exact match matches = vec![(text, direction)]; break; } else if text.starts_with(s.as_str()) { // starts the same, add to the list matches.push((text, direction)); } } // check if there was a match if matches.is_empty() { return Err(Box::from(ParserError::Unknown)); } // sort and take the first match // this allows possibly more directions, the original set shouldn't ever have multiple matches.sort(); Ok(matches[0].1) } } impl Direction { /// Provide the opposite direction pub fn opposite(self) -> Direction { match self { Self::North => Self::South, Self::East => Self::West, Self::South => Self::North, Self::West => Self::East, Self::Up => Self::Down, Self::Down => Self::Up, Self::Default => Self::Default, } } /// Single character identifier for the direction. pub fn short(&self) -> &str { match self { Self::North => "N", Self::East => "E", Self::South => "S", Self::West => "W", Self::Up => "U", Self::Down => "D", Self::Default => "", } } /// String identifier for the direction. pub fn long(&self) -> &str { match self { Self::North => "north", Self::East => "east", Self::South => "south", Self::West => "west", Self::Up => "up", Self::Down => "down", Self::Default => "", } } pub fn try_from_long>(s: S) -> RudeResult { let s = s.as_ref(); match s { "north" => Ok(Self::North), "east" => Ok(Self::East), "south" => Ok(Self::South), "west" => Ok(Self::West), "up" => Ok(Self::Up), "down" => Ok(Self::Down), _ => Err(format!("Invalid direction: {}", s).into()), } } }