payredu

[WIP] Cross-platform ledger GUI written in c99

commit db705b3345e02c595119980a66b6442469892d32
parent 8b5eb06ff3db5f05ca5bebc5a57da3eb298ee719
Author: Bharatvaj Hemanth <bharatvaj@yahoo.com>
Date: Wed, 26 Mar 2025 07:56:38 +0530

Merge branch 'main' of nonplanar.org:payredu
7 files changed, 151 insertions(+), 54 deletions(-)
M
.gitignore
|
4
++++
M
HACKING
|
67
++++++++++++++++++++++++++++++++++++++++++++++++-------------------
M
Makefile
|
44
+++++++++++++++++++++++++++++++-------------
M
README
|
37
+++++++++++++++++++++++--------------
M
book.c
|
15
+++++++++++----
M
payredu.c
|
36
+++++++++++++++++++++++++++++++++---
M
strn.h
|
2
+-
diff --git a/.gitignore b/.gitignore
@@ -3,6 +3,10 @@ hot
 hotbook
 *.so
 *.o
+*.obj
+*.a
+payredu
+payredu.exe
 tags
 
 tests/*
diff --git a/HACKING b/HACKING
@@ -7,38 +7,65 @@ yyyy/mm/dd <till \n>
   ...\n\n // mark posting on \n\n
 
 
-payredo can be bulit as a standalone library(libpayredo) or can be built as an executable.
+payredu can be bulit as a standalone library(libpayredu) or can be
+built as
+an executable.
 
-	make libpayredo.a
-	make libpayredo.so
-	make payredo
+	make libpayredu.a
+	make libpayredu.so
+	make payredu
 
-You can control what you want to build by invoking it separately. By default builds everything.
+You can control what you want to build by invoking it separately. By
+default
+builds everything.
 
-commit.c
-------
-The original ledger-cli does not do edits, to make the runtime simple but since the database is completely ASCII and unorganized, consequent and programmatic writes creates a stress on the CPU and RAM, making it unsuitable for building touch friendly GUIs or general clients on top of it without comprosing UX.
+Makefile
+--------
+This project strives to use the same Makefile to work on both gmake
+and bmake.
 
-To prevent this, payredo exposes a commit API which can be used by text editors and other frontends to validate added content before saving it to the file.
+If you want to add any ${CC} specific flags, check the part under
+"#if ${CC}"
+in Makefile.
 
-Once you are done with the changes in the text editor or other GUI, you can commit the data to payredo using the APIs
+commit.c
+--------
+The original ledger-cli does not do edits, to make the runtime simple
+but since
+the database is completely ASCII and unorganized, consequent and
+programmatic
+writes creates a stress on the CPU and RAM, making it unsuitable for
+building
+touch friendly GUIs or general clients on top of it without comprosing UX.
+
+To prevent this, payredu exposes a commit API which can be used by
+text editors
+and other frontends to validate added content before saving it to
+the file.
+
+Once you are done with the changes in the text editor or other GUI,
+you can
+commit the data to payredu using the APIs
 
 	int ledger_commit_text(new_text, new_text_len)
 	int ledger_commit_post(timestamp, comment, comment_len, entries**)
 
-The first variant can be used by text editors where the structure of the parsed text is not understood.
+The first variant can be used by text editors where the structure of the
+parsed text is not understood.
 
-The second variant can be used when frontend is a GUI or other UI where the user input is controlled.
+The second variant can be used when frontend is a GUI or other UI
+where the
+user input is controlled.
 
 RETURN VALUE
 ------------
 Both variants return PARSE_OK if suceeded, and -1 on failure.
 
-
-payredo.c
+payredu.c
 ---------
 Similar to ledger-cli
-payredo follows the UNIX style option arguments to make the parsing easy and to combine multiple options
+payredu follows the UNIX style option arguments to make the parsing easy
+and to combine multiple options
 
 
 parse.c

@@ -50,9 +77,13 @@ There are two parsers written for the
 
 The parser is written entirely by hand to reduce dependencies.
 
-The `state` variable at any given time holds the information 'what we are trying to parse'. If the `state` has the value `DATE`, it means we are in a condition where we expect DATE to occur such as when starting the parser or when a posting is parsed.
+The `state` variable at any given time holds the information 'what we are
+trying to parse'. If the `state` has the value `DATE`, it means we are in
+a condition where we expect DATE to occur such as when starting the parser
+or when a posting is parsed.
 
-There aren't as many states as the ledger format itself is quite minimal.  These are currently the states,
+There aren't as many states as the ledger format itself is quite minimal.
+These are currently the states,
 
 DATE
 COMMENT

@@ -60,5 +91,3 @@ ENTRY_WHO
 ENTRY_AMOUNT
 ENTRY_END
 
-
-
diff --git a/Makefile b/Makefile
@@ -1,18 +1,34 @@
-GENERAL_FLAGS=-fPIC
+GENERAL_FLAGS = # -fPIC
+
 GUI_LDFLAGS=-lglfw -lGL
-LDFLAGS:=$(GENERAL_FLAGS)  -lm
-CFLAGS:=$(GENERAL_FLAGS) -O0 -I. -g -Werror #-Wpedantic
-export LD_LIBRARY_PATH=.
 
-.DEFAULT_GOAL=payredu
+CFLAGS:=${GENERAL_FLAGS} -I.
+LDFLAGS:=${GENERAL_FLAGS}
 
-CC=gcc
+# if ${CC}
+${CC}_CFLAGS := -O0 -g -Wpedantic -Werror -lm
+${CC}_out := -o
+${CC}_exe_out := ${${CC}_out}
+# else cl
+cl_CFLAGS := /nologo /WX /W3 /D_CRT_SECURE_NO_WARNINGS
+cl_exe_ext := .exe
+cl_out := /Fo:
+cl_exe_out := /Fe:
 
-payredu: payredu.c libbook.a
-	$(CC) -o $@ $(CFLAGS) $(LDFLAGS) $> -L. -lbook
+# consolidate
+CFLAGS += ${${CC}_CFLAGS}
+out := ${${CC}_out}
 
-.o: .c
-	$(CC) $(CFLAGS) -c $> -o $@
+# gmake hack
+^ ?= ${.ALLSRC}
+
+export LD_LIBRARY_PATH=.
+
+payredu${${CC}_exe_ext}: payredu.c libbook.a
+	${CC} ${${CC}_exe_out}$@ ${CFLAGS} ${LDFLAGS} $^
+
+.c.o:
+	${CC} ${CFLAGS} -c $^ ${out}$@
 
 bal:
 	#ledger -f october-2023.txt bal

@@ -22,10 +38,10 @@ balance: balance.c ledger.h
 hot: hot.c libbook.so
 
 libbook.a: book.o account.o
-	ar cr $@ $>
+	ar cr $@ $^
 
 libbook.so: book.o  account.o
-	$(CC) -shared -Wl,-soname,$@ -o $@ $>
+	${CC} -shared -Wl,-soname,$@ -o $@ $^
 
 refresh:
 	git ls-files | entr sh hot.sh

@@ -35,5 +51,7 @@ format:
 
 include tests/tests.mk
 
+RM?=rm -f
+
 clean:
-	-rm *.so *.o hotbook libbook.a libbook.so $(TESTS)
+	-${RM} payredu payredu.exe *.a *.so *.o *.obj hot hot.exe $(TESTS)
diff --git a/README b/README
@@ -3,43 +3,52 @@ payredu
 பேரேடு
 ======
 
-payredu is a cross-platform frontend to ledger(pta) with emphasis on simplicity. payredu 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. payredu 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.
+NOTE: The quality of the software is beta in the least, it's still
+in development.
 
 To build and run,
 
 	$ make
-	$ ./payredu
+	$ ./payredu -f ledger.dat
 
 Why payredu when ledger-cli exists?
 ------------------------------------
 
-ledger-cli by itself is pretty lightweight but it has a handful of dependencies and features which I don't particularly care about.
+ledger-cli by itself is pretty lightweight but it has a handful of
+dependencies and features which I don't particularly care about.
 
-payredu 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 payredu is usually faster than ledger-cli as 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
 -----
 - Compact as possible
-- Limited regex, ^,$ and *
-- BSD style arguments
-- Native Windows support
+- Useful regex queries (^ $ *)
+- UNIX style arguments
+- Native MSVC support (Windows, ReactOS)
 - csv, emacs export/import
 
 Non-Goals
 ---------
 - Python support
 - Elaborate regex
-- limited REPL
 - XML support
 
-Build
------
+Other Build Targets
+-------------------
+MSVC under Windows,
+
+	C:/payredu> make CC=cl
+
+Experimental GUI with hot reload (Linux only),
 
-	make hot
+	$ make hot
 
-will generate 'hot' which can be used to test the hot-reload functionality with GUI for development.
 This is may be removed in feature to provide only a CUI interface.
diff --git a/book.c b/book.c
@@ -9,7 +9,9 @@
 #include "common.h"
 #include "strn.h"
 
+#ifndef _WIN32
 #include <unistd.h>
+#endif
 
 #define _XOPEN_SOURCE
 #include <time.h>

@@ -121,11 +123,11 @@ ledger_parse_data(char *text, size_t text_len)
 	memset(denom_list, 0, 256);
 	char* denomptr = NULL;
 	setvbuf(stdout, NULL, _IONBF, 0);
+
 	LedgerParseStates state = DATE;
 	size_t line_no = 1;
 	size_t current_column = 1;
 	time_t t = time(NULL);
-	// 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

@@ -140,6 +142,10 @@ ledger_parse_data(char *text, size_t text_len)
 
 	hold_amount = LONG_MAX;
 
+	setvbuf(stdout, NULL, _IONBF, 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
+
 	while (i < text_len) {
 		char c = text[i];
 		/* \n identifies an entry done in ledger */

