1use std::collections::BTreeMap;
9
10use humansize::{DECIMAL, format_size};
11use serde::Serialize;
12
13use crate::project::{Project, ProjectType};
14
15#[derive(Serialize)]
17pub struct JsonOutput {
18 pub mode: String,
20
21 pub projects: Vec<JsonProjectEntry>,
23
24 pub summary: JsonSummary,
26
27 #[serde(skip_serializing_if = "Option::is_none")]
30 pub cleanup: Option<JsonCleanupResult>,
31}
32
33#[derive(Serialize)]
35pub struct JsonProjectEntry {
36 pub name: Option<String>,
38
39 #[serde(rename = "type")]
41 pub project_type: ProjectType,
42
43 pub root_path: String,
45
46 pub build_artifacts_path: String,
48
49 pub build_artifacts_size: u64,
51
52 pub build_artifacts_size_formatted: String,
54}
55
56#[derive(Serialize)]
58pub struct JsonSummary {
59 pub total_projects: usize,
61
62 pub total_size: u64,
64
65 pub total_size_formatted: String,
67
68 pub by_type: BTreeMap<String, JsonTypeSummary>,
70}
71
72#[derive(Serialize)]
74pub struct JsonTypeSummary {
75 pub count: usize,
77
78 pub size: u64,
80
81 pub size_formatted: String,
83}
84
85#[derive(Serialize)]
87pub struct JsonCleanupResult {
88 pub success_count: usize,
90
91 pub failure_count: usize,
93
94 pub total_freed: u64,
96
97 pub total_freed_formatted: String,
99
100 pub errors: Vec<String>,
102}
103
104impl JsonOutput {
105 #[must_use]
107 pub fn from_projects_dry_run(projects: &[Project]) -> Self {
108 Self {
109 mode: "dry_run".to_string(),
110 projects: projects
111 .iter()
112 .map(JsonProjectEntry::from_project)
113 .collect(),
114 summary: JsonSummary::from_projects(projects),
115 cleanup: None,
116 }
117 }
118
119 #[must_use]
121 pub fn from_projects_cleanup(
122 projects: &[Project],
123 clean_result: &crate::cleaner::CleanResult,
124 ) -> Self {
125 Self {
126 mode: "cleanup".to_string(),
127 projects: projects
128 .iter()
129 .map(JsonProjectEntry::from_project)
130 .collect(),
131 summary: JsonSummary::from_projects(projects),
132 cleanup: Some(JsonCleanupResult::from_clean_result(clean_result)),
133 }
134 }
135}
136
137impl JsonProjectEntry {
138 #[must_use]
140 pub fn from_project(project: &Project) -> Self {
141 Self {
142 name: project.name.clone(),
143 project_type: project.kind.clone(),
144 root_path: project.root_path.display().to_string(),
145 build_artifacts_path: project.build_arts.path.display().to_string(),
146 build_artifacts_size: project.build_arts.size,
147 build_artifacts_size_formatted: format_size(project.build_arts.size, DECIMAL),
148 }
149 }
150}
151
152impl JsonSummary {
153 #[must_use]
155 pub fn from_projects(projects: &[Project]) -> Self {
156 let mut by_type: BTreeMap<String, (usize, u64)> = BTreeMap::new();
157
158 for project in projects {
159 let key = match project.kind {
160 ProjectType::Rust => "rust",
161 ProjectType::Node => "node",
162 ProjectType::Python => "python",
163 ProjectType::Go => "go",
164 ProjectType::Java => "java",
165 ProjectType::Cpp => "cpp",
166 ProjectType::Swift => "swift",
167 ProjectType::DotNet => "dotnet",
168 ProjectType::Ruby => "ruby",
169 ProjectType::Elixir => "elixir",
170 ProjectType::Deno => "deno",
171 };
172
173 let entry = by_type.entry(key.to_string()).or_insert((0, 0));
174 entry.0 += 1;
175 entry.1 += project.build_arts.size;
176 }
177
178 let total_size: u64 = projects.iter().map(|p| p.build_arts.size).sum();
179
180 Self {
181 total_projects: projects.len(),
182 total_size,
183 total_size_formatted: format_size(total_size, DECIMAL),
184 by_type: by_type
185 .into_iter()
186 .map(|(k, (count, size))| {
187 (
188 k,
189 JsonTypeSummary {
190 count,
191 size,
192 size_formatted: format_size(size, DECIMAL),
193 },
194 )
195 })
196 .collect(),
197 }
198 }
199}
200
201impl JsonCleanupResult {
202 #[must_use]
204 pub fn from_clean_result(result: &crate::cleaner::CleanResult) -> Self {
205 Self {
206 success_count: result.success_count,
207 failure_count: result.errors.len(),
208 total_freed: result.total_freed,
209 total_freed_formatted: format_size(result.total_freed, DECIMAL),
210 errors: result.errors.clone(),
211 }
212 }
213}