# HG changeset patch # User Nathan Phillip Brink # Date 2010-09-24 11:07:58 # Node ID 2d956d54943007a2219b1674458734950b5b95cd # Parent 393ab8fcf2eec544294a5843f7ba1dc6ac6fa69f Add simple CSV parser for normaldotcom. 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; +}