Make command execution async.

This commit is contained in:
projectmoon 2020-10-17 15:47:17 +00:00 committed by ProjectMoon
parent 97d91704a1
commit 3c2a37c0f7
4 changed files with 36 additions and 30 deletions

View File

@ -1,17 +1,18 @@
use chronicle_dicebot::commands::Command; use chronicle_dicebot::commands;
use chronicle_dicebot::context::Context; use chronicle_dicebot::context::Context;
use chronicle_dicebot::db::Database; use chronicle_dicebot::db::Database;
use chronicle_dicebot::error::BotError; use chronicle_dicebot::error::BotError;
fn main() -> Result<(), BotError> { #[tokio::main]
async fn main() -> Result<(), BotError> {
let db = Database::new(&sled::open("test-db")?); let db = Database::new(&sled::open("test-db")?);
let input = std::env::args().skip(1).collect::<Vec<String>>().join(" "); let input = std::env::args().skip(1).collect::<Vec<String>>().join(" ");
let command = match Command::parse(&input) { let command = match commands::parse(&input) {
Ok(command) => command, Ok(command) => command,
Err(e) => return Err(e), Err(e) => return Err(e),
}; };
let context = Context::new(&db, "roomid", "localuser", &input); let context = Context::new(&db, "roomid", "localuser", &input);
println!("{}", command.execute(&context).plain()); println!("{}", command.execute(&context).await.plain());
Ok(()) Ok(())
} }

View File

