Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 16 additions & 25 deletions README.ko.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,42 @@
# DustFril

Rust 개발자를 위한 산출물(Artifact) 분석 및 정리 도구입니다.
개발 산출물(Artifact) 분석 및 정리 도구입니다.

DustFril은 Cargo와 Rust 도구들이 생성하는 빌드 산출물과 캐시를 탐지하고, 용량을 분석하며, 안전하게 정리할 수 있도록 돕는 것을 목표로 합니다.
DustFril은 Rust, Node, Java 프로젝트에서 생성되는 산출물을 탐지하고, 용량을 분석하며, 안전하게 정리할 수 있도록 돕는 것을 목표로 합니다.

Rust 프로젝트를 오래 관리하다 보면 `target/`, Cargo 캐시 등의 디렉터리가 수 GB에서 수십 GB까지 증가할 수 있습니다.
프로젝트를 오래 관리하다 보면 빌드 결과물과 의존성 디렉터리가 수 GB에서 수십 GB까지 증가할 수 있습니다.

DustFril은 이러한 생성 파일들을 쉽고 안전하게 관리할 수 있는 CLI 도구를 지향합니다.

## 주요 기능

### 현재 목표

- Rust 산출물 탐지
- 지원 생태계의 삭제 가능한 산출물 탐지
- 디스크 사용량 분석
- Cargo 캐시 분석
- CLI 기반 생태계 필터링
- 안전한 정리 기능

### 지원 예정
### 현재 탐지 대상

- `target/`
- `~/.cargo/registry`
- `~/.cargo/git`
- `node_modules/`
- `build/`

### 지원 예정

- Cargo 홈 캐시
- 추가 생태계별 캐시

## 사용 예시

프로젝트 스캔:
산출물 스캔:

```bash
dfr scan
```

용량 분석:
산출물 용량 분석:

```bash
dfr analyze
Expand All @@ -51,28 +56,14 @@ dfr clean

## 로드맵

### Phase 1

- Cargo 프로젝트 탐지
- Rust 산출물 탐지
- 기본 CLI 구현

### Phase 2

- 다중 생태계 산출물 탐지
- 디스크 사용량 분석
- Dry Run 지원
- 안전 삭제 기능

### Phase 3

- 인터랙티브 터미널 UI
- 설정 파일 지원
- 고급 필터링

### Phase 4

- 데스크톱 애플리케이션
- 다중 언어 생태계 지원

## 철학

Expand Down
41 changes: 16 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,37 +1,42 @@
# DustFril

A Rust development artifact analyzer and cleaner.
A development artifact analyzer and cleaner.

🚧 Early development stage

DustFril helps Rust developers discover, analyze, and safely manage generated files created by Cargo and Rust tooling.
DustFril helps developers discover, analyze, and safely manage generated files from Rust, Node, and Java projects.

Over time, Rust projects accumulate build artifacts and caches that consume significant disk space. DustFril aims to provide a simple and transparent way to inspect and clean those artifacts.
Over time, build outputs and dependency directories consume significant disk space. DustFril aims to provide a simple and transparent way to inspect and clean those artifacts.

## Features

### Current Focus

- Detect Rust build artifacts
- Detect removable build artifacts across supported ecosystems
- Analyze disk usage
- Inspect Cargo caches
- Filter by ecosystem from the CLI
- Safe cleanup workflow

### Planned Support
### Currently Detected Artifacts

- `target/`
- `~/.cargo/registry`
- `~/.cargo/git`
- `node_modules/`
- `build/`

### Planned Support

- Cargo home caches
- Additional ecosystem-specific caches

## Example

Scan Rust artifacts:
Scan artifacts:

```bash
dfr scan
```

Analyze disk usage:
Analyze artifact disk usage:

```bash
dfr analyze
Expand All @@ -51,28 +56,14 @@ dfr clean

## Project Goals

### Phase 1

- Cargo project scanning
- Rust artifact detection
- Basic CLI

### Phase 2

- Multi-ecosystem artifact detection
- Disk usage analysis
- Dry-run support
- Safe cleanup operations

### Phase 3

- Interactive terminal interface
- Configuration support
- Advanced filtering

### Phase 4

- Desktop application
- Multi-language ecosystem support

## Philosophy

Expand Down
63 changes: 59 additions & 4 deletions apps/dustfril-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ use dustfril_core::models::Ecosystem;

