Skip to content

Commit aa977aa

Browse files
authored
Dynamically allocate JQ_COLORS escapes for truecolor support (#3282)
1 parent c6e0416 commit aa977aa

File tree

3 files changed

+75
-52
lines changed

3 files changed

+75
-52
lines changed

src/jv_print.c

Lines changed: 58 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -27,43 +27,70 @@
2727
// Color table. See https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
2828
// for how to choose these. The order is same as jv_kind definition, and
2929
// the last color is used for object keys.
30-
static char color_bufs[8][16];
31-
static const char *color_bufps[8];
32-
static const char *const def_colors[] =
33-
{COL("0;90"), COL("0;39"), COL("0;39"), COL("0;39"),
30+
#define DEFAULT_COLORS \
31+
{COL("0;90"), COL("0;39"), COL("0;39"), COL("0;39"),\
3432
COL("0;32"), COL("1;39"), COL("1;39"), COL("1;34")};
33+
static const char *const default_colors[] = DEFAULT_COLORS;
34+
static const char *colors[] = DEFAULT_COLORS;
35+
#define COLORS_LEN (sizeof(colors) / sizeof(colors[0]))
3536
#define FIELD_COLOR (colors[7])
3637

37-
static const char *const *colors = def_colors;
38+
static char *colors_buf = NULL;
39+
int jq_set_colors(const char *code_str) {
40+
if (code_str == NULL)
41+
return 1;
3842

39-
int
40-
jq_set_colors(const char *c)
41-
{
42-
const char *e;
43-
size_t i;
43+
// the start of each color code in the env var, and the byte after the end of the last one
44+
const char *codes[COLORS_LEN + 1];
45+
size_t num_colors;
46+
// must be initialized before `goto default_colors`, used later to loop over every color
47+
size_t ci = 0;
4448

45-
if (c == NULL)
46-
return 1;
47-
colors = def_colors;
48-
memset(color_bufs, 0, sizeof(color_bufs));
49-
for (i = 0; i < sizeof(def_colors) / sizeof(def_colors[0]); i++)
50-
color_bufps[i] = def_colors[i];
51-
for (i = 0; i < sizeof(def_colors) / sizeof(def_colors[0]) && *c != '\0'; i++, c = e) {
52-
if ((e = strchr(c, ':')) == NULL)
53-
e = c + strlen(c);
54-
if ((size_t)(e - c) > sizeof(color_bufs[i]) - 4 /* ESC [ m NUL */)
55-
return 0;
56-
color_bufs[i][0] = ESC[0];
57-
color_bufs[i][1] = '[';
58-
(void) strncpy(&color_bufs[i][2], c, e - c);
59-
if (strspn(&color_bufs[i][2], "0123456789;") < strlen(&color_bufs[i][2]))
60-
return 0;
61-
color_bufs[i][2 + (e - c)] = 'm';
62-
color_bufps[i] = color_bufs[i];
63-
if (e[0] == ':')
64-
e++;
49+
for (num_colors = 0;; num_colors++) {
50+
codes[num_colors] = code_str;
51+
code_str += strspn(code_str, "0123456789;");
52+
if (code_str[0] == '\0' || num_colors + 1 >= COLORS_LEN) {
53+
break;
54+
} else if (code_str[0] != ':') {
55+
return 0; // invalid character
56+
}
57+
code_str++;
58+
}
59+
if (codes[num_colors] != code_str) {
60+
// count the last color and store its end (plus one byte for consistency with starts)
61+
// an empty last color would be ignored (for cases like "" and "0:")
62+
num_colors++;
63+
codes[num_colors] = code_str + 1;
64+
} else if (num_colors == 0) {
65+
if (colors_buf != NULL) {
66+
jv_mem_free(colors_buf);
67+
colors_buf = NULL;
68+
}
69+
goto default_colors;
70+
}
71+
72+
colors_buf = jv_mem_realloc(
73+
colors_buf,
74+
// add ESC '[' 'm' to each string
75+
// '\0' is already included in difference of codes
76+
codes[num_colors] - codes[0] + 3 * num_colors
77+
);
78+
char *cb = colors_buf;
79+
for (; ci < num_colors; ci++) {
80+
colors[ci] = cb;
81+
size_t len = codes[ci + 1] - 1 - codes[ci];
82+
83+
cb[0] = ESC[0];
84+
cb[1] = '[';
85+
memcpy(cb + 2, codes[ci], len);
86+
cb[2 + len] = 'm';
87+
cb[3 + len] = '\0';
88+
89+
cb += len + 4;
6590
}
66-
colors = color_bufps;
91+
default_colors:
92+
for (; ci < COLORS_LEN; ci++)
93+
colors[ci] = default_colors[ci];
6794
return 1;
6895
}
6996

src/main.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,7 @@ int main(int argc, char* argv[]) {
554554
if (options & COLOR_OUTPUT) dumpopts |= JV_PRINT_COLOR;
555555
if (options & NO_COLOR_OUTPUT) dumpopts &= ~JV_PRINT_COLOR;
556556

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

560560
if (jv_get_kind(lib_search_paths) == JV_KIND_NULL) {

tests/shtest

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,21 @@ JQ_COLORS='4;31' $JQ -Ccn . > $d/color
440440
printf '\033[4;31mnull\033[0m\n' > $d/expect
441441
cmp $d/color $d/expect
442442

443+
## Set implicit empty color, null input
444+
$JQ -Ccn . > $d/color
445+
printf '\033[0;90mnull\033[0m\n' > $d/expect
446+
cmp $d/color $d/expect
447+
448+
## Set explicit empty color, null input
449+
JQ_COLORS=':' $JQ -Ccn . > $d/color
450+
printf '\033[mnull\033[0m\n' > $d/expect
451+
cmp $d/color $d/expect
452+
453+
## Extra colors, null input
454+
JQ_COLORS='::::::::' $JQ -Ccn . > $d/color
455+
printf '\033[mnull\033[0m\n' > $d/expect
456+
cmp $d/color $d/expect
457+
443458
## Default colors, complex input
444459
$JQ -Ccn '[{"a":true,"b":false},"abc",123,null]' > $d/color
445460
{
@@ -534,33 +549,14 @@ JQ_COLORS='0;30:0;31:0;32:0;33:0;34:1;35:1;36:1;37' \
534549
} > $d/expect
535550
cmp $d/color $d/expect
536551

537-
# Check garbage in JQ_COLORS. We write each color sequence into a 16
538-
# char buffer that needs to hold ESC [ <color> m NUL, so each color
539-
# sequence can be no more than 12 chars (bytes). These emit a warning
540-
# on stderr.
552+
# Check garbage in JQ_COLORS. These emit a warning on stderr.
541553
set -vx
542554
echo 'Failed to set $JQ_COLORS' > $d/expect_warning
543555
$JQ -Ccn '[{"a":true,"b":false},"abc",123,null]' > $d/expect
544556
JQ_COLORS='garbage;30:*;31:,;3^:0;$%:0;34:1;35:1;36' \
545557
$JQ -Ccn '[{"a":true,"b":false},"abc",123,null]' > $d/color 2>$d/warning
546558
cmp $d/color $d/expect
547559
cmp $d/warning $d/expect_warning
548-
JQ_COLORS='1234567890123456789;30:0;31:0;32:0;33:0;34:1;35:1;36' \
549-
$JQ -Ccn '[{"a":true,"b":false},"abc",123,null]' > $d/color 2>$d/warning
550-
cmp $d/color $d/expect
551-
cmp $d/warning $d/expect_warning
552-
JQ_COLORS='1;31234567890123456789:0;31:0;32:0;33:0;34:1;35:1;36' \
553-
$JQ -Ccn '[{"a":true,"b":false},"abc",123,null]' > $d/color 2>$d/warning
554-
cmp $d/color $d/expect
555-
cmp $d/warning $d/expect_warning
556-
JQ_COLORS='1234567890123456;1234567890123456:1234567890123456;1234567890123456:1234567890123456;1234567890123456:1234567890123456;1234567890123456:1234567890123456;1234567890123456:1234567890123456;1234567890123456:1234567890123456;1234567890123456' \
557-
$JQ -Ccn '[{"a":true,"b":false},"abc",123,null]' > $d/color 2>$d/warning
558-
cmp $d/color $d/expect
559-
cmp $d/warning $d/expect_warning
560-
JQ_COLORS="0123456789123:0123456789123:0123456789123:0123456789123:0123456789123:0123456789123:0123456789123:0123456789123:" \
561-
$JQ -Ccn '[{"a":true,"b":false},"abc",123,null]' > $d/color 2>$d/warning
562-
cmp $d/color $d/expect
563-
cmp $d/warning $d/expect_warning
564560

565561
# Check $NO_COLOR
566562
test_no_color=true

0 commit comments

Comments
 (0)