1use crate::fn_pathtype::{DIR_ICON, get_file_type};
3use crate::fn_weight::{WeightConfig, format_weight, get_path_weight};
4use std::cmp::Ordering;
5use std::collections::BTreeMap;
6use std::path::PathBuf;
7
8#[derive(Debug, Clone)]
10pub struct FileNode {
11 pub name: String,
12 pub path: PathBuf,
13 pub is_dir: bool,
14 pub icon: String,
15 pub weight_str: String, pub weight_bytes: u64, pub children: Vec<FileNode>,
18}
19
20fn sort_nodes(nodes: &mut [FileNode], sort_method: &str) {
22 match sort_method {
23 "files-first" => nodes.sort_by(|a, b| {
24 if a.is_dir == b.is_dir {
25 a.name.cmp(&b.name)
26 } else if !a.is_dir {
27 Ordering::Less
28 } else {
29 Ordering::Greater
30 }
31 }),
32 "dirs-first" => nodes.sort_by(|a, b| {
33 if a.is_dir == b.is_dir {
34 a.name.cmp(&b.name)
35 } else if a.is_dir {
36 Ordering::Less
37 } else {
38 Ordering::Greater
39 }
40 }),
41 _ => nodes.sort_by(|a, b| a.name.cmp(&b.name)),
42 }
43}
44
45pub fn filestree(
47 paths: Vec<PathBuf>,
48 sort_method: &str,
49 weight_cfg: &WeightConfig, ) -> Vec<FileNode> {
51 let mut tree_map: BTreeMap<PathBuf, Vec<PathBuf>> = BTreeMap::new();
52 for p in &paths {
53 let parent = p
54 .parent()
55 .map(|p| p.to_path_buf())
56 .unwrap_or_else(|| PathBuf::from("/"));
57 tree_map.entry(parent).or_default().push(p.clone());
58 }
59
60 fn build_node(
61 path: &PathBuf,
62 paths: &BTreeMap<PathBuf, Vec<PathBuf>>,
63 sort_method: &str,
64 weight_cfg: &WeightConfig, ) -> FileNode {
66 let name = path
67 .file_name()
68 .map(|n| n.to_string_lossy().to_string())
69 .unwrap_or_else(|| "/".to_string());
70
71 let is_dir = path.is_dir();
72
73 let icon = if is_dir {
74 DIR_ICON.to_string()
75 } else if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
76 get_file_type(ext).icon.to_string()
77 } else {
78 "📄".to_string()
79 };
80
81 let mut weight_bytes = get_path_weight(path, weight_cfg.dir_sum_included);
83
84 let mut children = vec![];
85 if let Some(child_paths) = paths.get(path) {
86 let mut child_nodes: Vec<FileNode> = child_paths
87 .iter()
88 .map(|c| build_node(c, paths, sort_method, weight_cfg))
89 .collect();
90
91 crate::fn_filestree::sort_nodes(&mut child_nodes, sort_method);
92
93 if is_dir && weight_cfg.dir_sum_included {
95 weight_bytes = child_nodes.iter().map(|n| n.weight_bytes).sum();
96 }
97
98 children = child_nodes;
99 }
100
101 let mut weight_str = String::new();
103
104 if weight_cfg.system != crate::fn_weight::UnitSystem::None {
106 let should_show =
107 (is_dir && weight_cfg.show_for_dirs) || (!is_dir && weight_cfg.show_for_files);
108
109 if should_show {
110 weight_str = format_weight(weight_bytes, weight_cfg);
111 } else {
112 let empty_width = 7 + weight_cfg.precision;
115 weight_str = format!("{:width$}", "", width = empty_width);
116 }
117 }
118
119 FileNode {
120 name,
121 path: path.clone(),
122 is_dir,
123 icon,
124 weight_str,
125 weight_bytes,
126 children,
127 }
128 }
129
130 let roots: Vec<PathBuf> = paths
131 .iter()
132 .filter(|p| p.parent().is_none() || !paths.contains(&p.parent().unwrap().to_path_buf()))
133 .cloned()
134 .collect();
135
136 let mut top_nodes: Vec<FileNode> = roots
137 .into_iter()
138 .map(|r| build_node(&r, &tree_map, sort_method, weight_cfg))
139 .collect();
140
141 crate::fn_filestree::sort_nodes(&mut top_nodes, sort_method);
142
143 top_nodes
144}