use std::env;
use std::fs;
use std::path::Path;
use rich_rs::{Console, Style, Text, Tree, TreeNodeOptions, filesize_decimal};
fn walk_directory(directory: &Path, tree: &mut Tree) {
let mut entries: Vec<_> = match fs::read_dir(directory) {
Ok(rd) => rd.filter_map(|e| e.ok()).collect(),
Err(_) => return,
};
entries.sort_by(|a, b| {
let a_is_file = a.file_type().map(|ft| ft.is_file()).unwrap_or(true);
let b_is_file = b.file_type().map(|ft| ft.is_file()).unwrap_or(true);
let a_name = a.file_name().to_string_lossy().to_lowercase();
let b_name = b.file_name().to_string_lossy().to_lowercase();
(a_is_file, a_name).cmp(&(b_is_file, b_name))
});
for entry in entries {
let path = entry.path();
let name = entry.file_name().to_string_lossy().to_string();
if name.starts_with('.') {
continue;
}
let is_dir = path.is_dir();
if is_dir {
let dim_style = Style::parse("dim").unwrap_or_default();
let label = Text::from_markup(
&format!(
"[bold magenta]:open_file_folder: [link file://{}]{}",
path.display(),
escape_markup(&name)
),
true,
)
.unwrap_or_else(|_| Text::plain(&name));
let branch = if name.starts_with("__") {
tree.add_with_options(
Box::new(label),
TreeNodeOptions::new()
.with_style(dim_style)
.with_guide_style(dim_style),
)
} else {
tree.add(Box::new(label))
};
walk_directory(&path, branch);
} else {
let mut text_filename = Text::styled(&name, Style::parse("green").unwrap_or_default());
text_filename
.highlight_regex(r"\.[^.]+$", Style::parse("bold red").unwrap_or_default());
let file_size = fs::metadata(&path).map(|m| m.len()).unwrap_or(0);
text_filename.append(
&format!(" ({})", filesize_decimal(file_size)),
Some(Style::parse("blue").unwrap_or_default()),
);
let extension = path.extension().and_then(|e| e.to_str()).unwrap_or("");
let icon = if extension == "py" {
"\u{1F40D} " } else {
"\u{1F4C4} " };
let mut label = Text::plain(icon);
label.append_text(&text_filename);
tree.add(Box::new(label));
}
}
}
fn escape_markup(s: &str) -> String {
s.replace('[', "\\[").replace(']', "\\]")
}
fn main() {
let args: Vec<String> = env::args().collect();
let directory = if args.len() > 1 {
match fs::canonicalize(&args[1]) {
Ok(path) => path,
Err(e) => {
eprintln!("Error: Cannot access '{}': {}", args[1], e);
std::process::exit(1);
}
}
} else {
let mut console = Console::new();
let usage = Text::from_markup("[b]Usage:[/] cargo run --example tree <DIRECTORY>", false)
.unwrap_or_else(|_| Text::plain("Usage: cargo run --example tree <DIRECTORY>"));
console
.print(&usage, None, None, None, false, "\n")
.unwrap();
return;
};
let root_label = Text::from_markup(
&format!(
":open_file_folder: [link file://{}]{}",
directory.display(),
directory.display()
),
true,
)
.unwrap_or_else(|_| Text::plain(&directory.display().to_string()));
let mut tree = Tree::new(Box::new(root_label))
.with_guide_style(Style::parse("bold bright_blue").unwrap_or_default());
walk_directory(&directory, &mut tree);
let mut console = Console::new();
console.print(&tree, None, None, None, false, "\n").unwrap();
}