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, row_count, isOkRead := read(file_name, DELIMITER, true); isOkRead { fmt.println("Column count(no headers): ", row_count); fmt.println(content); } } // Write file and read with the headers being read as well if isOkWrite := write(file_name, &ctx); isOkWrite { if content, row_count, isOkRead := read(file_name); isOkRead { fmt.println("Column count(with headers): ", row_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; row_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; row_count += 1; } else if bytes[i] == '\r' { if bytes[i + 1] == '\n' { append(&cols, string(bytes[prev_index:i])); i += 2; prev_index = i; row_count += 1; } else { append(&cols, string(bytes[prev_index:i])); i += 1; prev_index = i; row_count += 1; } } } for col in cols do append(&out, ..strings.split(col, delimiter)); if skip_header do return out[row_count:], row_count - 1, true; else do return out[:], row_count, true; } return nil, -1, false; }