use crate::parser::{segment::Segment, template::Template};
use std::fmt;
pub enum IteratorLocation {
First,
Nth(usize),
Last,
Only,
}
fn replace_chars_with_whitespace(line: &str) -> String {
let mut out = String::with_capacity(line.len());
for c in line.chars() {
match c {
'\t' => out.push('\t'),
_ => out.push(' '),
}
}
out
}
#[derive(Clone, PartialEq, Debug)]
pub enum LineSegment {
Content(String),
Placeholder(String),
Block(Block),
EndOfInput,
}
impl LineSegment {
fn write_to(&self, f: &mut fmt::Formatter, prefix: &str) -> fmt::Result {
match self {
LineSegment::Content(s) => write!(f, "{}", s),
LineSegment::Placeholder(s) => write!(f, "${{{}}}", s),
LineSegment::Block(b) => {
let prefix = replace_chars_with_whitespace(prefix);
b.write_to(f, &prefix)
}
LineSegment::EndOfInput => Ok(()),
}
}
fn replace(&mut self, new_segment: LineSegment) {
match self.clone() {
LineSegment::Placeholder(_) => {
*self = new_segment;
}
_ => (),
}
}
}
impl<T: Into<String>> From<T> for LineSegment {
fn from(v: T) -> Self {
LineSegment::Content(v.into())
}
}
#[derive(Clone, PartialEq, Debug)]
pub struct Line(pub Vec<LineSegment>);
impl<T: Into<String>> From<T> for Line {
fn from(v: T) -> Self {
Line(vec![LineSegment::Content(v.into())])
}
}
impl Line {
fn write_to(&self, f: &mut fmt::Formatter, prefix: &str) -> fmt::Result {
let mut sub_prefix = String::from(prefix);
for segment in &self.0 {
match segment {
LineSegment::Content(x) => sub_prefix = sub_prefix + x,
_ => (),
}
segment.write_to(f, &sub_prefix)?;
}
Ok(())
}
pub fn set(&mut self, placeholder_name: &str, content: &Block) {
for segment in &mut self.0 {
match segment.clone() {
LineSegment::Placeholder(ref name) if name == placeholder_name => {
segment.replace(LineSegment::Block(content.clone()));
}
_ => (),
}
}
}
}
#[derive(Clone, PartialEq, Debug)]
pub struct Block(pub Vec<Line>);
impl From<&Block> for String {
fn from(v: &Block) -> Self {
v.into()
}
}
impl<T: Into<String>> From<T> for Block {
fn from(v: T) -> Self {
Block(vec![Line::from(v.into())])
}
}
impl fmt::Display for Block {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.write_to(f, "")
}
}
impl Block {
pub fn empty() -> Self {
Block(vec![])
}
pub fn write_to(&self, f: &mut fmt::Formatter, prefix: &str) -> fmt::Result {
let mut first_line = true;
for line in &self.0 {
if !first_line {
write!(f, "\n{}", prefix).unwrap();
}
first_line = false;
line.write_to(f, prefix)?;
}
Ok(())
}
pub fn set<T: Into<Block>>(mut self, placeholder_name: &str, content: T) -> Self {
let content: &Block = &content.into();
for line in &mut self.0 {
line.set(placeholder_name, content);
}
self
}
pub fn join_map<T, U, F>(iter: T, mapper: F) -> Self
where
T: IntoIterator<Item = U>,
F: Fn(U, IteratorLocation) -> Block,
{
let items: Vec<U> = iter.into_iter().collect();
let count = items.len();
match count {
0 => Block::empty(),
1 => mapper(items.into_iter().next().unwrap(), IteratorLocation::Only),
count => Block::join(items.into_iter().enumerate().map(|(i, item)| {
let loc = match (i, count) {
(0, _) => IteratorLocation::First,
(x, y) if x == y - 1 => IteratorLocation::Last,
(x, _) => IteratorLocation::Nth(x),
};
mapper(item, loc)
})),
}
}
#[deprecated]
pub fn for_each<T, U, F>(self, iter: T, mapper: F) -> Self
where
T: IntoIterator<Item = U>,
F: Fn(U, Block) -> Block,
{
Block::join(iter.into_iter().map(|item| mapper(item, self.clone())))
}
pub fn join<T>(blocks: T) -> Block
where
T: IntoIterator<Item = Block>,
{
Block(
blocks
.into_iter()
.map(|block| Line(vec![LineSegment::Block(block)]))
.collect(),
)
}
}
impl<'a> From<&'a Template> for Block {
fn from(t: &'a Template) -> Self {
let mut lines: Vec<Line> = Vec::with_capacity(t.lines.len());
let indent_ignored = t.indent_ignored;
for template_line in &t.lines {
let mut segments: Vec<LineSegment> =
Vec::with_capacity(template_line.segments.len() + 1);
let indentation_len = template_line.indentation.len();
if indentation_len > indent_ignored {
let indentation: &str = &template_line.indentation[indent_ignored..];
segments.push(LineSegment::Content(String::from(indentation)));
}
for template_segment in &template_line.segments {
segments.push(match template_segment {
Segment::Placeholder(x) => LineSegment::Placeholder(x.clone()),
Segment::Content(x) => LineSegment::Content(x.clone()),
})
}
lines.push(Line(segments));
}
Block(lines)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn outputs_a_block_with_correct_indentation() {
use insta::assert_snapshot_matches;
use std::fmt::Write;
let arg_list = Block(vec![
Line(vec![LineSegment::from("arg1: string,")]),
Line(vec![LineSegment::from("arg2: number,")]),
Line(vec![LineSegment::from("arg3: Object")]),
]);
let function_body = Block(vec![
Line(vec![LineSegment::from("body();")]),
Line(vec![LineSegment::from("body2();")]),
]);
let function = Block(vec![
Line(vec![
LineSegment::from("function test("),
LineSegment::Block(arg_list),
LineSegment::from(") {"),
]),
Line(vec![
LineSegment::from(" "),
LineSegment::Block(function_body.clone()),
]),
Line(vec![LineSegment::from("}")]),
]);
let mut s = String::new();
write!(&mut s, "{}", function).unwrap();
assert_snapshot_matches!("block.outputs_a_block_with_correct_indentation", s);
}
#[test]
fn replaces_a_placeholder() {
use insta::assert_debug_snapshot_matches;
let block = Block(vec![Line(vec![
LineSegment::from("A"),
LineSegment::Placeholder(String::from("x")),
LineSegment::from("C"),
])]);
let block = block.set("x", Block(vec![Line(vec![LineSegment::from("B")])]));
assert_debug_snapshot_matches!("block.replaces_a_placeholder", block);
}
}