use crate::{
format::{FormatChanges, Formatter, Sequence, SequenceState, TagSequence},
syntax::{Language, SyntaxConfig},
};
use std::fmt::Write;
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
#[derive(Debug)]
pub struct MarkupSth<'d> {
pub syntax: SyntaxConfig,
pub formatter: Box<dyn Formatter>,
seq_state: SequenceState,
indent_str: String,
document: &'d mut String,
}
macro_rules! final_op_arm {
(selfclosing $self:expr) => {{
$self.document.write_fmt(format_args!(
"{}",
$self.syntax.self_closing.as_ref().unwrap().after
))?;
}};
(opening $self:expr) => {{
$self.document.write_fmt(format_args!(
"{}",
$self.syntax.tag_pairs.as_ref().unwrap().opening_after
))?;
}};
(closing $self:expr) => {{
$self.document.write_fmt(format_args!(
"{}",
$self.syntax.tag_pairs.as_ref().unwrap().closing_after
))?;
}};
}
pub(crate) use final_op_arm;
impl<'d> MarkupSth<'d> {
pub fn new(document: &'d mut String, ml: Language) -> Result<MarkupSth<'d>> {
Ok(MarkupSth {
syntax: SyntaxConfig::from(ml),
formatter: Box::new(crate::formatters::AutoIndent::new()),
seq_state: SequenceState::new(),
indent_str: String::new(),
document,
})
}
pub fn set_formatter(&mut self, formatter: Box<dyn Formatter>) {
self.formatter = formatter;
}
pub fn self_closing(&mut self, tag: &str) -> Result<()> {
self.finalize_last_op(TagSequence::self_closing(tag))?;
if let Some(cfg) = &self.syntax.self_closing {
self.document
.write_fmt(format_args!("{}{}", cfg.before, tag))?;
Ok(())
} else {
Err("MarkupSth: in this syntaxuration are no self-closing tag elements allowed".into())
}
}
pub fn open(&mut self, tag: &str) -> Result<()> {
self.finalize_last_op(TagSequence::opening(tag))?;
if let Some(cfg) = &self.syntax.tag_pairs {
self.document
.write_fmt(format_args!("{}{}", cfg.opening_before, tag))?;
self.seq_state.tag_stack.push(tag.to_string());
Ok(())
} else {
Err("MarkupSth: in this syntaxuration are no tag-pair element allowed".into())
}
}
pub fn close(&mut self) -> Result<()> {
if self.syntax.tag_pairs.is_none() {
return Err("MarkupSth: in this syntaxuration are no tag-pair element allowed".into());
}
if self.seq_state.tag_stack.is_empty() {
return Err("MarkupSth: tag-pair tag_stack error".into());
}
let tag = self.seq_state.tag_stack.pop().unwrap();
self.finalize_last_op(TagSequence::closing(&tag))?;
let cfg = self.syntax.tag_pairs.as_ref().unwrap();
self.document
.write_fmt(format_args!("{}{}", cfg.closing_before, &tag))?;
Ok(())
}
pub fn open_close_w(&mut self, tag: &str, content: &str) -> Result<()> {
self.open(tag)?;
self.text(content)?;
self.close()?;
Ok(())
}
pub fn properties(&mut self, properties: &[(&str, &str)]) -> Result<()> {
if !matches!(
self.seq_state.last.0,
Sequence::SelfClosing | Sequence::Opening
) {
return Err(
"MarkupSth: properties can only be added to self-closing or opening tags".into(),
);
}
if let Some(cfg) = &self.syntax.properties {
self.document.write_fmt(format_args!("{}", cfg.initiator))?;
let len = properties.len();
for property in properties[..len - 1].iter() {
self.document.write_fmt(format_args!(
"{}{}{}{}{}{}{}{}",
cfg.name_before,
property.0,
cfg.name_after,
cfg.name_separator,
cfg.value_before,
property.1,
cfg.value_after,
cfg.value_separator
))?;
}
let len = len - 1;
self.document.write_fmt(format_args!(
"{}{}{}{}{}{}{}",
cfg.name_before,
properties[len].0,
cfg.name_after,
cfg.name_separator,
cfg.value_before,
properties[len].1,
cfg.value_after,
))?;
Ok(())
} else {
Err("MarkupSth: in this syntaxuration are no properties in tag elements allowed".into())
}
}
pub fn text(&mut self, text: &str) -> Result<()> {
self.finalize_last_op(TagSequence::text())?;
self.document.write_str(text)?;
Ok(())
}
pub fn new_line(&mut self) -> Result<()> {
self.finalize_last_op(TagSequence::linefeed())?;
Ok(())
}
pub fn indent_more(&mut self) -> Result<()> {
self.apply_format_changes(FormatChanges::indent_more(
self.seq_state.indent,
self.formatter.get_indent_step_size(),
))?;
Ok(())
}
pub fn indent_less(&mut self) -> Result<()> {
self.apply_format_changes(FormatChanges::indent_less(
self.seq_state.indent,
self.formatter.get_indent_step_size(),
))?;
Ok(())
}
fn new_line_internal(&mut self) -> Result<()> {
self.document
.write_fmt(format_args!("\n{}", self.indent_str))?;
Ok(())
}
pub fn close_all(&mut self) -> Result<()> {
for _ in 0..self.seq_state.tag_stack.len() {
self.close()?;
}
Ok(())
}
pub fn finalize(self) -> Result<()> {
match self.seq_state.last.0 {
Sequence::SelfClosing => final_op_arm!(selfclosing self),
Sequence::Opening => final_op_arm!(opening self),
Sequence::Closing => final_op_arm!(closing self),
_ => {}
}
Ok(())
}
fn finalize_last_op(&mut self, next: TagSequence) -> Result<()> {
match self.seq_state.last.0 {
Sequence::Initial => {
if let Some(dt) = self.syntax.doctype.as_ref() {
self.document.write_str(dt)?;
}
}
Sequence::SelfClosing => final_op_arm!(selfclosing self),
Sequence::Opening => final_op_arm!(opening self),
Sequence::Closing => final_op_arm!(closing self),
Sequence::Text | Sequence::LineFeed => {}
}
self.seq_state.next = next.clone();
let check = self.formatter.check(&self.seq_state);
self.apply_format_changes(check)?;
self.seq_state.last = next;
Ok(())
}
fn apply_format_changes(&mut self, changes: FormatChanges) -> Result<()> {
if let Some(indent) = changes.new_indent {
self.indent_str = " ".repeat(indent);
self.seq_state.indent = indent;
}
if changes.new_line {
self.new_line_internal()?;
}
Ok(())
}
}
#[macro_export]
macro_rules! properties {
($markup:expr, $($name:literal, $value:literal),*) => {{
$markup.properties(&[$(($name, $value)),*])
}};
}