/*
* csv.d
* Lightweight implementation of a CSV parser
*/
module tiny.csv;
import std.stdio : File;
// Read CSV lines from a file into the struct of type T
struct CSVreader(T,F=File) {
private:
import core.stdc.ctype : isdigit;
import std.exception : enforce;
F f;
char sep = ',';
bool have_elem = false;
bool at_eof = false;
string curline;
T elem;
// Tell if we've consumed all of the current line
bool at_eol() {
return this.curline.length == 0;
}
// Get next char from input stream
char nextc() {
enforce(!this.at_eol(), "Short CSV line");
char c = this.curline[0];
this.curline = this.curline[1 .. $];
return c;
}
// Get integer CSV column value
int get_int() {
auto c = this.nextc();
enforce(isdigit(c), "Bad digits in CSV numeric field");
int res = c - '0';
while (true) {
// Last field in CSV input line
if (this.at_eol()) {
return res;
}
// Another digit?
c = this.nextc();
if (isdigit(c)) {
res *= 10;
res += (c - '0');
continue;
}
// Otherwise must be comma leading to next field
enforce(c == this.sep, "Bad digits in CSV numeric field");
// Here's your assembled value
return res;
}
}
// Get string CSV column value
string get_str() {
auto res = "";
while (true) {
// End of line
if (this.at_eol()) {
return res;
}
auto c = this.nextc();
// Field separator
if (c == this.sep) {
return res;
}
res ~= c;
}
}
// If needed, go get and decode the next line
void refill() {
// We have a current element
if (this.have_elem) {
return;
}
// All out
if (this.at_eof) {
return;
}
// Otherwise we need to go get the next one
this.curline = this.f.readln();
if (this.curline.length == 0) {
// At EOF now
this.at_eof = true;
return;
}
auto t = T();
foreach (i, ref val; t.tupleof) {
static if (is(typeof(val) == int)) {
val = this.get_int();
} else {
val = this.get_str();
}
}
// Trailing fields?
enforce(this.at_eol(), "Trailing fields");
// New current element
this.elem = t;
this.have_elem = true;
}
public:
// Is input stream empty?
bool empty() {
// We have the next element available
if (this.have_elem) {
return false;
}
// Get the next one?
this.refill();
return (!this.have_elem);
}
// Drop current
void popFront() {
this.have_elem = false;
}
// Current element
T front() {
// They must check empty() first
assert(this.have_elem);
return this.elem;
}
}
unittest {
struct Test {
int a;
string b;
}
import tiny.strfile : StrFile;
auto f = StrFile("123,b\n", "r");
auto c = new CSVreader!(Test,StrFile)(f);
assert(!c.empty());
auto t1 = c.front();
assert(t1.a == 123);
assert(t1.b == "b");
c.popFront();
assert(c.empty());
}
version(unittest) {
void
main()
{
}
}