email/email/message/template/
mod.rs

1//! # Template
2//!
3//! A template is a simplified version of an email MIME message, based
4//! on [MML](https://www.gnu.org/software/emacs/manual/html_node/emacs-mime/Composing.html).
5
6pub mod config;
7pub mod forward;
8pub mod new;
9pub mod reply;
10
11use std::{
12    borrow::Cow,
13    fmt,
14    ops::{Deref, DerefMut},
15};
16
17pub use mml::{
18    message::{FilterHeaders, FilterParts},
19    MimeInterpreter,
20};
21
22#[derive(Clone, Debug, Default, Eq, PartialEq)]
23#[cfg_attr(
24    feature = "derive",
25    derive(serde::Serialize, serde::Deserialize),
26    serde(rename_all = "kebab-case")
27)]
28pub struct Template {
29    pub content: String,
30    pub cursor: TemplateCursor,
31}
32
33impl Template {
34    pub fn new(content: impl ToString) -> Self {
35        Self::new_with_cursor(content, TemplateCursor::default())
36    }
37
38    pub fn new_with_cursor(content: impl ToString, cursor: impl Into<TemplateCursor>) -> Self {
39        let content = content.to_string();
40        let cursor = cursor.into();
41        Self { content, cursor }
42    }
43
44    pub fn append(&mut self, section: impl AsRef<str>) {
45        if !self.content.is_empty() {
46            self.content.push_str(section.as_ref())
47        }
48    }
49}
50
51impl Deref for Template {
52    type Target = String;
53
54    fn deref(&self) -> &Self::Target {
55        &self.content
56    }
57}
58
59impl DerefMut for Template {
60    fn deref_mut(&mut self) -> &mut Self::Target {
61        &mut self.content
62    }
63}
64
65impl From<String> for Template {
66    fn from(s: String) -> Self {
67        Self::new(s)
68    }
69}
70
71impl fmt::Display for Template {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        write!(f, "{}", self.content)
74    }
75}
76
77#[derive(Clone, Debug, Eq)]
78#[cfg_attr(
79    feature = "derive",
80    derive(serde::Serialize, serde::Deserialize),
81    serde(rename_all = "kebab-case")
82)]
83pub struct TemplateCursor {
84    pub row: usize,
85    pub col: usize,
86
87    #[cfg_attr(feature = "derive", serde(skip))]
88    locked: bool,
89}
90
91impl TemplateCursor {
92    pub fn new(row: usize, col: usize) -> Self {
93        Self {
94            row,
95            col,
96            locked: false,
97        }
98    }
99
100    pub fn lock(&mut self) {
101        self.locked = true;
102    }
103
104    pub fn is_locked(&self) -> bool {
105        self.locked
106    }
107}
108
109impl Default for TemplateCursor {
110    fn default() -> Self {
111        Self {
112            row: 1,
113            col: 0,
114            locked: false,
115        }
116    }
117}
118
119impl PartialEq for TemplateCursor {
120    fn eq(&self, other: &Self) -> bool {
121        self.row == other.row && self.col == other.col
122    }
123}
124
125impl From<(usize, usize)> for TemplateCursor {
126    fn from((row, col): (usize, usize)) -> Self {
127        TemplateCursor::new(row, col)
128    }
129}
130
131#[derive(Debug, Eq, PartialEq)]
132pub struct TemplateBody {
133    content: String,
134    buffer: String,
135    cursor: TemplateCursor,
136    push_new_lines: bool,
137}
138
139impl TemplateBody {
140    pub fn new(mut cursor: TemplateCursor) -> Self {
141        cursor.row += 1;
142
143        Self {
144            content: Default::default(),
145            buffer: Default::default(),
146            cursor,
147            push_new_lines: false,
148        }
149    }
150
151    pub fn flush(&mut self) {
152        let mut buffer = String::new();
153
154        if self.push_new_lines {
155            buffer.push_str("\n\n");
156        } else {
157            self.push_new_lines = true;
158        };
159
160        buffer.push_str(self.buffer.drain(..).as_str());
161
162        if !self.cursor.is_locked() {
163            match buffer.rsplit_once('\n') {
164                Some((left, right)) => {
165                    // NOTE: left.lines().count() does not distinguish
166                    // "hello" from "hello\n" (returns 1)
167                    let left_lines_count = left
168                        .chars()
169                        .fold(1, |count, c| count + if c == '\n' { 1 } else { 0 });
170
171                    self.cursor.row += left_lines_count;
172                    self.cursor.col = right.len();
173                }
174                None => {
175                    self.cursor.col += buffer.len();
176                }
177            }
178        }
179
180        self.content.push_str(&buffer)
181    }
182}
183
184impl Deref for TemplateBody {
185    type Target = String;
186
187    fn deref(&self) -> &Self::Target {
188        &self.buffer
189    }
190}
191
192impl DerefMut for TemplateBody {
193    fn deref_mut(&mut self) -> &mut Self::Target {
194        &mut self.buffer
195    }
196}
197
198impl From<TemplateBody> for Cow<'_, str> {
199    fn from(value: TemplateBody) -> Self {
200        value.content.into()
201    }
202}