tabiew 0.14.0

A lightweight TUI application to view and query tabular data files, such as CSV, TSV, and parquet.
use std::{fs::File, io::Write};

use polars::{frame::DataFrame, prelude::AnyValue};

use crate::{AppResult, misc::osc52::CopyToClipboardOsc52};

use super::traits::{Destination, WriteToFile};

#[derive(Debug, Default)]
pub struct WriteToMarkdown;

impl WriteToFile for WriteToMarkdown {
    fn write_to_file(&self, dest: Destination, data_frame: &mut DataFrame) -> AppResult<()> {
        let buf = render_markdown(data_frame);
        match dest {
            Destination::File(path) => {
                File::create(path)?.write_all(buf.as_bytes())?;
                Ok(())
            }
            Destination::Clipboard => {
                buf.into_bytes().copy_to_clipboard_via_osc52();
                Ok(())
            }
        }
    }
}

fn render_markdown(df: &DataFrame) -> String {
    let columns = df.columns();
    let height = df.height();

    let headers: Vec<String> = columns
        .iter()
        .map(|c| escape_cell(c.name().as_str()))
        .collect();

    let mut out = String::new();

    out.push('|');
    for h in &headers {
        out.push(' ');
        out.push_str(h);
        out.push_str(" |");
    }
    out.push('\n');

    out.push('|');
    for _ in &headers {
        out.push_str("---|");
    }
    out.push('\n');

    for i in 0..height {
        out.push('|');
        for col in columns {
            let cell = match col.get(i) {
                Ok(v) => escape_cell(&format_value(&v)),
                Err(_) => String::new(),
            };
            out.push(' ');
            out.push_str(&cell);
            out.push_str(" |");
        }
        out.push('\n');
    }

    out
}

fn format_value(v: &AnyValue<'_>) -> String {
    match v {
        AnyValue::String(s) => (*s).to_string(),
        AnyValue::StringOwned(s) => s.to_string(),
        _ => v.to_string(),
    }
}

fn escape_cell(s: &str) -> String {
    s.replace('\\', "\\\\")
        .replace('|', "\\|")
        .replace('\r', "")
        .replace('\n', "<br>")
}