From b5a804d1afaef1969de9c32060808fefb81a91e7 Mon Sep 17 00:00:00 2001
From: Felix Buehler <account@buehler.rocks>
Date: Mon, 10 Feb 2020 18:52:04 +0100
Subject: use format_placeholder for battery

---
 Makefile.am                                        |   1 +
 include/i3status.h                                 |  14 +++
 src/format_placeholders.c                          |  70 +++++++++++
 src/output.c                                       |  34 +++++
 src/print_battery_info.c                           | 139 +++++++++------------
 testcases/020-percentliteral-battery/BAT0_uevent   |   4 +
 .../020-percentliteral-battery/expected_output.txt |   1 +
 testcases/020-percentliteral-battery/i3status.conf |  10 ++
 8 files changed, 192 insertions(+), 81 deletions(-)
 create mode 100644 src/format_placeholders.c
 create mode 100644 testcases/020-percentliteral-battery/BAT0_uevent
 create mode 100644 testcases/020-percentliteral-battery/expected_output.txt
 create mode 100644 testcases/020-percentliteral-battery/i3status.conf

diff --git a/Makefile.am b/Makefile.am
index c2c1c0a..15b8ffb 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -50,6 +50,7 @@ i3status_SOURCES = \
 	i3status.c \
 	src/auto_detect_format.c \
 	src/first_network_device.c \
+	src/format_placeholders.c \
 	src/general.c \
 	src/output.c \
 	src/print_battery_info.c \
diff --git a/include/i3status.h b/include/i3status.h
index 2e314b1..95e6eba 100644
--- a/include/i3status.h
+++ b/include/i3status.h
@@ -198,6 +198,20 @@ char *endcolor() __attribute__((pure));
 void reset_cursor(void);
 void maybe_escape_markup(char *text, char **buffer);
 
+char *rtrim(const char *s);
+char *ltrim(const char *s);
+char *trim(const char *s);
+
+// copied from  i3:libi3/format_placeholders.c
+/* src/format_placeholders.c */
+typedef struct {
+    /* The placeholder to be replaced, e.g., "%title". */
+    char *name;
+    /* The value this placeholder should be replaced with. */
+    char *value;
+} placeholder_t;
+char *format_placeholders(const char *format, placeholder_t *placeholders, int num);
+
 /* src/auto_detect_format.c */
 char *auto_detect_format();
 
diff --git a/src/format_placeholders.c b/src/format_placeholders.c
new file mode 100644
index 0000000..650089d
--- /dev/null
+++ b/src/format_placeholders.c
@@ -0,0 +1,70 @@
+/*
+ * vim:ts=4:sw=4:expandtab
+ *
+ * i3 - an improved dynamic tiling window manager
+ * © 2009 Michael Stapelberg and contributors (see also: LICENSE)
+ *
+ */
+// copied from  i3:libi3/format_placeholders.c
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "i3status.h"
+
+#ifndef CS_STARTS_WITH
+#define CS_STARTS_WITH(string, needle) (strncmp((string), (needle), strlen((needle))) == 0)
+#endif
+
+/*
+ * Replaces occurrences of the defined placeholders in the format string.
+ *
+ */
+char *format_placeholders(const char *format, placeholder_t *placeholders, int num) {
+    if (format == NULL)
+        return NULL;
+
+    /* We have to first iterate over the string to see how much buffer space
+     * we need to allocate. */
+    int buffer_len = strlen(format) + 1;
+    for (const char *walk = format; *walk != '\0'; walk++) {
+        for (int i = 0; i < num; i++) {
+            if (!CS_STARTS_WITH(walk, placeholders[i].name))
+                continue;
+
+            buffer_len = buffer_len - strlen(placeholders[i].name) + strlen(placeholders[i].value);
+            walk += strlen(placeholders[i].name) - 1;
+            break;
+        }
+    }
+
+    /* Now we can parse the format string. */
+    char buffer[buffer_len];
+    char *outwalk = buffer;
+    for (const char *walk = format; *walk != '\0'; walk++) {
+        if (*walk != '%') {
+            *(outwalk++) = *walk;
+            continue;
+        }
+
+        bool matched = false;
+        for (int i = 0; i < num; i++) {
+            if (!CS_STARTS_WITH(walk, placeholders[i].name)) {
+                continue;
+            }
+
+            matched = true;
+            outwalk += sprintf(outwalk, "%s", placeholders[i].value);
+            walk += strlen(placeholders[i].name) - 1;
+            break;
+        }
+
+        if (!matched)
+            *(outwalk++) = *walk;
+    }
+
+    *outwalk = '\0';
+    return sstrdup(buffer);
+}
diff --git a/src/output.c b/src/output.c
index 937fa60..9a18049 100644
--- a/src/output.c
+++ b/src/output.c
@@ -8,6 +8,7 @@
 #include <sys/types.h>
 #include <fcntl.h>
 #include <dirent.h>
+#include <ctype.h>
 
 #include "i3status.h"
 
