diff --git a/src/bot.rs b/src/bot.rs index d13440a..be0c9c5 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -1,6 +1,6 @@ use crate::commands::parse_command; use dirs; -use log::{error, info, warn}; +use log::{error, info, trace, warn}; use matrix_sdk::{ self, events::{ @@ -92,12 +92,19 @@ impl EventEmitter for DiceBot { (String::new(), String::new()) }; + //Command parser can handle non-commands, but faster to + //not parse them. + if !msg_body.starts_with("!") { + trace!("Ignoring non-command: {}", msg_body); + return; + } + let (plain, html) = match parse_command(&msg_body) { Ok(Some(command)) => { let command = command.execute(); (command.plain().into(), command.html().into()) } - Ok(None) => return, + Ok(None) => return, //Ignore non-commands. Err(e) => { let message = format!("Error parsing command: {}", e); let html_message = format!("

{}

", message); diff --git a/src/commands.rs b/src/commands.rs index 337423c..85e2519 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -82,14 +82,15 @@ impl Command for HelpCommand { /// Parse a command string into a dynamic command execution trait /// object. Returns an error if a command was recognized but not -/// parsed correctly. Returns None if no command was recognized. +/// parsed correctly. Returns Ok(None) if no command was recognized. pub fn parse_command(s: &str) -> Result>, String> { match parser::parse_command(s) { Ok((input, command)) => match (input, &command) { - //This clause prevents bot from spamming messages to itself - //after executing a previous command. + //Any command, or text transformed into non-command is + //sent upwards. ("", Some(_)) | (_, None) => Ok(command), + //TODO replcae with nom all_consuming? //Any unconsumed input (whitespace should already be // stripped) is considered a parsing error. _ => Err(format!("{}: malformed expression", s)), diff --git a/src/commands/parser.rs b/src/commands/parser.rs index 2a9d381..d04e09d 100644 --- a/src/commands/parser.rs +++ b/src/commands/parser.rs @@ -2,7 +2,10 @@ use crate::cofd::parser::{create_chance_die, parse_dice_pool}; use crate::commands::{Command, HelpCommand, PoolRollCommand, RollCommand}; use crate::dice::parser::parse_element_expression; use crate::help::parse_help_topic; -use nom::{bytes::complete::tag, character::complete::alpha1, IResult}; +use nom::bytes::streaming::tag; +use nom::error::ErrorKind as NomErrorKind; +use nom::Err as NomErr; +use nom::{character::complete::alpha1, IResult}; // Parse a roll expression. fn parse_roll(input: &str) -> IResult<&str, Box> { @@ -30,9 +33,9 @@ fn help(topic: &str) -> IResult<&str, Box> { /// rest of the line) and returns a tuple of (command_input, command). /// Whitespace at the start and end of the command input is removed. fn split_command(input: &str) -> IResult<&str, &str> { - //let (input, _) = eat_whitespace(input)?; let input = input.trim_start(); let (input, _) = tag("!")(input)?; + let (mut command_input, command) = alpha1(input)?; command_input = command_input.trim(); Ok((command_input, command)) @@ -40,17 +43,28 @@ fn split_command(input: &str) -> IResult<&str, &str> { /// Potentially parse a command expression. If we recognize the /// command, an error should be raised if the command is misparsed. If -/// we don't recognize the command, ignore it and return none +/// we don't recognize the command, ignore it and return None. pub fn parse_command(input: &str) -> IResult<&str, Option>> { - let (cmd_input, cmd) = split_command(input)?; + match split_command(input) { + Ok((cmd_input, cmd)) => match cmd { + "r" | "roll" => parse_roll(cmd_input).map(|(input, command)| (input, Some(command))), + "rp" | "pool" => { + parse_pool_roll(cmd_input).map(|(input, command)| (input, Some(command))) + } + "chance" => chance_die().map(|(input, command)| (input, Some(command))), + "help" => help(cmd_input).map(|(input, command)| (input, Some(command))), + // No recognized command, ignore this. + _ => Ok((input, None)), + }, - match cmd { - "r" | "roll" => parse_roll(cmd_input).map(|(input, command)| (input, Some(command))), - "rp" | "pool" => parse_pool_roll(cmd_input).map(|(input, command)| (input, Some(command))), - "chance" => chance_die().map(|(input, command)| (input, Some(command))), - "help" => help(cmd_input).map(|(input, command)| (input, Some(command))), - // No recognized command, ignore this. - _ => Ok((input, None)), + //TODO better way to do this? + //If the input is not a command, or the message is incomplete + //(empty), we declare this to be a non-command, and don't do + //anything else with it. + Err(NomErr::Error((_, NomErrorKind::Tag))) | Err(NomErr::Incomplete(_)) => Ok(("", None)), + + //All other errors passed up. + Err(e) => Err(e), } } @@ -58,6 +72,26 @@ pub fn parse_command(input: &str) -> IResult<&str, Option>> { mod tests { use super::*; + #[test] + fn non_command_test() { + let result = parse_command("not a command"); + assert!(result.is_ok()); + assert!(result.unwrap().1.is_none()); + } + + #[test] + fn empty_message_test() { + let result = parse_command(""); + assert!(result.is_ok()); + assert!(result.unwrap().1.is_none()); + } + + #[test] + fn just_exclamation_point_test() { + let result = parse_command("!"); + assert!(result.is_err()); + } + #[test] fn basic_command_test() { assert_eq!( @@ -76,7 +110,6 @@ mod tests { #[test] fn whitespace_at_end_test() { - //TODO currently it does not chop whitespace off the end. assert_eq!( ("1d4", "roll"), split_command("!roll 1d4 ").expect("got parsing error")