]> git.refcnt.org Git - colorize.git/blobdiff - colorize.c
.gitignore: add backup pattern
[colorize.git] / colorize.c
index 147f13e2cbbaa550867da764026ab7a81e653d42..eb8f7c084c257789ec64e5453fb00d66e942cf56 100644 (file)
@@ -2,7 +2,7 @@
  * colorize - Read text from standard input stream or file and print
  *            it colorized through use of ANSI escape sequences
  *
- * Copyright (c) 2011-2019 Steven Schubiger
+ * Copyright (c) 2011-2022 Steven Schubiger
  *
  * This program is free software: you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -74,6 +74,7 @@
 
 #define LF 0x01
 #define CR 0x02
+#define PARTIAL 0x04
 
 #define COUNT_OF(obj, type) (sizeof (obj) / sizeof (type))
 
 
 #define VALID_FILE_TYPE(mode) (S_ISREG (mode) || S_ISLNK (mode) || S_ISFIFO (mode))
 
-#define STACK_VAR(ptr) do {                                   \
-    stack_var (&vars_list, &stacked_vars, stacked_vars, ptr); \
+#define STACK_VAR(ptr) do {                                           \
+    stack (&vars_list, &stacked_vars, stacked_vars, ptr, IS_GENERIC); \
 } while (false)
-
-#define RELEASE_VAR(ptr) do {                             \
-    release_var (vars_list, stacked_vars, (void **)&ptr); \
+#define STACK_FILE(ptr) do {                                       \
+    stack (&vars_list, &stacked_vars, stacked_vars, ptr, IS_FILE); \
+} while (false)
+#define RELEASE(ptr) do {                             \
+    release (vars_list, stacked_vars, (void **)&ptr); \
 } while (false)
 
 #if !DEBUG
 
 #define PROGRAM_NAME "colorize"
 
-#define VERSION "0.65"
+#define VERSION "0.66"
 
 typedef enum { false, true } bool;
 
@@ -139,6 +142,8 @@ struct conf {
     char *color;
     char *exclude_random;
     char *omit_color_empty;
+    char *rainbow_fg;
+    char *rainbow_bg;
 };
 
 enum { DESC_OPTION, DESC_CONF };
@@ -151,31 +156,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 {
@@ -192,18 +200,24 @@ enum {
     FMT_ERROR,
     FMT_FILE,
     FMT_TYPE,
-    FMT_CONF
+    FMT_CONF,
+    FMT_CONF_FILE,
+    FMT_CONF_INIT,
+    FMT_RAINBOW
 };
 static const char *formats[] = {
-    "%s",                     /* generic */
-    "%s '%s'",                /* string  */
-    "%s `%s' %s",             /* quote   */
-    "%s color '%s' %s",       /* color   */
-    "%s color '%s' %s '%s'",  /* random  */
-    "less than %lu bytes %s", /* error   */
-    "%s: %s",                 /* file    */
-    "%s: %s: %s",             /* type    */
-    "%s: option '%s' %s"      /* conf    */
+    "%s",                     /* generic   */
+    "%s '%s'",                /* string    */
+    "%s `%s' %s",             /* quote     */
+    "%s color '%s' %s",       /* color     */
+    "%s color '%s' %s '%s'",  /* random    */
+    "less than %lu bytes %s", /* error     */
+    "%s: %s",                 /* file      */
+    "%s: %s: %s",             /* type      */
+    "%s: option '%s' %s",     /* conf      */
+    "config file %s: %s",     /* conf file */
+    "%s %s",                  /* conf init */
+    "%s color '%s' %s %s"     /* rainbow   */
 };
 
 enum { GENERIC, FOREGROUND = 0, BACKGROUND };
@@ -221,7 +235,9 @@ 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,
+    OPT_RAINBOW_BG_SET = 0x10
 };
 static struct {
     char *attr;
@@ -235,6 +251,8 @@ enum {
     OPT_CONFIG,
     OPT_EXCLUDE_RANDOM,
     OPT_OMIT_COLOR_EMPTY,
+    OPT_RAINBOW_FG,
+    OPT_RAINBOW_BG,
     OPT_HELP,
     OPT_VERSION
 };
@@ -246,6 +264,8 @@ 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       },
+    { "rainbow-bg",       no_argument,       &opt_type, OPT_RAINBOW_BG       },
     { "help",             no_argument,       &opt_type, OPT_HELP             },
     { "version",          no_argument,       &opt_type, OPT_VERSION          },
     {  NULL,              0,                 NULL,      0                    },
