1pub mod dot;
20pub mod evidence_formatter;
21pub mod formatters;
22pub mod json;
23pub mod llm_markdown;
24pub mod markdown;
25pub mod pattern_analysis;
26pub mod pattern_formatter;
27pub mod terminal;
28pub mod unified;
29
30use crate::io::view_formatters;
31use crate::priority::tiers::TierConfig;
32use crate::priority::view::{PreparedDebtView, SortCriteria, ViewConfig};
33use crate::priority::view_pipeline;
34use crate::{core::AnalysisResults, formatting::FormattingConfig, io, priority, risk};
35use anyhow::Result;
36use std::path::PathBuf;
37
38pub struct OutputConfig {
39 pub top: Option<usize>,
40 pub tail: Option<usize>,
41 pub summary: bool,
42 pub verbosity: u8,
43 pub output_file: Option<PathBuf>,
44 pub output_format: Option<crate::cli::OutputFormat>,
45 pub formatting_config: FormattingConfig,
46 pub show_filter_stats: bool,
47}
48
49pub fn output_with_prepared_view(
67 analysis: &priority::UnifiedAnalysis,
68 config: &OutputConfig,
69) -> Result<()> {
70 let view_config = build_view_config(config);
72 let tier_config = TierConfig::default();
73
74 let view = view_pipeline::prepare_view(analysis, &view_config, &tier_config);
76
77 output_prepared_view(&view, config)
79}
80
81fn output_prepared_view(view: &PreparedDebtView, config: &OutputConfig) -> Result<()> {
85 match &config.output_format {
86 Some(crate::cli::OutputFormat::Json) => {
87 let include_scoring_details = config.verbosity >= 2;
88 let json = view_formatters::format_json(view, include_scoring_details);
89 write_output(&json, &config.output_file)
90 }
91 Some(crate::cli::OutputFormat::Markdown) => {
92 let md_config = view_formatters::MarkdownConfig {
93 verbosity: config.verbosity,
94 show_filter_stats: config.show_filter_stats,
95 };
96 let markdown = view_formatters::format_markdown(view, &md_config);
97 write_output(&markdown, &config.output_file)
98 }
99 _ => {
100 if is_markdown_file(&config.output_file) {
102 let md_config = view_formatters::MarkdownConfig {
103 verbosity: config.verbosity,
104 show_filter_stats: config.show_filter_stats,
105 };
106 let markdown = view_formatters::format_markdown(view, &md_config);
107 write_output(&markdown, &config.output_file)
108 } else {
109 let term_config = view_formatters::TerminalConfig {
110 verbosity: config.verbosity,
111 use_color: config.formatting_config.color.should_use_color(),
112 summary_mode: config.summary,
113 };
114 let terminal = view_formatters::format_terminal(view, &term_config);
115 write_output(&terminal, &config.output_file)
116 }
117 }
118 }
119}
120
121fn build_view_config(config: &OutputConfig) -> ViewConfig {
123 let limit = match (config.top, config.tail) {
125 (Some(n), _) => Some(n),
126 (_, Some(n)) => Some(n),
127 _ => None,
128 };
129
130 let min_score_threshold = std::env::var("DEBTMAP_MIN_SCORE_THRESHOLD")
132 .ok()
133 .and_then(|s| s.parse::<f64>().ok())
134 .unwrap_or(3.0);
135
136 ViewConfig {
137 min_score_threshold,
138 exclude_t4_maintenance: true, limit,
140 sort_by: SortCriteria::Score, compute_groups: false, }
143}
144
145fn write_output(content: &str, output_file: &Option<PathBuf>) -> Result<()> {
147 use crate::progress::ProgressManager;
148
149 if let Some(pm) = ProgressManager::global() {
151 let _ = pm.clear();
152 }
153
154 if let Some(path) = output_file {
155 if let Some(parent) = path.parent() {
156 io::ensure_dir(parent)?;
157 }
158 std::fs::write(path, content)?;
159 } else {
160 println!("{content}");
161 }
162 Ok(())
163}
164
165pub use dot::*;
166pub use evidence_formatter::*;
167pub use formatters::*;
168pub use json::*;
169pub use llm_markdown::*;
170pub use markdown::*;
171pub use pattern_analysis::*;
172pub use pattern_formatter::*;
173pub use terminal::*;
174pub use unified::*;
175
176pub fn output_results_with_risk(
177 results: AnalysisResults,
178 risk_insights: Option<risk::RiskInsight>,
179 format: io::output::OutputFormat,
180 output_file: Option<PathBuf>,
181) -> Result<()> {
182 match output_file {
183 Some(path) => {
184 let content = format_results_to_string(&results, &risk_insights, format)?;
185 io::write_file(&path, &content)?;
186 }
187 None => {
188 let mut writer = io::output::create_writer(format);
189 writer.write_results(&results)?;
190 if let Some(insights) = risk_insights {
191 writer.write_risk_insights(&insights)?;
192 }
193 }
194 }
195 Ok(())
196}
197
198pub fn output_unified_priorities_with_config(
199 analysis: priority::UnifiedAnalysis,
200 config: OutputConfig,
201 results: &AnalysisResults,
202 _coverage_file: Option<&PathBuf>,
203) -> Result<()> {
204 output_unified_priorities_with_summary(
205 analysis,
206 config.top,
207 config.tail,
208 config.summary,
209 config.verbosity,
210 config.output_file,
211 config.output_format,
212 config.formatting_config,
213 results,
214 config.show_filter_stats,
215 )
216}
217
218#[allow(clippy::too_many_arguments)]
219pub fn output_unified_priorities(
220 analysis: priority::UnifiedAnalysis,
221 top: Option<usize>,
222 tail: Option<usize>,
223 verbosity: u8,
224 output_file: Option<PathBuf>,
225 output_format: Option<crate::cli::OutputFormat>,
226 formatting_config: FormattingConfig,
227 results: &AnalysisResults,
228) -> Result<()> {
229 output_unified_priorities_with_summary(
230 analysis,
231 top,
232 tail,
233 false, verbosity,
235 output_file,
236 output_format,
237 formatting_config,
238 results,
239 false, )
241}
242
243#[allow(clippy::too_many_arguments)]
244pub fn output_unified_priorities_with_summary(
245 analysis: priority::UnifiedAnalysis,
246 top: Option<usize>,
247 tail: Option<usize>,
248 summary: bool,
249 verbosity: u8,
250 output_file: Option<PathBuf>,
251 output_format: Option<crate::cli::OutputFormat>,
252 formatting_config: FormattingConfig,
253 _results: &AnalysisResults,
254 show_filter_stats: bool,
255) -> Result<()> {
256 match output_format {
257 Some(crate::cli::OutputFormat::Json) => {
258 let include_scoring_details = verbosity >= 2;
259 json::output_json_with_format(
260 &analysis,
261 top,
262 tail,
263 output_file,
264 include_scoring_details,
265 )
266 }
267 Some(crate::cli::OutputFormat::Markdown) => {
268 let include_scoring_details = verbosity >= 2;
269 llm_markdown::output_llm_markdown_with_format(
270 &analysis,
271 top,
272 tail,
273 output_file,
274 include_scoring_details,
275 )
276 }
277 Some(crate::cli::OutputFormat::Dot) => {
278 dot::output_dot_default(&analysis, output_file)
280 }
281 _ => {
282 if is_markdown_file(&output_file) {
283 markdown::output_markdown(
284 &analysis,
285 top,
286 tail,
287 verbosity,
288 output_file,
289 formatting_config,
290 show_filter_stats,
291 )
292 } else {
293 terminal::output_terminal_with_mode(
294 &analysis,
295 top,
296 tail,
297 verbosity,
298 output_file,
299 formatting_config,
300 summary,
301 )
302 }
303 }
304 }
305}
306
307fn is_markdown_file(output_file: &Option<PathBuf>) -> bool {
308 output_file
309 .as_ref()
310 .and_then(|p| p.extension())
311 .map(|ext| ext == "md")
312 .unwrap_or(false)
313}