]> git.ipfire.org Git - thirdparty/util-linux.git/commitdiff
lib/colors: support schemes customization
authorKarel Zak <kzak@redhat.com>
Mon, 5 May 2014 14:22:43 +0000 (16:22 +0200)
committerKarel Zak <kzak@redhat.com>
Tue, 13 May 2014 10:09:03 +0000 (12:09 +0200)
 * parse terminal-colors.d/*.scheme files, expected format is

    <name> <color-sequence>

 * supported color sequences:

     - color name (e.g. "red")
     - dir_colors compatible xx;yy (e.g. 01;31) where the sequence may
       contains control chars like \e \a  ..etc.

 * scheme is parsed on demand

Signed-off-by: Karel Zak <kzak@redhat.com>
include/colors.h
lib/colors.c

index 392bf38f1ab84152a2ffa2ceeb63f17f1b912f6c..e8ab3951a72701ce1e6803ff57c780db36e9fd71 100644 (file)
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2012 Ondrej Oprala <ooprala@redhat.com>
+ * Copyright (C) 2012-2014 Karel Zak <kzak@redhat.com>
  *
  * This file may be distributed under the terms of the
  * GNU Lesser General Public License.
@@ -62,12 +63,18 @@ extern void colors_off(void);
 extern void colors_on(void);
 
 
-/* Set the color to CLR_SCHEME */
-extern void color_fenable(const char *clr_scheme, FILE *f);
+/* Set the color */
+extern void color_fenable(const char *seq, FILE *f);
+extern void color_scheme_fenable(const char *name, const char *dflt, FILE *f);
 
-static inline void color_enable(const char *clr_scheme)
+static inline void color_enable(const char *seq)
 {
-       color_fenable(clr_scheme, stdout);
+       color_fenable(seq, stdout);
+}
+
+static inline void color_scheme_enable(const char *name, const char *dflt)
+{
+       color_scheme_fenable(name, dflt, stdout);
 }
 
 /* Reset colors to default */
@@ -78,8 +85,8 @@ static inline void color_disable(void)
        color_fdisable(stdout);
 }
 
-
-extern const char *colorscheme_from_string(const char *str);
+/* converts "red" to UL_COLOR_RED, etc. */
+extern const char *color_sequence_from_colorname(const char *str);
 
 
 #endif /* UTIL_LINUX_COLORS_H */
index 91677ba9be4d682d984d46a2c52bde4c89c3904c..cb342a0e2cca099a332c7b5b92d110740fc6145e 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2012 Ondrej Oprala <ooprala@redhat.com>
- * Copyright (C) 2014 Karel Zak <kzak@redhat.com>
+ * Copyright (C) 2012-2014 Karel Zak <kzak@redhat.com>
  *
  * This file may be distributed under the terms of the
  * GNU Lesser General Public License.
@@ -9,11 +9,12 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <dirent.h>
+#include <ctype.h>
 
 #include "c.h"
 #include "colors.h"
-#include "xalloc.h"
 #include "pathnames.h"
+#include "strutils.h"
 
 /*
  * terminal-colors.d file types
@@ -26,6 +27,11 @@ enum {
        __UL_COLORFILE_COUNT
 };
 
+struct ul_color_scheme {
+       char *name;
+       char *seq;
+};
+
 /*
  * Global colors control struct
  *
@@ -44,11 +50,16 @@ struct ul_color_ctl {
        const char      *utilname;      /* util name */
        const char      *termname;      /* terminal name ($TERM) */
 
-       char            *scheme;        /* path to scheme */
+       char            *sfile; /* path to scheme */
+
+       struct ul_color_scheme  *schemes;       /* array with color schemes */
+       size_t                  nschemes;       /* number of the items */
+       size_t                  schemes_sz;     /* number of the allocated items */
 
        int             mode;           /* UL_COLORMODE_* */
        unsigned int    has_colors : 1, /* based on mode and scores[] */
                        disabled   : 1, /* disable colors */
+                       cs_configured : 1, /* color schemes read */
                        configured : 1; /* terminal-colors.d parsed */
 
        int             scores[__UL_COLORFILE_COUNT];   /* the best match */
