rnotify 0.2.0

Rnotify allows sending informative messages to various destinations such as Discord, Telegram and Mail through the command line.
Documentation
use crate::message::formatted_detail::{FormattedMessageComponent, FormattedMessageDetail, FormattedString, Style};
use crate::message::MessageDetail;

pub trait FormattedStringAppendable {
    fn append(&mut self, s: FormattedString) -> &mut Self;

    fn append_styled<S: ToString>(&mut self, s: S, style: Style) -> &mut Self {
        self.append(FormattedString::styled(s, style));
        self
    }

    fn append_plain<S: ToString>(&mut self, s: S) -> &mut Self {
        self.append(FormattedString::plain(s));
        self
    }
}

pub struct MessageDetailBuilder {
    contents: Vec<FormattedMessageComponent>,
    raw: String,
}

impl MessageDetailBuilder {
    pub fn new() -> Self {
        Self::with_raw(String::from("Raw not available"))
    }

    pub fn with_raw(raw: String) -> Self {
        Self {
            contents: vec![],
            raw,
        }
    }

    pub fn raw(&mut self, raw: String) -> &mut Self {
        self.raw = raw;
        self
    }

    pub fn section<F, S>(&mut self, name: S, apply: F) -> &mut Self
        where F: FnOnce(&mut SectionBuilder),
              S: ToString {
        let mut section = SectionBuilder {
            name: name.to_string(),
            contents: vec![],
        };
        apply(&mut section);
        self.contents.push(section.build());
        self
    }

    pub fn text_block<F>(&mut self, apply: F) -> &mut Self
        where F: FnOnce(&mut TextBlockBuilder) {
        let mut text_block = TextBlockBuilder::default();
        apply(&mut text_block);
        self.contents.push(text_block.build());
        self
    }

    pub fn build(self) -> MessageDetail {
        MessageDetail::Formatted(FormattedMessageDetail::new(self.raw, self.contents))
    }
}

#[derive(Default)]
pub struct TextBlockBuilder {
    contents: Vec<FormattedString>,
}

impl TextBlockBuilder {
    pub fn build(self) -> FormattedMessageComponent {
        FormattedMessageComponent::Text(self.contents)
    }

    pub fn build_vec(self) -> Vec<FormattedString> {
        self.contents
    }
}

impl FormattedStringAppendable for TextBlockBuilder {
    fn append(&mut self, s: FormattedString) -> &mut Self {
        self.contents.push(s);
        self
    }
}

pub struct SectionBuilder {
    name: String,
    contents: Vec<FormattedString>,
}

impl SectionBuilder {
    pub fn build(self) -> FormattedMessageComponent {
        FormattedMessageComponent::Section(self.name, self.contents)
    }
}

impl FormattedStringAppendable for SectionBuilder {
    fn append(&mut self, s: FormattedString) -> &mut Self {
        self.contents.push(s);
        self
    }
}

#[cfg(test)]
mod test {
    use crate::message::formatted_detail::{FormattedMessageComponent, FormattedMessageDetail, FormattedString, Style};
    use crate::message::detail_builder::{FormattedStringAppendable, MessageDetailBuilder};
    use crate::message::MessageDetail;

    #[test]
    fn test_detail_builder() {
        let mut builder = MessageDetailBuilder::new();
        builder.text_block(|block| {
                block.append_plain("Base Description");
            })
            .section("New section", |section| {
                section.append_styled("hello", Style::Monospace);
            });

        let built = builder.build();

        let test = MessageDetail::Formatted(FormattedMessageDetail::new(
            "Raw not available".to_string(),
            vec![
                FormattedMessageComponent::Text(vec![FormattedString::plain("Base Description".to_owned())]),
                FormattedMessageComponent::Section("New section".to_owned(),
                                                    vec![FormattedString::styled("hello", Style::Monospace)])]
        ));

        assert_eq!(built, test);
    }
}