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_paths: Vec<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 let total = project.total_size();
142 Self {
143 name: project.name.clone(),
144 project_type: project.kind.clone(),
145 root_path: project.root_path.display().to_string(),
146 build_artifacts_paths: project
147 .build_arts
148 .iter()
149 .map(|a| a.path.display().to_string())
150 .collect(),
151 build_artifacts_size: total,
152 build_artifacts_size_formatted: format_size(total, DECIMAL),
153 }
154 }
155}
156
157impl JsonSummary {
158 #[must_use]
160 pub fn from_projects(projects: &[Project]) -> Self {
161 let mut by_type: BTreeMap<String, (usize, u64)> = BTreeMap::new();
162
163 for project in projects {
164 let key = match project.kind {
165 ProjectType::Rust => "rust",
166 ProjectType::Node => "node",
167 ProjectType::Python => "python",
168 ProjectType::Go => "go",
169 ProjectType::Java => "java",
170 ProjectType::Cpp => "cpp",
171 ProjectType::Swift => "swift",
172 ProjectType::DotNet => "dotnet",
173 ProjectType::Ruby => "ruby",
174 ProjectType::Elixir => "elixir",
175 ProjectType::Deno => "deno",
176 };
177
178 let entry = by_type.entry(key.to_string()).or_insert((0, 0));
179 entry.0 += 1;
180 entry.1 += project.total_size();
181 }
182
183 let total_size: u64 = projects.iter().map(Project::total_size).sum();
184
185 Self {
186 total_projects: projects.len(),
187 total_size,
188 total_size_formatted: format_size(total_size, DECIMAL),
189 by_type: by_type
190 .into_iter()
191 .map(|(k, (count, size))| {
192 (
193 k,
194 JsonTypeSummary {
195 count,
196 size,
197 size_formatted: format_size(size, DECIMAL),
198 },
199 )
200 })
201 .collect(),
202 }
203 }
204}
205
206impl JsonCleanupResult {
207 #[must_use]
209 pub fn from_clean_result(result: &crate::cleaner::CleanResult) -> Self {
210 Self {
211 success_count: result.success_count,
212 failure_count: result.errors.len(),
213 total_freed: result.total_freed,
214 total_freed_formatted: format_size(result.total_freed, DECIMAL),
215 errors: result.errors.clone(),
216 }
217 }
218}