use getset::{Getters, MutGetters, Setters};
use rayon::{iter::{IntoParallelRefMutIterator, ParallelIterator}, slice::ParallelSliceMut};
use serde::Serialize;
use crate::{codex::Codex, compilable_text::CompilableText, compilation::{compilable::Compilable, compilation_configuration::{compilation_configuration_overlay::CompilationConfigurationOverLay, CompilationConfiguration}, compilation_error::CompilationError, compilation_outcome::CompilationOutcome}, dossier::document::{chapter::{heading::HeadingLevel, paragraph::Paragraph}, Chapter}, load_block::{LoadBlock, LoadBlockContent}, output_format::OutputFormat};
#[derive(Debug, Getters, MutGetters, Setters, Serialize)]
pub struct ContentBundle {
#[getset(get = "pub", get_mut = "pub", set = "pub")]
#[serde(skip)] // TODO
preamble: Vec<Box<dyn Paragraph>>,
#[getset(get = "pub", get_mut = "pub", set = "pub")]
chapters: Vec<Chapter>,
compiled_content: Option<CompilableText>,
}
impl ContentBundle {
pub fn new(preamble: Vec<Box<dyn Paragraph>>, chapters: Vec<Chapter>,) -> Self {
Self {
preamble,
chapters,
compiled_content: None
}
}
}
impl From<Vec<LoadBlock>> for ContentBundle {
fn from(mut blocks: Vec<LoadBlock>) -> Self {
if !blocks.windows(2).all(|w| {
assert!(w[0].start() <= w[0].end());
assert!(w[1].start() <= w[1].end());
w[0].start() <= w[1].start()
}) {
blocks.par_sort_by(|a, b| a.start().cmp(&b.start()));
}
let mut preamble: Vec<Box<dyn Paragraph>> = Vec::new();
let mut current_chapter: Option<Chapter> = None;
let mut chapters: Vec<Chapter> = Vec::new();
let mut last_heading_level: u32 = 0;
for block in blocks {
match Into::<LoadBlockContent>::into(block) {
LoadBlockContent::Paragraph(paragraph) => {
if let Some(ref mut cc) = current_chapter {
cc.paragraphs_mut().push(paragraph);
} else {
preamble.push(paragraph);
}
},
LoadBlockContent::ChapterHeader(mut header) => {
if let Some(cc) = current_chapter.take() {
chapters.push(cc);
}
assert!(current_chapter.is_none());
let level = match header.heading().level() {
HeadingLevel::Minor => {
let l;
if last_heading_level < 1 {
log::warn!("minor heading found, but last heading has level {}, so it is set as 1", last_heading_level);
l = HeadingLevel::Explicit(1)
} else {
l = HeadingLevel::Explicit(last_heading_level - 1);
}
l
},
HeadingLevel::Major => {
let l;
if last_heading_level < 1 {
log::warn!("major heading found, but last heading has level {}, so it is set as 1", last_heading_level);
l = HeadingLevel::Explicit(1)
} else {
l = HeadingLevel::Explicit(last_heading_level + 1);
}
l
},
HeadingLevel::Same => {
let l;
if last_heading_level < 1 {
log::warn!("same heading found, but last heading has level {}, so it is set as 1", last_heading_level);
l = HeadingLevel::Explicit(1)
} else {
l = HeadingLevel::Explicit(last_heading_level);
}
l
},
HeadingLevel::Explicit(l) => HeadingLevel::Explicit(*l)
};
if let HeadingLevel::Explicit(l) = &level {
last_heading_level = *l;
} else {
unreachable!("heading level must be made 'explicit' now");
}
header.heading_mut().set_level(level);
current_chapter = Some(Chapter::new(header, Vec::new()));
},
}
}
if let Some(cc) = current_chapter.take() {
chapters.push(cc);
}
Self::new(preamble, chapters)
}
}
impl Compilable for ContentBundle {
fn standard_compile(&mut self, format: &OutputFormat, codex: &Codex, compilation_configuration: &CompilationConfiguration, compilation_configuration_overlay: CompilationConfigurationOverLay) -> Result<CompilationOutcome, CompilationError> {
if compilation_configuration_overlay.document_name().is_none() {
return Err(CompilationError::DocumentNameNotFound)
}
let parallelization = compilation_configuration.parallelization();
let mut preamble_outcomes: Vec<CompilationOutcome> = Vec::new();
let mut chapter_outcomes: Vec<CompilationOutcome> = Vec::new();
if parallelization {
let preamble_results: Vec<Result<CompilationOutcome, CompilationError>> = self.preamble.par_iter_mut()
.map(|paragraph| {
paragraph.compile(format, codex, compilation_configuration, compilation_configuration_overlay.clone())
}).collect();
let mut preamble_errors: Vec<CompilationError> = Vec::new();
preamble_results.into_iter().for_each(|result| {
match result {
Ok(outcome) => preamble_outcomes.push(outcome),
Err(err) => preamble_errors.push(err),
}
});
if !preamble_errors.is_empty() {
return Err(CompilationError::BucketOfErrors(preamble_errors))
}
let chapter_results: Vec<Result<CompilationOutcome, CompilationError>> = self.chapters.par_iter_mut()
.map(|chapter| {
chapter.compile(format, codex, compilation_configuration, compilation_configuration_overlay.clone())
}).collect();
let mut chapter_errors: Vec<CompilationError> = Vec::new();
chapter_results.into_iter().for_each(|result| {
match result {
Ok(outcome) => chapter_outcomes.push(outcome),
Err(err) => chapter_errors.push(err),
}
});
if !chapter_errors.is_empty() {
return Err(CompilationError::BucketOfErrors(chapter_errors))
}
} else {
for paragraph in self.preamble.iter_mut() {
preamble_outcomes.push(paragraph.compile(format, codex, compilation_configuration, compilation_configuration_overlay.clone())?);
}
for chapter in self.chapters.iter_mut() {
chapter_outcomes.push(chapter.compile(format, codex, compilation_configuration, compilation_configuration_overlay.clone())?);
}
}
Ok(CompilationOutcome::from(codex.assembler().assemble_bundle(&preamble_outcomes, &chapter_outcomes, compilation_configuration_overlay.assembler_configuration())?))
}
}
#[cfg(test)]
mod test {
use std::sync::Arc;
use crate::{assembler::html_assembler::HtmlAssembler, codex::{modifier::{base_modifier::BaseModifier, standard_text_modifier::StandardTextModifier, Modifier, ModifiersBucket}, Codex, ParagraphModifierOrderedMap, TextModifierOrderedMap}, compilable_text::{compilable_text_part::CompilableTextPart, CompilableText}, compilation::{compilable::Compilable, compilation_configuration::{compilation_configuration_overlay::CompilationConfigurationOverLay, CompilationConfiguration}, compilation_rule::{replacement_rule::{replacement_rule_part::{closure_replacement_rule_part::ClosureReplacementRuleReplacerPart, fixed_replacement_rule_part::FixedReplacementRuleReplacerPart}, ReplacementRule}, CompilationRule}}, output_format::OutputFormat};
#[test]
fn compile_fake_paragraph_with_bold_text() {
let mut compilable_text = CompilableText::new(
vec![
CompilableTextPart::new_fixed(String::from("<p>")),
CompilableTextPart::new_compilable(
String::from("This is a **bold text**!"),
ModifiersBucket::None
),
CompilableTextPart::new_fixed(String::from(" € ")),
CompilableTextPart::new_compilable(
String::from("**again"),
ModifiersBucket::None
),
CompilableTextPart::new_fixed(String::from(" €")),
CompilableTextPart::new_compilable(
String::from("**"),
ModifiersBucket::None
),
CompilableTextPart::new_fixed(String::from("</p>")),
],
);
let codex = Codex::new(
TextModifierOrderedMap::from([
(
StandardTextModifier::BoldStarVersion.identifier(),
(
Box::new(Into::<BaseModifier>::into(StandardTextModifier::BoldStarVersion)) as Box<dyn Modifier>,
Box::new(
ReplacementRule::new(
StandardTextModifier::BoldStarVersion.modifier_pattern(),
vec![
Arc::new(FixedReplacementRuleReplacerPart::new(String::from("<strong>"))),
Arc::new(ClosureReplacementRuleReplacerPart::new(Arc::new(|captures, compilable, _, _, _| {
let capture1 = captures.get(1).unwrap();
let slice = compilable.parts_slice(capture1.start(), capture1.end())?;
Ok(CompilableText::new(slice))
}))),
Arc::new(FixedReplacementRuleReplacerPart::new(String::from("</strong>"))),
]
)
) as Box<dyn CompilationRule>
) as (Box<dyn Modifier>, Box<dyn CompilationRule>)
)
]),
ParagraphModifierOrderedMap::new(),
None,
Box::new(HtmlAssembler::new())
);
compilable_text.compile(
&OutputFormat::Html,
&codex,
&CompilationConfiguration::default(),
CompilationConfigurationOverLay::default()
).unwrap();
assert_eq!(
compilable_text.content(),
"<p>This is a <strong>bold text</strong>! € <strong>again €</strong></p>"
)
}
}