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