payredu

[WIP] Cross-platform ledger GUI written in c99

commit 16ed12c0750f417facc9e2b5c2d096240016af4f
parent 4981c3dbef0b5a7e92806ea2c3c37f64098fe5e2
Author: Bharatvaj Hemanth <bharatvaj@yahoo.com>
Date: Mon, 18 Dec 2023 01:45:23 +0530

Use separate book.c and account.c for definitions

Fix Makefile test setup

Rename to payredu

Update README

Update .gitigonre
13 files changed, 321 insertions(+), 202 deletions(-)
M
.gitignore
|
5
+++++
M
CHANGELOG
|
1
+
M
Makefile
|
27
+++++++++++----------------
M
README
|
19
++++++++++---------
A
account.c
|
72
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
M
account.h
|
65
++---------------------------------------------------------------
M
book.c
|
72
++++++++++++++++++++++++++++++++----------------------------------------
M
book.h
|
25
+++++++------------------
D
hotbook.c
|
56
--------------------------------------------------------
A
payredu.c
|
72
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
tests/account_tree.c
|
47
+++++++++++++++++++++++++++++++++++++++++++++++
A
tests/csv_export.c
|
51
+++++++++++++++++++++++++++++++++++++++++++++++++++
A
tests/tests.mk
|
11
+++++++++++
diff --git a/.gitignore b/.gitignore
@@ -4,3 +4,8 @@ hotbook
 *.so
 *.o
 tags
