A robust RFC 4180 compliant CSV parser with support for custom delimiters, quoted fields, and serialization
///|
test "basic csv parsing" {
let csv = "name,age,city\nAlice,30,NYC\nBob,25,LA"
let doc = @csv_parser.parse(csv).unwrap()
@json.inspect(doc[0], content=["name", "age", "city"])
@json.inspect(doc[1], content=["Alice", "30", "NYC"])
@json.inspect(doc[2], content=["Bob", "25", "LA"])
}{
"deps": {
"maria/csv_parser": "*"
}
}{
"import": [
"maria/csv_parser"
]
}///|
test "parsing functions" {
// Parse with default config (comma delimiter)
let csv1 = "a,b,c\n1,2,3"
let doc1 = @csv_parser.parse(csv1).unwrap()
@json.inspect(doc1, content=[["a", "b", "c"], ["1", "2", "3"]])
// Parse TSV (tab-separated values)
let tsv = "a\tb\tc\n1\t2\t3"
let doc2 = @csv_parser.parse_tsv(tsv).unwrap()
@json.inspect(doc2, content=[["a", "b", "c"], ["1", "2", "3"]])
// Parse with custom delimiter
let csv3 = "a;b;c\n1;2;3"
let config = @csv_parser.CsvConfig::with_delimiter(';')
let doc3 = @csv_parser.parse_with_config(csv3, config).unwrap()
@json.inspect(doc3, content=[["a", "b", "c"], ["1", "2", "3"]])
}///|
test "serialization" {
let doc : @csv_parser.Document = [
["name", "email"],
["Alice", "alice@example.com"],
["Bob", "bob@example.com"],
]
let csv = @csv_parser.document_to_string(doc)
inspect(
csv,
content="name,email\nAlice,alice@example.com\nBob,bob@example.com",
)
}///|
test "quoted fields with commas" {
let csv =
#|"product","description","price"
#|"Widget A","A useful widget, very popular","19.99"
#|
let doc = @csv_parser.parse(csv).unwrap()
@json.inspect(doc[1], content=[
"Widget A", "A useful widget, very popular", "19.99",
])
}///|
test "escaped quotes" {
let csv = "\"She said \"\"hello\"\"\",\"End\""
let doc = @csv_parser.parse(csv).unwrap()
@json.inspect(doc[0], content=["She said \"hello\"", "End"])
}///|
test "multiline fields" {
let csv = "\"field1\",\"line1\nline2\nline3\",\"field3\""
let doc = @csv_parser.parse(csv).unwrap()
inspect(doc[0][1], content="line1\nline2\nline3")
}///|
test "error handling" {
let invalid_csv = "\"unterminated quote"
let result = @csv_parser.parse(invalid_csv)
match result {
Err(@csv_parser.CsvError::UnterminatedQuote(row, col)) => {
inspect(row, content="1")
inspect(col, content="20")
}
_ => fail("Expected UnterminatedQuote error")
}
}///|
test "custom configuration" {
let config : @csv_parser.CsvConfig = {
delimiter: '|',
quote: '\'',
skip_empty_lines: false,
}
let csv = "name|age\n'Alice'|30\n\n'Bob'|25"
let doc = @csv_parser.parse_with_config(csv, config).unwrap()
inspect(doc.length(), content="4")
@json.inspect(doc[0], content=["name", "age"])
@json.inspect(doc[1], content=["Alice", "30"])
@json.inspect(doc[2], content=[""])
@json.inspect(doc[3], content=["Bob", "25"])
}///|
test "roundtrip conversion" {
let original = "a,b,c\n1,2,3\n\"hello, world\",test,end"
let doc = @csv_parser.parse(original).unwrap()
let serialized = @csv_parser.document_to_string(doc)
let reparsed = @csv_parser.parse(serialized).unwrap()
@json.inspect(reparsed, content=[
["a", "b", "c"],
["1", "2", "3"],
["hello, world", "test", "end"],
])
}pub(all) struct CsvConfig {
delimiter : Char
quote : Char
skip_empty_lines : Bool
}pub(all) enum CsvError {
UnexpectedEof(Int, Int)
UnterminatedQuote(Int, Int)
InvalidQuoteEscape(Int, Int)
}Install
Download zipA robust RFC 4180 compliant CSV parser with support for custom delimiters, quoted fields, and serialization