arui_core/tree/root.rs
1//! # 项目树入口
2//! 用于初始化操作和启动目录树分析
3use crate::tree::config::ProjectConfig;
4use crate::tree::node::TreeNode;
5use crate::utils::{check_path, generate_id};
6use std::fs;
7use std::io::Result;
8use std::path::{Path, PathBuf};
9
10/// 项目目录树根节点
11/// 用于初始化操作和启动目录树分析
12pub struct ProjectTree {
13 /// 项目 ID,自动生成
14 pub id: String,
15 /// 项目别名,自定义
16 pub name: String,
17 /// 项目根路径,自定义
18 pub path: String,
19 /// 根节点,在 `build` 方法中生成,作为整个项目树迭代入口
20 pub root: Option<TreeNode>,
21 /// 项目树迭代配置,用于过滤、仅包含等等
22 pub config: Option<ProjectConfig>,
23}
24
25/// 初始化项目及构建属性
26impl ProjectTree {
27 /// 通过一系列参数初始化项目树
28 /// - name:项目别名
29 /// - path:根路径
30 /// - config:配置
31 ///
32 /// 但是需要注意,该操作仅初始化一个基础的项目树对象,可以通过该对象启动项目树的构建和分析:
33 /// - build: 构建项目树,生成各个节点,但不包含总结信息
34 /// - summarize: 遍历已有项目树,获取各个节点的总结信息
35 ///
36 /// # Example
37 ///
38 /// ```rust
39 /// use arui_core::tree::root::ProjectTree;
40 /// const NAME: &str = "test";
41 /// const PATH: &str = ".";
42 /// // 需要使用可变类型来初始化
43 /// let mut project = ProjectTree::new(NAME, PATH, None);
44 /// // 需要通过 build 和 summary 方法来启动项目树的构建和分析
45 /// project.build().unwrap();
46 /// project.summarize().unwrap();
47 /// ```
48 pub fn new<S, I>(name: S, path: I, config: Option<ProjectConfig>) -> Self
49 where
50 S: Into<String>,
51 I: Into<String>,
52 {
53 ProjectTree {
54 id: generate_id(),
55 name: name.into(),
56 path: path.into(),
57 root: None,
58 config,
59 }
60 }
61
62 /// 动态检查节点路径是否合法
63 ///
64 /// # Example
65 ///
66 /// ```rust
67 /// use arui_core::tree::root::ProjectTree;
68 /// // 只有在路径合法的情况下,才能进行项目树构建,但可正常初始化项目
69 /// let can_build = ProjectTree::new("test", "/@not_exist", None).is_valid();
70 /// println!("{}", can_build); // should be false
71 /// ```
72 pub fn is_valid(&self) -> bool {
73 check_path(&self.path).is_ok()
74 }
75
76 /// "种植"一棵项目树
77 /// - name:项目别名
78 /// - path:根路径
79 /// - config:配置
80 ///
81 /// 该操作和 `new` 的不同的地方在于,`new` 方法仅初始化项目树入口,并不完成后续操作:
82 /// - build: 从 `path` 启动,遍历并生成项目树
83 /// - summary: 从 `root` 启动,遍历并生成项目树各节点的总结信息
84 ///
85 /// # Example
86 ///
87 /// ```rust
88 /// use arui_core::tree::root::ProjectTree;
89 /// const NAME: &str = "test";
90 /// const PATH: &str = ".";
91 /// // 如果只用 `plant`,后续也不需要更新项目树,则可不使用 `mut`
92 /// let project = ProjectTree::plant(NAME, PATH, None);
93 /// ```
94 pub fn plant<S, I>(name: S, path: I, config: Option<ProjectConfig>) -> Self
95 where
96 S: Into<String>,
97 I: Into<String>,
98 {
99 let mut tree = ProjectTree::new(name, path, config);
100 tree.build().expect("project build panic");
101 tree.summarize().expect("project summarize panic");
102 tree
103 }
104
105 // ------------------------- 遍历构建节点 -------------------------
106
107 /// 构建项目文件树(不包含summary信息)
108 /// 通过 `path` 启动,遍历并生成项目树,但仅初始化各级树结构:
109 /// - path:节点对应文件/目录的路径
110 /// - is_dir:是否是文件夹
111 /// - children:子节点(is_dir为true时有值)
112 /// 其中并不包含 `summary` 字段的获取,需要单独调用 `summarize` 方法来获取
113 ///
114 /// # Example
115 ///
116 /// ```rust
117 /// use arui_core::tree::root::ProjectTree;
118 /// let mut project = ProjectTree::new("test", "./src", None);
119 /// project.build().unwrap();
120 /// ```
121 pub fn build(&mut self) -> Result<()> {
122 // 如果路径不合法,返回错误
123 // TODO: 修改为自定义错误
124 if !self.is_valid() {
125 return Err(std::io::Error::new(
126 std::io::ErrorKind::InvalidInput,
127 "invalid path",
128 ));
129 }
130 let root_path = PathBuf::from(&self.path);
131 // 尝试遍历构建项目树,生成各个节点
132 self.root = Some(Self::build_tree_node(&root_path)?);
133 Ok(())
134 }
135
136 /// 递归构建树节点
137 fn build_tree_node(path: &Path) -> Result<TreeNode> {
138 // 获取文件元数据
139 // TODO: 自定义错误处理
140 let metadata = fs::metadata(path)?;
141 let is_dir = metadata.is_dir();
142 // 创建节点
143 let mut node = TreeNode::new(path.to_string_lossy().into_owned(), is_dir);
144 // 如果是目录,递归构建该节点的子节点
145 // TODO:自定义错误
146 if is_dir {
147 let mut children = Vec::new();
148 for entry in fs::read_dir(path)? {
149 let entry = entry?;
150 children.push(Self::build_tree_node(&entry.path())?);
151 }
152 node.children = Some(children);
153 }
154
155 Ok(node)
156 }
157
158 // ------------------------- 生成总结信息 -------------------------
159
160 /// 生成项目树的总结信息
161 /// 从 `root` 启动,遍历并生成项目树各节点的总结信息
162 ///
163 /// # Example
164 ///
165 /// ```rust
166 /// use arui_core::tree::root::ProjectTree;
167 /// let mut project = ProjectTree::new("test", "./src", None);
168 /// project.build().unwrap();
169 /// project.summarize().unwrap();
170 /// println!("{}", project.root.as_ref().unwrap());
171 /// ```
172 pub fn summarize(&mut self) -> Result<()> {
173 // 如果根节点不存在,返回错误
174 if self.root.is_none() {
175 return Err(std::io::Error::new(
176 std::io::ErrorKind::InvalidInput,
177 "root is none, please build by `build()` first",
178 ));
179 }
180 // 如果根路径不合法,返回错误
181 if !self.is_valid() {
182 return Err(std::io::Error::new(
183 std::io::ErrorKind::InvalidInput,
184 "invalid root path, please check it",
185 ));
186 }
187 // 递归获取总结信息
188 self.root.as_mut().unwrap().upsert_summary();
189 Ok(())
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use super::*;
196 use crate::tree::visible::ProjectTreeVisible;
197
198 #[test]
199 fn test_new() {
200 let name = "test";
201 let path = ".".to_string();
202 let tree = ProjectTree::new(name, path, None);
203 assert_eq!(tree.id.len(), 36);
204 assert_eq!(tree.name, name.to_string());
205 assert_eq!(tree.path, ".".to_string());
206 }
207
208 #[test]
209 fn test_get_valid() {
210 let valid_tree = ProjectTree::new("test".to_string(), "./src".to_string(), None);
211 assert_eq!(valid_tree.is_valid(), true);
212 let invalid_tree = ProjectTree::new("test".to_string(), "/not_exist".to_string(), None);
213 assert_eq!(invalid_tree.is_valid(), false);
214 }
215
216 #[test]
217 fn test_build_project_tree() {
218 let name = "test";
219 let path = "./src";
220 let mut tree = ProjectTree::new(name, path, None);
221 tree.build().expect("panic");
222 assert_eq!(tree.root.is_some(), true);
223 assert_eq!(tree.name, "test".to_string());
224 assert_eq!(tree.path, "./src".to_string());
225 // 打印一下看看结构是否正确
226 tree.print_tree();
227 }
228
229 #[test]
230 fn test_plant() {
231 let name = "test";
232 let path = "./src";
233 let tree = ProjectTree::plant(name, path, None);
234 assert_eq!(tree.root.is_some(), true);
235 assert_eq!(tree.name, "test".to_string());
236 assert_eq!(tree.path, "./src".to_string());
237 // 打印一下看看结构是否正确
238 tree.print_tree();
239 println!("{}", tree.root.unwrap());
240 }
241}