1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
use regex::Regex;
use serde::Serialize;
use std::fmt;

/// major data struct including file path and all crumbs
#[derive(Debug, PartialEq, Eq, Serialize)]
pub struct Bread {
    pub(super) file_path: String,
    pub(super) crumbs: Vec<Crumb>,
}

impl Bread {
    pub fn new(f: String, crumbs: Vec<Crumb>) -> Self {
        Bread {
            file_path: f,
            crumbs,
        }
    }

    pub fn to_org(&self) -> Result<String, !> {
        let mut content = format!("* {}\n", self.file_path);
        self.crumbs
            .iter()
            .filter_map(|c| c.to_org())
            .for_each(|org_inside| {
                content += "** ";
                content += &org_inside;
                content += "\n"
            });
        Ok(content)
    }
}

impl fmt::Display for Bread {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "|-- {}\n", self.file_path)?; // write file_path

        for c in &self.crumbs {
            write!(f, "  |-- {}", c)?;
        }
        Ok(())
    }
}

/// Crumb including the data of this line
#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)]
pub struct Crumb {
    pub(crate) line_num: usize,

    #[serde(skip)]
    /// the position of the crumb start from in this line
    pub(crate) position: usize,

    /// store tail lines' numbers after `line_num`
    tails: Vec<Crumb>,

    pub(crate) keyword: Option<String>,
    pub(crate) content: String,

    ignore: bool,
}

impl Crumb {
    /// side effect: will change keyword to Some(_) if match successed
    pub fn filter_keywords(&mut self, re: &Regex) -> bool {
        match re.captures(&self.content) {
            Some(a) => {
                self.keyword = Some(a[1].to_string());
                self.content = a[2].to_string();
                true
            }
            None => false,
        }
    }

    pub fn has_tail(&self) -> bool {
        self.content.ends_with("...")
    }

    /// add tail crumbs in this one
    pub fn add_tail(&mut self, tail: Self) {
        // update the first crumb's content
        self.content = self.content.trim_end().trim_end_matches("...").to_string();
        self.content.push(' ');
        self.content.push_str(&tail.content);
        self.tails.push(tail);
    }

    pub fn new(line_num: usize, position: usize, keyword: Option<String>, content: String) -> Self {
        Self {
            line_num,
            position,
            keyword,
            tails: vec![],
            content,
            ignore: false,
        }
    }

    /// keyword crumb can transfer to org string
    pub fn to_org(&self) -> Option<String> {
        match &self.keyword {
            Some(k) => Some(format!("{} {}", k, self.content)),
            None => None,
        }
    }

    /// return this crumb line_num and all tails line numbers if it has tails
    pub fn all_lines_num(&self) -> Vec<usize> {
        let mut a = vec![self.line_num];
        a.append(&mut self.tails.iter().map(|t| t.line_num).collect());
        a
    }

    /// return this crumb line numbers and the position of lines pairs
    pub fn all_lines_num_postion_pair(&self) -> Vec<(usize, usize)> {
        let mut a = vec![(self.line_num, self.position)];
        a.append(
            &mut self
                .tails
                .iter()
                .map(|t| (t.line_num, t.position))
                .collect(),
        );
        a
    }

    // add the ignore flag to this crumb
    pub fn add_ignore_flag(mut self) -> Self {
        self.ignore = true;
        self
    }

    pub fn is_ignore(&self) -> bool {
        self.ignore
    }

    pub fn list_format(&self) -> String {
        let kw = match self.keyword {
            Some(ref k) => {
                let mut c = String::from(k);
                c.push_str(": ");
                c
            }
            None => "".to_string(),
        };
        format!("{}: {}{}", self.line_num, kw, self.content)
    }
}

/// default format
impl fmt::Display for Crumb {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let a = match self.keyword {
            Some(ref k) => {
                let mut c = String::from(k);
                c.push_str(": ");
                c
            }
            None => "".to_string(),
        };
        write!(f, "Line {}: {}{}\n", self.line_num, a, self.content)?;
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_filter_keyowrds() {
        let mut a: Crumb = Default::default();
        a.content = "TODO: test1".to_string();

        assert!(a.filter_keywords(&Regex::new(&format!("({}):\\s*(.*)", "TODO")).unwrap()));
        assert_eq!(a.keyword, Some("TODO".to_string()));

        a.content = "TODO: test1".to_string();
        assert!(
            a.filter_keywords(&Regex::new(&format!("({}|{}):\\s*(.*)", "TODO", "MARK")).unwrap())
        );
        assert_eq!(a.keyword, Some("TODO".to_string()));
        assert_eq!(a.content, "test1");

        // test 2
        let mut a: Crumb = Default::default();
        a.content = "test1".to_string();

        assert!(!a.filter_keywords(&Regex::new(&format!("({}):\\s*(.*)", "TODO")).unwrap()));
        assert_eq!(a.keyword, None);

        // test 3
        let mut a: Crumb = Default::default();
        a.content = "!TODO: test3".to_string();
        a.ignore = true;
        dbg!(&a);
        assert!(
            a.filter_keywords(&Regex::new(&format!("({}|{}):\\s*(.*)", "TODO", "MARK")).unwrap())
        );
        dbg!(&a);
        assert_eq!(a.keyword, Some("TODO".to_string()));
    }

    #[test]
    fn test_to_org() {
        let b0 = Bread::new("a".into(), vec![]);
        assert_eq!(b0.to_org().unwrap(), "* a\n".to_string());

        let b1 = Bread::new(
            "a".into(),
            vec![
                Crumb::new(1, 0, None, "1".to_string()),
                Crumb::new(2, 0, Some("TODO".to_string()), "2".to_string()),
            ],
        );
        assert_eq!(b1.to_org().unwrap(), "* a\n** TODO 2\n".to_string());
    }
}