tpnote_lib/
template.rs

1//!Abstractions for content templates and filename templates.
2use crate::filename::NotePath;
3use crate::settings::SETTINGS;
4use crate::{config::LIB_CFG, content::Content};
5use std::path::Path;
6
7/// Each workflow is related to one `TemplateKind`, which relates to one
8/// content template and one filename template.
9#[non_exhaustive]
10#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
11pub enum TemplateKind {
12    /// Templates used when Tp-Note is invoked with a directory path.
13    FromDir,
14    /// Templates used when the clipboard contains a text with a YAML header.
15    FromClipboardYaml,
16    /// Templates used when the clipboard contains a text without header.
17    FromClipboard,
18    /// Templates used when Tp-Note is invoked with a path pointing to a text file
19    /// that does not contain a YAML header.
20    FromTextFile,
21    /// Templates used when Tp-Note is invoked with a path pointing to a non text
22    /// file.
23    AnnotateFile,
24    /// Templates used when Tp-Note is invoked with a path pointing to a Tp-Note
25    /// text file with a valid YAML header (with a `title:` field).
26    SyncFilename,
27    /// No templates are used, but the file is still parsed in order to render it
28    /// later to HTML (c.f. `<Note>.render_content_to_html()` and
29    /// `<Note>.export_html()`).
30    #[default]
31    None,
32}
33
34impl TemplateKind {
35    /// A constructor returning the tuple `(template_kind, Some(content))`.
36    /// `template_kind` is the result of the logic calculating under what
37    /// circumstances what template should be used.
38    /// If `path` has a Tp-Note extension (e.g. `.md`) and the file indicated by
39    /// `path` could be opened and loaed from disk, `Some(content)` contains
40    /// its content. Otherwise `None` is returned.
41    pub fn from<T: Content>(
42        path: &Path,
43        html_clipboard: &T,
44        txt_clipboard: &T,
45        stdin: &T,
46    ) -> (Self, Option<T>) {
47        let stdin_is_empty = stdin.is_empty();
48        let stdin_has_header = !stdin.header().is_empty();
49
50        let clipboard_is_empty = html_clipboard.is_empty() && txt_clipboard.is_empty();
51        let clipboard_has_header =
52            !html_clipboard.header().is_empty() || !txt_clipboard.header().is_empty();
53
54        let input_stream_is_some = !stdin_is_empty || !clipboard_is_empty;
55        let input_stream_has_header = stdin_has_header || clipboard_has_header;
56
57        let path_is_dir = path.is_dir();
58        let path_is_file = path.is_file();
59
60        let path_has_tpnote_extension = path.has_tpnote_ext();
61        let path_is_tpnote_file = path_is_file && path_has_tpnote_extension;
62
63        let (path_is_tpnote_file_and_has_header, content) = if path_is_tpnote_file {
64            let content: T = Content::open(path).unwrap_or_default();
65            (!content.header().is_empty(), Some(content))
66        } else {
67            (false, None)
68        };
69
70        // This determines the workflow and what template will be applied.
71        let template_kind = match (
72            path_is_dir,
73            input_stream_is_some,
74            input_stream_has_header,
75            path_is_file,
76            path_is_tpnote_file,
77            path_is_tpnote_file_and_has_header,
78        ) {
79            (true, false, _, false, _, _) => TemplateKind::FromDir,
80            (true, true, false, false, _, _) => TemplateKind::FromClipboard,
81            (true, true, true, false, _, _) => TemplateKind::FromClipboardYaml,
82            (false, _, _, true, true, true) => TemplateKind::SyncFilename,
83            (false, _, _, true, true, false) => TemplateKind::FromTextFile,
84            (false, _, _, true, false, _) => TemplateKind::AnnotateFile,
85            (_, _, _, _, _, _) => TemplateKind::None,
86        };
87
88        log::debug!("Choosing the \"{:?}\" template.", template_kind);
89
90        log::trace!(
91            "Template choice is based on:
92             path=\"{}\",
93             path_is_dir={},
94             input_stream_is_some={},
95             input_stream_has_header={},
96             path_is_file={},
97             path_is_tpnote_file={},
98             path_is_tpnote_file_and_has_header={}",
99            path.to_str().unwrap(),
100            path_is_dir,
101            input_stream_is_some,
102            input_stream_has_header,
103            path_is_file,
104            path_is_tpnote_file,
105            path_is_tpnote_file_and_has_header,
106        );
107        (template_kind, content)
108    }
109
110    /// Returns the content template string as it is defined in the configuration file.
111    /// Panics for `TemplateKind::SyncFilename` and `TemplateKind::None`.
112    pub fn get_content_template(&self) -> String {
113        let lib_cfg = LIB_CFG.read_recursive();
114        let scheme_idx = SETTINGS.read_recursive().current_scheme;
115        log::trace!(
116            "Scheme index: {}, applying the content template: `{}`",
117            scheme_idx,
118            self.get_content_template_name()
119        );
120        let tmpl = &lib_cfg.scheme[scheme_idx].tmpl;
121
122        match self {
123            Self::FromDir => tmpl.from_dir_content.clone(),
124            Self::FromClipboardYaml => tmpl.from_clipboard_yaml_content.clone(),
125            Self::FromClipboard => tmpl.from_clipboard_content.clone(),
126            Self::FromTextFile => tmpl.from_text_file_content.clone(),
127            Self::AnnotateFile => tmpl.annotate_file_content.clone(),
128            Self::SyncFilename => {
129                panic!("`TemplateKind::SyncFilename` has no content template")
130            }
131            Self::None => panic!("`TemplateKind::None` has no content template"),
132        }
133    }
134
135    /// Returns the content template variable name as it is used in the configuration file.
136    pub fn get_content_template_name(&self) -> &str {
137        match self {
138            Self::FromDir => "tmpl.from_dir_content",
139            Self::FromClipboardYaml => "tmpl.from_clipboard_yaml_content",
140            Self::FromClipboard => "tmpl.from_clipboard_content",
141            Self::FromTextFile => "tmpl.from_text_file_content",
142            Self::AnnotateFile => "tmpl.annotate_file_content",
143            Self::SyncFilename => "`TemplateKind::SyncFilename` has no content template",
144            Self::None => "`TemplateKind::None` has no content template",
145        }
146    }
147
148    /// Returns the file template string as it is defined in the configuration file.
149    /// Panics for `TemplateKind::None`.
150    pub fn get_filename_template(&self) -> String {
151        let lib_cfg = LIB_CFG.read_recursive();
152        let scheme_idx = SETTINGS.read_recursive().current_scheme;
153        log::trace!(
154            "Scheme index: {}, applying the filename template: `{}`",
155            scheme_idx,
156            self.get_filename_template_name()
157        );
158        let tmpl = &lib_cfg.scheme[scheme_idx].tmpl;
159
160        match self {
161            Self::FromDir => tmpl.from_dir_filename.clone(),
162            Self::FromClipboardYaml => tmpl.from_clipboard_yaml_filename.clone(),
163            Self::FromClipboard => tmpl.from_clipboard_filename.clone(),
164            Self::FromTextFile => tmpl.from_text_file_filename.clone(),
165            Self::AnnotateFile => tmpl.annotate_file_filename.clone(),
166            Self::SyncFilename => tmpl.sync_filename.clone(),
167            Self::None => panic!("`TemplateKind::None` has no filename template"),
168        }
169    }
170
171    /// Returns the content template variable name as it is used in the configuration file.
172    pub fn get_filename_template_name(&self) -> &str {
173        match self {
174            Self::FromDir => "tmpl.from_dir_filename",
175            Self::FromClipboardYaml => "tmpl.from_clipboard_yaml_filename",
176            Self::FromClipboard => "tmpl.from_clipboard_filename",
177            Self::FromTextFile => "tmpl.from_text_file_filename",
178            Self::AnnotateFile => "tmpl.annotate_file_filename",
179            Self::SyncFilename => "tmpl.sync_filename",
180            Self::None => "`TemplateKind::None` has no filename template",
181        }
182    }
183}
184
185#[cfg(test)]
186mod tests {
187    use crate::content::ContentString;
188
189    use super::*;
190
191    #[test]
192    fn test_template_kind_from() {
193        use std::env::temp_dir;
194        use std::fs;
195
196        // Some data in the clipboard.
197        let tk = TemplateKind::from(
198            Path::new("."),
199            &ContentString::from("my html input".to_string()),
200            &ContentString::from("".to_string()),
201            &ContentString::from("".to_string()),
202        );
203        assert_eq!(tk, (TemplateKind::FromClipboard, None));
204
205        // Some data in the clipboard.
206        let tk = TemplateKind::from(
207            Path::new("."),
208            &ContentString::from("".to_string()),
209            &ContentString::from("my txt clipboard".to_string()),
210            &ContentString::from("".to_string()),
211        );
212        assert_eq!(tk, (TemplateKind::FromClipboard, None));
213
214        //
215        // No data in the clipboard.
216        let tk = TemplateKind::from(
217            Path::new("."),
218            &ContentString::from("".to_string()),
219            &ContentString::from("".to_string()),
220            &ContentString::from("".to_string()),
221        );
222        assert_eq!(tk, (TemplateKind::FromDir, None));
223
224        //
225        // No data in the clipboard.
226        let tk = TemplateKind::from(
227            Path::new("."),
228            &ContentString::from("<!DOCTYPE html>".to_string()),
229            &ContentString::from("".to_string()),
230            &ContentString::from("".to_string()),
231        );
232        assert_eq!(tk, (TemplateKind::FromDir, None));
233
234        //
235        // Tp-Note file.
236        // Prepare test: open existing text file without header.
237        let raw = "Body text without header";
238        let notefile = temp_dir().join("no header.md");
239        let _ = fs::write(&notefile, raw.as_bytes());
240        // Execute test.
241        let (tk, content) = TemplateKind::from(
242            &notefile,
243            &ContentString::from("<!DOCTYPE html>".to_string()),
244            &ContentString::from("".to_string()),
245            &ContentString::from("".to_string()),
246        );
247        // Inspect result.
248        let expected_template_kind = TemplateKind::FromTextFile;
249        let expected_body = "Body text without header";
250        let expected_header = "";
251        //println!("{:?}", tk);
252        assert_eq!(tk, expected_template_kind);
253        let content = content.unwrap();
254        assert_eq!(content.header(), expected_header);
255        assert_eq!(content.body(), expected_body);
256        let _ = fs::remove_file(&notefile);
257
258        //
259        // Tp-Note file.
260        // Prepare test: open existing note file with header.
261        let raw = "---\ntitle: my doc\n---\nBody";
262        let notefile = temp_dir().join("some.md");
263        let _ = fs::write(&notefile, raw.as_bytes());
264        // Execute test.
265        let (tk, content) = TemplateKind::from(
266            &notefile,
267            &ContentString::from("<!DOCTYPE html>".to_string()),
268            &ContentString::from("".to_string()),
269            &ContentString::from("".to_string()),
270        );
271        // Inspect result.
272        let expected_template_kind = TemplateKind::SyncFilename;
273        let expected_body = "Body";
274        let expected_header = "title: my doc";
275        //println!("{:?}", tk);
276        assert_eq!(tk, expected_template_kind);
277        let content = content.unwrap();
278        assert_eq!(content.header(), expected_header);
279        assert_eq!(content.body(), expected_body);
280        let _ = fs::remove_file(&notefile);
281
282        //
283        // Non-Tp-Note file.
284        // Prepare test: annotate existing PDF file.
285        let raw = "some data";
286        let notefile = temp_dir().join("some.pdf");
287        let _ = fs::write(&notefile, raw.as_bytes());
288        // Execute test.
289        let (tk, content) = TemplateKind::from(
290            &notefile,
291            &ContentString::from("<!DOCTYPE html>".to_string()),
292            &ContentString::from("".to_string()),
293            &ContentString::from("".to_string()),
294        );
295        // Inspect result.
296        let expected_template_kind = TemplateKind::AnnotateFile;
297        assert_eq!(tk, expected_template_kind);
298        assert_eq!(content, None);
299        let _ = fs::remove_file(&notefile);
300    }
301}