+
+tests/*
+!tests/tests.mk
+!tests/account_tree.c
+!tests/csv_export.c
diff --git a/CHANGELOG b/CHANGELOG
@@ -3,3 +3,4 @@ Dark Ages
 - Starting keeping CHANGELOG file for payredo
 - Basic combined parser/lexer for the ledger format has been
 implemented in book.c
+- Fix Makefile issue with tests/tests.mk
diff --git a/Makefile b/Makefile
@@ -1,13 +1,14 @@
 GENERAL_FLAGS=-fPIC
 LDFLAGS:=$(GENERAL_FLAGS) -lglfw -lGL -lm
 CFLAGS:=$(GENERAL_FLAGS) -O0 -I. -g -Werror #-Wpedantic
+export LD_LIBRARY_PATH=.
 
-.DEFAULT_GOAL=book
+.DEFAULT_GOAL=payredu
 
 CC=gcc
 
-%: %.c account.h
-	$(CC) -o $@ $(CFLAGS) $(LDFLAGS) $<
+%: %.c libbook.so
+	$(CC) -o $@ $(CFLAGS) $(LDFLAGS) libbook.so $<
 
 %.o: %.c
 	$(CC) $(CFLAGS) -c $< -o $@

@@ -15,16 +16,16 @@ CC=gcc
 bal:
 	ledger -f october-2023.txt bal
 
-lib%.so: %.o
-	$(CC) -shared -Wl,-soname,$@ -o $@ $<
-
 balance: balance.c ledger.h
 
 hot: hot.c libbalance.so
 
-libbook.so: book.c book.h
+libbook.a: book.c book.h account.c account.h
+
+libbook.so: book.o  account.o
+	$(CC) -shared -Wl,-soname,$@ -o $@ $^
 
-hotbook: hotbook.c
+payredu: payredu.c libbook.so
 
 refresh:
 	git ls-files | entr sh hot.sh

@@ -32,13 +33,7 @@ refresh:
 format:
 	indent -psl -pal --use-tabs -ts4 -br -brs -ce -cli0 book.c
 
-awk_query = $$(awk '/\/\* $1/{flag=1; next}/$1 \*\//{flag=0}flag' $2.c)
-test_cmd = if [ "$$($< $(call awk_query,TEST_INPUT,$<))" = "$(call awk_query,TEST_OUTPUT,$<)" ]; \
-			   then echo Passed; \
-			   else echo Failed; \
-		   fi
-
-#include tests/tests.mk
+include tests/tests.mk
 
 clean:
-	-rm *.so *.o hotbook libbook.so test/account_tree
+	-rm *.so *.o hotbook libbook.so $(TESTS)
diff --git a/README b/README
@@ -1,24 +1,25 @@
-payeredo
-========
+payredu
+=======
 பேரேடு
 ======
 
-payeredo is a cross-platform frontend to ledger(pta) with emphasis on simplicity. perudu means ledger in Tamil. It is written in c99 and works on top of nuklear making it lightweight and fast.
+payredu is a cross-platform frontend to ledger(pta) with emphasis on simplicity. perudu means ledger in Tamil. It is written in c99 and works on top of nuklear making it lightweight and fast.
 
 NOTE: The quality of the software is beta in the least, it's still in development.
 
-For now the following commands work,
+To build and run,
 
-	$ ./hotbook
+	$ make
+	$ ./payredu
 
-Why payeredo when ledger-cli exists?
+Why payredu when ledger-cli exists?
 ------------------------------------
 
-ledger-cli itself pretty lightweight but it has a handful of dependencies and features which I don't particularly use.
+ledger-cli itself pretty lightweight but it has a handful of dependencies and features which I don't particularly care about.
 
-payeredo follows a very suckless approach to ledger and does NOT provide some of the advanced features ledger provides.
+payredu follows a very suckless approach to ledger and does NOT provide some of the advanced features ledger provides.
 
-It should be noted that payeredo is usually faster than ledger-cli, it does not provide some of the niceties that ledger-cil provides.
+It should be noted that payredu is usually faster than ledger-cli as it does not provide some of the niceties that ledger-cil provides.
 
 Goals
 -----
diff --git a/account.c b/account.c
@@ -0,0 +1,72 @@
+#include <account.h>
+
+#include <stdlib.h>
+
+// TODO make this dynamic
+size_t tree_depth = 4;
+
+
+map_tree_t *account_search(map_tree_t *rootp, char *acc, size_t acc_size)
+{
+	assert(rootp != NULL);
+	// we hit leaf node, return rootp
+	if (rootp->children == NULL) return rootp;
+
+	//  return rootp when the 'acc' matches exactly with rootp->value
+	//  acc: this, rootp->value: this
+	vstr_t *rk = rootp->value;
+	if (rk != NULL && acc_size == rk->len && (strncmp(acc, rk->str, acc_size) == 0)) {
+		return rootp;
+	}
+
+	// search the string in it's children
+	for (size_t i = 0; i < rootp->children_len; i++) {
+		vstr_t *val = rootp->children[i].value;
+		if (val != NULL && acc_size == val->len && (strncmp(acc, val->str, acc_size) == 0)) {
+			return rootp->children + i;
+		}
+	}
+	return NULL;
+}
+
+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_len = 0;
+		(*rootp)->children_cap = records_needed;
+	}
+	map_tree_t* _rootp = *rootp;
+	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);
+			if (current_node == NULL) {
+				// return the previously allocated child
+				current_node = _rootp->children + _rootp->children_len++;
+				// 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, would provide a sane way to free memory
+				vstr_t *vstr = (vstr_t *) malloc(sizeof(vstr_t));
+				vstr->str = acc;
+				vstr->len = j;
+				current_node->value = vstr;
+				//printf("%zu : %zu %d %.*s\n", current_node, vstr, j, j, acc);
+			} else {
+				//printf("Present already= %d %.*s\n", j, j, acc);
+			}
+			if (j != acc_size) {
+				return account_add(&current_node, acc + j,
+						acc_size - j);
+			}
+		}
+		i++;
+	}
+	return -1;
+}
diff --git a/account.h b/account.h
@@ -6,8 +6,6 @@
 
 #include <vstr.h>
 
-size_t tree_depth = 4;
-
 struct map_tree;
 
 struct map_tree {

@@ -27,69 +25,10 @@ typedef struct map_tree map_tree_t;
 
 // TODO handle both rootp,this:is:us case and rootp->children,is:us case
 // Currently only the rootp->value and acc are compared
-map_tree_t *account_search(map_tree_t *rootp, char *acc, size_t acc_size)
-{
-	assert(rootp != NULL);
-	// we hit leaf node, return rootp
-	if (rootp->children == NULL) return rootp;
 
-	//  return rootp when the 'acc' matches exactly with rootp->value
-	//  acc: this, rootp->value: this
-	vstr_t *rk = rootp->value;
-	if (rk != NULL && acc_size == rk->len && (strncmp(acc, rk->str, acc_size) == 0)) {
-		return rootp;
-	}
+map_tree_t *account_search(map_tree_t *rootp, char *acc, size_t acc_size);
 
-	// search the string in it's children
-	for (size_t i = 0; i < rootp->children_len; i++) {
-		vstr_t *val = rootp->children[i].value;
-		if (val != NULL && acc_size == val->len && (strncmp(acc, val->str, acc_size) == 0)) {
-			return rootp->children + i;
-		}
-	}
-	return NULL;
-}
 
-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_len = 0;
-		(*rootp)->children_cap = records_needed;
-	}
-	map_tree_t* _rootp = *rootp;
-	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);
-			if (current_node == NULL) {
-				// return the previously allocated child
-				current_node = _rootp->children + _rootp->children_len++;
-				// 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, would provide a sane way to free memory
-				vstr_t *vstr = (vstr_t *) malloc(sizeof(vstr_t));
-				vstr->str = acc;
-				vstr->len = j;
-				current_node->value = vstr;
-				//printf("%zu : %zu %d %.*s\n", current_node, vstr, j, j, acc);
-			} else {
-				//printf("Present already= %d %.*s\n", j, j, acc);
-			}
-			if (j != acc_size) {
-				return account_add(&current_node, acc + j,
-						acc_size - j);
-			}
-		}
-		i++;
-	}
-	return -1;
-}
+int account_add(map_tree_t **rootp, char *acc, size_t acc_size);
 
 #endif
diff --git a/book.c b/book.c
@@ -27,8 +27,8 @@
 #include <time.h>
 
 #include "vstr.h"
-#include "account.h"
-#include "book.h"
+#include <account.h>
+#include <book.h>
 
 #define BUFFER_SIZE 256
 

@@ -159,30 +159,31 @@ void ledger_parse_data(char *text, size_t text_len)
 			case '\r':
 				line_no++;
 				n_count++;
-				printf("\n%d| ", line_no);
 				switch (state) {
 					// after parsing the amount seq, we set the state to ENTRY_WHO
 					case ENTRY_WHO:
 					case ENTRY_END:
-						warning("----- Entry End Marked -----\n");
 						hold_sign = -1;
 						hold_amount = LONG_MAX;
 						// if entry_count <= 1 throw error
 						if (text[i - 1] == '\n') {
 							state = DATE;
 							// TODO push the entries to stack or somethin
-							warning("----- Posting End Marked -----\n");
+							warning("\n==\n");
 							// state = POSTING_END;
 						} else {
 							state = ENTRY_WHO;
+							warning(",");
 						}
 						break;
 					case COMMENT:
+						state = ENTRY_START;
+						break;
 					case ENTRY_SIGN_DENOM_AMOUNT:
 						state = ENTRY_WHO;
 						break;
 					case ENTRY_DENOM:
-						warningf("%s\n", "denom not found, setting state WHO");
+						//warningf("%s", "denom not found, setting state WHO");
 						state = ENTRY_WHO;
 						break;
 					case ENTRY_AMOUNT:

@@ -205,8 +206,7 @@ void ledger_parse_data(char *text, size_t text_len)
 				if (isdigit(c)) {
 					// try to parse a date
 					time_t tn = ledger_timestamp_from_ledger_date(text + i);
-					warningf("date str: %.*s\n", 10, text + i);
-					warningf("date: %ld\n", tn);
+					warningf("%.*s: %ld	", 10, text + i, tn);
 					// date is expected to have the form DD/MM/YYYY (10)
 					i += 10;
 					if (tn == (time_t) - 1) goto ledger_parse_error_handle;

@@ -226,23 +226,12 @@ void ledger_parse_data(char *text, size_t text_len)
 						comment_len++;
 					}
 					comment.len = comment_len;
-					warningf("Comment: %.*s\n", comment_len,
+					warningf("Comment: %.*s", 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;
-					warningf("i: %ld, Spaces: %d\n", i, wsc);
-					if (wsc < 2) {
-						goto ledger_parse_error_handle;
-					}
-					state = ENTRY_WHO;
+					state = ENTRY_START;
 				}
 				break;
+			case ENTRY_START:
 			case ENTRY_WHO:
 				{
 					// add this to register

@@ -268,9 +257,8 @@ void ledger_parse_data(char *text, size_t text_len)
 					}
 ledger_who_parsed:
 					who_len = i - who_len;
-					warningf("parsed: i=%d\n", i);
 					account_add(&rootp, who.str, who_len);
-					warningf("i=%d, Who: %.*s\n", i, who_len, who);
+					warningf("\n(%d) Who: %.*s", i, who_len, who);
 					state = ENTRY_SIGN_DENOM_AMOUNT;
 					// add to tags here
 				}

@@ -302,7 +290,7 @@ ledger_who_parsed:
 			 } break;
 			case ENTRY_DENOM: {
 				char _c;
-				warningf("denom-i: %d\n", i + 1);
+				warningf("  %d: D:", i + 1);
 				char *denom = text + i;
 				size_t denom_len = 0;
 				while (i < text_len &&

@@ -313,12 +301,12 @@ ledger_who_parsed:
 					state = hold_sign? ENTRY_AMOUNT: ENTRY_SIGN_AMOUNT;
 				else
 					state = ENTRY_END;
-				warningf("%d> len: %d, denom: %.*s\n", i, denom_len, denom_len, denom);
+				warningf(" %.*s(%d)", denom_len, denom, denom_len);
 				break;
 			}
 			case ENTRY_AMOUNT: {
 				char _c;
-				warningf("amount-i: %d\n", i + 1);
+				warningf("  %d A:", i + 1);
 				char *amount = text + i;
 				size_t amount_len = 0;
 				while (i < text_len  &&  (_c = *(text + i)) == '.' || isdigit(_c) || _c == ',') i++;

@@ -326,7 +314,7 @@ ledger_who_parsed:
 				// TODO convert amount to hold_amount integer
 				hold_amount = 0;
 				state = hold_denom_id == 0? ENTRY_DENOM : ENTRY_END;
-				warningf("%d> len: %d, amount: %.*s\n", i, amount_len, amount_len, amount);
+				warningf(" %.*s(%d)", amount_len, amount, amount_len);
 				}
 				break;
 			default:

@@ -339,19 +327,23 @@ ledger_parse_error_handle:
 	warningf("Parse failed at %ld b:(%d), Expected %s, got '%c'",
 			line_no, i, states_str[state], text[i]);
 }
-
-int main(int argc, char* argv[]) {
-	FILE* in = fopen("october-2023.txt", "r");
-	char* data = (char*)malloc(2048 * sizeof(char));
-	size_t data_size = 0;
-	size_t c_read =  0;
-	while((c_read = fread(data + data_size + 0, 1, BUFFER_SIZE, in)) != 0) {
-		data_size += c_read;
+Entry** ledger_read_file(const char* filename, time_t date_start, time_t date_end) {
+	Entity me = {"Account:Income"};
+	// list population, read from
+	FILE* file = fopen(filename, "r");
+	if (file == NULL) {
+		printf("Failed to open file %s\n", filename);
+	}
+	Entry** new_list = (Entry**)malloc(sizeof(Entry*) * 12);
+	for(int i = 0; i < 12; i++) {
+		Entry* entry = (Entry*)malloc(sizeof(Entry));
+		new_list[i] = entry;
+		entry->from = &me;
+		entry->to = (Entity*)malloc(sizeof(Entity));
+		entry->to->name = (char*)malloc(sizeof(char*) * 20);
+		strcpy(entry->to->name, "Man");
 	}
-	if (ferror(in)) fprintf(stderr, "Error reading file\n");
-	fprintf(stdout, "Startig loop\n");
-	ledger_parse_data(data, data_size);
-	return 0;
+	return new_list;
 }
 
 void *module_main(char *data, size_t data_len)
diff --git a/book.h b/book.h
@@ -1,3 +1,6 @@
+#ifndef _LEDGER_BOOK_H
+#define _LEDGER_BOOK_H
+
 #include <string.h>
 #include <stdlib.h>
 

@@ -11,23 +14,9 @@ typedef struct {
 	long long amt;
 } Entry;
 
-Entity me = {"Account:Income"};
 
-Entry** ledger_read_file(const char* filename, time_t date_start, time_t date_end) {
-	// list population, read from
-	FILE* file = fopen(filename, "r");
-	if (file == NULL) {
-		printf("Failed to open file %s\n", filename);
-	}
-	Entry** new_list = (Entry**)malloc(sizeof(Entry*) * 12);
-	for(int i = 0; i < 12; i++) {
-		Entry* entry = (Entry*)malloc(sizeof(Entry));
-		new_list[i] = entry;
-		entry->from = &me;
-		entry->to = (Entity*)malloc(sizeof(Entity));
-		entry->to->name = (char*)malloc(sizeof(char*) * 20);
-		strcpy(entry->to->name, "Man");
-	}
-	return new_list;
-}
+void ledger_parse_data(char *text, size_t text_len);
+
+Entry** ledger_read_file(const char* filename, time_t date_start, time_t date_end);
 
+#endif
diff --git a/hotbook.c b/hotbook.c
@@ -1,56 +0,0 @@
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <dlfcn.h>
-#include <signal.h>
-#include <unistd.h>
-
-#include "common.h"
-
-#define MAX_MEMORY 4064
-#define WINDOW_WIDTH 512
-#define WINDOW_HEIGHT 512
-#define BUFFER_SIZE 256
-
-int should_exit = 0;
-
-void sig_handle() {
-	printf("Reloaded\n");
-	system("date");
-	should_exit = 1;
-}
-
-typedef void* (*module_main_func)(const char*, size_t);
-
-int main(int argc, char* argv[]) {
-	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());
-			fprintf(stderr, "Press return to try again.\n");
-			getchar();
-			module = dlopen("./libbook.so", RTLD_NOW);
-		}
-		module_main_func module_main = dlsym(module, "module_main");
-		FILE* in = fopen("october-2023.txt", "r");
-		char* data = (char*)malloc(2048 * sizeof(char));
-		size_t data_size = 0;
-		size_t c_read =  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/payredu.c b/payredu.c
@@ -0,0 +1,72 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <dlfcn.h>
+#include <signal.h>
+#include <unistd.h>
+
+#include <book.h>
+
+#define MAX_MEMORY 4064
+#define WINDOW_WIDTH 512
+#define WINDOW_HEIGHT 512
+#define BUFFER_SIZE 256
+
+int should_exit = 0;
+
+void sig_handle() {
+	printf("Reloaded\n");
+	system("date");
+	should_exit = 1;
+}
+
+typedef void* (*module_main_func)(const char*, size_t);
+
+int main(int argc, char* argv[]) {
+	FILE* in = fopen("october-2023.txt", "r");
+	char* data = (char*)malloc(2048 * sizeof(char));
+	size_t data_size = 0;
+	size_t c_read =  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");
+	ledger_parse_data(data, data_size);
+	return 0;
+}
+
+
+/*
+int main(int argc, char* argv[]) {
+	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());
+			fprintf(stderr, "Press return to try again.\n");
+			getchar();
+			module = dlopen("./libbook.so", RTLD_NOW);
+		}
+		module_main_func module_main = dlsym(module, "module_main");
+		FILE* in = fopen("october-2023.txt", "r");
+		char* data = (char*)malloc(2048 * sizeof(char));
+		size_t data_size = 0;
+		size_t c_read =  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/tests/account_tree.c b/tests/account_tree.c
@@ -0,0 +1,47 @@
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <account.h>
+
+/* TEST_INPUT
+this:is:a:account
+virtualaccount:
+virtualaccount
+this:is:
+TEST_INPUT */
+
+void account_walk (map_tree_t* rootp)
+{
+	static int tab_acc;
+	if (rootp == NULL) return;
+	for(int i = 0; i < tab_acc; i++) {
+		printf("  ");
+	}
+	vstr_t *val = rootp->value;
+	if (val != NULL) {
+		printf("%.*s\n", val->len, val->str);
+		tab_acc++;
+	}
+	for (int i = 0; i < rootp->children_len; i++) {
+		account_walk(rootp->children + i);
+	}
+	tab_acc--;
+}
+
+
+int main(int argc, char* argv[]) {
+	map_tree_t* account_tree = NULL;
+	for(int i = 1; i < argc; i++) {
+		account_add(&account_tree, argv[i], strlen(argv[i]));
+	}
+	account_walk(account_tree);
+}
+
+/* TEST_OUTPUT
+this:
+  is:
+    a:
+      account
+virtualaccount:
+virtualaccount
+TEST_OUTPUT */
diff --git a/tests/csv_export.c b/tests/csv_export.c
@@ -0,0 +1,51 @@
+#include <stdio.h>
+#include <book.h>
+
+/* TEST_INPUT
+october-2023.txt
+TEST_INPUT */
+
+int main(int argc, char* argv[]) {
+	char* content = (char*)malloc(10000);
+	size_t content_len = 0;
+	//if (argc < 2) {
+	//	fprintf(stderr, "Usage: %s filename.txt\n", argv[0]);
+	//	return 1;
+	//}
+	//FILE* fp = fopen(argv[1], "r");
+	FILE* fp = fopen("october-2023.txt", "r");
+	if (fp == NULL) {
+		printf("Cannot open file\n");
+		goto export_csv_cleanup;
+	}
+	size_t content_read = 0;
+	char* content_ptr = content;
+	while ( (content_read = fread(content_ptr, 1, 1, fp)) != 0) {
+		content_ptr += content_read;
+	}
+	content_len = content_ptr - content;
+
+	printf("%s\n%ld", content, content_len);
+
+	ledger_parse_data(content, content_len);
+
+export_csv_cleanup:
+	if (fp != NULL) fclose(fp);
+	return 0;
+}
+
+/* TEST_OUTPUT
+"2015/10/12","","Exxon","Liabilities:MasterCard","$","-10","",""
+"2015/10/12","","Exxon","Expenses:Auto:Gas","$","10","",""
+"2015/10/12","","Donna's Cake World","Liabilities:Credit","$","-5","",""
+"2015/10/12","","Donna's Cake World","Expenses:Food:Dessert","$","5","",""
+"2024/10/25","","Zaitoon","Liabilities:Credit","$","-5","",""
+"2024/10/25","","Zaitoon","Expenses:Food:Dinner","$","5","",""
+"2024/10/24","","Donna's Cake World","Expenses:Food:Dessert","$","5","",""
+"2024/10/24","","Donna's Cake World","Expenses:Food:Dinner","$","19","",""
+"2024/10/24","","Donna's Cake World","Assets:Cash","$","-24","",""
+"2015/10/12","","Zoho","Income:Salary","$","-10000","",""
+"2015/10/12","","Zoho","Assets:Bank:Checking","$","10000","",""
+"2025/10/20","","Old loan from friend","Assets:Cash","$","200","",""
+"2025/10/20","","Old loan from friend","Friend:Cash","$","-200","",""
+TEST_OUTPUT */
diff --git a/tests/tests.mk b/tests/tests.mk
@@ -0,0 +1,11 @@
+awk_query = $$(awk '/\/\* $1/{flag=1; next}/$1 \*\//{flag=0}flag' $2.c)
+test_cmd = if [ "$$($< $(call awk_query,TEST_INPUT,$<))" = "$(call awk_query,TEST_OUTPUT,$<)" ]; \
+			   then echo Passed; \
+			   else echo Failed; \
+		   fi
+
+%.test: %
+	@printf "Test: $<... "
+	@$(call test_cmd)
+
+test: $(addsuffix .test,$(basename $(wildcard tests/*.c)))