diff --git a/src/grammar/expenses.y b/src/grammar/expenses.y index f2b4a69..4e1f1b1 100644 --- a/src/grammar/expenses.y +++ b/src/grammar/expenses.y @@ -57,9 +57,9 @@ msg: update value TOK_USER TOK_STR { - write_bot_expense_entry(stdout, - create_bot_expense_entry(NULL, $1, $2, $3, $4)); - printf("update: %d %f %s %s\n", $1, $2, $3->str, $4->str); $$ = 0;} + int res = write_update_to_csv(g_string_new("test.csv"), $1, $2, $3, $4); + printf("update [%d]: %d %f %s %s\n", res, $1, $2, $3->str, $4->str); $$ = 0; + } | TOK_CMD_DEL TOK_INT { printf("delete: %d\n", $2); $$ = 0; } | TOK_CMD_REPORT ts { printf("report: %d\n", $2); $$ = 0; } diff --git a/src/grammar/types.c b/src/grammar/types.c index 41cd83b..a7942a6 100644 --- a/src/grammar/types.c +++ b/src/grammar/types.c @@ -2,6 +2,19 @@ */ #include "types.h" +extern BotExpenseEntry *g_LastExpenseEntry = NULL; +BotExpenseEntry *bot_get_last_expense() +{ + return g_LastExpenseEntry; +} +void bot_set_last_expense(BotExpenseEntry *entry) +{ + if(g_LastExpenseEntry) { + free_bot_expense_entry(g_LastExpenseEntry); + } + g_LastExpenseEntry = entry; +} + /* Parse a string with Flex+Bison instead of a file / stdout */ int grammar_parse_string(const GString *buf) { int res; @@ -11,7 +24,29 @@ return res; } -/* Create a BotExpenseEntry with data fed from the parser. If 'previous' is null, initialize as first expense and 'totalAmount' will be 0 +int bot_get_current_entry_id +( + const GString* filename +) +{ + /* TODO this counts by char, probably more efficient ways exist */ + char c; + FILE *fp; + unsigned int count = 0; + + if((fp = fopen(filename->str, "r"))) { + for (c = getc(fp); c != EOF; c = getc(fp)) { + if (c == '\n') { + count++; + } + } + fclose(fp); + } + + return count; +} + +/* Create a BotExpenseEntry with data fed from the parser. * Returns NULL if error getting local time. */ BotExpenseEntry *create_bot_expense_entry @@ -24,7 +59,6 @@ ) { unsigned int id; - double totalAmount; double signedValue = value; BotExpenseEntry *entry = malloc(sizeof *entry); // TODO this should be freed once it's written to registry struct tm *lt = malloc(sizeof *lt); @@ -43,17 +77,14 @@ if(previous == NULL) { id = 1; - totalAmount = signedValue; } else { id = previous->id + 1; - totalAmount = previous->totalAmount + signedValue; /* value can be negative */ } entry->id = id; entry->time = *lt; entry->value = signedValue; - entry->totalAmount = totalAmount; - entry->user = user; /* we are setting the pointer here, not copying! */ + entry->user = user; entry->notes = notes; entry->_serialized = false; @@ -88,11 +119,10 @@ entry->time.tm_sec); g_string_printf(result, - "%u,%s,%.2f,%.2f,%s,%s\n", + "%u,%s,%.2f,%s,%s\n", entry->id, tstr->str, entry->value, - entry->totalAmount, entry->user->str, entry->notes->str ); @@ -107,25 +137,73 @@ */ int write_bot_expense_entry ( - FILE *fp, - BotExpenseEntry *entry + const GString *filename, + BotExpenseEntry *entry, + bool needsHeaders ) { + FILE *fp; + int result; GString *line = serialize_bot_expense_entry_csv(entry); - if(fp == NULL) { - fprintf(stderr, "File pointer to CSV is NULL. Initialize it before calling 'serialize_bot_expense_entry_csv'\n"); - return -1; - } - if(line == NULL) { return -1; } - /* line is terminated with '\n' */ - return fprintf(fp, "%s", line->str); + if((fp = fopen(filename->str, "a"))) { + if(needsHeaders) { + fprintf(fp, "ID,Time,Value,User,Notes\n"); + } + + /* line is terminated with '\n' */ + result = fprintf(fp, "%s", line->str); + fclose(fp); + + return result; + fprintf(stderr, "Error opening %s\n", filename->str); + } + + fprintf(stderr, "Error opening %s\n", filename->str); + return -1; } +/* whole routine to write an expense to a CSV file */ +int write_update_to_csv +( + const GString *filename, + const BotCommand cmd, + const double value, + GString* user, + GString* notes +) +{ + int result; + unsigned int id; + BotExpenseEntry *entry; + BotExpenseEntry *previous = bot_get_last_expense(); + bool needsHeaders = false; + + entry = create_bot_expense_entry(previous, cmd, value, user, notes); + if(entry == NULL) { + return -1; + } + + if(previous == NULL) { /* first message received by the bot; needs to get latest ID */ + id = bot_get_current_entry_id(filename); + if(id == 0) { /* file is empty; initialize with headers */ + needsHeaders = true; + } + } + + result = write_bot_expense_entry(filename, entry, needsHeaders); + + if(result > 0) { /* write successful */ + bot_set_last_expense(entry); + } + return result; +} + + /* To be called after it has been serialized */ void free_bot_expense_entry ( diff --git a/src/grammar/types.h b/src/grammar/types.h index 83afabf..8222b87 100644 --- a/src/grammar/types.h +++ b/src/grammar/types.h @@ -23,7 +23,6 @@ unsigned int id; struct tm time; /* number of seconds since the epoch, needs `localtime()` + timezone to localize: https://stackoverflow.com/questions/1442116/how-can-i-get-the-date-and-time-values-in-a-c-program */ double value; /* positive or negative according to in or out */ - double totalAmount; /* keep track of the total amount at this point */ bool _serialized; /* utility to check if entry has been serialized before freeing */ GString *user; GString *notes; @@ -39,7 +38,23 @@ /* Parse a string with Flex+Bison instead of a file / stdout */ int grammar_parse_string(const GString *buf); -/* Create a BotExpenseEntry with data fed from the parser. If 'previous' is null, initialize as first expense and 'totalAmount' will be 0 */ +/* count the lines of the file to get the last ID */ +int bot_get_current_entry_id +( + const GString* filename +); + +/* complete routine to write a message to CSV file */ +int write_update_to_csv +( + const GString *filename, + const BotCommand cmd, + const double value, + GString* user, + GString* notes +); + +/* Create a BotExpenseEntry with data fed from the parser. */ BotExpenseEntry *create_bot_expense_entry ( const BotExpenseEntry *previous, @@ -58,6 +73,19 @@ /* returns the number of bytes written, following the convention of fprintf */ int write_bot_expense_entry ( - FILE *fp, + const GString *filename, + BotExpenseEntry *entry, + bool needsHeaders +); + +/* To be called after it has been serialized */ +void free_bot_expense_entry +( BotExpenseEntry *entry ); + + +/* store the last expense in memory */ +extern BotExpenseEntry *g_LastExpenseEntry; +BotExpenseEntry *bot_get_last_expense(); +void bot_set_last_expense(BotExpenseEntry *entry);