Standard library · Data formats
data/csv
Imported as import "data/csv" as csv, its names are then csv.…. Every signature below is the one the checker infers.
CSV, read and written, as RFC 4180 has it: a field is quoted when it holds the separator, a quote, or a line break; a quote inside a quoted field is doubled; a line ends in \n or \r\n. Reading is forgiving in the ways the files people actually have require — a quote in the middle of an unquoted field is a quote, a blank line is nothing, a short row is a short row — and strict where forgiveness would hide an error: a quoted field that never closes, or text after its closing quote, is refused, and the refusal says which line.
The separator is a comma unless parse_with / write_with are told otherwise — a semicolon or a tab, as a spreadsheet in another locale writes. Fields are bytes; the file's encoding is the caller's business, and UTF-8 passes through untouched.
rows = csv.parse("name,age\nada,36\n")? rows # => Cons(Cons(name, Cons(age, Nil)), Cons(Cons(ada, Cons(36, Nil)), Nil)) people = csv.records("name,age\nada,36\n")? match people Cons(p, _) -> map_or(p, "name", "?") Nil -> "nobody" csv.write(rows) # => name,age\nada,36
Types
Scan
Scan(at: Int, mode: Int, fields: List(Str), rows: List(List(Str)), line: Int, opened: Int, err: Str)
Functions
fn parse(text: Str) -> Result(List(List(Str)), Str)
csv.parse("a,b\n1,\"x,y\"\n") # => Ok(Cons(Cons(a, Cons(b, Nil)), Cons(Cons(1, Cons(x,y, Nil)), Nil))) csv.parse("a,\"open") # => Err(a quoted field opened on line 1 is not closed)
fn parse_with(text: Str, sep: Int) -> Result(List(List(Str)), Str)
The rows of text, each a list of its fields, or the first error.
csv.parse_with("a;b\n", ";"[0]) # => Ok(Cons(Cons(a, Cons(b, Nil)), Nil))
fn records(text: Str) -> Result(List(Map(Str, Str)), Str)
The rows as records: the first line names the fields, and every line after it is a map from those names to its values. A short row leaves its missing fields out; a long one's extras are dropped.
people = csv.records("name,age\nada,36\nbob\n")? map(people, \p -> map_or(p, "name", "?")) # => Cons(ada, Cons(bob, Nil)) map(people, \p -> map_or(p, "age", "?")) # => Cons(36, Cons(?, Nil))
fn records_with(text: Str, sep: Int) -> Result(List(Map(Str, Str)), Str)
people = csv.records_with("k\tv\na\t1\n", 9)? map(people, \p -> map_or(p, "v", "?")) # => Cons(1, Nil)
fn write(rows: List(List(Str))) -> Str
csv.write(Cons(Cons("a", Cons("b,c", Nil)), Nil)) # => a,"b,c" csv.write(Cons(Cons("say \"hi\"", Nil), Nil)) # => "say ""hi"""
fn write_with(rows: List(List(Str)), sep: Int) -> Str
The rows as text, one a line, ending in \n; a field is quoted only when it has to be.
csv.write_with(Cons(Cons("a", Cons("b", Nil)), Nil), ";"[0]) # => a;b
Tests
test_plain_rowstest_quotes_and_line_breakstest_refusals_name_the_linetest_other_separatorstest_records_by_nametest_write_quotes_what_it_must