use crate::ErrorList;
use crate::FullContext;
use crate::Output;
use crate::PassageContent;
use crate::PassageHeader;
use crate::Position;
use crate::PositionKind;
use crate::ScriptContent;
use crate::StoryData;
use crate::StoryTitle;
use crate::StylesheetContent;
use crate::TwineContent;
#[derive(Debug)]
pub struct Passage {
pub header: PassageHeader,
pub content: PassageContent,
pub context: FullContext,
}
impl Passage {
pub fn new(
header: Output<Result<PassageHeader, ErrorList>>,
content: Output<Result<PassageContent, ErrorList>>,
context: FullContext,
) -> Output<Result<Self, ErrorList>> {
let (mut header_res, mut warnings) = header.take();
let (mut content_res, mut content_warnings) = content.take();
warnings.append(&mut content_warnings);
let possible_errors = ErrorList::merge(&mut header_res, &mut content_res);
Output::new(match possible_errors {
Err(e) => Err(e),
Ok(_) => {
let header = header_res.ok().unwrap();
let content = content_res.ok().unwrap();
Ok(Passage {
header,
content,
context,
})
}
})
.with_warnings(warnings)
}
pub fn metadata(&self) -> &serde_json::Map<String, serde_json::Value> {
&self.header.metadata
}
pub fn tags(&self) -> &Vec<String> {
&self.header.tags
}
pub(crate) fn parse(context: FullContext) -> Output<Result<Self, ErrorList>> {
let header_context = context.subcontext(..=context.end_of_line(1, PositionKind::Relative));
let header = PassageHeader::parse(header_context);
if header.is_err() {
return header.into_err();
}
let header_ref = header.get_output().as_ref().ok().unwrap();
let mut new_iter = context.get_contents().split('\n');
new_iter.rfind(|&x| !x.is_empty());
let len = new_iter.fold(0, |acc, _| acc + 1);
let content_context = context
.subcontext(Position::rel(2, 1)..=context.end_of_line(len + 1, PositionKind::Relative));
let trimmed_context = context.subcontext(..=content_context.get_end_position());
let content: Output<Result<PassageContent, ErrorList>>;
content = if header_ref.name == "StoryTitle" {
StoryTitle::parse(content_context).into_result()
} else if header_ref.name == "StoryData" {
StoryData::parse(content_context).into_result()
} else if header_ref.has_tag("script") {
ScriptContent::parse(content_context).into_result()
} else if header_ref.has_tag("stylesheet") {
StylesheetContent::parse(content_context).into_result()
} else {
TwineContent::parse(content_context).into_result()
};
Self::new(header, content, trimmed_context)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn story_title_subtest(input: String, expected_title: &str) {
let context = FullContext::from(None, input);
let out = Passage::parse(context);
assert_eq!(out.has_warnings(), false);
let (res, _) = out.take();
assert_eq!(res.is_ok(), true);
let passage = res.ok().unwrap();
let content = passage.content;
let expected = if let PassageContent::StoryTitle(story_title) = content {
assert_eq!(story_title.title, expected_title);
true
} else {
false
};
assert_eq!(expected, true);
}
#[test]
fn one_line_story_title() {
let input = ":: StoryTitle\nOne line story title\n\n".to_string();
story_title_subtest(input, "One line story title");
}
#[test]
fn multi_line_story_title() {
let input = "::StoryTitle\nMulti\nLine\nTitle".to_string();
story_title_subtest(input, "Multi\nLine\nTitle")
}
#[test]
fn script_passage() {
let input = ":: Script Passage [script]\nfoo\nbar".to_string();
let context = FullContext::from(None, input);
let out = Passage::parse(context);
assert_eq!(out.has_warnings(), false);
let (res, _) = out.take();
assert_eq!(res.is_ok(), true);
let passage = res.ok().unwrap();
assert_eq!(passage.tags(), &vec!["script".to_string()]);
let content = passage.content;
let expected = if let PassageContent::Script(script) = content {
assert_eq!(passage.header.name, "Script Passage");
assert_eq!(script.content, "foo\nbar");
true
} else {
false
};
assert_eq!(expected, true);
}
#[test]
fn stylesheet_passage() {
let input = ":: Style Passage [stylesheet]\nfoo\nbar".to_string();
let context = FullContext::from(None, input);
let out = Passage::parse(context);
assert_eq!(out.has_warnings(), false);
let (res, _) = out.take();
assert_eq!(res.is_ok(), true);
let passage = res.ok().unwrap();
assert_eq!(passage.metadata()["position"], "10,10");
assert_eq!(passage.metadata()["size"], "100,100");
let content = passage.content;
let expected = if let PassageContent::Stylesheet(stylesheet) = content {
assert_eq!(passage.header.name, "Style Passage");
assert_eq!(stylesheet.content, "foo\nbar");
true
} else {
false
};
assert_eq!(expected, true);
}
#[test]
fn a_test() {
let input_string = r#":: An overgrown path[tag tag2 ]
This
That
"#
.to_string();
let context = FullContext::from(None, input_string);
let out = Passage::parse(context);
assert_eq!(out.has_warnings(), false);
let (res, _) = out.take();
assert_eq!(res.is_ok(), true);
let passage = res.ok().unwrap();
let content = passage.content;
let expected = if let PassageContent::Normal(normal) = content {
assert_eq!(passage.header.name, "An overgrown path");
assert_eq!(normal.content, "This\nThat\n");
true
} else {
false
};
assert_eq!(expected, true);
}
}