80 lines
2.3 KiB
Zig
80 lines
2.3 KiB
Zig
|
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);
|
||
|
},
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
}
|