@@ -56,17 +67,67 @@ struct ul_color_ctl {
 
 static struct ul_color_ctl ul_colors;
 
+static void colors_free_schemes(struct ul_color_ctl *cc);
+static int colors_read_schemes(struct ul_color_ctl *cc);
+
+/*
+ * qsort/bsearch buddy
+ */
+static int cmp_scheme_name(const void *a0, const void *b0)
+{
+       struct ul_color_scheme  *a = (struct ul_color_scheme *) a0,
+                               *b = (struct ul_color_scheme *) b0;
+       return strcmp(a->name, b->name);
+}
+
+/*
+ * Maintains human readable color names
+ */
+const char *color_sequence_from_colorname(const char *str)
+{
+       static const struct ul_color_scheme basic_schemes[] = {
+               { "black",      UL_COLOR_BLACK           },
+               { "blue",       UL_COLOR_BLUE            },
+               { "brown",      UL_COLOR_BROWN           },
+               { "cyan",       UL_COLOR_CYAN            },
+               { "darkgray",   UL_COLOR_DARK_GRAY       },
+               { "gray",       UL_COLOR_GRAY            },
+               { "green",      UL_COLOR_GREEN           },
+               { "lightblue",  UL_COLOR_BOLD_BLUE       },
+               { "lightcyan",  UL_COLOR_BOLD_CYAN       },
+               { "lightgray,", UL_COLOR_GRAY            },
+               { "lightgreen", UL_COLOR_BOLD_GREEN      },
+               { "lightmagenta", UL_COLOR_BOLD_MAGENTA  },
+               { "lightred",   UL_COLOR_BOLD_RED        },
+               { "magenta",    UL_COLOR_MAGENTA         },
+               { "red",        UL_COLOR_RED             },
+               { "yellow",     UL_COLOR_BOLD_YELLOW     },
+       };
+       struct ul_color_scheme key = { .name = (char *) str }, *res;
+
+       if (!str)
+               return NULL;
+
+       res = bsearch(&key, basic_schemes, ARRAY_SIZE(basic_schemes),
+                               sizeof(struct ul_color_scheme),
+                               cmp_scheme_name);
+       return res ? res->seq : NULL;
+}
+
+
 /*
- * Reset control struct (note that we don't allocate the struct)
+ * Resets control struct (note that we don't allocate the struct)
  */
 static void colors_reset(struct ul_color_ctl *cc)
 {
        if (!cc)
                return;
 
-       free(cc->scheme);
+       colors_free_schemes(cc);
+
+       free(cc->sfile);
 
-       cc->scheme = NULL;
+       cc->sfile = NULL;
        cc->utilname = NULL;
        cc->termname = NULL;
        cc->mode = UL_COLORMODE_UNDEF;
@@ -85,12 +146,18 @@ static void colors_debug(struct ul_color_ctl *cc)
        printf("Colors:\n");
        printf("\tutilname = '%s'\n", cc->utilname);
        printf("\ttermname = '%s'\n", cc->termname);
-       printf("\tscheme = '%s'\n", cc->scheme);
+       printf("\tscheme file = '%s'\n", cc->sfile);
        printf("\tmode = %s\n",
                        cc->mode == UL_COLORMODE_UNDEF ? "undefined" :
                        cc->mode == UL_COLORMODE_AUTO ?  "auto" :
                        cc->mode == UL_COLORMODE_NEVER ? "never" :
                        cc->mode == UL_COLORMODE_ALWAYS ? "always" : "???");
+       printf("\thas_colors = %d\n", cc->has_colors);
+       printf("\tdisabled = %d\n", cc->disabled);
+       printf("\tconfigured = %d\n", cc->configured);
+       printf("\tcs configured = %d\n", cc->cs_configured);
+
+       fputc('\n', stdout);
 
        for (i = 0; i < ARRAY_SIZE(cc->scores); i++)
                printf("\tscore %s = %d\n",
@@ -98,6 +165,17 @@ static void colors_debug(struct ul_color_ctl *cc)
                                i == UL_COLORFILE_ENABLE ? "enable" :
                                i == UL_COLORFILE_SCHEME ? "scheme" : "???",
                                cc->scores[i]);
+
+       fputc('\n', stdout);
+
+       for (i = 0; i < cc->nschemes; i++) {
+               printf("\tscheme #%02d ", i);
+               color_scheme_enable(cc->schemes[i].name, NULL);
+               fputs(cc->schemes[i].name, stdout);
+               color_disable();
+               fputc('\n', stdout);
+       }
+       fputc('\n', stdout);
 }
 
 #endif
@@ -159,7 +237,7 @@ static int colors_readdir(struct ul_color_ctl *cc, const char *dirname)
        DIR *dir;
        int rc = 0;
        struct dirent *d;
-       char scheme[PATH_MAX] = { '\0' };
+       char sfile[PATH_MAX] = { '\0' };
        size_t namesz, termsz;
 
        if (!dirname || !cc || !cc->utilname || !*cc->utilname)
@@ -194,10 +272,12 @@ static int colors_readdir(struct ul_color_ctl *cc, const char *dirname)
                        score += 20;
                if (tk_term)
                        score += 10;
+
                /*
                fprintf(stderr, "%20s score: %2d [cur max: %2d]\n",
                                d->d_name, score, cc->scores[type]);
                */
+
                if (score < cc->scores[type])
                        continue;
 
@@ -205,19 +285,19 @@ static int colors_readdir(struct ul_color_ctl *cc, const char *dirname)
                if (tk_namesz != namesz
                    || strncmp(tk_name, cc->utilname, namesz) != 0)
                        continue;
-               if (tk_termsz != termsz
-                   || termsz == 0
-                   || strncmp(tk_term, cc->termname, termsz) != 0)
+
+               if (tk_termsz && (termsz == 0 || tk_termsz != termsz ||
+                                 strncmp(tk_term, cc->termname, termsz) != 0))
                        continue;
 
                cc->scores[type] = score;
                if (type == UL_COLORFILE_SCHEME)
-                       strncpy(scheme, d->d_name, sizeof(scheme));
+                       strncpy(sfile, d->d_name, sizeof(sfile));
        }
 
