arui_core/tree/summary.rs
1//! # 节点总结信息相关
2//! 负责计算和汇总节点所在的总结信息。
3//! 该模块仅维护数据层面的字段和接口,而不负责数据获取等等操作。
4//! 对于不同的节点,计算规则如下:
5//! - 文件:直接计算
6//! - 目录:统计目录下所有文件的累加
7use crate::tree::node::TreeNode;
8use crate::tree::node::count::get_file_count;
9use crate::tree::node::file::get_file_size;
10use std::fmt::Display;
11
12#[derive(Debug, Clone)]
13/// 节点总结信息
14/// - size: 磁盘占用大小
15/// - count: 包含文本行数
16/// - updated_at: 最后更新时间
17/// - suffixes: 后缀
18/// - 文件:当前文件的后缀
19/// - 目录:当前目录下所有文件的后缀
20pub struct NodeSummary {
21 /// u64 磁盘占用大小,默认为 0
22 pub size: u64,
23 /// u64 包含文本行数,默认为 0
24 pub count: u64,
25 /// 最后更新时间,若没有启动 `project_tree.summarize` 则为空
26 pub updated_at: Option<std::time::SystemTime>,
27 /// 包含的文件后缀,默认为空
28 pub suffixes: Vec<String>,
29}
30
31impl Display for NodeSummary {
32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 write!(
34 f,
35 "\n size: {},\n count: {},\n updated_at: {:?},\n suffixes: {:?}",
36 self.size, self.count, self.updated_at, self.suffixes
37 )
38 }
39}
40
41impl NodeSummary {
42 /// 创建节点总结实例
43 /// 以默认值填充:
44 /// - size: 0
45 /// - count: 0
46 /// - updated_at: None
47 /// - suffixes: Vec::new
48 pub fn new() -> Self {
49 NodeSummary {
50 size: 0,
51 count: 0,
52 updated_at: None,
53 suffixes: Vec::new(),
54 }
55 }
56
57 // 更新节点信息
58 // 任意节点可以为文件,也可以为目录,所以我们需要对两种情况都做判断,并使用不同的分支处理
59 //
60 // ### 文件
61 // 如果节点为文件,直接调用文件的总结函数然后赋值即可
62 //
63 // ### 目录
64 // 如果节点为目录,则需要递归的调用子节点的总结函数,由底向上调用总结信息,直到根节点
65 // 在每次发现节点是目录时,不对其调用总结函数,而是继续递归调用子节点的总结函数
66 // 直到最后子项遍历完毕,目录节点对子节点进行累加得到最终结果。
67 // ----------------- 以上注释为内部注释 -----------------
68 // ----------------- 以下注释为文档注释 -----------------
69 /// 更新节点的总结信息
70 /// - node:{&mut TreeNode} 可变节点实例
71 /// - return:{NodeSummary}
72 /// 该函数属于 `NodeSummary` 模块,不直接绑定于 `TreeNode`,即不直接修改 `TreeNode.summary`
73 /// 若有手动更新某节点信息的需求,则需要在调用该函数后手动赋值 `node.summary = summary;`
74 /// 但在某节点的 `update` 过程中,子节点的 `summary` 会自动赋值,无需手动处理,最终返回的总结信息为当前启动节点的总结信息
75 ///
76 /// # Examples
77 ///
78 /// ```rust
79 /// use arui_core::tree::node::TreeNode;
80 /// use arui_core::tree::summary::NodeSummary;
81 ///
82 /// const BASE_URL: &str = "./tests/examples/tree/summary";
83 /// let mut node = TreeNode::new(BASE_URL, true);
84 /// let sub_node = TreeNode::new(format!("{}/test.rs", BASE_URL), false);
85 /// node.children = Some(vec![sub_node]);
86 ///
87 /// let summary = NodeSummary::update(&mut node);
88 /// // 手动赋值!
89 /// node.summary = summary;
90 ///
91 /// assert_eq!(node.summary.size > 0, true);
92 /// assert_eq!(node.summary.count > 0, true);
93 /// ```
94 pub fn update(node: &mut TreeNode) -> NodeSummary {
95 // 实例化节点总结对象
96 let mut summary = NodeSummary::new();
97 summary.updated_at = Some(std::time::SystemTime::now());
98
99 // 若非目录,直接计算当前文件,并终止递归
100 if !node.is_dir {
101 summary.size = get_file_size(&node.path).unwrap_or(0);
102 summary.count = get_file_count(&node.path).unwrap_or(0);
103 return summary;
104 }
105
106 // 若为目录,递归遍历所有子节点,从底向上获取总结信息,直到根节点
107 if let Some(children) = &mut node.children {
108 for child in children {
109 // 递归调用子节点
110 let child_summary = NodeSummary::update(child);
111 // 写入子节点
112 child.summary = child_summary.clone();
113
114 // 累加到父节点
115 summary.size += child_summary.size;
116 summary.count += child_summary.count;
117 // summary.suffixes.extend(child_summary.suffixes.clone());
118 }
119 }
120
121 summary
122 }
123}
124
125// --------------------- 单元测试 ---------------------
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130 use crate::tree::node::TreeNode;
131
132 #[test]
133 fn test_display() {
134 let ns = NodeSummary::new();
135 println!("{}", ns);
136 }
137
138 #[test]
139 fn test_create_summary() {
140 let ns = NodeSummary::new();
141 assert_eq!(ns.size, 0);
142 assert_eq!(ns.count, 0);
143 assert_eq!(ns.updated_at, None);
144 assert_eq!(ns.suffixes.len(), 0);
145 }
146
147 #[test]
148 fn test_summary_with_file() {
149 // 创建节点
150 let mut node = TreeNode::new("./tests/examples/tree/summary/test.txt", false);
151 // 启动节点总结
152 node.upsert_summary();
153 println!("{}", node);
154 }
155
156 #[test]
157 fn test_summary_with_dir() {
158 // 创建节点
159 let mut node = TreeNode::new("./tests/examples/tree/summary", true);
160 let sub_node_1 = TreeNode::new("./tests/examples/tree/summary/test.txt", false);
161 let sub_node_2 = TreeNode::new("./tests/examples/tree/summary/test.rs", false);
162 let sub_node_3 = TreeNode::new("./tests/examples/tree/summary/test.js", false);
163 // 模拟子节点
164 node.children = Some(vec![sub_node_1, sub_node_2, sub_node_3]);
165 // 启动节点总结
166 node.upsert_summary();
167 // 打印子节点信息
168 if let Some(children) = &node.children {
169 for child in children {
170 println!("{}", child.summary);
171 }
172 }
173 // 打印父节点信息
174 println!("{}", node);
175 }
176}