use crate::{format::*, Result};
#[derive(Debug)]
pub struct NoFormatting;
impl Formatter for NoFormatting {
fn new() -> NoFormatting {
NoFormatting
}
fn check(&mut self, _: &SequenceState) -> FormatChanges {
FormatChanges::nothing()
}
}
#[derive(Debug)]
pub struct AlwaysIndentAlwaysLf(usize);
impl Formatter for AlwaysIndentAlwaysLf {
fn new() -> AlwaysIndentAlwaysLf {
AlwaysIndentAlwaysLf(DEFAULT_INDENT)
}
fn set_indent_step_size(&mut self, step_size: usize) {
self.0 = step_size;
}
fn get_indent_step_size(&self) -> usize {
self.0
}
fn reset_to_defaults(&mut self) {
self.0 = DEFAULT_INDENT;
}
fn check(&mut self, state: &SequenceState) -> FormatChanges {
if matches!(state.next.0, Sequence::Closing) {
match state.last.0 {
Sequence::Opening => FormatChanges::may_lf(true),
_ => FormatChanges::indent_less(state.indent, self.0),
}
} else {
match state.last.0 {
Sequence::Initial => FormatChanges::may_lf(true),
Sequence::Opening => match state.next.0 {
Sequence::SelfClosing | Sequence::Text | Sequence::Opening => {
FormatChanges::indent_more(state.indent, self.0)
}
_ => FormatChanges::nothing(),
},
Sequence::Closing => FormatChanges::may_lf(true),
Sequence::SelfClosing => FormatChanges::may_lf(true),
_ => FormatChanges::nothing(),
}
}
}
}
#[derive(Debug)]
pub struct AutoIndent {
pub fltr_indent_always: Vec<String>,
pub fltr_lf_always: Vec<String>,
pub fltr_lf_closing: Vec<String>,
indent_stack: Vec<bool>,
indent_step: usize,
}
impl AutoIndent {
fn check_other_filter(&self, tags: &[&str], fltr: FixedRule, other: FixedRule) -> Result<()> {
let errtags: Vec<String> = tags
.iter()
.filter(|t| self.is_ts_in_filter(&TagSequence::opening(t), other))
.map(|t| t.to_string())
.collect();
if errtags.is_empty() {
Ok(())
} else {
Err(format!(
"{}({:?}), {} {:?} too: {:?}",
"AutoIndent::add_tags_to_rule",
fltr,
"trying to add tags which have already been added to filter",
other,
errtags
)
.into())
}
}
fn is_ts_in_filter(&self, tagseq: &TagSequence, fltr: FixedRule) -> bool {
let fltr: &Vec<String> = match fltr {
FixedRule::IndentAlways => &self.fltr_indent_always,
FixedRule::LfAlways => &self.fltr_lf_always,
FixedRule::LfClosing => &self.fltr_lf_closing,
};
for tf in fltr.iter() {
if tf == &tagseq.1 {
return true;
}
}
false
}
fn is_ts_in_fltr_aot(&self, tagseq: &TagSequence, fltr: FixedRule, seq: Sequence) -> bool {
if tagseq.0 != seq {
return false;
}
self.is_ts_in_filter(tagseq, fltr)
}
}
impl Formatter for AutoIndent {
fn new() -> AutoIndent {
AutoIndent {
fltr_indent_always: Vec::new(),
fltr_lf_always: Vec::new(),
fltr_lf_closing: Vec::new(),
indent_stack: Vec::new(),
indent_step: DEFAULT_INDENT,
}
}
fn set_indent_step_size(&mut self, step_size: usize) {
self.indent_step = step_size;
}
fn get_indent_step_size(&self) -> usize {
self.indent_step
}
fn reset_to_defaults(&mut self) {
self.fltr_indent_always.clear();
self.fltr_lf_always.clear();
self.fltr_lf_closing.clear();
self.indent_step = DEFAULT_INDENT;
}
fn optional_fixed_ruleset(&mut self) -> Option<&mut dyn FixedRuleset> {
Some(self)
}
fn check(&mut self, state: &SequenceState) -> FormatChanges {
let mut changes = FormatChanges::nothing();
if matches!(state.next.0, Sequence::Closing) {
if matches!(state.last.0, Sequence::Opening)
&& self.is_ts_in_filter(&state.last, FixedRule::LfAlways)
{
changes = FormatChanges::lf();
} else if let Some(true) = self.indent_stack.pop() {
changes = FormatChanges::indent_less(state.indent, self.indent_step);
} else if self.is_ts_in_fltr_aot(
&state.last,
FixedRule::IndentAlways,
Sequence::Closing,
) || self.is_ts_in_filter(&state.last, FixedRule::LfAlways)
|| self.is_ts_in_fltr_aot(&state.last, FixedRule::LfClosing, Sequence::Closing)
|| self.is_ts_in_fltr_aot(&state.last, FixedRule::LfClosing, Sequence::SelfClosing)
{
changes = FormatChanges::lf();
}
} else {
match state.last.0 {
Sequence::Opening => {
let do_indent = (matches!(state.next.0, Sequence::LineFeed)
&& !self.is_ts_in_filter(&state.last, FixedRule::LfAlways))
|| self.is_ts_in_filter(&state.last, FixedRule::IndentAlways);
self.indent_stack.push(do_indent);
if do_indent {
changes = FormatChanges::indent_more(state.indent, self.indent_step);
} else if self.is_ts_in_filter(&state.last, FixedRule::LfAlways) {
changes = FormatChanges::lf();
}
}
Sequence::Closing => {
if self.is_ts_in_filter(&state.last, FixedRule::IndentAlways)
|| self.is_ts_in_filter(&state.last, FixedRule::LfAlways)
|| self.is_ts_in_filter(&state.last, FixedRule::LfClosing)
{
changes = FormatChanges::lf();
}
}
Sequence::SelfClosing => {
if self.is_ts_in_fltr_aot(
&state.last,
FixedRule::LfClosing,
Sequence::SelfClosing,
) {
changes = FormatChanges::lf();
}
}
Sequence::Initial => {
changes = FormatChanges::lf()
}
_ => {}
}
}
changes
}
}
impl FixedRuleset for AutoIndent {
fn add_tags_to_rule(&mut self, tags: &[&str], rule: FixedRule) -> Result<()> {
match rule {
FixedRule::IndentAlways => {
self.check_other_filter(tags, FixedRule::IndentAlways, FixedRule::LfAlways)?;
self.fltr_indent_always = tags.iter().map(|s| s.to_string()).collect();
}
FixedRule::LfAlways => {
self.check_other_filter(tags, FixedRule::LfAlways, FixedRule::IndentAlways)?;
self.check_other_filter(tags, FixedRule::LfAlways, FixedRule::LfClosing)?;
self.fltr_lf_always = tags.iter().map(|s| s.to_string()).collect();
}
FixedRule::LfClosing => {
self.check_other_filter(tags, FixedRule::LfClosing, FixedRule::LfAlways)?;
self.fltr_lf_closing = tags.iter().map(|s| s.to_string()).collect();
}
}
Ok(())
}
fn reset_ruleset(&mut self) -> Result<()> {
self.fltr_indent_always.clear();
self.fltr_lf_always.clear();
self.fltr_lf_closing.clear();
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use totems::assert_err;
const NOTHING: FormatChanges = FormatChanges {
new_line: false,
new_indent: None,
};
const LINEFEED: FormatChanges = FormatChanges {
new_line: true,
new_indent: None,
};
const INDENT_LESS: FormatChanges = FormatChanges {
new_line: true,
new_indent: Some(0),
};
const INDENT_MORE: FormatChanges = FormatChanges {
new_line: true,
new_indent: Some(8),
};
fn get_formatters_list() -> Vec<Box<dyn Formatter>> {
vec![
Box::new(NoFormatting::new()),
Box::new(AlwaysIndentAlwaysLf::new()),
Box::new(AutoIndent::new()),
]
}
#[test]
fn all_initially_default() {
for fmt in get_formatters_list().iter_mut() {
assert_eq!(fmt.get_indent_step_size(), DEFAULT_INDENT);
}
}
#[test]
fn after_reset_default_again() {
for fmt in get_formatters_list().iter_mut() {
fmt.set_indent_step_size(DEFAULT_INDENT + 1);
fmt.reset_to_defaults();
assert_eq!(fmt.get_indent_step_size(), DEFAULT_INDENT);
}
}
#[test]
fn auto_indenting_after_initial() {
let mut fmtr = Box::new(AutoIndent::new());
assert_eq!(fmtr.check(&SequenceState::initial_open("html")), LINEFEED);
}
#[test]
fn auto_indenting_rule_always_indent() {
let mut fmtr = Box::new(AutoIndent::new());
fmtr.add_tags_to_rule(&["html"], FixedRule::IndentAlways)
.unwrap();
assert_eq!(fmtr.check(&SequenceState::open_lf("body")), INDENT_MORE);
assert_eq!(fmtr.check(&SequenceState::lf_self_closing("img")), NOTHING);
assert_eq!(
fmtr.check(&SequenceState::self_closing_close("img", "body")),
INDENT_LESS
);
assert_eq!(fmtr.check(&SequenceState::open_lf("html")), INDENT_MORE);
assert_eq!(fmtr.check(&SequenceState::lf_self_closing("img")), NOTHING);
assert_eq!(
fmtr.check(&SequenceState::self_closing_close("img", "html")),
INDENT_LESS
);
assert_eq!(
fmtr.check(&SequenceState::open_self_closing("html", "img")),
INDENT_MORE
);
assert_eq!(
fmtr.check(&SequenceState::self_closing_close("img", "html")),
INDENT_LESS
);
assert_eq!(
fmtr.check(&SequenceState::open_open("html", "body")),
INDENT_MORE
);
assert_eq!(fmtr.check(&SequenceState::open_lf("body")), INDENT_MORE);
assert_eq!(fmtr.check(&SequenceState::lf_self_closing("img")), NOTHING);
assert_eq!(
fmtr.check(&SequenceState::self_closing_close("img", "body")),
INDENT_LESS
);
assert_eq!(
fmtr.check(&SequenceState::close_close("body", "html")),
INDENT_LESS
);
}
#[test]
fn auto_indenting_rule_lf_always() {
let mut fmtr = Box::new(AutoIndent::new());
fmtr.add_tags_to_rule(&["html"], FixedRule::LfAlways)
.unwrap();
assert_eq!(
fmtr.check(&SequenceState::open_self_closing("html", "img")),
LINEFEED
);
assert_eq!(
fmtr.check(&SequenceState::self_closing_close("img", "html")),
NOTHING
);
assert_eq!(fmtr.check(&SequenceState::close_text("html")), LINEFEED);
assert_eq!(
fmtr.check(&SequenceState::open_self_closing("body", "img")),
NOTHING
);
assert_eq!(
fmtr.check(&SequenceState::self_closing_close("img", "body")),
NOTHING
);
assert_eq!(fmtr.check(&SequenceState::close_text("body")), NOTHING);
assert_eq!(fmtr.check(&SequenceState::open_lf("html")), LINEFEED);
assert_eq!(fmtr.check(&SequenceState::lf_self_closing("img")), NOTHING);
assert_eq!(
fmtr.check(&SequenceState::self_closing_close("img", "html")),
NOTHING
);
assert_eq!(fmtr.check(&SequenceState::close_text("html")), LINEFEED);
assert_eq!(fmtr.check(&SequenceState::open_lf("body")), INDENT_MORE);
assert_eq!(fmtr.check(&SequenceState::lf_self_closing("img")), NOTHING);
assert_eq!(
fmtr.check(&SequenceState::self_closing_close("img", "body")),
INDENT_LESS
);
assert_eq!(fmtr.check(&SequenceState::close_text("body")), NOTHING);
}
#[test]
fn auto_indenting_rule_lf_closing() {
let mut fmtr = Box::new(AutoIndent::new());
fmtr.add_tags_to_rule(&["html", "img"], FixedRule::LfClosing)
.unwrap();
assert_eq!(
fmtr.check(&SequenceState::open_close("html", "html")),
NOTHING
);
assert_eq!(fmtr.check(&SequenceState::open_text("html")), NOTHING);
assert_eq!(fmtr.check(&SequenceState::text_close("html")), NOTHING);
assert_eq!(fmtr.check(&SequenceState::close_text("html")), LINEFEED);
assert_eq!(fmtr.check(&SequenceState::open_text("body")), NOTHING);
assert_eq!(fmtr.check(&SequenceState::text_close("body")), NOTHING);
assert_eq!(fmtr.check(&SequenceState::close_text("body")), NOTHING);
assert_eq!(fmtr.check(&SequenceState::open_lf("body")), INDENT_MORE);
assert_eq!(fmtr.check(&SequenceState::lf_self_closing("link")), NOTHING);
assert_eq!(
fmtr.check(&SequenceState::self_closing_close("link", "body")),
INDENT_LESS
);
assert_eq!(fmtr.check(&SequenceState::close_text("body")), NOTHING);
assert_eq!(fmtr.check(&SequenceState::open_lf("html")), INDENT_MORE);
assert_eq!(fmtr.check(&SequenceState::lf_self_closing("link")), NOTHING);
assert_eq!(
fmtr.check(&SequenceState::self_closing_close("link", "html")),
INDENT_LESS
);
assert_eq!(fmtr.check(&SequenceState::close_text("html")), LINEFEED);
assert_eq!(
fmtr.check(&SequenceState::open_self_closing("div", "img")),
NOTHING
);
assert_eq!(
fmtr.check(&SequenceState::self_closing_close("img", "div")),
LINEFEED
);
assert_eq!(fmtr.check(&SequenceState::close_text("div")), NOTHING);
}
#[test]
fn auto_indenting_mixed_rules() {
let mut fmtr = Box::new(AutoIndent::new());
fmtr.add_tags_to_rule(&["html", "body"], FixedRule::IndentAlways)
.unwrap();
assert_err!(fmtr.add_tags_to_rule(&["html", "head"], FixedRule::LfAlways));
fmtr.add_tags_to_rule(&["html", "body"], FixedRule::LfClosing)
.unwrap();
fmtr.reset_to_defaults();
fmtr.add_tags_to_rule(&["html", "body"], FixedRule::LfAlways)
.unwrap();
assert_err!(fmtr.add_tags_to_rule(&["html", "head"], FixedRule::IndentAlways));
assert_err!(fmtr.add_tags_to_rule(&["body", "header"], FixedRule::LfClosing));
fmtr.reset_to_defaults();
fmtr.add_tags_to_rule(&["head", "body"], FixedRule::IndentAlways)
.unwrap();
fmtr.add_tags_to_rule(&["html"], FixedRule::LfAlways)
.unwrap();
fmtr.add_tags_to_rule(&["body", "div"], FixedRule::LfClosing)
.unwrap();
assert_eq!(
fmtr.check(&SequenceState::open_open("html", "head")),
LINEFEED
);
assert_eq!(
fmtr.check(&SequenceState::open_self_closing("head", "link")),
INDENT_MORE
);
assert_eq!(
fmtr.check(&SequenceState::self_closing_close("link", "head")),
INDENT_LESS
);
assert_eq!(
fmtr.check(&SequenceState::close_close("head", "html")),
LINEFEED
);
assert_eq!(fmtr.check(&SequenceState::close_text("html")), LINEFEED);
assert_eq!(
fmtr.check(&SequenceState::open_open("body", "div")),
INDENT_MORE
);
assert_eq!(fmtr.check(&SequenceState::open_text("div")), NOTHING);
assert_eq!(fmtr.check(&SequenceState::text_close("div")), NOTHING);
assert_eq!(
fmtr.check(&SequenceState::close_close("div", "body")),
INDENT_LESS
);
assert_eq!(fmtr.check(&SequenceState::close_text("body")), LINEFEED);
assert_eq!(
fmtr.check(&SequenceState::open_self_closing("body", "img")),
INDENT_MORE
);
assert_eq!(
fmtr.check(&SequenceState::self_closing_close("img", "body")),
INDENT_LESS
);
assert_eq!(fmtr.check(&SequenceState::close_text("body")), LINEFEED);
}
}