payredu

Cross-platform ledger GUI written in c99
git clone git@getsh.org:payredu.git
Log | Files | Refs | README

commit 34d5c65e4c9ded5e7ebe8552fc73e015089b0cb9
parent 475634600bc5adfaa6bc087a707b9eb6d0f2a795
Author: Bharatvaj Hemanth <bharatvaj@yahoo.com>
Date:   Sun, 19 Nov 2023 02:11:22 +0530

Add basic ledger file parsing in book.c

Add strn.h to parse integers with in between '.' and ',' (basic
localization support)

Diffstat:
M.gitignore | 1+
MMakefile | 6+++++-
Mbalance.c | 8++++----
Mbook.c | 393+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
Mhot.c | 5+++--
Dhotbook | 0
Mhotbook.c | 9++++++---
Astrn.h | 16++++++++++++++++
8 files changed, 378 insertions(+), 60 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,5 +1,6 @@ balance hot +hotbook *.so *.o tags diff --git a/Makefile b/Makefile @@ -26,5 +26,9 @@ hotbook: hotbook.c libbook.so refresh: git ls-files | entr sh hot.sh +format: + indent -psl -pal --use-tabs -ts4 -br -brs -ce -cli0 book.c + clean: - -rm $$(cat .gitignore) + -rm *.so *.o hotbook libbook.so + diff --git a/balance.c b/balance.c @@ -38,8 +38,8 @@ void* module_main(struct nk_context* ctx, int width, int height) { float size[] = {0.10, 0.70, 0.30}; nk_layout_row(ctx, NK_DYNAMIC, 0, 3, size); nk_layout_row_begin(ctx, NK_STATIC, 25, 1); - nk_layout_row_push(ctx, 40); - nk_label(ctx, "500", NK_TEXT_LEFT); + //nk_layout_row_push(ctx, 40); + //nk_label(ctx, "500", NK_TEXT_LEFT); nk_layout_row_end(ctx); int len = 12; @@ -52,8 +52,8 @@ void* module_main(struct nk_context* ctx, int width, int height) { printf("%s\n", name); edit_entry(i); } - nk_layout_row_push(ctx, 0.3f); - nk_label(ctx, "500", NK_TEXT_LEFT); + //nk_layout_row_push(ctx, 0.3f); + //nk_label(ctx, "500", NK_TEXT_LEFT); nk_layout_row_end(ctx); } diff --git a/book.c b/book.c @@ -2,7 +2,11 @@ #include <stddef.h> #include <stdlib.h> #include <inttypes.h> +#include <ctype.h> #include "common.h" +#include "strn.h" + +#include <unistd.h> #include <GLFW/glfw3.h> #define NK_INCLUDE_FIXED_TYPES @@ -23,84 +27,373 @@ #include "book.h" -float your_text_width_calculation(nk_handle handle, float height, const char *text, int len) { - return 10.0f; -} +#define warning(STR,...) \ + fprintf(stdout, "\033[31m"STR"\033[0m", __VA_ARGS__); + +typedef struct { + char *str; + size_t len; +} vstr_t; + +size_t tree_depth = 4; + +struct map_tree; -char* tags[100]; +struct map_tree { + vstr_t *value; + size_t children_cap; + size_t children_len; + struct map_tree *children; +}; + +typedef struct map_tree map_tree_t; + +vstr_t tags[100] = { 0 }; + +size_t tags_size = 0; /* -char* tags = { - "Expenses:Auto:Gas", - "Liabilities:MasterCard", - "Assets:Credit" - }; -*/ + * char* tags = { "Expenses:Auto:Gas", "Liabilities:MasterCard", + * "Assets:Credit" }; + */ /** * -read the file | parse for newlines -2023/10/24 - 5, fptr + 0, fptr + 6, fptr + 9 -Entry* entry = get_entry("2023/10/24"); + read the file | parse for newlines + 2023/10/24 - 5, fptr + 0, fptr + 6, fptr + 9 + Entry* entry = get_entry("2023/10/24"); -skip \n -read until date, and read until from + skip \n + read until date, and read until from -**/ + **/ // commodity max size 256 // commodity name max size 4 -char commodity_list[256][4]; +char commodity_list[256][8]; + +map_tree_t *rootp = NULL; typedef struct { - time_t date; // the date associated with the entry - char* from; - size_t from_len; - short from_denom; - long from_amount; - char* to; - size_t to_len; - short to_denom; - long to_amount; -} LedgerEntry; + vstr_t *denom; + int amount; +} LedgerValue; +typedef struct { + vstr_t *reg; + LedgerValue val; +} LedgerRecord; -time_t ledger_timestamp_from_ledger_date(const char* date_str) { - // coneverts string 'YYYY-MM-DD' to unix timestamp +typedef struct { + time_t date; // the date associated with the entry + vstr_t comment; // comment associated with the entry + LedgerRecord **records; +} LedgerEntry; + +time_t ledger_timestamp_from_ledger_date(char *date_str) +{ + // converts string 'YYYY-MM-DD' to unix timestamp // date_str should be exactly 10 characters - assert(strlen(date_str) == 10); - struct tm tm; - tm.tm_year = 2023;//strtoi(date_str, date_str + 10, 0, 0, 3); - printf("%d", tm.tm_year); - return 0; + // printf("%.*s\n", 4, date_str); + struct tm tm = { 0 }; + tm.tm_year = natoi(date_str, 4) - 1900; + tm.tm_mon = natoi(date_str + 5, 2) - 1; + tm.tm_mday = natoi(date_str + 8, 2); + // warning("tm_year %d, tm_mon: %d, tm_mday: %d", natoi(date_str, 4), + // tm.tm_mon, tm.tm_mday); + return mktime(&tm); +} + +const char *states_str[] = { + "DATE", + "COMMENT", + "ENTRY START", + "ENTRY SPACE", + "ENTRY WHO", + "ENTRY DENOM", + "ENTRY AMOUNT", + "ENTRY END", +}; + +typedef enum { + DATE, + COMMENT, + ENTRY_START, // entry starts after a comment + ENTRY_SPACE, + ENTRY_WHO, + ENTRY_DENOM, + ENTRY_AMOUNT, + ENTRY_END, // finish up entry if encountering any \n\n or \n text_len == i or text_len == i, otherwise set state to ENTRY_SPACE +} LedgerParseStates; + +map_tree_t *account_search(map_tree_t *children, char *acc, size_t acc_size) +{ + if (children->children == NULL) + return children; + vstr_t *rk = children->value; + if (rk != NULL && acc_size == rk->len + && (strncmp(acc, rk->str, acc_size) == 0)) { + return children; + } + for (size_t i = 0; i < children->children_len; i++) { + vstr_t *val = children->children[i].value; + if (val != NULL && acc_size == val->len + && (strncmp(acc, val->str, acc_size) == 0)) { + return children->children + i; + } + } + // when the search is exhausted and nothing is found, + // return the previously allocated child + // TODO if len < cap allocate memory + map_tree_t *child_to_return = + children->children + children->children_len; + children->children_len++; + return child_to_return; } -void ledger_parse_data(const char* text, size_t text_len) { +int account_add(map_tree_t **rootp, char *acc, size_t acc_size) +{ + size_t records_needed = tree_depth * 4; + if (*rootp == NULL) { + *rootp = malloc(sizeof(map_tree_t)); + } + if ((*rootp)->children == NULL) { + (*rootp)->children = + (map_tree_t *) calloc(records_needed, sizeof(map_tree_t)); + (*rootp)->children_cap = records_needed; + } + size_t i = 0; + while (i < acc_size) { + if (acc[i] == ':' || i + 1 == acc_size) { + size_t j = i + 1; + map_tree_t *current_node = + account_search(*rootp, acc, j); + assert(current_node != NULL); + if (current_node->value == NULL) { + // current_node->value is NULL when the search fails + // we have to set the value now + // TODO maybe save vstrs in a pool and use them + vstr_t *vstr = + (vstr_t *) malloc(sizeof(vstr_t)); + vstr->str = acc; + vstr->len = j; + current_node->value = vstr; + printf("%d %.*s\n", j, j, acc); + } else { + printf("Present already= %d %.*s\n", j, j, acc); + } + if (i + 1 != acc_size) { + return account_add(&(*rootp)->children, acc + j, + acc_size - j); + } + } + i++; + } + return -1; +} + +size_t tab_acc = 0; + +void walk_it (map_tree_t* rootp) +{ + if (rootp == NULL) + return; + vstr_t *val = rootp->value; + if (val != NULL) { + for (size_t i = 0; i < tab_acc; i++) { + printf("\t"); + } + printf("-|%.*s|-\n", val->len, val->str); + } + tab_acc++; + if (rootp->children == NULL) + return; + for (int i = 0; i < rootp->children_len; i++) { + printf("|", i); + walk_it(rootp->children + i); + } + tab_acc--; +} + +void ledger_parse_data(char *text, size_t text_len) +{ + setvbuf(stdout, NULL, _IONBF, 0); + LedgerParseStates state = DATE; + size_t line_no = 1; + size_t current_column = 1; time_t t = time(NULL); - for(int c = 0; c < text_len; c++) { - switch(text[c]) { - case ';': - printf("#"); - break; + // printf("1|"); + size_t i = 0; + // TODO it may be possible to push these to the tree itself, explore the possibility + // these act as temporary register until we push back the entry to a tree + time_t hold_date; + vstr_t hold_comment = { 0 }; + vstr_t hold_register = { 0 }; + size_t hold_denom_id = { 0 }; + short n_count = 0; + + while (i < text_len) { + char c = text[i]; + // we use \n to identify entry done in ledger + // + switch (c) { + case '\n': + case '\r': + line_no++; + n_count++; + printf("\n%d| ", line_no); + switch (state) { + case ENTRY_WHO: + case ENTRY_END: + // push the entries to stack or somethin + printf("case ENTRY_END: '%c', prev: '%c'\n", c, text[i-1]); + if (text[i - 1] == '\n') { + printf("----- Entry End Marked -----\n"); + state = DATE; + } else { + state = ENTRY_WHO; + } + break; + case COMMENT: + state = ENTRY_WHO; + break; + case ENTRY_DENOM: + warning("%s\n", "denom not found, setting state WHO"); + state = ENTRY_WHO; + break; + case ENTRY_AMOUNT: + goto ledger_parse_error_handle; + break; + } + i++; + continue; case ' ': - printf(" "); + case '\t': + n_count = 0; + i++; + continue; break; - case ' ': - printf(" "); + } + n_count = 0; + // next state + switch (state) { + case DATE: + if (isdigit(c)) { + // try to parse a date + time_t tn = ledger_timestamp_from_ledger_date(text + i); + warning("date str: %.*s\n", 10, text + i); + warning("date: %ld\n", tn); + // date is expected to have the form DD/MM/YYYY (10) + i += 10; + if (tn == (time_t) - 1) goto ledger_parse_error_handle; + state = COMMENT; + } break; - case '\n': - printf(",\n"); + case COMMENT: + if (isalnum(c)) { + // we hit alphanumerical after whitespace + size_t comment_len = 0; + vstr_t comment = { + .str = text + i, + .len = 0 + }; + while (i < text_len && *(text + i) != '\n') { + i++; + comment_len++; + } + comment.len = comment_len; + warning("Comment: %.*s\n", comment_len, + comment); + state = ENTRY_WHO; + } + break; + case ENTRY_SPACE: + { + size_t original_i = i; + while (i < text_len && isspace(text[i])) i++; + int wsc = i - original_i; + warning("i: %ld, Spaces: %d\n", i, wsc); + if (wsc < 2) { + goto ledger_parse_error_handle; + } + state = ENTRY_WHO; + } break; - default: - printf("*"); + case ENTRY_WHO: + { + // add this to register + size_t who_len = i; + vstr_t who = { + .str = text + i, + .len = 0 + }; + while (i < text_len) { + switch (text[i]) { + case '\n': + case '\r': + goto ledger_who_parsed; + break; + case '\t': + case ' ': + goto ledger_who_parsed; + break; + default: + i++; + break; + } + } +ledger_who_parsed: + who_len = i - who_len; + printf("parsed: i=%d\n", i); + account_add(&rootp, who.str, who_len); + warning("i=%d, Who: %.*s\n", i, who_len, who); + state = ENTRY_DENOM; + // add to tags here + } break; + case ENTRY_DENOM: + { + warning("denom-i: %d\n", i + 1); + size_t denom_len = i; + vstr_t denom = { + .str = text + i, + .len = 0 + }; + while (i < text_len && !isdigit(*(text + i))) + i++; + state = ENTRY_AMOUNT; + denom_len = i - denom_len; + denom.len = denom_len; + warning("len: %d, denom: %.*s, i: %d\n", denom_len, denom_len, denom.str, i); + } + break; + case ENTRY_AMOUNT: + { + warning("amount-i: %d\n", i + 1); + char *amount = text + i; + size_t amount_len = i; + char _c = *(text + i); + while (i < text_len && (isdigit(_c) || _c == '.' || _c == ',')) { + i++; + _c = *(text + i); + } + state = ENTRY_END; + amount_len = i - amount_len; + warning("%d> len: %d, amount: %.*s\n", i, amount_len, amount_len, amount); + } } } + printf("read complete\n"); return; +ledger_parse_error_handle: + warning("Parse failed at line %ld(%d)\n, Expected %s, got '%c'", + line_no, i, states_str[state], text[i]); } -void* module_main(const char* data, size_t data_len) { - printf("%s\n", data); - +void *module_main(char *data, size_t data_len) +{ + // printf("%s\n", data); + printf("\n=======| Startality |=======\n"); ledger_parse_data(data, data_len); + printf("\n========| Fatality |========\n"); } diff --git a/hot.c b/hot.c @@ -56,7 +56,8 @@ int main(int argc, char* argv[]) { ctx = nk_glfw3_init(win, NK_GLFW3_INSTALL_CALLBACKS); /***********************/ //nk_init_fixed(ctx, calloc(1, MAX_MEMORY), MAX_MEMORY, &font); - {struct nk_font_atlas *atlas; + { + struct nk_font_atlas *atlas; nk_glfw3_font_stash_begin(&atlas); struct nk_font *droid = nk_font_atlas_add_from_file(atlas, "./Ubuntu-Medium.ttf", 14, 0); nk_glfw3_font_stash_end(); @@ -64,7 +65,7 @@ int main(int argc, char* argv[]) { } while(1) { void* module = dlopen("./libbalance.so", RTLD_NOW); - while(module == NULL){ + while(module == NULL) { fprintf(stderr, "Failed to load module. (%s)\n", dlerror()); fprintf(stderr, "Press return to try again.\n"); getchar(); diff --git a/hotbook b/hotbook Binary files differ. diff --git a/hotbook.c b/hotbook.c @@ -23,7 +23,8 @@ void sig_handle() { typedef void* (*module_main_func)(const char*, size_t); int main(int argc, char* argv[]) { - signal(SIGQUIT, sig_handle); while(1) { + signal(SIGQUIT, sig_handle); + //while(1) { void* module = dlopen("./libbook.so", RTLD_NOW); while(module == NULL){ fprintf(stderr, "Failed to load module. (%s)\n", dlerror()); @@ -36,18 +37,20 @@ int main(int argc, char* argv[]) { char* data = (char*)malloc(2048 * sizeof(char)); size_t data_size = 0; size_t c_read = 0; - while((c_read = fread(data, 1, BUFFER_SIZE, in)) != 0) { + while((c_read = fread(data + data_size + 0, 1, BUFFER_SIZE, in)) != 0) { data_size += c_read; } if (ferror(in)) fprintf(stderr, "Error reading file\n"); + fprintf(stdout, "Startig loop\n"); module_main(data, data_size); + while(should_exit == 0) { sleep(1); } should_exit = 0; dlclose(module); fprintf(stderr, "Continue?\n"); - } + //} return 0; } diff --git a/strn.h b/strn.h @@ -0,0 +1,16 @@ +#ifndef _STRN_H +#define _STRN_H + +inline int natoi(char* str, size_t len) { + int final = 0; + int i = 0; + // ignore leading zeroes + while(i < len && str[i] == '0') i++; + for(;i < len; i++) { + final *= 10; + final += str[i] - '0'; + } + return final; +} + +#endif