use std::path::Path;
use clap::{ArgAction, Parser};
use colored::Colorize;
use colored::control::set_override;
use comfy_table::Table;
use comfy_table::presets::{ASCII_MARKDOWN, NOTHING};
use humansize::{DECIMAL, format_size};
use spinoff::{Color, Spinner, spinners};
use sysinfo::Disks;
use crate::tree;
use crate::utils;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Args {
#[arg(default_values_t = [".".to_string()])]
path: Vec<String>,
#[arg(long, short, action = ArgAction::SetTrue)]
sort_by_size: bool,
#[arg(long, action = ArgAction::SetTrue)]
disk_usage: bool,
#[arg(long, action = ArgAction::SetTrue)]
json: bool,
#[arg(long, short, action = ArgAction::SetTrue, conflicts_with_all = ["sort_by_size", "disk_usage", "json"])]
tree: bool,
#[arg(long, short, action = ArgAction::Set, requires = "tree")]
depth: Option<usize>,
#[arg(long, action = ArgAction::SetTrue, requires = "tree")]
ascii: bool,
#[arg(long, action = ArgAction::SetTrue)]
no_color: bool,
}
pub fn run() {
let cli = Args::parse();
if cli.no_color {
set_override(false);
}
let mut sp = if cli.json {
None
} else {
Some(Spinner::new(spinners::Dots, "Computing...", Color::Yellow))
};
let stop_spinner = |sp: &mut Option<Spinner>| {
if let Some(mut spinner) = sp.take() {
spinner.stop_with_message("");
}
};
if cli.tree {
for input_path in cli.path.iter() {
let path = Path::new(&input_path);
if !path.exists() {
stop_spinner(&mut sp);
println!("{} {}", input_path.red().bold(), "does not exist".red());
continue;
}
stop_spinner(&mut sp);
print!("{}", tree::generate_tree(path, cli.depth, cli.ascii));
}
return;
}
let mut sizes: Vec<utils::Sizes> = Vec::new();
for (index, input_path) in cli.path.iter().enumerate() {
let path = Path::new(&input_path);
if !path.exists() {
if index == 0 {
stop_spinner(&mut sp);
}
println!("{} {}", input_path.red().bold(), "does not exist".red());
if index == cli.path.len() - 1 {
return;
}
continue;
}
if path.is_file() {
match path.metadata() {
Ok(metadata) => {
let file_size = metadata.len();
if let Some(file_name) = path.file_name() {
let file_name = utils::truncate_filename(Path::new(file_name));
sizes.push(utils::Sizes {
name: file_name.to_string(),
size: file_size,
is_dir: false,
});
}
}
Err(e) => println!("Failed to get metadata for {}: {}", path.display(), e),
}
continue;
}
if let Ok(entries) = std::fs::read_dir(path) {
for entry in entries.flatten() {
let entry_path = entry.path();
if let Some(file_name) = entry_path.file_name().and_then(|n| n.to_str()) {
let file_name = utils::truncate_filename(Path::new(file_name));
match entry.file_type() {
Ok(file_type) => {
if file_type.is_file() {
if let Ok(metadata) = entry.metadata() {
sizes.push(utils::Sizes {
name: file_name.to_string(),
size: metadata.len(),
is_dir: false,
});
}
} else if file_type.is_dir() {
let dir_size = utils::calculate_dir_size(&entry_path);
sizes.push(utils::Sizes {
name: file_name.to_string(),
size: dir_size,
is_dir: true,
});
}
}
Err(e) => println!("Error getting file type: {}", e),
}
}
}
}
}
if sizes.is_empty() {
stop_spinner(&mut sp);
eprintln!("No files or folders found");
return;
}
if cli.sort_by_size {
utils::sort_by_size(&mut sizes);
} else {
utils::sort_by_name(&mut sizes);
}
if cli.json {
let json_entries: Vec<serde_json::Value> = sizes
.iter()
.map(|s| {
serde_json::json!({
"name": s.name,
"size_bytes": s.size,
"size_human": format_size(s.size, DECIMAL),
"is_dir": s.is_dir,
})
})
.collect();
match serde_json::to_string(&json_entries) {
Ok(json_output) => println!("{json_output}"),
Err(e) => eprintln!("Failed to serialize JSON: {e}"),
}
return;
}
let mut table = Table::new();
table.load_preset(NOTHING).set_width(80);
utils::add_row(&mut table, &sizes);
stop_spinner(&mut sp);
println!("{table}");
let total_size = sizes.iter().map(|s| s.size).sum::<u64>();
let sz = format_size(total_size, DECIMAL);
println!("\n{} {}", "Total size:".green(), sz.green().bold());
println!(
"{} {}\n",
"Number of files:".green(),
sizes.len().to_string().green().bold()
);
if cli.disk_usage {
let mut disk_table = Table::new();
disk_table
.load_preset(ASCII_MARKDOWN)
.set_header(vec!["Name", "Total", "Available"]);
let disks = Disks::new_with_refreshed_list();
for disk in &disks {
let disk_name = disk.name().to_str().unwrap_or("Unknown");
disk_table.add_row(vec![
disk_name.to_string(),
format_size(disk.total_space(), DECIMAL),
format_size(disk.available_space(), DECIMAL),
]);
}
println!("{disk_table}");
}
}