@ -8,7 +8,6 @@ use chronicle_dicebot::state::DiceBotState;
use env_logger::Env; use env_logger::Env;
use log::error; use log::error;
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use tokio::prelude::*;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {

View File

@ -227,7 +227,7 @@ impl EventEmitter for DiceBot {
let ctx = Context::new(&self.db, &room_id.as_str(), &sender_username, &msg_body); let ctx = Context::new(&self.db, &room_id.as_str(), &sender_username, &msg_body);
if let Some(cmd_result) = execute_command(&ctx) { if let Some(cmd_result) = execute_command(&ctx).await {
let response = AnyMessageEventContent::RoomMessage(MessageEventContent::Notice( let response = AnyMessageEventContent::RoomMessage(MessageEventContent::Notice(
NoticeMessageEventContent::html(cmd_result.plain, cmd_result.html), NoticeMessageEventContent::html(cmd_result.plain, cmd_result.html),
)); ));

View File

@ -5,6 +5,7 @@ use crate::dice::ElementExpression;
use crate::error::BotError; use crate::error::BotError;
use crate::help::HelpTopic; use crate::help::HelpTopic;
use crate::roll::Roll; use crate::roll::Roll;
use async_trait::async_trait;
use thiserror::Error; use thiserror::Error;
pub mod parser; pub mod parser;
@ -33,19 +34,21 @@ impl Execution {
} }
} }
pub trait Command { #[async_trait]
fn execute(&self, ctx: &Context) -> Execution; pub trait Command: Send + Sync {
async fn execute(&self, ctx: &Context) -> Execution;
fn name(&self) -> &'static str; fn name(&self) -> &'static str;
} }
pub struct RollCommand(ElementExpression); pub struct RollCommand(ElementExpression);
#[async_trait]
impl Command for RollCommand { impl Command for RollCommand {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
"roll regular dice" "roll regular dice"
} }
fn execute(&self, _ctx: &Context) -> Execution { async fn execute(&self, _ctx: &Context) -> Execution {
let roll = self.0.roll(); let roll = self.0.roll();
let plain = format!("Dice: {}\nResult: {}", self.0, roll); let plain = format!("Dice: {}\nResult: {}", self.0, roll);
let html = format!( let html = format!(
@ -58,12 +61,13 @@ impl Command for RollCommand {
pub struct PoolRollCommand(DicePool); pub struct PoolRollCommand(DicePool);
#[async_trait]
impl Command for PoolRollCommand { impl Command for PoolRollCommand {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
"roll dice pool" "roll dice pool"
} }
fn execute(&self, ctx: &Context) -> Execution { async fn execute(&self, ctx: &Context) -> Execution {
let pool_with_ctx = DicePoolWithContext(&self.0, ctx); let pool_with_ctx = DicePoolWithContext(&self.0, ctx);
let roll_result = pool_with_ctx.roll(); let roll_result = pool_with_ctx.roll();
@ -89,12 +93,13 @@ impl Command for PoolRollCommand {
pub struct HelpCommand(Option<HelpTopic>); pub struct HelpCommand(Option<HelpTopic>);
#[async_trait]
impl Command for HelpCommand { impl Command for HelpCommand {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
"help information" "help information"
} }
fn execute(&self, _ctx: &Context) -> Execution { async fn execute(&self, _ctx: &Context) -> Execution {
let help = match &self.0 { let help = match &self.0 {
Some(topic) => topic.message(), Some(topic) => topic.message(),
_ => "There is no help for this topic", _ => "There is no help for this topic",
@ -108,12 +113,13 @@ impl Command for HelpCommand {
pub struct GetVariableCommand(String); pub struct GetVariableCommand(String);
#[async_trait]
impl Command for GetVariableCommand { impl Command for GetVariableCommand {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
"retrieve variable value" "retrieve variable value"
} }
fn execute(&self, ctx: &Context) -> Execution { async fn execute(&self, ctx: &Context) -> Execution {
let name = &self.0; let name = &self.0;
let value = match ctx.db.get_user_variable(&ctx.room_id, &ctx.username, name) { let value = match ctx.db.get_user_variable(&ctx.room_id, &ctx.username, name) {
Ok(num) => format!("{} = {}", name, num), Ok(num) => format!("{} = {}", name, num),
@ -129,12 +135,13 @@ impl Command for GetVariableCommand {
pub struct SetVariableCommand(String, i32); pub struct SetVariableCommand(String, i32);
#[async_trait]
impl Command for SetVariableCommand { impl Command for SetVariableCommand {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
"set variable value" "set variable value"
} }
fn execute(&self, ctx: &Context) -> Execution { async fn execute(&self, ctx: &Context) -> Execution {
let name = &self.0; let name = &self.0;
let value = self.1; let value = self.1;
let result = ctx let result = ctx
@ -154,12 +161,13 @@ impl Command for SetVariableCommand {
pub struct DeleteVariableCommand(String); pub struct DeleteVariableCommand(String);
#[async_trait]
impl Command for DeleteVariableCommand { impl Command for DeleteVariableCommand {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
"delete variable" "delete variable"
} }
fn execute(&self, ctx: &Context) -> Execution { async fn execute(&self, ctx: &Context) -> Execution {
let name = &self.0; let name = &self.0;
let value = match ctx let value = match ctx
.db .db
@ -176,17 +184,15 @@ impl Command for DeleteVariableCommand {
} }
} }
impl dyn Command { /// Parse a command string into a dynamic command execution trait
/// Parse a command string into a dynamic command execution trait /// object. Returns an error if a command was recognized but not
/// object. Returns an error if a command was recognized but not /// parsed correctly. Returns Ok(None) if no command was recognized.
/// parsed correctly. Returns Ok(None) if no command was recognized. pub fn parse(s: &str) -> Result<Box<dyn Command>, BotError> {
pub fn parse(s: &str) -> Result<Box<dyn Command>, BotError> {
match parser::parse_command(s) { match parser::parse_command(s) {
Ok(Some(command)) => Ok(command), Ok(Some(command)) => Ok(command),
Ok(None) => Err(BotError::CommandError(CommandError::IgnoredCommand)), Ok(None) => Err(BotError::CommandError(CommandError::IgnoredCommand)),
Err(e) => Err(e), Err(e) => Err(e),
} }
}
} }
pub struct CommandResult { pub struct CommandResult {
@ -198,14 +204,14 @@ pub struct CommandResult {
/// go back to Matrix, if the command was executed (successfully or /// go back to Matrix, if the command was executed (successfully or
/// not). If a command is determined to be ignored, this function will /// not). If a command is determined to be ignored, this function will
/// return None, signifying that we should not send a response. /// return None, signifying that we should not send a response.
pub fn execute_command(ctx: &Context) -> Option<CommandResult> { pub async fn execute_command(ctx: &Context) -> Option<CommandResult> {
let res = Command::parse(&ctx.message_body).map(|cmd| { let res = parse(&ctx.message_body);
let execution = cmd.execute(ctx);
(execution.plain().into(), execution.html().into())
});
let (plain, html) = match res { let (plain, html) = match res {
Ok(plain_and_html) => plain_and_html, Ok(cmd) => {
let execution = cmd.execute(ctx).await;
(execution.plain().into(), execution.html().into())
}
Err(BotError::CommandError(CommandError::IgnoredCommand)) => return None, Err(BotError::CommandError(CommandError::IgnoredCommand)) => return None,
Err(e) => { Err(e) => {
let message = format!("Error parsing command: {}", e); let message = format!("Error parsing command: {}", e);