You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

194 lines
4.1 KiB

5 years ago
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<Self> {
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<ToSqlOutput<'_>> {
Ok(ToSqlOutput::from(self.long()))
}
}
impl FromStr for Direction {
type Err = Box<dyn Error + Send + Sync + 'static>;
fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
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: AsRef<str>>(s: S) -> RudeResult<Self> {
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()),
}
}
}