commit e1d1e9f72396a95015d682d0349c974fc1c3cf27 Author: Pradana Aumars Date: Sun Feb 11 18:14:34 2018 +0100 Initial commit diff --git a/src/easycsv.c b/src/easycsv.c new file mode 100644 index 0000000..ab55f70 --- /dev/null +++ b/src/easycsv.c @@ -0,0 +1,824 @@ +#include "easycsv.h" + +/* Flags */ +typedef enum { + EASYCSV_NONE, + EASYCSV_STRING, + EASYCSV_INT, + EASYCSV_UNKNOWN +} _EASYCSV_TYPE; + +typedef enum { + + /* Generic errors */ + EASYCSV_NOERROR = 0, // no error + EASYCSV_NULLCSV, // easycsv* is NULL + EASYCSV_NULLPTR, // generic pointer is NULL + EASYCSV_EMPTYSTRING, // empty string + EASYCSV_OVERMAXROW, // int exceeds row limit + EASYCSV_OVERMAXCOL, // int exceeds col limit + EASYCSV_ZEROROW, + EASYCSV_ZEROCOL, + + /* File input/output errors */ + EASYCSV_UNKNOWNMODE, // unknown file IO mode + EASYCSV_OPENFAIL, // fail to open + EASYCSV_REOPENFAIL, // fail to reopen + EASYCSV_EMPTYCSV, // csv is empty or does not exist + EASYCSV_UNWRITABLE, // csv mode not at EASYCSV_W + EASYCSV_UNREADABLE, // csv mode not at EASYCSV_R + EASYCSV_UPDATEFAIL, // update file fail + EASYCSV_FILEPTRFAIL, // move file pointer fail + + /* Non-existant elements */ + EASYCSV_ROWNOTEXIST, // row does not exist + EASYCSV_COLNOTEXIST, // column does not exist + + /* Subroutine failure */ + EASYCSV_PUSHCOLFAIL, // push column value fail + EASYCSV_COLNUMFAIL // column number retrieval fail +} EASYCSV_ERROR; + +typedef struct _easycsv { + FILE *file; // original CSV file + FILE *temp; // temporary CSV file for writing + char *fp; + char *tmpfp; + unsigned int rows; + unsigned int cols; + EASYCSV_ERRORMSG error; +#ifdef EASYCSV_DEBUG + clock_t start; +#endif +} _easycsv; + +/* PRIVATE FUNCTIONS */ + +/* Constructor of private members */ +static _easycsv *_easycsv_priv_init(const char*, const EASYCSV_MODE, const EASYCSV_ERRORMSG); + +/* Destructor of private members */ +static void _easycsv_priv_free(_easycsv*); + +/* Verifies mode of file */ +// static int _easycsv_checkmode(struct easycsv*, const char); + +/* Copies data from temp FILE to file FILE */ +static int _easycsv_update(easycsv*); + +/* Rewind easycsv, checks for readability */ +static int _easycsv_rewind(_easycsv*, const EASYCSV_MODE); + +/* Returns string of a specific row */ +static char *_easycsv_getrow(const easycsv*, const unsigned int); + +/* Return column number of a named column */ +static int _easycsv_getcolumn(const easycsv*, const char*); + +/* Verifies the type of the value (eg: string or int) */ +static int _easycsv_checktype(const easycsv*, const int, const int); + +/* Verifies if there is a value or not */ +static int _easycsv_checkifvalue(const easycsv*, const int, const int); + +/* */ +// static char *_easycsv_getvalueinrow(const int, const char*); + +/* Calculate rows */ +static int _easycsv_rows(_easycsv*, const EASYCSV_MODE); + +/* Calculate columns*/ +static int _easycsv_columns(_easycsv*, const EASYCSV_MODE); + +static void _easycsv_printerror(const _easycsv*, const EASYCSV_ERROR); + +/* Print error from _easycsv struct in stderr */ +static void _easycsv_geterror(const _easycsv*, const EASYCSV_ERROR, FILE*); + +/* Check if easycsv* and const char* are NULL */ +static int _easycsv_checkcsvandstring_one(const easycsv*, const char*); + +/* Check if easycsv* and two const char* are NULL*/ +static int _easycsv_checkcsvandstring_two(const easycsv*, const char*, const char*); + +/* Verifies if the string is not NULL or empty, returns 0 on success and -1 on failure */ +// static int _easycsv_checkstring(const char*); + +/* Verifies if easycsv is not NULL or unallocated */ +// static int _easycsv_checkeasycsv(const struct easycsv*); + +/* Verifies if int is */ +// static int _easycsv_checkunsigned(const int); + +/* + * ==================== + * FUNCTION DEFINITIONS + * ==================== + */ + +/* PUBLIC FUNCTIONS */ + +easycsv* +easycsv_init(const char *fp, const EASYCSV_MODE mode) +{ + return easycsv_init_errormsg(fp, mode, EASYCSV_ERRORSTDERR); +} + +easycsv* +easycsv_init_errormsg(const char *fp, const EASYCSV_MODE mode, const EASYCSV_ERRORMSG error) +{ +#ifdef EASYCSV_DEBUG + csv->start = clock(); +#endif + + easycsv *csv = malloc(sizeof *csv); + csv->mode = mode; + csv->_priv = _easycsv_priv_init(fp, mode, error); + + if (csv->_priv == NULL) { + easycsv_free(csv); + return NULL; + } + + return csv; +} + +void +easycsv_free(easycsv *csv) +{ + if (csv == NULL) return; + _easycsv_priv_free(csv->_priv); + free(csv); +} + +_easycsv* +_easycsv_priv_init(const char *fp, const EASYCSV_MODE mode, const EASYCSV_ERRORMSG error) +{ + _easycsv *_priv = malloc(sizeof *_priv); + memset(_priv, 0, sizeof(*_priv)); // set all members to NULL + + _priv->error = error; + + /* Open file according to mode */ + switch (mode) { + case EASYCSV_R: + case EASYCSV_W: { + _priv->file = fopen(fp, "r"); + break; + } + default: { + _easycsv_printerror(_priv, EASYCSV_UNKNOWNMODE); + _easycsv_priv_free(_priv); + return NULL; + } + + if (_priv->file == NULL) + { + _easycsv_printerror(_priv, EASYCSV_OPENFAIL); + _easycsv_priv_free(_priv); + return NULL; + } + + size_t stfp = strlen(fp); + + /* Allocate memory for char* */ + _priv->fp = malloc(stfp + 1); // + 1 for null + + strncpy(_priv->fp, fp, stfp); + + /* Calculate rows and cols if file exists */ + if (access(_priv->fp, F_OK) == 0) { + _priv->rows = _easycsv_rows(_priv, mode); + _priv->cols = _easycsv_columns(_priv, mode); + } + else { + _priv->rows = 0; + _priv->cols = 0; + } + + if (mode == EASYCSV_W) { + + /* csv->tmpfp = malloc(16 + stfp + 1); */ + /* strncpy(csv->tmpfp, prefix, 16); */ + /* strncat(csv->tmpfp, fp, stfp); */ + + do { + /* Write to temporary file */ + unsigned int i = 1; + char buffer[21] = "/tmp/easycsv-"; // 13 char, 3 for digits, 4 for .csv, 1 for NULL + char *pbuffer = &buffer[13]; // don't free, is used for digit con + + if (i < 100) { + strncpy(pbuffer, "0", 1); + pbuffer++; + } + + if (i < 10) { + strncpy(pbuffer, "0", 1); + pbuffer++; + } + + sprintf(pbuffer, "%i", i); + strncpy(++pbuffer, ".csv", 4); + buffer[20] = '\0'; + + if (access(buffer, F_OK) < 0) { + i++; + } + else { + _priv->tmpfp = malloc(21); + strncpy(_priv->tmpfp, buffer, 21); + break; + } + + } while (1); + + _priv->temp = fopen(_priv->tmpfp, "w"); + if (_priv->temp == NULL) { + _easycsv_printerror(_priv, EASYCSV_OPENFAIL); + _easycsv_priv_free(_priv); + return NULL; + } + /* +#ifdef EASYCSV_DEBUG + printf("[%i] easycsv_debug: temp file %s opened\n", clock() - csv->start, csv->tmpfp); + #endif*/ + /* + + if (freopen(csv->fp, csv->mode, csv->file) == NULL) { + fprintf(stderr, "easycsv: failed to set temporary file %s to %s mode\n", csv->tmpfp, csv->mode); + return NULL; + } + + if (freopen(csv->tmpfp, csv->mode, csv->temp) == NULL) { + fprintf(stderr, "easycsv: failed to set temporary file %s to %s mode\n", csv->tmpfp, csv->mode); + return NULL; + } + */ + + } + + return _priv; +} + +void +_easycsv_priv_free(_easycsv *_priv) +{ + if (_priv == NULL) return; + if (_priv->file != NULL) fclose(_priv->file); + if (_priv->temp != NULL) fclose(_priv->temp); + if (_priv->fp != NULL) free(_priv->fp); + if (_priv->tmpfp != NULL) free(_priv->tmpfp); + free(_priv); +} + + +int +easycsv_pushcolumn(easycsv *csv, const char *col) +{ + if (_easycsv_checkcsvandstring_one(csv, col) < 0) return -1; + + if (csv->mode == EASYCSV_R) { + _easycsv_printerror(csv->_priv, EASYCSV_UNWRITABLE); + return -1; + } + + if ((csv->mode == EASYCSV_W) && (access(csv->_priv->fp, F_OK) == 0)) { + /* If the file doesn't exist, just put the col in the file */ + + if (fputs(col, csv->_priv->file) < 0) { + _easycsv_printerror(csv->_priv, EASYCSV_PUSHCOLFAIL); + return -1; + } + + return 0; + } + + char *str = _easycsv_getrow(csv, 1); + size_t ststr = strlen(str); + size_t stcol = strlen(col); + realloc(str, ststr + stcol + 2); // 1 for null and 1 for comma + + strncat(str + ststr, ",", 1); + strncat(str + ststr + 1, col, stcol); + /* +#ifdef EASYCSV_DEBUG + printf("[%i] easycsv_debug: push column str: %s", clock() - csv->_priv->start, str); +#endif + */ + /* Put str in temp file */ + if (fputs(str, csv->_priv->temp) < 0) { + _easycsv_printerror(csv->_priv, EASYCSV_PUSHCOLFAIL); + return -1; + } + + free(str); + + /* Copy every row following the first into temp */ + for (int i = 2; i <= csv->_priv->rows; i++) { + char *row = _easycsv_getrow(csv, i); + fputs(row, csv->_priv->temp); + free(row); + } + + /* Update the file */ + if (_easycsv_update(csv) < 0) { + _easycsv_printerror(csv->_priv, EASYCSV_UPDATEFAIL); + return -1; + } + + return 0; +} + +int +easycsv_pushcolumnvalue(const easycsv *csv, const char *col, const char *val) +{ + if (_easycsv_checkcsvandstring_two(csv, col, val) < 0) return -1; + + int colnum = _easycsv_getcolumn(csv, col); + + if (colnum < 0) { + _easycsv_printerror(csv->_priv, EASYCSV_COLNUMFAIL); + return -1; + } + + /* Find a free cell under col within csv->_priv->cols limits, if there is none, generate a new + row */ + unsigned int row; + + for (row = 2; row <= csv->_priv->cols; row++) { + if (easycsv_readvalue(csv, colnum, row) != NULL) break; + } + + /* All rows are filled, generate new row */ + if (row == csv->_priv->cols) { + row++; + + /* Set file pointer to end */ + if (fseek(csv->_priv->file, 0L, SEEK_END) < 0) { + _easycsv_printerror(csv->_priv, EASYCSV_FILEPTRFAIL); + return -1; + } + + char *rowstr = malloc(csv->_priv->cols + strlen(val) + 2); // 1 extra for '\n' + + strncpy(rowstr, "\n", 1); + + for (unsigned int i = 1; i < colnum; i++) { + strncpy(rowstr, ",", 1); + } + + strncpy(rowstr, val, strlen(val)); + + unsigned int colsleft = csv->_priv->cols - colnum; + + for (unsigned i = 1; i < colsleft; i++) { + strncpy(rowstr, ",", 1); + } + + strncpy(rowstr, "/0", 1); + + csv->_priv->rows++; + } + + /* Fill in rows before */ + + for (unsigned int i = 1; i < row; i++) { + char *str = _easycsv_getrow(csv, i); + if (fputs(str, csv->_priv->temp) == EOF) { + fprintf(stderr, "easycsv: failed to write \"%s\" in temporary file %s for %s\n", str, csv->_priv->tmpfp, csv->_priv->fp); + return -1; + } + + free(str); + } + + return 0; +} + +int +easycsv_insertcolumnvalue(const easycsv *csv, const char *col, const unsigned int row, const char *val, const EASYCSV_VALMODE valmode) +{ + if (_easycsv_checkcsvandstring_two(csv, col, val) < 0) return -1; + + int colnum = _easycsv_getcolumn(csv, col); + if (colnum < 0) { + _easycsv_printerror(csv->_priv, EASYCSV_COLNUMFAIL); + return -1; + } + + char *rowstr = _easycsv_getrow(csv, row); + char *pch = rowstr; + + for (unsigned int i = 1; i < colnum; i++) { + pch = strchr(pch, ','); + pch++; + } + + size_t st = strcspn(pch, ','); + + /* Empty cell */ + if (st == 0) { + + } + + return 0; +} + +int +easycsv_printrows(const easycsv *csv) +{ + return csv->_priv->rows; +} + +int +easycsv_printcolumns(const easycsv *csv) +{ + return csv->_priv->cols; +} + +int +_easycsv_rows(_easycsv *_priv, const EASYCSV_MODE mode) +{ + // no need to check _priv for NULL + + if (_easycsv_rewind(_priv, mode) < 0) return -1; + + int rows = 1; + char c; + + /* Go through each character in the file and count the number of \n + in it */ + while ((c = fgetc(_priv->file)) != EOF) { + if (c == '\n') rows++; + } + + return rows; +} + +int +_easycsv_columns(_easycsv *_priv, const EASYCSV_MODE mode) +{ + // no need to check _priv for NULL + + /* Prepare it for reading */ + if (_easycsv_rewind(_priv, mode) < 0) return -1; + + /* + 1. check if empty file + 2. check if only one column -> 0 commas + 3. if >1 column, n commas = n+1 columns + */ + + unsigned int col = 1; + char c; + + while ((c = fgetc(_priv->file)) != '\n') { + if (c == ',') col++; + } + + return col; +} + +const char* +easycsv_readcolumnvalue(const easycsv *csv, const char *col, const unsigned int row) +{ + if (_easycsv_checkcsvandstring_one(csv, col) < 0) return NULL; + + if (row > csv->_priv->rows) { + _easycsv_printerror(csv->_priv, EASYCSV_OVERMAXROW); + return NULL; + } + + int i = _easycsv_getcolumn(csv, col); + if (i < 1) { + _easycsv_printerror(csv->_priv, EASYCSV_COLNUMFAIL); + return NULL; + } + + return easycsv_readvalue(csv, i, row); +} + +const char* +easycsv_readvalue(const easycsv *csv, const unsigned int col, const unsigned int row) +{ + if (row == 0) { + _easycsv_printerror(csv->_priv, EASYCSV_ZEROROW); + return NULL; + } + + if (col == 0) { + _easycsv_printerror(csv->_priv, EASYCSV_ZEROCOL); + return NULL; + } + + char *str = _easycsv_getrow(csv, row); + if (str == NULL) { + _easycsv_printerror(csv->_priv, EASYCSV_ROWNOTEXIST); + return NULL; + } + /* +#ifdef EASYCSV_DEBUG + printf("[%i] rv_col: %i\nstr: %s\n", clock() - csv->_priv->start, col, str); +#endif + */ + /* Return if first col */ + if (col == 1) { + size_t st = strcspn(str, ","); + if (st == 0) { + free(str); + return NULL; + } + char *pch = malloc(st); + strncpy(pch, str, st); + free(str); + return pch; + } + + /* Get first occurance of comma in str, + the first value is ommited but not the comma */ + char *pch = strpbrk(str, ","); + + /* Repeat until desired col is found */ + for (int i = 2; i < col; i++) { + pch = strpbrk(pch + 1, ","); + } + + /* Get span from start of string to first occurence of comma */ + size_t st = strcspn(pch + 1, ","); + + /* If 0, no string exists! */ + if (st == 0) { + free(str); + return NULL; + } + + char *val = malloc(st + 1); + strncpy(val, pch + 1, st); + strncat(val, "\0", 1); + free(str); + + return val; +} + +/* PRIVATE FUNCTIONS */ + +/* +static int _easycsv_checkmode(struct easycsv *csv, const char mode) +{ + if (strchr(csv->mode, mode) == NULL) + { + return -1; + } +} +*/ + +int +_easycsv_update(easycsv *csv) +{ + if (csv == NULL) { + _easycsv_printerror(csv->_priv, EASYCSV_NULLCSV); + return -1; + } + + /* Set temp file to read binary */ + if (freopen(csv->_priv->tmpfp, "rb", csv->_priv->temp) == NULL) { + _easycsv_printerror(csv->_priv, EASYCSV_REOPENFAIL); + easycsv_free(csv); + return -1; + } + + /* Set file to write binary */ + if (freopen(csv->_priv->fp, "wb", csv->_priv->file) == NULL) { + _easycsv_printerror(csv->_priv, EASYCSV_REOPENFAIL); + easycsv_free(csv); + return -1; + } + + char buf[BUFSIZ]; + size_t size; + while (size = fread(buf, 1, BUFSIZ, csv->_priv->temp)) { + fwrite(buf, 1, size, csv->_priv->file); + } + + /* Set temp file back to write */ + if (freopen(csv->_priv->tmpfp, "w", csv->_priv->temp) == NULL) { + _easycsv_printerror(csv->_priv, EASYCSV_REOPENFAIL); + easycsv_free(csv); + return -1; + } + + /* Set file back to read */ + if (freopen(csv->_priv->fp, "r", csv->_priv->file) == NULL) { + _easycsv_printerror(csv->_priv, EASYCSV_REOPENFAIL); + easycsv_free(csv); + return -1; + } + + return 0; +} + +char* +_easycsv_getrow(const easycsv *csv, const unsigned int row) +{ + if (csv == NULL) { + _easycsv_printerror(csv->_priv, EASYCSV_NULLCSV); + return NULL; + } + + if (row > csv->_priv->rows) { + _easycsv_printerror(csv->_priv, EASYCSV_OVERMAXROW); + return NULL; + } + + if (row == 0) { + _easycsv_printerror(csv->_priv, EASYCSV_ZEROROW); + return NULL; + } + + /* Allocate memory */ + char *str = malloc(BUFSIZ); + + /* Set file pointer to start */ + rewind(csv->_priv->file); + + for (int i = 1; i < row; i++) + { + /* skip until row is reached */ + fscanf(csv->_priv->file, "%*[^\n]\n", NULL); + } + + /* Grab the row and store it in str */ + fscanf(csv->_priv->file, "%s\n", str); + + // printf("row: %s\n", str); + + return str; +} + +int +_easycsv_rewind(_easycsv *_priv, const EASYCSV_MODE mode) +{ + /* Check if empty file */ + if (fscanf(_priv->file, "\n") == EOF) { + _easycsv_printerror(_priv, EASYCSV_EMPTYCSV); + return -1; + } + + /* Check if file is readable */ + if (mode != EASYCSV_R) { + _easycsv_printerror(_priv, EASYCSV_UNREADABLE); + return -1; + } + + /* Set file pointer to the start */ + rewind(_priv->file); + + return 0; +} + +int +_easycsv_getcolumn(const easycsv *csv, const char *col) +{ + if (_easycsv_checkcsvandstring_one(csv, col) < 0) return -1; + + /* Grab str of row 1 */ + char *firstrow = _easycsv_getrow(csv, 1); + + if (firstrow == NULL) { + _easycsv_printerror(csv->_priv, EASYCSV_ROWNOTEXIST); + return -1; + } + + unsigned int commas = 0; + + // printf("FIRST COLUMN: %s\n", firstrow); + + /* Find first occurance of col in firstrow */ + char *str = strstr(firstrow, col); + + if (str == NULL) { + _easycsv_printerror(csv->_priv, EASYCSV_COLNOTEXIST); + return -1; + } + + /* Count numbers of commas following str */ + char *c = strpbrk(str, ","); + while (c != NULL) { + commas++; + c = strpbrk(c + 1, ","); + } + + /* no need to free c as it is already NULL at this point */ + // free((char*) str); apparently invalid pointer + + // printf("ROW: %i\nCOL: %i\n", row, csv->_priv->cols - commas); + /* +#ifdef EASYCSV_DEBUG + printf("[%i] rcv_commas: %i\n", clock() - csv->_priv->start, commas); +#endif + */ + free(firstrow); + + return csv->_priv->cols - commas; +} + +/* +int _easycsv_checkifvalue(struct easycsv *csv, const int col, const int row) +{ + const char *rowstr = _easycsv_getrow(csv, row); + + return 0; +} + +char *_easycsv_getvalueinrow(const int col, const char *row) +{ + char *pbrk = strchr(row, ','); + for (unsigned int i = 1; i < col; i++) { + + } +} +*/ + +void +_easycsv_printerror(const _easycsv *_priv, const EASYCSV_ERROR error) +{ + switch (_priv->error) { + case EASYCSV_ERROROFF: return; + case EASYCSV_ERRORSTDOUT: _easycsv_geterror(_priv, error, stdout); break; + case EASYCSV_ERRORSTDERR: + default: _easycsv_geterror(_priv, error, stderr); return; + } +} + +void +_easycsv_geterror(const _easycsv *_priv, const EASYCSV_ERROR error, FILE *fs) +{ +#ifdef EASYCSV_DEBUG + fprintf(stderr, "[%i] ", clock() - _priv->start); +#endif + + fputs("easycsv: ", fs); + + switch (error) { + case EASYCSV_NOERROR: fputs("no error", fs); break; + case EASYCSV_NULLCSV: fputs("easycsv pointer is NULL", fs); break; + case EASYCSV_NULLPTR: fputs("pointer is NULL", fs); break; + case EASYCSV_EMPTYSTRING: fputs("string is empty", fs); break; + case EASYCSV_OVERMAXROW: fprintf(fs, "int exceeds row limit %i", _priv->rows); break; + case EASYCSV_OVERMAXCOL: fprintf(fs, "int exceeds column limit %i", _priv->cols); break; + case EASYCSV_ZEROROW: fputs("parameterised row number is zero", fs); break; + case EASYCSV_ZEROCOL: fputs("parameterised column number is zero", fs); break; + + case EASYCSV_UNKNOWNMODE: fputs("unknown file IO mode", fs); break; + case EASYCSV_OPENFAIL: fputs("failed to open file", fs); break; + case EASYCSV_REOPENFAIL: fputs("failed to reopen file", fs); break; + case EASYCSV_EMPTYCSV: fputs("CSV file is empty", fs); break; + case EASYCSV_UNWRITABLE: fputs("CSV file is not in a writable mode", fs); break; + case EASYCSV_UNREADABLE: fputs("CSV file is not in a readable mode", fs); break; + case EASYCSV_UPDATEFAIL: fputs("CSV file has failed to update", fs); break; + case EASYCSV_FILEPTRFAIL: fputs("failed to move FILE pointer", fs); break; + + case EASYCSV_ROWNOTEXIST: fputs("given row does not exist", fs); break; + case EASYCSV_COLNOTEXIST: fputs("given column does not exist", fs); break; + + case EASYCSV_PUSHCOLFAIL: fputs("failed to push value under column", fs); break; + case EASYCSV_COLNUMFAIL: fputs("failed to determine the column number of a value in the first row", fs); break; + default: fputs("unknown error, easycsv* is possibly NULL", fs); break; + } + + if (_priv->fp != NULL) { + fprintf(fs, " in %s", _priv->fp); + } + + fputc('\n', fs); +} + +int +_easycsv_checkcsvandstring_one(const easycsv *csv, const char *one) +{ + if (csv == NULL) { + _easycsv_printerror(csv->_priv, EASYCSV_NULLCSV); + return -1; + } + + if ((one == NULL) || (one[0] == '\0')) { + _easycsv_printerror(csv->_priv, EASYCSV_EMPTYSTRING); + return -1; + } + + return 0; +} + +int + _easycsv_checkcsvandstringtwo(const easycsv *csv, const char *one, const char *two) +{ + if (_easycsv_checkcsvandstring_one(csv, one) < 0) return -1; + + if ((two == NULL) || (two[0] == '\0')) { + _easycsv_printerror(csv->_priv, EASYCSV_EMPTYSTRING); + return -1; + } + + return 0; +} diff --git a/src/easycsv.c~ b/src/easycsv.c~ new file mode 100644 index 0000000..774e08f --- /dev/null +++ b/src/easycsv.c~ @@ -0,0 +1,878 @@ +#include "easycsv.h" + +/* Flags */ +typedef enum { + EASYCSV_NULL, + EASYCSV_STRING, + EASYCSV_INT +} _EASYCSV_TYPE; + +typedef enum { + + /* Generic errors */ + EASYCSV_NOERROR = 0, // no error + EASYCSV_NULLPTR, // pointer is NULL + EASYCSV_EMPTYSTRING, // empty string + EASYCSV_OVERMAXROW, // int exceeds row limit + EASYCSV_OVERMAXCOL, // int exceeds col limit + + /* File input/output errors */ + EASYCSV_UNKNOWNMODE, // unknown file IO mode + EASYCSV_OPENFAIL, // fail to open + EASYCSV_REOPENFAIL, // fail to reopen + EASYCSV_EMPTYCSV, // csv is empty or does not exist + EASYCSV_UNWRITABLE, // csv mode not at EASYCSV_W + EASYCSV_UNREADABLE, // csv mode not at EASYCSV_R + EASYCSV_UPDATEFAIL, // update file fail + EASYCSV_FILEPTRFAIL, // move file pointer fail + + /* Non-existant elements */ + EASYCSV_ROWNOTEXIST, // row does not exist + EASYCSV_COLNOTEXIST, // column does not exist + + /* Subroutine failure */ + EASYCSV_PUSHCOLFAIL, // push column value fail + EASYCSV_COLNUMFAIL // column number retrieval fail +} EASYCSV_ERROR; + +typedef struct _easycsv { + FILE *file; // original CSV file + FILE *temp; // temporary CSV file for writing + char *fp; + char *tmpfp; + unsigned int rows; + unsigned int cols; + EASYCSV_ERRORMSG error; +#ifdef EASYCSV_DEBUG + clock_t start; +#endif +} _easycsv; + +/* PRIVATE FUNCTIONS */ + +/* Constructor of private members */ +static _easycsv *_easycsv_priv_init(const char*, const EASYCSV_MODE, const EASYCSV_ERRORMSG); + +/* Destructor of private members */ +static void _easycsv_priv_free(_easycsv*); + +/* Verifies mode of file */ +// static int _easycsv_checkmode(struct easycsv*, const char); + +/* Copies data from temp FILE to file FILE */ +static int _easycsv_update(easycsv*); + +/* Rewind easycsv, checks for readability */ +static int _easycsv_rewind(_easycsv*, const EASYCSV_MODE); + +/* Returns string of a specific row */ +static char *_easycsv_getrow(const easycsv*, const unsigned int); + +/* Return column number of a named column */ +static int _easycsv_getcolumn(const easycsv*, const char*); + +/* Verifies the type of the value (eg: string or int) */ +static int _easycsv_checktype(const easycsv*, const int, const int); + +/* Verifies if there is a value or not */ +static int _easycsv_checkifvalue(const easycsv*, const int, const int); + +/* */ +// static char *_easycsv_getvalueinrow(const int, const char*); + +/* Calculate rows */ +static int _easycsv_rows(_easycsv*, const EASYCSV_MODE); + +/* Calculate columns*/ +static int _easycsv_columns(_easycsv*, const EASYCSV_MODE); + +static void _easycsv_printerror(const _easycsv*, const EASYCSV_ERROR); + +/* Print error from _easycsv struct in stderr */ +static void _easycsv_geterror(const _easycsv*, const EASYCSV_ERROR, FILE*); + +/* Verifies if the string is not NULL or empty, returns 0 on success and -1 on failure */ +// static int _easycsv_checkstring(const char*); + +/* Verifies if easycsv is not NULL or unallocated */ +// static int _easycsv_checkeasycsv(const struct easycsv*); + +/* Verifies if int is */ +// static int _easycsv_checkunsigned(const int); + +/* + * ==================== + * FUNCTION DEFINITIONS + * ==================== + */ + +/* PUBLIC FUNCTIONS */ + +easycsv* +easycsv_init(const char *fp, const EASYCSV_MODE mode) +{ + return easycsv_init_errormsg(fp, mode, EASYCSV_ERRORSTDERR); +} + +easycsv* +easycsv_init_errormsg(const char *fp, const EASYCSV_MODE mode, const EASYCSV_ERRORMSG error) +{ +#ifdef EASYCSV_DEBUG + csv->start = clock(); +#endif + + easycsv *csv = malloc(sizeof *csv); + csv->mode = mode; + csv->_priv = _easycsv_priv_init(fp, mode, error); + + if (csv->_priv == NULL) { + easycsv_free(csv); + return NULL; + } + + return csv; +} + +void +easycsv_free(easycsv *csv) +{ + if (csv == NULL) return; + _easycsv_priv_free(csv->_priv); + free(csv); +} + +_easycsv* +_easycsv_priv_init(const char *fp, const EASYCSV_MODE mode, const EASYCSV_ERRORMSG error) +{ + _easycsv *_priv = malloc(sizeof *_priv); + memset(_priv, 0, sizeof(*_priv)); // set all members to NULL + + _priv->error = error; + + /* Open file according to mode */ + switch (mode) { + case EASYCSV_R: + case EASYCSV_W: { + _priv->file = fopen(fp, "r"); + break; + } + default: { + _easycsv_printerror(_priv, EASYCSV_UNKNOWNMODE); + _easycsv_priv_free(_priv); + return NULL; + } + + if (_priv->file == NULL) + { + _easycsv_printerror(_priv, EASYCSV_OPENFAIL); + _easycsv_priv_free(_priv); + return NULL; + } + + size_t stfp = strlen(fp); + + /* Allocate memory for char* */ + _priv->fp = malloc(stfp + 1); // + 1 for null + + strncpy(_priv->fp, fp, stfp); + + /* Calculate rows and cols if file exists */ + if (access(_priv->fp, F_OK) == 0) { + _priv->rows = _easycsv_rows(_priv, mode); + _priv->cols = _easycsv_columns(_priv, mode); + } + else { + _priv->rows = 0; + _priv->cols = 0; + } + + if (mode == EASYCSV_W) { + + /* csv->tmpfp = malloc(16 + stfp + 1); */ + /* strncpy(csv->tmpfp, prefix, 16); */ + /* strncat(csv->tmpfp, fp, stfp); */ + + do { + /* Write to temporary file */ + unsigned int i = 1; + char buffer[21] = "/tmp/easycsv-"; // 13 char, 3 for digits, 4 for .csv, 1 for NULL + char *pbuffer = &buffer[13]; // don't free, is used for digit con + + if (i < 100) { + strncpy(pbuffer, "0", 1); + pbuffer++; + } + + if (i < 10) { + strncpy(pbuffer, "0", 1); + pbuffer++; + } + + sprintf(pbuffer, "%i", i); + strncpy(++pbuffer, ".csv", 4); + buffer[20] = '\0'; + + if (access(buffer, F_OK) < 0) { + i++; + } + else { + _priv->tmpfp = malloc(21); + strncpy(_priv->tmpfp, buffer, 21); + break; + } + + } while (1); + + _priv->temp = fopen(_priv->tmpfp, "w"); + if (_priv->temp == NULL) { + _easycsv_printerror(_priv, ) + _easycsv_priv_free(_priv); + return NULL; + } + +#ifdef EASYCSV_DEBUG + printf("[%i] easycsv_debug: temp file %s opened\n", clock() - csv->start, csv->tmpfp); +#endif + /* + + if (freopen(csv->fp, csv->mode, csv->file) == NULL) { + fprintf(stderr, "easycsv: failed to set temporary file %s to %s mode\n", csv->tmpfp, csv->mode); + return NULL; + } + + if (freopen(csv->tmpfp, csv->mode, csv->temp) == NULL) { + fprintf(stderr, "easycsv: failed to set temporary file %s to %s mode\n", csv->tmpfp, csv->mode); + return NULL; + } + */ + + } + + return _priv; +} + +void +_easycsv_priv_free(_easycsv *_priv) +{ + if (_priv == NULL) return; + if (_priv->file != NULL) fclose(_priv->file); + if (_priv->temp != NULL) fclose(_priv->temp); + if (_priv->fp != NULL) free(_priv->fp); + if (_priv->tmpfp != NULL) free(_priv->tmpfp); + free(_priv); +} + + +int +easycsv_pushcolumn(easycsv *csv, const char *col) +{ + if (csv == NULL) { + fprintf(stderr, "easycsv: csv is NULL in %s\n", csv->_priv->fp); + return -1; + } + + if ((col == NULL) || (col[0] == '\0')) { + fprintf(stderr, "easycsv: col is either NULL or empty in %s\n", csv->_priv->fp); + return -1; + } + + if (csv->mode == EASYCSV_R) { +#ifdef EASYCSV_DEBUG + fprintf(stderr, "[%i] ", clock() - csv->_priv->start); +#endif + fprintf(stderr, "easycsv: %s set to read\n", csv->_priv->fp); + return -1; + } + + if ((csv->mode == EASYCSV_W) && (access(csv->_priv->fp, F_OK) == 0)) { + /* If the file doesn't exist, just put the col in the file */ + + if (fputs(col, csv->_priv->file) < 0) { +#ifdef EASYCSV_DEBUG + fprintf(stderr, "[%i] ", clock() - csv->_priv->start); +#endif + fprintf(stderr, "easycsv: failed to push column %s in %s\n", col, csv->_priv->fp); + return -1; + } + + return 0; + } + + char *str = _easycsv_getrow(csv, 1); + size_t ststr = strlen(str); + size_t stcol = strlen(col); + realloc(str, ststr + stcol + 2); // 1 for null and 1 for comma + + strncat(str + ststr, ",", 1); + strncat(str + ststr + 1, col, stcol); + +#ifdef EASYCSV_DEBUG + printf("[%i] easycsv_debug: push column str: %s", clock() - csv->_priv->start, str); +#endif + + /* Put str in temp file */ + if (fputs(str, csv->_priv->temp) < 0) { +#ifdef EASYCSV_DEBUG + fprintf(stderr, "[%i] ", clock() - csv->_priv->start); +#endif + fprintf(stderr, "easycsv: failed to push column %s in %s\n", col, csv->_priv->tmpfp); + return -1; + } + + free(str); + + /* Copy every row following the first into temp */ + for (int i = 2; i <= csv->_priv->rows; i++) { + char *row = _easycsv_getrow(csv, i); + fputs(row, csv->_priv->temp); + free(row); + } + + /* Update the file */ + if (_easycsv_update(csv) < 0) { +#ifdef EASYCSV_DEBUG + fprintf(stderr, "[%i] ", clock() - csv->_priv->start); +#endif + fprintf(stderr, "easycsv: failed to update %s\n", csv->_priv->fp); + return -1; + } + + return 0; +} + +int +easycsv_pushcolumnvalue(const easycsv *csv, const char *col, const char *val) +{ + if (csv == NULL) { + fprintf(stderr, "easycsv: csv is NULL in %s\n", csv->_priv->fp); + return -1; + } + + if ((col == NULL) || (col[0] == '\0')) { + fprintf(stderr, "easycsv: col is either NULL or empty in %s\n", csv->_priv->fp); + return -1; + } + + if ((val == NULL) || (val[0] == '\0')) { + fprintf(stderr, "easycsv: val is either NULL or empty in %s\n", csv->_priv->fp); + return -1; + } + + int colnum = _easycsv_getcolumn(csv, col); + + if (colnum < 0) { + fprintf(stderr, "easycsv: error occured when fetcing col number in %s", csv->_priv->fp); + return -1; + } + + /* Find a free cell under col within csv->_priv->cols limits, if there is none, generate a new + row */ + unsigned int row; + + for (row = 2; row <= csv->_priv->cols; row++) { + if (easycsv_readvalue(csv, colnum, row) != NULL) break; + } + + /* All rows are filled, generate new row */ + if (row == csv->_priv->cols) { + row++; + + /* Set file pointer to end */ + if (fseek(csv->_priv->file, 0L, SEEK_END) < 0) { + fprintf(stderr, "easycsv: failed to set file pointer to end in %s", csv->_priv->fp); + return -1; + } + + char *rowstr = malloc(csv->_priv->cols + strlen(val) + 2); // 1 extra for '\n' + + strncpy(rowstr, "\n", 1); + + for (unsigned int i = 1; i < colnum; i++) { + strncpy(rowstr, ",", 1); + } + + strncpy(rowstr, val, strlen(val)); + + unsigned int colsleft = csv->_priv->cols - colnum; + + for (unsigned i = 1; i < colsleft; i++) { + strncpy(rowstr, ",", 1); + } + + strncpy(rowstr, "/0", 1); + + csv->_priv->rows++; + } + + /* Fill in rows before */ + + for (unsigned int i = 1; i < row; i++) { + char *str = _easycsv_getrow(csv, i); + if (fputs(str, csv->_priv->temp) == EOF) { + fprintf(stderr, "easycsv: failed to write \"%s\" in temporary file %s for %s\n", str, csv->_priv->tmpfp, csv->_priv->fp); + return -1; + } + + free(str); + } + + return 0; +} + +int +easycsv_insertcolumnvalue(const easycsv *csv, const char *col, const unsigned int row, const char *val, const EASYCSV_VALMODE valmode) +{ + if (csv == NULL) { + fprintf(stderr, "easycsv: csv is NULL in %s\n", csv->_priv->fp); + return -1; + } + + if ((col == NULL) || (col[0] == '\0')) { + fprintf(stderr, "easycsv: col is either NULL or empty in %s\n", csv->_priv->fp); + return -1; + } + + if ((val == NULL) || (val[0] == '\0')) { + fprintf(stderr, "easycsv: val is either NULL or empty in %s\n", csv->_priv->fp); + return -1; + } + + int colnum = _easycsv_getcolumn(csv, col); + if (colnum < 0) { + fprintf(stderr, "easycsv: failed to get colnum of %s in %s", col, csv->_priv->fp); + return -1; + } + + char *rowstr = _easycsv_getrow(csv, row); + char *pch = rowstr; + + for (unsigned int i = 1; i < colnum; i++) { + pch = strchr(pch, ','); + pch++; + } + + size_t st = strcspn(pch, ','); + + /* Empty cell */ + if (st == 0) { + + } + + return 0; +} + +int +easycsv_printrows(const easycsv *csv) +{ + return csv->_priv->rows; +} + +int +easycsv_printcolumns(const easycsv *csv) +{ + return csv->_priv->cols; +} + +int +_easycsv_rows(_easycsv *_priv, const EASYCSV_MODE mode) +{ + if (_priv == NULL) { + fprintf(stderr, "easycsv: csv is NULL in %s\n", _priv->fp); + return -1; + } + + /* Prepare it for reading */ + if (_easycsv_rewind(_priv, mode) < 0) return -1; + + int rows = 1; + char c; + + /* Go through each character in the file and count the number of \n + in it */ + while ((c = fgetc(_priv->file)) != EOF) { + if (c == '\n') rows++; + } + + return rows; +} + +int +_easycsv_columns(_easycsv *_priv, const EASYCSV_MODE mode) +{ + if (_priv == NULL) { + fprintf(stderr, "easycsv: csv is NULL in %s\n", _priv->fp); + return -1; + } + + /* Prepare it for reading */ + if (_easycsv_rewind(_priv, mode) < 0) return -1; + + /* + 1. check if empty file + 2. check if only one column -> 0 commas + 3. if >1 column, n commas = n+1 columns + */ + + unsigned int col = 1; + char c; + + while ((c = fgetc(_priv->file)) != '\n') { + if (c == ',') col++; + } + + return col; +} + +const char* +easycsv_readcolumnvalue(const easycsv *csv, const char *col, const unsigned int row) +{ + if (csv == NULL) { + fprintf(stderr, "easycsv: csv is NULL in %s\n", csv->_priv->fp); + return NULL; + } + + if (row > csv->_priv->rows) { + fprintf(stderr, "easycsv: row exceeds max in %s\n", csv->_priv->fp); + return NULL; + } + + int i = _easycsv_getcolumn(csv, col); + if (i < 1) { + return NULL; + } + + return easycsv_readvalue(csv, i, row); +} + +const char* +easycsv_readvalue(const easycsv *csv, const unsigned int col, const unsigned int row) +{ + if (col == 0 || row == 0) { + fputs("easycsv: row or col is 0\n", stderr); + return NULL; + } + + char *str = _easycsv_getrow(csv, row); + if (str == NULL) { + fprintf(stderr, "easycsv: row %i does not exist in %s\n", row, csv->_priv->fp); + return NULL; + } + +#ifdef EASYCSV_DEBUG + printf("[%i] rv_col: %i\nstr: %s\n", clock() - csv->_priv->start, col, str); +#endif + + /* Return if first col */ + if (col == 1) { + size_t st = strcspn(str, ","); + if (st == 0) { + free(str); + return NULL; + } + char *pch = malloc(st); + strncpy(pch, str, st); + free(str); + return pch; + } + + /* Get first occurance of comma in str, + the first value is ommited but not the comma */ + char *pch = strpbrk(str, ","); + + /* Repeat until desired col is found */ + for (int i = 2; i < col; i++) { + pch = strpbrk(pch + 1, ","); + } + + /* Get span from start of string to first occurence of comma */ + size_t st = strcspn(pch + 1, ","); + + /* If 0, no string exists! */ + if (st == 0) { + free(str); + return NULL; + } + + char *val = malloc(st + 1); + strncpy(val, pch + 1, st); + strncat(val, "\0", 1); + free(str); + + return val; +} + +void +easycsv_geterror(const easycsv *csv) +{ + _easycsv_pgeterror(csv->_priv); +} + +void +easycsv_fgeterror(const easycsv *csv, FILE *fs) +{ + _easycsv_fpgeterror(csv->_priv, fs); +} + +/* PRIVATE FUNCTIONS */ + +/* +static int _easycsv_checkmode(struct easycsv *csv, const char mode) +{ + if (strchr(csv->mode, mode) == NULL) + { + return -1; + } +} +*/ + +int +_easycsv_update(easycsv *csv) +{ + if (csv == NULL) { + fputs("easycsv: csv is NULL\n", stderr); + return -1; + } + + /* Set temp file to read binary */ + if (freopen(csv->_priv->tmpfp, "rb", csv->_priv->temp) == NULL) { +#ifdef EASYCSV_DEBUG + fprintf(stderr, "[%i] ", clock() - csv->_priv->start); +#endif + fprintf(stderr, "easycsv: failed to reopen temp file %s in read binary mode\n", csv->_priv->fp); + easycsv_free(csv); + return -1; + } + + /* Set file to write binary */ + if (freopen(csv->_priv->fp, "wb", csv->_priv->file) == NULL) { +#ifdef EASYCSV_DEBUG + fprintf(stderr, "[%i] ", clock() - csv->_priv->start); +#endif + fprintf(stderr, "easycsv: failed to reopen file %s in write binary mode\n", csv->_priv->tmpfp); + easycsv_free(csv); + return -1; + } + + char buf[BUFSIZ]; + size_t size; + while (size = fread(buf, 1, BUFSIZ, csv->_priv->temp)) { + fwrite(buf, 1, size, csv->_priv->file); + } + + /* Set temp file back to write */ + if (freopen(csv->_priv->tmpfp, "w", csv->_priv->temp) == NULL) { +#ifdef EASYCSV_DEBUG + fprintf(stderr, "[%i] ", clock() - csv->_priv->start); +#endif + fprintf(stderr, "easycsv: failed to reopen temp file %s in write mode\n", csv->_priv->fp); + easycsv_free(csv); + return -1; + } + + /* Set file back to read */ + if (freopen(csv->_priv->fp, "r", csv->_priv->file) == NULL) { +#ifdef EASYCSV_DEBUG + fprintf(stderr, "[%i] ", clock() - csv->_priv->start); +#endif + fprintf(stderr, "easycsv: failed to reopen file %s in read mode\n", csv->_priv->tmpfp); + easycsv_free(csv); + return -1; + } + + return 0; +} + +char* +_easycsv_getrow(const easycsv *csv, const unsigned int row) +{ + if (csv == NULL) { + fputs("easycsv: csv is NULL\n", stderr); + return NULL; + } + + if (row > csv->_priv->rows) { + fputs("easycsv: row overflow\n", stderr); + return NULL; + } + + if (row == 0) { + fputs("easycsv: row is 0\n", stderr); + return NULL; + } + + /* Allocate memory */ + char *str = malloc(BUFSIZ); + + /* Set file pointer to start */ + rewind(csv->_priv->file); + + for (int i = 1; i < row; i++) + { + /* skip until row is reached */ + fscanf(csv->_priv->file, "%*[^\n]\n", NULL); + } + + /* Grab the row and store it in str */ + fscanf(csv->_priv->file, "%s\n", str); + + // printf("row: %s\n", str); + + return str; +} + +int +_easycsv_rewind(_easycsv *_priv, const EASYCSV_MODE mode) +{ + if (_priv == NULL) { + fprintf(stderr, "easycsv: csv is NULL in %s\n", _priv->fp); + return -1; + } + + /* Check if file is readable */ + if (mode == EASYCSV_R) { +#ifdef EASYCSV_DEBUG + fprintf(stderr, "[%i] ", clock() - _priv->start); +#endif + fprintf(stderr, "easycsv: %s is not be readable\n", _priv->fp); + return -1; + } + + /* Set file pointer to the start */ + rewind(_priv->file); + + /* Check if empty file */ + if (fscanf(_priv->file, "\n") == EOF) { +#ifdef EASYCSV_DEBUG + fprintf(stderr, "[%i] ", clock() - _priv->start); +#endif + fprintf(stderr, "easycsv: %s is empty\n", _priv->fp); + return -1; + } + + return 0; +} + +int +_easycsv_getcolumn(const easycsv *csv, const char *col) +{ + if (csv == NULL) { + fputs("easycsv: csv is NULL\n", stderr); + return -1; + } + + if ((col == NULL) || (col[0] == '\0')) { + fputs("easycsv: col is either NULL or empty\n", stderr); + return -1; + } + + /* Grab str of row 1 */ + char *firstrow = _easycsv_getrow(csv, 1); + + if (firstrow == NULL) { +#ifdef EASYCSV_DEBUG + fprintf(stderr, "[%i] ", clock() - csv->_priv->start); +#endif + fprintf(stderr, "easycsv: row 1 does not exist in %s\n", csv->_priv->fp); + return -1; + } + + unsigned int commas = 0; + + // printf("FIRST COLUMN: %s\n", firstrow); + + /* Find first occurance of col in firstrow */ + char *str = strstr(firstrow, col); + + if (str == NULL) { +#ifdef EASYCSV_DEBUG + fprintf(stderr, "[%i] ", clock() - csv->_priv->start); +#endif + fprintf(stderr, "easycsv: column %s not found in %s\n", col, csv->_priv->fp); + return -1; + } + + /* Count numbers of commas following str */ + char *c = strpbrk(str, ","); + while (c != NULL) { + commas++; + c = strpbrk(c + 1, ","); + } + + /* no need to free c as it is already NULL at this point */ + // free((char*) str); apparently invalid pointer + + // printf("ROW: %i\nCOL: %i\n", row, csv->_priv->cols - commas); +#ifdef EASYCSV_DEBUG + printf("[%i] rcv_commas: %i\n", clock() - csv->_priv->start, commas); +#endif + + free(firstrow); + + return csv->_priv->cols - commas; +} + +/* +int _easycsv_checkifvalue(struct easycsv *csv, const int col, const int row) +{ + const char *rowstr = _easycsv_getrow(csv, row); + + return 0; +} + +char *_easycsv_getvalueinrow(const int col, const char *row) +{ + char *pbrk = strchr(row, ','); + for (unsigned int i = 1; i < col; i++) { + + } +} +*/ + +void +_easycsv_printerror(const _easycsv *_priv, const EASYCSV_ERROR error) +{ + switch (_priv->error) { + case EASYCSV_ERROROFF: return; + case EASYCSV_ERRORSTDERR: _easycsv_geterror(_priv, error, stderr); break; + case EASYCSV_ERRORSTDOUT: _easycsv_geterror(_priv, error, stdout); break; + default: return; + } +} + +void +_easycsv_geterror(const _easycsv *_priv, const EASYCSV_ERROR error, FILE *fs) +{ +#ifdef EASYCSV_DEBUG + fprintf(stderr, "[%i] ", clock() - _priv->start); +#endif + + fputs("easycsv: ", fs); + + switch (error) { + case EASYCSV_NOERROR: fputs("no error", fs); break; + case EASYCSV_NULLPTR: fputs("pointer is NULL", fs); break; + case EASYCSV_EMPTYSTRING: fputs("string is empty", fs); break; + case EASYCSV_OVERMAXROW: fprintf(fs, "int exceeds row limit %i", _priv->rows); break; + case EASYCSV_OVERMAXCOL: fprintf(fs, "int exceeds column limit %i", _priv->cols); break; + + case EASYCSV_UNKNOWNMODE: fputs("unknown file IO mode", fs); break; + case EASYCSV_OPENFAIL: fputs("failed to open file", fs); break; + case EASYCSV_REOPENFAIL: fputs("failed to reopen file", fs); break; + case EASYCSV_EMPTYCSV: fputs("CSV file is empty", fs); break; + case EASYCSV_UNWRITABLE: fputs("CSV file is not in a writable mode", fs); break; + case EASYCSV_UNREADABLE: fputs("CSV file is not in a readable mode", fs); break; + case EASYCSV_UPDATEFAIL: fputs("CSV file has failed to update", fs); break; + case EASYCSV_FILEPTRFAIL: fputs("failed to move FILE pointer", fs); break; + + case EASYCSV_ROWNOTEXIST: fputs("given row does not exist", fs); break; + case EASYCSV_COLNOTEXIST: fputs("given column does not exist", fs); break; + + case EASYCSV_PUSHCOLFAIL: fputs("failed to push value under column", fs); break; + case EASYCSV_COLNUMFAIL: fputs("failed to determine the column number of a value in the first row", fs); break; + default: fputs("unknown error", fs); break; + } + + if (_priv->fp != NULL) { + fprintf(fs, " in %s", _priv->fp); + } +} diff --git a/src/easycsv.h b/src/easycsv.h new file mode 100644 index 0000000..4b9b2da --- /dev/null +++ b/src/easycsv.h @@ -0,0 +1,102 @@ +#ifndef EASYCSV_H +#define EASYCSV_H + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Flags for error messages */ +typedef enum { + EASYCSV_ERROROFF, + EASYCSV_ERRORSTDERR, + EASYCSV_ERRORSTDOUT +} EASYCSV_ERRORMSG; + +/* Using our own flags to facilitate changing modes during file copying phases */ +typedef enum { + EASYCSV_R, + EASYCSV_W +} EASYCSV_MODE; + +typedef enum { + EASYCSV_REPLACE, + EASYCSV_APPEND, + EASYCSV_APPEND_R, +} EASYCSV_VALMODE; + +typedef struct _easycsv _easycsv; + +typedef struct easycsv { + EASYCSV_MODE mode; + _easycsv *_priv; // private members +} easycsv; + +/* + * ============================ + * FUNCTION PROTOTYPES - PUBLIC + * ============================ + */ + +/* (Constructor) Initialise easycsv */ +easycsv* easycsv_init(const char*, const EASYCSV_MODE); + +/* (Constructor) Initialise easycsv with error message option */ +easycsv* easycsv_init_errormsg(const char*, const EASYCSV_MODE, const EASYCSV_ERRORMSG); + +/* (Destructor) Free easycsv memory */ +void easycsv_free(easycsv*); + +/* Insert in */ +// int easycsv_insertrow(struct easycsv*, const char*, const int); + +/* Create new column on the RHS of an existing one */ +int easycsv_pushcolumn(easycsv*, const char*); + +/* Insert string in a specific cell */ +int easycsv_insertvalue(const easycsv*, const char*, const unsigned int, const unsigned int); + +/* Insert string in a vacant cell under a named column */ +int easycsv_pushcolumnvalue(const easycsv*, const char*, const char*); + +/* Insert string in a specific cell with named column in row 1 */ +int easycsv_insertcolumnvalue(const easycsv*, const char*, const unsigned int, const char*, const EASYCSV_VALMODE); + +/* Insert array of string pointers */ +int easycsv_pushcolumnstrarray(const easycsv*, const char*[], const char*); + +/* Insert array of void pointers */ +int easycsv_pushcolumnvoidarray(const easycsv*, const void*[], const char*); + +/* Delete CSV value */ +int easycsv_deletevalue(const easycsv*, const unsigned int, const unsigned int); + +/* Delete column with unsigned int */ +int easycsv_deletecolumnint(const easycsv*, const unsigned int); + +/* Delete column with str */ +int easycsv_deletecolumnstr(const easycsv*, const char*); + +/* Delete column value with str */ +int easycsv_deletecolumnstrvalue(const easycsv*, const char*, const char*); + +/* Append to CSV files */ +int easycsv_appendcsv(easycsv*, easycsv*); + +/* Read string in a specific cell */ +const char *easycsv_readvalue(const easycsv*, const unsigned int, const unsigned int); + +/* Read string in a specific cell with named column in row 1 */ +const char *easycsv_readcolumnvalue(const easycsv*, const char*, const unsigned int); + +/* Number of rows in entire CSV file */ +int easycsv_printrows(const easycsv*); + +/* Number of columns in entire CSV file */ +int easycsv_printcolumns(const easycsv*); + +#endif /* EASYCSV_H */ diff --git a/src/easycsv.h~ b/src/easycsv.h~ new file mode 100644 index 0000000..da73b12 --- /dev/null +++ b/src/easycsv.h~ @@ -0,0 +1,90 @@ +#ifndef EASYCSV_H +#define EASYCSV_H + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Flags for error messages */ +typedef enum { + EASYCSV_ERROROFF, + EASYCSV_ERRORSTDERR, + EASYCSV_ERRORSTDOUT +} EASYCSV_ERRORMSG; + +/* Using our own flags to facilitate changing modes during file copying phases */ +typedef enum { + EASYCSV_R, + EASYCSV_W +} EASYCSV_MODE; + +typedef enum { + EASYCSV_REPLACE, + EASYCSV_APPEND, + EASYCSV_APPEND_R, +} EASYCSV_VALMODE; + +typedef struct _easycsv _easycsv; + +typedef struct easycsv { + EASYCSV_MODE mode; + _easycsv *_priv; // private members +} easycsv; + +/* + * ============================ + * FUNCTION PROTOTYPES - PUBLIC + * ============================ + */ + +/* (Constructor) Initialise easycsv */ +easycsv* easycsv_init(const char*, const EASYCSV_MODE); + +/* (Constructor) Initialise easycsv with error message option */ +easycsv* easycsv_init_errormsg(const char*, const EASYCSV_MODE, const EASYCSV_ERRORMSG); + +/* (Destructor) Free easycsv memory */ +void easycsv_free(easycsv*); + +/* Insert in */ +// int easycsv_insertrow(struct easycsv*, const char*, const int); + +/* Create new column on the RHS of an existing one */ +int easycsv_pushcolumn(easycsv*, const char*); + +/* Insert string in a specific cell */ +int easycsv_insertvalue(const easycsv*, const char*, const unsigned int, const unsigned int); + +/* Insert string in a vacant cell under a named column */ +int easycsv_pushcolumnvalue(const easycsv*, const char*, const char*); + +/* Insert string in a specific cell with named column in row 1 */ +int easycsv_insertcolumnvalue(const easycsv*, const char*, const unsigned int, const char*, const EASYCSV_VALMODE); + +/* Insert array of string pointers */ +int easycsv_pushcolumnstrarray(const easycsv*, const char*[], const char*); + +/* Insert array of void pointers */ +int easycsv_pushcolumnvoidarray(const easycsv*, const void*[], const char*); + +/* Append to CSV files */ +int easycsv_appendcsv(easycsv*, easycsv*); + +/* Read string in a specific cell */ +const char *easycsv_readvalue(const easycsv*, const unsigned int, const unsigned int); + +/* Read string in a specific cell with named column in row 1 */ +const char *easycsv_readcolumnvalue(const easycsv*, const char*, const unsigned int); + +/* Number of rows in entire CSV file */ +int easycsv_printrows(const easycsv*); + +/* Number of columns in entire CSV file */ +int easycsv_printcolumns(const easycsv*); + +#endif /* EASYCSV_H */ diff --git a/src/internal/easycsv_error.h b/src/internal/easycsv_error.h new file mode 100644 index 0000000..e69de29 diff --git a/tests/01 b/tests/01 new file mode 100755 index 0000000..5be12dc Binary files /dev/null and b/tests/01 differ diff --git a/tests/01.c b/tests/01.c new file mode 100644 index 0000000..82aa9af --- /dev/null +++ b/tests/01.c @@ -0,0 +1,44 @@ +#include "../include/easycsv/easycsv.h" + +#define CSV_FP "01.csv" +#define CSV_MODE "r" + +int main(int argc, char **argv) +{ + if (argc < 2) { + fprintf(stderr, "No arguments!\n"); + return -1; + } + + struct easycsv *csv = easycsv_init(CSV_FP, CSV_MODE); + + if (access(CSV_FP, F_OK) < 0) { + fprintf(stderr, "%s cannot be found!\n", CSV_FP); + return -1; + } + + if (csv == NULL) { + fprintf(stderr, "Failed to initialise %s on mode %s\n", CSV_FP, CSV_MODE); + return -1; + } + + printf("COLUMNS: %i\nROWS: %i\n", easycsv_rows(csv), easycsv_columns(csv)); + + // char *str = malloc(EASYCSV_BUFFER_SIZE); + + printf("Searching column values of %s...\n", argv[1]); + + for (int i = 2;;i++) { + char *null = (char*) easycsv_readcolumnvalue(csv, argv[1], i); + if (null == NULL) { + free(null); + puts("End of file!\n"); + break; + } + printf("ROW %i: %s\n", i, null); + } + + easycsv_free(csv); + csv = NULL; + return 0; +} diff --git a/tests/02.c b/tests/02.c new file mode 100644 index 0000000..cdae6b3 --- /dev/null +++ b/tests/02.c @@ -0,0 +1,53 @@ +#include "../include/easycsv/easycsv.h" + +int main(void) +{ + const char CSV_FP[] = "02.csv"; + + struct easycsv *csv = easycsv_init(CSV_FP, EASYCSV_W); + + if (csv == NULL) { + fprintf(stderr, "Failed to load %s\n", CSV_FP); + return -1; + } + + const char mod_one[] = "first-mod-deluxe-edition"; + + const char *paths_one[5] = { + "FILEPATH1ONE", + "FILEPATH2ONE", + "FILEPATH3ONE", + "FILEPATH4ONE", + "FILEPATH5ONE" + }; + + const char mod_two[] = "second-mod-gold-edition"; + + const char *paths_two[4] = { + "FILEPATH1TWO", + "FILEPATH2TWO", + "FILEPATH3TWO", + "FILEPATH4TWO" + }; + + if (easycsv_pushcolumn(csv, mod_one) < 0) { + return -1; + } + + for (size_t st = 0; st < 5; st++) { + if (easycsv_pushcolumnvalue(csv, mod_one, paths_one[st]) < 0) return -1; + } + + if (easycsv_pushcolumn(csv, mod_two) < 0) { + return -1; + } + + for (size_t st = 0; st < 4; st++) { + if (easycsv_pushcolumnvalue(csv, mod_two, paths_two[st]) < 0) return -1; + } + + easycsv_free(csv); + csv = NULL; + + return 0; +} diff --git a/tests/02.c~ b/tests/02.c~ new file mode 100644 index 0000000..07bc365 --- /dev/null +++ b/tests/02.c~ @@ -0,0 +1,54 @@ +#include "../include/easycsv/easycsv.h" + +#define CSV_FP "02.csv" +#define CSV_MODE "w" + +int main(void) +{ + struct easycsv *csv = easycsv_init(CSV_FP, CSV_MODE); + + if (csv == NULL) { + fprintf(stderr, "Failed to load %s\n", CSV_FP); + return -1; + } + + const char mod_one[] = "first-mod-deluxe-edition"; + + const char *paths_one[5] = { + "FILEPATH1ONE", + "FILEPATH2ONE", + "FILEPATH3ONE", + "FILEPATH4ONE", + "FILEPATH5ONE" + }; + + const char mod_two[] = "second-mod-gold-edition"; + + const char *paths_two[4] = { + "FILEPATH1TWO", + "FILEPATH2TWO", + "FILEPATH3TWO", + "FILEPATH4TWO" + }; + + if (easycsv_pushcolumn(csv, mod_one) < 0) { + return -1; + } + + for (size_t st = 0; st < 5; st++) { + if (easycsv_pushcolumnvalue(csv, mod_one, paths_one[st]) < 0) return -1; + } + + if (easycsv_pushcolumn(csv, mod_two) < 0) { + return -1; + } + + for (size_t st = 0; st < 4; st++) { + if (easycsv_pushcolumnvalue(csv, mod_two, paths_two[st]) < 0) return -1; + } + + easycsv_free(csv); + csv = NULL; + + return 0; +} diff --git a/tests/csv/01.csv b/tests/csv/01.csv new file mode 100644 index 0000000..195695a --- /dev/null +++ b/tests/csv/01.csv @@ -0,0 +1,5 @@ +TEST1,TEST2,TEST3 +FILEPATHA1,FILEPATHA2,FILEPATHA3 +FILEPATHB1,FILEPATHB2,FILEPATHB3 +,FILEPATHC2,FILEPATHC3 +,FILEPATHD2, \ No newline at end of file