Skip to main content

code_it_later_rs/
datatypes.rs

1use regex::Regex;
2use serde::Serialize;
3use std::fmt;
4
5/// major data struct including file path and all crumbs
6#[derive(Debug, PartialEq, Eq, Serialize)]
7pub struct Bread {
8    pub(super) file_path: String,
9    pub(super) crumbs: Vec<Crumb>,
10}
11
12impl Bread {
13    pub fn new(f: String, crumbs: Vec<Crumb>) -> Self {
14        Bread {
15            file_path: f,
16            crumbs,
17        }
18    }
19
20    pub fn to_org(&self) -> Result<String, !> {
21        let mut content = format!("* {}\n", self.file_path);
22        self.crumbs
23            .iter()
24            .filter_map(|c| c.to_org())
25            .for_each(|org_inside| {
26                content += "** ";
27                content += &org_inside;
28                content += "\n"
29            });
30        Ok(content)
31    }
32}
33
34impl fmt::Display for Bread {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        write!(f, "|-- {}\n", self.file_path)?; // write file_path
37
38        for c in &self.crumbs {
39            write!(f, "  |-- {}", c)?;
40        }
41        Ok(())
42    }
43}
44
45/// Crumb including the data of this line
46#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)]
47pub struct Crumb {
48    pub(crate) line_num: usize,
49
50    #[serde(skip)]
51    /// the position of the crumb start from in this line
52    pub(crate) position: usize,
53
54    /// store tail lines' numbers after `line_num`
55    tails: Vec<Crumb>,
56
57    pub(crate) keyword: Option<String>,
58
59    /// view_content use to print out
60    /// like in tails and keywords
61    /// the `content` below keep original content
62    pub(crate) view_content: String,
63
64    /// the content including original content after :=
65    /// maybe different than view_content
66    pub(crate) content: String,
67
68    /// record the crumb header for restore
69    /// like in lisp `;;;:= here`, `;;;` should be header
70    comment_symbol_header: String,
71
72    /// This store the endding syntex of multi-line comment
73    comment_symbol_endding: String,
74
75    pub(crate) has_tail: bool,
76
77    /// ignore this crumb or not
78    ignore: bool,
79
80    /// range content
81    pub(crate) range_content: Option<Vec<(usize, String)>>,
82}
83
84impl Crumb {
85    pub fn new_for_test(
86        line_num: usize,
87        position: usize,
88        tails: Vec<Crumb>,
89        keyword: Option<String>,
90        view_content: String,
91        content: String,
92        comment_symbol_header: String,
93        ignore: bool,
94    ) -> Self {
95        Self {
96            line_num,
97            position,
98            tails,
99            keyword,
100            view_content,
101            content,
102            comment_symbol_header,
103            ignore,
104            range_content: None,
105            ..Default::default()
106        }
107    }
108
109    pub fn with_has_tail(mut self, has_tail: bool) -> Self {
110        self.has_tail = has_tail;
111        self
112    }
113
114    /// side effect: will change keyword to Some(_) if match successed
115    pub fn filter_keywords(&mut self, re: &Regex) -> bool {
116        match re.captures(&self.content) {
117            Some(a) => {
118                self.keyword = Some(a[1].to_string());
119                self.view_content = a[2].to_string();
120                true
121            }
122            None => false,
123        }
124    }
125
126    pub fn has_tail(&mut self) -> bool {
127        if self.view_content.ends_with("...") {
128            self.has_tail = true
129        }
130        self.has_tail
131    }
132
133    /// add tail crumbs in this one
134    pub fn add_tail(&mut self, mut tail: Self) {
135        // update the first crumb's content
136        self.view_content = self
137            .view_content
138            .trim_end()
139            .trim_end_matches("...")
140            .to_string();
141        if !self.view_content.is_empty() {
142            self.view_content.push(' ');
143        }
144        self.view_content.push_str(&tail.content);
145        self.has_tail = tail.has_tail();
146        self.tails.push(tail);
147    }
148
149    pub fn new(
150        line_num: usize,
151        position: usize,
152        content: String,
153        comment_symbol_header: String,
154        comment_symbol_endding: String,
155    ) -> Self {
156        Self {
157            line_num,
158            position,
159            keyword: None,
160            tails: vec![],
161            view_content: content.clone(),
162            content,
163            comment_symbol_header,
164            ignore: false,
165            range_content: None,
166            comment_symbol_endding,
167            ..Default::default()
168        }
169    }
170
171    /// keyword crumb can transfer to org string
172    pub fn to_org(&self) -> Option<String> {
173        match &self.keyword {
174            Some(k) => Some(format!("{} {}", k, self.content)),
175            None => None,
176        }
177    }
178
179    /// return this crumb line_num and all tails line numbers if it has tails
180    pub fn all_lines_num(&self) -> Vec<usize> {
181        let mut a = vec![self.line_num];
182        a.append(&mut self.tails.iter().map(|t| t.line_num).collect());
183        a
184    }
185
186    /// return this crumb line numbers and the position of lines pairs
187    pub fn all_lines_num_postion_pair(&self) -> Vec<(usize, usize)> {
188        let mut a = vec![(self.line_num, self.position)];
189        a.append(
190            &mut self
191                .tails
192                .iter()
193                .map(|t| (t.line_num, t.position))
194                .collect(),
195        );
196        a
197    }
198
199    /// return this crumb line numbers, the position, the header, the endding and content of lines pairs
200    pub fn all_lines_num_postion_and_header_content(
201        &self,
202    ) -> Vec<(usize, usize, &str, &str, &str)> {
203        let mut a = vec![(
204            self.line_num,
205            self.position,
206            self.comment_symbol_header.as_str(),
207            self.comment_symbol_endding.as_str(),
208            self.content.as_str(),
209        )];
210
211        a.append(
212            &mut self
213                .tails
214                .iter()
215                .map(|t| {
216                    (
217                        t.line_num,
218                        t.position,
219                        t.comment_symbol_header.as_str(),
220                        t.comment_symbol_endding.as_str(),
221                        t.content.as_str(),
222                    )
223                })
224                .collect(),
225        );
226        a
227    }
228
229    // add the ignore flag to this crumb
230    pub fn add_ignore_flag(mut self) -> Self {
231        self.ignore = true;
232        self
233    }
234
235    pub fn is_ignore(&self) -> bool {
236        self.ignore
237    }
238
239    pub fn list_format(&self) -> String {
240        let kw = match self.keyword {
241            Some(ref k) => {
242                let mut c = String::from(k);
243                c.push_str(": ");
244                c
245            }
246            None => "".to_string(),
247        };
248        format!("{}: {}{}", self.line_num, kw, self.view_content)
249    }
250
251    pub fn range_format(&self) -> String {
252        match &self.range_content {
253            Some(content) => content
254                .iter()
255                .map(|(ln, line)| format!("Line {}: {}", ln, line))
256                .collect::<Vec<_>>()
257                .join("\n"),
258            None => String::new(),
259        }
260    }
261}
262
263/// default format
264impl fmt::Display for Crumb {
265    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266        let a = match self.keyword {
267            Some(ref k) => {
268                let mut c = String::from(k);
269                c.push_str(": ");
270                c
271            }
272            None => "".to_string(),
273        };
274        write!(f, "Line {}: {}{}\n", self.line_num, a, self.view_content)?;
275        Ok(())
276    }
277}
278
279#[cfg(test)]
280mod tests {
281    use super::*;
282
283    #[test]
284    fn test_filter_keyowrds() {
285        let mut a: Crumb = Default::default();
286        a.content = "TODO: test1".to_string();
287
288        assert!(a.filter_keywords(&Regex::new(&format!("({}):\\s*(.*)", "TODO")).unwrap()));
289        assert_eq!(a.keyword, Some("TODO".to_string()));
290
291        a.content = "TODO: test1".to_string();
292        assert!(
293            a.filter_keywords(&Regex::new(&format!("({}|{}):\\s*(.*)", "TODO", "MARK")).unwrap())
294        );
295        assert_eq!(a.keyword, Some("TODO".to_string()));
296        assert_eq!(a.view_content, "test1");
297
298        // test 2
299        let mut a: Crumb = Default::default();
300        a.content = "test1".to_string();
301
302        assert!(!a.filter_keywords(&Regex::new(&format!("({}):\\s*(.*)", "TODO")).unwrap()));
303        assert_eq!(a.keyword, None);
304
305        // test 3
306        let mut a: Crumb = Default::default();
307        a.content = "!TODO: test3".to_string();
308        a.ignore = true;
309        //dbg!(&a);
310        assert!(
311            a.filter_keywords(&Regex::new(&format!("({}|{}):\\s*(.*)", "TODO", "MARK")).unwrap())
312        );
313        //dbg!(&a);
314        assert_eq!(a.keyword, Some("TODO".to_string()));
315    }
316}