Skip to content

Commit

Permalink
dynamically allocate JQ_COLORS escapes (#2426)
Browse files Browse the repository at this point in the history
valid color escapes can be over 100 characters long,
so a static buffer isn't a good option.
  • Loading branch information
SArpnt committed Mar 3, 2025
1 parent 8ba03f7 commit 40eae50
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 50 deletions.
88 changes: 58 additions & 30 deletions src/jv_print.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,43 +27,71 @@
// 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 DEF_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 def_colors[] = DEF_COLORS;
static const char *colors[] = DEF_COLORS;
#define COLORS_LEN (sizeof(colors) / sizeof(colors[0]))
#define FIELD_COLOR (colors[7])

static const char *const *colors = def_colors;

int
jq_set_colors(const char *c)
{
const char *e;
size_t i;

static char *colors_buf = NULL;
int jq_set_colors(const char *c) {
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]))
const char *offsets[COLORS_LEN + 1]; // extra item for the end of the last string

size_t cl = 0;
size_t cn = 0;

while (cl < COLORS_LEN) {
offsets[cl++] = c;
// gcc won't optimize out strspn
letter:
switch (c++[0]) {
// technically posix doesn't specify ascii so a range wouldn't be portable
case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case ';':
goto letter;
case ':':
continue;
case '\0':
goto var_end;
default:
return 0;
color_bufs[i][2 + (e - c)] = 'm';
color_bufps[i] = color_bufs[i];
if (e[0] == ':')
e++;
}
}
var_end:
// don't override last color on empty variable or trailing :
if (offsets[--cl] != c - 1) {
offsets[++cl] = c;
} else if (cl == 0) {
if (colors_buf != NULL) {
jv_mem_free(colors_buf);
colors_buf = NULL;
}
goto reset;
}

colors_buf = jv_mem_realloc(
colors_buf,
// add ESC '[' 'm' to each string
// '\0' is already included in difference of offsets
offsets[cl] - offsets[0] + 3 * cl
);
char *cb = colors_buf;
for (; cn < cl; cn++) {
colors[cn] = cb;
cb[0] = ESC[0];
cb[1] = '[';
size_t len = offsets[cn + 1] - 1 - offsets[cn];
memcpy(cb + 2, offsets[cn], len);
cb[len + 2] = 'm';
cb[len + 3] = '\0';
cb += len + 4;
}
colors = color_bufps;
reset:
for (; cn < COLORS_LEN; cn++)
colors[cn] = def_colors[cn];
return 1;
}

Expand Down
31 changes: 11 additions & 20 deletions tests/shtest
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,16 @@ 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

## Default colors, complex input
$JQ -Ccn '[{"a":true,"b":false},"abc",123,null]' > $d/color
{
Expand Down Expand Up @@ -519,33 +529,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

0 comments on commit 40eae50

Please sign in to comment.