payredu

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

book.c (8717B)


      1 #include <stdio.h>
      2 #include <stddef.h>
      3 #include <stdlib.h>
      4 #include <inttypes.h>
      5 #include <ctype.h>
      6 #include <limits.h>
      7 #include "common.h"
      8 #include "strn.h"
      9 
     10 #ifndef _WIN32
     11 #include <unistd.h>
     12 #endif
     13 
     14 #ifdef ENABLE_GUI
     15 #include <GLFW/glfw3.h>
     16 #define NK_INCLUDE_FIXED_TYPES
     17 #define NK_INCLUDE_STANDARD_IO
     18 #define NK_INCLUDE_STANDARD_VARARGS
     19 #define NK_INCLUDE_DEFAULT_ALLOCATOR
     20 #define NK_INCLUDE_VERTEX_BUFFER_OUTPUT
     21 #define NK_INCLUDE_FONT_BAKING
     22 #define NK_INCLUDE_DEFAULT_FONT
     23 #define NK_IMPLEMENTATION
     24 #define NK_GLFW_GL2_IMPLEMENTATION
     25 #define NK_KEYSTATE_BASED_INPUT
     26 #include <nuklear.h>
     27 #include <nuklear_glfw_gl2.h>
     28 #endif
     29 
     30 #define _XOPEN_SOURCE
     31 #include <time.h>
     32 
     33 #include "vstr.h"
     34 #include <account.h>
     35 #include <book.h>
     36 
     37 #define BUFFER_SIZE 256
     38 
     39 #define warning(STR) \
     40 	fprintf(stdout, "\033[31m"STR"\033[0m");
     41 
     42 #define warningf(STR,...) \
     43 	fprintf(stdout, "\033[31m"STR"\033[0m", __VA_ARGS__);
     44 
     45 vstr_t tags[100] = { 0 };
     46 
     47 size_t tags_size = 0;
     48 
     49 /*
     50  * char* tags = { "Expenses:Auto:Gas", "Liabilities:MasterCard",
     51  * "Assets:Credit" };
     52  */
     53 
     54 /**
     55  *
     56  read the file | parse for newlines
     57  2023/10/24 - 5, fptr + 0, fptr + 6, fptr + 9
     58  Entry* entry = get_entry("2023/10/24");
     59 
     60  skip \n
     61  read until date, and read until from
     62 
     63  **/
     64 
     65 // commodity max size 256
     66 // commodity name max size 4
     67 char commodity_list[256][8];
     68 
     69 map_tree_t *rootp = NULL;
     70 
     71 
     72 // store numbers in the least denom
     73 // 1.50$ == 150
     74 // 2$ == 200
     75 // 2.23$ == 200
     76 
     77 typedef struct {
     78 	vstr_t *denom;
     79 	size_t amount;
     80 } LedgerValue;
     81 
     82 typedef struct {
     83 	vstr_t *reg;
     84 	LedgerValue val;
     85 } LedgerRecord;
     86 
     87 typedef struct {
     88 	time_t date;		// the date associated with the entry
     89 	vstr_t comment;		// comment associated with the entry
     90 	LedgerRecord **records;
     91 } LedgerEntry;
     92 
     93 time_t ledger_timestamp_from_ledger_date(char *date_str)
     94 {
     95 	// converts string 'YYYY-MM-DD' to unix timestamp
     96 	// date_str should be exactly 10 characters
     97 	// printf("%.*s\n", 4, date_str);
     98 	struct tm tm = { 0 };
     99 	tm.tm_year = natoi(date_str, 4) - 1900;
    100 	tm.tm_mon = natoi(date_str + 5, 2) - 1;
    101 	tm.tm_mday = natoi(date_str + 8, 2);
    102 	// warning("tm_year %d, tm_mon: %d, tm_mday: %d", natoi(date_str, 4),
    103 	// tm.tm_mon, tm.tm_mday);
    104 	return mktime(&tm);
    105 }
    106 
    107 const char *states_str[] = {
    108 	"DATE",
    109 	"COMMENT",
    110 	"ENTRY START",
    111 	"ENTRY SPACE",
    112 	"ENTRY WHO",
    113 	"ENTRY SIGN",
    114 	"ENTRY SIGN OR AMOUNT",
    115 	"ENTRY AMOUNT",
    116 	"ENTRY DENOM",
    117 	"ENTRY DENOM OR AMOUNT",
    118 	"ENTRY SIGN OR DENOM OR AMOUNT",
    119 	"ENTRY END",
    120 };
    121 
    122 typedef enum {
    123 	DATE,
    124 	COMMENT,
    125 	ENTRY_START, // entry starts after a comment
    126 	ENTRY_SPACE,
    127 	ENTRY_WHO,
    128 	ENTRY_SIGN,
    129 	ENTRY_SIGN_AMOUNT,
    130 	ENTRY_AMOUNT,
    131 	ENTRY_DENOM ,
    132 	ENTRY_DENOM_AMOUNT,
    133 	ENTRY_SIGN_DENOM_AMOUNT,
    134 	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
    135 	POSTING_END,
    136 } LedgerParseStates;
    137 
    138 
    139 void ledger_parse_data(char *text, size_t text_len)
    140 {
    141 	LedgerParseStates state = DATE;
    142 	size_t line_no = 1;
    143 	size_t current_column = 1;
    144 	time_t t = time(NULL);
    145 	size_t i = 0;
    146 	//time_t hold_date;
    147 	//vstr_t hold_comment = { 0 };
    148 	//vstr_t hold_register = { 0 };
    149 	long int hold_amount = LONG_MAX;
    150 	short hold_sign = -1;
    151 	size_t hold_denom_id = { 0 };
    152 	short n_count = 0;
    153 
    154 	setvbuf(stdout, NULL, _IONBF, 0);
    155 	// TODO it may be possible to push these to the tree itself, explore the possibility
    156 	// these act as temporary register until we push back the entry to a tree
    157 
    158 	while (i < text_len) {
    159 		char c = text[i];
    160 		// we use \n to identify entry done in ledger
    161 		switch (c) {
    162 			case '\n':
    163 			case '\r':
    164 				line_no++;
    165 				n_count++;
    166 				switch (state) {
    167 					// after parsing the amount seq, we set the state to ENTRY_WHO
    168 					case ENTRY_WHO:
    169 					case ENTRY_END:
    170 						hold_sign = -1;
    171 						hold_amount = LONG_MAX;
    172 						// if entry_count <= 1 throw error
    173 						if (text[i - 1] == '\n') {
    174 							state = DATE;
    175 							// TODO push the entries to stack or somethin
    176 							warning("\n==\n");
    177 							// state = POSTING_END;
    178 						} else {
    179 							state = ENTRY_WHO;
    180 							warning(",");
    181 						}
    182 						break;
    183 					case COMMENT:
    184 						state = ENTRY_START;
    185 						break;
    186 					case ENTRY_SIGN_DENOM_AMOUNT:
    187 						state = ENTRY_WHO;
    188 						break;
    189 					case ENTRY_DENOM:
    190 						//warningf("%s", "denom not found, setting state WHO");
    191 						state = ENTRY_WHO;
    192 						break;
    193 					case ENTRY_AMOUNT:
    194 						goto ledger_parse_error_handle;
    195 						break;
    196 				}
    197 				i++;
    198 				continue;
    199 			case ' ':
    200 			case '\t':
    201 				n_count = 0;
    202 				i++;
    203 				continue;
    204 				break;
    205 		}
    206 		n_count = 0;
    207 		// next state
    208 		switch (state) {
    209 			case DATE:
    210 				if (isdigit(c)) {
    211 					// try to parse a date
    212 					time_t tn = ledger_timestamp_from_ledger_date(text + i);
    213 					warningf("%.*s: %ld	", 10, text + i, (long)tn);
    214 					// date is expected to have the form DD/MM/YYYY (10)
    215 					i += 10;
    216 					if (tn == (time_t) - 1) goto ledger_parse_error_handle;
    217 					state = COMMENT;
    218 				}
    219 				break;
    220 			case COMMENT:
    221 				if (isalnum(c)) {
    222 					// we hit alphanumerical after whitespace
    223 					size_t comment_len = 0;
    224 					vstr_t comment = {
    225 						.str = text + i,
    226 						.len = 0
    227 					};
    228 					while (i < text_len && *(text + i) != '\n') {
    229 						i++;
    230 						comment_len++;
    231 					}
    232 					comment.len = comment_len;
    233 					warningf("Comment: %.*s", comment_len,
    234 							comment.str);
    235 					state = ENTRY_START;
    236 				}
    237 				break;
    238 			case ENTRY_START:
    239 			case ENTRY_WHO:
    240 				{
    241 					// add this to register
    242 					size_t who_len = i;
    243 					vstr_t who = {
    244 						.str = text + i,
    245 						.len = 0
    246 					};
    247 					while (i < text_len) {
    248 						switch (text[i]) {
    249 							case '\n':
    250 							case '\r':
    251 								goto ledger_who_parsed;
    252 								break;
    253 							case '\t':
    254 							case ' ':
    255 								goto ledger_who_parsed;
    256 								break;
    257 							default:
    258 								i++;
    259 								break;
    260 						}
    261 					}
    262 ledger_who_parsed:
    263 					who_len = i - who_len;
    264 					account_add(&rootp, who.str, who_len);
    265 					warningf("\n(%d) Who: %.*s", i, who_len, who.str);
    266 					state = ENTRY_SIGN_DENOM_AMOUNT;
    267 					// add to tags here
    268 				}
    269 				break;
    270 			case ENTRY_SIGN_DENOM_AMOUNT:
    271 				if (*(text + i) == '-' ) {
    272 					// TODO throw already set error
    273 					if (hold_sign >= 0) goto ledger_parse_error_handle;
    274 					state = ENTRY_SIGN;
    275 				} else if (isdigit(*(text + i))) state = ENTRY_AMOUNT;
    276 				else state = ENTRY_DENOM;
    277 				continue;
    278 			case ENTRY_SIGN_AMOUNT:
    279 				if (*(text + i) == '-' ) {
    280 					// TODO throw already set error
    281 					if (hold_sign >= 0) goto ledger_parse_error_handle;
    282 					state = ENTRY_SIGN;
    283 				} else if (isdigit(*(text + i))) state = ENTRY_AMOUNT;
    284 				else goto ledger_parse_error_handle;
    285 				break;
    286 			case ENTRY_SIGN: {
    287 				if (*(text + i) == '-') {
    288 					   i++;
    289 					   // AMOUNT cannot be set before SIGN
    290 					   if (hold_amount != LONG_MAX) goto ledger_parse_error_handle;
    291 					   hold_sign = 1;
    292 					   state = ENTRY_SIGN_DENOM_AMOUNT;
    293 				}
    294 			 } break;
    295 			case ENTRY_DENOM: {
    296 				warningf("  %d: D:", i + 1);
    297 				char *denom = text + i;
    298 				size_t denom_len = 0;
    299 				while (i < text_len &&
    300 						( isalpha(*(text + i))
    301 						 || *(text + i) == '$')) i++;
    302 				denom_len = (text + i) - denom;
    303 				if (hold_amount == LONG_MAX)
    304 					state = hold_sign? ENTRY_AMOUNT: ENTRY_SIGN_AMOUNT;
    305 				else
    306 					state = ENTRY_END;
    307 				warningf(" %.*s(%d)", denom_len, denom, denom_len);
    308 				break;
    309 			}
    310 			case ENTRY_AMOUNT: {
    311 				char _c;
    312 				warningf("  %d A:", i + 1);
    313 				char *amount = text + i;
    314 				size_t amount_len = 0;
    315 				while (i < text_len  &&  (_c = *(text + i)) == '.' || isdigit(_c) || _c == ',') i++;
    316 				amount_len = (text + i) - amount;
    317 				// TODO convert amount to hold_amount integer
    318 				hold_amount = 0;
    319 				state = hold_denom_id == 0? ENTRY_DENOM : ENTRY_END;
    320 				warningf(" %.*s(%d)", amount_len, amount, amount_len);
    321 				}
    322 				break;
    323 			default:
    324 				goto ledger_parse_error_handle;
    325 		}
    326 	}
    327 	warning("read complete\n");
    328 	return;
    329 ledger_parse_error_handle:
    330 	warningf("Parse failed at %ld b:(%d), Expected %s, got '%c'",
    331 			line_no, i, states_str[state], text[i]);
    332 }
    333 Entry** ledger_read_file(const char* filename, time_t date_start, time_t date_end) {
    334 	Entity me = {"Account:Income"};
    335 	// list population, read from
    336 	FILE* file = fopen(filename, "r");
    337 	if (file == NULL) {
    338 		printf("Failed to open file %s\n", filename);
    339 	}
    340 	Entry** new_list = (Entry**)malloc(sizeof(Entry*) * 12);
    341 	for(int i = 0; i < 12; i++) {
    342 		Entry* entry = (Entry*)malloc(sizeof(Entry));
    343 		new_list[i] = entry;
    344 		entry->from = &me;
    345 		entry->to = (Entity*)malloc(sizeof(Entity));
    346 		entry->to->name = (char*)malloc(sizeof(char*) * 20);
    347 		strncpy(entry->to->name, "Man", 3);
    348 	}
    349 	return new_list;
    350 }
    351 
    352 void *module_main(char *data, size_t data_len)
    353 {
    354 	// printf("%s\n", data);
    355 	warning("\n=======| Startality |=======\n");
    356 	ledger_parse_data(data, data_len);
    357 	warning("\n========| Fatality |========\n");
    358 	return NULL;
    359 }