@@ -264,17 +284,34 @@ struct attr {
     enum attr_type type;
 };
 
+enum var_type {
+    IS_GENERIC,
+    IS_FILE,
+    IS_UNUSED
+};
+struct var_list {
+    void *ptr;
+    enum var_type type;
+};
+
 static FILE *stream;
 #if DEBUG
 static FILE *log;
 #endif
 
 static unsigned int stacked_vars;
-static void **vars_list;
+static struct var_list *vars_list;
+
+static struct {
+    bool fg;
+    bool bg;
+} rainbow_from_conf = { false, false };
 
 static bool clean;
 static bool clean_all;
 static bool omit_color_empty;
+static bool rainbow_fg;
+static bool rainbow_bg;
 
 static char attr[MAX_ATTRIBUTE_CHARS + 1];
 static char *exclude;
@@ -291,7 +328,8 @@ static void write_attr (const struct attr *, unsigned int *, const bool);
 static void process_opt_exclude_random (const char *, const bool);
 static void parse_conf (const char *, struct conf *);
 static void assign_conf (const char *, struct conf *, const char *, char *);
-static void init_conf_vars (const struct conf *);
+static void init_conf_vars (const char *, const struct conf *);
+static void init_conf_boolean (const char *, bool *, const char *, bool *);
 static void init_opts_vars (void);
 static void print_hint (void);
 static void print_help (void);
@@ -311,6 +349,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, unsigned int);
+static bool skipable_rainbow_index (const struct color **, unsigned int, unsigned int);
 static void print_clean (const char *);
 static bool is_esc (const char *);
 static const char *get_end_of_esc (const char *);
@@ -342,10 +382,8 @@ static bool has_color_name (const char *, const char *);
 static FILE *open_file (const char *, const char *);
 static void vfprintf_diag (const char *, ...);
 static void vfprintf_fail (const char *, ...);
-static void stack_var (void ***, unsigned int *, unsigned int, void *);
-static void release_var (void **, unsigned int, void **);
-
-extern int optind;
+static void stack (struct var_list **, unsigned int *, unsigned int, void *, enum var_type);
+static void release (struct var_list *, unsigned int, void **);
 
 int
 main (int argc, char **argv)
@@ -360,7 +398,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, NULL };
 
     program_name = argv[0];
     atexit (cleanup);
@@ -370,6 +408,11 @@ main (int argc, char **argv)
 #if DEBUG
     log = open_file (DEBUG_FILE, "w");
     print_tstamp (log);
+    /* We're in debugging mode, hence we can't invoke STACK_FILE()
+       prior to print_tstamp(), because both cause text to be written
+       to the same logfile which is expected to have the timestamp
+       first.  */
+    STACK_FILE (log);
 #endif
 
     attr[0] = '\0';
@@ -380,7 +423,10 @@ main (int argc, char **argv)
     conf_file = to_str (CONF_FILE_TEST);
 #elif !defined(TEST)
     if (conf_file == NULL)
-      conf_file_path (&conf_file);
+      {
+        conf_file_path (&conf_file);
+        STACK_VAR (conf_file);
+      }
     else
       {
         char *s;
@@ -389,22 +435,24 @@ main (int argc, char **argv)
             free (conf_file);
             conf_file = s;
           }
+        STACK_VAR (conf_file);
         errno = 0;
         if (access (conf_file, F_OK) == -1)
-          vfprintf_fail (formats[FMT_FILE], conf_file, strerror (errno));
+          vfprintf_fail (formats[FMT_CONF_FILE], conf_file, strerror (errno));
       }
 #endif
 #if defined(CONF_FILE_TEST) || !defined(TEST)
     if (access (conf_file, F_OK) != -1)
       parse_conf (conf_file, &config);
 #endif
