1
0
mirror of https://github.com/systemd/systemd synced 2026-03-16 10:04:47 +01:00

Compare commits

..

7 Commits

Author SHA1 Message Date
Daan De Meyer
b61aeeb315 meson: Drop minimum meson version required for clang-tidy 2026-01-22 13:33:35 +09:00
Lennart Poettering
0910cb9324
os-release: add a new FANCY_NAME= field to /etc/os-release, similar to PRETTY_NAME, that may carry ansi sequences + more unicode chars (#40367)
It's sometimes useful include non-ascii unicode chars in an os name, and
give it some ansi coloring. Since we usualy don't want to show that,
introduce a new field for it, and show it at boot and in thostnamectl
only, with safe fallbacks if colors/emojis are not available.
2026-01-21 23:57:39 +01:00
Lennart Poettering
1dab8472e0 hostnamectl: show fancy name if available 2026-01-21 21:32:49 +01:00
Lennart Poettering
955446d2e4 hostnamed: expose fancy OS name as a field 2026-01-21 21:32:49 +01:00
Lennart Poettering
d6749f35ae pid1: show fancy name field at boot, if specified 2026-01-21 21:32:49 +01:00
Lennart Poettering
a01cd5bed3 man: introduce FANCY_NAME field 2026-01-21 21:32:49 +01:00
Lennart Poettering
f42dc5ea18 format-table: add new string cell type that accepts ANSI sequences
For various usecases it's useful that we can embed ANSI sequences in
cells of tables. For example, I hope we can eventually switch "systemctl
status" output to use the table formatter, and multiple of its fields
contain ANSI sequences (since they pack multiple different pieces
information into the same field, and highlight parts of it to
communicate relevance of distinct parts).

Add a distinct cell type for this, which gets special processing when we
output to a terminal that doesn't support ANSI sequences, and to JSON:
we strip the sequences.
2026-01-21 21:32:49 +01:00
10 changed files with 322 additions and 38 deletions

View File

@ -80,6 +80,8 @@ node /org/freedesktop/hostname1 {
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s OperatingSystemPrettyName = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s OperatingSystemFancyName = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly s OperatingSystemCPEName = '...';
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly t OperatingSystemSupportEnd = ...;
@ -170,6 +172,8 @@ node /org/freedesktop/hostname1 {
<variablelist class="dbus-property" generated="True" extra-ref="OperatingSystemPrettyName"/>
<variablelist class="dbus-property" generated="True" extra-ref="OperatingSystemFancyName"/>
<variablelist class="dbus-property" generated="True" extra-ref="OperatingSystemCPEName"/>
<variablelist class="dbus-property" generated="True" extra-ref="OperatingSystemSupportEnd"/>
@ -285,9 +289,11 @@ node /org/freedesktop/hostname1 {
<para><varname>KernelName</varname>, <varname>KernelRelease</varname>, and
<varname>KernelVersion</varname> expose the kernel name (e.g. <literal>Linux</literal>), release
(e.g. <literal>5.0.0-11</literal>), and version (i.e. the build number, e.g. <literal>#11</literal>) as
reported by <citerefentry project="man-pages"><refentrytitle>uname</refentrytitle><manvolnum>2</manvolnum></citerefentry>.
<varname>OperatingSystemPrettyName</varname>, <varname>OperatingSystemCPEName</varname>, and
<varname>HomeURL</varname> expose the <varname>PRETTY_NAME=</varname>, <varname>CPE_NAME=</varname> and
reported by <citerefentry project="man-pages"><refentrytitle>uname</refentrytitle><manvolnum>2</manvolnum></citerefentry>.</para>
<para><varname>OperatingSystemPrettyName</varname>, <varname>OperatingSystemFancyName</varname>,
<varname>OperatingSystemCPEName</varname>, and <varname>HomeURL</varname> expose the
<varname>PRETTY_NAME=</varname>, <varname>FANCY_NAME=</varname>, <varname>CPE_NAME=</varname> and
<varname>HOME_URL=</varname> fields from
<citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry>. The
purpose of those properties is to allow remote clients to access this information over D-Bus. Local
@ -487,6 +493,7 @@ node /org/freedesktop/hostname1 {
<para><varname>ChassisAssetTag</varname>, <varname>OperatingSystemImageID</varname>,
<varname>OperatingSystemImageVersion</varname>, <varname>HardwareSKU</varname>, and
<varname>HardwareVersion</varname> were added in version 258.</para>
<para><varname>OperatingSystemFancyName</varname> was added in version 260.</para>
</refsect2>
</refsect1>

View File

@ -191,9 +191,34 @@
<listitem><para>A pretty operating system name in a format suitable for presentation to the
user. May or may not contain a release code name or OS version of some kind, as suitable. If not
set, a default of <literal>PRETTY_NAME="Linux"</literal> may be used</para>
set, a default of <literal>PRETTY_NAME="Linux"</literal> may be used.</para>
<para>Example: <literal>PRETTY_NAME="Fedora 17 (Beefy Miracle)"</literal>.</para></listitem>
<para>Example: <literal>PRETTY_NAME="Fedora 17 (Beefy Miracle)"</literal></para></listitem>
</varlistentry>
<varlistentry>
<term><varname>FANCY_NAME=</varname></term>
<listitem><para>Similar to <varname>PRETTY_NAME=</varname>, but may contain ANSI sequences and
fancy UTF-8 characters such as emojis. If defined this shall preferably used when displaying the OS
name on modern terminal emulators. If the terminal emulator does not support emojis,
<varname>PRETTY_NAME=</varname> shall be shown instead, possibly with
<varname>ANSI_COLOR=</varname> coloring. Use <literal>\033</literal> to encode the ESC
character and <literal>\\</literal> to encode the backslash character.</para>
<!-- NB: the code actually does a full blown C style unescaping, but we are not going to mention
this here: people may add ANSI sequences, but maybe not the full expressivity of ASCII control
characters. -->
<para>Unlike <varname>PRETTY_NAME=</varname> this field must not contain information that is
present in other fields, in particular the version (already specified in
<varname>VERSION=</varname>) or the codename (already specified in
<varname>VERSION_CODENAME=</varname>).</para>
<para>Example:
<literal>FANCY_NAME="🍅 \033[31mTomato\033[0;1mOS\033[0m"</literal></para>
<xi:include href="version-info.xml" xpointer="v260"/></listitem>
</varlistentry>
<varlistentry>

View File

@ -2911,7 +2911,7 @@ endif
alias_target('gensources', generated_sources)
clang_tidy = find_program('clang-tidy', required : false)
if meson.version().version_compare('>=1.10.0')
if meson.version().version_compare('>=1.4.0')
uniq = {}
foreach source : sources
@ -2921,7 +2921,11 @@ if meson.version().version_compare('>=1.10.0')
uniq += {source.full_path(): source}
endforeach
sources = uniq.values()
# TODO: Use uniq.values() when we can rely on meson 1.10.0.
sources = []
foreach path, file : uniq
sources += [file]
endforeach
foreach source : sources
if systemd_headers.contains(source)

View File

@ -102,6 +102,7 @@
#include "umask-util.h"
#include "unit-name.h"
#include "user-util.h"
#include "utf8.h"
#include "version.h"
#include "virt.h"
#include "watchdog.h"
@ -1389,14 +1390,16 @@ static int enforce_syscall_archs(Set *archs) {
}
static int os_release_status(void) {
_cleanup_free_ char *pretty_name = NULL, *name = NULL, *version = NULL,
*ansi_color = NULL, *support_end = NULL;
_cleanup_free_ char *pretty_name = NULL, *fancy_name = NULL,
*name = NULL, *version = NULL, *ansi_color = NULL, *support_end = NULL, *codename = NULL;
int r;
r = parse_os_release(NULL,
"PRETTY_NAME", &pretty_name,
"FANCY_NAME", &fancy_name,
"NAME", &name,
"VERSION", &version,
"VERSION_CODENAME", &codename,
"ANSI_COLOR", &ansi_color,
"SUPPORT_END", &support_end);
if (r < 0)
@ -1404,22 +1407,59 @@ static int os_release_status(void) {
"Failed to read os-release file, ignoring: %m");
const char *label = os_release_pretty_name(pretty_name, name);
const char *color = empty_to_null(ansi_color) ?: "1";
if (show_status_on(arg_show_status)) {
const char *color = empty_to_null(ansi_color) ?: "1";
/* The fancy name may contain emoji characters and ANSI sequences. Don't use it if our locale
* doesn't allow that, or ANSI sequences are off, or if it is empty. */
if (!isempty(fancy_name)) {
_cleanup_free_ char *unescaped = NULL;
/* Undo one level of C-style unescaping for this one */
ssize_t l = cunescape(fancy_name, /* flags= */ 0, &unescaped);
if (l < 0) {
log_debug_errno(l, "Failed to unescape FANCY_NAME=, ignoring: %m");
fancy_name = mfree(fancy_name);
} else {
free_and_replace(fancy_name, unescaped);
/* FANCY_NAME= does not contain version/codename info (unlike PRETTY_NAME=),
* but in this context it makes sense to show them if defined, hence append
* them here. */
if (version && !strextend(&fancy_name, " ", version))
return log_oom();
if (codename && !strextend(&fancy_name, " (", codename, ")"))
return log_oom();
}
}
if (isempty(fancy_name) ||
(!emoji_enabled() && !ascii_is_valid(fancy_name)) ||
!log_get_show_color())
fancy_name = mfree(fancy_name);
if (!fancy_name && log_get_show_color()) {
fancy_name = strjoin("\x1B[", color, "m", label);
if (!fancy_name)
return log_oom();
}
if (in_initrd()) {
if (log_get_show_color())
status_printf(NULL, 0,
ANSI_HIGHLIGHT "Booting initrd of " ANSI_NORMAL "\x1B[%sm%s" ANSI_NORMAL ANSI_HIGHLIGHT "." ANSI_NORMAL,
color, label);
ANSI_HIGHLIGHT "Booting initrd of " ANSI_NORMAL "%s" ANSI_NORMAL ANSI_HIGHLIGHT "." ANSI_NORMAL,
fancy_name);
else
status_printf(NULL, 0,
"Booting initrd of %s...", label);
} else {
if (log_get_show_color())
status_printf(NULL, 0,
"\n" ANSI_HIGHLIGHT "Welcome to " ANSI_NORMAL "\x1B[%sm%s" ANSI_NORMAL ANSI_HIGHLIGHT "!" ANSI_NORMAL "\n",
color, label);
"\n" ANSI_HIGHLIGHT "Welcome to " ANSI_NORMAL "%s" ANSI_NORMAL ANSI_HIGHLIGHT "!" ANSI_NORMAL "\n",
fancy_name);
else
status_printf(NULL, 0,
"\nWelcome to %s!\n",

View File

@ -29,6 +29,7 @@
#include "runtime-scope.h"
#include "string-util.h"
#include "time-util.h"
#include "utf8.h"
#include "verbs.h"
static bool arg_ask_password = true;
@ -51,6 +52,7 @@ typedef struct StatusInfo {
const char *kernel_name;
const char *kernel_release;
const char *os_pretty_name;
const char *os_fancy_name;
const char *os_cpe_name;
usec_t os_support_end;
const char *os_image_id;
@ -234,7 +236,14 @@ static int print_status_info(StatusInfo *i) {
return table_log_add_error(r);
}
if (!isempty(i->os_pretty_name)) {
if (!isempty(i->os_fancy_name) && (emoji_enabled() || ascii_is_valid(i->os_fancy_name)) && colors_enabled()) {
r = table_add_many(table,
TABLE_FIELD, "Operating System",
TABLE_STRING_WITH_ANSI, i->os_fancy_name,
TABLE_SET_URL, i->home_url);
if (r < 0)
return table_log_add_error(r);
} else if (!isempty(i->os_pretty_name)) {
r = table_add_many(table,
TABLE_FIELD, "Operating System",
TABLE_STRING, i->os_pretty_name,
@ -425,6 +434,7 @@ static int show_all_names(sd_bus *bus) {
{ "KernelName", "s", NULL, offsetof(StatusInfo, kernel_name) },
{ "KernelRelease", "s", NULL, offsetof(StatusInfo, kernel_release) },
{ "OperatingSystemPrettyName", "s", NULL, offsetof(StatusInfo, os_pretty_name) },
{ "OperatingSystemFancyName", "s", NULL, offsetof(StatusInfo, os_fancy_name) },
{ "OperatingSystemCPEName", "s", NULL, offsetof(StatusInfo, os_cpe_name) },
{ "OperatingSystemSupportEnd", "t", NULL, offsetof(StatusInfo, os_support_end) },
{ "OperatingSystemImageID", "s", NULL, offsetof(StatusInfo, os_image_id) },

View File

@ -22,6 +22,7 @@
#include "device-private.h"
#include "env-file.h"
#include "env-util.h"
#include "escape.h"
#include "extract-word.h"
#include "fileio.h"
#include "hashmap.h"
@ -75,6 +76,7 @@ typedef enum {
PROP_OS_SUPPORT_END,
PROP_OS_IMAGE_ID,
PROP_OS_IMAGE_VERSION,
PROP_OS_FANCY_NAME,
_PROP_MAX,
_PROP_INVALID = -EINVAL,
} HostProperty;
@ -192,7 +194,7 @@ static void context_read_machine_info(Context *c) {
}
static void context_read_os_release(Context *c) {
_cleanup_free_ char *os_name = NULL, *os_pretty_name = NULL;
_cleanup_free_ char *os_name = NULL, *os_pretty_name = NULL, *os_fancy_name = NULL, *os_ansi_color = NULL;
struct stat current_stat = {};
int r;
@ -209,10 +211,13 @@ static void context_read_os_release(Context *c) {
(UINT64_C(1) << PROP_OS_HOME_URL) |
(UINT64_C(1) << PROP_OS_SUPPORT_END) |
(UINT64_C(1) << PROP_OS_IMAGE_ID) |
(UINT64_C(1) << PROP_OS_IMAGE_VERSION));
(UINT64_C(1) << PROP_OS_IMAGE_VERSION) |
(UINT64_C(1) << PROP_OS_FANCY_NAME));
r = parse_os_release(NULL,
"PRETTY_NAME", &os_pretty_name,
"FANCY_NAME", &os_fancy_name,
"ANSI_COLOR", &os_ansi_color,
"NAME", &os_name,
"CPE_NAME", &c->data[PROP_OS_CPE_NAME],
"HOME_URL", &c->data[PROP_OS_HOME_URL],
@ -225,6 +230,31 @@ static void context_read_os_release(Context *c) {
if (free_and_strdup(&c->data[PROP_OS_PRETTY_NAME], os_release_pretty_name(os_pretty_name, os_name)) < 0)
log_oom();
if (!isempty(os_fancy_name)) {
_cleanup_free_ char *unescaped = NULL;
/* We undo one level of C escapes on this */
ssize_t l = cunescape(os_fancy_name, /* flags= */ 0, &unescaped);
if (l < 0) {
log_warning_errno(l, "Failed to unescape fancy OS name, ignoring: %m");
os_fancy_name = mfree(os_fancy_name);
} else
free_and_replace(os_fancy_name, unescaped);
}
if (isempty(os_fancy_name)) {
free(os_fancy_name); /* free if empty string */
if (isempty(os_ansi_color))
os_fancy_name = strdup(c->data[PROP_OS_PRETTY_NAME]);
else
os_fancy_name = strjoin("\x1B[", os_ansi_color, "m", c->data[PROP_OS_PRETTY_NAME]);
if (!os_fancy_name)
log_oom();
}
free_and_replace(c->data[PROP_OS_FANCY_NAME], os_fancy_name);
c->etc_os_release_stat = current_stat;
}
@ -1705,6 +1735,7 @@ static int build_describe_response(Context *c, bool privileged, sd_json_variant
SD_JSON_BUILD_PAIR_STRING("KernelRelease", u.release),
SD_JSON_BUILD_PAIR_STRING("KernelVersion", u.version),
SD_JSON_BUILD_PAIR_STRING("OperatingSystemPrettyName", c->data[PROP_OS_PRETTY_NAME]),
SD_JSON_BUILD_PAIR_STRING("OperatingSystemFancyName", c->data[PROP_OS_FANCY_NAME]),
SD_JSON_BUILD_PAIR_STRING("OperatingSystemCPEName", c->data[PROP_OS_CPE_NAME]),
SD_JSON_BUILD_PAIR_STRING("OperatingSystemHomeURL", c->data[PROP_OS_HOME_URL]),
JSON_BUILD_PAIR_FINITE_USEC("OperatingSystemSupportEnd", eol),
@ -1781,6 +1812,7 @@ static const sd_bus_vtable hostname_vtable[] = {
SD_BUS_PROPERTY("KernelRelease", "s", property_get_uname_field, offsetof(struct utsname, release), SD_BUS_VTABLE_ABSOLUTE_OFFSET|SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("KernelVersion", "s", property_get_uname_field, offsetof(struct utsname, version), SD_BUS_VTABLE_ABSOLUTE_OFFSET|SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("OperatingSystemPrettyName", "s", property_get_os_release_field, offsetof(Context, data[PROP_OS_PRETTY_NAME]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("OperatingSystemFancyName", "s", property_get_os_release_field, offsetof(Context, data[PROP_OS_FANCY_NAME]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("OperatingSystemCPEName", "s", property_get_os_release_field, offsetof(Context, data[PROP_OS_CPE_NAME]), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("OperatingSystemSupportEnd", "t", property_get_os_support_end, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("HomeURL", "s", property_get_os_release_field, offsetof(Context, data[PROP_OS_HOME_URL]), SD_BUS_VTABLE_PROPERTY_CONST),

View File

@ -283,6 +283,7 @@ static size_t table_data_size(TableDataType type, const void *data) {
return 0;
case TABLE_STRING:
case TABLE_STRING_WITH_ANSI:
case TABLE_PATH:
case TABLE_PATH_BASENAME:
case TABLE_FIELD:
@ -529,7 +530,7 @@ int table_add_cell_stringf_full(Table *t, TableCell **ret_cell, TableDataType dt
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);
@ -933,6 +934,7 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) {
break;
case TABLE_STRING:
case TABLE_STRING_WITH_ANSI:
case TABLE_PATH:
case TABLE_PATH_BASENAME:
case TABLE_FIELD:
@ -1393,6 +1395,7 @@ static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t
switch (a->type) {
case TABLE_STRING:
case TABLE_STRING_WITH_ANSI:
case TABLE_FIELD:
case TABLE_HEADER:
return strcmp(a->string, b->string);
@ -1574,7 +1577,13 @@ static char* format_strv_width(char **strv, size_t column_width) {
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 &&
@ -1587,6 +1596,7 @@ static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercas
return table_ersatz_string(t);
case TABLE_STRING:
case TABLE_STRING_WITH_ANSI:
case TABLE_PATH:
case TABLE_PATH_BASENAME:
case TABLE_FIELD:
@ -2068,6 +2078,42 @@ static const char *table_data_format(Table *t, TableData *d, bool avoid_uppercas
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,
@ -2122,14 +2168,20 @@ static int table_data_requested_width_height(
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;
@ -2352,7 +2404,7 @@ int table_print(Table *t, FILE *f) {
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
@ -2360,9 +2412,13 @@ int table_print(Table *t, FILE *f) {
* ellipsis. Hence, let's figure out the last line, and account for its
* length plus ellipsis. */
field = table_data_format(t, d, false,
field = table_data_format_strip_ansi(
t,
d,
/* avoid_uppercasing= */ false,
width ? width[j] : SIZE_MAX,
&any_soft);
&any_soft,
&buffer);
if (!field)
return -ENOMEM;
@ -2550,7 +2606,7 @@ int table_print(Table *t, FILE *f) {
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;
@ -2558,7 +2614,21 @@ int table_print(Table *t, FILE *f) {
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;
@ -2665,7 +2735,8 @@ int table_print(Table *t, FILE *f) {
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;
@ -2920,6 +2991,18 @@ static int table_data_to_json(TableData *d, sd_json_variant **ret) {
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;
}
@ -2963,7 +3046,7 @@ char* table_mangle_to_json_field_name(const char *str) {
}
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);
@ -2973,7 +3056,13 @@ static int table_make_json_field_name(Table *t, TableData *d, char **ret) {
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;
}

View File

@ -10,6 +10,7 @@
typedef enum TableDataType {
TABLE_EMPTY,
TABLE_STRING,
TABLE_STRING_WITH_ANSI, /* like the above, but contains ANSI sequences/TABs. They will be stripped when outputing to JSON */
TABLE_HEADER, /* in regular mode: the cells in the first row, that carry the column names */
TABLE_FIELD, /* in vertical mode: the cells in the first column, that carry the field names */
TABLE_STRV,

View File

@ -20,7 +20,10 @@ static SD_VARLINK_DEFINE_METHOD(
SD_VARLINK_DEFINE_OUTPUT(KernelName, SD_VARLINK_STRING, 0),
SD_VARLINK_DEFINE_OUTPUT(KernelRelease, SD_VARLINK_STRING, 0),
SD_VARLINK_DEFINE_OUTPUT(KernelVersion, SD_VARLINK_STRING, 0),
SD_VARLINK_FIELD_COMMENT("'Pretty' name of the OS. This is the primary OS identifier that is suitable for presentation to the user. Typically includes a version too, but doesn't have to."),
SD_VARLINK_DEFINE_OUTPUT(OperatingSystemPrettyName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_FIELD_COMMENT("'Fancy' name of the OS; may contain non-ASCII Unicode chars, as well as basic ANSI sequences. This is similar to 'OperatingSystemPrettyName', but is preferably used on terminals that support ANSI sequences and full Unicode."),
SD_VARLINK_DEFINE_OUTPUT(OperatingSystemFancyName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_DEFINE_OUTPUT(OperatingSystemCPEName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_DEFINE_OUTPUT(OperatingSystemHomeURL, SD_VARLINK_STRING, SD_VARLINK_NULLABLE),
SD_VARLINK_DEFINE_OUTPUT(OperatingSystemSupportEnd, SD_VARLINK_INT, SD_VARLINK_NULLABLE),

View File

@ -4,6 +4,8 @@
#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"
@ -835,9 +837,80 @@ TEST(table_bps) {
"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;
}