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 };
169
170 let entry = by_type.entry(key.to_string()).or_insert((0, 0));
171 entry.0 += 1;
172 entry.1 += project.build_arts.size;
173 }
174
175 let total_size: u64 = projects.iter().map(|p| p.build_arts.size).sum();
176
177 Self {
178 total_projects: projects.len(),
179 total_size,
180 total_size_formatted: format_size(total_size, DECIMAL),
181 by_type: by_type
182 .into_iter()
183 .map(|(k, (count, size))| {
184 (
185 k,
186 JsonTypeSummary {
187 count,
188 size,
189 size_formatted: format_size(size, DECIMAL),
190 },
191 )
192 })
193 .collect(),
194 }
195 }
196}
197
198impl JsonCleanupResult {
199 #[must_use]
201 pub fn from_clean_result(result: &crate::cleaner::CleanResult) -> Self {
202 Self {
203 success_count: result.success_count,
204 failure_count: result.errors.len(),
205 total_freed: result.total_freed,
206 total_freed_formatted: format_size(result.total_freed, DECIMAL),
207 errors: result.errors.clone(),
208 }
209 }
210}