Rill v0.13 Reference

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_rows
  • test_quotes_and_line_breaks
  • test_refusals_name_the_line
  • test_other_separators
  • test_records_by_name
  • test_write_quotes_what_it_must