@@ -121,3 +122,36 @@ void maybe_escape_markup(char *text, char **buffer) {
         }
     }
 }
+
+/*
+ * remove leading spaces
+ */
+char *ltrim(const char *s) {
+    while (isspace(*s))
+        ++s;
+    return sstrdup(s);
+}
+
+/*
+ * remove trailing spaces
+ */
+char *rtrim(const char *s) {
+    char *r = sstrdup(s);
+    if (r != NULL) {
+        char *fr = r + strlen(s) - 1;
+        while ((isspace(*fr) || *fr == 0) && fr >= r)
+            --fr;
+        *++fr = 0;
+    }
+    return r;
+}
+
+/*
+ * remove leading & trailing spaces
+ */
+char *trim(const char *s) {
+    char *r = rtrim(s);
+    char *f = ltrim(r);
+    free(r);
+    return f;
+}
\ No newline at end of file
diff --git a/src/print_battery_info.c b/src/print_battery_info.c
index 1240626..dd3211a 100644
--- a/src/print_battery_info.c
+++ b/src/print_battery_info.c
@@ -10,6 +10,8 @@
 
 #include "i3status.h"
 
+#define STRING_SIZE 10
+
 #if defined(__linux__)
 #include <errno.h>
 #include <glob.h>
@@ -571,7 +573,6 @@ static bool slurp_all_batteries(struct battery_info *batt_info, yajl_gen json_ge
 }
 
 void print_battery_info(yajl_gen json_gen, char *buffer, int number, const char *path, const char *format, const char *format_down, const char *status_chr, const char *status_bat, const char *status_unk, const char *status_full, int low_threshold, char *threshold_type, bool last_full_capacity, const char *format_percentage, bool hide_seconds) {
-    const char *walk;
     char *outwalk = buffer;
     struct battery_info batt_info = {
         .full_design = -1,
@@ -653,94 +654,70 @@ void print_battery_info(yajl_gen json_gen, char *buffer, int number, const char
         }
     }
 
-#define EAT_SPACE_FROM_OUTPUT_IF_NO_OUTPUT()                   \
-    do {                                                       \
-        if (outwalk == prevoutwalk) {                          \
-            if (outwalk > buffer && isspace((int)outwalk[-1])) \
-                outwalk--;                                     \
-            else if (isspace((int)*(walk + 1)))                \
-                walk++;                                        \
-        }                                                      \
-    } while (0)
-
-    for (walk = format; *walk != '\0'; walk++) {
-        char *prevoutwalk = outwalk;
-
-        if (*walk != '%') {
-            *(outwalk++) = *walk;
-
-        } else if (BEGINS_WITH(walk + 1, "status")) {
-            const char *statusstr;
-            switch (batt_info.status) {
-                case CS_CHARGING:
-                    statusstr = status_chr;
-                    break;
-                case CS_DISCHARGING:
-                    statusstr = status_bat;
-                    break;
-                case CS_FULL:
-                    statusstr = status_full;
-                    break;
-                default:
-                    statusstr = status_unk;
-            }
-
-            outwalk += sprintf(outwalk, "%s", statusstr);
-            walk += strlen("status");
+    char string_status[STRING_SIZE];
+    char string_percentage[STRING_SIZE];
+    // following variables are not alwasy set. If they are not set they should be empty.
+    char string_remaining[STRING_SIZE] = "";
+    char string_emptytime[STRING_SIZE] = "";
+    char string_consumption[STRING_SIZE] = "";
 
-        } else if (BEGINS_WITH(walk + 1, "percentage")) {
-            outwalk += sprintf(outwalk, format_percentage, batt_info.percentage_remaining, pct_mark);
-            walk += strlen("percentage");
-
-        } else if (BEGINS_WITH(walk + 1, "remaining")) {
-            if (batt_info.seconds_remaining >= 0) {
-                int seconds, hours, minutes;
-
-                hours = batt_info.seconds_remaining / 3600;
-                seconds = batt_info.seconds_remaining - (hours * 3600);
-                minutes = seconds / 60;
-                seconds -= (minutes * 60);
+    const char *statusstr;
+    switch (batt_info.status) {
+        case CS_CHARGING:
+            statusstr = status_chr;
+            break;
+        case CS_DISCHARGING:
+            statusstr = status_bat;
+            break;
+        case CS_FULL:
+            statusstr = status_full;
+            break;
+        default:
+            statusstr = status_unk;
+    }
+    snprintf(string_status, STRING_SIZE, "%s", statusstr);
+    snprintf(string_percentage, STRING_SIZE, format_percentage, batt_info.percentage_remaining, pct_mark);
+
+    if (batt_info.seconds_remaining >= 0) {
+        int seconds, hours, minutes;
+        hours = batt_info.seconds_remaining / 3600;
+        seconds = batt_info.seconds_remaining - (hours * 3600);
+        minutes = seconds / 60;
+        seconds -= (minutes * 60);
+        if (hide_seconds)
+            snprintf(string_remaining, STRING_SIZE, "%02d:%02d", max(hours, 0), max(minutes, 0));
+        else
+            snprintf(string_remaining, STRING_SIZE, "%02d:%02d:%02d", max(hours, 0), max(minutes, 0), max(seconds, 0));
+    }
 
-                if (hide_seconds)
-                    outwalk += sprintf(outwalk, "%02d:%02d",
-                                       max(hours, 0), max(minutes, 0));
-                else
-                    outwalk += sprintf(outwalk, "%02d:%02d:%02d",
-                                       max(hours, 0), max(minutes, 0), max(seconds, 0));
-            }
-            walk += strlen("remaining");
-            EAT_SPACE_FROM_OUTPUT_IF_NO_OUTPUT();
-
-        } else if (BEGINS_WITH(walk + 1, "emptytime")) {
-            if (batt_info.seconds_remaining >= 0) {
-                time_t empty_time = time(NULL) + batt_info.seconds_remaining;
-                set_timezone(NULL); /* Use local time. */
-                struct tm *empty_tm = localtime(&empty_time);
-
-                if (hide_seconds)
-                    outwalk += sprintf(outwalk, "%02d:%02d",
-                                       max(empty_tm->tm_hour, 0), max(empty_tm->tm_min, 0));
-                else
-                    outwalk += sprintf(outwalk, "%02d:%02d:%02d",
-                                       max(empty_tm->tm_hour, 0), max(empty_tm->tm_min, 0), max(empty_tm->tm_sec, 0));
-            }
-            walk += strlen("emptytime");
-            EAT_SPACE_FROM_OUTPUT_IF_NO_OUTPUT();
+    if (batt_info.seconds_remaining >= 0) {
+        time_t empty_time = time(NULL) + batt_info.seconds_remaining;
+        set_timezone(NULL); /* Use local time. */
+        struct tm *empty_tm = localtime(&empty_time);
+        if (hide_seconds)
+            snprintf(string_emptytime, STRING_SIZE, "%02d:%02d", max(empty_tm->tm_hour, 0), max(empty_tm->tm_min, 0));
+        else
+            snprintf(string_emptytime, STRING_SIZE, "%02d:%02d:%02d", max(empty_tm->tm_hour, 0), max(empty_tm->tm_min, 0), max(empty_tm->tm_sec, 0));
+    }
 
-        } else if (BEGINS_WITH(walk + 1, "consumption")) {
-            if (batt_info.present_rate >= 0)
-                outwalk += sprintf(outwalk, "%1.2fW", batt_info.present_rate / 1e6);
+    if (batt_info.present_rate >= 0)
+        snprintf(string_consumption, STRING_SIZE, "%1.2fW", batt_info.present_rate / 1e6);
 
-            walk += strlen("consumption");
-            EAT_SPACE_FROM_OUTPUT_IF_NO_OUTPUT();
+    placeholder_t placeholders[] = {
+        {.name = "%status", .value = string_status},
+        {.name = "%percentage", .value = string_percentage},
+        {.name = "%remaining", .value = string_remaining},
+        {.name = "%emptytime", .value = string_emptytime},
+        {.name = "%consumption", .value = string_consumption}};
 
-        } else {
-            *(outwalk++) = '%';
-        }
-    }
+    const size_t num = sizeof(placeholders) / sizeof(placeholder_t);
+    char *untrimmed = format_placeholders(format, &placeholders[0], num);
+    buffer = trim(untrimmed);
+    free(untrimmed);
 
     if (colorful_output)
         END_COLOR;
 
     OUTPUT_FULL_TEXT(buffer);
+    free(buffer);
 }
