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

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>();
}
}