1use crate::cli::Args;
2use crate::openssl::ParsedPfx;
3use crate::output::OutputConfig;
4use colored::*;
5use console::Term;
6use std::io::{self, Write};
7use tabled::{
8 Table, Tabled,
9 settings::{Alignment, Modify, Style, object::Rows},
10};
11
12pub struct OutputFormatter {
14 config: OutputConfig,
15}
16
17#[derive(Tabled)]
18struct FileOutput {
19 #[tabled(rename = "Type")]
20 file_type: String,
21 #[tabled(rename = "Filename")]
22 filename: String,
23 #[tabled(rename = "Location")]
24 location: String,
25 #[tabled(rename = "Status")]
26 status: String,
27}
28
29#[derive(Tabled)]
30struct CertInfo {
31 #[tabled(rename = "Property")]
32 property: String,
33 #[tabled(rename = "Value")]
34 value: String,
35}
36
37impl OutputFormatter {
38 pub fn new(config: &OutputConfig) -> Self {
39 Self {
40 config: config.clone(),
41 }
42 }
43
44 pub fn print_summary(
46 &self,
47 args: &Args,
48 parsed: &ParsedPfx,
49 term: &mut Term,
50 ) -> io::Result<()> {
51 self.print_header("Conversion Summary", term)?;
53
54 let mut files = vec![
56 FileOutput {
57 file_type: "Private Key".to_string(),
58 filename: args.key_filename().to_string(),
59 location: args.output_dir().to_string(),
60 status: if self.config.use_colors {
61 "✓ Created".green().to_string()
62 } else {
63 "✓ Created".to_string()
64 },
65 },
66 FileOutput {
67 file_type: "Certificate".to_string(),
68 filename: args.cert_filename().to_string(),
69 location: args.output_dir().to_string(),
70 status: if self.config.use_colors {
71 "✓ Created".green().to_string()
72 } else {
73 "✓ Created".to_string()
74 },
75 },
76 ];
77
78 if args.combined {
80 files.push(FileOutput {
81 file_type: "Combined PEM".to_string(),
82 filename: args.combined_filename().to_string(),
83 location: args.output_dir().to_string(),
84 status: if self.config.use_colors {
85 "✓ Created".green().to_string()
86 } else {
87 "✓ Created".to_string()
88 },
89 });
90 }
91
92 if args.chain && parsed.has_chain() {
94 files.push(FileOutput {
95 file_type: "Certificate Chain".to_string(),
96 filename: "certificate_chain.pem".to_string(),
97 location: args.output_dir().to_string(),
98 status: if self.config.use_colors {
99 "✓ Created".green().to_string()
100 } else {
101 "✓ Created".to_string()
102 },
103 });
104
105 for i in 0..parsed.chain_length() {
106 files.push(FileOutput {
107 file_type: format!("Chain Cert {}", i + 1),
108 filename: format!("chain_cert_{}.pem", i + 1),
109 location: args.output_dir().to_string(),
110 status: if self.config.use_colors {
111 "✓ Created".green().to_string()
112 } else {
113 "✓ Created".to_string()
114 },
115 });
116 }
117 }
118
119 let mut table = Table::new(&files);
121 table
122 .with(Style::rounded())
123 .with(Modify::new(Rows::first()).with(Alignment::center()));
124
125 if self.config.use_colors {
126 writeln!(term, "{}", table.to_string().bright_white())?;
127 } else {
128 writeln!(term, "{}", table)?;
129 }
130
131 self.print_stats_box(parsed, term)?;
133
134 if self.config.use_colors {
136 writeln!(
137 term,
138 "\n{} Conversion completed successfully!",
139 "🎉".bright_green()
140 )?;
141 } else {
142 writeln!(term, "\n✓ Conversion completed successfully!")?;
143 }
144
145 Ok(())
146 }
147
148 pub fn print_cert_info(&self, parsed: &ParsedPfx, term: &mut Term) -> io::Result<()> {
150 self.print_header("Certificate Information", term)?;
151
152 let cert_info = parsed.certificate_info();
153
154 let cert_data = vec![
155 CertInfo {
156 property: "Subject".to_string(),
157 value: cert_info.subject,
158 },
159 CertInfo {
160 property: "Issuer".to_string(),
161 value: cert_info.issuer,
162 },
163 CertInfo {
164 property: "Serial Number".to_string(),
165 value: cert_info.serial_number,
166 },
167 CertInfo {
168 property: "Valid From".to_string(),
169 value: cert_info.not_before,
170 },
171 CertInfo {
172 property: "Valid Until".to_string(),
173 value: cert_info.not_after,
174 },
175 CertInfo {
176 property: "Signature Algorithm".to_string(),
177 value: cert_info.signature_algorithm,
178 },
179 ];
180
181 let mut table = Table::new(&cert_data);
182 table
183 .with(Style::rounded())
184 .with(Modify::new(Rows::first()).with(Alignment::center()));
185
186 if self.config.use_colors {
187 writeln!(term, "{}", table.to_string().bright_white())?;
188 } else {
189 writeln!(term, "{}", table)?;
190 }
191
192 Ok(())
193 }
194
195 fn print_header(&self, title: &str, term: &mut Term) -> io::Result<()> {
197 let width = 60;
198 let padding = (width - title.len() - 2) / 2;
199
200 if self.config.use_colors {
201 writeln!(term, "\n{}", "═".repeat(width).bright_cyan())?;
202 writeln!(
203 term,
204 "{}{}{}{}",
205 "║".bright_cyan(),
206 " ".repeat(padding),
207 title.bold().bright_white(),
208 " ".repeat(width - padding - title.len() - 2) + "║"
209 )?;
210 writeln!(term, "{}", "═".repeat(width).bright_cyan())?;
211 } else {
212 writeln!(term, "\n{}", "=".repeat(width))?;
213 writeln!(
214 term,
215 "|{}{}{}|",
216 " ".repeat(padding),
217 title,
218 " ".repeat(width - padding - title.len() - 2)
219 )?;
220 writeln!(term, "{}", "=".repeat(width))?;
221 }
222
223 Ok(())
224 }
225
226 fn print_stats_box(&self, parsed: &ParsedPfx, term: &mut Term) -> io::Result<()> {
228 writeln!(term)?;
229
230 if self.config.use_colors {
231 writeln!(
232 term,
233 "{}",
234 "┌─ Statistics ─────────────────────────────────┐".bright_blue()
235 )?;
236 writeln!(
237 term,
238 "{} Files generated: {} {}",
239 "│".bright_blue(),
240 if parsed.has_chain() { "5+" } else { "2-3" }.bright_yellow(),
241 "│".bright_blue()
242 )?;
243 writeln!(
244 term,
245 "{} Certificate chain: {} {}",
246 "│".bright_blue(),
247 if parsed.has_chain() {
248 format!("{} certificates", parsed.chain_length() + 1).bright_green()
249 } else {
250 "No chain".bright_red()
251 },
252 "│".bright_blue()
253 )?;
254 writeln!(
255 term,
256 "{} Private key format: {} {}",
257 "│".bright_blue(),
258 "PKCS#8 PEM".bright_green(),
259 "│".bright_blue()
260 )?;
261 writeln!(
262 term,
263 "{}",
264 "└─────────────────────────────────────────────┘".bright_blue()
265 )?;
266 } else {
267 writeln!(term, "┌─ Statistics ─────────────────────────────────┐")?;
268 writeln!(
269 term,
270 "│ Files generated: {} │",
271 if parsed.has_chain() { "5+" } else { "2-3" }
272 )?;
273 writeln!(
274 term,
275 "│ Certificate chain: {} │",
276 if parsed.has_chain() {
277 format!("{} certificates", parsed.chain_length() + 1)
278 } else {
279 "No chain".to_string()
280 }
281 )?;
282 writeln!(term, "│ Private key format: PKCS#8 PEM │")?;
283 writeln!(term, "└─────────────────────────────────────────────┘")?;
284 }
285
286 Ok(())
287 }
288}