diff --git a/testcases/020-percentliteral-battery/BAT0_uevent b/testcases/020-percentliteral-battery/BAT0_uevent
new file mode 100644
index 0000000..b994324
--- /dev/null
+++ b/testcases/020-percentliteral-battery/BAT0_uevent
@@ -0,0 +1,4 @@
+POWER_SUPPLY_STATUS=Discharging
+POWER_SUPPLY_CURRENT_NOW=1107000
+POWER_SUPPLY_CHARGE_FULL_DESIGN=7800000
+POWER_SUPPLY_CHARGE_NOW=2390000
diff --git a/testcases/020-percentliteral-battery/expected_output.txt b/testcases/020-percentliteral-battery/expected_output.txt
new file mode 100644
index 0000000..a0c0525
--- /dev/null
+++ b/testcases/020-percentliteral-battery/expected_output.txt
@@ -0,0 +1 @@
+I can %haz literal% % ?
diff --git a/testcases/020-percentliteral-battery/i3status.conf b/testcases/020-percentliteral-battery/i3status.conf
new file mode 100644
index 0000000..71a333d
--- /dev/null
+++ b/testcases/020-percentliteral-battery/i3status.conf
@@ -0,0 +1,10 @@
+general {
+	output_format = "none"
+}
+
+order += "battery all"
+
+battery all {
+	format = "I can %haz literal% % ?"
+	path = "testcases/020-percentliteral-battery/BAT%d_uevent"
+}
-- 
cgit v1.2.3