basic test suite for monologue parsing

main
Elijah Voigt 3 months ago
parent dba46a99da
commit df069d0337

9
Cargo.lock generated

@ -2226,7 +2226,8 @@ dependencies = [
"bevy",
"bevy_rapier3d",
"chrono",
"itertools 0.13.0",
"indoc",
"itertools 0.14.0",
"lipsum",
"rand",
"serde",
@ -2583,6 +2584,12 @@ dependencies = [
"serde",
]
[[package]]
name = "indoc"
version = "2.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd"
[[package]]
name = "inflections"
version = "1.1.1"

@ -21,6 +21,7 @@ features = ["wayland", "dynamic_linking"]
lipsum = "*"
rand = "*"
itertools = "*"
indoc = "*"
[build-dependencies]
walkdir = "*"

@ -1,7 +1,7 @@
use super::*;
/// A monologue containing a list of optional lines
#[derive(Asset, TypePath, Debug, Deserialize, Default, Clone)]
#[derive(Asset, TypePath, Debug, Deserialize, Default, Clone, PartialEq)]
pub(crate) struct Monologue {
pub batches: Vec<MonologueLineBatch>,
}
@ -16,8 +16,49 @@ impl Monologue {
}
}
#[derive(Debug, Error)]
pub struct MonologueParseError;
impl std::fmt::Display for MonologueParseError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Could not parse monologue file")
}
}
impl TryFrom<&str> for Monologue {
type Error = MonologueParseError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
// Create a blank monologue to populate
let mut monologue = Monologue::default();
// Add an initial batch that may end up being empty
monologue.add_batch(MonologueLineBatch::default());
// Iterate over raw string lines in the .mono file
for line in value.lines() {
// Break up into batches by --- separator
if line.starts_with("---") {
monologue.add_batch(MonologueLineBatch::default());
// Skip any empty lines or comments
} else if line.starts_with("#") || line.is_empty() {
// Skip comments and blank lines
// everything else we read as a monologue line
} else {
monologue.batches.last_mut().unwrap().add_line(line.into());
}
}
// Clear empty batches
monologue.batches.retain(|batch| !batch.lines.is_empty());
Ok(monologue)
}
}
/// A set of possible lines in a monologue
#[derive(Debug, Deserialize, Default, Clone)]
#[derive(Debug, Deserialize, Default, Clone, PartialEq)]
pub(crate) struct MonologueLineBatch {
pub lines: Vec<MonologueLine>,
}
@ -29,7 +70,7 @@ impl MonologueLineBatch {
}
/// A single monologue line
#[derive(Debug, Deserialize, Default, Clone)]
#[derive(Debug, Deserialize, Default, Clone, PartialEq)]
pub(crate) struct MonologueLine {
pub value: String,
}
@ -60,6 +101,156 @@ impl From<&str> for MonologueLine {
}
}
#[cfg(test)]
mod tests {
use super::*;
use indoc::*;
#[test]
fn empty_monologue() {
const MONO: &str = "";
let parsed: Monologue = MONO.try_into().unwrap();
let expected = Monologue::default();
assert_eq!(parsed, expected);
}
#[test]
fn complicated_empty_monologue() {
const MONO: &str = indoc! {"
---
---
# a comment
---
--- # more stuff
# another comment
---
---
"};
let parsed: Monologue = MONO.try_into().unwrap();
let expected = Monologue::default();
assert_eq!(parsed, expected);
}
#[test]
fn basic_monologue() {
const MONO: &str = indoc! {"
---
hello
---
world
---
"};
let parsed: Monologue = MONO.try_into().unwrap();
let expected = Monologue {
batches: vec![
MonologueLineBatch {
lines: vec![
"hello".into()
],
},
MonologueLineBatch {
lines: vec![
"world".into()
]
}
]
};
assert_eq!(parsed, expected);
}
#[test]
fn basic_with_comments_monologue() {
const MONO: &str = indoc! {"
# Some stuff before we get started
---
# I really like this line
hello
---
# I'm skeptical about this one...
world
# This line needs work
---
# More notes after the lines
"};
let parsed: Monologue = MONO.try_into().unwrap();
let expected = Monologue {
batches: vec![
MonologueLineBatch {
lines: vec![
"hello".into()
],
},
MonologueLineBatch {
lines: vec![
"world".into()
]
}
]
};
assert_eq!(parsed, expected);
}
#[test]
fn batches_monologue() {
const MONO: &str = indoc! {"
a
b
c
---
d
e
---
# with comment before...
f
# ...and after
"};
let parsed: Monologue = MONO.try_into().unwrap();
let expected = Monologue {
batches: vec![
MonologueLineBatch {
lines: vec![
"a".into(), "b".into(), "c".into()
]
},
MonologueLineBatch {
lines: vec![
"d".into(), "e".into()
]
},
MonologueLineBatch {
lines: vec![
"f".into()
]
},
]
};
assert_eq!(parsed, expected);
}
}
#[derive(Default)]
struct MonologueLoader;
@ -69,6 +260,9 @@ enum MonologueLoaderError {
Io(#[from] std::io::Error),
#[error("Could not parse utf8")]
Utf8(#[from] std::string::FromUtf8Error),
// TODO: Real errors
#[error("Could not as monologue")]
Parse(#[from] MonologueParseError),
}
impl AssetLoader for MonologueLoader {
@ -86,30 +280,7 @@ impl AssetLoader for MonologueLoader {
let raw_string = String::from_utf8(bytes)?;
// Create a blank monologue to populate
let mut monologue = Monologue::default();
// Add an initial batch that may end up being empty
monologue.add_batch(MonologueLineBatch::default());
// Iterate over raw string lines in the .mono file
for line in raw_string.lines() {
// Break up into batches by --- separator
if line.starts_with("---") {
monologue.add_batch(MonologueLineBatch::default());
// Skip any empty lines or comments
} else if line.starts_with("#") || line.is_empty() {
// Skip comments and blank lines
// everything else we read as a monologue line
} else {
monologue.batches.last_mut().unwrap().add_line(line.into());
}
}
// Clear empty batches
monologue.batches.retain(|batch| !batch.lines.is_empty());
Ok(monologue)
Ok(Monologue::try_from(raw_string.as_str())?)
}
fn extensions(&self) -> &[&str] {

Loading…
Cancel
Save