ras-filesystem 2.0.0

Sandboxed file system for the agent (Csv, Docx, Pdf, Md, Json, Jsonl, Html, Txt)
Documentation
#[must_use]
pub fn normalize_csv(raw: &str) -> String {
    let mut out_lines: Vec<String> = Vec::new();
    let mut leading_blank = true;
    for line in raw.lines() {
        let trimmed = line.trim_end();
        if trimmed.is_empty() && leading_blank {
            continue;
        }
        leading_blank = false;
        out_lines.push(normalize_row(trimmed));
    }
    while out_lines.last().is_some_and(|l| l.is_empty()) {
        out_lines.pop();
    }
    out_lines.join("\n")
}

fn normalize_row(line: &str) -> String {
    let fields = split_respecting_quotes(line);
    fields
        .into_iter()
        .map(quote_if_needed)
        .collect::<Vec<_>>()
        .join(",")
}

fn split_respecting_quotes(line: &str) -> Vec<String> {
    let mut fields: Vec<String> = Vec::new();
    let mut buf = String::new();
    let mut in_quotes = false;
    let mut field_started_quoted = false;
    let mut chars = line.chars().peekable();
    while let Some(c) = chars.next() {
        match c {
            '"' if in_quotes => {
                if matches!(chars.peek(), Some('"')) {
                    buf.push('"');
                    chars.next();
                } else {
                    in_quotes = false;
                }
            }
            '"' if !field_started_quoted && buf.trim().is_empty() => {
                in_quotes = true;
                field_started_quoted = true;
                buf.clear();
            }
            '"' => buf.push('"'),
            ',' if !in_quotes => {
                fields.push(buf.trim().to_string());
                buf.clear();
                field_started_quoted = false;
            }
            _ => buf.push(c),
        }
    }
    fields.push(buf.trim().to_string());
    fields
}

fn quote_if_needed(field: String) -> String {
    let needs = field.contains(',') || field.contains('"') || field.contains('\n');
    if !needs {
        return field;
    }
    let escaped = field.replace('"', "\"\"");
    format!("\"{escaped}\"")
}