aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Steven Schubiger <stsc@refcnt.org> 2024-07-25 14:35:26 +0200
committerGravatar Steven Schubiger <stsc@refcnt.org> 2024-07-25 14:35:26 +0200
commit369e45fece49bb8487b8924a0851cc0ef8c6fbb8 (patch)
treed59932c0ba5c338e06490c3fab762836d16c1616
parent743e75340d0882c7338eee51e03148984774a532 (diff)
downloadcolorize-369e45fece49bb8487b8924a0851cc0ef8c6fbb8.tar.gz
colorize-369e45fece49bb8487b8924a0851cc0ef8c6fbb8.tar.bz2
Add initial implementation of foreground color rainbow mode
-rw-r--r--colorize.17
-rw-r--r--colorize.c137
-rwxr-xr-xt/conf/fail.t3
-rwxr-xr-xt/conf/param.t5
-rwxr-xr-xt/conf/parse/fail.t3
-rwxr-xr-xt/conf/parse/success.t4
-rwxr-xr-xt/conf/use.t5
-rwxr-xr-xt/fail.t56
-rwxr-xr-xtest.pl16
9 files changed, 176 insertions, 60 deletions
diff --git a/colorize.1 b/colorize.1
index afa1f87..0f805fe 100644
--- a/colorize.1
+++ b/colorize.1
@@ -1,4 +1,4 @@
-.TH COLORIZE 1 "2019-09-01" "colorize v0.66" "User Commands"
+.TH COLORIZE 1 "2024-07-25" "colorize v0.66" "User Commands"
.SH NAME
colorize \- colorize text on terminal with ANSI escape sequences
.SH SYNOPSIS
@@ -49,6 +49,9 @@ text color to be excluded when selecting a random foreground color
.BR \-\-omit\-color\-empty
omit printing color escape sequences for empty lines
.TP
+.BR \-\-rainbow\-fg
+enable foreground color rainbow mode
+.TP
.BR \-h ", " \-\-help
show help screen and exit
.TP
@@ -76,6 +79,7 @@ attr = bold,underscore
color = magenta # favorite one
exclude-random = black
omit-color-empty = yes
+rainbow-fg = no
.fi
.RE
.RE
@@ -88,6 +92,7 @@ attr (values same as command-line option)
color (value same as command-line colors)
exclude-random (value same as command-line option)
omit-color-empty (yes/no)
+rainbow-fg (yes/no)
.fi
.RE
.RE
diff --git a/colorize.c b/colorize.c
index 157c4f0..9635471 100644
--- a/colorize.c
+++ b/colorize.c
@@ -141,6 +141,7 @@ struct conf {
char *color;
char *exclude_random;
char *omit_color_empty;
+ char *rainbow_fg;
};
enum { DESC_OPTION, DESC_CONF };
@@ -153,31 +154,34 @@ struct color_name {
struct color {
const char *name;
const char *code;
+ unsigned int index;
};
+static unsigned int rainbow_index;
+
static const struct color fg_colors[] = {
- { "none", NULL },
- { "black", "30m" },
- { "red", "31m" },
- { "green", "32m" },
- { "yellow", "33m" },
- { "blue", "34m" },
- { "magenta", "35m" },
- { "cyan", "36m" },
- { "white", "37m" },
- { "default", "39m" },
+ { "none", NULL, 0 },
+ { "black", "30m", 1 },
+ { "red", "31m", 2 },
+ { "green", "32m", 3 },
+ { "yellow", "33m", 4 },
+ { "blue", "34m", 5 },
+ { "magenta", "35m", 6 },
+ { "cyan", "36m", 7 },
+ { "white", "37m", 8 },
+ { "default", "39m", 9 },
};
static const struct color bg_colors[] = {
- { "none", NULL },
- { "black", "40m" },
- { "red", "41m" },
- { "green", "42m" },
- { "yellow", "43m" },
- { "blue", "44m" },
- { "magenta", "45m" },
- { "cyan", "46m" },
- { "white", "47m" },
- { "default", "49m" },
+ { "none", NULL, 0 },
+ { "black", "40m", 1 },
+ { "red", "41m", 2 },
+ { "green", "42m", 3 },
+ { "yellow", "43m", 4 },
+ { "blue", "44m", 5 },
+ { "magenta", "45m", 6 },
+ { "cyan", "46m", 7 },
+ { "white", "47m", 8 },
+ { "default", "49m", 9 },
};
struct bytes_size {
@@ -195,7 +199,8 @@ enum {
FMT_FILE,
FMT_TYPE,
FMT_CONF,
- FMT_CONF_FILE
+ FMT_CONF_FILE,
+ FMT_RAINBOW
};
static const char *formats[] = {
"%s", /* generic */
@@ -207,7 +212,8 @@ static const char *formats[] = {
"%s: %s", /* file */
"%s: %s: %s", /* type */
"%s: option '%s' %s", /* conf */
- "config file %s: %s" /* conf file */
+ "config file %s: %s", /* conf file */
+ "%s color '%s' %s" /* rainbow */
};
enum { GENERIC, FOREGROUND = 0, BACKGROUND };
@@ -225,7 +231,8 @@ static unsigned int opts_set;
enum opt_set {
OPT_ATTR_SET = 0x01,
OPT_EXCLUDE_RANDOM_SET = 0x02,
- OPT_OMIT_COLOR_EMPTY_SET = 0x04
+ OPT_OMIT_COLOR_EMPTY_SET = 0x04,
+ OPT_RAINBOW_FG_SET = 0x08
};
static struct {
char *attr;
@@ -239,6 +246,7 @@ enum {
OPT_CONFIG,
OPT_EXCLUDE_RANDOM,
OPT_OMIT_COLOR_EMPTY,
+ OPT_RAINBOW_FG,
OPT_HELP,
OPT_VERSION
};
@@ -250,6 +258,7 @@ static const struct option long_opts[] = {
{ "config", required_argument, &opt_type, OPT_CONFIG },
{ "exclude-random", required_argument, &opt_type, OPT_EXCLUDE_RANDOM },
{ "omit-color-empty", no_argument, &opt_type, OPT_OMIT_COLOR_EMPTY },
+ { "rainbow-fg", no_argument, &opt_type, OPT_RAINBOW_FG },
{ "help", no_argument, &opt_type, OPT_HELP },
{ "version", no_argument, &opt_type, OPT_VERSION },
{ NULL, 0, NULL, 0 },
@@ -289,6 +298,7 @@ static struct var_list *vars_list;
static bool clean;
static bool clean_all;
static bool omit_color_empty;
+static bool rainbow_fg;
static char attr[MAX_ATTRIBUTE_CHARS + 1];
static char *exclude;
@@ -325,6 +335,8 @@ static void save_char (char, char **, size_t *, size_t *);
static void find_color_entries (struct color_name **, const struct color **);
static void find_color_entry (const struct color_name *, unsigned int, const struct color **);
static void print_line (const char *, const struct color **, const char * const, unsigned int, bool);
+static unsigned int get_rainbow_index (const struct color **, unsigned int, unsigned int);
+static bool skipable_rainbow_index (const struct color **, unsigned int);
static void print_clean (const char *);
static bool is_esc (const char *);
static const char *get_end_of_esc (const char *);
@@ -372,7 +384,7 @@ main (int argc, char **argv)
const char *file = NULL;
char *conf_file = NULL;
- struct conf config = { NULL, NULL, NULL, NULL };
+ struct conf config = { NULL, NULL, NULL, NULL, NULL };
program_name = argv[0];
atexit (cleanup);
@@ -443,6 +455,7 @@ main (int argc, char **argv)
{ "attr", OPT_ATTR_SET },
{ "exclude-random", OPT_EXCLUDE_RANDOM_SET },
{ "omit-color-empty", OPT_OMIT_COLOR_EMPTY_SET },
+ { "rainbow-fg", OPT_RAINBOW_FG_SET },
};
for (i = 0; i < COUNT_OF (options, struct option_set); i++)
if (opts_set & options[i].set)
@@ -543,6 +556,9 @@ process_opts (int argc, char **argv, char **conf_file)
case OPT_OMIT_COLOR_EMPTY:
opts_set |= OPT_OMIT_COLOR_EMPTY_SET;
break;
+ case OPT_RAINBOW_FG:
+ opts_set |= OPT_RAINBOW_FG_SET;
+ break;
case OPT_HELP:
PRINT_HELP_EXIT ();
case OPT_VERSION:
@@ -694,6 +710,8 @@ init_opts_vars (void)
process_opt_exclude_random (opts_arg.exclude_random, true);
if (opts_set & OPT_OMIT_COLOR_EMPTY_SET)
omit_color_empty = true;
+ if (opts_set & OPT_RAINBOW_FG_SET)
+ rainbow_fg = true;
RELEASE (opts_arg.attr);
RELEASE (opts_arg.exclude_random);
@@ -800,6 +818,8 @@ assign_conf (const char *conf_file, struct conf *config, const char *cfg, char *
ASSIGN_CONF (config->exclude_random, val);
else if (streq (cfg, "omit-color-empty"))
ASSIGN_CONF (config->omit_color_empty, val);
+ else if (streq (cfg, "rainbow-fg"))
+ ASSIGN_CONF (config->rainbow_fg, val);
else
vfprintf_fail (formats[FMT_CONF], conf_file, cfg, "not recognized");
}
@@ -820,6 +840,15 @@ init_conf_vars (const struct conf *config)
else
vfprintf_fail (formats[FMT_GENERIC], "omit-color-empty conf option is not valid");
}
+ if (config->rainbow_fg)
+ {
+ if (streq (config->rainbow_fg, "yes"))
+ rainbow_fg = true;
+ else if (streq (config->rainbow_fg, "no"))
+ rainbow_fg = false;
+ else
+ vfprintf_fail (formats[FMT_GENERIC], "rainbow-fg conf option is not valid");
+ }
}
static void
@@ -998,6 +1027,7 @@ free_conf (struct conf *config)
RELEASE (config->color);
RELEASE (config->exclude_random);
RELEASE (config->omit_color_empty);
+ RELEASE (config->rainbow_fg);
}
static void
@@ -1071,6 +1101,22 @@ process_args (unsigned int arg_cnt, char **arg_strings, char *attr, const struct
}
}
+ /* --rainbow-fg */
+ if (rainbow_fg)
+ {
+ unsigned int i;
+ const unsigned int color_set[2] = { FOREGROUND, BACKGROUND };
+ for (i = 0; i < 2; i++)
+ {
+ const unsigned int color = color_set[i];
+ if (color_names[color] && (
+ streq (color_names[color]->name, "none")
+ || streq (color_names[color]->name, "default"))
+ )
+ vfprintf_fail (formats[FMT_RAINBOW], tables[color].desc, color_names[color]->orig, "cannot be used with --rainbow-fg");
+ }
+ }
+
find_color_entries (color_names, colors);
assert (colors[FOREGROUND] != NULL);
free_color_names (color_names);
@@ -1507,6 +1553,24 @@ print_line (const char *attr, const struct color **colors, const char *const lin
/* skip for --omit-color-empty? */
else if (emit_colors)
{
+ /* --rainbow-fg */
+ if (rainbow_fg)
+ {
+ unsigned int index;
+ const unsigned int max_index = tables[FOREGROUND].count - 2; /* omit color default */
+
+ if (rainbow_index == 0)
+ rainbow_index = colors[FOREGROUND]->index; /* init */
+ else if (rainbow_index > max_index)
+ rainbow_index = 1; /* black */
+
+ index = get_rainbow_index (colors, rainbow_index, max_index);
+
+ colors[FOREGROUND] = (struct color *)&tables[FOREGROUND].entries[index];
+
+ rainbow_index = index + 1;
+ }
+
/* Foreground color code is guaranteed to be set when background color code is present. */
if (colors[BACKGROUND] && colors[BACKGROUND]->code)
printf ("\033[%s", colors[BACKGROUND]->code);
@@ -1521,6 +1585,31 @@ print_line (const char *attr, const struct color **colors, const char *const lin
putchar ('\n');
}
+static unsigned int
+get_rainbow_index (const struct color **colors, unsigned int index, unsigned int max)
+{
+ if (skipable_rainbow_index (colors, index))
+ {
+ if (index + 1 > max)
+ {
+ if (skipable_rainbow_index (colors, 1))
+ return 2;
+ else
+ return 1;
+ }
+ else
+ return index + 1;
+ }
+ else
+ return index;
+}
+
+static bool
+skipable_rainbow_index (const struct color **colors, unsigned int index)
+{
+ return (colors[BACKGROUND] && index == colors[BACKGROUND]->index);
+}
+
static void
print_clean (const char *line)
{
diff --git a/t/conf/fail.t b/t/conf/fail.t
index d3b8852..9e16a49 100755
--- a/t/conf/fail.t
+++ b/t/conf/fail.t
@@ -12,7 +12,7 @@ use IPC::Open3 qw(open3);
use Symbol qw(gensym);
use Test::More;
-my $tests = 7;
+my $tests = 8;
my $run_program_fail = sub
{
@@ -46,6 +46,7 @@ SKIP: {
[ 'attr=bold,bold', 'attr conf option has attribute \'bold\' twice or more' ],
[ 'exclude-random=random', 'exclude-random conf option must be provided a plain color' ],
[ 'omit-color-empty=unsure', 'omit-color-empty conf option is not valid' ],
+ [ 'rainbow-fg=unsure', 'rainbow-fg conf option is not valid' ],
);
foreach my $set (@set) {
open(my $fh, '>', $conf_file) or die "Cannot open `$conf_file' for writing: $!\n";
diff --git a/t/conf/param.t b/t/conf/param.t
index 520df2b..f8f535f 100755
--- a/t/conf/param.t
+++ b/t/conf/param.t
@@ -14,14 +14,15 @@ my $conf = <<'EOT';
attr=bold
color=blue
omit-color-empty=yes
+rainbow-fg=yes
EOT
my $expected = <<"EOT";
\e[1;34mfoo\e[0m
-\e[1;34mbar\e[0m
+\e[1;35mbar\e[0m
-\e[1;34mbaz\e[0m
+\e[1;36mbaz\e[0m
EOT
plan tests => $tests;
diff --git a/t/conf/parse/fail.t b/t/conf/parse/fail.t
index d2988e4..13f27ff 100755
--- a/t/conf/parse/fail.t
+++ b/t/conf/parse/fail.t
@@ -12,7 +12,7 @@ use IPC::Open3 qw(open3);
use Symbol qw(gensym);
use Test::More;
-my $tests = 9;
+my $tests = 10;
my $run_program_fail = sub
{
@@ -46,6 +46,7 @@ SKIP: {
[ 'color1=magenta', 'option \'color1\' not recognized' ],
[ 'exclude-random1=black', 'option \'exclude-random1\' not recognized' ],
[ 'omit-color-empty1=yes', 'option \'omit-color-empty1\' not recognized' ],
+ [ 'rainbow-fg1=no', 'option \'rainbow-fg1\' not recognized' ],
[ 'attr', 'option \'attr\' not followed by =' ],
[ 'attr#', 'option \'attr\' not followed by =' ],
[ 'attr bold', 'option \'attr\' not followed by =' ],
diff --git a/t/conf/parse/success.t b/t/conf/parse/success.t
index fe2d46d..1ce06b0 100755
--- a/t/conf/parse/success.t
+++ b/t/conf/parse/success.t
@@ -12,7 +12,7 @@ use IPC::Open3 qw(open3);
use Symbol qw(gensym);
use Test::More;
-my $tests = 21;
+my $tests = 23;
my $conf = <<'EOT';
# comment
@@ -30,12 +30,14 @@ color=green
color=green
exclude-random=black
omit-color-empty=yes
+rainbow-fg=no
attr=bold # comment
attr=bold # comment
attr=
color=
exclude-random=
omit-color-empty=
+rainbow-fg=
EOT
my $run_program_succeed = sub
diff --git a/t/conf/use.t b/t/conf/use.t
index 0a695df..5132afa 100755
--- a/t/conf/use.t
+++ b/t/conf/use.t
@@ -14,6 +14,7 @@ my $conf = <<'EOT';
attr=underscore
color=yellow # tested also in color.t
omit-color-empty=yes
+rainbow-fg=yes
EOT
plan tests => $tests;
@@ -38,9 +39,9 @@ EOT
is(qx($program $infile1), <<"EOT", 'use config');
\e[4;33mfoo\e[0m
-\e[4;33mbar\e[0m
+\e[4;34mbar\e[0m
-\e[4;33mbaz\e[0m
+\e[4;35mbaz\e[0m
EOT
my $infile2 = $write_to_tmpfile->('foo');
diff --git a/t/fail.t b/t/fail.t
index 10378c4..c44cb7a 100755
--- a/t/fail.t
+++ b/t/fail.t
@@ -12,7 +12,7 @@ use IPC::Open3 qw(open3);
use Symbol qw(gensym);
use Test::More;
-my $tests = 25;
+my $tests = 29;
my $run_program_fail = sub
{
@@ -40,31 +40,35 @@ SKIP: {
my $dir = tempdir(CLEANUP => true);
my @set = (
- [ '--attr=:', 'must be provided a string' ],
- [ '--attr=bold:underscore', 'must have strings separated by ,' ],
- [ '--attr=b0ld', 'attribute \'b0ld\' is not valid' ],
- [ '--attr=b0ld,underscore', 'attribute \'b0ld\' is not valid' ], # handle comma
- [ '--attr=bold,bold', 'has attribute \'bold\' twice or more' ],
- [ '--exclude-random=random', 'must be provided a plain color' ],
- [ '--clean --clean-all', 'mutually exclusive' ],
- [ '--clean file1 file2', 'more than one file' ],
- [ '--clean-all file1 file2', 'more than one file' ],
- [ '- file', 'hyphen cannot be used as color string' ],
- [ '-', 'hyphen must be preceded by color string' ],
- [ "$file file", 'cannot be used as color string' ],
- [ "$file", 'must be preceded by color string' ],
- [ "$dir", 'is not a valid file type' ],
- [ '/black', 'foreground color missing' ],
- [ 'white/', 'background color missing' ],
- [ 'white/black/yellow', 'one color pair allowed only' ],
- [ 'y3llow', 'cannot be made of non-alphabetic characters' ],
- [ 'yEllow', 'cannot be in mixed lower/upper case' ],
- [ 'None', 'cannot be bold' ],
- [ 'white/Black', 'cannot be bold' ],
- [ 'random/none', 'cannot be combined with' ],
- [ 'random/default', 'cannot be combined with' ],
- [ 'none/random', 'cannot be combined with' ],
- [ 'default/random', 'cannot be combined with' ],
+ [ '--attr=:', 'must be provided a string' ],
+ [ '--attr=bold:underscore', 'must have strings separated by ,' ],
+ [ '--attr=b0ld', 'attribute \'b0ld\' is not valid' ],
+ [ '--attr=b0ld,underscore', 'attribute \'b0ld\' is not valid' ], # handle comma
+ [ '--attr=bold,bold', 'has attribute \'bold\' twice or more' ],
+ [ '--exclude-random=random', 'must be provided a plain color' ],
+ [ '--clean --clean-all', 'mutually exclusive' ],
+ [ '--clean file1 file2', 'more than one file' ],
+ [ '--clean-all file1 file2', 'more than one file' ],
+ [ '- file', 'hyphen cannot be used as color string' ],
+ [ '-', 'hyphen must be preceded by color string' ],
+ [ "$file file", 'cannot be used as color string' ],
+ [ "$file", 'must be preceded by color string' ],
+ [ "$dir", 'is not a valid file type' ],
+ [ '/black', 'foreground color missing' ],
+ [ 'white/', 'background color missing' ],
+ [ 'white/black/yellow', 'one color pair allowed only' ],
+ [ 'y3llow', 'cannot be made of non-alphabetic characters' ],
+ [ 'yEllow', 'cannot be in mixed lower/upper case' ],
+ [ 'None', 'cannot be bold' ],
+ [ 'white/Black', 'cannot be bold' ],
+ [ 'random/none', 'cannot be combined with' ],
+ [ 'random/default', 'cannot be combined with' ],
+ [ 'none/random', 'cannot be combined with' ],
+ [ 'default/random', 'cannot be combined with' ],
+ [ 'white/none --rainbow-fg', 'cannot be used with --rainbow-fg' ],
+ [ 'white/default --rainbow-fg', 'cannot be used with --rainbow-fg' ],
+ [ 'none/white --rainbow-fg', 'cannot be used with --rainbow-fg' ],
+ [ 'default/white --rainbow-fg', 'cannot be used with --rainbow-fg' ],
);
foreach my $set (@set) {
ok($run_program_fail->($program, $set->[0], $set->[1]), $set->[1]);
diff --git a/test.pl b/test.pl
index 928c698..5467722 100755
--- a/test.pl
+++ b/test.pl
@@ -13,7 +13,7 @@ use Getopt::Long qw(:config no_auto_abbrev no_ignore_case);
use Test::Harness qw(runtests);
use Test::More;
-my $tests = 32;
+my $tests = 34;
my $valgrind_cmd = '';
{
@@ -101,7 +101,7 @@ SKIP: {
{
my $ok = true;
- foreach my $option (qw(--attr=bold --exclude-random=black --omit-color-empty)) {
+ foreach my $option (qw(--attr=bold --exclude-random=black --omit-color-empty --rainbow-fg)) {
$ok &= qx($valgrind_cmd$program $option $switch $infile1 2>&1 >/dev/null) =~ /switch has no meaning with/;
}
ok($ok, "$type strict options");
@@ -160,6 +160,18 @@ SKIP: {
'switch omit-color-empty');
}
+ {
+ my $infile = $write_to_tmpfile->("foo\nbar\nbaz");
+
+ is_deeply([split /\n/, qx($valgrind_cmd$program black/black --rainbow-fg $infile)],
+ [split /\n/, "\e[40m\e[31mfoo\e[0m\n\e[40m\e[32mbar\e[0m\n\e[40m\e[33mbaz\e[0m"],
+ 'switch rainbow-fg (init)');
+
+ is_deeply([split /\n/, qx($valgrind_cmd$program white --rainbow-fg $infile)],
+ [split /\n/, "\e[37mfoo\e[0m\n\e[30mbar\e[0m\n\e[31mbaz\e[0m"],
+ 'switch rainbow-fg (reset)');
+ }
+
SKIP: {
skip 'valgrind not found', 1 unless system('which valgrind >/dev/null 2>&1') == 0;
like(qx(valgrind $program none/none $infile1 2>&1 >/dev/null), qr/no leaks are possible/, 'valgrind memleaks');