1use regex::Regex;
2use serde::Serialize;
3use std::fmt;
4
5#[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)?; for c in &self.crumbs {
39 write!(f, " |-- {}", c)?;
40 }
41 Ok(())
42 }
43}
44
45#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize)]
47pub struct Crumb {
48 pub(crate) line_num: usize,
49
50 #[serde(skip)]
51 pub(crate) position: usize,
53
54 tails: Vec<Crumb>,
56
57 pub(crate) keyword: Option<String>,
58
59 pub(crate) view_content: String,
63
64 pub(crate) content: String,
67
68 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 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 pub fn add_tail(&mut self, tail: Self) {
116 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 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 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 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 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 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
221impl 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 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 let mut a: Crumb = Default::default();
265 a.content = "!TODO: test3".to_string();
266 a.ignore = true;
267 assert!(
269 a.filter_keywords(&Regex::new(&format!("({}|{}):\\s*(.*)", "TODO", "MARK")).unwrap())
270 );
271 assert_eq!(a.keyword, Some("TODO".to_string()));
273 }
274}