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