use crate::{
clean, composite::Composite, compound::Compound, line::Line, line_parser::LineParser,
text::Text,
};
#[derive(Debug, Default)]
struct SubTemplate<'s> {
start_line_idx: usize,
line_count: usize,
name: &'s str,
}
#[derive(Debug, Default, PartialEq, Eq)]
struct CompoundArg<'s> {
name: &'s str,
line_idx: usize,
composite_idx: usize,
compound_idx: usize,
}
#[derive(Debug)]
pub struct TextTemplate<'s> {
pub text: Text<'s>,
compound_args: Vec<CompoundArg<'s>>,
sub_templates: Vec<SubTemplate<'s>>,
}
#[derive(Debug)]
enum SubTemplateToken<'s> {
None,
Start(&'s str),
End,
}
#[derive(Debug, Clone)]
struct Replacement<'s, 'b> {
name: &'b str,
value: &'s str,
}
#[derive(Debug)]
pub struct SubTemplateExpander<'s, 'b> {
name: &'b str,
raw_replacements: Vec<Replacement<'s, 'b>>,
md_replacements: Vec<Replacement<'s, 'b>>,
}
pub struct TextTemplateExpander<'s, 'b> {
template: &'b TextTemplate<'s>,
text: Text<'s>,
sub_expansions: Vec<SubTemplateExpander<'s, 'b>>,
md_replacements: Vec<Replacement<'s, 'b>>,
lines_to_add: Vec<Vec<Line<'s>>>,
lines_to_exclude: Vec<bool>,
}
fn is_valid_name_char(c: char) -> bool {
c.is_ascii_lowercase() || c.is_digit(10) || c == '_' || c == '-'
}
fn read_sub_template_token(md_line: &str) -> SubTemplateToken<'_> {
let mut chars = md_line.chars();
match (chars.next(), chars.next()) {
(Some('$'), Some('{')) => {
let name = &md_line[2..];
if !name.is_empty() && name.chars().all(is_valid_name_char) {
SubTemplateToken::Start(name)
} else {
SubTemplateToken::None
}
}
(Some('}'), None) => SubTemplateToken::End,
_ => SubTemplateToken::None,
}
}
fn find_args<'s>(
composite: &mut Composite<'s>,
args: &mut Vec<CompoundArg<'s>>,
line_idx: usize,
composite_idx: usize,
) {
let mut compounds = Vec::new();
for compound in &composite.compounds {
let mut start = 0;
let mut iter = compound.as_str().char_indices();
while let Some((_, c)) = iter.next() {
if c == '$' {
if let Some((bridx, c)) = iter.next() {
if c == '{' {
while let Some((idx, c)) = iter.next() {
if c == '}' {
if idx - bridx > 1 {
if start + 1 < bridx {
compounds.push(compound.sub(start, bridx - 1));
}
args.push(CompoundArg {
name: &compound.as_str()[bridx + 1..idx],
line_idx,
composite_idx,
compound_idx: compounds.len(),
});
compounds.push(compound.sub(bridx - 1, idx + 1));
start = idx + 1;
}
break;
} else if !is_valid_name_char(c) {
break;
}
}
}
}
}
}
let tail = compound.tail(start);
if !tail.is_empty() {
compounds.push(tail);
}
}
composite.compounds = compounds;
}
impl<'s> From<&'s str> for TextTemplate<'s> {
fn from(md: &'s str) -> TextTemplate<'s> {
let mut text = Text { lines: Vec::new() };
let mut compound_args = Vec::new();
let mut sub_templates = Vec::new();
let mut current_sub_template: Option<SubTemplate<'_>> = None;
let mut between_fences = false;
for md_line in clean::lines(md) {
match read_sub_template_token(md_line) {
SubTemplateToken::Start(name) => {
current_sub_template = Some(SubTemplate {
start_line_idx: text.lines.len(),
line_count: 0,
name,
});
continue;
}
SubTemplateToken::End => {
if current_sub_template.is_some() {
let mut sub_template = current_sub_template.take().unwrap();
sub_template.line_count = text.lines.len() - sub_template.start_line_idx;
sub_templates.push(sub_template);
continue;
} else {
}
}
SubTemplateToken::None => {}
}
let line_idx = text.lines.len();
let parser = LineParser::from(md_line);
let mut line = if between_fences {
parser.as_code()
} else {
parser.line()
};
match &mut line {
Line::Normal(ref mut composite) => {
find_args(composite, &mut compound_args, line_idx, 0);
text.lines.push(line);
}
Line::TableRow(ref mut table_row) => {
for (composite_idx, composite) in table_row.cells.iter_mut().enumerate() {
find_args(composite, &mut compound_args, line_idx, composite_idx);
}
text.lines.push(line);
}
Line::CodeFence(..) => {
between_fences = !between_fences;
}
_ => {
text.lines.push(line);
}
};
}
TextTemplate {
text,
compound_args,
sub_templates,
}
}
}
impl<'s> TextTemplate<'s> {
pub fn expander<'b>(&'b self) -> TextTemplateExpander<'s, 'b> {
TextTemplateExpander::from(self)
}
fn get_sub_of_line(&self, line_idx: usize) -> Option<usize> {
for (sub_idx, sub_template) in self.sub_templates.iter().enumerate() {
if line_idx >= sub_template.start_line_idx
&& line_idx < sub_template.start_line_idx + sub_template.line_count
{
return Some(sub_idx);
}
}
None
}
}
impl<'s, 'b> From<&'b TextTemplate<'s>> for TextTemplateExpander<'s, 'b> {
fn from(template: &'b TextTemplate<'s>) -> Self {
let line_count = template.text.lines.len();
let lines_to_add = vec![Vec::new(); line_count];
let lines_to_exclude = vec![false; line_count];
Self {
template,
text: template.text.clone(),
sub_expansions: Vec::new(),
md_replacements: Vec::new(),
lines_to_add,
lines_to_exclude,
}
}
}
impl<'s, 'b> SubTemplateExpander<'s, 'b> {
pub fn set(&mut self, name: &'b str, value: &'s str) -> &mut SubTemplateExpander<'s, 'b> {
self.raw_replacements.push(Replacement { name, value });
self
}
pub fn set_md(&mut self, name: &'b str, value: &'s str) -> &mut SubTemplateExpander<'s, 'b> {
self.md_replacements.push(Replacement { name, value });
self
}
}
fn set_in_line<'s>(line: &mut Line<'s>, compound_arg: &CompoundArg<'s>, value: &'s str) {
match line {
Line::Normal(composite) => {
composite.compounds[compound_arg.compound_idx].set_str(value);
}
Line::TableRow(table_row) => {
table_row.cells[compound_arg.composite_idx].compounds[compound_arg.compound_idx]
.set_str(value);
}
_ => {}
}
}
fn set_in_text<'s>(
template: &TextTemplate<'s>,
text: &mut Text<'s>,
line_offset: usize,
name: Option<&str>,
value: &'s str,
) {
for compound_arg in &template.compound_args {
if name.is_none () || name == Some(compound_arg.name) {
let idx = compound_arg.line_idx;
if idx < line_offset || idx - line_offset >= text.lines.len() {
continue;
}
set_in_line(&mut text.lines[idx - line_offset], compound_arg, value);
}
}
}
fn set_all_md_in_text<'s, 'b>(
template: &TextTemplate<'s>,
text: &mut Text<'s>,
line_offset: usize,
md_replacements: &Vec<Replacement<'s, 'b>>,
) {
if md_replacements.is_empty() {
return;
}
for compound_arg in template.compound_args.iter().rev() {
let idx = compound_arg.line_idx;
if idx < line_offset || idx - line_offset >= text.lines.len() {
continue;
}
for md_repl in md_replacements {
if md_repl.name == compound_arg.name {
let replacing_composite = Composite::from_inline(md_repl.value);
let patched_line = &mut text.lines[idx - line_offset];
match patched_line {
Line::Normal(ref mut composite) => {
replace_compound(
composite,
compound_arg.compound_idx,
replacing_composite.compounds,
);
}
Line::TableRow(ref mut table_row) => {
replace_compound(
&mut table_row.cells[compound_arg.composite_idx],
compound_arg.compound_idx,
replacing_composite.compounds,
);
}
_ => {}
}
break;
}
}
}
}
fn replace_compound<'s>(
composite: &mut Composite<'s>,
mut compound_idx: usize,
mut replacing_compounds: Vec<Compound<'s>>,
) {
let mut replacing_compounds = replacing_compounds.drain(..);
if let Some(compound) = replacing_compounds.next() {
composite.compounds[compound_idx] = compound;
for compound in replacing_compounds {
compound_idx += 1;
composite.compounds.insert(compound_idx, compound);
}
}
}
impl<'s, 'b> TextTemplateExpander<'s, 'b> {
pub fn set(&mut self, name: &str, value: &'s str) -> &mut TextTemplateExpander<'s, 'b> {
set_in_text(&self.template, &mut self.text, 0, Some(name), value);
self
}
pub fn set_all(&mut self, value: &'s str) -> &mut TextTemplateExpander<'s, 'b> {
set_in_text(&self.template, &mut self.text, 0, None, value);
self
}
pub fn set_md(&mut self, name: &'b str, value: &'s str) -> &mut TextTemplateExpander<'s, 'b> {
self.md_replacements.push(Replacement { name, value });
self
}
pub fn set_lines(
&mut self,
name: &'b str,
raw_lines: &'s str,
) -> &mut TextTemplateExpander<'s, 'b> {
for compound_arg in &self.template.compound_args {
if compound_arg.name == name {
self.lines_to_exclude[compound_arg.line_idx] = true;
for value in clean::lines(raw_lines) {
let mut line = self.text.lines[compound_arg.line_idx].clone();
set_in_line(&mut line, compound_arg, value);
self.lines_to_add[compound_arg.line_idx].push(line);
}
}
}
self
}
pub fn set_lines_md(
&mut self,
name: &'b str,
md: &'s str,
) -> &mut TextTemplateExpander<'s, 'b> {
for compound_arg in &self.template.compound_args {
if compound_arg.name == name {
self.lines_to_exclude[compound_arg.line_idx] = true;
for line in md.lines().map(|md| LineParser::from(md).line()) {
self.lines_to_add[compound_arg.line_idx].push(line);
}
}
}
self
}
pub fn sub(&mut self, name: &'b str) -> &mut SubTemplateExpander<'s, 'b> {
let sub = SubTemplateExpander {
name,
raw_replacements: Vec::new(),
md_replacements: Vec::new(),
};
let idx = self.sub_expansions.len();
self.sub_expansions.push(sub);
&mut self.sub_expansions[idx]
}
pub fn expand(mut self) -> Text<'s> {
let mut defered_repls: Vec<Vec<Replacement<'_, '_>>> =
vec![Vec::new(); self.template.sub_templates.len()];
for compound_arg in self.template.compound_args.iter().rev() {
let line_idx = compound_arg.line_idx;
let sub_idx = self.template.get_sub_of_line(line_idx);
for md_repl in &self.md_replacements {
if md_repl.name == compound_arg.name {
if let Some(sub_idx) = sub_idx {
defered_repls[sub_idx].push(md_repl.clone());
} else {
let replacing_composite = Composite::from_inline(md_repl.value);
let patched_line = &mut self.text.lines[line_idx];
match patched_line {
Line::Normal(ref mut composite) => {
replace_compound(
composite,
compound_arg.compound_idx,
replacing_composite.compounds,
);
}
Line::TableRow(ref mut table_row) => {
replace_compound(
&mut table_row.cells[compound_arg.composite_idx],
compound_arg.compound_idx,
replacing_composite.compounds,
);
}
_ => {}
}
break;
}
}
}
}
for (sub_idx, sub_template) in self.template.sub_templates.iter().enumerate() {
let start = sub_template.start_line_idx;
let end = start + sub_template.line_count;
for idx in start..end {
self.lines_to_exclude[idx] = true;
}
for sub_expansion in self.sub_expansions.iter() {
if sub_expansion.name != sub_template.name {
continue;
}
let mut sub_text = Text::default();
for line in &self.text.lines[start..end] {
sub_text.lines.push(line.clone());
}
for repl in &sub_expansion.raw_replacements {
set_in_text(
&self.template,
&mut sub_text,
sub_template.start_line_idx,
Some(repl.name),
repl.value,
);
}
let mut md_replacements = sub_expansion.md_replacements.clone();
md_replacements.extend(defered_repls[sub_idx].clone());
set_all_md_in_text(
self.template,
&mut sub_text,
sub_template.start_line_idx,
&md_replacements,
);
for line in sub_text.lines.drain(..) {
self.lines_to_add[sub_template.start_line_idx].push(line);
}
}
}
let mut lines = Vec::new();
for (idx, line) in self.text.lines.drain(..).enumerate() {
if !self.lines_to_exclude[idx] {
lines.push(line);
}
for line in self.lines_to_add[idx].drain(..) {
lines.push(line);
}
}
Text { lines }
}
}