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 comment_symbol_endding: String,
74
75 pub(crate) has_tail: bool,
76
77 ignore: bool,
79
80 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 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 pub fn add_tail(&mut self, mut tail: Self) {
135 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 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 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 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 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 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
263impl 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 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 let mut a: Crumb = Default::default();
307 a.content = "!TODO: test3".to_string();
308 a.ignore = true;
309 assert!(
311 a.filter_keywords(&Regex::new(&format!("({}|{}):\\s*(.*)", "TODO", "MARK")).unwrap())
312 );
313 assert_eq!(a.keyword, Some("TODO".to_string()));
315 }
316}