]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
format-table: add tristate field
authorLennart Poettering <lennart@amutable.com>
Wed, 22 Apr 2026 21:43:17 +0000 (23:43 +0200)
committerLennart Poettering <lennart@amutable.com>
Sun, 26 Apr 2026 19:21:49 +0000 (21:21 +0200)
src/shared/format-table.c
src/shared/format-table.h
src/test/test-format-table.c

index 200890d3cdccf99186f05717d88e4e6a74e470f1..432e054d4a70cb034c13facdd99c052fa385ea31 100644 (file)
@@ -87,6 +87,7 @@ typedef struct TableData {
         union {
                 uint8_t data[0];    /* data is generic array */
                 bool boolean;
+                int tristate;
                 usec_t timestamp;
                 usec_t timespan;
                 uint64_t size;
@@ -342,6 +343,7 @@ static size_t table_data_size(TableDataType type, const void *data) {
         case TABLE_PERCENT:
         case TABLE_IFINDEX:
         case TABLE_SIGNAL:
+        case TABLE_TRISTATE:
                 return sizeof(int);
 
         case TABLE_IN_ADDR:
@@ -935,6 +937,7 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) {
                         uint64_t uint64;
                         int percent;
                         int ifindex;
+                        int tristate;
                         bool b;
                         union in_addr_union address;
                         sd_id128_t id128;
@@ -972,6 +975,11 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) {
                         data = &buffer.b;
                         break;
 
+                case TABLE_TRISTATE:
+                        buffer.tristate = va_arg(ap, int);
+                        data = &buffer.tristate;
+                        break;
+
                 case TABLE_TIMESTAMP:
                 case TABLE_TIMESTAMP_UTC:
                 case TABLE_TIMESTAMP_RELATIVE:
@@ -1434,12 +1442,25 @@ static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t
                         return strv_compare(a->strv, b->strv);
 
                 case TABLE_BOOLEAN:
+                case TABLE_BOOLEAN_CHECKMARK:
                         if (!a->boolean && b->boolean)
                                 return -1;
                         if (a->boolean && !b->boolean)
                                 return 1;
                         return 0;
 
+                case TABLE_TRISTATE:
+                        /* NB: we do not use CMP() here, since we want to collapse all negative and all
+                         * positive into one bucket each. */
+                        if ((a->tristate < 0 && b->tristate >= 0) ||
+                            (a->tristate == 0 && b->tristate > 0))
+                                return -1;
+
+                        if ((b->tristate < 0 && a->tristate >= 0) ||
+                            (b->tristate == 0 && a->tristate > 0))
+                                return 1;
+                        return 0;
+
                 case TABLE_TIMESTAMP:
                 case TABLE_TIMESTAMP_UTC:
                 case TABLE_TIMESTAMP_RELATIVE:
@@ -1691,6 +1712,12 @@ static const char* table_data_format(
         case TABLE_BOOLEAN_CHECKMARK:
                 return glyph(d->boolean ? GLYPH_CHECK_MARK : GLYPH_CROSS_MARK);
 
+        case TABLE_TRISTATE:
+                if (d->tristate < 0)
+                        return table_ersatz_string(t);
+
+                return yes_no(d->tristate);
+
         case TABLE_TIMESTAMP:
         case TABLE_TIMESTAMP_UTC:
         case TABLE_TIMESTAMP_RELATIVE:
@@ -2776,6 +2803,12 @@ static int table_data_to_json(TableData *d, sd_json_variant **ret) {
         case TABLE_BOOLEAN:
                 return sd_json_variant_new_boolean(ret, d->boolean);
 
+        case TABLE_TRISTATE:
+                if (d->tristate < 0)
+                        return sd_json_variant_new_null(ret);
+
+                return sd_json_variant_new_boolean(ret, d->tristate);
+
         case TABLE_TIMESTAMP:
         case TABLE_TIMESTAMP_UTC:
         case TABLE_TIMESTAMP_RELATIVE:
index 7665c93e593e61a0b62c8b088c7b04a281bccf00..5b98d4901752420abc2e28b961721d5ab20dd461 100644 (file)
@@ -20,6 +20,7 @@ typedef enum TableDataType {
         TABLE_VERSION,             /* just like TABLE_STRING, but uses version comparison when sorting */
         TABLE_BOOLEAN,
         TABLE_BOOLEAN_CHECKMARK,
+        TABLE_TRISTATE,
         TABLE_TIMESTAMP,
         TABLE_TIMESTAMP_UTC,
         TABLE_TIMESTAMP_RELATIVE,
index 677adaa9c964d28505e8f43838dc55e9509046e2..9aae79e223987304624809dabad5f5edf28d5eb1 100644 (file)
@@ -581,6 +581,72 @@ TEST(table) {
                              "5min              5min              \n");
 }
 
+TEST(tristate) {
+        _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL, *w = NULL;
+        _cleanup_(table_unrefp) Table *t = NULL;
+        _cleanup_free_ char *formatted = NULL;
+
+        ASSERT_NOT_NULL((t = table_new("name", "flag")));
+
+        ASSERT_OK(table_add_many(t,
+                                 TABLE_STRING, "neg",
+                                 TABLE_TRISTATE, -1));
+        ASSERT_OK(table_add_many(t,
+                                 TABLE_STRING, "zero",
+                                 TABLE_TRISTATE, 0));
+        ASSERT_OK(table_add_many(t,
+                                 TABLE_STRING, "pos",
+                                 TABLE_TRISTATE, 1));
+
+        ASSERT_OK(table_format(t, &formatted));
+        printf("%s\n", formatted);
+        ASSERT_STREQ(formatted,
+                     "NAME FLAG\n"
+                     "neg  \n"
+                     "zero no\n"
+                     "pos  yes\n");
+        formatted = mfree(formatted);
+
+        /* Try a non-default ersatz string. */
+        table_set_ersatz_string(t, TABLE_ERSATZ_DASH);
+        ASSERT_OK(table_format(t, &formatted));
+        printf("%s\n", formatted);
+        ASSERT_STREQ(formatted,
+                     "NAME FLAG\n"
+                     "neg  -\n"
+                     "zero no\n"
+                     "pos  yes\n");
+        formatted = mfree(formatted);
+
+        /* Sorting: -1 < 0 < 1 */
+        ASSERT_OK(table_set_sort(t, (size_t) 1, SIZE_MAX));
+        ASSERT_OK(table_format(t, &formatted));
+        printf("%s\n", formatted);
+        ASSERT_STREQ(formatted,
+                     "NAME FLAG\n"
+                     "neg  -\n"
+                     "zero no\n"
+                     "pos  yes\n");
+        formatted = mfree(formatted);
+
+        /* JSON: -1 → null, 0 → false, positive → true */
+        ASSERT_OK(table_to_json(t, &v));
+
+        ASSERT_OK(sd_json_build(&w,
+                                SD_JSON_BUILD_ARRAY(
+                                                SD_JSON_BUILD_OBJECT(
+                                                                SD_JSON_BUILD_PAIR("name", JSON_BUILD_CONST_STRING("neg")),
+                                                                SD_JSON_BUILD_PAIR("flag", SD_JSON_BUILD_NULL)),
+                                                SD_JSON_BUILD_OBJECT(
+                                                                SD_JSON_BUILD_PAIR("name", JSON_BUILD_CONST_STRING("zero")),
+                                                                SD_JSON_BUILD_PAIR_BOOLEAN("flag", false)),
+                                                SD_JSON_BUILD_OBJECT(
+                                                                SD_JSON_BUILD_PAIR("name", JSON_BUILD_CONST_STRING("pos")),
+                                                                SD_JSON_BUILD_PAIR_BOOLEAN("flag", true)))));
+
+        ASSERT_TRUE(sd_json_variant_equal(v, w));
+}
+
 TEST(signed_integers) {
         _cleanup_(table_unrefp) Table *t = NULL;
         _cleanup_free_ char *formatted = NULL;