From 4b1dd8e66b62b110ad075d36fceea16a83e4b137 Mon Sep 17 00:00:00 2001
From: Daniel Ledda <ledda@cip.ifi.lmu.de>
Date: Sun, 2 Feb 2025 20:20:25 +0100
Subject: [PATCH] feat: add ansi colors

---
 app.cpp         | 24 +++++++++++++++++++++---
 djstdlib/core.h | 46 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 67 insertions(+), 3 deletions(-)

diff --git a/app.cpp b/app.cpp
index 915e93e..864eb56 100644
--- a/app.cpp
+++ b/app.cpp
@@ -143,7 +143,7 @@ int gymTrackerStatus(Arena *arena, list<string> args) {
         GymLogDbParsed *db = parseDb(arena, os_readEntireFile(arena, DB_FILE_LOCATION));
 
         Timestamp startTs = {0};
-        ParsePositiveIntResult numDays = {1, false};
+        ParsePositiveIntResult numDays = {1, true};
         bool showAll = args.length == 1 && strEql(args.data[0], "--all"_s);
         if (!showAll) {
             if (args.length == 2 && (strEql(args.data[0], "--days"_s) || strEql(args.data[0], "-d"_s))) {
@@ -169,6 +169,7 @@ int gymTrackerStatus(Arena *arena, list<string> args) {
             list<string> nameByExercise = PushFullListZero(arena, string, db->header.nextId);
 
             list<real32> workPerExerciseByDay = PushFullListZero(arena, real32, db->header.nextId);
+            list<real32> workPerExerciseByPrevDay = PushFullListZero(arena, real32, db->header.nextId);
             list<uint32> restPerExerciseByDay = PushFullListZero(arena, uint32, db->header.nextId);
             list<uint32> lastTsPerExerciseByDay = PushFullListZero(arena, uint32, db->header.nextId);
 
@@ -192,7 +193,7 @@ int gymTrackerStatus(Arena *arena, list<string> args) {
                     if (dayCount > 0) {
                         log("\n");
                     }
-                    log("================ %S =================================\n", formatTimeYmd(arena, &timestamp));
+                    log("================ %S ===================\n", formatTimeYmd(arena, &timestamp));
                     lastDay = timestamp.tm_yday;
                     lastYear = timestamp.tm_year;
                     dayCount++;
@@ -245,12 +246,29 @@ int gymTrackerStatus(Arena *arena, list<string> args) {
                     log("Work summary:\n");
                     for (size_t j = 0; j < workPerExerciseByDay.length; j++) {
                         if (workPerExerciseByDay.data[j] != 0.0f) {
-                            log("%S: %.2fkg in %.2fmin\n", nameByExercise.data[j], workPerExerciseByDay.data[j], (real32)restPerExerciseByDay.data[j] / 60.0f);
+                            const char *fmtString;
+                            real32 improvement = 0;
+                            if (workPerExerciseByPrevDay.data[j] == 0) {
+                                fmtString = COLOR_TEXT("%S", ANSI_fg_cyan) ": %.2fkg in %.2fmin\n";
+                            } else {
+                                improvement = workPerExerciseByDay.data[j] - workPerExerciseByPrevDay.data[j];
+                                if (improvement > 0) {
+                                    fmtString = COLOR_TEXT("%S", ANSI_fg_cyan) ": %.2fkg in %.2fmin " COLOR_TEXT("+%.2fkg\n", ANSI_fg_green);
+                                } else {
+                                    fmtString = COLOR_TEXT("%S", ANSI_fg_cyan) ": %.2fkg in %.2fmin " COLOR_TEXT("%.2fkg\n", ANSI_fg_red);
+                                }
+                            }
+
+                            log(fmtString, nameByExercise.data[j], workPerExerciseByDay.data[j], (real32)restPerExerciseByDay.data[j] / 60.0f, improvement);
+
+                            workPerExerciseByPrevDay.data[j]  = workPerExerciseByDay.data[j];
                         }
                     }
                     zeroListFull(&workPerExerciseByDay);
                     zeroListFull(&restPerExerciseByDay);
                     zeroListFull(&lastTsPerExerciseByDay);
+                    prevEntry = 0;
+                    entry = 0;
                 }
             }
         }
diff --git a/djstdlib/core.h b/djstdlib/core.h
index 7a82d5a..257cb65 100644
--- a/djstdlib/core.h
+++ b/djstdlib/core.h
@@ -208,6 +208,52 @@ enum LogTarget {
     LogTarget_count,
 };
 
+#define ANSI_INSTRUCTION_FROM_ENUM(ansiCodeEnum) ANSI_INSTRUCTION(ansiCodeEnum)
+#define ANSI_INSTRUCTION(ansiCode) "\u001b[" #ansiCode "m"
+#define ANSI_INSTRUCTION_STR(ansiCodeStr) "\u001b[" ansiCodeStr "m"
+#define ANSI_RESET ANSI_INSTRUCTION(0)
+
+#define ANSI_fg_black 30
+#define ANSI_fg_red 31
+#define ANSI_fg_green 32
+#define ANSI_fg_yellow 33
+#define ANSI_fg_blue 34
+#define ANSI_fg_magenta 35
+#define ANSI_fg_cyan 36
+#define ANSI_fg_white 37
+
+#define ANSI_fg_bblack 90
+#define ANSI_fg_bred 91
+#define ANSI_fg_bgreen 92
+#define ANSI_fg_byellow 93
+#define ANSI_fg_bblue 94
+#define ANSI_fg_bmagenta 95
+#define ANSI_fg_bcyan 96
+#define ANSI_fg_bwhite 97
+
+#define ANSI_bg_black 40
+#define ANSI_bg_red 41
+#define ANSI_bg_green 42
+#define ANSI_bg_yellow 43
+#define ANSI_bg_blue 44
+#define ANSI_bg_magenta 45
+#define ANSI_bg_cyan 46
+#define ANSI_bg_white 47
+
+#define ANSI_bg_bblack 100
+#define ANSI_bg_bred 101
+#define ANSI_bg_bgreen 102
+#define ANSI_bg_byellow 103
+#define ANSI_bg_bblue 104
+#define ANSI_bg_bmagenta 105
+#define ANSI_bg_bcyan 106
+#define ANSI_bg_bwhite 107
+
+#define COLOR_TEXT(text, foregroundcolor) ANSI_INSTRUCTION_FROM_ENUM(foregroundcolor) text ANSI_RESET
+#define COLOR_TEXT_BG(text, backgroundcolor) ANSI_INSTRUCTION_FROM_ENUM(backgroundcolor) text ANSI_RESET
+#define COLOR_TEXT_FG_BG(text, foregroundcolor, backgroundcolor) ANSI_INSTRUCTION_FROM_ENUM(foregroundcolor) ANSI_INSTRUCTION_FROM_ENUM(backgroundcolor) text  ANSI_RESET
+#define COLOR_TEXT_RGB(text, red, green, blue) ANSI_INSTRUCTION_STR("38;2;" #red ";" #green ";" #blue) text ANSI_RESET
+
 void log(list<int> l, LogTarget target = LogTarget_stdout);
 void log(list<string> l, LogTarget target = LogTarget_stdout);
 void log(const char *fmt, ...);