1use crate::doorstop::file;
20use serde::{Deserialize, Serialize};
21use std::collections::{BTreeMap, HashMap};
22use std::error::Error;
23use std::path::PathBuf;
24use std::rc::Rc;
25use std::str::FromStr;
26use walkdir::WalkDir;
27
28#[derive(Debug, PartialEq, Serialize, Deserialize)]
29pub struct DocumentSettings {
30 pub digits: i32,
31 pub parent: Option<String>,
32 pub prefix: String,
33 pub sep: String,
34}
35
36#[derive(Debug, PartialEq, Serialize, Deserialize)]
37pub struct Attributes {
38 pub publish: Option<Vec<String>>,
39}
40
41#[derive(Debug, PartialEq, Serialize, Deserialize)]
42pub struct DocumentConfig {
43 pub settings: DocumentSettings,
44 pub attributes: Option<Attributes>,
45}
46
47#[derive(Debug, PartialEq, Serialize, Deserialize)]
48pub struct Item {
49 pub id: Option<String>,
50 pub active: Option<bool>,
51 pub derived: Option<bool>,
52 pub header: Option<String>,
53 pub level: Option<String>,
54 pub normative: Option<bool>,
55 pub reviewed: Option<String>,
56 pub text: Option<String>,
57}
58
59#[derive(Debug, PartialEq)]
60pub enum LevelRelation {
61 SameLevel,
62 OutLevel(i32),
63 InLevel(i32),
64}
65
66impl Item {
67 pub fn level_relation(&self, other: &Self) -> LevelRelation {
68 let mut self_level = self.get_level_key();
69 let mut other_level = other.get_level_key();
70 while self_level.last().is_some_and(|x| *x == 0) {
72 self_level.pop();
73 }
74 while other_level.last().is_some_and(|x| *x == 0) {
75 other_level.pop();
76 }
77 let self_len = self_level.len();
78 let other_len = other_level.len();
79
80 match self_len.cmp(&other_len) {
81 std::cmp::Ordering::Less => LevelRelation::OutLevel((other_len - self_len) as i32),
82 std::cmp::Ordering::Equal => LevelRelation::SameLevel,
83 std::cmp::Ordering::Greater => LevelRelation::InLevel((self_len - other_len) as i32),
84 }
85 }
86
87 pub fn get_depth(&self) -> i32 {
91 let mut level = self.get_level_key();
92 while level.last().is_some_and(|l| 0 == *l) {
93 level.pop();
94 }
95 (level.len() - 1) as i32
96 }
97
98 pub fn new(path: PathBuf) -> Result<Self, Box<dyn Error>> {
99 let reader = std::fs::File::open(&path)?;
100 let mut item: Item = serde_yaml::from_reader(reader).unwrap();
101 let id = path
102 .file_stem()
103 .ok_or("Error on stem")?
104 .to_str()
105 .ok_or("Error on convert to str")?
106 .to_string();
107 item.id = Some(id);
108 Ok(item)
109 }
110
111 pub fn get_level(&self) -> &str {
112 match &self.level {
113 Some(s) => s,
114 None => "1",
115 }
116 }
117
118 pub fn get_level_key(&self) -> Vec<i32> {
119 let level = self.get_level();
120 level
121 .split(".")
122 .filter_map(|level| i32::from_str(level).ok())
123 .collect()
124 }
125}
126
127#[derive(Debug)]
128pub struct Document {
129 pub config: DocumentConfig,
130 pub root_path: PathBuf,
131 pub items: HashMap<Rc<String>, Rc<Item>>,
132 pub items_sorted_by_level: BTreeMap<Vec<i32>, Rc<Item>>,
133}
134
135impl Document {
136 pub fn new(path: PathBuf) -> Result<Self, Box<dyn Error>> {
137 let reader = std::fs::File::open(&path)?;
138 let document_config: DocumentConfig = serde_yaml::from_reader(reader).unwrap();
139 let id_item: HashMap<Rc<String>, Rc<Item>> = HashMap::new();
140
141 let mut doc = Document {
142 config: document_config,
143 root_path: path,
144 items: id_item,
145 items_sorted_by_level: BTreeMap::new(),
146 };
147
148 for each_item_path in doc.get_items_files() {
149 let i = Rc::new(Item::new(each_item_path)?);
150 let a = Rc::new(i.id.as_ref().unwrap().clone());
151 doc.items.insert(a.clone(), i.clone());
152 doc.items_sorted_by_level.insert(i.get_level_key(), i);
153 }
154 Ok(doc)
155 }
156
157 fn get_items_files(&self) -> Vec<PathBuf> {
158 let d = self.root_path.parent().unwrap();
159 let into_iter = WalkDir::new(d).into_iter();
160
161 let only_reqs_yml = into_iter
162 .filter_map(|e| e.ok())
163 .filter(|e| file::is_yml_with_prefix(&e, &self.config.settings.prefix));
164
165 only_reqs_yml.filter_map(|e| Some(e.into_path())).collect()
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use crate::doorstop::document::LevelRelation;
172
173 use super::{Document, Item};
174 use std::{path::Path, rc::Rc};
175
176 #[test]
177 fn test_new_document() {
178 let p = Path::new("resources/reqs/.doorstop.yml");
179 let d = Document::new(p.to_path_buf());
180 let k = Rc::new("REQ012".to_string());
181 assert!(d.is_ok());
182 assert!(
183 d.unwrap().items.get(k.as_ref()).is_some(),
184 "Getting one item"
185 );
186 }
187
188 #[test]
189 fn test_new_document_ext() {
190 let p = Path::new("resources/reqs/ext/.doorstop.yml");
191 let d = Document::new(p.to_path_buf());
192 assert!(d.is_ok());
193 }
194
195 #[test]
196 fn test_get_items() {
197 let p = Path::new("resources/reqs/.doorstop.yml");
198 let d = Document::new(p.to_path_buf()).unwrap();
199 let files = d.get_items_files();
200 assert_eq!(18, files.len(), "Wrong amount of files detected")
201 }
202
203 #[test]
204 fn test_deserialize_item() {
205 let p = Path::new("resources/reqs/REQ004.yml");
206 let item = Item::new(p.to_path_buf()).ok().unwrap();
207 assert!(item.active.unwrap());
208 assert!(!item.derived.unwrap());
209 assert_eq!("REQ004", item.id.unwrap());
210 }
211
212 #[test]
213 fn test_item_get_depth_default() {
214 let item = Item {
215 id: None,
216 active: None,
217 derived: None,
218 header: None,
219 level: None,
220 normative: None,
221 reviewed: None,
222 text: None,
223 };
224 assert_eq!(0, item.get_depth());
225 }
226
227 #[test]
228 fn test_item_get_depth_1() {
229 let item = Item {
230 id: None,
231 active: None,
232 derived: None,
233 header: None,
234 level: Some("2.10".to_string()),
235 normative: None,
236 reviewed: None,
237 text: None,
238 };
239 assert_eq!(1, item.get_depth());
240 }
241
242 #[test]
243 fn test_item_get_depth_0() {
244 let item = Item {
245 id: None,
246 active: None,
247 derived: None,
248 header: None,
249 level: Some("2.0".to_string()),
250 normative: None,
251 reviewed: None,
252 text: None,
253 };
254 assert_eq!(0, item.get_depth());
255 }
256
257 #[test]
258 fn test_sort_by_level() {
259 let p = Path::new("resources/reqs/.doorstop.yml");
260 let d = Document::new(p.to_path_buf()).unwrap();
261 let level_stack = 0;
262 for (_, each_item) in d.items_sorted_by_level.iter() {
263 println!("{},{:?}-{:?}", level_stack, each_item.level, each_item.id);
264 }
265 }
266 #[test]
267 fn test_level_relation_same() {
268 let a = Item {
269 level: Some("1.0".to_string()),
270 id: None,
271 active: None,
272 derived: None,
273 header: None,
274 normative: None,
275 reviewed: None,
276 text: None,
277 };
278 let b = Item {
279 level: Some("2.0".to_string()),
280 id: None,
281 active: None,
282 derived: None,
283 header: None,
284 normative: None,
285 reviewed: None,
286 text: None,
287 };
288 assert_eq!(a.level_relation(&b), LevelRelation::SameLevel);
289 }
290
291 #[test]
292 fn test_level_relation_level_out() {
293 let a = Item {
294 level: Some("1.2".to_string()),
295 id: None,
296 active: None,
297 derived: None,
298 header: None,
299 normative: None,
300 reviewed: None,
301 text: None,
302 };
303 let b = Item {
304 level: Some("2.0".to_string()),
305 id: None,
306 active: None,
307 derived: None,
308 header: None,
309 normative: None,
310 reviewed: None,
311 text: None,
312 };
313 assert_eq!(b.level_relation(&a), LevelRelation::OutLevel(1));
314 }
315
316 #[test]
317 fn test_level_relation_level_out_2() {
318 let a = Item {
319 level: Some("1.2.1".to_string()),
320 id: None,
321 active: None,
322 derived: None,
323 header: None,
324 normative: None,
325 reviewed: None,
326 text: None,
327 };
328 let b = Item {
329 level: Some("2.0".to_string()),
330 id: None,
331 active: None,
332 derived: None,
333 header: None,
334 normative: None,
335 reviewed: None,
336 text: None,
337 };
338 assert_eq!(b.level_relation(&a), LevelRelation::OutLevel(2));
339 }
340
341 #[test]
342 fn test_level_relation_level_in() {
343 let a = Item {
344 level: Some("1.0".to_string()),
345 id: None,
346 active: None,
347 derived: None,
348 header: None,
349 normative: None,
350 reviewed: None,
351 text: None,
352 };
353 let b = Item {
354 level: Some("1.2".to_string()),
355 id: None,
356 active: None,
357 derived: None,
358 header: None,
359 normative: None,
360 reviewed: None,
361 text: None,
362 };
363 assert_eq!(b.level_relation(&a), LevelRelation::InLevel(1));
364 }
365}