Skip to main content

codelens_core/output/
csv.rs

1//! CSV output format.
2
3use std::io::Write;
4
5use crate::analyzer::stats::AnalysisResult;
6use crate::error::Result;
7
8use super::format::{OutputFormat, OutputOptions};
9
10/// CSV output formatter.
11pub struct CsvOutput;
12
13impl CsvOutput {
14    /// Create a new CSV output formatter.
15    pub fn new() -> Self {
16        Self
17    }
18}
19
20impl Default for CsvOutput {
21    fn default() -> Self {
22        Self::new()
23    }
24}
25
26impl OutputFormat for CsvOutput {
27    fn name(&self) -> &'static str {
28        "csv"
29    }
30
31    fn extension(&self) -> &'static str {
32        "csv"
33    }
34
35    fn write(
36        &self,
37        result: &AnalysisResult,
38        _options: &OutputOptions,
39        writer: &mut dyn Write,
40    ) -> Result<()> {
41        // Header
42        writeln!(writer, "Language,Files,Code,Comment,Blank,Total,Size")?;
43
44        // Data rows
45        for (name, stats) in &result.summary.by_language {
46            writeln!(
47                writer,
48                "{},{},{},{},{},{},{}",
49                name,
50                stats.files,
51                stats.lines.code,
52                stats.lines.comment,
53                stats.lines.blank,
54                stats.lines.total,
55                stats.size
56            )?;
57        }
58
59        Ok(())
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66    use crate::analyzer::stats::{FileStats, LineStats, Summary};
67    use std::path::PathBuf;
68    use std::time::Duration;
69
70    fn make_test_result() -> AnalysisResult {
71        let files = vec![
72            FileStats {
73                path: PathBuf::from("main.rs"),
74                language: "Rust".to_string(),
75                lines: LineStats {
76                    total: 100,
77                    code: 80,
78                    comment: 10,
79                    blank: 10,
80                },
81                size: 2000,
82                complexity: Default::default(),
83            },
84            FileStats {
85                path: PathBuf::from("test.py"),
86                language: "Python".to_string(),
87                lines: LineStats {
88                    total: 50,
89                    code: 40,
90                    comment: 5,
91                    blank: 5,
92                },
93                size: 1000,
94                complexity: Default::default(),
95            },
96        ];
97        AnalysisResult {
98            summary: Summary::from_file_stats(&files),
99            files,
100            elapsed: Duration::from_millis(100),
101            scanned_files: 2,
102            skipped_files: 0,
103        }
104    }
105
106    #[test]
107    fn test_csv_output_name() {
108        let output = CsvOutput::new();
109        assert_eq!(output.name(), "csv");
110        assert_eq!(output.extension(), "csv");
111    }
112
113    #[test]
114    fn test_csv_output_header() {
115        let output = CsvOutput;
116        let result = make_test_result();
117        let options = OutputOptions::default();
118
119        let mut buffer = Vec::new();
120        output.write(&result, &options, &mut buffer).unwrap();
121
122        let csv_str = String::from_utf8(buffer).unwrap();
123        let lines: Vec<&str> = csv_str.lines().collect();
124
125        assert_eq!(lines[0], "Language,Files,Code,Comment,Blank,Total,Size");
126    }
127
128    #[test]
129    fn test_csv_output_data() {
130        let output = CsvOutput;
131        let result = make_test_result();
132        let options = OutputOptions::default();
133
134        let mut buffer = Vec::new();
135        output.write(&result, &options, &mut buffer).unwrap();
136
137        let csv_str = String::from_utf8(buffer).unwrap();
138
139        // Should contain language data
140        assert!(csv_str.contains("Rust"));
141        assert!(csv_str.contains("Python"));
142        assert!(csv_str.contains(",80,")); // Rust code lines
143        assert!(csv_str.contains(",40,")); // Python code lines
144    }
145
146    #[test]
147    fn test_csv_output_line_count() {
148        let output = CsvOutput;
149        let result = make_test_result();
150        let options = OutputOptions::default();
151
152        let mut buffer = Vec::new();
153        output.write(&result, &options, &mut buffer).unwrap();
154
155        let csv_str = String::from_utf8(buffer).unwrap();
156        let lines: Vec<&str> = csv_str.lines().collect();
157
158        // 1 header + 2 languages = 3 lines
159        assert_eq!(lines.len(), 3);
160    }
161}