aptu_coder_core/formatter/
render.rs1use crate::types::{ModuleInfo, SemanticAnalysis};
6use std::fmt::Write;
7use std::path::Path;
8use thiserror::Error;
9use tracing::instrument;
10
11use super::emit;
12
13#[derive(Debug, Error)]
14pub enum FormatterError {
15 #[error("Graph error: {0}")]
16 GraphError(#[from] crate::graph::GraphError),
17}
18
19#[instrument(skip_all)]
21pub fn format_file_details(
22 path: &str,
23 analysis: &SemanticAnalysis,
24 line_count: usize,
25 is_test: bool,
26 base_path: Option<&Path>,
27) -> String {
28 let mut output = String::new();
29
30 let display_path = emit::strip_base_path(Path::new(path), base_path);
31 let fn_count = analysis.functions.len();
32 let class_count = analysis.classes.len();
33 let import_count = analysis.imports.len();
34 if is_test {
35 let _ = writeln!(
36 output,
37 "FILE [TEST] {display_path}({line_count}L, {fn_count}F, {class_count}C, {import_count}I)"
38 );
39 } else {
40 let _ = writeln!(
41 output,
42 "FILE: {display_path}({line_count}L, {fn_count}F, {class_count}C, {import_count}I)"
43 );
44 }
45
46 output.push_str(&emit::format_classes_section(
47 &analysis.classes,
48 &analysis.functions,
49 ));
50
51 let top_level_functions: Vec<&crate::types::FunctionInfo> = analysis
52 .functions
53 .iter()
54 .filter(|func| {
55 !analysis
56 .classes
57 .iter()
58 .any(|class| emit::is_method_of_class(func, class))
59 })
60 .collect();
61
62 if !top_level_functions.is_empty() {
63 output.push_str("F:\n");
64 output.push_str(&emit::format_function_list_wrapped(
65 top_level_functions.iter().copied(),
66 &analysis.call_frequency,
67 ));
68 }
69
70 output.push_str(&emit::format_imports_section(&analysis.imports));
71
72 output
73}
74
75#[instrument(skip_all)]
77pub fn format_file_details_summary(
78 semantic: &SemanticAnalysis,
79 path: &str,
80 line_count: usize,
81) -> String {
82 let mut output = String::new();
83
84 output.push_str("FILE:\n");
85 let _ = writeln!(output, " path: {path}");
86 let fn_count = semantic.functions.len();
87 let class_count = semantic.classes.len();
88 let _ = writeln!(output, " {line_count}L, {fn_count}F, {class_count}C");
89 output.push('\n');
90
91 if !semantic.functions.is_empty() {
92 output.push_str("TOP FUNCTIONS BY SIZE:\n");
93 let mut funcs: Vec<&crate::types::FunctionInfo> = semantic.functions.iter().collect();
94 let k = funcs.len().min(10);
95 if k > 0 {
96 funcs.select_nth_unstable_by(k.saturating_sub(1), |a, b| {
97 let a_span = a.end_line.saturating_sub(a.line);
98 let b_span = b.end_line.saturating_sub(b.line);
99 b_span.cmp(&a_span)
100 });
101 funcs[..k].sort_by(|a, b| {
102 let a_span = a.end_line.saturating_sub(a.line);
103 let b_span = b.end_line.saturating_sub(b.line);
104 b_span.cmp(&a_span)
105 });
106 }
107
108 for func in &funcs[..k] {
109 let span = func.end_line.saturating_sub(func.line);
110 let params = if func.parameters.is_empty() {
111 String::new()
112 } else {
113 format!("({})", func.parameters.join(", "))
114 };
115 let _ = writeln!(
116 output,
117 " {}:{}: {} {} [{}L]",
118 func.line, func.end_line, func.name, params, span
119 );
120 }
121 output.push('\n');
122 }
123
124 if !semantic.classes.is_empty() {
125 output.push_str("CLASSES:\n");
126 if semantic.classes.len() <= 10 {
127 let class_strs: Vec<String> = semantic
128 .classes
129 .iter()
130 .map(|c| {
131 if c.inherits.is_empty() {
132 format!("{}:{}-{}", c.name, c.line, c.end_line)
133 } else {
134 format!(
135 "{}:{}-{} ({})",
136 c.name,
137 c.line,
138 c.end_line,
139 c.inherits.join(", ")
140 )
141 }
142 })
143 .collect();
144 let _ = writeln!(output, " {}", class_strs.join("; "));
145 } else {
146 let _ = writeln!(output, " {} classes total", semantic.classes.len());
147 for class in semantic.classes.iter().take(5) {
148 let _ = writeln!(output, " {}", class.name);
149 }
150 if semantic.classes.len() > 5 {
151 let _ = writeln!(output, " ... and {} more", semantic.classes.len() - 5);
152 }
153 }
154 output.push('\n');
155 }
156
157 if !semantic.imports.is_empty() {
158 output.push_str("IMPORTS:\n");
159 let import_strs: Vec<String> = semantic
160 .imports
161 .iter()
162 .map(|i| {
163 if i.items.is_empty() {
164 format!(" {}", i.module)
165 } else {
166 format!(" {}: {}", i.module, i.items.join(", "))
167 }
168 })
169 .collect();
170 for s in import_strs {
171 let _ = writeln!(output, "{s}");
172 }
173 output.push('\n');
174 }
175
176 output.push_str("SUGGESTION:\n");
177 output.push_str("Use analyze_file for full function list, signatures, imports, and classes\n");
178
179 output
180}
181
182#[instrument(skip_all)]
184pub fn format_module_info(info: &ModuleInfo) -> String {
185 use std::fmt::Write as _;
186 let fn_count = info.functions.len();
187 let import_count = info.imports.len();
188 let mut out = String::with_capacity(64 + fn_count * 24 + import_count * 32);
189 let _ = writeln!(
190 out,
191 "FILE: {} ({}L, {}F, {}I)",
192 info.name, info.line_count, fn_count, import_count
193 );
194 if !info.functions.is_empty() {
195 out.push_str("F:\n ");
196 let parts: Vec<String> = info
197 .functions
198 .iter()
199 .map(|f| format!("{}:{}", f.name, f.line))
200 .collect();
201 out.push_str(&parts.join(", "));
202 out.push('\n');
203 }
204 if !info.imports.is_empty() {
205 out.push_str("I:\n ");
206 let parts: Vec<String> = info
207 .imports
208 .iter()
209 .map(|i| {
210 if i.items.is_empty() {
211 i.module.clone()
212 } else {
213 format!("{}:{}", i.module, i.items.join(", "))
214 }
215 })
216 .collect();
217 out.push_str(&parts.join("; "));
218 out.push('\n');
219 }
220 out
221}