vendor/csv Vendor

Read and write CSV data from strings or files, with proper quoting and escaping.

import vendor/csv

The parser handles quoted fields, escaped quotes (""), embedded newlines, and trailing carriage returns. Every reading function accepts optional arguments thanks to Bern's clause-based defaults.

Data shape

Rows are returned as an object keyed by row number ("0", "1", …). With headers enabled (the default), each row is itself an object keyed by the header names; without headers, each row is a plain list of fields.

-- with headers (default)
#{ "0": #{ name: "Ana", age: "30" }#, "1": #{ name: "Bob", age: "25" }# }#

-- without headers / parse_csv_rows
#{ "0": ["name", "age"], "1": ["Ana", "30"], "2": ["Bob", "25"] }#

Reading & parsing

parse_csv(content, delimiter=",", has_header=true, skip_empty_lines=true) → object

Parse CSV text. With headers on, returns rows as header-keyed objects.

csv = "name,age\nAna,30\nBob,25"
rows = parse_csv(csv)
rows["0"]["name"]
-- Output: "Ana"
rows["1"]["age"]
-- Output: "25"
parse_csv_rows(content, delimiter=",", skip_empty_lines=true) → object

Parse into raw rows (lists of fields) without applying headers.

rows = parse_csv_rows("a,b\n1,2")
rows["0"]
-- Output: ["a", "b"]
rows["1"]
-- Output: ["1", "2"]
read_csv(file_name, delimiter=",", has_header=true, skip_empty_lines=true) → object

Read a file and parse it in one step (the file version of parse_csv).

rows = read_csv("people.csv")
rows["0"]["name"]
-- Output: (first person's name)
read_csv_rows(file_name, delimiter=",", skip_empty_lines=true) → object

Read a file into raw rows (the file version of parse_csv_rows).

rows = read_csv_rows("data.tsv", "\t")
rows["0"]
-- Output: (first row as a list of fields)

Writing

write_csv_from_list(headers, data_list, file_name, delimiter=",") → void

Write a list-of-lists dataset, prefixed by the header row. Fields are quoted automatically when needed.

write_csv_from_list(
    ["name", "age"],
    [["Ana", "30"], ["Bob", "25"]],
    "out.csv"
)
-- Writes:
-- name,age
-- Ana,30
-- Bob,25
write_csv_from_objects(headers, data_object, file_name, delimiter=",") → void

Write an indexed object of row-objects, pulling fields in the given header order.

people = #{
    "0": #{ name: "Ana", age: "30" }#,
    "1": #{ name: "Bob", age: "25" }#
}#
write_csv_from_objects(["name", "age"], people, "out.csv")

Putting it together

A full pipeline: read a file, transform a column, and write the result back out. Here we read people, increment everyone's age, and save:

Read → transform → write

import core
import strings
import vendor/csv

rows = parse_csv("name,age\nAna,30\nBob,25")

-- build the new rows as lists, in header order
out = []
loop i : [0..(length(keys(rows)) - 1)] do
    r = rows["" + i]
    next_age = to_int(r["age"]) + 1
    out = out <> [[r["name"], from_int(next_age)]]
end

write_csv_from_list(["name", "age"], out, "people_aged.csv")
-- people_aged.csv now contains Ana,31 and Bob,26

And a quick aggregation straight from parsed rows, with core:

Totalling a numeric column

import core
import vendor/csv

rows = parse_csv("item,price\npen,3\nbook,12\nlamp,20")

total = 0
loop i : [0..(length(keys(rows)) - 1)] do
    total = total + to_int(rows["" + i]["price"])
end

total
-- Output: 35