lunar-lib 0.8.0

Common utilities for lunar applications
Documentation
use std::fmt::{self, Write};

use crate::formatter::{
    ErrorKind, Render, TemplateError,
    condition::{Condition, ConditionalToken},
    template::TemplateItem,
};

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Block<'a> {
    content: Vec<TemplateItem<'a>>,
    prefix: Vec<TemplateItem<'a>>,
    postfix: Vec<TemplateItem<'a>>,
    fallback: Vec<TemplateItem<'a>>,
    condition: Option<Condition<'a>>,
}

impl<'a> Render for Block<'a> {
    fn render(&self, format_table: &super::FormatTable) -> String {
        if let Some(condition) = &self.condition
            && !condition.solve(format_table)
        {
            return String::new();
        }

        let mut content = self.content.render(format_table);

        if content.is_empty() {
            let fallback = self.fallback.render(format_table);

            if fallback.is_empty() {
                return String::new();
            }

            content = fallback
        }

        let mut prefix = self.prefix.render(format_table);
        prefix.push_str(&content);
        prefix.push_str(&self.postfix.render(format_table));

        prefix
    }
}

#[derive(Debug, PartialEq, Eq)]
pub(super) enum BlockMode {
    Content,
    Prefix,
    Suffix,
    Fallback,
    Condition,
}

impl BlockMode {
    pub fn to_str(&self) -> &'static str {
        match self {
            BlockMode::Content => panic!(
                "Attempted to turn BlockMode::Content into a &str. This mode should never be set, only defaulted to"
            ),
            BlockMode::Prefix => "<",
            BlockMode::Suffix => ">",
            BlockMode::Fallback => "?",
            BlockMode::Condition => "@",
        }
    }
}

pub(super) struct BlockBuilder<'a> {
    content: Vec<TemplateItem<'a>>,
    prefix: Vec<TemplateItem<'a>>,
    postfix: Vec<TemplateItem<'a>>,
    fallback: Vec<TemplateItem<'a>>,
    condition: Option<Condition<'a>>,
    mode: Vec<BlockMode>,
}

impl<'a> Default for BlockBuilder<'a> {
    fn default() -> Self {
        Self {
            content: Default::default(),
            prefix: Default::default(),
            postfix: Default::default(),
            fallback: Default::default(),
            condition: Default::default(),
            mode: vec![BlockMode::Content],
        }
    }
}

impl<'a> BlockBuilder<'a> {
    pub(super) fn build(self) -> Block<'a> {
        Block {
            content: self.content,
            prefix: self.prefix,
            postfix: self.postfix,
            fallback: self.fallback,
            condition: self.condition,
        }
    }

    pub(super) fn set_mode(&mut self, mode: BlockMode) -> Result<(), TemplateError> {
        if self.mode.contains(&mode) {
            Err(TemplateError::new(ErrorKind::DoubleDefinition(
                mode.to_str(),
            )))
        } else {
            self.mode.push(mode);
            Ok(())
        }
    }

    pub(super) fn push_arg(&mut self, arg: TemplateItem<'a>) {
        let arg_list = match self.mode.last().unwrap() {
            BlockMode::Content => &mut self.content,
            BlockMode::Prefix => &mut self.prefix,
            BlockMode::Suffix => &mut self.postfix,
            BlockMode::Fallback => &mut self.fallback,
            BlockMode::Condition => {
                let condition = self.condition.get_or_insert(Condition::default());
                condition.tokens.push(ConditionalToken::Arg(arg));
                return;
            }
        };

        arg_list.push(arg);
    }

    pub(super) fn push_conditional_token(&mut self, token: ConditionalToken<'a>) {
        match self.mode.last().unwrap() {
            BlockMode::Condition => {
                let condition = self.condition.get_or_insert(Condition::default());
                condition.tokens.push(token);
            }
            _ => self.push_arg(TemplateItem::Text(token.to_str())),
        }
    }
}

impl fmt::Display for Block<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_char('{')?;

        for arg in &self.content {
            arg.fmt(f)?;
        }
        if let Some(condition) = &self.condition {
            f.write_char('@')?;
            condition.fmt(f)?;
        }
        if !self.prefix.is_empty() {
            f.write_char('<')?;
            for arg in &self.prefix {
                arg.fmt(f)?;
            }
        }
        if !self.postfix.is_empty() {
            f.write_char('>')?;
            for arg in &self.postfix {
                arg.fmt(f)?;
            }
        }
        if !self.fallback.is_empty() {
            f.write_char('?')?;
            for arg in &self.fallback {
                arg.fmt(f)?;
            }
        }

        f.write_char('}')
    }
}