diary_cli/
moc.rs

1pub mod collection;
2
3pub use collection::*;
4use soulog::*;
5use lazy_db::*;
6use crate::{entry::*, search::Searchable};
7use toml::Table;
8
9// Some ease of life macros
10macro_rules! get {
11    ($key:ident at $moc: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: MOC("moc '{0}' must have '{key}' attribute", $moc));
14
15        unwrap_opt!((obj.$func()) with $logger, format: MOC("moc '{0}'s '{key}' attribute must be of correct type", $moc))
16    }};
17
18    ($var:ident = $key:ident at $entry: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: MOC("moc '{0}'s '{key}' attribute must be of the correct type", $entry)))
23            .unwrap_or(&default);
24    };
25}
26
27pub struct MOC {
28    pub container: LazyContainer,
29    pub uid: String,
30    pub title: Option<String>,
31    pub description: Option<String>,
32    pub notes: Option<Box<[String]>>,
33    pub tags: Option<Box<[String]>>,
34    pub collections: Option<Box<[Collection]>>,
35}
36
37impl MOC {
38    pub fn store_lazy(&self, mut logger: impl Logger) {
39        log!((logger) MOC("Storing moc into archive..."));
40        // Only store them if modified
41        if let Some(x) = &self.title { write_db_container!(MOC(self.container) title = new_string(x) with logger); }
42        if let Some(x) = &self.description { write_db_container!(MOC(self.container) description = new_string(x) with logger); }
43        
44        // The bloody lists & arrays
45        if let Some(x) = &self.notes {
46            list::write(
47                x.as_ref(),
48                |file, data| LazyData::new_string(file, data),
49                &if_err!((logger) [MOC, err => ("While writing notes to archive: {:?}", err)] retry self.container.new_container("notes")),
50                logger.hollow()
51            );
52        }
53
54        if let Some(x) = &self.tags {
55            list::write(
56                x.as_ref(),
57                |file, data| LazyData::new_string(file, data),
58                &if_err!((logger) [MOC, err => ("While writing tags to archive: {:?}", err)] retry self.container.new_container("tags")),
59                logger.hollow()
60            );
61        }
62    }
63
64    pub fn load_lazy(uid: String, container: LazyContainer) -> Self {
65        Self {
66            container,
67            uid,
68            title: None,
69            description: None,
70            notes: None,
71            tags: None,
72            collections: None,
73        }
74    }
75
76    pub fn clear_cache(&mut self) {
77        self.title = None;
78        self.description = None;
79        self.notes = None;
80        self.tags = None;
81        self.collections = None;
82    }
83
84    cache_field!(title(this, logger) -> String {
85        read_db_container!(title from MOCSection(this.container) as collect_string with logger)
86    });
87
88    cache_field!(description(this, logger) -> String {
89        read_db_container!(description from MOC(this.container) as collect_string with logger)
90    });
91
92    cache_field!(notes(this, logger) -> Box<[String]> {
93        list::read(
94            |data| data.collect_string(),
95            &if_err!((logger) [MOC, err => ("While reading from moc's notes: {err:?}")] retry this.container.child_container("notes")),
96            logger
97        )
98    });
99
100    cache_field!(tags(this, logger) -> Box<[String]> {
101        list::read(
102            |data| data.collect_string(),
103            &if_err!((logger) [MOC, err => ("While reading from moc's tags: {err:?}")] retry this.container.child_container("tags")),
104            logger
105        )
106    });
107
108    cache_field!(collections(this, logger) -> Box<[Collection]> {
109        let container = if_err!((logger) [MOC, err => ("While reading from moc's collections: {err:?}")] retry this.container.child_container("collections"));
110        let length = if_err!((logger) [MOC, err => ("While reading from moc's collections' length: {err:?}")] retry container.read_data("length"));
111        let length = if_err!((logger) [MOC, err => ("While reading from moc's collections' length: {err:?}")] {length.collect_u16()} crash {
112            log!((logger) MOC("{err:#?}") as Fatal);
113            logger.crash()
114        });
115        let mut colletions = Vec::with_capacity(length as usize);
116
117        for i in 0..length {
118            colletions.push(Collection::load_lazy(
119                if_err!((logger) [MOC, err => ("While reading moc collection {i}: {err:?}")] retry container.child_container(i.to_string()))
120            ));
121        }
122
123        colletions.into_boxed_slice()
124    });
125
126    pub fn new(table: Table, moc_path: &str, database: LazyContainer, mut logger: impl Logger) -> Self {
127        log!((logger) MOC("Reading moc '{moc_path}'s raw unchecked data..."));
128
129        let moc_table = get!(moc at moc_path from table as as_table with logger);
130        let uid = get!(uid at moc_path from moc_table as as_str with logger).to_string();
131
132        let title = get!(title at moc_path from moc_table as as_str with logger).to_string();
133        let description = get!(description at moc_path from moc_table as as_str with logger).to_string();
134        get!(raw_notes = notes at moc_path from moc_table as as_array with logger or Vec::<toml::Value>::with_capacity(0));
135        let raw_tags = get!(tags at moc_path from moc_table as as_array with logger);
136        let raw_collections = get!(collection at moc_path from table as as_array with logger);
137
138        // set the container
139        let container =
140            if_err!((logger) [MOC, err => ("While initialising moc: '{err:?}'")] retry database.new_container(&uid));
141
142        // parse simple arrays
143        log!((logger) MOC("Parsing notes & tags"));
144        unpack_array!(notes from raw_notes with logger by x
145            => unwrap_opt!((x.as_str()) with logger, format: MOC("All notes in moc '{moc_path}' must be strings")).to_string()
146        );
147
148        unpack_array!(tags from raw_tags with logger by x
149            => unwrap_opt!((x.as_str()) with logger, format: MOC("All tags in moc '{moc_path}' must be strings")).to_string()
150        );
151
152        // parse collections
153        log!((logger) MOC("Parsing moc's collections..."));
154        let list = if_err!((logger) [MOC, err => ("While initialising collections: {err:?}")] retry container.new_container("collections"));
155        unpack_array!(collections from raw_collections with logger by (i, x) => {
156            let container = if_err!((logger) [MOC, err => ("While initialising collection {i}: {err:?}")] retry list.new_container(i.to_string()));
157            let table = unwrap_opt!((x.as_table()) with logger, format: MOC("MOC '{moc_path}', collection {i} must be a toml table"));
158            Collection::new(table, container, moc_path, i as u8, logger.hollow()) // Write into that container
159        });
160        if_err!((logger) [MOC, err => ("While writing collection list length: {err:?}")] retry write_container!((list) length = new_u16(raw_collections.len() as u16)));
161
162        log!((logger) Entry("Storing moc's parsed and checked data into archive..."));
163
164        let mut this = Self {
165            uid,
166            container,
167            title: Some(title),
168            description: Some(description),
169            notes: Some(notes.into_boxed_slice()),
170            tags: Some(tags.into_boxed_slice()),
171            collections: Some(collections.into_boxed_slice()),
172        };
173        this.store_lazy(logger.hollow());
174        log!((logger) MOC("Successfully written moc into archive"));
175        log!((logger) MOC(""));
176        this.clear_cache();
177        this
178    }
179
180    pub fn pull(&mut self, logger: impl Logger) -> Table {
181        let mut map = Table::new();
182        let mut moc = Table::new();
183
184        // Insert uid, title, description, notes, tags, and date
185        moc.insert("uid".into(), self.uid.clone().into());
186        moc.insert("title".into(), self.title(logger.hollow()).clone().into());
187        moc.insert("description".into(), self.description(logger.hollow()).clone().into());
188        moc.insert("notes".into(), self.notes(logger.hollow()).to_vec().into());
189        moc.insert("tags".into(), self.tags(logger.hollow()).to_vec().into());
190        map.insert("moc".into(), moc.into());
191        map.insert("is-moc".into(), true.into());
192
193        self.clear_cache();
194
195        map.insert("collection".into(), self.collections(logger.hollow())
196            .iter_mut()
197            .map(|x| x.pull(logger.hollow()))
198            .collect::<Vec<Table>>()
199            .into()
200        );
201
202        self.clear_cache();
203
204        map
205    }
206}
207
208impl Searchable for MOC {
209    fn get_uid(&self) -> String {
210        self.uid.clone()
211    }
212
213    fn contains_tag(&mut self, tag: &String, logger: impl Logger) -> bool {
214        let result = self.tags(logger).contains(tag);
215        self.tags = None;
216        result
217    }
218}