return 0;
case TABLE_STRING:
+ case TABLE_STRING_WITH_ANSI:
case TABLE_PATH:
case TABLE_PATH_BASENAME:
case TABLE_FIELD:
int r;
assert(t);
- assert(IN_SET(dt, TABLE_STRING, TABLE_PATH, TABLE_PATH_BASENAME, TABLE_FIELD, TABLE_HEADER, TABLE_VERSION));
+ assert(IN_SET(dt, TABLE_STRING, TABLE_STRING_WITH_ANSI, TABLE_PATH, TABLE_PATH_BASENAME, TABLE_FIELD, TABLE_HEADER, TABLE_VERSION));
va_start(ap, format);
r = vasprintf(&buffer, format, ap);
break;
case TABLE_STRING:
+ case TABLE_STRING_WITH_ANSI:
case TABLE_PATH:
case TABLE_PATH_BASENAME:
case TABLE_FIELD:
switch (a->type) {
case TABLE_STRING:
+ case TABLE_STRING_WITH_ANSI:
case TABLE_FIELD:
case TABLE_HEADER:
return strcmp(a->string, b->string);
return buf;
}
-static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercasing, size_t column_width, bool *have_soft) {
+static const char *table_data_format(
+ Table *t,
+ TableData *d,
+ bool avoid_uppercasing,
+ size_t column_width,
+ bool *have_soft) {
+
assert(d);
if (d->formatted &&
return table_ersatz_string(t);
case TABLE_STRING:
+ case TABLE_STRING_WITH_ANSI:
case TABLE_PATH:
case TABLE_PATH_BASENAME:
case TABLE_FIELD:
return d->formatted;
}
+static const char *table_data_format_strip_ansi(
+ Table *t,
+ TableData *d,
+ bool avoid_uppercasing,
+ size_t column_width,
+ bool *have_soft,
+ char **ret_buffer) {
+
+ /* Just like table_data_format() but strips ANSI sequences for ANSI fields. */
+
+ assert(ret_buffer);
+
+ const char *c;
+
+ c = table_data_format(t, d, avoid_uppercasing, column_width, have_soft);
+ if (!c)
+ return NULL;
+
+ if (d->type != TABLE_STRING_WITH_ANSI) {
+ /* Shortcut: we do not consider ANSI sequences for all other column types, hence return the
+ * original string as-is */
+ *ret_buffer = NULL;
+ return c;
+ }
+
+ _cleanup_free_ char *s = strdup(c);
+ if (!s)
+ return NULL;
+
+ if (!strip_tab_ansi(&s, /* isz= */ NULL, /* highlight= */ NULL))
+ return NULL;
+
+ *ret_buffer = TAKE_PTR(s);
+ return *ret_buffer;
+}
+
static int console_width_height(
const char *s,
size_t *ret_width,
size_t *ret_height,
bool *have_soft) {
- _cleanup_free_ char *truncated = NULL;
+ _cleanup_free_ char *truncated = NULL, *buffer = NULL;
bool truncation_applied = false;
size_t width, height;
+ bool soft = false;
const char *t;
int r;
- bool soft = false;
- t = table_data_format(table, d, false, available_width, &soft);
+ t = table_data_format_strip_ansi(
+ table,
+ d,
+ /* avoid_uppercasing= */ false,
+ available_width,
+ &soft,
+ &buffer);
if (!t)
return -ENOMEM;
if (r < 0)
return r;
if (r > 0) { /* Truncated because too many lines? */
- _cleanup_free_ char *last = NULL;
+ _cleanup_free_ char *last = NULL, *buffer = NULL;
const char *field;
/* If we are going to show only the first few lines of a cell that has
* ellipsis. Hence, let's figure out the last line, and account for its
* length plus ellipsis. */
- field = table_data_format(t, d, false,
- width ? width[j] : SIZE_MAX,
- &any_soft);
+ field = table_data_format_strip_ansi(
+ t,
+ d,
+ /* avoid_uppercasing= */ false,
+ width ? width[j] : SIZE_MAX,
+ &any_soft,
+ &buffer);
if (!field)
return -ENOMEM;
more_sublines = false;
for (size_t j = 0; j < display_columns; j++) {
- _cleanup_free_ char *buffer = NULL, *extracted = NULL;
+ _cleanup_free_ char *buffer = NULL, *stripped_ansi_buffer = NULL, *extracted = NULL;
bool lines_truncated = false;
const char *field, *color = NULL, *underline = NULL;
TableData *d;
assert_se(d = row[t->display_map ? t->display_map[j] : j]);
- field = table_data_format(t, d, false, width[j], NULL);
+ if (colors_enabled())
+ field = table_data_format(
+ t,
+ d,
+ /* avoid_uppercasing= */ false,
+ width[j],
+ /* have_soft= */ NULL);
+ else
+ field = table_data_format_strip_ansi(
+ t,
+ d,
+ /* avoid_uppercasing= */ false,
+ width[j],
+ /* have_soft= */ NULL,
+ &stripped_ansi_buffer);
if (!field)
return -ENOMEM;
fputs(field, f);
- if (color || underline)
+ /* Reset color afterwards if colors was set or the string to output contained ANSI sequences. */
+ if (color || underline || (d->type == TABLE_STRING_WITH_ANSI && colors_enabled()))
fputs(ANSI_NORMAL, f);
gap_color = d->rgap_color;
SD_JSON_BUILD_UNSIGNED(major(d->devnum)),
SD_JSON_BUILD_UNSIGNED(minor(d->devnum))));
+ case TABLE_STRING_WITH_ANSI: {
+ _cleanup_free_ char *s = strdup(d->string);
+ if (!s)
+ return -ENOMEM;
+
+ /* We strip the ANSI data when outputting to JSON */
+ if (!strip_tab_ansi(&s, /* isz= */ NULL, /* highlight= */ NULL))
+ return -ENOMEM;
+
+ return sd_json_variant_new_string(ret, s);
+ }
+
default:
return -EINVAL;
}
}
static int table_make_json_field_name(Table *t, TableData *d, char **ret) {
- _cleanup_free_ char *mangled = NULL;
+ _cleanup_free_ char *mangled = NULL, *buffer = NULL;
const char *n;
assert(t);
if (IN_SET(d->type, TABLE_HEADER, TABLE_FIELD))
n = d->string;
else {
- n = table_data_format(t, d, /* avoid_uppercasing= */ true, SIZE_MAX, NULL);
+ n = table_data_format_strip_ansi(
+ t,
+ d,
+ /* avoid_uppercasing= */ true,
+ /* column_width= */ SIZE_MAX,
+ /* have_soft= */ NULL,
+ &buffer);
if (!n)
return -ENOMEM;
}
#include <unistd.h>
#include "alloc-util.h"
+#include "ansi-color.h"
+#include "env-util.h"
#include "format-table.h"
#include "json-util.h"
#include "terminal-util.h"
"2500000000 2.3G 2.5Gbps\n");
}
+TEST(table_ansi) {
+ _cleanup_(table_unrefp) Table *table = NULL;
+
+ ASSERT_NOT_NULL((table = table_new("foo", "bar", "baz", "kkk")));
+
+ ASSERT_OK(table_add_many(table,
+ TABLE_STRING, "hallo",
+ TABLE_STRING_WITH_ANSI, "knuerz" ANSI_HIGHLIGHT_RED "red" ANSI_HIGHLIGHT_GREEN "green",
+ TABLE_STRING_WITH_ANSI, "noansi",
+ TABLE_STRING_WITH_ANSI, ANSI_GREY "thisisgrey"));
+
+ unsigned saved_columns = columns();
+ bool saved_color = colors_enabled();
+ _cleanup_free_ char *saved_term = NULL;
+ const char *e = getenv("TERM");
+ if (e)
+ ASSERT_NOT_NULL((saved_term = strdup(e)));
+
+ ASSERT_OK_ERRNO(setenv("COLUMNS", "200", /* overwrite= */ true));
+ ASSERT_OK_ERRNO(setenv("SYSTEMD_COLORS", "1", /* overwrite= */ true));
+ ASSERT_OK_ERRNO(setenv("TERM", FALLBACK_TERM, /* overwrite= */ true));
+ reset_terminal_feature_caches();
+
+ _cleanup_free_ char *formatted = NULL;
+ ASSERT_OK(table_format(table, &formatted));
+
+ ASSERT_STREQ(formatted,
+ ANSI_ADD_UNDERLINE "FOO " ANSI_NORMAL
+ ANSI_ADD_UNDERLINE " " ANSI_NORMAL
+ ANSI_ADD_UNDERLINE "BAR " ANSI_NORMAL
+ ANSI_ADD_UNDERLINE " " ANSI_NORMAL
+ ANSI_ADD_UNDERLINE "BAZ " ANSI_NORMAL
+ ANSI_ADD_UNDERLINE " " ANSI_NORMAL
+ ANSI_ADD_UNDERLINE "KKK " ANSI_NORMAL "\n"
+ "hallo knuerz" ANSI_HIGHLIGHT_RED "red" ANSI_HIGHLIGHT_GREEN "green" ANSI_NORMAL
+ " noansi" ANSI_NORMAL
+ " " ANSI_GREY "thisisgrey" ANSI_NORMAL "\n");
+
+ /* Validate that color is correctly stripped */
+ ASSERT_OK_ERRNO(setenv("SYSTEMD_COLORS", "0", /* overwrite= */ true));
+ reset_terminal_feature_caches();
+
+ formatted = mfree(formatted);
+ ASSERT_OK(table_format(table, &formatted));
+
+ ASSERT_STREQ(formatted,
+ "FOO BAR BAZ KKK\n"
+ "hallo knuerzredgreen noansi thisisgrey\n");
+
+ ASSERT_OK(table_print(table, /* f= */ NULL));
+
+ _cleanup_(sd_json_variant_unrefp) sd_json_variant *j = NULL, *jj = NULL;
+
+ ASSERT_OK(table_to_json(table, &j));
+
+ ASSERT_OK(sd_json_build(&jj,
+ SD_JSON_BUILD_ARRAY(
+ SD_JSON_BUILD_OBJECT(
+ SD_JSON_BUILD_PAIR_STRING("foo", "hallo"),
+ SD_JSON_BUILD_PAIR_STRING("bar", "knuerzredgreen"),
+ SD_JSON_BUILD_PAIR_STRING("baz", "noansi"),
+ SD_JSON_BUILD_PAIR_STRING("kkk", "thisisgrey")))));
+ ASSERT_TRUE(sd_json_variant_equal(j, jj));
+
+ ASSERT_OK(sd_json_variant_dump(j, SD_JSON_FORMAT_COLOR_AUTO|SD_JSON_FORMAT_PRETTY_AUTO, /* f= */ NULL, /* prefix= */ NULL));
+
+ ASSERT_OK(setenvf("COLUMNS", /* overwrite= */ true, "%u", saved_columns));
+ ASSERT_OK(setenvf("SYSTEMD_COLORS", /* overwrite= */ true, "%i", saved_color));
+ ASSERT_OK(set_unset_env("TERM", saved_term, /* overwrite= */ true));
+}
+
static int intro(void) {
- ASSERT_OK(setenv("SYSTEMD_COLORS", "0", 1));
- ASSERT_OK(setenv("COLUMNS", "40", 1));
+ ASSERT_OK_ERRNO(setenv("SYSTEMD_COLORS", "0", /* overwrite= */ true));
+ ASSERT_OK_ERRNO(setenv("COLUMNS", "40", /* overwrite= */ true));
return EXIT_SUCCESS;
}