Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 58 additions & 31 deletions src/jv_print.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,43 +27,70 @@
// Color table. See https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
// for how to choose these. The order is same as jv_kind definition, and
// the last color is used for object keys.
static char color_bufs[8][16];
static const char *color_bufps[8];
static const char *const def_colors[] =
{COL("0;90"), COL("0;39"), COL("0;39"), COL("0;39"),
#define DEFAULT_COLORS \
{COL("0;90"), COL("0;39"), COL("0;39"), COL("0;39"),\
COL("0;32"), COL("1;39"), COL("1;39"), COL("1;34")};
static const char *const default_colors[] = DEFAULT_COLORS;
static const char *colors[] = DEFAULT_COLORS;
#define COLORS_LEN (sizeof(colors) / sizeof(colors[0]))
#define FIELD_COLOR (colors[7])

static const char *const *colors = def_colors;
static char *colors_buf = NULL;
int jq_set_colors(const char *code_str) {
if (code_str == NULL)
return 1;

int
jq_set_colors(const char *c)
{
const char *e;
size_t i;
// the start of each color code in the env var, and the byte after the end of the last one
const char *codes[COLORS_LEN + 1];
size_t num_colors;
// must be initialized before `goto default_colors`, used later to loop over every color
size_t ci = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this variable initialization moved just before used?

Copy link
Contributor

@itchyny itchyny May 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, okay. this is needed because of the goto. (Sorry I commented before understanding the comment).


if (c == NULL)
return 1;
colors = def_colors;
memset(color_bufs, 0, sizeof(color_bufs));
for (i = 0; i < sizeof(def_colors) / sizeof(def_colors[0]); i++)
color_bufps[i] = def_colors[i];
for (i = 0; i < sizeof(def_colors) / sizeof(def_colors[0]) && *c != '\0'; i++, c = e) {
if ((e = strchr(c, ':')) == NULL)
e = c + strlen(c);
if ((size_t)(e - c) > sizeof(color_bufs[i]) - 4 /* ESC [ m NUL */)
return 0;
color_bufs[i][0] = ESC[0];
color_bufs[i][1] = '[';
(void) strncpy(&color_bufs[i][2], c, e - c);
if (strspn(&color_bufs[i][2], "0123456789;") < strlen(&color_bufs[i][2]))
return 0;
color_bufs[i][2 + (e - c)] = 'm';
color_bufps[i] = color_bufs[i];
if (e[0] == ':')
e++;
for (num_colors = 0;; num_colors++) {
codes[num_colors] = code_str;
code_str += strspn(code_str, "0123456789;");
if (code_str[0] == '\0' || num_colors + 1 >= COLORS_LEN) {
break;
} else if (code_str[0] != ':') {
return 0; // invalid character
}
code_str++;
}
if (codes[num_colors] != code_str) {
// count the last color and store its end (plus one byte for consistency with starts)
// an empty last color would be ignored (for cases like "" and "0:")
num_colors++;
codes[num_colors] = code_str + 1;
} else if (num_colors == 0) {
if (colors_buf != NULL) {
jv_mem_free(colors_buf);
colors_buf = NULL;
}
goto default_colors;
}

colors_buf = jv_mem_realloc(
colors_buf,
// add ESC '[' 'm' to each string
// '\0' is already included in difference of codes
codes[num_colors] - codes[0] + 3 * num_colors
);
char *cb = colors_buf;
for (; ci < num_colors; ci++) {
colors[ci] = cb;
size_t len = codes[ci + 1] - 1 - codes[ci];

cb[0] = ESC[0];
cb[1] = '[';
memcpy(cb + 2, codes[ci], len);
cb[2 + len] = 'm';
cb[3 + len] = '\0';

cb += len + 4;
}
colors = color_bufps;
default_colors:
for (; ci < COLORS_LEN; ci++)
colors[ci] = default_colors[ci];
return 1;
}

Expand Down
2 changes: 1 addition & 1 deletion src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -554,7 +554,7 @@ int main(int argc, char* argv[]) {
if (options & COLOR_OUTPUT) dumpopts |= JV_PRINT_COLOR;
if (options & NO_COLOR_OUTPUT) dumpopts &= ~JV_PRINT_COLOR;

if (getenv("JQ_COLORS") != NULL && !jq_set_colors(getenv("JQ_COLORS")))
if (!jq_set_colors(getenv("JQ_COLORS")))
fprintf(stderr, "Failed to set $JQ_COLORS\n");

if (jv_get_kind(lib_search_paths) == JV_KIND_NULL) {
Expand Down
36 changes: 16 additions & 20 deletions tests/shtest
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,21 @@ JQ_COLORS='4;31' $JQ -Ccn . > $d/color
printf '\033[4;31mnull\033[0m\n' > $d/expect
cmp $d/color $d/expect

## Set implicit empty color, null input
$JQ -Ccn . > $d/color
printf '\033[0;90mnull\033[0m\n' > $d/expect
cmp $d/color $d/expect

## Set explicit empty color, null input
JQ_COLORS=':' $JQ -Ccn . > $d/color
printf '\033[mnull\033[0m\n' > $d/expect
cmp $d/color $d/expect

## Extra colors, null input
JQ_COLORS='::::::::' $JQ -Ccn . > $d/color
printf '\033[mnull\033[0m\n' > $d/expect
cmp $d/color $d/expect

## Default colors, complex input
$JQ -Ccn '[{"a":true,"b":false},"abc",123,null]' > $d/color
{
Expand Down Expand Up @@ -534,33 +549,14 @@ JQ_COLORS='0;30:0;31:0;32:0;33:0;34:1;35:1;36:1;37' \
} > $d/expect
cmp $d/color $d/expect

# Check garbage in JQ_COLORS. We write each color sequence into a 16
# char buffer that needs to hold ESC [ <color> m NUL, so each color
# sequence can be no more than 12 chars (bytes). These emit a warning
# on stderr.
# Check garbage in JQ_COLORS. These emit a warning on stderr.
set -vx
echo 'Failed to set $JQ_COLORS' > $d/expect_warning
$JQ -Ccn '[{"a":true,"b":false},"abc",123,null]' > $d/expect
JQ_COLORS='garbage;30:*;31:,;3^:0;$%:0;34:1;35:1;36' \
$JQ -Ccn '[{"a":true,"b":false},"abc",123,null]' > $d/color 2>$d/warning
cmp $d/color $d/expect
cmp $d/warning $d/expect_warning
JQ_COLORS='1234567890123456789;30:0;31:0;32:0;33:0;34:1;35:1;36' \
$JQ -Ccn '[{"a":true,"b":false},"abc",123,null]' > $d/color 2>$d/warning
cmp $d/color $d/expect
cmp $d/warning $d/expect_warning
JQ_COLORS='1;31234567890123456789:0;31:0;32:0;33:0;34:1;35:1;36' \
$JQ -Ccn '[{"a":true,"b":false},"abc",123,null]' > $d/color 2>$d/warning
cmp $d/color $d/expect
cmp $d/warning $d/expect_warning
JQ_COLORS='1234567890123456;1234567890123456:1234567890123456;1234567890123456:1234567890123456;1234567890123456:1234567890123456;1234567890123456:1234567890123456;1234567890123456:1234567890123456;1234567890123456:1234567890123456;1234567890123456' \
$JQ -Ccn '[{"a":true,"b":false},"abc",123,null]' > $d/color 2>$d/warning
cmp $d/color $d/expect
cmp $d/warning $d/expect_warning
JQ_COLORS="0123456789123:0123456789123:0123456789123:0123456789123:0123456789123:0123456789123:0123456789123:0123456789123:" \
$JQ -Ccn '[{"a":true,"b":false},"abc",123,null]' > $d/color 2>$d/warning
cmp $d/color $d/expect
cmp $d/warning $d/expect_warning

# Check $NO_COLOR
test_no_color=true
Expand Down