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 ProjectType::Php => "php",
177 ProjectType::Haskell => "haskell",
178 ProjectType::Dart => "dart",
179 ProjectType::Zig => "zig",
180 ProjectType::Scala => "scala",
181 };
182
183 let entry = by_type.entry(key.to_string()).or_insert((0, 0));
184 entry.0 += 1;
185 entry.1 += project.total_size();
186 }
187
188 let total_size: u64 = projects.iter().map(Project::total_size).sum();
189
190 Self {
191 total_projects: projects.len(),
192 total_size,
193 total_size_formatted: format_size(total_size, DECIMAL),
194 by_type: by_type
195 .into_iter()
196 .map(|(k, (count, size))| {
197 (
198 k,
199 JsonTypeSummary {
200 count,
201 size,
202 size_formatted: format_size(size, DECIMAL),
203 },
204 )
205 })
206 .collect(),
207 }
208 }
209}
210
211impl JsonCleanupResult {
212 #[must_use]
214 pub fn from_clean_result(result: &crate::cleaner::CleanResult) -> Self {
215 Self {
216 success_count: result.success_count,
217 failure_count: result.errors.len(),
218 total_freed: result.total_freed,
219 total_freed_formatted: format_size(result.total_freed, DECIMAL),
220 errors: result.errors.clone(),
221 }
222 }
223}