diff --git a/Makefile.am b/Makefile.am
--- a/Makefile.am
+++ b/Makefile.am
@@ -30,6 +30,7 @@ libdistrencommon_la_SOURCES = src/common
src/common/multiio.c src/common/multiio.h \
src/common/options.c src/common/options.h \
src/common/protocol.c src/common/protocol.h \
+ src/common/csv.c src/common/csv.h \
src/common/remoteio.h \
src/common/remoteio.c src/common/libremoteio.h \
src/common/request.c src/common/request.h
@@ -85,13 +86,14 @@ EXTRA_DIST = etc/distrendaemon.conf.in \
# tests
-TESTS=test/check_execio test/check_asprintf
+TESTS=test/check_execio test/check_asprintf test/check_csv
check_PROGRAMS=$(TESTS)
#check_execio_LIBS = $(CHECK_LIBS)
#check_asprintf_LIBS = $(CHECK_LIBS)
+test_check_csv_LDADD = $(CHECK_LIBS) libdistrencommon.la
test_check_execio_LDADD = $(CHECK_LIBS) libdistrencommon.la
test_check_asprintf_LDADD = $(CHECK_LIBS) libdistrencommon.la
+test_check_csv_CFLAGS = $(AM_CFLAGS) $(CHECK_CFLAGS)
test_check_execio_CFLAGS = $(AM_CFLAGS) $(CHECK_CFLAGS)
test_check_asprintf_CFLAGS = $(AM_CFLAGS) $(CHECK_CFLAGS)
-
diff --git a/src/common/csv.c b/src/common/csv.c
new file mode 100644
--- /dev/null
+++ b/src/common/csv.c
@@ -0,0 +1,123 @@
+/*
+ * Copyright 2010 Nathan Phillip Brink
+ *
+ * This file is a part of DistRen.
+ *
+ * DistRen is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DistRen is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with DistRen. If not, see .
+ */
+
+#include "common/config.h"
+
+#include "common/csv.h"
+
+#include
+#include
+#include
+#include
+
+/**
+ * \todo
+ * replace with a more generic function for rendering a CSV file
+ */
+static int csv_print_row(size_t *columns, char *row[])
+{
+ size_t c;
+
+ for (c = 0; c < *columns; c ++)
+ {
+ printf("%s", row[c]);
+ if (c < *columns - 1)
+ printf(", ");
+ }
+ printf("\n");
+
+ return TRUE;
+}
+
+csv_row_t csv_row_init(size_t columns)
+{
+ csv_row_t row;
+
+ row = malloc(sizeof(struct csv_row));
+ if (!row)
+ return NULL;
+
+ row->cols = malloc(sizeof(char *) * columns);
+ if (!row->cols)
+ {
+ free(row);
+ return NULL;
+ }
+ row->num_cols = columns;
+
+ return row;
+}
+
+void csv_row_free(csv_row_t row)
+{
+ size_t c;
+
+ for (c = 0; c < row->num_cols; c ++)
+ free(row->cols[c]);
+ free(row);
+}
+
+list_t csv_parse(const char *inbuf, size_t columns)
+{
+ list_t rows;
+ size_t seek;
+ const char newline = '\n';
+ csv_row_t row;
+
+ size_t cur_col;
+
+ rows = list_init();
+ while(*inbuf)
+ {
+ row = csv_row_init(columns);
+ if (!row)
+ {
+ list_free(rows, (list_dealloc_func_t)&csv_row_free);
+ return NULL;
+ }
+
+ for(cur_col = 0; cur_col < columns; cur_col ++)
+ {
+ for(seek = 0; inbuf[seek]; seek ++)
+ if (inbuf[seek] == newline
+ || inbuf[seek] == ',')
+ break;
+ row->cols[cur_col] = malloc(seek + 1);
+ if (seek)
+ memcpy(row->cols[cur_col], inbuf, seek);
+ row->cols[cur_col][seek] = '\0';
+
+ /* get us to the ',' (or NULL or newline char, if things are bad) */
+ inbuf += seek;
+ /* skip a comma, but not a NULL char or newline */
+ if (*inbuf == ',')
+ inbuf ++;
+ }
+
+ list_insert_after(rows, row, 0);
+
+ /* skip the newline char */
+ while (*inbuf && *inbuf != newline)
+ inbuf ++;
+ if (*inbuf == newline)
+ inbuf ++;
+ }
+
+ return rows;
+}
diff --git a/src/common/csv.h b/src/common/csv.h
new file mode 100644
--- /dev/null
+++ b/src/common/csv.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2010 Nathan Phillip Brink
+ *
+ * This file is a part of DistRen.
+ *
+ * DistRen is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DistRen is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with DistRen. If not, see .
+ */
+
+#ifndef _DISTREN_CSV_H
+#define _DISTREN_CSV_H
+
+#include
+#include
+
+/**
+ * \brief
+ * A single row in a CSV.
+ */
+struct csv_row
+{
+ /**
+ * \brief
+ * An array of NULL-terminated strings representing each column of
+ * a row in a CSV
+ *
+ * There are num_cols indexes to this array.
+ */
+ char **cols;
+ size_t num_cols;
+};
+/**
+ * \brief
+ * A row handle.
+ */
+typedef struct csv_row *csv_row_t;
+
+/**
+ * \brief
+ * Initialize a row handle.
+ * \param columns
+ * The number of columns this row handle shall have.
+ */
+csv_row_t csv_row_init(size_t columns);
+
+/**
+ * \brief
+ * Parses a CSV file with a predetermined number of columns.
+ *
+ * If a line of the CSV file contains fewer columns than passed in by
+ * the columns parameter, then the missing columns are just set to
+ * instances of the empty string. If a line of the CSV file contains
+ * more columns than specified by the columns parameter, then the
+ * extra columns of the input are silently ignored.
+ *
+ * \param inbuf
+ * A NULL-terminated string to parse as CSV.
+ * \param columns
+ * The number of columns the result should have.
+ *
+ * \return
+ * A list of csv_row_t handles.
+ */
+list_t csv_parse(const char *inbuf, size_t columns);
+
+/**
+ * \brief
+ * Frees a row allocated by csv_parse() or csv_row()
+ *
+ * You may use this in conjunction with list_free() to free an entire
+ * CSV. Use list_free(csv_row_list,
+ * (list_dealloc_func_t)&csv_row_free);
+ *
+ * \param row
+ * The row to free.
+ */
+void csv_row_free(csv_row_t row);
+
+#endif /* _DISTREN_CSV_H */
diff --git a/test/check_csv.c b/test/check_csv.c
new file mode 100644
--- /dev/null
+++ b/test/check_csv.c
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2010 Nathan Phillip Brink
+ *
+ * This file is a part of DistRen.
+ *
+ * DistRen is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * DistRen is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with DistRen. If not, see .
+ */
+
+#include "common/csv.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+struct check_correct_vals
+{
+ size_t row;
+ size_t num_cols;
+ char ***correct_vals;
+};
+
+static void **array_init(size_t num_elems, ...)
+{
+ va_list args;
+ void **array;
+ size_t c;
+
+ array = malloc(sizeof(void *) * (num_elems + 1));
+ if (!array)
+ return NULL;
+
+ va_start(args, num_elems);
+
+ for (c = 0; c < num_elems; c ++)
+ array[c] = va_arg(args, void *);
+ array[c] = NULL;
+
+ va_end(args);
+
+ return array;
+}
+
+static int check_correct_vals(struct check_correct_vals *state, csv_row_t row)
+{
+ size_t c;
+
+ fail_unless (state->num_cols == row->num_cols,
+ "We asked for %d columns but got %d on row %d",
+ state->num_cols, row->num_cols, state->row);
+
+ for (c = 0; c < row->num_cols; c ++)
+ {
+ fail_if(strcmp(state->correct_vals[state->row][c],
+ row->cols[c]),
+ "%d:%d: Expecting %s: got %s",
+ state->row, c,
+ state->correct_vals[state->row][c],
+ row->cols[c]
+ );
+ }
+
+ state->row ++;
+ if (state->correct_vals[state->row])
+ /* there is yet another row to process */
+ return TRUE;
+ return FALSE;
+}
+
+START_TEST(check_csv)
+{
+ list_t rows;
+ struct check_correct_vals vals;
+
+ char ***correct_vals;
+
+ correct_vals =
+ (char ***)array_init(4,
+ array_init(3, "23", "1" , "0.0"),
+ array_init(3, "a" , " b", "" ),
+ array_init(3, "f" , "df", "" ),
+ array_init(3, "d" , "f" , "ss" ));
+ fail_if(correct_vals == NULL, "array_init() returned NULL");
+
+ vals.num_cols = 3;
+ vals.correct_vals = correct_vals;
+ vals.row = 0;
+
+ rows = csv_parse("23,1,0.0\n\
+a, b,,d\n\
+f,df\n\
+d,f,ss,e\n", vals.num_cols);
+ fail_if(rows == NULL, "csv_parse() returned NULL");
+ list_traverse(rows, &vals, (list_traverse_func_t)&check_correct_vals, LIST_FRNT|LIST_SAVE|LIST_FORW);
+
+ list_free(rows, (list_dealloc_func_t)&csv_row_free);
+}
+END_TEST
+
+static Suite *csv_suite()
+{
+ Suite *s;
+ TCase *tc;
+
+ s = suite_create("csv");
+
+ tc = tcase_create("core");
+ tcase_add_test(tc, check_csv);
+ suite_add_tcase(s, tc);
+
+ return s;
+}
+
+int main(int argc, char *argv[])
+{
+ int number_failed;
+ Suite *s;
+ SRunner *sr;
+
+ s = csv_suite();
+ sr = srunner_create(s);
+
+ srunner_run_all(sr, CK_NORMAL);
+ number_failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ if (number_failed)
+ return EXIT_FAILURE;
+ return EXIT_SUCCESS;
+}