forked from projectmoon/tenebrous-dicebot
Implement basic, not-well-formatted help.
This commit is contained in:
parent
8484e9ffde
commit
531844fbb7
|
@ -172,6 +172,7 @@ dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"dirs",
|
"dirs",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
"indoc",
|
||||||
"itertools",
|
"itertools",
|
||||||
"log",
|
"log",
|
||||||
"matrix-sdk",
|
"matrix-sdk",
|
||||||
|
@ -680,6 +681,15 @@ dependencies = [
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indoc"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "644defcefee68d7805653a682e99a2e2a5014a1fc3cc9be7059a215844eeea6f"
|
||||||
|
dependencies = [
|
||||||
|
"unindent",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "instant"
|
name = "instant"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
|
@ -2051,6 +2061,12 @@ version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unindent"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af41d708427f8fd0e915dcebb2cae0f0e6acb2a939b2d399c265c39a38a18942"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.1.1"
|
version = "2.1.1"
|
||||||
|
|
|
@ -21,6 +21,7 @@ itertools = "0.9"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
url = "2.1"
|
url = "2.1"
|
||||||
dirs = "3.0"
|
dirs = "3.0"
|
||||||
|
indoc = "1.0"
|
||||||
|
|
||||||
# The versioning of the matrix SDK follows its Cargo.toml. The SDK and
|
# The versioning of the matrix SDK follows its Cargo.toml. The SDK and
|
||||||
# macros are on master, but it imports the common and base from 0.1.0.
|
# macros are on master, but it imports the common and base from 0.1.0.
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use crate::cofd::dice::DicePool;
|
use crate::cofd::dice::DicePool;
|
||||||
use crate::dice::ElementExpression;
|
use crate::dice::ElementExpression;
|
||||||
|
use crate::help::HelpTopic;
|
||||||
use crate::parser::trim;
|
use crate::parser::trim;
|
||||||
use crate::roll::Roll;
|
use crate::roll::Roll;
|
||||||
|
|
||||||
|
@ -61,10 +62,29 @@ impl Command for PoolRollCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a command string into a dynamic command execution trait object.
|
pub struct HelpCommand(Option<HelpTopic>);
|
||||||
/// Returns an error if a command was recognized but not parsed correctly. Returns None if no
|
|
||||||
/// command was recognized.
|
impl Command for HelpCommand {
|
||||||
pub fn parse_command(s: &str) -> Result<Option<Box<dyn Command>>, String> {
|
fn name(&self) -> &'static str {
|
||||||
|
"help information"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute(&self) -> Execution {
|
||||||
|
let help = match &self.0 {
|
||||||
|
Some(topic) => topic.message(),
|
||||||
|
_ => "There is no help for this topic",
|
||||||
|
};
|
||||||
|
|
||||||
|
let plain = format!("Help: {}", help);
|
||||||
|
let html = format!("<p><strong>Help:</strong> {}", help.replace("\n", "<br/>"));
|
||||||
|
Execution { plain, html }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
pub fn parse_command<'a>(s: &'a str) -> Result<Option<Box<dyn Command + 'a>>, String> {
|
||||||
match parser::parse_command(s) {
|
match parser::parse_command(s) {
|
||||||
Ok((input, command)) => match (input, &command) {
|
Ok((input, command)) => match (input, &command) {
|
||||||
//This clause prevents bot from spamming messages to itself
|
//This clause prevents bot from spamming messages to itself
|
||||||
|
@ -115,6 +135,19 @@ mod tests {
|
||||||
.expect("was error"));
|
.expect("was error"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn help_whitespace_test() {
|
||||||
|
assert!(parse_command("!help stuff ")
|
||||||
|
.map(|p| p.is_some())
|
||||||
|
.expect("was error"));
|
||||||
|
assert!(parse_command(" !help stuff")
|
||||||
|
.map(|p| p.is_some())
|
||||||
|
.expect("was error"));
|
||||||
|
assert!(parse_command(" !help stuff ")
|
||||||
|
.map(|p| p.is_some())
|
||||||
|
.expect("was error"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn roll_whitespace_test() {
|
fn roll_whitespace_test() {
|
||||||
assert!(parse_command("!roll 1d4 + 5d6 -3 ")
|
assert!(parse_command("!roll 1d4 + 5d6 -3 ")
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
use nom::{alt, complete, named, tag, take_while, tuple, IResult};
|
use nom::{alt, complete, named, tag, take_while, tuple, IResult};
|
||||||
|
|
||||||
use crate::cofd::parser::{create_chance_die, parse_dice_pool};
|
use crate::cofd::parser::{create_chance_die, parse_dice_pool};
|
||||||
use crate::commands::{Command, PoolRollCommand, RollCommand};
|
use crate::commands::{Command, HelpCommand, PoolRollCommand, RollCommand};
|
||||||
use crate::dice::parser::parse_element_expression;
|
use crate::dice::parser::parse_element_expression;
|
||||||
use crate::parser::eat_whitespace;
|
use crate::help::parse_help_topic;
|
||||||
|
use crate::parser::{eat_whitespace, trim};
|
||||||
|
|
||||||
// Parse a roll expression.
|
// Parse a roll expression.
|
||||||
fn parse_roll(input: &str) -> IResult<&str, Box<dyn Command>> {
|
fn parse_roll(input: &str) -> IResult<&str, Box<dyn Command>> {
|
||||||
|
@ -23,6 +24,12 @@ fn chance_die() -> IResult<&'static str, Box<dyn Command>> {
|
||||||
Ok((input, Box::new(PoolRollCommand(pool))))
|
Ok((input, Box::new(PoolRollCommand(pool))))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn help(topic: &str) -> IResult<&str, Box<dyn Command>> {
|
||||||
|
let (topic, _) = eat_whitespace(topic)?;
|
||||||
|
let topic = parse_help_topic(&trim(topic));
|
||||||
|
Ok(("", Box::new(HelpCommand(topic))))
|
||||||
|
}
|
||||||
|
|
||||||
/// Potentially parse a command expression. If we recognize the command, an error should be raised
|
/// 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
|
/// if the command is misparsed. If we don't recognize the command, ignore it and return none
|
||||||
pub fn parse_command(original_input: &str) -> IResult<&str, Option<Box<dyn Command>>> {
|
pub fn parse_command(original_input: &str) -> IResult<&str, Option<Box<dyn Command>>> {
|
||||||
|
@ -32,7 +39,9 @@ pub fn parse_command(original_input: &str) -> IResult<&str, Option<Box<dyn Comma
|
||||||
named!(command(&str) -> (&str, &str), tuple!(
|
named!(command(&str) -> (&str, &str), tuple!(
|
||||||
complete!(tag!("!")),
|
complete!(tag!("!")),
|
||||||
alt!(
|
alt!(
|
||||||
complete!(tag!("chance")) | //TODO figure out how to just have it read single commands.
|
//TODO figure out how to gracefully handle arbitrary single commands.
|
||||||
|
complete!(tag!("chance")) |
|
||||||
|
complete!(tag!("help")) |
|
||||||
complete!(take_while!(char::is_alphabetic))
|
complete!(take_while!(char::is_alphabetic))
|
||||||
)
|
)
|
||||||
));
|
));
|
||||||
|
@ -40,13 +49,16 @@ pub fn parse_command(original_input: &str) -> IResult<&str, Option<Box<dyn Comma
|
||||||
let (input, command) = match command(input) {
|
let (input, command) = match command(input) {
|
||||||
// Strip the exclamation mark
|
// Strip the exclamation mark
|
||||||
Ok((input, (_, result))) => (input, result),
|
Ok((input, (_, result))) => (input, result),
|
||||||
Err(_e) => return Ok((original_input, None)),
|
Err(_e) => {
|
||||||
|
return Ok((original_input, None));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
match command {
|
match command {
|
||||||
"r" | "roll" => parse_roll(input).map(|(input, command)| (input, Some(command))),
|
"r" | "roll" => parse_roll(input).map(|(input, command)| (input, Some(command))),
|
||||||
"rp" | "pool" => parse_pool_roll(input).map(|(input, command)| (input, Some(command))),
|
"rp" | "pool" => parse_pool_roll(input).map(|(input, command)| (input, Some(command))),
|
||||||
"chance" => chance_die().map(|(input, command)| (input, Some(command))),
|
"chance" => chance_die().map(|(input, command)| (input, Some(command))),
|
||||||
|
"help" => help(input).map(|(input, command)| (input, Some(command))),
|
||||||
// No recognized command, ignore this.
|
// No recognized command, ignore this.
|
||||||
_ => Ok((original_input, None)),
|
_ => Ok((original_input, None)),
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
use indoc::indoc;
|
||||||
|
|
||||||
|
pub fn parse_help_topic(input: &str) -> Option<HelpTopic> {
|
||||||
|
match input {
|
||||||
|
"cofd" => Some(HelpTopic::ChroniclesOfDarkness),
|
||||||
|
"dicepool" => Some(HelpTopic::DicePool),
|
||||||
|
"dice" => Some(HelpTopic::RollingDice),
|
||||||
|
"" => Some(HelpTopic::General),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum HelpTopic {
|
||||||
|
ChroniclesOfDarkness,
|
||||||
|
DicePool,
|
||||||
|
RollingDice,
|
||||||
|
General,
|
||||||
|
}
|
||||||
|
|
||||||
|
const COFD_HELP: &'static str = indoc! {"
|
||||||
|
Chronicles of Darkness
|
||||||
|
|
||||||
|
Commands available:
|
||||||
|
!pool, !rp: roll a dice pool
|
||||||
|
!chance: roll a chance die
|
||||||
|
|
||||||
|
See also:
|
||||||
|
!help dicepool
|
||||||
|
"};
|
||||||
|
|
||||||
|
const DICE_HELP: &'static str = indoc! {"
|
||||||
|
Rolling basic dice
|
||||||
|
|
||||||
|
Command: !roll, !r
|
||||||
|
|
||||||
|
Syntax !roll <dice-expression>
|
||||||
|
|
||||||
|
Dice expression can be a basic die (e.g. 1d4), with a bonus (1d4+3),
|
||||||
|
or a more complex series of dice rolls or arbitrary numbers.
|
||||||
|
Parentheses are not supported.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
!roll 1d4
|
||||||
|
!roll 1d4+5
|
||||||
|
!roll 2d6+8
|
||||||
|
!roll 2d8 + 4d6 - 3
|
||||||
|
"};
|
||||||
|
|
||||||
|
const DICEPOOL_HELP: &'static str = indoc! {"
|
||||||
|
Rolling dice pools
|
||||||
|
|
||||||
|
Command: !pool, !rp
|
||||||
|
|
||||||
|
Syntax: !pool <num><modifiers>
|
||||||
|
|
||||||
|
Modifiers:
|
||||||
|
n = nine-again
|
||||||
|
e = eight-again
|
||||||
|
r = rote quality
|
||||||
|
x = do not re-roll 10s
|
||||||
|
s<num> = number of successes for exceptional
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
!pool 8 (roll a regular pool of 8 dice)
|
||||||
|
!pool 5n (roll dice pool of 5, nine-again)
|
||||||
|
!pool 6rs3 (roll dice pool of 6, rote quality, 3 successes for exceptional)
|
||||||
|
"};
|
||||||
|
|
||||||
|
const GENERAL_HELP: &'static str = indoc! {"
|
||||||
|
General Help
|
||||||
|
|
||||||
|
Try these help commands:
|
||||||
|
!help cofd
|
||||||
|
!help dice
|
||||||
|
"};
|
||||||
|
|
||||||
|
impl HelpTopic {
|
||||||
|
pub fn message(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
HelpTopic::ChroniclesOfDarkness => COFD_HELP,
|
||||||
|
HelpTopic::DicePool => DICEPOOL_HELP,
|
||||||
|
HelpTopic::RollingDice => DICE_HELP,
|
||||||
|
HelpTopic::General => GENERAL_HELP,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,5 +2,6 @@ pub mod bot;
|
||||||
pub mod cofd;
|
pub mod cofd;
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
pub mod dice;
|
pub mod dice;
|
||||||
|
mod help;
|
||||||
mod parser;
|
mod parser;
|
||||||
pub mod roll;
|
pub mod roll;
|
||||||
|
|
Loading…
Reference in New Issue