Localize all command parsing code into trait impls.
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details

This cleans up the command parser a lot, as all of the one or two line
functions and associated imports have been removed. Unfortunately it
does make the command files larger, as two trait impls are required:
one for converting to Box<dyn Command>, and one for converting from
&str to the command type.

Fixes #66.
This commit is contained in:
projectmoon 2021-05-25 23:54:06 +00:00
parent 5d002e5063
commit b05129ad9f
7 changed files with 228 additions and 88 deletions

View File

@ -1,11 +1,37 @@
use super::{Command, Execution, ExecutionResult}; use super::{Command, Execution, ExecutionResult};
use crate::basic::dice::ElementExpression; use crate::basic::dice::ElementExpression;
use crate::basic::parser::parse_element_expression;
use crate::basic::roll::Roll; use crate::basic::roll::Roll;
use crate::context::Context; use crate::context::Context;
use crate::error::BotError;
use async_trait::async_trait; use async_trait::async_trait;
use nom::Err as NomErr;
use std::convert::TryFrom;
pub struct RollCommand(pub ElementExpression); pub struct RollCommand(pub ElementExpression);
impl From<RollCommand> for Box<dyn Command> {
fn from(cmd: RollCommand) -> Self {
Box::new(cmd)
}
}
impl TryFrom<&str> for RollCommand {
type Error = BotError;
fn try_from(input: &str) -> Result<Self, Self::Error> {
let result = parse_element_expression(input);
match result {
Ok((rest, expression)) if rest.len() == 0 => Ok(RollCommand(expression)),
//"Legacy code boundary": translates Nom errors into BotErrors.
Ok(_) => Err(BotError::NomParserIncomplete),
Err(NomErr::Error(e)) => Err(BotError::NomParserError(e.1)),
Err(NomErr::Failure(e)) => Err(BotError::NomParserError(e.1)),
Err(NomErr::Incomplete(_)) => Err(BotError::NomParserIncomplete),
}
}
}
#[async_trait] #[async_trait]
impl Command for RollCommand { impl Command for RollCommand {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {

View File

@ -1,10 +1,35 @@
use super::{Command, Execution, ExecutionResult}; use super::{Command, Execution, ExecutionResult};
use crate::cofd::dice::{roll_pool, DicePool, DicePoolWithContext}; use crate::cofd::dice::{roll_pool, DicePool, DicePoolWithContext};
use crate::cofd::parser::{create_chance_die, parse_dice_pool};
use crate::context::Context; use crate::context::Context;
use crate::error::BotError;
use async_trait::async_trait; use async_trait::async_trait;
use std::convert::TryFrom;
pub struct PoolRollCommand(pub DicePool); pub struct PoolRollCommand(pub DicePool);
impl PoolRollCommand {
pub fn chance_die() -> Result<PoolRollCommand, BotError> {
let pool = create_chance_die()?;
Ok(PoolRollCommand(pool))
}
}
impl From<PoolRollCommand> for Box<dyn Command> {
fn from(cmd: PoolRollCommand) -> Self {
Box::new(cmd)
}
}
impl TryFrom<&str> for PoolRollCommand {
type Error = BotError;
fn try_from(input: &str) -> Result<Self, Self::Error> {
let pool = parse_dice_pool(input)?;
Ok(PoolRollCommand(pool))
}
}
#[async_trait] #[async_trait]
impl Command for PoolRollCommand { impl Command for PoolRollCommand {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {

View File

@ -4,10 +4,28 @@ use crate::cthulhu::dice::{
advancement_roll, regular_roll, AdvancementRoll, AdvancementRollWithContext, DiceRoll, advancement_roll, regular_roll, AdvancementRoll, AdvancementRollWithContext, DiceRoll,
DiceRollWithContext, DiceRollWithContext,
}; };
use crate::cthulhu::parser::{parse_advancement_roll, parse_regular_roll};
use crate::error::BotError;
use async_trait::async_trait; use async_trait::async_trait;
use std::convert::TryFrom;
pub struct CthRoll(pub DiceRoll); pub struct CthRoll(pub DiceRoll);
impl From<CthRoll> for Box<dyn Command> {
fn from(cmd: CthRoll) -> Self {
Box::new(cmd)
}
}
impl TryFrom<&str> for CthRoll {
type Error = BotError;
fn try_from(input: &str) -> Result<Self, Self::Error> {
let roll = parse_regular_roll(input)?;
Ok(CthRoll(roll))
}
}
#[async_trait] #[async_trait]
impl Command for CthRoll { impl Command for CthRoll {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
@ -33,6 +51,21 @@ impl Command for CthRoll {
pub struct CthAdvanceRoll(pub AdvancementRoll); pub struct CthAdvanceRoll(pub AdvancementRoll);
impl From<CthAdvanceRoll> for Box<dyn Command> {
fn from(cmd: CthAdvanceRoll) -> Self {
Box::new(cmd)
}
}
impl TryFrom<&str> for CthAdvanceRoll {
type Error = BotError;
fn try_from(input: &str) -> Result<Self, Self::Error> {
let roll = parse_advancement_roll(input)?;
Ok(CthAdvanceRoll(roll))
}
}
#[async_trait] #[async_trait]
impl Command for CthAdvanceRoll { impl Command for CthAdvanceRoll {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {

View File

@ -1,13 +1,28 @@
use super::{Command, Execution, ExecutionResult}; use super::{Command, Execution, ExecutionResult};
use crate::context::Context;
use crate::db::Users; use crate::db::Users;
use crate::error::BotError::{AccountDoesNotExist, AuthenticationError, PasswordCreationError}; use crate::error::BotError::{AccountDoesNotExist, AuthenticationError, PasswordCreationError};
use crate::logic::hash_password; use crate::logic::hash_password;
use crate::models::{AccountStatus, User}; use crate::models::{AccountStatus, User};
use crate::{context::Context, error::BotError};
use async_trait::async_trait; use async_trait::async_trait;
use std::convert::{Into, TryFrom};
pub struct RegisterCommand(pub String); pub struct RegisterCommand(pub String);
impl From<RegisterCommand> for Box<dyn Command> {
fn from(cmd: RegisterCommand) -> Self {
Box::new(cmd)
}
}
impl TryFrom<&str> for RegisterCommand {
type Error = BotError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Ok(RegisterCommand(value.to_owned()))
}
}
#[async_trait] #[async_trait]
impl Command for RegisterCommand { impl Command for RegisterCommand {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
@ -38,6 +53,20 @@ impl Command for RegisterCommand {
pub struct CheckCommand(pub String); pub struct CheckCommand(pub String);
impl From<CheckCommand> for Box<dyn Command> {
fn from(cmd: CheckCommand) -> Self {
Box::new(cmd)
}
}
impl TryFrom<&str> for CheckCommand {
type Error = BotError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
Ok(CheckCommand(value.to_owned()))
}
}
#[async_trait] #[async_trait]
impl Command for CheckCommand { impl Command for CheckCommand {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
@ -60,6 +89,20 @@ impl Command for CheckCommand {
pub struct UnregisterCommand; pub struct UnregisterCommand;
impl From<UnregisterCommand> for Box<dyn Command> {
fn from(cmd: UnregisterCommand) -> Self {
Box::new(cmd)
}
}
impl TryFrom<&str> for UnregisterCommand {
type Error = BotError;
fn try_from(_: &str) -> Result<Self, Self::Error> {
Ok(UnregisterCommand)
}
}
#[async_trait] #[async_trait]
impl Command for UnregisterCommand { impl Command for UnregisterCommand {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {

View File

@ -1,10 +1,27 @@
use super::{Command, Execution, ExecutionResult}; use super::{Command, Execution, ExecutionResult};
use crate::context::Context; use crate::context::Context;
use crate::help::HelpTopic; use crate::error::BotError;
use crate::help::{parse_help_topic, HelpTopic};
use async_trait::async_trait; use async_trait::async_trait;
use std::convert::TryFrom;
pub struct HelpCommand(pub Option<HelpTopic>); pub struct HelpCommand(pub Option<HelpTopic>);
impl From<HelpCommand> for Box<dyn Command> {
fn from(cmd: HelpCommand) -> Self {
Box::new(cmd)
}
}
impl TryFrom<&str> for HelpCommand {
type Error = BotError;
fn try_from(input: &str) -> Result<Self, Self::Error> {
let topic = parse_help_topic(input);
Ok(HelpCommand(topic))
}
}
#[async_trait] #[async_trait]
impl Command for HelpCommand { impl Command for HelpCommand {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {

View File

@ -3,8 +3,6 @@
* governed by the terms of the MIT license, from the original * governed by the terms of the MIT license, from the original
* axfive-matrix-dicebot project. * axfive-matrix-dicebot project.
*/ */
use crate::basic::parser::parse_element_expression;
use crate::cofd::parser::{create_chance_die, parse_dice_pool};
use crate::commands::{ use crate::commands::{
basic_rolling::RollCommand, basic_rolling::RollCommand,
cofd::PoolRollCommand, cofd::PoolRollCommand,
@ -16,13 +14,10 @@ use crate::commands::{
}, },
Command, Command,
}; };
use crate::cthulhu::parser::{parse_advancement_roll, parse_regular_roll};
use crate::error::BotError; use crate::error::BotError;
use crate::help::parse_help_topic;
use crate::parser::variables::parse_set_variable;
use combine::parser::char::{char, letter, space}; use combine::parser::char::{char, letter, space};
use combine::{any, many1, optional, Parser}; use combine::{any, many1, optional, Parser};
use nom::Err as NomErr; use std::convert::TryFrom;
use thiserror::Error; use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Error)] #[derive(Debug, Clone, PartialEq, Error)]
@ -34,73 +29,6 @@ pub enum CommandParsingError {
InternalParseError(#[from] combine::error::StringStreamError), InternalParseError(#[from] combine::error::StringStreamError),
} }
// Parse a roll expression.
fn parse_roll(input: &str) -> Result<Box<dyn Command>, BotError> {
let result = parse_element_expression(input);
match result {
Ok((rest, expression)) if rest.len() == 0 => Ok(Box::new(RollCommand(expression))),
//Legacy code boundary translates nom errors into BotErrors.
Ok(_) => Err(BotError::NomParserIncomplete),
Err(NomErr::Error(e)) => Err(BotError::NomParserError(e.1)),
Err(NomErr::Failure(e)) => Err(BotError::NomParserError(e.1)),
Err(NomErr::Incomplete(_)) => Err(BotError::NomParserIncomplete),
}
}
fn parse_register_command(input: &str) -> Result<Box<dyn Command>, BotError> {
Ok(Box::new(RegisterCommand(input.to_owned())))
}
fn parse_check_command(input: &str) -> Result<Box<dyn Command>, BotError> {
Ok(Box::new(CheckCommand(input.to_owned())))
}
fn parse_unregister_command() -> Result<Box<dyn Command>, BotError> {
Ok(Box::new(UnregisterCommand))
}
fn parse_get_variable_command(input: &str) -> Result<Box<dyn Command>, BotError> {
Ok(Box::new(GetVariableCommand(input.to_owned())))
}
fn parse_set_variable_command(input: &str) -> Result<Box<dyn Command>, BotError> {
let (variable_name, value) = parse_set_variable(input)?;
Ok(Box::new(SetVariableCommand(variable_name, value)))
}
fn parse_delete_variable_command(input: &str) -> Result<Box<dyn Command>, BotError> {
Ok(Box::new(DeleteVariableCommand(input.to_owned())))
}
fn parse_pool_roll(input: &str) -> Result<Box<dyn Command>, BotError> {
let pool = parse_dice_pool(input)?;
Ok(Box::new(PoolRollCommand(pool)))
}
fn parse_cth_roll(input: &str) -> Result<Box<dyn Command>, BotError> {
let roll = parse_regular_roll(input)?;
Ok(Box::new(CthRoll(roll)))
}
fn parse_cth_advancement_roll(input: &str) -> Result<Box<dyn Command>, BotError> {
let roll = parse_advancement_roll(input)?;
Ok(Box::new(CthAdvanceRoll(roll)))
}
fn chance_die() -> Result<Box<dyn Command>, BotError> {
let pool = create_chance_die()?;
Ok(Box::new(PoolRollCommand(pool)))
}
fn get_all_variables() -> Result<Box<dyn Command>, BotError> {
Ok(Box::new(GetAllVariablesCommand))
}
fn help(topic: &str) -> Result<Box<dyn Command>, BotError> {
let topic = parse_help_topic(topic);
Ok(Box::new(HelpCommand(topic)))
}
/// Split an input string into its constituent command and "everything /// Split an input string into its constituent command and "everything
/// else" parts. Extracts the command separately from its input (i.e. /// else" parts. Extracts the command separately from its input (i.e.
/// rest of the line) and returns a tuple of (command_input, command). /// rest of the line) and returns a tuple of (command_input, command).
@ -132,25 +60,34 @@ fn split_command(input: &str) -> Result<(String, String), CommandParsingError> {
Ok((command, command_input)) Ok((command, command_input))
} }
/// Atempt to convert text input to a Boxed command type. Shortens
/// boilerplate.
macro_rules! convert_to {
($type:ident, $input: expr) => {
$type::try_from($input.as_str()).map(Into::into)
};
}
/// Potentially parse a command expression. If we recognize the /// Potentially parse a command expression. If we recognize the
/// command, an error should be raised if the command is misparsed. If /// command, an error should be raised if the command is misparsed. If
/// we don't recognize the command, return an error. /// we don't recognize the command, return an error.
pub fn parse_command(input: &str) -> Result<Box<dyn Command>, BotError> { pub fn parse_command(input: &str) -> Result<Box<dyn Command>, BotError> {
match split_command(input) { match split_command(input) {
Ok((cmd, cmd_input)) => match cmd.to_lowercase().as_ref() { Ok((cmd, cmd_input)) => match cmd.to_lowercase().as_ref() {
"variables" => get_all_variables(), // "variables" => GetAllVariablesCommand::try_from(input).map(Into::into),
"get" => parse_get_variable_command(&cmd_input), "variables" => convert_to!(GetAllVariablesCommand, cmd_input),
"set" => parse_set_variable_command(&cmd_input), "get" => convert_to!(GetVariableCommand, cmd_input),
"del" => parse_delete_variable_command(&cmd_input), "set" => convert_to!(SetVariableCommand, cmd_input),
"r" | "roll" => parse_roll(&cmd_input), "del" => convert_to!(DeleteVariableCommand, cmd_input),
"rp" | "pool" => parse_pool_roll(&cmd_input), "r" | "roll" => convert_to!(RollCommand, cmd_input),
"cthroll" => parse_cth_roll(&cmd_input), "rp" | "pool" => convert_to!(PoolRollCommand, cmd_input),
"cthadv" | "ctharoll" => parse_cth_advancement_roll(&cmd_input), "chance" => PoolRollCommand::chance_die().map(Into::into),
"chance" => chance_die(), "cthroll" => convert_to!(CthRoll, cmd_input),
"help" => help(&cmd_input), "cthadv" | "ctharoll" => convert_to!(CthAdvanceRoll, cmd_input),
"register" => parse_register_command(&cmd_input), "help" => convert_to!(HelpCommand, cmd_input),
"check" => parse_check_command(&cmd_input), "register" => convert_to!(RegisterCommand, cmd_input),
"unregister" => parse_unregister_command(), "check" => convert_to!(CheckCommand, cmd_input),
"unregister" => convert_to!(UnregisterCommand, cmd_input),
_ => Err(CommandParsingError::UnrecognizedCommand(cmd).into()), _ => Err(CommandParsingError::UnrecognizedCommand(cmd).into()),
}, },
//All other errors passed up. //All other errors passed up.

View File

@ -2,10 +2,26 @@ use super::{Command, Execution, ExecutionResult};
use crate::context::Context; use crate::context::Context;
use crate::db::errors::DataError; use crate::db::errors::DataError;
use crate::db::Variables; use crate::db::Variables;
use crate::error::BotError;
use async_trait::async_trait; use async_trait::async_trait;
use std::convert::TryFrom;
pub struct GetAllVariablesCommand; pub struct GetAllVariablesCommand;
impl From<GetAllVariablesCommand> for Box<dyn Command> {
fn from(cmd: GetAllVariablesCommand) -> Self {
Box::new(cmd)
}
}
impl TryFrom<&str> for GetAllVariablesCommand {
type Error = BotError;
fn try_from(_: &str) -> Result<Self, Self::Error> {
Ok(GetAllVariablesCommand)
}
}
#[async_trait] #[async_trait]
impl Command for GetAllVariablesCommand { impl Command for GetAllVariablesCommand {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
@ -41,6 +57,20 @@ impl Command for GetAllVariablesCommand {
pub struct GetVariableCommand(pub String); pub struct GetVariableCommand(pub String);
impl From<GetVariableCommand> for Box<dyn Command> {
fn from(cmd: GetVariableCommand) -> Self {
Box::new(cmd)
}
}
impl TryFrom<&str> for GetVariableCommand {
type Error = BotError;
fn try_from(input: &str) -> Result<Self, Self::Error> {
Ok(GetVariableCommand(input.to_owned()))
}
}
#[async_trait] #[async_trait]
impl Command for GetVariableCommand { impl Command for GetVariableCommand {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
@ -71,6 +101,21 @@ impl Command for GetVariableCommand {
pub struct SetVariableCommand(pub String, pub i32); pub struct SetVariableCommand(pub String, pub i32);
impl From<SetVariableCommand> for Box<dyn Command> {
fn from(cmd: SetVariableCommand) -> Self {
Box::new(cmd)
}
}
impl TryFrom<&str> for SetVariableCommand {
type Error = BotError;
fn try_from(input: &str) -> Result<Self, Self::Error> {
let (variable_name, value) = crate::parser::variables::parse_set_variable(input)?;
Ok(SetVariableCommand(variable_name, value))
}
}
#[async_trait] #[async_trait]
impl Command for SetVariableCommand { impl Command for SetVariableCommand {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
@ -97,6 +142,20 @@ impl Command for SetVariableCommand {
pub struct DeleteVariableCommand(pub String); pub struct DeleteVariableCommand(pub String);
impl From<DeleteVariableCommand> for Box<dyn Command> {
fn from(cmd: DeleteVariableCommand) -> Self {
Box::new(cmd)
}
}
impl TryFrom<&str> for DeleteVariableCommand {
type Error = BotError;
fn try_from(input: &str) -> Result<Self, Self::Error> {
Ok(DeleteVariableCommand(input.to_owned()))
}
}
#[async_trait] #[async_trait]
impl Command for DeleteVariableCommand { impl Command for DeleteVariableCommand {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {