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    /// Clipboard data may be available.
14    FromDir,
15    /// Templates used when Tp-Note is invoked with a path pointing to a text
16    /// file that does not contain a YAML header.
17    FromTextFile,
18    /// Templates used when Tp-Note is invoked with a path pointing to a non
19    /// text file.
20    AnnotateFile,
21    /// Templates used when Tp-Note is invoked with a path pointing to a Tp-Note
22    /// text file with a valid YAML header (with a `title:` field).
23    SyncFilename,
24    /// No templates are used, but the file is still parsed in order to
25    /// render it later to HTML (cf. `<Note>.render_content_to_html()`.
26    #[default]
27    None,
28}
29
30impl TemplateKind {
31    /// A constructor returning the tuple `(template_kind, Some(content))`.
32    /// `template_kind` is the result of the logic calculating under what
33    /// circumstances what template should be used.
34    /// If `path` has a Tp-Note extension (e.g. `.md`) and the file indicated by
35    /// `path` could be opened and loaded from disk, `Some(content)` contains
36    /// its content. Otherwise `None` is returned.
37    pub fn from<T: Content>(path: &Path) -> (Self, Option<T>) {
38        //
39        let path_is_dir = path.is_dir();
40        let path_is_file = path.is_file();
41
42        let path_has_tpnote_extension = path.has_tpnote_ext();
43        let path_is_tpnote_file = path_is_file && path_has_tpnote_extension;
44
45        let (path_is_tpnote_file_and_has_header, content) = if path_is_tpnote_file {
46            let content: T = Content::open(path).unwrap_or_default();
47            (!content.header().is_empty(), Some(content))
48        } else {
49            (false, None)
50        };
51
52        // This determines the workflow and what template will be applied.
53        let template_kind = match (
54            path_is_dir,
55            path_is_file,
56            path_is_tpnote_file,
57            path_is_tpnote_file_and_has_header,
58        ) {
59            (true, false, _, _) => TemplateKind::FromDir,
60            (false, true, true, true) => TemplateKind::SyncFilename,
61            (false, true, true, false) => TemplateKind::FromTextFile,
62            (false, true, false, _) => TemplateKind::AnnotateFile,
63            (_, _, _, _) => TemplateKind::None,
64        };
65
66        log::debug!("Choosing the \"{:?}\" template.", template_kind);
67
68        log::trace!(
69            "Template choice is based on:
70             path=\"{}\",
71             path_is_dir={},
72             path_is_file={},
73             path_is_tpnote_file={},
74             path_is_tpnote_file_and_has_header={}",
75            path.to_str().unwrap(),
76            path_is_dir,
77            path_is_file,
78            path_is_tpnote_file,
79            path_is_tpnote_file_and_has_header,
80        );
81        (template_kind, content)
82    }
83
84    /// Returns the content template string as it is defined in the configuration file.
85    /// Panics for `TemplateKind::SyncFilename` and `TemplateKind::None`.
86    pub fn get_content_template(&self) -> String {
87        let lib_cfg = LIB_CFG.read_recursive();
88        let scheme_idx = SETTINGS.read_recursive().current_scheme;
89        log::trace!(
90            "Scheme index: {}, applying the content template: `{}`",
91            scheme_idx,
92            self.get_content_template_name()
93        );
94        let tmpl = &lib_cfg.scheme[scheme_idx].tmpl;
95
96        match self {
97            Self::FromDir => tmpl.from_dir_content.clone(),
98            Self::FromTextFile => tmpl.from_text_file_content.clone(),
99            Self::AnnotateFile => tmpl.annotate_file_content.clone(),
100            Self::SyncFilename => {
101                panic!("`TemplateKind::SyncFilename` has no content template")
102            }
103            Self::None => panic!("`TemplateKind::None` has no content template"),
104        }
105    }
106
107    /// Returns the content template variable name as it is used in the configuration file.
108    pub fn get_content_template_name(&self) -> &str {
109        match self {
110            Self::FromDir => "tmpl.from_dir_content",
111            Self::FromTextFile => "tmpl.from_text_file_content",
112            Self::AnnotateFile => "tmpl.annotate_file_content",
113            Self::SyncFilename => "`TemplateKind::SyncFilename` has no content template",
114            Self::None => "`TemplateKind::None` has no content template",
115        }
116    }
117
118    /// Returns the file template string as it is defined in the configuration file.
119    /// Panics for `TemplateKind::None`.
120    pub fn get_filename_template(&self) -> String {
121        let lib_cfg = LIB_CFG.read_recursive();
122        let scheme_idx = SETTINGS.read_recursive().current_scheme;
123        log::trace!(
124            "Scheme index: {}, applying the filename template: `{}`",
125            scheme_idx,
126            self.get_filename_template_name()
127        );
128        let tmpl = &lib_cfg.scheme[scheme_idx].tmpl;
129
130        match self {
131            Self::FromDir => tmpl.from_dir_filename.clone(),
132            Self::FromTextFile => tmpl.from_text_file_filename.clone(),
133            Self::AnnotateFile => tmpl.annotate_file_filename.clone(),
134            Self::SyncFilename => tmpl.sync_filename.clone(),
135            Self::None => panic!("`TemplateKind::None` has no filename template"),
136        }
137    }
138
139    /// Returns the content template variable name as it is used in the configuration file.
140    pub fn get_filename_template_name(&self) -> &str {
141        match self {
142            Self::FromDir => "tmpl.from_dir_filename",
143            Self::FromTextFile => "tmpl.from_text_file_filename",
144            Self::AnnotateFile => "tmpl.annotate_file_filename",
145            Self::SyncFilename => "tmpl.sync_filename",
146            Self::None => "`TemplateKind::None` has no filename template",
147        }
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use crate::content::Content;
154    use crate::content::ContentString;
155
156    use super::*;
157
158    #[test]
159    fn test_template_kind_from() {
160        use std::env::temp_dir;
161        use std::fs;
162
163        //
164        let tk: (TemplateKind, Option<ContentString>) = TemplateKind::from(Path::new("."));
165        assert_eq!(tk, (TemplateKind::FromDir, None));
166
167        //
168        // Tp-Note file.
169        // Prepare test: open existing text file without header.
170        let raw = "Body text without header";
171        let notefile = temp_dir().join("no header.md");
172        let _ = fs::write(&notefile, raw.as_bytes());
173        // Execute test.
174        let (tk, content) = TemplateKind::from(&notefile);
175        // Inspect result.
176        let expected_template_kind = TemplateKind::FromTextFile;
177        let expected_body = "Body text without header";
178        let expected_header = "";
179        //println!("{:?}", tk);
180        assert_eq!(tk, expected_template_kind);
181        let content: ContentString = content.unwrap();
182        assert_eq!(content.header(), expected_header);
183        assert_eq!(content.body(), expected_body);
184        let _ = fs::remove_file(&notefile);
185
186        //
187        // Tp-Note file.
188        // Prepare test: open existing note file with header.
189        let raw = "---\ntitle: my doc\n---\nBody";
190        let notefile = temp_dir().join("some.md");
191        let _ = fs::write(&notefile, raw.as_bytes());
192        // Execute test.
193        let (tk, content) = TemplateKind::from(&notefile);
194        // Inspect result.
195        let expected_template_kind = TemplateKind::SyncFilename;
196        let expected_body = "Body";
197        let expected_header = "title: my doc";
198        //println!("{:?}", tk);
199        assert_eq!(tk, expected_template_kind);
200        let content: ContentString = content.unwrap();
201        assert_eq!(content.header(), expected_header);
202        assert_eq!(content.body(), expected_body);
203        let _ = fs::remove_file(&notefile);
204
205        //
206        // Non-Tp-Note file.
207        // Prepare test: annotate existing PDF file.
208        let raw = "some data";
209        let notefile = temp_dir().join("some.pdf");
210        let _ = fs::write(&notefile, raw.as_bytes());
211
212        let (tk, content): (TemplateKind, Option<ContentString>) = TemplateKind::from(&notefile);
213        // Inspect result.
214        let expected_template_kind = TemplateKind::AnnotateFile;
215        assert_eq!(tk, expected_template_kind);
216        assert_eq!(content, None);
217        let _ = fs::remove_file(&notefile);
218    }
219}