-#if !defined(CONF_FILE_TEST) && !defined(TEST)
-    free (conf_file);
-#endif
-    init_conf_vars (&config);
+    init_conf_vars (conf_file, &config);
 
     init_opts_vars ();
 
+#if !defined(CONF_FILE_TEST) && !defined(TEST)
+    RELEASE (conf_file);
+#endif
+
     arg_cnt = argc - optind;
 
     if (clean || clean_all)
@@ -412,14 +460,7 @@ main (int argc, char **argv)
         if (clean && clean_all)
           vfprintf_fail (formats[FMT_GENERIC], "--clean and --clean-all switch are mutually exclusive");
         if (arg_cnt > 1)
-          {
-            const char *const format = "%s %s";
-            const char *const message = "switch cannot be used with more than one file";
-            if (clean)
-              vfprintf_fail (format, "--clean", message);
-            else if (clean_all)
-              vfprintf_fail (format, "--clean-all", message);
-          }
+          vfprintf_fail ("--clean%s switch cannot be used with more than one file", clean_all ? "-all" : "");
         {
           unsigned int i;
           const struct option_set {
@@ -429,6 +470,8 @@ 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       },
+              { "rainbow-bg",       OPT_RAINBOW_BG_SET       },
           };
           for (i = 0; i < COUNT_OF (options, struct option_set); i++)
             if (opts_set & options[i].set)
@@ -437,9 +480,15 @@ main (int argc, char **argv)
       }
     else
       {
+        if (rainbow_fg && rainbow_bg)
+          vfprintf_fail ("%s and %s are mutually exclusive",
+            !rainbow_from_conf.fg ? "--rainbow-fg switch" : "rainbow-fg conf option",
+            !rainbow_from_conf.bg ? "--rainbow-bg switch" : "rainbow-bg conf option"
+          );
+
         if (arg_cnt == 0 || arg_cnt > 2)
           {
-            vfprintf_diag ("%u arguments provided, expected 1-2 arguments or clean option", arg_cnt);
+            vfprintf_diag ("%u arguments provided, expected 1-2 arguments or --clean[-all]", arg_cnt);
             print_hint ();
             exit (EXIT_FAILURE);
           }
@@ -453,7 +502,7 @@ main (int argc, char **argv)
 
     free_conf (&config);
 
-    RELEASE_VAR (exclude);
+    RELEASE (exclude);
 
     exit (EXIT_SUCCESS);
 }
@@ -497,8 +546,6 @@ print_tstamp (FILE *log)
     print_version ();        \
     exit (EXIT_SUCCESS);
 
-extern char *optarg;
-
 static void
 process_opts (int argc, char **argv, char **conf_file)
 {
@@ -513,6 +560,7 @@ process_opts (int argc, char **argv, char **conf_file)
                   case OPT_ATTR:
                     opts_set |= OPT_ATTR_SET;
                     opts_arg.attr = xstrdup (optarg);
+                    STACK_VAR (opts_arg.attr);
                     break;
                   case OPT_CLEAN:
                     clean = true;
@@ -525,10 +573,17 @@ process_opts (int argc, char **argv, char **conf_file)
                   case OPT_EXCLUDE_RANDOM:
                     opts_set |= OPT_EXCLUDE_RANDOM_SET;
                     opts_arg.exclude_random = xstrdup (optarg);
+                    STACK_VAR (opts_arg.exclude_random);
                     break;
                   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_RAINBOW_BG:
+                    opts_set |= OPT_RAINBOW_BG_SET;
+                    break;
                   case OPT_HELP:
                     PRINT_HELP_EXIT ();
                   case OPT_VERSION:
@@ -570,6 +625,7 @@ conf_file_path (char **conf_file)
           perror ("getpwuid");
         exit (EXIT_FAILURE);
       }
+    /* getpwuid() leaks memory */
     size = strlen (passwd->pw_dir) + 1 + strlen (CONF_FILE) + 1;
     path = xmalloc (size);
     snprintf (path, size, "%s/%s", passwd->pw_dir, CONF_FILE);
