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,
74
75 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 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 pub fn add_tail(&mut self, tail: Self) {
121 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 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 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 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 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 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
238impl 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 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 let mut a: Crumb = Default::default();
282 a.content = "!TODO: test3".to_string();
283 a.ignore = true;
284 assert!(
286 a.filter_keywords(&Regex::new(&format!("({}|{}):\\s*(.*)", "TODO", "MARK")).unwrap())
287 );
288 assert_eq!(a.keyword, Some("TODO".to_string()));
290 }
291}