-       if (*scheme) {
-               scheme[sizeof(scheme) - 1] = '\0';
-               if (asprintf(&cc->scheme, "%s/%s", dirname, scheme) <= 0)
+       if (*sfile) {
+               sfile[sizeof(sfile) - 1] = '\0';
+               if (asprintf(&cc->sfile, "%s/%s", dirname, sfile) <= 0)
                        rc = -ENOMEM;
        }
 
@@ -252,6 +332,194 @@ static char *colors_get_homedir(char *buf, size_t bufsz)
        return NULL;
 }
 
+/* canonicalize sequence */
+static int cn_sequence(const char *str, char **seq)
+{
+       char *in, *out;
+
+       if (!str)
+               return -EINVAL;
+
+       *seq = NULL;
+
+       /* convert logical names like "red" to the real sequence */
+       if (*str != '\\' && isalpha(*str)) {
+               const char *s = color_sequence_from_colorname(str);
+               *seq = strdup(s ? s : str);
+
+               return *seq ? 0 : -ENOMEM;
+       }
+
+       /* convert xx;yy sequences to "\033[xx;yy" */
+       if (asprintf(seq, "\033[%sm", str) < 1)
+               return -ENOMEM;
+
+       for (in = *seq, out = *seq; in && *in; in++) {
+               if (*in != '\\') {
+                       *out++ = *in;
+                       continue;
+               }
+               switch(*(in + 1)) {
+               case 'a':
+                       *out++ = '\a';  /* Bell */
+                       break;
+               case 'b':
+                       *out++ = '\b';  /* Backspace */
+                       break;
+               case 'e':
+                       *out++ = '\033';        /* Escape */
+                       break;
+               case 'f':
+                       *out++ = '\f';  /* Form Feed */
+                       break;
+               case 'n':
+                       *out++ = '\n';  /* Newline */
+                       break;
+               case 'r':
+                       *out++ = '\r';  /* Carriage Return */
+                       break;
+               case 't':
+                       *out++ = '\t';  /* Tab */
+                       break;
+               case 'v':
+                       *out++ = '\v';  /* Vertical Tab */
+                       break;
+               case '\\':
+                       *out++ = '\\';  /* Backslash */
+                       break;
+               case '_':
+                       *out++ = ' ';   /* Space */
+                       break;
+               case '#':
+                       *out++ = '#';   /* Hash mark */
+                       break;
+               case '?':
+                       *out++ = '?';   /* Qestion mark */
+                       break;
+               default:
+                       *out++ = *in;
+                       *out++ = *(in + 1);
+                       break;
+               }
+               in++;
+       }
+       *out = '\0';
+
+       return 0;
+}
+
+
+/*
+ * Adds one color sequence to array with color scheme,
+ * @seq and @name have to be allocated strings
+ */
+static int colors_add_scheme(struct ul_color_ctl *cc,
+                            char *name,
+                            char *seq0)
+{
+       struct ul_color_scheme *cs;
+       char *seq;
+       int rc;
+
+       if (!cc || !name || !*name || !seq0 || !*seq0)
+               return -EINVAL;
+
+       rc = cn_sequence(seq0, &seq);
+       if (rc)
+               return rc;
+       free(seq0);
+
+       /* convert logical name (e.g. "red") to real ESC code */
+       if (isalpha(*seq)) {
+               const char *s = color_sequence_from_colorname(seq);
+               char *p;
+
+               if (!s)
+                       return -EINVAL;
+               p = strdup(s);
+               if (!p)
+                       return -ENOMEM;
+               free(seq);
+               seq = p;
+       }
+
+       /* enlarge the array */
+       if (cc->nschemes == cc->schemes_sz) {
+               void *tmp = realloc(cc->schemes, (cc->nschemes + 10)
+                                       * sizeof(struct ul_color_scheme));
+               if (!tmp)
+                       return -ENOMEM;
+               cc->schemes = tmp;
+               cc->schemes_sz = cc->nschemes + 10;
+       }
+
+       /* add a new item */
+       cs = &cc->schemes[cc->nschemes++];
+       cs->name = name;
+       cs->seq = seq;
+
+       return 0;
+}
+
+/*
+ * Deallocates all regards to color schemes
+ */
+static void colors_free_schemes(struct ul_color_ctl *cc)
+{
+       size_t i;
+
+       for (i = 0; i < cc->nschemes; i++) {
+               free(cc->schemes[i].name);
+               free(cc->schemes[i].seq);
+       }
+
+       free(cc->schemes);
+       cc->schemes = NULL;
+       cc->nschemes = 0;
+       cc->schemes_sz = 0;
+}
+
+/*
+ * The scheme configuration has to be sorted for bsearch
+ */
+static void colors_sort_schemes(struct ul_color_ctl *cc)
+{
+       if (!cc->nschemes)
+               return;
+
+       qsort(cc->schemes, cc->nschemes,
+             sizeof(struct ul_color_scheme), cmp_scheme_name);
+}
+
+/*
+ * Returns just one color scheme
+ */
+static struct ul_color_scheme *colors_get_scheme(struct ul_color_ctl *cc,
+                                                const char *name)
+{
+       struct ul_color_scheme key = { .name = (char *) name}, *res;
+
+       if (!cc || !name || !*name)
+               return NULL;
+
+       if (!cc->cs_configured) {
+               int rc = colors_read_schemes(cc);
+               if (rc)
+                       return NULL;
+       }
+       if (!cc->nschemes)
+               return NULL;
+
+       res = bsearch(&key, cc->schemes, cc->nschemes,
+                               sizeof(struct ul_color_scheme),
+                               cmp_scheme_name);
+
+       return res && res->seq ? res  : NULL;
+}
+
+/*
+ * Parses filenames in terminal-colors.d
+ */
 static int colors_read_configuration(struct ul_color_ctl *cc)
 {
        int rc = -ENOENT;
@@ -270,8 +538,71 @@ static int colors_read_configuration(struct ul_color_ctl *cc)
 }
 
 /*
- * Initialize private color control struct, @mode is UL_COLORMODE_*
- * and @name is util argv[0]
+ * Reads terminal-colors.d/ scheme file into array schemes
+ */
+static int colors_read_schemes(struct ul_color_ctl *cc)
+{
+       int rc = 0;
+       FILE *f = NULL;
+       char buf[BUFSIZ];
+
+       if (!cc->configured)
+               rc = colors_read_configuration(cc);
+
+       cc->cs_configured = 1;
+
+       if (rc || !cc->sfile)
+               goto done;
+
+       f = fopen(cc->sfile, "r");
+       if (!f) {
+               rc = -errno;
+               goto done;
+       }
+
+       while (fgets(buf, sizeof(buf), f)) {
+               char *cn = NULL, *seq = NULL;
+               char *p = strchr(buf, '\n');
+
+               if (!p) {
+                       if (feof(f))
+                               p = strchr(buf, '\0');
+                       else {
+                               rc = -errno;
+                               goto done;
+                       }
+               }
+               *p = '\0';
+               p = (char *) skip_blank(buf);
+               if (*p == '\0' || *p == '#')
+                       continue;
+
+               rc = sscanf(p,  UL_SCNsA" "     /* name */
+                               UL_SCNsA,       /* color */
+                               &cn, &seq);
+               if (rc == 2 && cn && seq)
+                       rc = colors_add_scheme(cc, cn, seq);    /* set rc=0 on success */
+               if (rc) {
+                       free(cn);
+                       free(seq);
+               }
+               rc = 0;
+       }
+
+done:
+       if (f)
+               fclose(f);
+       colors_sort_schemes(cc);
+
+       return rc;
+}
+
+/*
+ * Initialize private color control struct and initialize the colors
+ * status. The color schemes are parsed on demand by colors_get_scheme().
+ *
+ * @mode: UL_COLORMODE_*
+ * @name: util argv[0]
  */
 int colors_init(int mode, const char *name)
 {
@@ -311,33 +642,65 @@ int colors_init(int mode, const char *name)
        return cc->has_colors;
 }
 
