Skip to main content

jtool_grep/output/
csv.rs

1//! CSV/TSV output formatter
2
3use super::OutputFormatter;
4use crate::types::GrepResult;
5use anyhow::Result;
6
7/// CSV output formatter
8pub struct CsvFormatter {
9    delimiter: char,
10}
11
12impl CsvFormatter {
13    pub fn new() -> Self {
14        Self { delimiter: ',' }
15    }
16
17    pub fn with_delimiter(delimiter: char) -> Self {
18        Self { delimiter }
19    }
20}
21
22impl Default for CsvFormatter {
23    fn default() -> Self {
24        Self::new()
25    }
26}
27
28impl OutputFormatter for CsvFormatter {
29    fn format_result(&self, result: &GrepResult) -> Result<String> {
30        let mut output = String::new();
31
32        // Header (only for single result)
33        output.push_str(&format!(
34            "notebook{}cell_index{}cell_number{}execution_count{}match_type{}line_index{}line_number{}matched_text{}line_content\n",
35            self.delimiter, self.delimiter, self.delimiter, self.delimiter,
36            self.delimiter, self.delimiter, self.delimiter, self.delimiter
37        ));
38
39        // Data rows
40        for m in &result.matches {
41            let exec_count = m.execution_count.map(|c| c.to_string()).unwrap_or_default();
42
43            output.push_str(&format!(
44                "\"{}\"{}{}{}{}{}\"{}\"{}\"{}\"{}{}{}{}{}\"{}\"{}\"{}\"",
45                escape_csv(&result.notebook),
46                self.delimiter,
47                m.cell_index,
48                self.delimiter,
49                m.cell_number,
50                self.delimiter,
51                exec_count,
52                self.delimiter,
53                m.match_type,
54                self.delimiter,
55                m.line_index,
56                self.delimiter,
57                m.line_number,
58                self.delimiter,
59                escape_csv(&m.matched_text),
60                self.delimiter,
61                escape_csv(&m.line_content)
62            ));
63            output.push('\n');
64        }
65
66        Ok(output)
67    }
68
69    fn format_results(&self, results: &[GrepResult]) -> Result<String> {
70        let mut output = String::new();
71
72        // Header
73        output.push_str(&format!(
74            "notebook{}cell_index{}cell_number{}execution_count{}match_type{}line_index{}line_number{}matched_text{}line_content\n",
75            self.delimiter, self.delimiter, self.delimiter, self.delimiter,
76            self.delimiter, self.delimiter, self.delimiter, self.delimiter
77        ));
78
79        // Data rows from all results
80        for result in results {
81            for m in &result.matches {
82                let exec_count = m.execution_count.map(|c| c.to_string()).unwrap_or_default();
83
84                output.push_str(&format!(
85                    "\"{}\"{}{}{}{}{}\"{}\"{}\"{}\"{}{}{}{}{}\"{}\"{}\"{}\"",
86                    escape_csv(&result.notebook),
87                    self.delimiter,
88                    m.cell_index,
89                    self.delimiter,
90                    m.cell_number,
91                    self.delimiter,
92                    exec_count,
93                    self.delimiter,
94                    m.match_type,
95                    self.delimiter,
96                    m.line_index,
97                    self.delimiter,
98                    m.line_number,
99                    self.delimiter,
100                    escape_csv(&m.matched_text),
101                    self.delimiter,
102                    escape_csv(&m.line_content)
103                ));
104                output.push('\n');
105            }
106        }
107
108        Ok(output)
109    }
110}
111
112/// Escape CSV field
113fn escape_csv(s: &str) -> String {
114    s.replace('"', "\"\"")
115}