diff --git a/src/jv_print.c b/src/jv_print.c index ae17ec0a..791af179 100644 --- a/src/jv_print.c +++ b/src/jv_print.c @@ -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; - -int -jq_set_colors(const char *c) -{ - const char *e; - size_t i; - - if (c == NULL) +static char *colors_buf = NULL; +int jq_set_colors(const char *code_str) { + if (code_str == 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++; + + // 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; + + 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++; } - colors = color_bufps; + 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; + } + default_colors: + for (; ci < COLORS_LEN; ci++) + colors[ci] = default_colors[ci]; return 1; } diff --git a/src/main.c b/src/main.c index 7189a8eb..a316f63c 100644 --- a/src/main.c +++ b/src/main.c @@ -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) { diff --git a/tests/shtest b/tests/shtest index 5853efff..104ef727 100755 --- a/tests/shtest +++ b/tests/shtest @@ -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 { @@ -534,10 +549,7 @@ 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 [ 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 @@ -545,22 +557,6 @@ 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