Browse Source

Added an implementation for reading and writing csv files

zhibog 5 years ago
parent
commit
4b718aae75
1 changed files with 106 additions and 0 deletions
  1. 106 0
      core/encoding/csv/csv.odin

+ 106 - 0
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;
+}