Skip to main content

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}