/// DustFril CLI
#[derive(Parser)]
#[command(name = "dfr", version, about = "Rust artifact analyzer and cleaner")]
#[command(
name = "dfr",
version,
about = "Development artifact analyzer and cleaner"
)]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
Expand All @@ -14,13 +18,13 @@ pub struct Cli {
/// Available commands
#[derive(Subcommand)]
pub enum Commands {
/// Scan Rust artifacts
/// Scan build artifacts
Scan(PathArgs),

/// Analyze disk usage
/// Analyze artifact disk usage
Analyze(PathArgs),

/// Clean artifacts
/// Clean detected artifacts
Clean(CleanArgs),
}

Expand All @@ -39,6 +43,9 @@ pub struct PathArgs {
}

impl PathArgs {
/// Returns the selected ecosystem filters in CLI flag order.
///
/// An empty result means all ecosystems should be scanned.
pub fn ecosystems(&self) -> Vec<Ecosystem> {
let mut ecosystems = Vec::new();

Expand Down Expand Up @@ -69,7 +76,55 @@ pub struct CleanArgs {
}

impl CleanArgs {
/// Returns the selected cleanup ecosystem filters.
pub fn ecosystems(&self) -> Vec<Ecosystem> {
self.path_args.ecosystems()
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn path_args_ecosystems_returns_empty_when_no_flags_are_set() {
let args = PathArgs {
path: None,
rust: false,
node: false,
java: false,
};

assert!(args.ecosystems().is_empty());
}

#[test]
fn path_args_ecosystems_preserves_flag_order() {
let args = PathArgs {
path: None,
rust: true,
node: true,
java: true,
};

assert_eq!(
args.ecosystems(),
vec![Ecosystem::Rust, Ecosystem::Node, Ecosystem::Java]
);
}

#[test]
fn clean_args_ecosystems_delegates_to_path_args() {
let args = CleanArgs {
path_args: PathArgs {
path: None,
rust: false,
node: true,
java: true,
},
dry_run: true,
};

assert_eq!(args.ecosystems(), vec![Ecosystem::Node, Ecosystem::Java]);
}
}
2 changes: 1 addition & 1 deletion apps/dustfril-cli/src/commands/scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ pub fn execute(args: PathArgs) {
println!("Found {} artifact(s)\n", result.artifacts.len());

for artifact in result.artifacts {
println!(" {:?}\n", artifact.path);
println!(" [{}] {}", artifact.ecosystem, artifact.path.display());
}
}
6 changes: 6 additions & 0 deletions apps/dustfril-cli/src/format/size_format.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/// Formats a byte count using binary size units for human-readable CLI output.
pub fn format_size(bytes: u64) -> String {
const KB: f64 = 1024.0;
const MB: f64 = KB * 1024.0;
Expand Down Expand Up @@ -39,3 +40,8 @@ fn format_size_megabytes() {
fn format_size_gigabytes() {
assert_eq!(format_size(1024 * 1024 * 1024), "1.00 GB");
}

#[test]
fn format_size_terabytes() {
assert_eq!(format_size(1024_u64.pow(4)), "1.00 TB");
}
23 changes: 23 additions & 0 deletions apps/dustfril-cli/src/format/time_format.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use chrono::{DateTime, Local};
use std::time::SystemTime;

/// Formats a filesystem timestamp for display in the local timezone.
pub fn format_modified(modified: Option<SystemTime>) -> String {
let Some(modified) = modified else {
return "Unknown".to_string();
Expand All @@ -10,3 +11,25 @@ pub fn format_modified(modified: Option<SystemTime>) -> String {

datetime.format("%Y-%m-%d %H:%M:%S").to_string()
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn format_modified_returns_unknown_for_missing_timestamp() {
assert_eq!(format_modified(None), "Unknown");
}

#[test]
fn format_modified_returns_timestamp_string() {
let formatted = format_modified(Some(SystemTime::UNIX_EPOCH));

assert_eq!(formatted.len(), 19);
assert_eq!(&formatted[4..5], "-");
assert_eq!(&formatted[7..8], "-");
assert_eq!(&formatted[10..11], " ");
assert_eq!(&formatted[13..14], ":");
assert_eq!(&formatted[16..17], ":");
}
}
31 changes: 31 additions & 0 deletions apps/dustfril-cli/src/shared/path.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::path::{Path, PathBuf};

/// Returns `true` when the given path exists on disk.
pub fn validate_path(path: &Path) -> bool {
if !path.exists() {
eprintln!("Path does not exist: {}", path.display());
Expand All @@ -10,7 +11,37 @@ pub fn validate_path(path: &Path) -> bool {
true
}

/// Resolves an optional CLI path to an explicit filesystem path.
///
/// When no path is provided, the current working directory is used.
pub fn resolve_path(path: &Option<PathBuf>) -> PathBuf {
path.clone()
.unwrap_or_else(|| std::env::current_dir().expect("Failed to get current directory"))
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn validate_path_returns_true_for_existing_path() {
assert!(validate_path(std::env::current_dir().unwrap().as_path()));
}

#[test]
fn validate_path_returns_false_for_missing_path() {
let missing = std::env::temp_dir().join("dustfril-cli-missing-path");
assert!(!validate_path(&missing));
}

#[test]
fn resolve_path_returns_explicit_path_when_provided() {
let path = PathBuf::from("/tmp/dustfril-explicit");
assert_eq!(resolve_path(&Some(path.clone())), path);
}

#[test]
fn resolve_path_uses_current_dir_when_not_provided() {
assert_eq!(resolve_path(&None), std::env::current_dir().unwrap());
}
}
Loading
Loading