doorstop_rs/doorstop/
document_tree.rs1use crate::doorstop::document::Document;
20use crate::doorstop::file;
21use thiserror::Error;
22use walkdir::WalkDir;
23
24use std::{cell::RefCell, collections::HashMap, error::Error, path::PathBuf, rc::Rc};
25
26#[derive(Debug)]
27pub struct DocumentTree {
28 pub document: Rc<Document>,
29 pub children: Vec<Rc<RefCell<DocumentTree>>>,
30 pub prefix_index: Rc<RefCell<HashMap<String, Rc<RefCell<DocumentTree>>>>>,
32}
33
34#[derive(Error, Debug)]
35#[error("{msg}")]
36pub struct LoadError {
37 msg: String,
38}
39
40impl DocumentTree {
41 fn add_child(&mut self, child: Rc<RefCell<DocumentTree>>) {
42 self.children.push(child);
43 }
44
45 pub fn load(path: &str) -> Result<Rc<RefCell<DocumentTree>>, Box<dyn Error>> {
46 let ymls = find_document_yml(path);
47 if ymls.len() == 0 {
48 let s = "No documents found in ".to_string() + path;
49 return Err(Box::new(LoadError { msg: s }));
50 }
51 build_doc_tree(ymls)
52 }
53}
54
55fn build_doctree_index(
56 paths: Vec<PathBuf>,
57) -> Result<Rc<RefCell<HashMap<String, Rc<RefCell<DocumentTree>>>>>, Box<dyn Error>> {
58 let prefix_doc_map: Rc<RefCell<HashMap<String, Rc<RefCell<DocumentTree>>>>> =
59 Rc::new(RefCell::new(HashMap::new()));
60
61 for each_path in paths {
62 let doc = Document::new(each_path)?;
63
64 let each_tree = DocumentTree {
65 document: Rc::new(doc),
66 children: Vec::new(),
67 prefix_index: Rc::clone(&prefix_doc_map),
68 };
69
70 prefix_doc_map.borrow_mut().insert(
71 each_tree.document.config.settings.prefix.clone(),
72 Rc::new(RefCell::new(each_tree)),
73 );
74 }
75
76 Ok(prefix_doc_map)
77}
78
79fn build_doc_tree(paths: Vec<PathBuf>) -> Result<Rc<RefCell<DocumentTree>>, Box<dyn Error>> {
80 let mut root: Option<Rc<RefCell<DocumentTree>>> = None;
81 let prefix_doc_map = build_doctree_index(paths)?;
82
83 for (_, value) in prefix_doc_map.borrow().iter() {
85 let doc_tree = (**value).borrow();
86 let parent_prefix = &doc_tree.document.config.settings.parent;
87
88 match parent_prefix {
89 Some(p) => {
90 let h = prefix_doc_map.borrow();
91 let parent = h.get(p).unwrap();
92 (**parent).borrow_mut().add_child(value.clone());
93 }
94 None => {
95 root = Some(value.clone());
96 }
97 }
98 }
99 Ok(root.unwrap())
100}
101
102pub fn find_document_yml(path: &str) -> Vec<PathBuf> {
103 let into_iter = WalkDir::new(path).into_iter();
104
105 let only_doorstop_yml = into_iter.filter_entry(|e| !file::is_hidden_dir(e));
106
107 let iterator = only_doorstop_yml.filter(|e| e.as_ref().is_ok_and(file::is_doorstop_config));
108
109 let res: Vec<PathBuf> = iterator
110 .filter_map(|e| e.ok())
111 .filter_map(|e| Some(e.into_path()))
112 .collect();
113 res
114}
115
116#[cfg(test)]
117mod tests {
118
119 use crate::doorstop::document_tree::DocumentTree;
120
121 use super::find_document_yml;
122
123 #[test]
124 fn test_find_document_yml() {
125 let ymls = find_document_yml("resources/reqs");
126 assert_eq!(3, ymls.len());
127 }
128
129 #[test]
130 fn test_build_tree() {
131 let document_tree = DocumentTree::load("resources/reqs").unwrap();
132 let document_tree = document_tree.borrow();
133
134 assert_eq!(
135 String::from("REQ"),
136 document_tree.document.config.settings.prefix,
137 "testing REQ is the root prefix"
138 );
139
140 assert_eq!(2, document_tree.children.len(), "Wrong amount of children");
141 }
142
143 #[test]
144 fn get_document_by_index() {
145 let document_tree = DocumentTree::load("resources/reqs").unwrap();
146 let document_tree = document_tree.borrow();
147
148 let h = document_tree.prefix_index.borrow();
149 let doc = h.get("TUT");
150 assert_eq!(
151 String::from("TUT"),
152 doc.unwrap().borrow().document.config.settings.prefix,
153 "testing REQ is the root prefix"
154 );
155
156 }
157
158 #[test]
159 fn test_err_build_tree() {
160 let document_tree = DocumentTree::load("nonexistent-resources/reqs");
161 assert!(document_tree.is_err());
162 }
163}