Skip to main content

devclean/
model.rs

1use std::fmt;
2use std::path::PathBuf;
3
4use clap::ValueEnum;
5use serde::{Deserialize, Serialize};
6
7/// A class of rebuildable development data.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, ValueEnum)]
9#[serde(rename_all = "kebab-case")]
10pub enum Category {
11    /// Cargo compilation outputs with Rust-specific markers.
12    RustTarget,
13    /// JavaScript dependency installations.
14    NodeModules,
15    /// Framework caches such as `.next` and `.svelte-kit`.
16    FrameworkCache,
17    /// Build directories underneath a recognized project manifest.
18    BuildOutput,
19    /// Test, mutation, type-checker, and lint caches.
20    TestCache,
21    /// Downloaded package-manager or tool caches.
22    GlobalCache,
23    /// Large runtimes or model caches that are expensive to restore.
24    ExpensiveGlobalCache,
25}
26
27impl Category {
28    /// Returns all categories discovered by a comprehensive scan.
29    #[must_use]
30    pub fn all() -> [Self; 7] {
31        [
32            Self::RustTarget,
33            Self::NodeModules,
34            Self::FrameworkCache,
35            Self::BuildOutput,
36            Self::TestCache,
37            Self::GlobalCache,
38            Self::ExpensiveGlobalCache,
39        ]
40    }
41
42    /// Returns conservative categories used by `clean` unless overridden.
43    #[must_use]
44    pub fn safe_defaults() -> [Self; 3] {
45        [Self::RustTarget, Self::NodeModules, Self::FrameworkCache]
46    }
47}
48
49impl fmt::Display for Category {
50    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
51        let value = match self {
52            Self::RustTarget => "rust-target",
53            Self::NodeModules => "node-modules",
54            Self::FrameworkCache => "framework-cache",
55            Self::BuildOutput => "build-output",
56            Self::TestCache => "test-cache",
57            Self::GlobalCache => "global-cache",
58            Self::ExpensiveGlobalCache => "expensive-global-cache",
59        };
60        formatter.write_str(value)
61    }
62}
63
64/// A directory that can be rebuilt or downloaded again.
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct Candidate {
67    /// Artifact category.
68    pub category: Category,
69    /// Absolute or user-supplied path to the artifact.
70    pub path: PathBuf,
71    /// Estimated allocated bytes on disk.
72    pub bytes: u64,
73    /// Evidence used to classify the directory.
74    pub reason: String,
75    /// Latest observed modification time as seconds since the Unix epoch.
76    pub modified_at_unix: Option<u64>,
77}
78
79/// Result of scanning one or more roots.
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct ScanReport {
82    /// Roots traversed by the scanner.
83    pub roots: Vec<PathBuf>,
84    /// Rebuildable directories found.
85    pub candidates: Vec<Candidate>,
86    /// Non-fatal traversal or metadata errors.
87    pub warnings: Vec<String>,
88    /// Total estimated allocated bytes for all candidates.
89    pub total_bytes: u64,
90    /// Whether cleanup must repeat the Git tracked-file guard.
91    #[serde(default = "default_true")]
92    pub protect_git_tracked: bool,
93}
94
95const fn default_true() -> bool {
96    true
97}
98
99/// Human- or machine-readable report format.
100#[derive(Debug, Clone, Copy, ValueEnum)]
101pub enum OutputFormat {
102    /// Compact terminal table.
103    Table,
104    /// Structured JSON.
105    Json,
106    /// One JSON object per line for streaming automation.
107    Jsonl,
108    /// Standalone HTML document.
109    Html,
110}
111
112/// Controls presentation without changing the underlying scan result.
113#[derive(Debug, Clone, Copy, Default)]
114pub struct RenderOptions {
115    /// Replace absolute paths with stable root-relative placeholders.
116    pub redact_paths: bool,
117}