diary_cli/entry/
section.rs

1use toml::{Table, Value};
2use super::*;
3use soulog::*;
4use std::path::Path;
5use crate::list;
6use crate::unpack_array;
7use std::fs;
8
9// Some ease of life macros
10macro_rules! get {
11    ($key:ident at ($entry:ident, $idx:ident) from $table:ident as $func:ident with $logger:ident) => {{
12        let key = stringify!($key);
13        let obj = unwrap_opt!(($table.get(key)) with $logger, format: Section("Entry '{0}', section {1} must have '{key}' attribute", $entry, $idx));
14
15        unwrap_opt!((obj.$func()) with $logger, format: Section("Entry '{0}', section {1}'s '{key}' attribute must be of the correct type", $entry, $idx))
16    }};
17
18    ($var:ident = $key:ident at ($entry:ident, $idx:ident) from $table:ident as $func:ident with $logger:ident or $default:expr) => {
19        let key = stringify!($key);
20        let default = $default;
21        let $var = $table.get(key)
22            .map(|x| unwrap_opt!((x.$func()) with $logger, format: Entry("Entry '{0}', section {1}'s '{key}' attribute must be of the correct type", $entry, $idx)))
23            .unwrap_or(&default);
24    };
25}
26
27pub struct Section {
28    pub container: LazyContainer,
29    pub title: Option<String>,
30    pub notes: Option<Box<[String]>>,
31    pub content: Option<String>,
32}
33
34impl Section {
35    pub fn new(table: &Table, container: LazyContainer, entry: &str, idx: u8, mut logger: impl Logger) -> Self {
36        log!((logger) Section("Parsing entry '{entry}'s section {idx}..."));
37
38        // Get the basic needed data
39        log!((logger) Section("Reading section's data..."));
40        let title = get!(title at (entry, idx) from table as as_str with logger).to_string();
41        get!(raw_notes = notes at (entry, idx) from table as as_array with logger or Vec::<toml::Value>::with_capacity(0));
42
43        // Get contents
44        let content = table.get("path")
45            .map(|x| {
46                let path = unwrap_opt!((x.as_str()) with logger, format: Entry("Entry '{entry}', section {idx}'s 'path' attribute must be of the correct type"));
47                log!((logger) Section("Checking if path specified in the section is valid..."));
48                // Check if path exists
49                if !Path::new(path).exists() {
50                    log!((logger.error) Section("Path '{path}' specified in entry '{entry}', section {idx} does not exist") as Fatal);
51                    return logger.crash();
52                };
53                if_err!((logger) [Section, err => ("While reading entry '{entry}', section {idx}'s path contents: {err:?}")] retry fs::read_to_string(path))
54            }).unwrap_or_else(|| {
55                get!(contents at (entry, idx) from table as as_str with logger).to_string()
56            });
57
58        // Parse notes
59        log!((logger) Section("Parsing section's notes"));
60        unpack_array!(notes from raw_notes with logger by x
61            => unwrap_opt!((x.as_str()) with logger, format: Section("All notes in entry '{entry}', section '{idx}' must be strings")).to_string()
62        );
63
64        log!((logger) Section("Writing entry '{entry}'s section {idx} into archive..."));
65        let mut this = Self {
66            container,
67            title: Some(title),
68            content: Some(content),
69            notes: Some(notes.into_boxed_slice()),
70        };
71
72        this.store_lazy(logger.hollow());
73        this.clear_cache();
74        log!((logger) Section("Successfully parsed and written entry's section {idx} into archive"));
75        log!((logger) Section("")); // spacer
76        this
77    }
78
79    pub fn pull(&mut self, idx: u8, path: &Path, one_file: bool, mut logger: impl Logger) -> Table {
80        let mut map = Table::new();
81
82        // Insert title and notes
83        map.insert("title".into(), Value::String(self.title(logger.hollow()).clone()));
84        map.insert("notes".into(), self.notes(logger.hollow()).to_vec().into());
85
86        if one_file {
87            map.insert("contents".into(), Value::String(self.content(logger.hollow()).clone()));
88        } else {
89            let file_name = format!("section{idx}.txt");
90            let path = path.join(&file_name);
91            map.insert("path".into(), file_name.into());
92            if_err!((logger) [Pull, err => ("While writing section as text file: {err:?}")] retry fs::write(&path, self.content(logger.hollow())));
93        }
94
95        self.clear_cache();
96
97        map
98    }
99
100    pub fn store_lazy(&self, mut logger: impl Logger) {
101        // Only store them if they are accessed (maybe modified)
102        if let Some(x) = &self.title { write_db_container!(Section(self.container) title = new_string(x) with logger); }
103        if let Some(x) = &self.content { write_db_container!(Section(self.container) content = new_string(x) with logger); }
104        if let Some(x) = &self.notes {
105            list::write(
106                x.as_ref(),
107                |file, data| LazyData::new_string(file, data),
108                &if_err!((logger) [Section, err => ("While writing section's notes to archive: {:?}", err)] retry self.container.new_container("notes")),
109                logger
110            );
111        }
112    }
113
114    pub fn load_lazy(container: LazyContainer) -> Self {
115        Self {
116            container,
117            title: None,
118            notes: None,
119            content: None,
120        }
121    }
122
123    pub fn clear_cache(&mut self) {
124        self.title = None;
125        self.content = None;
126        self.notes = None;
127    }
128
129    pub fn fill_cache(&mut self, logger: impl Logger) {
130        self.title(logger.hollow());
131        self.content(logger.hollow());
132        self.notes(logger.hollow());
133    }
134
135    cache_field!(notes(this, logger) -> Box<[String]> {
136        list::read(
137            |data| data.collect_string(),
138            &if_err!((logger) [Section, err => ("While reading from section's notes: {err:?}")] retry this.container.child_container("notes")),
139            logger
140        )
141    });
142
143    cache_field!(title(this, logger) -> String {
144        read_db_container!(title from Section(this.container) as collect_string with logger)
145    });
146
147    cache_field!(content(this, logger) -> String {
148        read_db_container!(content from Section(this.container) as collect_string with logger)
149    });
150}