use std::collections::BTreeMap;
use std::path::Path;
use colored::Colorize;
use humansize::{DECIMAL, format_size};
use rayon::prelude::*;
use crate::utils;
type Entry = (String, u64, bool);
#[derive(Debug, Default)]
struct TreeNode {
size: u64,
is_dir: bool,
children: BTreeMap<String, TreeNode>,
}
fn collect_entries(
path: &Path,
base_path: &Path,
depth: usize,
max_depth: usize,
) -> (Vec<Entry>, u64) {
if depth > max_depth {
return (vec![], 0);
}
let read_dir = match std::fs::read_dir(path) {
Ok(rd) => rd,
Err(_) => return (vec![], 0),
};
let dir_entries: Vec<_> = read_dir.filter_map(Result::ok).collect();
let results: Vec<(Vec<Entry>, u64)> = dir_entries
.par_iter()
.map(|entry| {
let file_type = match entry.file_type() {
Ok(ft) => ft,
Err(_) => return (vec![], 0),
};
if file_type.is_symlink() {
return (vec![], 0);
}
let entry_path = entry.path();
let relative_path = entry_path
.strip_prefix(base_path)
.unwrap_or(&entry_path)
.to_string_lossy()
.to_string();
if file_type.is_file() {
let size = entry.metadata().map(|m| m.len()).unwrap_or(0);
(vec![(relative_path, size, false)], size)
} else if file_type.is_dir() {
if depth < max_depth {
let (sub_entries, dir_size) =
collect_entries(&entry_path, base_path, depth + 1, max_depth);
let mut entries = vec![(relative_path, dir_size, true)];
entries.extend(sub_entries);
(entries, dir_size)
} else {
let dir_size = utils::calculate_dir_size(&entry_path);
(vec![(relative_path, dir_size, true)], dir_size)
}
} else {
(vec![], 0)
}
})
.collect();
let mut all_entries = Vec::new();
let mut total_size = 0u64;
for (entries, size) in results {
all_entries.extend(entries);
total_size += size;
}
(all_entries, total_size)
}
fn build_tree(entries: &[Entry]) -> TreeNode {
let mut root = TreeNode::default();
for (path, size, is_dir) in entries {
let parts: Vec<&str> = path.split(std::path::MAIN_SEPARATOR).collect();
let mut current = &mut root;
for (i, part) in parts.iter().enumerate() {
current = current.children.entry(part.to_string()).or_default();
if i == parts.len() - 1 {
current.size = *size;
current.is_dir = *is_dir;
}
}
}
root
}
fn render_tree(node: &TreeNode, prefix: &str, ascii: bool) -> String {
let mut output = String::new();
let mut children: Vec<_> = node.children.iter().collect();
children.sort_by(|a, b| a.0.cmp(b.0));
let (branch_last, branch_mid, pipe) = if ascii {
("`-- ", "+-- ", "| ")
} else {
("└── ", "├── ", "│ ")
};
for (i, (name, child)) in children.iter().enumerate() {
let is_last_child = i == children.len() - 1;
let branch = if is_last_child {
branch_last
} else {
branch_mid
};
let size_str = format_size(child.size, DECIMAL);
let formatted_line = if child.is_dir {
format!(
"{}{}{}/ ({})\n",
prefix,
branch,
name.blue(),
size_str.blue()
)
} else {
format!(
"{}{}{}{} ({})\n",
prefix,
branch,
name.green(),
"*".green(),
size_str.green()
)
};
output.push_str(&formatted_line);
if !child.children.is_empty() {
let new_prefix = if is_last_child {
format!("{} ", prefix)
} else {
format!("{}{}", prefix, pipe)
};
output.push_str(&render_tree(child, &new_prefix, ascii));
}
}
output
}
pub fn generate_tree(path: &Path, depth: Option<usize>, ascii: bool) -> String {
let max_depth = depth.unwrap_or(usize::MAX);
let (entries, _) = collect_entries(path, path, 1, max_depth);
let tree = build_tree(&entries);
render_tree(&tree, "", ascii)
}