Changeset - 2d956d549430
[Not reviewed]
default
0 1 3
Nathan Brink (binki) - 15 years ago 2010-09-24 11:07:58
ohnobinki@ohnopublishing.net
Add simple CSV parser for normaldotcom.
4 files changed with 359 insertions and 2 deletions:
0 comments (0 inline, 0 general)
Makefile.am
Show inline comments
 
@@ -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)
 

	
src/common/csv.c
Show inline comments
 
new file 100644
 
/*
 
 * 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 <http://www.gnu.org/licenses/>.
 
 */
 

	
 
#include "common/config.h"
 

	
 
#include "common/csv.h"
 

	
 
#include <list.h>
 
#include <stdio.h>
 
#include <stdlib.h>
 
#include <string.h>
 

	
 
/**
 
 * \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;
 
}
src/common/csv.h
Show inline comments
 
new file 100644
 
/*
 
 * 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 <http://www.gnu.org/licenses/>.
 
 */
 

	
 
#ifndef _DISTREN_CSV_H
 
#define _DISTREN_CSV_H
 

	
 
#include <list.h>
 
#include <stdlib.h>
 

	
 
/**
 
 * \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 */
test/check_csv.c
Show inline comments
 
new file 100644
 
/*
 
 * 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 <http://www.gnu.org/licenses/>.
 
 */
 

	
 
#include "common/csv.h"
 

	
 
#include <check.h>
 
#include <list.h>
 
#include <stdarg.h>
 
#include <stdio.h>
 
#include <stdlib.h>
 
#include <string.h>
 

	
 
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;
 
}
0 comments (0 inline, 0 general)