]> git.ipfire.org Git - thirdparty/util-linux.git/blob - lib/colors.c
libblkid: do not interpret NTFS as MBR
[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, may be overriden 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 if (out) {
428 assert ((out - *seq) <= len);
429 *out = '\0';
430 }
431
432 return 0;
433 }
434
435
436 /*
437 * Adds one color sequence to array with color scheme.
438 * When returning success (0) this function takes ownership of
439 * @seq and @name, which have to be allocated strings.
440 */
441 static int colors_add_scheme(struct ul_color_ctl *cc,
442 char *name,
443 char *seq0)
444 {
445 struct ul_color_scheme *cs = NULL;
446 char *seq = NULL;
447 int rc;
448
449 if (!cc || !name || !*name || !seq0 || !*seq0)
450 return -EINVAL;
451
452 DBG(SCHEME, ul_debug("add '%s'", name));
453
454 rc = cn_sequence(seq0, &seq);
455 if (rc)
456 return rc;
457
458 rc = -ENOMEM;
459
460 /* convert logical name (e.g. "red") to real ESC code */
461 if (isalpha(*seq)) {
462 const char *s = color_sequence_from_colorname(seq);
463 char *p;
464
465 if (!s) {
466 DBG(SCHEME, ul_debug("unknown logical name: %s", seq));
467 rc = -EINVAL;
468 goto err;
469 }
470
471 p = strdup(s);
472 if (!p)
473 goto err;
474 free(seq);
475 seq = p;
476 }
477
478 /* enlarge the array */
479 if (cc->nschemes == cc->schemes_sz) {
480 void *tmp = realloc(cc->schemes, (cc->nschemes + 10)
481 * sizeof(struct ul_color_scheme));
482 if (!tmp)
483 goto err;
484 cc->schemes = tmp;
485 cc->schemes_sz = cc->nschemes + 10;
486 }
487
488 /* add a new item */
489 cs = &cc->schemes[cc->nschemes];
490 cs->seq = seq;
491 cs->name = strdup(name);
492 if (!cs->name)
493 goto err;
494
495 cc->nschemes++;
496 return 0;
497 err:
498 if (cs) {
499 free(cs->seq);
500 free(cs->name);
501 cs->seq = cs->name = NULL;
502 } else
503 free(seq);
504 return rc;
505 }
506
507 /*
508 * Deallocates all regards to color schemes
509 */
510 static void colors_free_schemes(struct ul_color_ctl *cc)
511 {
512 size_t i;
513
514 DBG(SCHEME, ul_debug("free scheme"));
515
516 for (i = 0; i < cc->nschemes; i++) {
517 free(cc->schemes[i].name);
518 free(cc->schemes[i].seq);
519 }
520
521 free(cc->schemes);
522 cc->schemes = NULL;
523 cc->nschemes = 0;
524 cc->schemes_sz = 0;
525 }
526
527 /*
528 * The scheme configuration has to be sorted for bsearch
529 */
530 static void colors_sort_schemes(struct ul_color_ctl *cc)
531 {
532 if (!cc->nschemes)
533 return;
534
535 DBG(SCHEME, ul_debug("sort scheme"));
536
537 qsort(cc->schemes, cc->nschemes,
538 sizeof(struct ul_color_scheme), cmp_scheme_name);
539 }
540
541 /*
542 * Returns just one color scheme
543 */
544 static struct ul_color_scheme *colors_get_scheme(struct ul_color_ctl *cc,
545 const char *name)
546 {
547 struct ul_color_scheme key = { .name = (char *) name}, *res;
548
549 if (!cc || !name || !*name)
550 return NULL;
551
552 if (!cc->cs_configured) {
553 int rc = colors_read_schemes(cc);
554 if (rc)
555 return NULL;
556 }
557 if (!cc->nschemes)
558 return NULL;
559
560 DBG(SCHEME, ul_debug("search '%s'", name));
561
562 res = bsearch(&key, cc->schemes, cc->nschemes,
563 sizeof(struct ul_color_scheme),
564 cmp_scheme_name);
565
566 return res && res->seq ? res : NULL;
567 }
568
569 /*
570 * Parses filenames in terminal-colors.d
571 */
572 static int colors_read_configuration(struct ul_color_ctl *cc)
573 {
574 int rc = -ENOENT;
575 char *dirname, buf[PATH_MAX];
576
577 cc->termname = getenv("TERM");
578
579 dirname = colors_get_homedir(buf, sizeof(buf));
580 if (dirname)
581 rc = colors_readdir(cc, dirname); /* ~/.config */
582 if (rc == -EPERM || rc == -EACCES || rc == -ENOENT)
583 rc = colors_readdir(cc, _PATH_TERMCOLORS_DIR); /* /etc */
584
585 cc->configured = 1;
586 return rc;
587 }
588
589 /*
590 * Reads terminal-colors.d/ scheme file into array schemes
591 */
592 static int colors_read_schemes(struct ul_color_ctl *cc)
593 {
594 int rc = 0;
595 FILE *f = NULL;
596 char buf[BUFSIZ],
597 cn[129], seq[129];
598
599 if (!cc->configured)
600 rc = colors_read_configuration(cc);
601
602 cc->cs_configured = 1;
603
604 if (rc || !cc->sfile)
605 goto done;
606
607 DBG(SCHEME, ul_debug("reading file '%s'", cc->sfile));
608
609 f = fopen(cc->sfile, "r");
610 if (!f) {
611 rc = -errno;
612 goto done;
613 }
614
615 while (fgets(buf, sizeof(buf), f)) {
616 char *p = strchr(buf, '\n');
617
618 if (!p) {
619 if (feof(f))
620 p = strchr(buf, '\0');
621 else {
622 rc = -errno;
623 goto done;
624 }
625 }
626 *p = '\0';
627 p = (char *) skip_blank(buf);
628 if (*p == '\0' || *p == '#')
629 continue;
630
631 rc = sscanf(p, "%128[^ ] %128[^\n ]", cn, seq);
632 if (rc == 2 && *cn && *seq) {
633 rc = colors_add_scheme(cc, cn, seq); /* set rc=0 on success */
634 if (rc)
635 goto done;
636 }
637 }
638 rc = 0;
639
640 done:
641 if (f)
642 fclose(f);
643 colors_sort_schemes(cc);
644
645 return rc;
646 }
647
648
649 static void termcolors_init_debug(void)
650 {
651 __UL_INIT_DEBUG_FROM_ENV(termcolors, TERMCOLORS_DEBUG_, 0, TERMINAL_COLORS_DEBUG);
652 }
653
654 static int colors_terminal_is_ready(void)
655 {
656 int ncolors = -1;
657
658 #if defined(HAVE_LIBNCURSES) || defined(HAVE_LIBNCURSESW)
659 {
660 int ret;
661
662 if (setupterm(NULL, STDOUT_FILENO, &ret) == 0 && ret == 1)
663 ncolors = tigetnum("colors");
664 }
665 #endif
666 if (1 < ncolors) {
667 DBG(CONF, ul_debug("terminal is ready (supports %d colors)", ncolors));
668 return 1;
669 }
670
671 DBG(CONF, ul_debug("terminal is NOT ready (no colors)"));
672 return 0;
673 }
674
675 /**
676 * colors_init:
677 * @mode: UL_COLORMODE_*
678 * @name: util argv[0]
679 *
680 * Initialize private color control struct and initialize the colors
681 * status. The color schemes are parsed on demand by colors_get_scheme().
682 *
683 * Returns: >0 on success.
684 */
685 int colors_init(int mode, const char *name)
686 {
687 int ready = -1;
688 struct ul_color_ctl *cc = &ul_colors;
689
690 cc->utilname = name;
691
692 termcolors_init_debug();
693
694 if (mode != UL_COLORMODE_ALWAYS && !isatty(STDOUT_FILENO))
695 cc->mode = UL_COLORMODE_NEVER;
696 else
697 cc->mode = mode;
698
699 if (cc->mode == UL_COLORMODE_UNDEF
700 && (ready = colors_terminal_is_ready())) {
701 int rc = colors_read_configuration(cc);
702 if (rc)
703 cc->mode = UL_COLORMODE_DEFAULT;
704 else {
705
706 /* evaluate scores */
707 if (cc->scores[UL_COLORFILE_DISABLE] >
708 cc->scores[UL_COLORFILE_ENABLE])
709 cc->mode = UL_COLORMODE_NEVER;
710 else
711 cc->mode = UL_COLORMODE_DEFAULT;
712
713 atexit(colors_deinit);
714 }
715 }
716
717 switch (cc->mode) {
718 case UL_COLORMODE_AUTO:
719 cc->has_colors = ready == -1 ? colors_terminal_is_ready() : ready;
720 break;
721 case UL_COLORMODE_ALWAYS:
722 cc->has_colors = 1;
723 break;
724 case UL_COLORMODE_NEVER:
725 default:
726 cc->has_colors = 0;
727 }
728
729 ON_DBG(CONF, colors_debug(cc));
730
731 return cc->has_colors;
732 }
733
734 /*
735 * Temporary disable colors (this setting is independent on terminal-colors.d/)
736 */
737 void colors_off(void)
738 {
739 ul_colors.disabled = 1;
740 }
741
742 /*
743 * Enable colors
744 */
745 void colors_on(void)
746 {
747 ul_colors.disabled = 0;
748 }
749
750 /*
751 * Is terminal-colors.d/ configured to use colors?
752 */
753 int colors_wanted(void)
754 {
755 return ul_colors.has_colors;
756 }
757
758 /*
759 * Returns mode
760 */
761 int colors_mode(void)
762 {
763 return ul_colors.mode;
764 }
765
766 /*
767 * Enable @seq color
768 */
769 void color_fenable(const char *seq, FILE *f)
770 {
771 if (!ul_colors.disabled && ul_colors.has_colors && seq)
772 fputs(seq, f);
773 }
774
775 /*
776 * Returns escape sequence by logical @name, if undefined then returns @dflt.
777 */
778 const char *color_scheme_get_sequence(const char *name, const char *dflt)
779 {
780 struct ul_color_scheme *cs;
781
782 if (ul_colors.disabled || !ul_colors.has_colors)
783 return NULL;
784
785 cs = colors_get_scheme(&ul_colors, name);
786 return cs && cs->seq ? cs->seq : dflt;
787 }
788
789 /*
790 * Enable color by logical @name, if undefined enable @dflt.
791 */
792 void color_scheme_fenable(const char *name, const char *dflt, FILE *f)
793 {
794 const char *seq = color_scheme_get_sequence(name, dflt);
795
796 if (!seq)
797 return;
798 color_fenable(seq, f);
799 }
800
801
802 /*
803 * Disable previously enabled color
804 */
805 void color_fdisable(FILE *f)
806 {
807 if (!ul_colors.disabled && ul_colors.has_colors)
808 fputs(UL_COLOR_RESET, f);
809 }
810
811 /*
812 * Parses @str to return UL_COLORMODE_*
813 */
814 int colormode_from_string(const char *str)
815 {
816 size_t i;
817 static const char *modes[] = {
818 [UL_COLORMODE_AUTO] = "auto",
819 [UL_COLORMODE_NEVER] = "never",
820 [UL_COLORMODE_ALWAYS] = "always",
821 [UL_COLORMODE_UNDEF] = ""
822 };
823
824 if (!str || !*str)
825 return -EINVAL;
826
827 assert(ARRAY_SIZE(modes) == __UL_NCOLORMODES);
828
829 for (i = 0; i < ARRAY_SIZE(modes); i++) {
830 if (strcasecmp(str, modes[i]) == 0)
831 return i;
832 }
833
834 return -EINVAL;
835 }
836
837 /*
838 * Parses @str and exit(EXIT_FAILURE) on error
839 */
840 int colormode_or_err(const char *str, const char *errmsg)
841 {
842 const char *p = str && *str == '=' ? str + 1 : str;
843 int colormode;
844
845 colormode = colormode_from_string(p);
846 if (colormode < 0)
847 errx(EXIT_FAILURE, "%s: '%s'", errmsg, p);
848
849 return colormode;
850 }
851
852 #ifdef TEST_PROGRAM_COLORS
853 # include <getopt.h>
854 int main(int argc, char *argv[])
855 {
856 static const struct option longopts[] = {
857 { "mode", required_argument, NULL, 'm' },
858 { "color", required_argument, NULL, 'c' },
859 { "color-scheme", required_argument, NULL, 'C' },
860 { "name", required_argument, NULL, 'n' },
861 { NULL, 0, NULL, 0 }
862 };
863 int c, mode = UL_COLORMODE_UNDEF; /* default */
864 const char *color = "red", *name = NULL, *color_scheme = NULL;
865 const char *seq = NULL;
866
867 while ((c = getopt_long(argc, argv, "C:c:m:n:", longopts, NULL)) != -1) {
868 switch (c) {
869 case 'c':
870 color = optarg;
871 break;
872 case 'C':
873 color_scheme = optarg;
874 break;
875 case 'm':
876 mode = colormode_or_err(optarg, "unsupported color mode");
877 break;
878 case 'n':
879 name = optarg;
880 break;
881 default:
882 fprintf(stderr, "usage: %s [options]\n"
883 " -m, --mode <auto|never|always> default is undefined\n"
884 " -c, --color <red|blue|...> color for the test message\n"
885 " -C, --color-scheme <name> color for the test message\n"
886 " -n, --name <utilname> util name\n",
887 program_invocation_short_name);
888 return EXIT_FAILURE;
889 }
890 }
891
892 colors_init(mode, name ? name : program_invocation_short_name);
893
894 seq = color_sequence_from_colorname(color);
895
896 if (color_scheme)
897 color_scheme_enable(color_scheme, seq);
898 else
899 color_enable(seq);
900 printf("Hello World!");
901 color_disable();
902 fputc('\n', stdout);
903
904 return EXIT_SUCCESS;
905 }
906 #endif /* TEST_PROGRAM_COLORS */
907