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
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()),
|
||
|
}
|
||
|
}
|
||
|
}
|