dialogue_rs/script/
command.rs1use crate::script::parser::{Parser, Rule};
8use anyhow::bail;
9use pest::iterators::Pair;
10use pest::Parser as PestParser;
11use std::borrow::Cow;
12use std::fmt;
13
14#[derive(Debug, PartialEq, Eq, Clone)]
16pub struct Command {
17 name: Cow<'static, str>,
18 prefix: Option<Cow<'static, str>>,
19 suffix: Option<Cow<'static, str>>,
20}
21
22impl fmt::Display for Command {
23 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24 if let Some(prefix) = &self.prefix {
25 write!(f, "{prefix} ")?;
26 }
27
28 write!(f, "|{}|", self.name)?;
29
30 if let Some(suffix) = &self.suffix {
31 write!(f, " {suffix}")?;
32 }
33
34 Ok(())
35 }
36}
37
38impl Command {
39 pub fn new<T: Into<Cow<'static, str>>>(name: T, prefix: Option<T>, suffix: Option<T>) -> Self {
41 Self {
42 name: name.into(),
43 prefix: prefix.map(Into::into),
44 suffix: suffix.map(Into::into),
45 }
46 }
47
48 pub fn parse(command_str: &str) -> Result<Self, anyhow::Error> {
50 let mut pairs = Parser::parse(Rule::Command, command_str)?;
51 let pair = pairs.next().expect("a pair exists");
52 assert_eq!(pairs.next(), None);
53
54 pair.try_into()
55 }
56}
57
58impl TryFrom<Pair<'_, Rule>> for Command {
59 type Error = anyhow::Error;
60
61 fn try_from(pair: Pair<'_, Rule>) -> Result<Self, Self::Error> {
62 match pair.as_rule() {
63 Rule::Command => {
64 let inner_pairs = pair.into_inner();
65 let mut prefix = None;
66 let mut command_name = None;
67 let mut suffix = None;
68
69 for pair in inner_pairs {
70 match pair.as_rule() {
71 Rule::CommandName => {
72 command_name = Some(
73 pair.as_str()
74 .trim_start_matches('|')
75 .trim_end_matches('|')
76 .to_owned(),
77 );
78 }
79 Rule::Prefix => {
80 if pair.as_str().trim().is_empty() {
81 continue;
82 }
83
84 prefix = Some(pair.as_str().trim().to_owned());
85 }
86 Rule::Text => {
87 suffix = Some(pair.as_str().trim().to_owned());
88 }
89 _ => unreachable!("hit unexpected pair: {pair}"),
90 }
91 }
92
93 let command_name = command_name.expect("all commands have a name");
94
95 Ok(Self::new(command_name, prefix, suffix))
96 }
97 _ => bail!("Pair is not a command: {:#?}", pair),
98 }
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::Command;
105 use pretty_assertions::assert_eq;
106
107 #[test]
108 fn test_infix_command_parse() {
109 let command_str = "ZELDA |SAY| \"Hello, world!\"";
110 let actual = Command::parse(command_str).expect("command is valid");
111 let expected = Command::new("SAY", Some("ZELDA"), Some("\"Hello, world!\""));
112 assert_eq!(expected, actual);
113 }
114
115 #[test]
116 fn test_postfix_command_parse() {
117 let command_str = "|CHOICE| Do the thing";
118 let expected = Command::new("CHOICE".to_owned(), None, Some("Do the thing".to_owned()));
119 let actual = Command::parse(command_str).expect("command is valid");
120 assert_eq!(expected, actual);
121 }
122
123 #[test]
124 fn test_round_trip() {
125 let input = "ZELDA |SAY| \"Hello, world!\"";
126 let command = Command::parse(input).expect("command is valid");
127 let output = command.to_string();
128 assert_eq!(input, output);
129 }
130}