You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
283 lines
6.6 KiB
Rust
283 lines
6.6 KiB
Rust
use super::*;
|
|
|
|
/// A monologue containing a list of optional lines
|
|
#[derive(Asset, TypePath, Debug, Deserialize, Default, Clone, PartialEq)]
|
|
pub(crate) struct Monologue {
|
|
pub batches: Vec<MonologueLineBatch>,
|
|
}
|
|
|
|
impl Monologue {
|
|
pub fn get(&self, idx: usize) -> Option<&MonologueLineBatch> {
|
|
self.batches.get(idx)
|
|
}
|
|
|
|
pub fn add_batch(&mut self, group: MonologueLineBatch) {
|
|
self.batches.push(group);
|
|
}
|
|
}
|
|
|
|
#[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, PartialEq)]
|
|
pub(crate) struct MonologueLineBatch {
|
|
pub lines: Vec<MonologueLine>,
|
|
}
|
|
|
|
impl MonologueLineBatch {
|
|
fn add_line(&mut self, line: MonologueLine) {
|
|
self.lines.push(line);
|
|
}
|
|
}
|
|
|
|
/// A single monologue line
|
|
#[derive(Debug, Deserialize, Default, Clone, PartialEq)]
|
|
pub(crate) struct MonologueLine {
|
|
pub value: String,
|
|
}
|
|
|
|
impl Display for MonologueLine {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
write!(f, "{}", self.value)
|
|
}
|
|
}
|
|
|
|
impl From<String> for MonologueLine {
|
|
fn from(value: String) -> Self {
|
|
MonologueLine { value }
|
|
}
|
|
}
|
|
|
|
impl From<MonologueLine> for String {
|
|
fn from(val: MonologueLine) -> Self {
|
|
val.value
|
|
}
|
|
}
|
|
|
|
impl From<&str> for MonologueLine {
|
|
fn from(value: &str) -> Self {
|
|
MonologueLine {
|
|
value: value.into(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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;
|
|
|
|
#[derive(Debug, Error)]
|
|
enum MonologueLoaderError {
|
|
#[error("Could not load asset: {0}")]
|
|
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 {
|
|
type Asset = Monologue;
|
|
type Settings = ();
|
|
type Error = MonologueLoaderError;
|
|
async fn load(
|
|
&self,
|
|
reader: &mut dyn Reader,
|
|
_settings: &(),
|
|
_load_context: &mut LoadContext<'_>,
|
|
) -> Result<Self::Asset, Self::Error> {
|
|
let mut bytes: Vec<u8> = Vec::new();
|
|
reader.read_to_end(&mut bytes).await?;
|
|
|
|
let raw_string = String::from_utf8(bytes)?;
|
|
|
|
Ok(Monologue::try_from(raw_string.as_str())?)
|
|
}
|
|
|
|
fn extensions(&self) -> &[&str] {
|
|
&["mono"]
|
|
}
|
|
}
|
|
|
|
pub struct MonologueAssetsPlugin;
|
|
|
|
impl Plugin for MonologueAssetsPlugin {
|
|
fn build(&self, app: &mut App) {
|
|
app.init_asset::<Monologue>()
|
|
.init_asset_loader::<MonologueLoader>();
|
|
}
|
|
}
|