diskr-cli 1.0.0

Save your disk space, without fear of deleting the wrong thing.
Documentation
mod common;
use common::TempDir;
use diskr::scanner::walker::{scan_path, WalkOptions};
use diskr::analyzer;
use std::fs;
use std::thread::sleep;
use std::time::Duration;

fn scan(dir: &TempDir) -> diskr::scanner::ScanResult {
    let opts = WalkOptions { show_progress: false, ..Default::default() };
    scan_path(dir.path(), &opts).unwrap()
}

#[test]
fn top_files_respects_min_size_and_order() {
    let dir = TempDir::new("size");
    fs::write(dir.path().join("small.bin"), vec![0u8; 10]).unwrap();
    fs::write(dir.path().join("big.bin"), vec![0u8; 5000]).unwrap();

    let result = scan(&dir);
    let top = analyzer::size::top_files(&result.root, 1000, 10);

    assert_eq!(top.len(), 1);
    assert!(top[0].path.ends_with("big.bin"));
}

#[test]
fn type_breakdown_groups_by_category() {
    let dir = TempDir::new("types");
    fs::write(dir.path().join("a.jpg"), vec![0u8; 100]).unwrap();
    fs::write(dir.path().join("b.png"), vec![0u8; 100]).unwrap();
    fs::write(dir.path().join("c.mp4"), vec![0u8; 100]).unwrap();

    let result = scan(&dir);
    let groups = analyzer::size::type_breakdown(&result.root);

    let image = groups.iter().find(|(name, _, _)| name == "image").unwrap();
    assert_eq!(image.2, 2);
    let video = groups.iter().find(|(name, _, _)| name == "video").unwrap();
    assert_eq!(video.2, 1);
}

#[test]
fn zero_byte_files_are_found() {
    let dir = TempDir::new("zero");
    fs::write(dir.path().join("empty.txt"), b"").unwrap();
    fs::write(dir.path().join("full.txt"), b"data").unwrap();

    let result = scan(&dir);
    let zero = analyzer::age::zero_byte_files(&result.root);

    assert_eq!(zero.len(), 1);
    assert!(zero[0].ends_with("empty.txt"));
}

#[test]
fn empty_dirs_are_found() {
    let dir = TempDir::new("emptydir");
    fs::create_dir_all(dir.path().join("nested/empty")).unwrap();
    fs::write(dir.path().join("nested/file.txt"), b"x").unwrap();

    let result = scan(&dir);
    let mut out = Vec::new();
    analyzer::age::empty_dirs(&result.root, &mut out);

    assert!(out.iter().any(|p| p.ends_with("empty")));
    assert!(!out.iter().any(|p| p.ends_with("nested")));
}

#[test]
fn duplicate_files_are_detected() {
    let dir = TempDir::new("dupes");
    let content = vec![7u8; 20_000];
    fs::write(dir.path().join("a.bin"), &content).unwrap();
    fs::write(dir.path().join("b.bin"), &content).unwrap();
    fs::write(dir.path().join("c.bin"), vec![9u8; 20_000]).unwrap();

    let result = scan(&dir);
    let groups = analyzer::duplicates::find_duplicates(&result.root, 1024);

    assert_eq!(groups.len(), 1);
    assert_eq!(groups[0].paths.len(), 2);
    assert_eq!(groups[0].wasted, content.len() as u64);
}

#[test]
fn dev_caches_are_detected() {
    let dir = TempDir::new("caches");
    fs::create_dir_all(dir.path().join("project/node_modules/pkg")).unwrap();
    fs::write(dir.path().join("project/node_modules/pkg/index.js"), b"x").unwrap();
    fs::create_dir_all(dir.path().join("project/src")).unwrap();

    let result = scan(&dir);
    let mut caches = Vec::new();
    analyzer::patterns::find_dev_caches(&result.root, &mut caches);

    assert_eq!(caches.len(), 1);
    assert_eq!(caches[0].kind, "node_modules");
}

#[test]
fn stale_files_respect_access_time_threshold() {
    let dir = TempDir::new("stale");
    fs::write(dir.path().join("recent.bin"), vec![0u8; 100_000]).unwrap();
    sleep(Duration::from_millis(10));

    let result = scan(&dir);
    let stale = analyzer::age::stale_files(&result.root, 9999, 1);

    assert!(stale.is_empty());
}

#[test]
fn cleanup_script_contains_every_path() {
    let paths = vec![std::path::PathBuf::from("/tmp/a"), std::path::PathBuf::from("/tmp/b")];
    let script = analyzer::extra::generate_cleanup_script(&paths, true);

    assert!(script.starts_with("#!/usr/bin/env bash"));
    assert!(script.contains("trash-put \"/tmp/a\""));
    assert!(script.contains("trash-put \"/tmp/b\""));
}