const std = @import("std"); pub const Record = struct { cols: [][]u8, pub fn deinit(self: Record, alloc: std.mem.Allocator) void { for (self.cols) |col| { alloc.free(col); } alloc.free(self.cols); } }; pub fn csvReader(reader: anytype) CsvReader(@TypeOf(reader)) { return .{ .reader = reader }; } pub fn CsvReader(comptime Reader: type) type { return struct { reader: Reader, const Self = @This(); pub fn next(self: *Self, alloc: std.mem.Allocator) !?Record { var prev_quote = false; var in_quote = false; var cols = std.ArrayList([]u8).init(alloc); defer cols.deinit(); var buf = std.ArrayList(u8).init(alloc); defer buf.deinit(); while (true) { var ch_buf: [1]u8 = undefined; if (try self.reader.read(&ch_buf) == 0) { if (buf.items.len != 0) { try cols.append(try buf.toOwnedSlice()); return Record{ .cols = try cols.toOwnedSlice() }; } return null; } const ch = ch_buf[0]; switch (ch) { '"' => { if (prev_quote) { try buf.append('"'); } else { prev_quote = true; in_quote = !in_quote; } }, ',' => { prev_quote = false; if (in_quote) { try buf.append(','); } else { defer buf.clearRetainingCapacity(); try cols.append(try alloc.dupe(u8, buf.items)); } }, '\n' => { try cols.append(try buf.toOwnedSlice()); return Record{ .cols = try cols.toOwnedSlice() }; }, else => { prev_quote = false; try buf.append(ch); }, } } } }; }