@@ -595,10 +651,10 @@ process_opt_attr (const char *p, const bool is_opt)
     while (*p)
       {
         const char *s;
-        if (!isalnum (*p))
+        if (!isalnum ((unsigned char)*p))
           vfprintf_fail ("%s must be provided a string", desc_type[DESC_TYPE]);
         s = p;
-        while (isalnum (*p))
+        while (isalnum ((unsigned char)*p))
           p++;
         if (*p != '\0' && *p != ',')
           vfprintf_fail ("%s must have strings separated by ,", desc_type[DESC_TYPE]);
@@ -623,7 +679,7 @@ process_opt_attr (const char *p, const bool is_opt)
                 strncpy (attr_invalid, s, p - s);
                 attr_invalid[p - s] = '\0';
                 vfprintf_fail ("%s attribute '%s' is not valid", desc_type[DESC_TYPE], attr_invalid);
-                RELEASE_VAR (attr_invalid); /* never reached */
+                RELEASE (attr_invalid); /* never reached */
               }
           }
         if (*p)
@@ -650,7 +706,7 @@ process_opt_exclude_random (const char *s, const bool is_opt)
 {
     bool valid = false;
     unsigned int i;
-    RELEASE_VAR (exclude);
+    RELEASE (exclude);
     exclude = xstrdup (s);
     STACK_VAR (exclude);
     for (i = 1; i < tables[GENERIC].count - 1; i++) /* skip color none and default */
@@ -679,9 +735,13 @@ 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;
+    if (opts_set & OPT_RAINBOW_BG_SET)
+      rainbow_bg = true;
 
-    free (opts_arg.attr);
-    free (opts_arg.exclude_random);
+    RELEASE (opts_arg.attr);
+    RELEASE (opts_arg.exclude_random);
 }
 
 #define IS_SPACE(c) ((c) == ' ' || (c) == '\t')
@@ -694,6 +754,7 @@ parse_conf (const char *conf_file, struct conf *config)
     FILE *conf;
 
     conf = open_file (conf_file, "r");
+    STACK_FILE (conf);
 
     while (fgets (line, sizeof (line), conf))
       {
@@ -732,7 +793,7 @@ parse_conf (const char *conf_file, struct conf *config)
 /* NAME PARSING (end) */
 /* NAME VALIDATION (start) */
         for (p = opt; *p; p++)
-          if (!isalnum (*p) && *p != '-')
+          if (!isalnum ((unsigned char)*p) && *p != '-')
             vfprintf_fail (formats[FMT_CONF], conf_file, opt, "cannot be made of non-option characters");
 /* NAME VALIDATION (end) */
 /* VALUE PARSING (start) */
@@ -756,18 +817,20 @@ parse_conf (const char *conf_file, struct conf *config)
 
         /* save option name */
         cfg = xstrdup (opt);
+        STACK_VAR (cfg);
         /* save option value (allow empty ones) */
         val = strlen (value) ? xstrdup (value) : NULL;
+        STACK_VAR (val);
 
         assign_conf (conf_file, config, cfg, val);
-        free (cfg);
+        RELEASE (cfg);
       }
 
-    fclose (conf);
+    RELEASE (conf);
 }
 
 #define ASSIGN_CONF(str,val) do { \
-    free (str);                   \
+    RELEASE (str);                \
     str = val;                    \
 } while (false)
 
@@ -782,28 +845,50 @@ 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 if (streq (cfg, "rainbow-bg"))
+      ASSIGN_CONF (config->rainbow_bg, val);
     else
       vfprintf_fail (formats[FMT_CONF], conf_file, cfg, "not recognized");
 }
 
 static void
