rust_diff_analyzer/output/
formatter.rs

1// SPDX-FileCopyrightText: 2025 RAprogramm <andrey.rozanov.vl@gmail.com>
2// SPDX-License-Identifier: MIT
3
4use masterror::AppError;
5
6use super::{comment::format_comment, github::GithubFormatter, json::JsonFormatter};
7use crate::{
8    config::{Config, OutputFormat},
9    types::AnalysisResult,
10};
11
12/// Trait for output formatters
13pub trait Formatter {
14    /// Formats analysis result as string
15    ///
16    /// # Arguments
17    ///
18    /// * `result` - Analysis result to format
19    /// * `config` - Configuration
20    ///
21    /// # Returns
22    ///
23    /// Formatted string or error
24    ///
25    /// # Errors
26    ///
27    /// Returns error if formatting fails
28    fn format(&self, result: &AnalysisResult, config: &Config) -> Result<String, AppError>;
29}
30
31/// Formats analysis result using configured format
32///
33/// # Arguments
34///
35/// * `result` - Analysis result to format
36/// * `config` - Configuration
37///
38/// # Returns
39///
40/// Formatted string or error
41///
42/// # Errors
43///
44/// Returns error if formatting fails
45///
46/// # Examples
47///
48/// ```
49/// use rust_diff_analyzer::{
50///     config::Config,
51///     output::format_output,
52///     types::{AnalysisResult, AnalysisScope, Summary},
53/// };
54///
55/// let result = AnalysisResult::new(vec![], Summary::default(), AnalysisScope::new());
56/// let config = Config::default();
57/// let output = format_output(&result, &config).unwrap();
58/// ```
59pub fn format_output(result: &AnalysisResult, config: &Config) -> Result<String, AppError> {
60    match config.output.format {
61        OutputFormat::Github => GithubFormatter.format(result, config),
62        OutputFormat::Json => JsonFormatter.format(result, config),
63        OutputFormat::Human => format_human(result, config),
64        OutputFormat::Comment => Ok(format_comment(result, config)),
65    }
66}
67
68fn format_human(result: &AnalysisResult, _config: &Config) -> Result<String, AppError> {
69    let mut output = String::new();
70
71    output.push_str("=== Rust Diff Analysis ===\n\n");
72
73    output.push_str("Production:\n");
74    output.push_str(&format!("  Functions: {}\n", result.summary.prod_functions));
75    output.push_str(&format!("  Structs: {}\n", result.summary.prod_structs));
76    output.push_str(&format!("  Other: {}\n", result.summary.prod_other));
77    output.push_str(&format!(
78        "  Lines: +{} -{}\n",
79        result.summary.prod_lines_added, result.summary.prod_lines_removed
80    ));
81
82    output.push_str("\nTest:\n");
83    output.push_str(&format!("  Units: {}\n", result.summary.test_units));
84    output.push_str(&format!(
85        "  Lines: +{} -{}\n",
86        result.summary.test_lines_added, result.summary.test_lines_removed
87    ));
88
89    output.push_str(&format!(
90        "\nWeighted score: {}\n",
91        result.summary.weighted_score
92    ));
93
94    if result.summary.exceeds_limit {
95        output.push_str("\nLIMIT EXCEEDED\n");
96    }
97
98    if !result.changes.is_empty() {
99        output.push_str("\nChanges:\n");
100        for change in &result.changes {
101            output.push_str(&format!(
102                "  - {} ({}) in {} [+{} -{}]\n",
103                change.unit.name,
104                change.unit.kind.as_str(),
105                change.file_path.display(),
106                change.lines_added,
107                change.lines_removed
108            ));
109        }
110    }
111
112    Ok(output)
113}