@@ -193,7 +199,7 @@ 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("%.*s: %ld	", 10, text + i, tn);
+					warningf("%.*s: %ld	", 10, text + i, (long)tn);
 					// date is expected to have the form DD/MM/YYYY (10)
 					i += 10;
 					if (tn == (time_t) - 1) goto ledger_parse_error_handle;

@@ -212,7 +218,7 @@ ledger_parse_data(char *text, size_t text_len)
 						comment.len++;
 					}
 					warningf("Comment: %.*s", comment.len,
-							comment);
+							comment.str);
 					state = ENTRY_START;
 				}
 				break;

@@ -375,7 +381,7 @@ ledger_read_file(const char* filename, time_t date_start, time_t date_end) {
 		entry->from = &me;
 		entry->to = (Entity*)malloc(sizeof(Entity));
 		entry->to->name = (char*)malloc(sizeof(char*) * 20);
-		strcpy(entry->to->name, "Man");
+		strncpy(entry->to->name, "Man", 3);
 	}
 	return new_list;
 }

@@ -387,5 +393,6 @@ void
 	warning("\n=======| Startality |=======\n");
 	ledger_parse_data(data, data_len);
 	warning("\n========| Fatality |========\n");
+	return NULL;
 }
 
diff --git a/payredu.c b/payredu.c
@@ -1,8 +1,11 @@
 #include <stdio.h>
 #include <stdlib.h>