-init_conf_vars (const struct conf *config)
+init_conf_vars (const char *conf_file, const struct conf *config)
 {
     if (config->attr)
       process_opt_attr (config->attr, false);
     if (config->exclude_random)
       process_opt_exclude_random (config->exclude_random, false);
     if (config->omit_color_empty)
+      init_conf_boolean (config->omit_color_empty, &omit_color_empty, "omit-color-empty", NULL);
+
+    if (config->rainbow_fg || config->rainbow_bg)
       {
-        if (streq (config->omit_color_empty, "yes"))
-          omit_color_empty = true;
-        else if (streq (config->omit_color_empty, "no"))
-          omit_color_empty = false;
-        else
-          vfprintf_fail (formats[FMT_GENERIC], "omit-color-empty conf option is not valid");
+        if (config->rainbow_fg && config->rainbow_bg)
+          vfprintf_fail (formats[FMT_CONF_FILE], conf_file, "rainbow-fg and rainbow-bg option are mutually exclusive");
+
+        if (config->rainbow_fg)
+          init_conf_boolean (config->rainbow_fg, &rainbow_fg, "rainbow-fg", &rainbow_from_conf.fg);
+        else if (config->rainbow_bg)
+          init_conf_boolean (config->rainbow_bg, &rainbow_bg, "rainbow-bg", &rainbow_from_conf.bg);
       }
 }
 
+static void
+init_conf_boolean (const char *conf_var, bool *boolean_var, const char *name, bool *seen_opt)
+{
+    if (streq (conf_var, "yes"))
+      *boolean_var = true;
+    else if (streq (conf_var, "no"))
+      *boolean_var = false;
+    else
+      vfprintf_fail (formats[FMT_CONF_INIT], name, "conf option is not valid");
+
+    if (seen_opt)
+      *seen_opt = true;
+}
+
 static void
 print_hint (void)
 {
@@ -837,7 +922,7 @@ print_help (void)
         const char *code = entry->code;
         if (code)
           printf ("\t\t{\033[%s#\033[0m} [%c%c]%s%*s%s\n",
-                   code, toupper (*name), *name, name + 1, 10 - (int)strlen (name), " ", name);
+                   code, toupper ((unsigned char)*name), *name, name + 1, 10 - (int)strlen (name), " ", name);
         else
           printf ("\t\t{-} %s%*s%s\n", name, 13 - (int)strlen (name), " ", name);
       }
@@ -931,17 +1016,32 @@ static void
 cleanup (void)
 {
     if (stream && fileno (stream) != STDIN_FILENO)
-      fclose (stream);
+      RELEASE (stream);
 #if DEBUG
     if (log)
-      fclose (log);
+      RELEASE (log);
 #endif
 
     if (vars_list)
       {
         unsigned int i;
         for (i = 0; i < stacked_vars; i++)
-          free (vars_list[i]);
+          {
+            struct var_list *var = &vars_list[i];
+            switch (var->type)
+              {
+                case IS_GENERIC:
+                  free (var->ptr);
+                  break;
+                case IS_FILE:
+                  fclose (var->ptr);
+                  break;
+                case IS_UNUSED:
+                  break;
+                default: /* never reached */
+                  ABORT_TRACE ();
+              }
+          }
         free_null (vars_list);
       }
 }
@@ -952,19 +1052,21 @@ free_color_names (struct color_name **color_names)
     unsigned int i;
     for (i = 0; color_names[i]; i++)
       {
-        RELEASE_VAR (color_names[i]->name);
-        RELEASE_VAR (color_names[i]->orig);
-        RELEASE_VAR (color_names[i]);
+        RELEASE (color_names[i]->name);
+        RELEASE (color_names[i]->orig);
+        RELEASE (color_names[i]);
       }
 }
 
 static void
 free_conf (struct conf *config)
 {
-    free (config->attr);
-    free (config->color);
-    free (config->exclude_random);
-    free (config->omit_color_empty);
+    RELEASE (config->attr);
+    RELEASE (config->color);
+    RELEASE (config->exclude_random);
+    RELEASE (config->omit_color_empty);
+    RELEASE (config->rainbow_fg);
+    RELEASE (config->rainbow_bg);
 }
 
 static void
@@ -1038,6 +1140,30 @@ process_args (unsigned int arg_cnt, char **arg_strings, char *attr, const struct
           }
       }
 