+/*
+ * Temporary disable colors (this setting is independent on terminal-colors.d/)
+ */
 void colors_off(void)
 {
        ul_colors.disabled = 1;
 }
 
+/*
+ * Enable colors
+ */
 void colors_on(void)
 {
        ul_colors.disabled = 0;
 }
 
+/*
+ * Is terminal-colors.d/ configured to use colors?
+ */
 int colors_wanted(void)
 {
-       return !ul_colors.disabled && ul_colors.has_colors;
+       return ul_colors.has_colors;
+}
+
+/*
+ * Enable @seq color
+ */
+void color_fenable(const char *seq, FILE *f)
+{
+       if (!ul_colors.disabled && ul_colors.has_colors && seq)
+               fputs(seq, f);
 }
 
-void color_fenable(const char *color_scheme, FILE *f)
+/*
+ * Enable color by logical @name
+ */
+void color_scheme_fenable(const char *name, const char *dflt, FILE *f)
 {
-       if (!ul_colors.disabled && ul_colors.has_colors && color_scheme)
-               fputs(color_scheme, f);
+       struct ul_color_scheme *cs;
+
+       if (ul_colors.disabled || !ul_colors.has_colors)
+               return;
+
+       cs = colors_get_scheme(&ul_colors, name);
+       color_fenable(cs && cs->seq ? cs->seq : dflt, f);
 }
 