-#include <dlfcn.h>
+// TODO enable this for GUI
+//#include <dlfcn.h>
 #include <signal.h>
+#ifndef _WIN32
 #include <unistd.h>
+#endif
 
 #include <book.h>
 

@@ -11,15 +14,42 @@
 
 int should_exit = 0;
 
-void sig_handle() {
+void
+sig_handle() {
 	printf("Reloaded\n");
 	system("date");
 	should_exit = 1;
 }
 
+typedef void* (*module_main_func)(const char*, size_t);
+
+#define die(...) \
+	do { printf(__VA_ARGS__); \
+	exit(-1); } while(0);
+
 int
 main(int argc, char* argv[]) {
-	FILE* in = fopen("october-2023.txt", "r");
+	FILE* in = NULL;
+	char* file_to_open = NULL;
+	// FIXME this is for debugging, payredu will not open any file by default
+	if (argc == 1) file_to_open = "october-2023.txt";
+	else for (int i = 1; i < argc; i++)
+	if (argv[i][0] == '-' && strlen(argv[i]) == 2) {
+		switch (argv[i][1]) {
+			case 'f':
+				file_to_open = argv[++i];
+				break;
+			default:
+				printf("unknown flag: %s\n", argv[i]);
+				break;
+		}
+	} else die("invalid argument: %s", argv[i]);
+
+	if (file_to_open == NULL)
+		die("require a file to open, pass -f <filename>");
+
+	in = fopen(file_to_open, "r");
+
 	char* data = (char*)malloc(2048 * sizeof(char));
 	size_t data_size = 0;
 	size_t c_read =  0;
diff --git a/strn.h b/strn.h
@@ -3,7 +3,7 @@
 
 int natoi(char* str, size_t len) {
 	int final = 0;
-	int i = 0;
+	size_t i = 0;
 	// ignore leading zeroes
 	while(i < len && str[i] == '0') i++;
 	for(;i < len; i++) {