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