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 }