jtool-grep 0.2.1

notebook-specific grep tool for jtool
Documentation
//! CSV/TSV output formatter

use super::OutputFormatter;
use crate::types::GrepResult;
use anyhow::Result;

/// CSV output formatter
pub struct CsvFormatter {
    delimiter: char,
}

impl CsvFormatter {
    pub fn new() -> Self {
        Self { delimiter: ',' }
    }

    pub fn with_delimiter(delimiter: char) -> Self {
        Self { delimiter }
    }
}

impl Default for CsvFormatter {
    fn default() -> Self {
        Self::new()
    }
}

impl OutputFormatter for CsvFormatter {
    fn format_result(&self, result: &GrepResult) -> Result<String> {
        let mut output = String::new();

        // Header (only for single result)
        output.push_str(&format!(
            "notebook{}cell_index{}cell_number{}execution_count{}match_type{}line_index{}line_number{}matched_text{}line_content\n",
            self.delimiter, self.delimiter, self.delimiter, self.delimiter,
            self.delimiter, self.delimiter, self.delimiter, self.delimiter
        ));

        // Data rows
        for m in &result.matches {
            let exec_count = m.execution_count.map(|c| c.to_string()).unwrap_or_default();

            output.push_str(&format!(
                "\"{}\"{}{}{}{}{}\"{}\"{}\"{}\"{}{}{}{}{}\"{}\"{}\"{}\"",
                escape_csv(&result.notebook),
                self.delimiter,
                m.cell_index,
                self.delimiter,
                m.cell_number,
                self.delimiter,
                exec_count,
                self.delimiter,
                m.match_type,
                self.delimiter,
                m.line_index,
                self.delimiter,
                m.line_number,
                self.delimiter,
                escape_csv(&m.matched_text),
                self.delimiter,
                escape_csv(&m.line_content)
            ));
            output.push('\n');
        }

        Ok(output)
    }

    fn format_results(&self, results: &[GrepResult]) -> Result<String> {
        let mut output = String::new();

        // Header
        output.push_str(&format!(
            "notebook{}cell_index{}cell_number{}execution_count{}match_type{}line_index{}line_number{}matched_text{}line_content\n",
            self.delimiter, self.delimiter, self.delimiter, self.delimiter,
            self.delimiter, self.delimiter, self.delimiter, self.delimiter
        ));

        // Data rows from all results
        for result in results {
            for m in &result.matches {
                let exec_count = m.execution_count.map(|c| c.to_string()).unwrap_or_default();

                output.push_str(&format!(
                    "\"{}\"{}{}{}{}{}\"{}\"{}\"{}\"{}{}{}{}{}\"{}\"{}\"{}\"",
                    escape_csv(&result.notebook),
                    self.delimiter,
                    m.cell_index,
                    self.delimiter,
                    m.cell_number,
                    self.delimiter,
                    exec_count,
                    self.delimiter,
                    m.match_type,
                    self.delimiter,
                    m.line_index,
                    self.delimiter,
                    m.line_number,
                    self.delimiter,
                    escape_csv(&m.matched_text),
                    self.delimiter,
                    escape_csv(&m.line_content)
                ));
                output.push('\n');
            }
        }

        Ok(output)
    }
}

/// Escape CSV field
fn escape_csv(s: &str) -> String {
    s.replace('"', "\"\"")
}