uklid 0.1.1

Interactively delete node_modules.
#![feature(path_try_exists)]
use colored::Colorize;
use dialoguer::{theme::SimpleTheme, Confirm, Input, MultiSelect};
use fs_extra::dir::get_size;
use home::home_dir;
use human_bytes::human_bytes;
use std::fs::remove_dir_all;
use std::path::Path;

use walkdir::WalkDir;

fn main() {
    let hd = home_dir();
    let home_directory = match hd.as_ref() {
        None => "/",
        Some(dir) => dir.to_str().unwrap(),
    };

    let starting_directory: String = Input::new()
        .with_prompt("Where should I start searching?")
        .default(home_directory.into())
        .interact_text()
        .unwrap();

    Path::new(&starting_directory)
        .try_exists()
        .expect("Path doesn't exist");

    let mut node_modules: Vec<(String, u64)> = WalkDir::new(starting_directory)
        .follow_links(true)
        .into_iter()
        .filter(|e| e.is_ok())
        .filter(|e| {
            let matches = e
                .as_ref()
                .unwrap()
                .path()
                .to_string_lossy()
                .matches("node_modules")
                .count();
            return matches == 1
                && e.as_ref()
                    .unwrap()
                    .file_name()
                    .to_string_lossy()
                    .ends_with("node_modules");
        })
        .filter(|e| e.as_ref().unwrap().path().exists())
        .map(|e| {
            let owned_e = e.unwrap();
            return (
                owned_e.path().to_str().unwrap().to_owned(),
                get_size(owned_e.path()).unwrap_or(0),
            );
        })
        .collect();

    node_modules.sort_unstable_by_key(|k| k.1);
    node_modules.reverse();

    let _count = node_modules.len();
    let _total_size = human_bytes(node_modules.iter().fold(0, |acc, x| acc + x.1) as f64);

    println!("Found {_count} node_module folders with a total size of {_total_size}");

    let names: Vec<String> = node_modules
        .iter()
        .map(|e| {
            let color = match e.1 {
                1..=100_000_000 => "green",
                100_000_001..=1_000_000_000 => "yellow",
                _ => "red",
            };
            let _human_size = human_bytes(e.1 as f64).color(color);
            return format!("{_human_size} {path}", path = e.0);
        })
        .collect();

    let selections = MultiSelect::with_theme(&SimpleTheme)
        .with_prompt("Choose which node_modules to delete")
        .items(&names)
        .interact()
        .unwrap();

    let mut deleted = 0;

    if selections.is_empty() {
        println!("No node_modules selected. Aborting...");
    } else {
        println!("The following node_modules will be deleted:");
        for selection in &selections {
            println!("  {}", &names[*selection]);
        }
        if Confirm::new().with_prompt("Continue? ").interact().unwrap() {
            for selection in &selections {
                if remove_dir_all(&names[*selection]).is_ok() {
                    deleted += &node_modules[*selection].1;
                } else {
                    println!("Failed to delete {}", &names[*selection]);
                }
            }

            println!("Freed  {} bytes", human_bytes(deleted as f64));
        } else {
            println!("Aborting...");
        }
    }
}