+    /* --rainbow-bg */
+    if (rainbow_bg && !color_names[BACKGROUND])
+      vfprintf_fail ("background color required with %s", !rainbow_from_conf.bg ? "--rainbow-bg switch" : "rainbow-bg conf option");
+
+    /* --rainbow{-fg,-bg} */
+    if (rainbow_fg || rainbow_bg)
+      {
+        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 ? !rainbow_from_conf.fg ? "--rainbow-fg switch" : "rainbow-fg conf option"
+                               : !rainbow_from_conf.bg ? "--rainbow-bg switch" : "rainbow-bg conf option"
+                  );
+              }
+          }
+      }
+
     find_color_entries (color_names, colors);
     assert (colors[FOREGROUND] != NULL);
     free_color_names (color_names);
@@ -1077,6 +1203,7 @@ process_file_arg (const char *file_string, const char **file, FILE **stream)
               vfprintf_fail (formats[FMT_TYPE], file, "unrecognized type", get_file_type (sb.st_mode));
 
             *stream = open_file (file, "r");
+            STACK_FILE (*stream);
           }
         *file = file_string;
       }
@@ -1169,17 +1296,17 @@ gather_color_names (const char *color_string, char *attr, struct color_name **co
         assert (p != NULL);
 
         for (ch = color; *ch; ch++)
-          if (!isalpha (*ch))
+          if (!isalpha ((unsigned char)*ch))
             vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color, "cannot be made of non-alphabetic characters");
 
         for (ch = color + 1; *ch; ch++)
-          if (!islower (*ch))
+          if (!islower ((unsigned char)*ch))
             vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color, "cannot be in mixed lower/upper case");
 
         if (streq (color, "None"))
           vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color, "cannot be bold");
 
