use crate::markup::normalize;
use crate::prelude::*;
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "bevy", derive(Reflect))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "bevy", reflect(Debug, PartialEq))]
#[cfg_attr(
all(feature = "bevy", feature = "serde"),
reflect(Serialize, Deserialize)
)]
pub struct Command {
pub name: String,
pub parameters: Vec<YarnValue>,
pub raw: String,
}
impl Command {
pub(crate) fn parse(input: String) -> Self {
assert!(
!input.trim().is_empty(),
"Failed to parse the command \"{input}\" because it is composed entirely of whitespace. \
Help: You might have passed an expression that evaluates to whitespace, e.g. `{{0}} {{\" \"}}`. \
If you think this is a bug, please report it at https://github.com/YarnSpinnerTool/YarnSpinner-Rust/issues/new"
);
let mut components = split_command_text(&input);
assert_or_bug!(
!components.is_empty(),
"Parsing the command \"{input}\" resulted in an empty list of components."
);
let name = components.remove(0);
let parameters = components.into_iter().map(YarnValue::from).collect();
Self {
name,
parameters,
raw: input,
}
}
}
fn split_command_text(input: &str) -> Vec<String> {
let input = normalize(input);
let mut chars = input.chars().peekable();
let mut results = Vec::new();
let mut current_component = String::new();
while let Some(mut char) = chars.next() {
match char {
_ if char.is_whitespace() => {
if !current_component.is_empty() {
results.push(core::mem::take(&mut current_component));
} else {
}
}
'\"' => {
loop {
char = match chars.next() {
Some(c) => c,
None => {
results.push(current_component);
return results;
}
};
match char {
'\\' => {
match chars.peek() {
Some('\\') | Some('\"') => {
let next = chars.next().unwrap();
current_component.push(next);
}
_ => {
current_component.push(char);
}
}
}
'\"' => {
break;
}
_ => {
current_component.push(char);
}
}
}
results.push(core::mem::take(&mut current_component));
}
_ => {
current_component.push(char);
}
}
}
if !current_component.is_empty() {
results.push(current_component);
}
results
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn split_command_text_splits_text_correctly() {
for (input, expected_components) in [
("one two three four", vec!["one", "two", "three", "four"]),
("one \"two three\" four", vec!["one", "two three", "four"]),
("one \"two three four", vec!["one", "two three four"]),
(
"one \"two \\\"three\" four",
vec!["one", "two \"three", "four"],
),
(
"one \\two three four",
vec!["one", "\\two", "three", "four"],
),
(
"one \"two \\\\ three\" four",
vec!["one", "two \\ three", "four"],
),
(
"one \"two \\1 three\" four",
vec!["one", "two \\1 three", "four"],
),
("one two", vec!["one", "two"]),
] {
let parsed_components = split_command_text(input);
assert_eq!(expected_components, parsed_components);
}
}
#[test]
fn parses_command() {
for (input, expected_command) in [
(
"foo bar",
Command {
name: "foo".to_string(),
parameters: vec!["bar".into()],
raw: "foo bar".to_string(),
},
),
(
"ayy",
Command {
name: "ayy".to_string(),
parameters: vec![],
raw: "ayy".to_string(),
},
),
(
"foo \"bar baz\"",
Command {
name: "foo".to_string(),
parameters: vec!["bar baz".into()],
raw: "foo \"bar baz\"".to_string(),
},
),
(
"set_sprite ship \"very happy\" 12.3",
Command {
name: "set_sprite".to_string(),
parameters: vec!["ship".into(), "very happy".into(), "12.3".into()],
raw: "set_sprite ship \"very happy\" 12.3".to_string(),
},
),
(
"!@#$%^&*()⁄€‹›fifl‡°·‚‘-=_+",
Command {
name: "!@#$%^&*()⁄€‹›fifl‡°·‚‘-=_+".to_string(),
parameters: vec![],
raw: "!@#$%^&*()⁄€‹›fifl‡°·‚‘-=_+".to_string(),
},
),
(
"\"A long name\"",
Command {
name: "A long name".to_string(),
parameters: vec![],
raw: "\"A long name\"".to_string(),
},
),
] {
let parsed_command = Command::parse(input.to_string());
assert_eq!(expected_command, parsed_command);
}
}
}