From 4b718aae7556511e7461faf89b147a3416ea8f8e Mon Sep 17 00:00:00 2001 From: zhibog Date: Fri, 1 Nov 2019 22:35:46 +0100 Subject: [PATCH] Added an implementation for reading and writing csv files --- core/encoding/csv/csv.odin | 106 +++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 core/encoding/csv/csv.odin diff --git a/core/encoding/csv/csv.odin b/core/encoding/csv/csv.odin new file mode 100644 index 000000000..a8f387e69 --- /dev/null +++ b/core/encoding/csv/csv.odin @@ -0,0 +1,106 @@ +package csv + +// @note(zh): Encoding utility for csv files +// You may use the provided struct to build your csv dynamically. +// If you have a string with the whole content already, just use write_raw. +// You are able to read the csv with the headers included in the data or omitted by providing +// a bool parameter to the proc as shown down below. +// Example useage: +/* +import "core:fmt" +main :: proc() { + ctx: CSV; + ctx.data = {{"Col 1", "Col 2", "Col 3"}, {"aaa", "bbb", "ccc"}, {"ddd", "eee", "fff"}, {"ggg", "hhh", "iii"}}; + ctx.line_ending = CRLF; + file_name := "test.csv"; + + // Write file and read with the headers omitted + if isOkWrite := write(file_name, &ctx); isOkWrite { + if content, col_count, isOkRead := read(file_name, DELIMITER, true); isOkRead { + fmt.println("Column count(no headers): ", col_count); + fmt.println(content); + } + } + + // Write file and read with the headers being read as well + if isOkWrite := write(file_name, &ctx); isOkWrite { + if content, col_count, isOkRead := read(file_name); isOkRead { + fmt.println("Column count(with headers): ", col_count); + fmt.println(content); + } + } +} +*/ + +import "core:os" +import "core:strings" + +CSV :: struct { + data: [][]string, + line_ending: string, + delimiter: string, +}; + +LF :: "\n"; +CRLF :: "\r\n"; +DELIMITER :: ","; + +write :: proc(path: string, ctx: ^CSV) -> bool { + b := strings.make_builder(); + defer strings.destroy_builder(&b); + + if ctx.line_ending == "" do ctx.line_ending = LF; + if ctx.delimiter == "" do ctx.delimiter = DELIMITER; + + for row in ctx.data { + for col, i in row { + strings.write_string(&b, col); + if i + 1 < len(row) do strings.write_string(&b, ctx.delimiter); + } + strings.write_string(&b, ctx.line_ending); + } + return write_raw(path, b.buf[:]); +} + +write_raw :: proc(path: string, data: []byte) -> bool { + file, err := os.open(path, os.O_RDWR | os.O_CREATE | os.O_TRUNC); + if err != os.ERROR_NONE do return false; + defer os.close(file); + + if _, err := os.write(file, data); err != os.ERROR_NONE do return false; + return true; +} + +read :: proc(path: string, delimiter := DELIMITER, skip_header := false) -> ([]string, int, bool) { + if bytes, isOk := os.read_entire_file(path); isOk { + cols: [dynamic]string; + defer delete(cols); + out: [dynamic]string; + col_count := 0; + prev_index := 0; + for i := 0; i < len(bytes); i += 1 { + if bytes[i] == '\n' { + append(&cols, string(bytes[prev_index:i])); + i += 1; + prev_index = i; + col_count += 1; + } else if bytes[i] == '\r' { + if bytes[i + 1] == '\n' { + append(&cols, string(bytes[prev_index:i])); + i += 2; + prev_index = i; + col_count += 1; + } else { + append(&cols, string(bytes[prev_index:i])); + i += 1; + prev_index = i; + col_count += 1; + } + } + } + for col in cols do append(&out, ..strings.split(col, delimiter)); + if skip_header do return out[col_count:], col_count - 1, true; + else do return out[:], col_count, true; + } + return nil, -1, false; +} \ No newline at end of file