]> git.ipfire.org Git - thirdparty/util-linux.git/blob - lib/colors.c
lib/loopdev.c: Inline loopcxt_has_device
[thirdparty/util-linux.git] / lib / colors.c
1 /*
2 * Copyright (C) 2012 Ondrej Oprala <ooprala@redhat.com>
3 * Copyright (C) 2012-2014 Karel Zak <kzak@redhat.com>
4 *
5 * This file may be distributed under the terms of the
6 * GNU Lesser General Public License.
7 */
8 #include <assert.h>
9 #include <sys/stat.h>
10 #include <sys/types.h>
11 #include <dirent.h>
12 #include <ctype.h>
13
14 #if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW)
15 # if defined(HAVE_NCURSESW_NCURSES_H)
16 # include <ncursesw/ncurses.h>
17 # elif defined(HAVE_NCURSES_NCURSES_H)
18 # include <ncurses/ncurses.h>
19 # elif defined(HAVE_NCURSES_H)
20 # include <ncurses.h>
21 # endif
22 # if defined(HAVE_NCURSESW_TERM_H)
23 # include <ncursesw/term.h>
24 # elif defined(HAVE_NCURSES_TERM_H)
25 # include <ncurses/term.h>
26 # elif defined(HAVE_TERM_H)
27 # include <term.h>
28 # endif
29 #endif
30
31 #include "c.h"
32 #include "colors.h"
33 #include "pathnames.h"
34 #include "strutils.h"
35
36 #include "debug.h"
37
38 /*
39 * Default behavior, maybe be override by terminal-colors.d/{enable,disable}.
40 */
41 #ifdef USE_COLORS_BY_DEFAULT
42 # define UL_COLORMODE_DEFAULT UL_COLORMODE_AUTO /* check isatty() */
43 #else
44 # define UL_COLORMODE_DEFAULT UL_COLORMODE_NEVER /* no colors by default */
45 #endif
46
47 /*
48 * terminal-colors.d debug stuff
49 */
50 static UL_DEBUG_DEFINE_MASK(termcolors);
51 UL_DEBUG_DEFINE_MASKNAMES(termcolors) = UL_DEBUG_EMPTY_MASKNAMES;
52
53 #define TERMCOLORS_DEBUG_INIT (1 << 1)
54 #define TERMCOLORS_DEBUG_CONF (1 << 2)
55 #define TERMCOLORS_DEBUG_SCHEME (1 << 3)
56 #define TERMCOLORS_DEBUG_ALL 0xFFFF
57
58 #define DBG(m, x) __UL_DBG(termcolors, TERMCOLORS_DEBUG_, m, x)
59 #define ON_DBG(m, x) __UL_DBG_CALL(termcolors, TERMCOLORS_DEBUG_, m, x)
60
61 /*
62 * terminal-colors.d file types
63 */
64 enum {
65 UL_COLORFILE_DISABLE, /* .disable */
66 UL_COLORFILE_ENABLE, /* .enable */
67 UL_COLORFILE_SCHEME, /* .scheme */
68
69 __UL_COLORFILE_COUNT
70 };
71
72 struct ul_color_scheme {
73 char *name;
74 char *seq;
75 };
76
77 /*
78 * Global colors control struct
79 *
80 * The terminal-colors.d/ evaluation is based on "scores":
81 *
82 * filename score
83 * ---------------------------------------
84 * type 1
85 * @termname.type 10 + 1
86 * utilname.type 20 + 1
87 * utilname@termname.type 20 + 10 + 1
88 *
89 * the match with higher score wins. The score is per type.
90 */
91 struct ul_color_ctl {
92 const char *utilname; /* util name */
93 const char *termname; /* terminal name ($TERM) */
94
95 char *sfile; /* path to scheme */
96
97 struct ul_color_scheme *schemes; /* array with color schemes */
98 size_t nschemes; /* number of the items */
99 size_t schemes_sz; /* number of the allocated items */
100
101 int mode; /* UL_COLORMODE_* */
102 unsigned int has_colors : 1, /* based on mode and scores[] */
103 disabled : 1, /* disable colors */
104 cs_configured : 1, /* color schemes read */
105 configured : 1; /* terminal-colors.d parsed */
106
107 int scores[__UL_COLORFILE_COUNT]; /* the best match */
108 };
109
110 /*
111 * Control struct, globally shared.
112 */
113 static struct ul_color_ctl ul_colors;
114
115 static void colors_free_schemes(struct ul_color_ctl *cc);
116 static int colors_read_schemes(struct ul_color_ctl *cc);
117
118 /*
119 * qsort/bsearch buddy
120 */
121 static int cmp_scheme_name(const void *a0, const void *b0)
122 {
123 const struct ul_color_scheme *a = (const struct ul_color_scheme *) a0,
124 *b = (const struct ul_color_scheme *) b0;
125 return strcmp(a->name, b->name);
126 }
127
128 /*
129 * Resets control struct (note that we don't allocate the struct)
130 */
131 static void colors_reset(struct ul_color_ctl *cc)
132 {
133 if (!cc)
134 return;
135
136 colors_free_schemes(cc);
137
138 free(cc->sfile);
139
140 cc->sfile = NULL;
141 cc->utilname = NULL;
142 cc->termname = NULL;
143 cc->mode = UL_COLORMODE_UNDEF;
144
145 memset(cc->scores, 0, sizeof(cc->scores));
146 }
147
148 static void colors_debug(struct ul_color_ctl *cc)
149 {
150 size_t i;
151
152 if (!cc)
153 return;
154
155 printf("Colors:\n");
156 printf("\tutilname = '%s'\n", cc->utilname);
157 printf("\ttermname = '%s'\n", cc->termname);
158 printf("\tscheme file = '%s'\n", cc->sfile);
159 printf("\tmode = %s\n",
160 cc->mode == UL_COLORMODE_UNDEF ? "undefined" :
161 cc->mode == UL_COLORMODE_AUTO ? "auto" :
162 cc->mode == UL_COLORMODE_NEVER ? "never" :
163 cc->mode == UL_COLORMODE_ALWAYS ? "always" : "???");
164 printf("\thas_colors = %d\n", cc->has_colors);
165 printf("\tdisabled = %d\n", cc->disabled);
166 printf("\tconfigured = %d\n", cc->configured);
167 printf("\tcs configured = %d\n", cc->cs_configured);
168
169 fputc('\n', stdout);
170
171 for (i = 0; i < ARRAY_SIZE(cc->scores); i++)
172 printf("\tscore %s = %d\n",
173 i == UL_COLORFILE_DISABLE ? "disable" :
174 i == UL_COLORFILE_ENABLE ? "enable" :
175 i == UL_COLORFILE_SCHEME ? "scheme" : "???",
176 cc->scores[i]);
177
178 fputc('\n', stdout);
179
180 for (i = 0; i < cc->nschemes; i++) {
181 printf("\tscheme #%02zu ", i);
182 color_scheme_enable(cc->schemes[i].name, NULL);
183 fputs(cc->schemes[i].name, stdout);
184 color_disable();
185 fputc('\n', stdout);
186 }
187 fputc('\n', stdout);
188 }
189
190 /*
191 * Parses [[<utilname>][@<termname>].]<type>
192 */
193 static int filename_to_tokens(const char *str,
194 const char **name, size_t *namesz,
195 const char **term, size_t *termsz,
196 int *filetype)
197 {
198 const char *type_start, *term_start, *p;
199
200 if (!str || !*str || *str == '.' || strlen(str) > PATH_MAX)
201 return -EINVAL;
202
203 /* parse .type */
204 p = strrchr(str, '.');
205 type_start = p ? p + 1 : str;
206
207 if (strcmp(type_start, "disable") == 0)
208 *filetype = UL_COLORFILE_DISABLE;
209 else if (strcmp(type_start, "enable") == 0)
210 *filetype = UL_COLORFILE_ENABLE;
211 else if (strcmp(type_start, "scheme") == 0)
212 *filetype = UL_COLORFILE_SCHEME;
213 else {
214 DBG(CONF, ul_debug("unknown type '%s'", type_start));
215 return 1; /* unknown type */
216 }
217
218 if (type_start == str)
219 return 0; /* "type" only */
220
221 /* parse @termname */
222 p = strchr(str, '@');
223 term_start = p ? p + 1 : NULL;
224 if (term_start) {
225 *term = term_start;
226 *termsz = type_start - term_start - 1;
227 if (term_start - 1 == str)
228 return 0; /* "@termname.type" */
229 }
230
231 /* parse utilname */
232 p = term_start ? term_start : type_start;
233 *name = str;
234 *namesz = p - str - 1;
235
236 return 0;
237 }
238
239 /*
240 * Scans @dirname and select the best matches for UL_COLORFILE_* types.
241 * The result is stored to cc->scores. The path to the best "scheme"
242 * file is stored to cc->scheme.
243 */
244 static int colors_readdir(struct ul_color_ctl *cc, const char *dirname)
245 {
246 DIR *dir;
247 int rc = 0;
248 struct dirent *d;
249 char sfile[PATH_MAX] = { '\0' };
250 size_t namesz, termsz;
251
252 if (!dirname || !cc || !cc->utilname || !*cc->utilname)
253 return -EINVAL;
254
255 DBG(CONF, ul_debug("reading dir: '%s'", dirname));
256
257 dir = opendir(dirname);
258 if (!dir)
259 return -errno;
260
261 namesz = strlen(cc->utilname);
262 termsz = cc->termname ? strlen(cc->termname) : 0;
263
264 while ((d = readdir(dir))) {
265 int type, score = 1;
266 const char *tk_name = NULL, *tk_term = NULL;
267 size_t tk_namesz = 0, tk_termsz = 0;
268
269 if (*d->d_name == '.')
270 continue;
271 #ifdef _DIRENT_HAVE_D_TYPE
272 if (d->d_type != DT_UNKNOWN && d->d_type != DT_LNK &&
273 d->d_type != DT_REG)
274 continue;
275 #endif
276 if (filename_to_tokens(d->d_name,
277 &tk_name, &tk_namesz,
278 &tk_term, &tk_termsz, &type) != 0)
279 continue;
280
281 /* count theoretical score before we check names to avoid
282 * unnecessary strcmp() */
283 if (tk_name)
284 score += 20;
285 if (tk_term)
286 score += 10;
287
288 DBG(CONF, ul_debug("item '%s': score=%d "
289 "[cur: %d, name(%zu): %s, term(%zu): %s]",
290 d->d_name, score, cc->scores[type],
291 tk_namesz, tk_name,
292 tk_termsz, tk_term));
293
294
295 if (score < cc->scores[type])
296 continue;
297
298 /* filter out by names */
299 if (tk_namesz && (tk_namesz != namesz ||
300 strncmp(tk_name, cc->utilname, namesz) != 0))
301 continue;
302
303 if (tk_termsz && (termsz == 0 || tk_termsz != termsz ||
304 strncmp(tk_term, cc->termname, termsz) != 0))
305 continue;
306
307 DBG(CONF, ul_debug("setting '%s' from %d -to-> %d",
308 type == UL_COLORFILE_SCHEME ? "scheme" :
309 type == UL_COLORFILE_DISABLE ? "disable" :
310 type == UL_COLORFILE_ENABLE ? "enable" : "???",
311 cc->scores[type], score));
312 cc->scores[type] = score;
313 if (type == UL_COLORFILE_SCHEME)
314 strncpy(sfile, d->d_name, sizeof(sfile));
315 }
316
317 if (*sfile) {
318 sfile[sizeof(sfile) - 1] = '\0';
319 if (asprintf(&cc->sfile, "%s/%s", dirname, sfile) <= 0)
320 rc = -ENOMEM;
321 }
322
323 closedir(dir);
324 return rc;
325 }
326
327 /* atexit() wrapper */
328 static void colors_deinit(void)
329 {
330 colors_reset(&ul_colors);
331 }
332
333 /*
334 * Returns path to $XDG_CONFIG_HOME/terminal-colors.d
335 */
336 static char *colors_get_homedir(char *buf, size_t bufsz)
337 {
338 char *p = getenv("XDG_CONFIG_HOME");
339
340 if (p) {
341 snprintf(buf, bufsz, "%s/" _PATH_TERMCOLORS_DIRNAME, p);
342 return buf;
343 }
344
345 p = getenv("HOME");
346 if (p) {
347 snprintf(buf, bufsz, "%s/.config/" _PATH_TERMCOLORS_DIRNAME, p);
348 return buf;
349 }
350
351 return NULL;
352 }
353
354 /* canonicalize sequence */
355 static int cn_sequence(const char *str, char **seq)
356 {
357 char *in, *out;
358 int len;
359
360 if (!str)
361 return -EINVAL;
362
363 *seq = NULL;
364
365 /* convert logical names like "red" to the real sequence */
366 if (*str != '\\' && isalpha(*str)) {
367 const char *s = color_sequence_from_colorname(str);
368 *seq = strdup(s ? s : str);
369
370 return *seq ? 0 : -ENOMEM;
371 }
372
373 /* convert xx;yy sequences to "\033[xx;yy" */
374 if ((len = asprintf(seq, "\033[%sm", str)) < 1)
375 return -ENOMEM;
376
377 for (in = *seq, out = *seq; in && *in; in++) {
378 if (*in != '\\') {
379 *out++ = *in;
380 continue;
381 }
382 switch(*(in + 1)) {
383 case 'a':
384 *out++ = '\a'; /* Bell */
385 break;
386 case 'b':
387 *out++ = '\b'; /* Backspace */
388 break;
389 case 'e':
390 *out++ = '\033'; /* Escape */
391 break;
392 case 'f':
393 *out++ = '\f'; /* Form Feed */
394 break;
395 case 'n':
396 *out++ = '\n'; /* Newline */
397 break;
398 case 'r':
399 *out++ = '\r'; /* Carriage Return */
400 break;
401 case 't':
402 *out++ = '\t'; /* Tab */
403 break;
404 case 'v':
405 *out++ = '\v'; /* Vertical Tab */
406 break;
407 case '\\':
408 *out++ = '\\'; /* Backslash */
409 break;
410 case '_':
411 *out++ = ' '; /* Space */
412 break;
413 case '#':
414 *out++ = '#'; /* Hash mark */
415 break;
416 case '?':
417 *out++ = '?'; /* Question mark */
418 break;
419 default:
420 *out++ = *in;
421 *out++ = *(in + 1);
422 break;
423 }
424 in++;
425 }
426
427 assert ((out - *seq) <= len);
428 *out = '\0';
429
430 return 0;
431 }
432
433
434 /*
435 * Adds one color sequence to array with color scheme.
436 * When returning success (0) this function takes ownership of
437 * @seq and @name, which have to be allocated strings.
438 */
439 static int colors_add_scheme(struct ul_color_ctl *cc,
440 char *name,
441 char *seq0)
442 {
443 struct ul_color_scheme *cs = NULL;
444 char *seq = NULL;
445 int rc;
446
447 if (!cc || !name || !*name || !seq0 || !*seq0)
448 return -EINVAL;
449
450 DBG(SCHEME, ul_debug("add '%s'", name));
451
452 rc = cn_sequence(seq0, &seq);
453 if (rc)
454 return rc;
455
456 rc = -ENOMEM;
457
458 /* convert logical name (e.g. "red") to real ESC code */
459 if (isalpha(*seq)) {
460 const char *s = color_sequence_from_colorname(seq);
461 char *p;
462
463 if (!s) {
464 DBG(SCHEME, ul_debug("unknown logical name: %s", seq));
465 rc = -EINVAL;
466 goto err;
467 }
468
469 p = strdup(s);
470 if (!p)
471 goto err;
472 free(seq);
473 seq = p;
474 }
475
476 /* enlarge the array */
477 if (cc->nschemes == cc->schemes_sz) {
478 void *tmp = realloc(cc->schemes, (cc->nschemes + 10)
479 * sizeof(struct ul_color_scheme));
480 if (!tmp)
481 goto err;
482 cc->schemes = tmp;
483 cc->schemes_sz = cc->nschemes + 10;
484 }
485
486 /* add a new item */
487 cs = &cc->schemes[cc->nschemes];
488 cs->seq = seq;
489 cs->name = strdup(name);
490 if (!cs->name)
491 goto err;
492
493 cc->nschemes++;
494 return 0;
495 err:
496 if (cs) {
497 free(cs->seq);
498 free(cs->name);
499 cs->seq = cs->name = NULL;
500 } else
501 free(seq);
502 return rc;
503 }
504
505 /*
506 * Deallocates all regards to color schemes
507 */
508 static void colors_free_schemes(struct ul_color_ctl *cc)
509 {
510 size_t i;
511
512 DBG(SCHEME, ul_debug("free scheme"));
513
514 for (i = 0; i < cc->nschemes; i++) {
515 free(cc->schemes[i].name);
516 free(cc->schemes[i].seq);
517 }
518
519 free(cc->schemes);
520 cc->schemes = NULL;
521 cc->nschemes = 0;
522 cc->schemes_sz = 0;
523 }
524
525 /*
526 * The scheme configuration has to be sorted for bsearch
527 */
528 static void colors_sort_schemes(struct ul_color_ctl *cc)
529 {
530 if (!cc->nschemes)
531 return;
532
533 DBG(SCHEME, ul_debug("sort scheme"));
534
535 qsort(cc->schemes, cc->nschemes,
536 sizeof(struct ul_color_scheme), cmp_scheme_name);
537 }
538
539 /*
540 * Returns just one color scheme
541 */
542 static struct ul_color_scheme *colors_get_scheme(struct ul_color_ctl *cc,
543 const char *name)
544 {
545 struct ul_color_scheme key = { .name = (char *) name}, *res;
546
547 if (!cc || !name || !*name)
548 return NULL;
549
550 if (!cc->cs_configured) {
551 int rc = colors_read_schemes(cc);
552 if (rc)
553 return NULL;
554 }
555 if (!cc->nschemes)
556 return NULL;
557
558 DBG(SCHEME, ul_debug("search '%s'", name));
559
560 res = bsearch(&key, cc->schemes, cc->nschemes,
561 sizeof(struct ul_color_scheme),
562 cmp_scheme_name);
563
564 return res && res->seq ? res : NULL;
565 }
566
567 /*
568 * Parses filenames in terminal-colors.d
569 */
570 static int colors_read_configuration(struct ul_color_ctl *cc)
571 {
572 int rc = -ENOENT;
573 char *dirname, buf[PATH_MAX];
574
575 cc->termname = getenv("TERM");
576
577 dirname = colors_get_homedir(buf, sizeof(buf));
578 if (dirname)
579 rc = colors_readdir(cc, dirname); /* ~/.config */
580 if (rc == -EPERM || rc == -EACCES || rc == -ENOENT)
581 rc = colors_readdir(cc, _PATH_TERMCOLORS_DIR); /* /etc */
582
583 cc->configured = 1;
584 return rc;
585 }
586
587 /*
588 * Reads terminal-colors.d/ scheme file into array schemes
589 */
590 static int colors_read_schemes(struct ul_color_ctl *cc)
591 {
592 int rc = 0;
593 FILE *f = NULL;
594 char buf[BUFSIZ],
595 cn[129], seq[129];
596
597 if (!cc->configured)
598 rc = colors_read_configuration(cc);
599
600 cc->cs_configured = 1;
601
602 if (rc || !cc->sfile)
603 goto done;
604
605 DBG(SCHEME, ul_debug("reading file '%s'", cc->sfile));
606
607 f = fopen(cc->sfile, "r");
608 if (!f) {
609 rc = -errno;
610 goto done;
611 }
612
613 while (fgets(buf, sizeof(buf), f)) {
614 char *p = strchr(buf, '\n');
615
616 if (!p) {
617 if (feof(f))
618 p = strchr(buf, '\0');
619 else {
620 rc = -errno;
621 goto done;
622 }
623 }
624 *p = '\0';
625 p = (char *) skip_blank(buf);
626 if (*p == '\0' || *p == '#')
627 continue;
628
629 rc = sscanf(p, "%128[^ ] %128[^\n ]", cn, seq);
630 if (rc == 2 && *cn && *seq) {
631 rc = colors_add_scheme(cc, cn, seq); /* set rc=0 on success */
632 if (rc)
633 goto done;
634 }
635 }
636 rc = 0;
637
638 done:
639 if (f)
640 fclose(f);
641 colors_sort_schemes(cc);
642
643 return rc;
644 }
645
646
647 static void termcolors_init_debug(void)
648 {
649 __UL_INIT_DEBUG_FROM_ENV(termcolors, TERMCOLORS_DEBUG_, 0, TERMINAL_COLORS_DEBUG);
650 }
651
652 static int colors_terminal_is_ready(void)
653 {
654 int ncolors = -1;
655
656 #if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW)
657 {
658 int ret;
659
660 if (setupterm(NULL, STDOUT_FILENO, &ret) != 0 || ret != 1)
661 goto none;
662 ncolors = tigetnum("colors");
663 if (ncolors <= 2)
664 goto none;
665 }
666 #endif
667 if (ncolors != -1) {
668 DBG(CONF, ul_debug("terminal is ready (supports %d colors)", ncolors));
669 return 1;
670 }
671 none:
672 DBG(CONF, ul_debug("terminal is NOT ready"));
673 return 0;
674 }
675
676 /**
677 * colors_init:
678 * @mode: UL_COLORMODE_*
679 * @name: util argv[0]
680 *
681 * Initialize private color control struct and initialize the colors
682 * status. The color schemes are parsed on demand by colors_get_scheme().
683 *
684 * Returns: >0 on success.
685 */
686 int colors_init(int mode, const char *name)
687 {
688 int ready = -1;
689 struct ul_color_ctl *cc = &ul_colors;
690
691 cc->utilname = name;
692
693 termcolors_init_debug();
694
695 if (mode != UL_COLORMODE_ALWAYS && !isatty(STDOUT_FILENO))
696 cc->mode = UL_COLORMODE_NEVER;
697 else
698 cc->mode = mode;
699
700 if (cc->mode == UL_COLORMODE_UNDEF
701 && (ready = colors_terminal_is_ready())) {
702 int rc = colors_read_configuration(cc);
703 if (rc)
704 cc->mode = UL_COLORMODE_DEFAULT;
705 else {
706
707 /* evaluate scores */
708 if (cc->scores[UL_COLORFILE_DISABLE] >
709 cc->scores[UL_COLORFILE_ENABLE])
710 cc->mode = UL_COLORMODE_NEVER;
711 else
712 cc->mode = UL_COLORMODE_DEFAULT;
713
714 atexit(colors_deinit);
715 }
716 }
717
718 switch (cc->mode) {
719 case UL_COLORMODE_AUTO:
720 cc->has_colors = ready == -1 ? colors_terminal_is_ready() : ready;
721 break;
722 case UL_COLORMODE_ALWAYS:
723 cc->has_colors = 1;
724 break;
725 case UL_COLORMODE_NEVER:
726 default:
727 cc->has_colors = 0;
728 }
729
730 ON_DBG(CONF, colors_debug(cc));
731
732 return cc->has_colors;
733 }
734
735 /*
736 * Temporary disable colors (this setting is independent on terminal-colors.d/)
737 */
738 void colors_off(void)
739 {
740 ul_colors.disabled = 1;
741 }
742
743 /*
744 * Enable colors
745 */
746 void colors_on(void)
747 {
748 ul_colors.disabled = 0;
749 }
750
751 /*
752 * Is terminal-colors.d/ configured to use colors?
753 */
754 int colors_wanted(void)
755 {
756 return ul_colors.has_colors;
757 }
758
759 /*
760 * Returns mode
761 */
762 int colors_mode(void)
763 {
764 return ul_colors.mode;
765 }
766
767 /*
768 * Enable @seq color
769 */
770 void color_fenable(const char *seq, FILE *f)
771 {
772 if (!ul_colors.disabled && ul_colors.has_colors && seq)
773 fputs(seq, f);
774 }
775
776 /*
777 * Returns escape sequence by logical @name, if undefined then returns @dflt.
778 */
779 const char *color_scheme_get_sequence(const char *name, const char *dflt)
780 {
781 struct ul_color_scheme *cs;
782
783 if (ul_colors.disabled || !ul_colors.has_colors)
784 return NULL;
785
786 cs = colors_get_scheme(&ul_colors, name);
787 return cs && cs->seq ? cs->seq : dflt;
788 }
789
790 /*
791 * Enable color by logical @name, if undefined enable @dflt.
792 */
793 void color_scheme_fenable(const char *name, const char *dflt, FILE *f)
794 {
795 const char *seq = color_scheme_get_sequence(name, dflt);
796
797 if (!seq)
798 return;
799 color_fenable(seq, f);
800 }
801
802
803 /*
804 * Disable previously enabled color
805 */
806 void color_fdisable(FILE *f)
807 {
808 if (!ul_colors.disabled && ul_colors.has_colors)
809 fputs(UL_COLOR_RESET, f);
810 }
811
812 /*
813 * Parses @str to return UL_COLORMODE_*
814 */
815 int colormode_from_string(const char *str)
816 {
817 size_t i;
818 static const char *modes[] = {
819 [UL_COLORMODE_AUTO] = "auto",
820 [UL_COLORMODE_NEVER] = "never",
821 [UL_COLORMODE_ALWAYS] = "always",
822 [UL_COLORMODE_UNDEF] = ""
823 };
824
825 if (!str || !*str)
826 return -EINVAL;
827
828 assert(ARRAY_SIZE(modes) == __UL_NCOLORMODES);
829
830 for (i = 0; i < ARRAY_SIZE(modes); i++) {
831 if (strcasecmp(str, modes[i]) == 0)
832 return i;
833 }
834
835 return -EINVAL;
836 }
837
838 /*
839 * Parses @str and exit(EXIT_FAILURE) on error
840 */
841 int colormode_or_err(const char *str, const char *errmsg)
842 {
843 const char *p = str && *str == '=' ? str + 1 : str;
844 int colormode;
845
846 colormode = colormode_from_string(p);
847 if (colormode < 0)
848 errx(EXIT_FAILURE, "%s: '%s'", errmsg, p);
849
850 return colormode;
851 }
852
853 #ifdef TEST_PROGRAM_COLORS
854 # include <getopt.h>
855 int main(int argc, char *argv[])
856 {
857 static const struct option longopts[] = {
858 { "mode", required_argument, NULL, 'm' },
859 { "color", required_argument, NULL, 'c' },
860 { "color-scheme", required_argument, NULL, 'C' },
861 { "name", required_argument, NULL, 'n' },
862 { NULL, 0, NULL, 0 }
863 };
864 int c, mode = UL_COLORMODE_UNDEF; /* default */
865 const char *color = "red", *name = NULL, *color_scheme = NULL;
866 const char *seq = NULL;
867
868 while ((c = getopt_long(argc, argv, "C:c:m:n:", longopts, NULL)) != -1) {
869 switch (c) {
870 case 'c':
871 color = optarg;
872 break;
873 case 'C':
874 color_scheme = optarg;
875 break;
876 case 'm':
877 mode = colormode_or_err(optarg, "unsupported color mode");
878 break;
879 case 'n':
880 name = optarg;
881 break;
882 default:
883 fprintf(stderr, "usage: %s [options]\n"
884 " -m, --mode <auto|never|always> default is undefined\n"
885 " -c, --color <red|blue|...> color for the test message\n"
886 " -C, --color-scheme <name> color for the test message\n"
887 " -n, --name <utilname> util name\n",
888 program_invocation_short_name);
889 return EXIT_FAILURE;
890 }
891 }
892
893 colors_init(mode, name ? name : program_invocation_short_name);
894
895 seq = color_sequence_from_colorname(color);
896
897 if (color_scheme)
898 color_scheme_enable(color_scheme, seq);
899 else
900 color_enable(seq);
901 printf("Hello World!");
902 color_disable();
903 fputc('\n', stdout);
904
905 return EXIT_SUCCESS;
906 }
907 #endif /* TEST_PROGRAM_COLORS */
908