+/*
+ * Disable previously enabled color
+ */
 void color_fdisable(FILE *f)
 {
        if (!ul_colors.disabled && ul_colors.has_colors)
                fputs(UL_COLOR_RESET, f);
 }
 
+/*
+ * Parses @str to return UL_COLORMODE_*
+ */
 int colormode_from_string(const char *str)
 {
        size_t i;
@@ -361,6 +724,9 @@ int colormode_from_string(const char *str)
        return -EINVAL;
 }
 
+/*
+ * Parses @str and exit(EXIT_FAILURE) on error
+ */
 int colormode_or_err(const char *str, const char *errmsg)
 {
        const char *p = str && *str == '=' ? str + 1 : str;
@@ -373,89 +739,59 @@ int colormode_or_err(const char *str, const char *errmsg)
        return colormode;
 }
 
-struct colorscheme {
-       const char *name, *scheme;
-};
-
-static int cmp_colorscheme_name(const void *a0, const void *b0)
-{
-       struct colorscheme *a = (struct colorscheme *) a0,
-                          *b = (struct colorscheme *) b0;
-       return strcmp(a->name, b->name);
-}
-
-const char *colorscheme_from_string(const char *str)
-{
-       static const struct colorscheme basic_schemes[] = {
-               { "black",      UL_COLOR_BLACK           },
-               { "blue",       UL_COLOR_BLUE            },
-               { "brown",      UL_COLOR_BROWN           },
-               { "cyan",       UL_COLOR_CYAN            },
-               { "darkgray",   UL_COLOR_DARK_GRAY       },
-               { "gray",       UL_COLOR_GRAY            },
-               { "green",      UL_COLOR_GREEN           },
-               { "lightblue",  UL_COLOR_BOLD_BLUE       },
-               { "lightcyan",  UL_COLOR_BOLD_CYAN       },
-               { "lightgray,", UL_COLOR_GRAY            },
-               { "lightgreen", UL_COLOR_BOLD_GREEN      },
-               { "lightmagenta", UL_COLOR_BOLD_MAGENTA  },
-               { "lightred",   UL_COLOR_BOLD_RED        },
-               { "magenta",    UL_COLOR_MAGENTA         },
-               { "red",        UL_COLOR_RED             },
-               { "yellow",     UL_COLOR_BOLD_YELLOW     },
-       };
-       struct colorscheme key = { .name = str }, *res;
-       if (!str)
-               return NULL;
-
-       res = bsearch(&key, basic_schemes, ARRAY_SIZE(basic_schemes),
-                               sizeof(struct colorscheme),
-                               cmp_colorscheme_name);
-       return res ? res->scheme : NULL;
-}
-
 #ifdef TEST_PROGRAM
 # include <getopt.h>
 int main(int argc, char *argv[])
 {
        static const struct option longopts[] = {
-               { "mode",     optional_argument, 0, 'm' },
-               { "color",    required_argument, 0, 'c' },
-               { "read-dir", required_argument, 0, 'd' },
+               { "mode",       required_argument, 0, 'm' },
+               { "color",      required_argument, 0, 'c' },
+               { "color-scheme", required_argument, 0, 'C' },
+               { "name",       required_argument, 0, 'n' },
                { NULL, 0, 0, 0 }
        };
        int c, mode = UL_COLORMODE_UNDEF;       /* default */
-       const char *color = "red", *dir = NULL;
+       const char *color = "red", *name = NULL, *color_scheme = NULL;
+       const char *seq = NULL;
 
-       while ((c = getopt_long(argc, argv, "m::c:d:", longopts, NULL)) != -1) {
+       while ((c = getopt_long(argc, argv, "C:c:m:n:", longopts, NULL)) != -1) {
                switch (c) {
-               case 'm':
-                       if (optarg)
-                               mode = colormode_or_err(optarg, "unsupported color mode");
-                       break;
                case 'c':
                        color = optarg;
                        break;
-               case 'd':
-                       dir = optarg;
+               case 'C':
+                       color_scheme = optarg;
+                       break;
+               case 'm':
+                       mode = colormode_or_err(optarg, "unsupported color mode");
+                       break;
+               case 'n':
+                       name = optarg;
                        break;
+               default:
+                       fprintf(stderr, "usage: %s [options]\n"
+                       " -m, --mode <auto|never|always>  default is undefined\n"
+                       " -c, --color <red|blue|...>      color for the test message\n"
+                       " -C, --color-scheme <name>       color for the test message\n"
+                       " -n, --name <utilname>           util name\n",
+                       program_invocation_short_name);
+                       return EXIT_FAILURE;
                }
        }
 
-       if (dir) {
-               struct ul_color_ctl cc = { .utilname = "foo",
-                                          .termname = "footerm" };
-
-               colors_readdir(&cc, dir);
-               colors_debug(&cc);
-               colors_reset(&cc);
-       }
+       colors_init(mode, name ? name : program_invocation_short_name);
 
-       colors_init(mode, program_invocation_short_name);
+       seq = color_sequence_from_colorname(color);
 
-       color_enable(colorscheme_from_string(color));
+       if (color_scheme)
+               color_scheme_enable(color_scheme, seq);
+       else
+               color_enable(seq);
        printf("Hello World!");
        color_disable();
+       fputc('\n', stdout);
+
+       colors_debug(&ul_colors);
 
        return EXIT_SUCCESS;
 }