codelens_core/output/
console.rs1use std::io::Write;
4
5use colored::Colorize;
6use comfy_table::{presets::UTF8_FULL, Attribute, Cell, Color, ContentArrangement, Table};
7
8use crate::analyzer::stats::AnalysisResult;
9use crate::error::Result;
10
11use super::format::{OutputFormat, OutputOptions};
12
13pub struct ConsoleOutput;
15
16impl ConsoleOutput {
17 pub fn new() -> Self {
19 Self
20 }
21
22 fn format_size(bytes: u64) -> String {
23 const KB: u64 = 1024;
24 const MB: u64 = KB * 1024;
25 const GB: u64 = MB * 1024;
26
27 if bytes >= GB {
28 format!("{:.2} GB", bytes as f64 / GB as f64)
29 } else if bytes >= MB {
30 format!("{:.2} MB", bytes as f64 / MB as f64)
31 } else if bytes >= KB {
32 format!("{:.2} KB", bytes as f64 / KB as f64)
33 } else {
34 format!("{} B", bytes)
35 }
36 }
37
38 fn format_number(n: usize) -> String {
39 let s = n.to_string();
40 let mut result = String::new();
41 for (i, c) in s.chars().rev().enumerate() {
42 if i > 0 && i % 3 == 0 {
43 result.push(',');
44 }
45 result.push(c);
46 }
47 result.chars().rev().collect()
48 }
49}
50
51impl Default for ConsoleOutput {
52 fn default() -> Self {
53 Self::new()
54 }
55}
56
57impl OutputFormat for ConsoleOutput {
58 fn name(&self) -> &'static str {
59 "console"
60 }
61
62 fn extension(&self) -> &'static str {
63 "txt"
64 }
65
66 fn write(
67 &self,
68 result: &AnalysisResult,
69 options: &OutputOptions,
70 writer: &mut dyn Write,
71 ) -> Result<()> {
72 let summary = &result.summary;
73
74 writeln!(writer)?;
76 writeln!(writer, "{}", "═".repeat(60).dimmed())?;
77 writeln!(
78 writer,
79 "{}",
80 " CODELENS - Code Statistics Report ".bold().cyan()
81 )?;
82 writeln!(writer, "{}", "═".repeat(60).dimmed())?;
83 writeln!(writer)?;
84
85 let mut table = Table::new();
87 table
88 .load_preset(UTF8_FULL)
89 .set_content_arrangement(ContentArrangement::Dynamic);
90
91 table.set_header(vec![
92 Cell::new("Metric").add_attribute(Attribute::Bold),
93 Cell::new("Value").add_attribute(Attribute::Bold),
94 ]);
95
96 table.add_row(vec![
97 Cell::new("Total Files"),
98 Cell::new(Self::format_number(summary.total_files)).fg(Color::Green),
99 ]);
100 table.add_row(vec![
101 Cell::new("Code Lines"),
102 Cell::new(Self::format_number(summary.lines.code)).fg(Color::Cyan),
103 ]);
104 table.add_row(vec![
105 Cell::new("Comment Lines"),
106 Cell::new(Self::format_number(summary.lines.comment)).fg(Color::Yellow),
107 ]);
108 table.add_row(vec![
109 Cell::new("Blank Lines"),
110 Cell::new(Self::format_number(summary.lines.blank)).fg(Color::DarkGrey),
111 ]);
112 table.add_row(vec![
113 Cell::new("Total Lines"),
114 Cell::new(Self::format_number(summary.lines.total)).add_attribute(Attribute::Bold),
115 ]);
116 table.add_row(vec![
117 Cell::new("Total Size"),
118 Cell::new(Self::format_size(summary.total_size)),
119 ]);
120 table.add_row(vec![
121 Cell::new("Languages"),
122 Cell::new(summary.by_language.len().to_string()),
123 ]);
124 table.add_row(vec![
125 Cell::new("Functions"),
126 Cell::new(Self::format_number(summary.complexity.functions)),
127 ]);
128
129 writeln!(writer, "{table}")?;
130 writeln!(writer)?;
131
132 if !options.summary_only && !summary.by_language.is_empty() {
134 writeln!(writer, "{}", "By Language".bold())?;
135 writeln!(writer)?;
136
137 let mut lang_table = Table::new();
138 lang_table
139 .load_preset(UTF8_FULL)
140 .set_content_arrangement(ContentArrangement::Dynamic);
141
142 lang_table.set_header(vec![
143 Cell::new("Language").add_attribute(Attribute::Bold),
144 Cell::new("Files").add_attribute(Attribute::Bold),
145 Cell::new("Code").add_attribute(Attribute::Bold),
146 Cell::new("Comment").add_attribute(Attribute::Bold),
147 Cell::new("Blank").add_attribute(Attribute::Bold),
148 Cell::new("Total").add_attribute(Attribute::Bold),
149 ]);
150
151 let mut langs: Vec<_> = summary.by_language.iter().collect();
152
153 if let Some(n) = options.top_n {
155 langs.truncate(n);
156 }
157
158 for (name, stats) in langs {
159 lang_table.add_row(vec![
160 Cell::new(name).fg(Color::Cyan),
161 Cell::new(Self::format_number(stats.files)),
162 Cell::new(Self::format_number(stats.lines.code)).fg(Color::Green),
163 Cell::new(Self::format_number(stats.lines.comment)).fg(Color::Yellow),
164 Cell::new(Self::format_number(stats.lines.blank)).fg(Color::DarkGrey),
165 Cell::new(Self::format_number(stats.lines.total)),
166 ]);
167 }
168
169 writeln!(writer, "{lang_table}")?;
170 writeln!(writer)?;
171 }
172
173 writeln!(writer, "{}", "─".repeat(60).dimmed())?;
175 writeln!(
176 writer,
177 "Scanned {} files in {:.2}s",
178 result.scanned_files.to_string().green(),
179 result.elapsed.as_secs_f64()
180 )?;
181
182 Ok(())
183 }
184}