-        if (isupper (*color))
+        if (isupper ((unsigned char)*color))
           {
             switch (index)
               {
@@ -1201,13 +1328,13 @@ gather_color_names (const char *color_string, char *attr, struct color_name **co
         STACK_VAR (color_names[index]->orig);
 
         for (ch = color; *ch; ch++)
-          *ch = tolower (*ch);
+          *ch = tolower ((unsigned char)*ch);
 
         color_names[index]->name = xstrdup (color);
         STACK_VAR (color_names[index]->name);
       }
 
-    RELEASE_VAR (str);
+    RELEASE (str);
 }
 
 static void
@@ -1250,13 +1377,15 @@ read_print_stream (const char *attr, const struct color **colors, const char *fi
         if (feof (stream))
           {
             if (*line != '\0')
-              print_line (attr, colors, line, 0, true);
+              print_line (attr, colors, line, PARTIAL, true);
           }
         else if (*line != '\0')
           {
             char *p;
             if ((clean || clean_all) && (p = strrchr (line, '\033')))
               merge_print_line (line, p, stream);
+            else if (rainbow_fg || rainbow_bg)
+              print_line (attr, colors, line, PARTIAL, true);
             else
               print_line (attr, colors, line, 0, true);
           }
@@ -1473,6 +1602,36 @@ print_line (const char *attr, const struct color **colors, const char *const lin
     /* skip for --omit-color-empty? */
     else if (emit_colors)
       {
+        /* --rainbow{-fg,-bg} */
+        if (rainbow_fg || rainbow_bg)
+          {
+            const unsigned int color_sets[2][2] = { { FOREGROUND, BACKGROUND }, { BACKGROUND, FOREGROUND } };
+            unsigned int color_iter, color_cmp, set;
+            unsigned int index, max_index;
+
+            if (rainbow_fg)
+              set = 0;
+            else if (rainbow_bg)
+              set = 1;
+
+            color_iter = color_sets[set][0];
+            color_cmp  = color_sets[set][1];
+
+            max_index = tables[color_iter].count - 2; /* omit color default */
+
+            if (rainbow_index == 0)
+              rainbow_index = colors[color_iter]->index; /* init */
+            else if (rainbow_index > max_index)
+              rainbow_index = 1; /* black */
+
+            index = get_rainbow_index (colors, color_cmp, rainbow_index, max_index);
+
+            colors[color_iter] = (struct color *)&tables[color_iter].entries[index];
+
+            if (!(flags & PARTIAL))
+              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);
@@ -1487,6 +1646,33 @@ 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 color_cmp, unsigned int index, unsigned int max)
+{
+    if (skipable_rainbow_index (colors, color_cmp, index))
+      {
+        if (index + 1 > max)
+          {
+            if (skipable_rainbow_index (colors, color_cmp, 1))
+              return 2;
+            else
+              return 1;
+          }
+        else
+          return index + 1;
+      }
+    else
+      return index;
+}
+
+static bool
+skipable_rainbow_index (const struct color **colors, unsigned int color_cmp, unsigned int index)
+{
+    if (color_cmp == BACKGROUND && !colors[color_cmp])
+      return false;
+    return (index == colors[color_cmp]->index);
+}
+
 static void
 print_clean (const char *line)
 {
@@ -1567,10 +1753,10 @@ gather_esc_offsets (const char *p, const char **start, const char **end)
             do {
               check_values = false;
               iter++;
-              if (!isdigit (*p))
+              if (!isdigit ((unsigned char)*p))
                 break;
               digit = p;
-              while (isdigit (*p))
+              while (isdigit ((unsigned char)*p))
                 p++;
               if (p - digit > 2)
                 break;
@@ -1603,7 +1789,7 @@ gather_esc_offsets (const char *p, const char **start, const char **end)
 static bool
 validate_esc_clean_all (const char **p)
 {
-    while (isdigit (**p) || **p == ';')
+    while (isdigit ((unsigned char)**p) || **p == ';')
       (*p)++;
     return (**p == 'm');
 }
@@ -1811,7 +1997,7 @@ has_color_name (const char *str, const char *name)
     assert (strlen (str) > 0);
     assert (strlen (name) > 0);
 
-    if (!(*str == *name || *str == toupper (*name)))
+    if (!(*str == *name || *str == toupper ((unsigned char)*name)))
       return false;
     else if (*(name + 1) != '\0'
      && !((p = strstr (str + 1, name + 1)) && p == str + 1))
@@ -1855,41 +2041,63 @@ vfprintf_fail (const char *fmt, ...)
 }
 
 static void
-stack_var (void ***list, unsigned int *stacked, unsigned int index, void *ptr)
+stack (struct var_list **list, unsigned int *stacked, unsigned int index, void *ptr, enum var_type type)
 {
+    struct var_list *var;
     /* nothing to stack */
     if (ptr == NULL)
       return;
     if (!*list)
-      *list = xmalloc (sizeof (void *));
+      *list = xmalloc (sizeof (struct var_list));
     else
       {
         unsigned int i;
         for (i = 0; i < *stacked; i++)
-          if (!(*list)[i])
-            {
-              (*list)[i] = ptr;
-              return; /* reused */
-            }
-        *list = xrealloc (*list, (*stacked + 1) * sizeof (void *));
+          {
+            var = &(*list)[i];
+            if (var->type == IS_UNUSED)
+              {
+                var->ptr  = ptr;
+                var->type = type;
+                return; /* reused */
+              }
+          }
+        *list = xrealloc (*list, (*stacked + 1) * sizeof (struct var_list));
       }
-    (*list)[index] = ptr;
+    var = &(*list)[index];
+    var->ptr  = ptr;
+    var->type = type;
     (*stacked)++;
 }
 
 static void
-release_var (void **list, unsigned int stacked, void **ptr)
+release (struct var_list *list, unsigned int stacked, void **ptr)
 {
     unsigned int i;
     /* nothing to release */
     if (*ptr == NULL)
       return;
     for (i = 0; i < stacked; i++)
-      if (list[i] == *ptr)
-        {
-          free (*ptr);
-          *ptr = NULL;
-          list[i] = NULL;
-          return;
+      {
+        struct var_list *var = &list[i];
+        if (var->type != IS_UNUSED
+         && var->ptr == *ptr)
+          {
+            switch (var->type)
+              {
+                case IS_GENERIC:
+                  free (*ptr);
+                  break;
+                case IS_FILE:
+                  fclose (*ptr);
+                  break;
+                default: /* never reached */
+                  ABORT_TRACE ();
+              }
+            *ptr = NULL;
+            var->ptr  = NULL;
+            var->type = IS_UNUSED;
+            return;
         }
+    }
 }