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    /// ignore this crumb or not
73    ignore: bool,
74
75    /// range content
76    pub(crate) range_content: Option<Vec<(usize, String)>>,
77}
78
79impl Crumb {
80    pub fn new_for_test(
81        line_num: usize,
82        position: usize,
83        tails: Vec<Crumb>,
84        keyword: Option<String>,
85        view_content: String,
86        content: String,
87        comment_symbol_header: String,
88        ignore: bool,
89    ) -> Self {
90        Self {
91            line_num,
92            position,
93            tails,
94            keyword,
95            view_content,
96            content,
97            comment_symbol_header,
98            ignore,
99            range_content: None,
100        }
101    }
102
103    /// side effect: will change keyword to Some(_) if match successed
104    pub fn filter_keywords(&mut self, re: &Regex) -> bool {
105        match re.captures(&self.content) {
106            Some(a) => {
107                self.keyword = Some(a[1].to_string());
108                self.view_content = a[2].to_string();
109                true
110            }
111            None => false,
112        }
113    }
114
115    pub fn has_tail(&self) -> bool {
116        self.view_content.ends_with("...")
117    }
118
119    /// add tail crumbs in this one
120    pub fn add_tail(&mut self, tail: Self) {
121        // update the first crumb's content
122        self.view_content = self
123            .view_content
124            .trim_end()
125            .trim_end_matches("...")
126            .to_string();
127        self.view_content.push(' ');
128        self.view_content.push_str(&tail.content);
129        self.tails.push(tail);
130    }
131
132    pub fn new(
133        line_num: usize,
134        position: usize,
135        content: String,
136        comment_symbol_header: String,
137    ) -> Self {
138        Self {
139            line_num,
140            position,
141            keyword: None,
142            tails: vec![],
143            view_content: content.clone(),
144            content,
145            comment_symbol_header,
146            ignore: false,
147            range_content: None,
148        }
149    }
150
151    /// keyword crumb can transfer to org string
152    pub fn to_org(&self) -> Option<String> {
153        match &self.keyword {
154            Some(k) => Some(format!("{} {}", k, self.content)),
155            None => None,
156        }
157    }
158
159    /// return this crumb line_num and all tails line numbers if it has tails
160    pub fn all_lines_num(&self) -> Vec<usize> {
161        let mut a = vec![self.line_num];
162        a.append(&mut self.tails.iter().map(|t| t.line_num).collect());
163        a
164    }
165
166    /// return this crumb line numbers and the position of lines pairs
167    pub fn all_lines_num_postion_pair(&self) -> Vec<(usize, usize)> {
168        let mut a = vec![(self.line_num, self.position)];
169        a.append(
170            &mut self
171                .tails
172                .iter()
173                .map(|t| (t.line_num, t.position))
174                .collect(),
175        );
176        a
177    }
178
179    /// return this crumb line numbers, the position, the header and content of lines pairs
180    pub fn all_lines_num_postion_and_header_content(&self) -> Vec<(usize, usize, &str, &str)> {
181        let mut a = vec![(
182            self.line_num,
183            self.position,
184            self.comment_symbol_header.as_str(),
185            self.content.as_str(),
186        )];
187        a.append(
188            &mut self
189                .tails
190                .iter()
191                .map(|t| {
192                    (
193                        t.line_num,
194                        t.position,
195                        self.comment_symbol_header.as_str(),
196                        t.content.as_str(),
197                    )
198                })
199                .collect(),
200        );
201        a
202    }
203
204    // add the ignore flag to this crumb
205    pub fn add_ignore_flag(mut self) -> Self {
206        self.ignore = true;
207        self
208    }
209
210    pub fn is_ignore(&self) -> bool {
211        self.ignore
212    }
213
214    pub fn list_format(&self) -> String {
215        let kw = match self.keyword {
216            Some(ref k) => {
217                let mut c = String::from(k);
218                c.push_str(": ");
219                c
220            }
221            None => "".to_string(),
222        };
223        format!("{}: {}{}", self.line_num, kw, self.view_content)
224    }
225
226    pub fn range_format(&self) -> String {
227        match &self.range_content {
228            Some(content) => content
229                .iter()
230                .map(|(ln, line)| format!("Line {}: {}", ln, line))
231                .collect::<Vec<_>>()
232                .join("\n"),
233            None => String::new(),
234        }
235    }
236}
237
238/// default format
239impl fmt::Display for Crumb {
240    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241        let a = match self.keyword {
242            Some(ref k) => {
243                let mut c = String::from(k);
244                c.push_str(": ");
245                c
246            }
247            None => "".to_string(),
248        };
249        write!(f, "Line {}: {}{}\n", self.line_num, a, self.view_content)?;
250        Ok(())
251    }
252}
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257
258    #[test]
259    fn test_filter_keyowrds() {
260        let mut a: Crumb = Default::default();
261        a.content = "TODO: test1".to_string();
262
263        assert!(a.filter_keywords(&Regex::new(&format!("({}):\\s*(.*)", "TODO")).unwrap()));
264        assert_eq!(a.keyword, Some("TODO".to_string()));
265
266        a.content = "TODO: test1".to_string();
267        assert!(
268            a.filter_keywords(&Regex::new(&format!("({}|{}):\\s*(.*)", "TODO", "MARK")).unwrap())
269        );
270        assert_eq!(a.keyword, Some("TODO".to_string()));
271        assert_eq!(a.view_content, "test1");
272
273        // test 2
274        let mut a: Crumb = Default::default();
275        a.content = "test1".to_string();
276
277        assert!(!a.filter_keywords(&Regex::new(&format!("({}):\\s*(.*)", "TODO")).unwrap()));
278        assert_eq!(a.keyword, None);
279
280        // test 3
281        let mut a: Crumb = Default::default();
282        a.content = "!TODO: test3".to_string();
283        a.ignore = true;
284        //dbg!(&a);
285        assert!(
286            a.filter_keywords(&Regex::new(&format!("({}|{}):\\s*(.*)", "TODO", "MARK")).unwrap())
287        );
288        //dbg!(&a);
289        assert_eq!(a.keyword, Some("TODO".to_string()));
290    }
291}