From a6e64fa3d35b1994a0a4a54920eac10b8b4eec26 Mon Sep 17 00:00:00 2001 From: "heesk0223@gmail.com" Date: Fri, 19 Jun 2026 17:21:15 +0900 Subject: [PATCH] scan / analyze / clean pattern unification --- Cargo.lock | 33 ++++++++------- apps/dustfril-cli/Cargo.toml | 3 +- apps/dustfril-cli/src/commands/analyze.rs | 22 +++++++--- apps/dustfril-cli/src/commands/clean.rs | 41 ++++++++++++------ apps/dustfril-cli/src/commands/scan.rs | 9 +++- apps/dustfril-cli/src/format/mod.rs | 5 +++ .../dustfril-cli/src/format/size_format.rs | 21 ++++++++++ .../dustfril-cli/src/format/time_format.rs | 0 apps/dustfril-cli/src/main.rs | 1 + crates/dustfril-core/Cargo.toml | 4 +- crates/dustfril-core/src/analyzer/analyze.rs | 5 ++- crates/dustfril-core/src/analyzer/tests.rs | 35 ++++------------ crates/dustfril-core/src/api/analyze.rs | 8 +++- crates/dustfril-core/src/api/clean.rs | 7 ++-- crates/dustfril-core/src/api/scan.rs | 4 +- crates/dustfril-core/src/cleaner/executor.rs | 9 ++-- crates/dustfril-core/src/cleaner/plan.rs | 23 +++++----- crates/dustfril-core/src/cleaner/tests.rs | 14 +++---- crates/dustfril-core/src/detector/project.rs | 18 ++++---- crates/dustfril-core/src/detector/rust/git.rs | 6 +-- .../src/detector/rust/registry.rs | 6 +-- .../dustfril-core/src/detector/rust/target.rs | 6 +-- crates/dustfril-core/src/detector/scan.rs | 18 ++++---- crates/dustfril-core/src/detector/tests.rs | 8 ++-- crates/dustfril-core/src/error.rs | 42 +++++++++++++++++++ crates/dustfril-core/src/format/mod.rs | 5 --- crates/dustfril-core/src/lib.rs | 2 +- .../src/models/analysis_result.rs | 4 +- .../{artifact_location.rs => artifact.rs} | 7 ++-- .../src/models/artifact_analysis.rs | 8 ++-- .../dustfril-core/src/models/artifact_type.rs | 3 +- .../src/models/cleanup_candidate.rs | 4 +- .../dustfril-core/src/models/cleanup_plan.rs | 4 +- .../src/models/cleanup_recommendation.rs | 4 +- .../src/models/cleanup_result.rs | 4 +- crates/dustfril-core/src/models/ecosystem.rs | 8 ++++ crates/dustfril-core/src/models/mod.rs | 6 ++- .../dustfril-core/src/models/scan_result.rs | 8 ++-- 38 files changed, 265 insertions(+), 150 deletions(-) create mode 100644 apps/dustfril-cli/src/format/mod.rs rename crates/dustfril-core/src/format/size.rs => apps/dustfril-cli/src/format/size_format.rs (56%) rename crates/dustfril-core/src/format/date.rs => apps/dustfril-cli/src/format/time_format.rs (100%) create mode 100644 crates/dustfril-core/src/error.rs delete mode 100644 crates/dustfril-core/src/format/mod.rs rename crates/dustfril-core/src/models/{artifact_location.rs => artifact.rs} (67%) create mode 100644 crates/dustfril-core/src/models/ecosystem.rs diff --git a/Cargo.lock b/Cargo.lock index 35301a8..1955d46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -87,9 +87,9 @@ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "cc" -version = "1.2.63" +version = "1.2.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f" dependencies = [ "find-msvc-tools", "shlex", @@ -103,9 +103,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", "js-sys", @@ -170,6 +170,7 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" name = "dustfril-cli" version = "0.1.0" dependencies = [ + "chrono", "clap", "dustfril-core", ] @@ -178,7 +179,7 @@ dependencies = [ name = "dustfril-core" version = "0.1.0" dependencies = [ - "chrono", + "serde", "tempfile", ] @@ -330,13 +331,12 @@ checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.99" +version = "0.3.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] @@ -463,6 +463,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", + "serde_derive", ] [[package]] @@ -578,9 +579,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.122" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a" dependencies = [ "cfg-if", "once_cell", @@ -591,9 +592,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.122" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -601,9 +602,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.122" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd" dependencies = [ "bumpalo", "proc-macro2", @@ -614,9 +615,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.122" +version = "0.2.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f" dependencies = [ "unicode-ident", ] diff --git a/apps/dustfril-cli/Cargo.toml b/apps/dustfril-cli/Cargo.toml index 7b70749..4fd0f20 100644 --- a/apps/dustfril-cli/Cargo.toml +++ b/apps/dustfril-cli/Cargo.toml @@ -9,5 +9,6 @@ path = "src/main.rs" [dependencies] dustfril-core = { path = "../../crates/dustfril-core" } +chrono = "0.4" +clap = { version = "4", features = ["derive"] } -clap = { version = "4", features = ["derive"] } \ No newline at end of file diff --git a/apps/dustfril-cli/src/commands/analyze.rs b/apps/dustfril-cli/src/commands/analyze.rs index e035aec..0d93d4b 100644 --- a/apps/dustfril-cli/src/commands/analyze.rs +++ b/apps/dustfril-cli/src/commands/analyze.rs @@ -1,7 +1,8 @@ +use dustfril_core::api; use dustfril_core::models::{AnalysisResult, CleanupRecommendation}; -use dustfril_core::{api, format}; use crate::cli::PathArgs; +use crate::format; use crate::shared::path::{resolve_path, validate_path}; fn print_summary(analysis: &AnalysisResult) { @@ -59,12 +60,25 @@ pub fn execute(args: PathArgs) { let path = resolve_path(&args.path); if !validate_path(&path) { + eprintln!("Invalid path"); return; } - let scan_result = api::scan(&path, args.global); + let scan_result = match api::scan(&path, args.global) { + Ok(res) => res, + Err(e) => { + eprintln!("Scan failed: {}", e); + return; + } + }; - let analysis_result = api::analyze(scan_result); + let analysis_result = match api::analyze(scan_result) { + Ok(res) => res, + Err(e) => { + eprintln!("Analysis failed: {}", e); + return; + } + }; if analysis_result.artifacts.is_empty() { println!("No Rust artifacts found."); @@ -75,9 +89,7 @@ pub fn execute(args: PathArgs) { for artifact in &analysis_result.artifacts { println!("[{}]", artifact.artifact.artifact_type); - println!(" Path: {}", artifact.artifact.path.display()); - println!(" Size: {}", format::format_size(artifact.size_bytes)); println!( diff --git a/apps/dustfril-cli/src/commands/clean.rs b/apps/dustfril-cli/src/commands/clean.rs index 7a51214..aaeb354 100644 --- a/apps/dustfril-cli/src/commands/clean.rs +++ b/apps/dustfril-cli/src/commands/clean.rs @@ -1,35 +1,39 @@ use dustfril_core::{ - api, format, + api, + error::DustError, models::{CleanupPlan, CleanupResult}, }; -use crate::{cli::CleanArgs, shared::path::resolve_path}; +use crate::{cli::CleanArgs, format, shared::path::resolve_path}; -// dry-run pub fn dry_run(args: &CleanArgs) { - let plan = build_cleanup_plan(args); + let plan = match build_cleanup_plan(args) { + Ok(plan) => plan, + Err(e) => { + eprintln!("Scan failed: {}", e); + return; + } + }; if plan.candidates.is_empty() { println!("No cleanup candidates found."); - return; } print_cleanup_plan(&plan); - println!("No files were deleted."); } use std::io::{self, Write}; -fn build_cleanup_plan(args: &CleanArgs) -> CleanupPlan { +fn build_cleanup_plan(args: &CleanArgs) -> Result { let path = resolve_path(&args.path_args.path); - let scan_result = api::scan(&path, args.path_args.global); + let scan = api::scan(&path, args.path_args.global)?; + let plan = api::clean::build_plan(scan)?; - api::clean::build_plan(scan_result) + Ok(plan) } - fn confirm_cleanup() -> bool { print!("Continue? (y/N): "); @@ -80,9 +84,14 @@ fn print_cleanup_result(result: &CleanupResult) { println!(); } } - pub fn execute(args: &CleanArgs) { - let plan = build_cleanup_plan(args); + let plan = match build_cleanup_plan(args) { + Ok(plan) => plan, + Err(e) => { + eprintln!("Scan failed: {}", e); + return; + } + }; if plan.candidates.is_empty() { println!("No cleanup candidates found."); @@ -96,7 +105,13 @@ pub fn execute(args: &CleanArgs) { return; } - let result = api::clean::execute(&plan); + let result = match api::clean::execute(&plan) { + Ok(res) => res, + Err(e) => { + eprintln!("Cleanup failed: {}", e); + return; + } + }; print_cleanup_result(&result); } diff --git a/apps/dustfril-cli/src/commands/scan.rs b/apps/dustfril-cli/src/commands/scan.rs index fca9ca0..e2dea0a 100644 --- a/apps/dustfril-cli/src/commands/scan.rs +++ b/apps/dustfril-cli/src/commands/scan.rs @@ -9,10 +9,17 @@ pub fn execute(args: PathArgs) { let path = resolve_path(&args.path); if !validate_path(&path) { + eprintln!("Invalid path"); return; } - let result = api::scan(&path, args.global); + let result = match api::scan(&path, args.global) { + Ok(res) => res, + Err(e) => { + eprintln!("Scan failed: {}", e); + return; + } + }; if result.artifacts.is_empty() { println!("No Rust artifacts found."); diff --git a/apps/dustfril-cli/src/format/mod.rs b/apps/dustfril-cli/src/format/mod.rs new file mode 100644 index 0000000..ae2cf19 --- /dev/null +++ b/apps/dustfril-cli/src/format/mod.rs @@ -0,0 +1,5 @@ +pub mod size_format; +pub mod time_format; + +pub use size_format::*; +pub use time_format::*; diff --git a/crates/dustfril-core/src/format/size.rs b/apps/dustfril-cli/src/format/size_format.rs similarity index 56% rename from crates/dustfril-core/src/format/size.rs rename to apps/dustfril-cli/src/format/size_format.rs index 06b65d3..6fada0f 100644 --- a/crates/dustfril-core/src/format/size.rs +++ b/apps/dustfril-cli/src/format/size_format.rs @@ -18,3 +18,24 @@ pub fn format_size(bytes: u64) -> String { format!("{:.0} B", bytes) } } + +#[test] +fn format_size_bytes() { + assert_eq!(format_size(512), "512 B"); +} + +#[test] +fn format_size_kilobytes() { + assert_eq!(format_size(1024), "1.00 KB"); + assert_eq!(format_size(2048), "2.00 KB"); +} + +#[test] +fn format_size_megabytes() { + assert_eq!(format_size(1024 * 1024), "1.00 MB"); +} + +#[test] +fn format_size_gigabytes() { + assert_eq!(format_size(1024 * 1024 * 1024), "1.00 GB"); +} diff --git a/crates/dustfril-core/src/format/date.rs b/apps/dustfril-cli/src/format/time_format.rs similarity index 100% rename from crates/dustfril-core/src/format/date.rs rename to apps/dustfril-cli/src/format/time_format.rs diff --git a/apps/dustfril-cli/src/main.rs b/apps/dustfril-cli/src/main.rs index 6f136cd..2725d46 100644 --- a/apps/dustfril-cli/src/main.rs +++ b/apps/dustfril-cli/src/main.rs @@ -1,5 +1,6 @@ mod cli; mod commands; +mod format; mod shared; use clap::Parser; diff --git a/crates/dustfril-core/Cargo.toml b/crates/dustfril-core/Cargo.toml index 07b1d36..721b557 100644 --- a/crates/dustfril-core/Cargo.toml +++ b/crates/dustfril-core/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] -chrono = "0.4" +serde = { version = "1", features = ["derive"] } [dev-dependencies] -tempfile = "3.0" \ No newline at end of file +tempfile = "3.0" diff --git a/crates/dustfril-core/src/analyzer/analyze.rs b/crates/dustfril-core/src/analyzer/analyze.rs index 56d7496..38ce052 100644 --- a/crates/dustfril-core/src/analyzer/analyze.rs +++ b/crates/dustfril-core/src/analyzer/analyze.rs @@ -2,10 +2,11 @@ use crate::{ analyzer::{ calculate_age_days, calculate_directory_size, find_latest_modified, recommend_cleanup, }, + error::DustResult, models::{AnalysisResult, ArtifactAnalysis, ScanResult}, }; -pub fn analyze(scan_result: ScanResult) -> AnalysisResult { +pub fn analyze(scan_result: ScanResult) -> DustResult { let mut result = AnalysisResult::default(); for artifact in scan_result.artifacts { @@ -29,5 +30,5 @@ pub fn analyze(scan_result: ScanResult) -> AnalysisResult { .artifacts .sort_by_key(|b| std::cmp::Reverse(b.size_bytes)); - result + Ok(result) } diff --git a/crates/dustfril-core/src/analyzer/tests.rs b/crates/dustfril-core/src/analyzer/tests.rs index 14e329f..0e7c08f 100644 --- a/crates/dustfril-core/src/analyzer/tests.rs +++ b/crates/dustfril-core/src/analyzer/tests.rs @@ -2,15 +2,14 @@ use std::fs; use tempfile::TempDir; use crate::analyzer::{calculate_age_days, calculate_directory_size, find_latest_modified}; -use crate::format::size; use crate::{ analyzer::analyze, - models::{ArtifactLocation, ArtifactType, ScanResult}, + models::{Artifact, ArtifactType, ScanResult}, }; #[test] fn analyze_empty_scan_result() { - let result = analyze(ScanResult::default()); + let result = analyze(ScanResult::default()).unwrap(); assert_eq!(result.total_size_bytes, 0); @@ -36,7 +35,7 @@ fn analyze_returns_total_size() { fs::write(temp_dir.path().join("a.txt"), vec![0_u8; 100]).unwrap(); - let artifact = ArtifactLocation { + let artifact = Artifact { path: temp_dir.path().to_path_buf(), artifact_type: ArtifactType::Target, }; @@ -45,7 +44,7 @@ fn analyze_returns_total_size() { artifacts: vec![artifact], }; - let result = analyze(scan_result); + let result = analyze(scan_result).unwrap(); assert_eq!(result.total_size_bytes, 100); @@ -61,26 +60,6 @@ fn find_latest_modified_returns_some() { assert!(modified.is_some()); } -#[test] -fn format_size_bytes() { - assert_eq!(size::format_size(512), "512 B"); -} - -#[test] -fn format_size_kilobytes() { - assert_eq!(size::format_size(2048), "2.00 KB"); -} - -#[test] -fn format_size_megabytes() { - assert_eq!(size::format_size(1024 * 1024), "1.00 MB"); -} - -#[test] -fn format_size_gigabytes() { - assert_eq!(size::format_size(1024 * 1024 * 1024), "1.00 GB"); -} - #[test] fn analyze_sorts_by_size_descending() { let small = TempDir::new().unwrap(); @@ -92,18 +71,18 @@ fn analyze_sorts_by_size_descending() { let scan_result = ScanResult { artifacts: vec![ - ArtifactLocation { + Artifact { path: small.path().to_path_buf(), artifact_type: ArtifactType::Target, }, - ArtifactLocation { + Artifact { path: large.path().to_path_buf(), artifact_type: ArtifactType::CargoRegistry, }, ], }; - let result = analyze(scan_result); + let result = analyze(scan_result).unwrap(); assert_eq!(result.artifacts[0].size_bytes, 200); diff --git a/crates/dustfril-core/src/api/analyze.rs b/crates/dustfril-core/src/api/analyze.rs index 34c25c4..8d2bd39 100644 --- a/crates/dustfril-core/src/api/analyze.rs +++ b/crates/dustfril-core/src/api/analyze.rs @@ -1,5 +1,9 @@ -use crate::{analyzer, models::ScanResult}; +use crate::{ + analyzer, + error::DustResult, + models::{AnalysisResult, ScanResult}, +}; -pub fn analyze(scan_result: ScanResult) -> crate::models::AnalysisResult { +pub fn analyze(scan_result: ScanResult) -> DustResult { analyzer::analyze(scan_result) } diff --git a/crates/dustfril-core/src/api/clean.rs b/crates/dustfril-core/src/api/clean.rs index 430cb87..b5c4979 100644 --- a/crates/dustfril-core/src/api/clean.rs +++ b/crates/dustfril-core/src/api/clean.rs @@ -1,13 +1,14 @@ use crate::{ analyzer, cleaner, + error::DustResult, models::{CleanupPlan, CleanupResult, ScanResult}, }; -pub fn build_plan(scan: ScanResult) -> CleanupPlan { - let analysis = analyzer::analyze(scan); +pub fn build_plan(scan: ScanResult) -> DustResult { + let analysis = analyzer::analyze(scan)?; cleaner::create_cleanup_plan(analysis) } -pub fn execute(plan: &CleanupPlan) -> CleanupResult { +pub fn execute(plan: &CleanupPlan) -> DustResult { cleaner::execute_cleanup(plan) } diff --git a/crates/dustfril-core/src/api/scan.rs b/crates/dustfril-core/src/api/scan.rs index 49a52d8..66c90cd 100644 --- a/crates/dustfril-core/src/api/scan.rs +++ b/crates/dustfril-core/src/api/scan.rs @@ -1,8 +1,8 @@ use std::path::Path; -use crate::detector; +use crate::{detector, error::DustResult, models::ScanResult}; -pub fn scan(root: &Path, global: bool) -> crate::models::ScanResult { +pub fn scan(root: &Path, global: bool) -> DustResult { if global { detector::scan_global() } else { diff --git a/crates/dustfril-core/src/cleaner/executor.rs b/crates/dustfril-core/src/cleaner/executor.rs index 9311fea..73ceb6b 100644 --- a/crates/dustfril-core/src/cleaner/executor.rs +++ b/crates/dustfril-core/src/cleaner/executor.rs @@ -1,8 +1,11 @@ use std::fs; -use crate::models::{ArtifactType, CleanupPlan, CleanupResult}; +use crate::{ + error::DustResult, + models::{ArtifactType, CleanupPlan, CleanupResult}, +}; -pub fn execute_cleanup(plan: &CleanupPlan) -> CleanupResult { +pub fn execute_cleanup(plan: &CleanupPlan) -> DustResult { let mut result = CleanupResult { deleted_paths: vec![], failed_paths: vec![], @@ -23,5 +26,5 @@ pub fn execute_cleanup(plan: &CleanupPlan) -> CleanupResult { } } - result + Ok(result) } diff --git a/crates/dustfril-core/src/cleaner/plan.rs b/crates/dustfril-core/src/cleaner/plan.rs index 236c914..1ed8e70 100644 --- a/crates/dustfril-core/src/cleaner/plan.rs +++ b/crates/dustfril-core/src/cleaner/plan.rs @@ -1,24 +1,27 @@ -use crate::models::{AnalysisResult, CleanupCandidate, CleanupPlan, CleanupRecommendation}; +use crate::{ + error::DustResult, + models::{AnalysisResult, CleanupCandidate, CleanupPlan, CleanupRecommendation}, +}; -pub fn create_cleanup_plan(analysis: AnalysisResult) -> CleanupPlan { +pub fn create_cleanup_plan(analysis: AnalysisResult) -> DustResult { let mut plan = CleanupPlan::default(); - for artifact in analysis.artifacts { - if artifact.recommendation == CleanupRecommendation::SafeToClean { + for artifact_analysis in analysis.artifacts { + if artifact_analysis.recommendation == CleanupRecommendation::SafeToClean { // Flatten the analysis into a cleanup candidate plan.candidates.push(CleanupCandidate { - path: artifact.artifact.path.clone(), + path: artifact_analysis.artifact.path.clone(), - artifact_type: artifact.artifact.artifact_type.clone(), + artifact_type: artifact_analysis.artifact.artifact_type, - size_bytes: artifact.size_bytes, + size_bytes: artifact_analysis.size_bytes, - age_days: artifact.age_days, + age_days: artifact_analysis.age_days, - recommendation: artifact.recommendation, + recommendation: artifact_analysis.recommendation, }); } } - plan + Ok(plan) } diff --git a/crates/dustfril-core/src/cleaner/tests.rs b/crates/dustfril-core/src/cleaner/tests.rs index ef95fb5..e57065a 100644 --- a/crates/dustfril-core/src/cleaner/tests.rs +++ b/crates/dustfril-core/src/cleaner/tests.rs @@ -9,7 +9,7 @@ use crate::{ #[test] fn create_empty_cleanup_plan() { - let plan = create_cleanup_plan(AnalysisResult::default()); + let plan = create_cleanup_plan(AnalysisResult::default()).unwrap(); assert!(plan.candidates.is_empty()); @@ -19,7 +19,7 @@ fn create_empty_cleanup_plan() { #[test] fn safe_to_clean_becomes_candidate() { let artifact = ArtifactAnalysis { - artifact: ArtifactLocation { + artifact: Artifact { path: PathBuf::from("target"), artifact_type: ArtifactType::Target, @@ -40,7 +40,7 @@ fn safe_to_clean_becomes_candidate() { total_size_bytes: 100, }; - let plan = create_cleanup_plan(analysis); + let plan = create_cleanup_plan(analysis).unwrap(); assert_eq!(plan.candidates.len(), 1,); @@ -50,7 +50,7 @@ fn safe_to_clean_becomes_candidate() { #[test] fn keep_is_not_candidate() { let artifact = ArtifactAnalysis { - artifact: ArtifactLocation { + artifact: Artifact { path: PathBuf::from("target"), artifact_type: ArtifactType::Target, @@ -71,7 +71,7 @@ fn keep_is_not_candidate() { total_size_bytes: 100, }; - let plan = create_cleanup_plan(analysis); + let plan = create_cleanup_plan(analysis).unwrap(); assert!(plan.candidates.is_empty()); } @@ -104,7 +104,7 @@ fn execute_cleanup_removes_target_directory() { candidates: vec![candidate], }; - let result = execute_cleanup(&plan); + let result = execute_cleanup(&plan).unwrap(); let size_bytes = CleanupPlan::reclaimable_size_bytes(&plan); @@ -131,7 +131,7 @@ fn cleanup_reports_failed_path() { candidates: vec![candidate], }; - let result = execute_cleanup(&plan); + let result = execute_cleanup(&plan).unwrap(); assert_eq!(result.deleted_paths.len(), 0); diff --git a/crates/dustfril-core/src/detector/project.rs b/crates/dustfril-core/src/detector/project.rs index 2957abe..b981870 100644 --- a/crates/dustfril-core/src/detector/project.rs +++ b/crates/dustfril-core/src/detector/project.rs @@ -3,17 +3,15 @@ use std::{ path::{Path, PathBuf}, }; -// #[derive(Debug, Clone, Copy, PartialEq, Eq)] -// pub enum Ecosystem { -// Rust, -// Node, -// Java, -// } - -#[derive(Debug, Clone)] +use serde::{Deserialize, Serialize}; + +use crate::models::Ecosystem; + +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Project { pub root: PathBuf, - // pub ecosystem: Ecosystem, + + pub ecosystem: Ecosystem, } pub fn find_projects(root: &Path) -> Vec { @@ -33,7 +31,7 @@ fn visit(dir: &Path, projects: &mut Vec) { if is_cargo_project(dir) { projects.push(Project { root: dir.to_path_buf(), - // ecosystem: Ecosystem::Rust, + ecosystem: Ecosystem::Rust, }); return; diff --git a/crates/dustfril-core/src/detector/rust/git.rs b/crates/dustfril-core/src/detector/rust/git.rs index 4309869..697a8dc 100644 --- a/crates/dustfril-core/src/detector/rust/git.rs +++ b/crates/dustfril-core/src/detector/rust/git.rs @@ -1,8 +1,8 @@ use std::{env, path::Path}; -use crate::models::{ArtifactLocation, ArtifactType}; +use crate::models::{Artifact, ArtifactType}; -pub fn detect() -> Option { +pub fn detect() -> Option { // TODO: // Replace HOME lookup with dirs::home_dir() // for cross-platform support. @@ -16,7 +16,7 @@ pub fn detect() -> Option { return None; } - Some(ArtifactLocation { + Some(Artifact { path: git_path, artifact_type: ArtifactType::CargoGit, }) diff --git a/crates/dustfril-core/src/detector/rust/registry.rs b/crates/dustfril-core/src/detector/rust/registry.rs index e9b4ca8..df24143 100644 --- a/crates/dustfril-core/src/detector/rust/registry.rs +++ b/crates/dustfril-core/src/detector/rust/registry.rs @@ -1,8 +1,8 @@ use std::{env, path::Path}; -use crate::models::{ArtifactLocation, ArtifactType}; +use crate::models::{Artifact, ArtifactType}; -pub fn detect() -> Option { +pub fn detect() -> Option { // TODO: // Replace HOME lookup with dirs::home_dir() // for cross-platform support. @@ -16,7 +16,7 @@ pub fn detect() -> Option { return None; } - Some(ArtifactLocation { + Some(Artifact { path: registry_path, artifact_type: ArtifactType::CargoRegistry, }) diff --git a/crates/dustfril-core/src/detector/rust/target.rs b/crates/dustfril-core/src/detector/rust/target.rs index 81ee194..052baff 100644 --- a/crates/dustfril-core/src/detector/rust/target.rs +++ b/crates/dustfril-core/src/detector/rust/target.rs @@ -1,15 +1,15 @@ use std::path::Path; -use crate::models::{ArtifactLocation, ArtifactType}; +use crate::models::{Artifact, ArtifactType}; -pub fn detect(root: &Path) -> Option { +pub fn detect(root: &Path) -> Option { let target_path = root.join("target"); if !target_path.exists() { return None; } - Some(ArtifactLocation { + Some(Artifact { path: target_path, artifact_type: ArtifactType::Target, }) diff --git a/crates/dustfril-core/src/detector/scan.rs b/crates/dustfril-core/src/detector/scan.rs index f7090be..a8a6f4d 100644 --- a/crates/dustfril-core/src/detector/scan.rs +++ b/crates/dustfril-core/src/detector/scan.rs @@ -1,6 +1,6 @@ use std::path::Path; -use crate::models::ScanResult; +use crate::{error::DustResult, models::ScanResult}; use super::{ project, @@ -8,37 +8,37 @@ use super::{ }; // Scan a single Rust project for artifacts. -pub fn scan_project(root: &Path) -> ScanResult { +pub fn scan_project(root: &Path) -> DustResult { let mut result = ScanResult::default(); if !project::is_cargo_project(root) { - return result; + return Ok(result); } if let Some(target) = target::detect(root) { result.artifacts.push(target); } - result + Ok(result) } // Recursively scan for Rust projects and their artifacts. -pub fn scan_workspace(root: &Path) -> ScanResult { +pub fn scan_workspace(root: &Path) -> DustResult { let mut result = ScanResult::default(); let projects = project::find_projects(root); for project in projects { - let project_result = scan_project(&project.root); + let project_result = scan_project(&project.root)?; result.artifacts.extend(project_result.artifacts); } - result + Ok(result) } // Global artifacts that are not tied to a specific project, like Cargo registry and Git repositories. -pub fn scan_global() -> ScanResult { +pub fn scan_global() -> DustResult { let mut result = ScanResult::default(); if let Some(registry) = registry::detect() { @@ -49,7 +49,7 @@ pub fn scan_global() -> ScanResult { result.artifacts.push(git); } - result + Ok(result) } // pub fn scan(root: &Path) -> ScanResult { diff --git a/crates/dustfril-core/src/detector/tests.rs b/crates/dustfril-core/src/detector/tests.rs index eec033e..2333475 100644 --- a/crates/dustfril-core/src/detector/tests.rs +++ b/crates/dustfril-core/src/detector/tests.rs @@ -9,7 +9,7 @@ use crate::{ fn scan_returns_empty_when_not_cargo_project() { let temp_dir = TempDir::new().unwrap(); - let result = scan_project(temp_dir.path()); + let result = scan_project(temp_dir.path()).unwrap(); assert!(result.artifacts.is_empty()); } @@ -20,7 +20,7 @@ fn cargo_project_without_target_returns_empty() { std::fs::write(temp_dir.path().join("Cargo.toml"), "[package]").unwrap(); - let result = scan_project(temp_dir.path()); + let result = scan_project(temp_dir.path()).unwrap(); assert!(result.artifacts.is_empty()); } @@ -33,7 +33,7 @@ fn detects_target_directory() { std::fs::create_dir(temp_dir.path().join("target")).unwrap(); - let result = scan_project(temp_dir.path()); + let result = scan_project(temp_dir.path()).unwrap(); assert_eq!(result.artifacts.len(), 1); @@ -78,7 +78,7 @@ fn scan_workspace_finds_targets_from_multiple_projects() { std::fs::create_dir(project_a.join("target")).unwrap(); std::fs::create_dir(project_b.join("target")).unwrap(); - let result = scan_workspace(temp_dir.path()); + let result = scan_workspace(temp_dir.path()).unwrap(); assert_eq!(result.artifacts.len(), 2); diff --git a/crates/dustfril-core/src/error.rs b/crates/dustfril-core/src/error.rs new file mode 100644 index 0000000..63a3cd8 --- /dev/null +++ b/crates/dustfril-core/src/error.rs @@ -0,0 +1,42 @@ +use std::fmt; + +#[derive(Debug)] +pub enum DustError { + Io(std::io::Error), + InvalidPath, + ScanFailed, + AnalysisFailed, + CleanupFailed, +} + +pub type DustResult = std::result::Result; + +impl fmt::Display for DustError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + DustError::Io(error) => { + write!(f, "I/O error: {error}") + } + DustError::InvalidPath => { + write!(f, "Invalid path") + } + DustError::ScanFailed => { + write!(f, "Scan failed") + } + DustError::AnalysisFailed => { + write!(f, "Analysis failed") + } + DustError::CleanupFailed => { + write!(f, "Cleanup failed") + } + } + } +} + +impl std::error::Error for DustError {} + +impl From for DustError { + fn from(error: std::io::Error) -> Self { + DustError::Io(error) + } +} diff --git a/crates/dustfril-core/src/format/mod.rs b/crates/dustfril-core/src/format/mod.rs deleted file mode 100644 index ee3a7d4..0000000 --- a/crates/dustfril-core/src/format/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod date; -pub mod size; - -pub use date::*; -pub use size::*; diff --git a/crates/dustfril-core/src/lib.rs b/crates/dustfril-core/src/lib.rs index 1171c4c..de31272 100644 --- a/crates/dustfril-core/src/lib.rs +++ b/crates/dustfril-core/src/lib.rs @@ -1,5 +1,5 @@ pub mod api; -pub mod format; +pub mod error; pub mod models; mod analyzer; diff --git a/crates/dustfril-core/src/models/analysis_result.rs b/crates/dustfril-core/src/models/analysis_result.rs index 88ad86d..a884a35 100644 --- a/crates/dustfril-core/src/models/analysis_result.rs +++ b/crates/dustfril-core/src/models/analysis_result.rs @@ -1,7 +1,9 @@ +use serde::{Deserialize, Serialize}; + use super::ArtifactAnalysis; /// Total Analysis Results -#[derive(Debug, Default)] +#[derive(Debug, Default, Serialize, Deserialize)] pub struct AnalysisResult { pub artifacts: Vec, pub total_size_bytes: u64, diff --git a/crates/dustfril-core/src/models/artifact_location.rs b/crates/dustfril-core/src/models/artifact.rs similarity index 67% rename from crates/dustfril-core/src/models/artifact_location.rs rename to crates/dustfril-core/src/models/artifact.rs index cd26b42..409bc2a 100644 --- a/crates/dustfril-core/src/models/artifact_location.rs +++ b/crates/dustfril-core/src/models/artifact.rs @@ -1,14 +1,15 @@ +use serde::{Deserialize, Serialize}; use std::path::PathBuf; use super::ArtifactType; -#[derive(Debug, Clone)] -pub struct ArtifactLocation { +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Artifact { pub path: PathBuf, pub artifact_type: ArtifactType, } -impl ArtifactLocation { +impl Artifact { pub fn new(path: PathBuf, artifact_type: ArtifactType) -> Self { Self { path, diff --git a/crates/dustfril-core/src/models/artifact_analysis.rs b/crates/dustfril-core/src/models/artifact_analysis.rs index 80da2a5..7e7959e 100644 --- a/crates/dustfril-core/src/models/artifact_analysis.rs +++ b/crates/dustfril-core/src/models/artifact_analysis.rs @@ -1,10 +1,12 @@ -use crate::models::{ArtifactLocation, CleanupRecommendation}; +use serde::{Deserialize, Serialize}; + +use crate::models::{Artifact, CleanupRecommendation}; use std::time::SystemTime; /// Analyzed artifact information -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub struct ArtifactAnalysis { - pub artifact: ArtifactLocation, + pub artifact: Artifact, pub size_bytes: u64, // permission denied, broken symlink, network filesystem failed to detect pub last_modified: Option, diff --git a/crates/dustfril-core/src/models/artifact_type.rs b/crates/dustfril-core/src/models/artifact_type.rs index cb4f55b..ffe780e 100644 --- a/crates/dustfril-core/src/models/artifact_type.rs +++ b/crates/dustfril-core/src/models/artifact_type.rs @@ -1,6 +1,7 @@ +use serde::{Deserialize, Serialize}; use std::fmt; -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum ArtifactType { Target, CargoRegistry, diff --git a/crates/dustfril-core/src/models/cleanup_candidate.rs b/crates/dustfril-core/src/models/cleanup_candidate.rs index 6b4e6ce..0cfaa0d 100644 --- a/crates/dustfril-core/src/models/cleanup_candidate.rs +++ b/crates/dustfril-core/src/models/cleanup_candidate.rs @@ -1,8 +1,10 @@ use std::path::PathBuf; +use serde::{Deserialize, Serialize}; + use crate::models::{ArtifactType, CleanupRecommendation}; -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub struct CleanupCandidate { pub path: PathBuf, diff --git a/crates/dustfril-core/src/models/cleanup_plan.rs b/crates/dustfril-core/src/models/cleanup_plan.rs index 227f808..54f2a20 100644 --- a/crates/dustfril-core/src/models/cleanup_plan.rs +++ b/crates/dustfril-core/src/models/cleanup_plan.rs @@ -1,6 +1,8 @@ +use serde::{Deserialize, Serialize}; + use crate::models::CleanupCandidate; -#[derive(Debug, Default)] +#[derive(Debug, Default, Serialize, Deserialize)] pub struct CleanupPlan { pub candidates: Vec, } diff --git a/crates/dustfril-core/src/models/cleanup_recommendation.rs b/crates/dustfril-core/src/models/cleanup_recommendation.rs index d62291a..f66c78c 100644 --- a/crates/dustfril-core/src/models/cleanup_recommendation.rs +++ b/crates/dustfril-core/src/models/cleanup_recommendation.rs @@ -1,6 +1,8 @@ use std::fmt; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum CleanupRecommendation { Keep, Review, diff --git a/crates/dustfril-core/src/models/cleanup_result.rs b/crates/dustfril-core/src/models/cleanup_result.rs index 3539be3..f27b15e 100644 --- a/crates/dustfril-core/src/models/cleanup_result.rs +++ b/crates/dustfril-core/src/models/cleanup_result.rs @@ -1,6 +1,8 @@ use std::path::PathBuf; -#[derive(Debug)] +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] pub struct CleanupResult { pub deleted_paths: Vec, diff --git a/crates/dustfril-core/src/models/ecosystem.rs b/crates/dustfril-core/src/models/ecosystem.rs new file mode 100644 index 0000000..a44798d --- /dev/null +++ b/crates/dustfril-core/src/models/ecosystem.rs @@ -0,0 +1,8 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum Ecosystem { + Rust, + Node, + Java, +} diff --git a/crates/dustfril-core/src/models/mod.rs b/crates/dustfril-core/src/models/mod.rs index 9ffad72..555e098 100644 --- a/crates/dustfril-core/src/models/mod.rs +++ b/crates/dustfril-core/src/models/mod.rs @@ -1,7 +1,8 @@ //! Shared models. // scan -mod artifact_location; +mod artifact; mod artifact_type; +mod ecosystem; mod scan_result; // analysis @@ -16,8 +17,9 @@ mod cleanup_plan; mod cleanup_result; // scan -pub use artifact_location::*; +pub use artifact::*; pub use artifact_type::*; +pub use ecosystem::*; pub use scan_result::*; // analysis diff --git a/crates/dustfril-core/src/models/scan_result.rs b/crates/dustfril-core/src/models/scan_result.rs index b26e0a7..3e3cd89 100644 --- a/crates/dustfril-core/src/models/scan_result.rs +++ b/crates/dustfril-core/src/models/scan_result.rs @@ -1,6 +1,8 @@ -use super::ArtifactLocation; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Default)] +use crate::models::Artifact; + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct ScanResult { - pub artifacts: Vec, + pub artifacts: Vec, }