]> git.refcnt.org Git - colorize.git/blob - colorize.c
colorize 0.53
[colorize.git] / colorize.c
1 /*
2 * colorize - Read text from standard input stream or file and print
3 * it colorized through use of ANSI escape sequences
4 *
5 * Copyright (c) 2011-2014 Steven Schubiger
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 *
20 */
21
22 #define _POSIX_C_SOURCE 200809L
23 #include <assert.h>
24 #include <ctype.h>
25 #include <errno.h>
26 #include <getopt.h>
27 #include <stdarg.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <sys/time.h>
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <time.h>
35 #include <unistd.h>
36
37 #ifndef DEBUG
38 # define DEBUG 0
39 #endif
40
41 #define str(arg) #arg
42 #define to_str(arg) str(arg)
43
44 #define streq(s1, s2) (strcmp (s1, s2) == 0)
45
46 #if DEBUG
47 # define xmalloc(size) malloc_wrap_debug(size, __FILE__, __LINE__)
48 # define xcalloc(nmemb, size) calloc_wrap_debug(nmemb, size, __FILE__, __LINE__)
49 # define xrealloc(ptr, size) realloc_wrap_debug(ptr, size, __FILE__, __LINE__)
50 #else
51 # define xmalloc(size) malloc_wrap(size)
52 # define xcalloc(nmemb, size) calloc_wrap(nmemb, size)
53 # define xrealloc(ptr, size) realloc_wrap(ptr, size)
54 #endif
55
56 #define free_null(ptr) free_wrap((void **)&ptr)
57 #define xstrdup(str) strdup_wrap(str)
58
59 #if BUF_SIZE <= 0
60 # undef BUF_SIZE
61 #endif
62 #ifndef BUF_SIZE
63 # define BUF_SIZE 4096
64 #endif
65
66 #define LF 0x01
67 #define CR 0x02
68
69 #define SKIP_LINE_ENDINGS(flags) (((flags) & CR) && ((flags) & LF) ? 2 : 1)
70
71 #define STACK_VAR(ptr) do { \
72 stack_var (&vars_list, &stacked_vars, stacked_vars, ptr); \
73 } while (false)
74
75 #define RELEASE_VAR(ptr) do { \
76 release_var (vars_list, stacked_vars, (void **)&ptr); \
77 } while (false)
78
79 #define MEM_ALLOC_FAIL_DEBUG(file, line) do { \
80 fprintf (stderr, "Memory allocation failure in source file %s, line %u\n", file, line); \
81 exit (2); \
82 } while (false)
83 #define MEM_ALLOC_FAIL() do { \
84 fprintf (stderr, "%s: memory allocation failure\n", program_name); \
85 exit (2); \
86 } while (false)
87
88 #define ABORT_TRACE() \
89 fprintf (stderr, "Aborting in source file %s, line %u\n", __FILE__, __LINE__); \
90 abort (); \
91
92 #define CHECK_COLORS_RANDOM(color1, color2) \
93 streq (color_names[color1]->name, "random") \
94 && (streq (color_names[color2]->name, "none") \
95 || streq (color_names[color2]->name, "default")) \
96
97 #define COLOR_SEP_CHAR '/'
98
99 #define VERSION "0.53"
100
101 typedef enum { false, true } bool;
102
103 struct color_name {
104 char *name;
105 char *orig;
106 };
107
108 static struct color_name *color_names[3] = { NULL, NULL, NULL };
109
110 struct color {
111 const char *name;
112 const char *code;
113 };
114
115 static const struct color fg_colors[] = {
116 { "none", NULL },
117 { "black", "30m" },
118 { "red", "31m" },
119 { "green", "32m" },
120 { "yellow", "33m" },
121 { "blue", "34m" },
122 { "magenta", "35m" },
123 { "cyan", "36m" },
124 { "white", "37m" },
125 { "default", "39m" },
126 };
127 static const struct color bg_colors[] = {
128 { "none", NULL },
129 { "black", "40m" },
130 { "red", "41m" },
131 { "green", "42m" },
132 { "yellow", "43m" },
133 { "blue", "44m" },
134 { "magenta", "45m" },
135 { "cyan", "46m" },
136 { "white", "47m" },
137 { "default", "49m" },
138 };
139
140 enum fmts {
141 FMT_GENERIC,
142 FMT_COLOR,
143 FMT_RANDOM,
144 FMT_ERROR,
145 FMT_FILE
146 };
147 static const char *formats[] = {
148 "%s", /* generic */
149 "%s color '%s' %s", /* color */
150 "%s color '%s' %s '%s'", /* random */
151 "less than %u bytes %s", /* error */
152 "%s: %s", /* file */
153 };
154
155 enum { FOREGROUND, BACKGROUND };
156
157 static const struct {
158 struct color const *entries;
159 unsigned int count;
160 const char *desc;
161 } tables[] = {
162 { fg_colors, sizeof (fg_colors) / sizeof (struct color), "foreground" },
163 { bg_colors, sizeof (bg_colors) / sizeof (struct color), "background" },
164 };
165
166 static FILE *stream = NULL;
167
168 static unsigned int stacked_vars = 0;
169 static void **vars_list = NULL;
170
171 static bool clean = false;
172 static bool clean_all = false;
173
174 static char *exclude = NULL;
175
176 static const char *program_name;
177
178 static void print_hint (void);
179 static void print_help (void);
180 static void print_version (void);
181 static void cleanup (void);
182 static void free_color_names (struct color_name **);
183 static void process_args (unsigned int, char **, bool *, const struct color **, const char **, FILE **);
184 static void process_file_arg (const char *, const char **, FILE **);
185 static void read_print_stream (bool, const struct color **, const char *, FILE *);
186 static void find_color_entries (struct color_name **, const struct color **);
187 static void find_color_entry (const struct color_name *, unsigned int, const struct color **);
188 static void print_line (bool, const struct color **, const char * const, unsigned int);
189 static void print_clean (const char *);
190 static void print_free_offsets (const char *, char ***, unsigned int);
191 static void *malloc_wrap (size_t);
192 static void *calloc_wrap (size_t, size_t);
193 static void *realloc_wrap (void *, size_t);
194 static void *malloc_wrap_debug (size_t, const char *, unsigned int);
195 static void *calloc_wrap_debug (size_t, size_t, const char *, unsigned int);
196 static void *realloc_wrap_debug (void *, size_t, const char *, unsigned int);
197 static void free_wrap (void **);
198 static char *strdup_wrap (const char *);
199 static char *str_concat (const char *, const char *);
200 static bool has_color_name (const char *, const char *);
201 static void vfprintf_diag (const char *, ...);
202 static void vfprintf_fail (const char *, ...);
203 static void stack_var (void ***, unsigned int *, unsigned int, void *);
204 static void release_var (void **, unsigned int, void **);
205
206 #define SET_OPT_TYPE(type) \
207 opt_type = type; \
208 opt = 0; \
209 goto PARSE_OPT; \
210
211 extern char *optarg;
212 extern int optind;
213
214 int
215 main (int argc, char **argv)
216 {
217 unsigned int arg_cnt = 0;
218
219 enum {
220 OPT_CLEAN = 1,
221 OPT_CLEAN_ALL,
222 OPT_EXCLUDE_RANDOM,
223 OPT_HELP,
224 OPT_VERSION
225 };
226
227 int opt, opt_type = 0;
228 struct option long_opts[] = {
229 { "clean", no_argument, &opt_type, OPT_CLEAN },
230 { "clean-all", no_argument, &opt_type, OPT_CLEAN_ALL },
231 { "exclude-random", required_argument, &opt_type, OPT_EXCLUDE_RANDOM },
232 { "help", no_argument, &opt_type, OPT_HELP },
233 { "version", no_argument, &opt_type, OPT_VERSION },
234 { NULL, 0, NULL, 0 },
235 };
236
237 bool bold = false;
238
239 const struct color *colors[2] = {
240 NULL, /* foreground */
241 NULL, /* background */
242 };
243
244 const char *file = NULL;
245
246 program_name = argv[0];
247 atexit (cleanup);
248
249 setvbuf (stdout, NULL, _IOLBF, 0);
250
251 while ((opt = getopt_long (argc, argv, "hv", long_opts, NULL)) != -1)
252 {
253 PARSE_OPT:
254 switch (opt)
255 {
256 case 0: /* long opts */
257 switch (opt_type)
258 {
259 case OPT_CLEAN:
260 clean = true;
261 break;
262 case OPT_CLEAN_ALL:
263 clean_all = true;
264 break;
265 case OPT_EXCLUDE_RANDOM: {
266 bool valid = false;
267 unsigned int i;
268 exclude = xstrdup (optarg);
269 STACK_VAR (exclude);
270 for (i = 1; i < tables[FOREGROUND].count - 1; i++) /* skip color none and default */
271 {
272 const struct color *entry = &tables[FOREGROUND].entries[i];
273 if (streq (exclude, entry->name))
274 {
275 valid = true;
276 break;
277 }
278 }
279 if (!valid)
280 vfprintf_fail (formats[FMT_GENERIC], "--exclude-random switch must be provided a plain color");
281 break;
282 }
283 case OPT_HELP:
284 print_help ();
285 exit (EXIT_SUCCESS);
286 case OPT_VERSION:
287 print_version ();
288 exit (EXIT_SUCCESS);
289 default: /* never reached */
290 ABORT_TRACE ();
291 }
292 break;
293 case 'h':
294 SET_OPT_TYPE (OPT_HELP);
295 case 'v':
296 SET_OPT_TYPE (OPT_VERSION);
297 case '?':
298 print_hint ();
299 exit (EXIT_FAILURE);
300 default: /* never reached */
301 ABORT_TRACE ();
302 }
303 }
304
305 arg_cnt = argc - optind;
306
307 if (clean || clean_all)
308 {
309 if (clean && clean_all)
310 vfprintf_fail (formats[FMT_GENERIC], "--clean and --clean-all switch are mutually exclusive");
311 if (arg_cnt > 1)
312 {
313 const char *format = "%s %s";
314 const char *message = "switch cannot be used with more than one file";
315 if (clean)
316 vfprintf_fail (format, "--clean", message);
317 else if (clean_all)
318 vfprintf_fail (format, "--clean-all", message);
319 }
320 }
321 else
322 {
323 if (arg_cnt == 0 || arg_cnt > 2)
324 {
325 vfprintf_diag ("%u arguments provided, expected 1-2 arguments or clean option", arg_cnt);
326 print_hint ();
327 exit (EXIT_FAILURE);
328 }
329 }
330
331 if (clean || clean_all)
332 process_file_arg (argv[optind], &file, &stream);
333 else
334 process_args (arg_cnt, &argv[optind], &bold, colors, &file, &stream);
335 read_print_stream (bold, colors, file, stream);
336
337 RELEASE_VAR (exclude);
338
339 exit (EXIT_SUCCESS);
340 }
341
342 static void
343 print_hint (void)
344 {
345 fprintf (stderr, "Type `%s --help' for help screen.\n", program_name);
346 }
347
348 static void
349 print_help (void)
350 {
351 unsigned int i;
352
353 printf ("Usage: %s (foreground) OR (foreground)%c(background) OR --clean[-all] [-|file]\n\n", program_name, COLOR_SEP_CHAR);
354 printf ("\tColors (foreground) (background)\n");
355 for (i = 0; i < tables[FOREGROUND].count; i++)
356 {
357 const struct color *entry = &tables[FOREGROUND].entries[i];
358 const char *name = entry->name;
359 const char *code = entry->code;
360 if (code)
361 printf ("\t\t{\033[%s#\033[0m} [%c%c]%s%*s%s\n",
362 code, toupper (*name), *name, name + 1, 10 - (int)strlen (name), " ", name);
363 else
364 printf ("\t\t{-} %s%*s%s\n", name, 13 - (int)strlen (name), " ", name);
365 }
366 printf ("\t\t{*} [Rr]%s%*s%s [--exclude-random=<foreground color>]\n", "andom", 10 - (int)strlen ("random"), " ", "random");
367
368 printf ("\n\tFirst character of color name in upper case denotes increased intensity,\n");
369 printf ("\twhereas for lower case colors will be of normal intensity.\n");
370
371 printf ("\n\tOptions\n");
372 printf ("\t\t --clean\n");
373 printf ("\t\t --clean-all\n");
374 printf ("\t\t --exclude-random\n");
375 printf ("\t\t-h, --help\n");
376 printf ("\t\t-v, --version\n\n");
377 }
378
379 static void
380 print_version (void)
381 {
382 const char *c_flags;
383 bool debug;
384 #ifdef CFLAGS
385 c_flags = to_str (CFLAGS);
386 #else
387 c_flags = "unknown";
388 #endif
389 #if DEBUG
390 debug = true;
391 #else
392 debug = false;
393 #endif
394 printf ("%s v%s (compiled at %s, %s)\n", "colorize", VERSION, __DATE__, __TIME__);
395 printf ("Compiler flags: %s\n", c_flags);
396 printf ("Buffer size: %u bytes\n", BUF_SIZE);
397 printf ("Debugging: %s\n", debug ? "yes" : "no");
398 }
399
400 static void
401 cleanup (void)
402 {
403 free_color_names (color_names);
404
405 if (stream && fileno (stream) != STDIN_FILENO)
406 fclose (stream);
407
408 if (vars_list)
409 {
410 unsigned int i;
411 for (i = 0; i < stacked_vars; i++)
412 if (vars_list[i])
413 free_null (vars_list[i]);
414
415 free_null (vars_list);
416 }
417 }
418
419 static void
420 free_color_names (struct color_name **color_names)
421 {
422 unsigned int i;
423 for (i = 0; color_names[i]; i++)
424 {
425 free_null (color_names[i]->name);
426 free_null (color_names[i]->orig);
427 free_null (color_names[i]);
428 }
429 }
430
431 static void
432 process_args (unsigned int arg_cnt, char **arg_strings, bool *bold, const struct color **colors, const char **file, FILE **stream)
433 {
434 int ret;
435 unsigned int index;
436 char *color, *p, *str;
437 struct stat sb;
438
439 const char *color_string = arg_cnt >= 1 ? arg_strings[0] : NULL;
440 const char *file_string = arg_cnt == 2 ? arg_strings[1] : NULL;
441
442 assert (color_string);
443
444 if (streq (color_string, "-"))
445 {
446 if (file_string)
447 vfprintf_fail (formats[FMT_GENERIC], "hyphen cannot be used as color string");
448 else
449 vfprintf_fail (formats[FMT_GENERIC], "hyphen must be preceeded by color string");
450 }
451
452 ret = stat (color_string, &sb);
453
454 /* Ensure that we don't fail if there's a file with one or more
455 color names in its path. */
456 if (ret != -1)
457 {
458 bool have_file;
459 unsigned int c;
460 const char *color = color_string;
461
462 for (c = 1; c <= 2 && *color; c++)
463 {
464 bool matched = false;
465 unsigned int i;
466 for (i = 0; i < tables[FOREGROUND].count; i++)
467 {
468 const struct color *entry = &tables[FOREGROUND].entries[i];
469 if (has_color_name (color, entry->name))
470 {
471 color += strlen (entry->name);
472 matched = true;
473 break;
474 }
475 }
476 if (!matched && has_color_name (color, "random"))
477 {
478 color += strlen ("random");
479 matched = true;
480 }
481 if (matched && *color == COLOR_SEP_CHAR && *(color + 1))
482 color++;
483 else
484 break;
485 }
486
487 have_file = (*color != '\0');
488
489 if (have_file)
490 {
491 if (file_string)
492 vfprintf_fail (formats[FMT_GENERIC], "file cannot be used as color string");
493 else
494 vfprintf_fail (formats[FMT_GENERIC], "file must be preceeded by color string");
495 }
496 }
497
498 if ((p = strchr (color_string, COLOR_SEP_CHAR)))
499 {
500 if (p == color_string)
501 vfprintf_fail (formats[FMT_GENERIC], "foreground color missing");
502 else if (p == color_string + strlen (color_string) - 1)
503 vfprintf_fail (formats[FMT_GENERIC], "background color missing");
504 else if (strchr (++p, COLOR_SEP_CHAR))
505 vfprintf_fail (formats[FMT_GENERIC], "one color pair allowed only");
506 }
507
508 str = xstrdup (color_string);
509 STACK_VAR (str);
510
511 for (index = 0, color = str; *color; index++, color = p)
512 {
513 char *ch, *sep;
514
515 p = NULL;
516 if ((sep = strchr (color, COLOR_SEP_CHAR)))
517 {
518 *sep = '\0';
519 p = sep + 1;
520 }
521 else
522 p = color + strlen (color);
523 assert (p);
524
525 for (ch = color; *ch; ch++)
526 if (!isalpha (*ch))
527 vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color, "cannot be made of non-alphabetic characters");
528
529 for (ch = color + 1; *ch; ch++)
530 if (!islower (*ch))
531 vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color, "cannot be in mixed lower/upper case");
532
533 if (streq (color, "None"))
534 vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color, "cannot be bold");
535
536 if (isupper (*color))
537 {
538 switch (index)
539 {
540 case FOREGROUND:
541 *bold = true;
542 break;
543 case BACKGROUND:
544 vfprintf_fail (formats[FMT_COLOR], tables[BACKGROUND].desc, color, "cannot be bold");
545 break;
546 default: /* never reached */
547 ABORT_TRACE ();
548 }
549 }
550
551 color_names[index] = xcalloc (1, sizeof (struct color_name));
552
553 color_names[index]->orig = xstrdup (color);
554
555 for (ch = color; *ch; ch++)
556 *ch = tolower (*ch);
557
558 color_names[index]->name = xstrdup (color);
559 }
560
561 RELEASE_VAR (str);
562
563 assert (color_names[FOREGROUND]);
564
565 if (color_names[BACKGROUND])
566 {
567 unsigned int i;
568 unsigned int color_sets[2][2] = { { FOREGROUND, BACKGROUND }, { BACKGROUND, FOREGROUND } };
569 for (i = 0; i < 2; i++)
570 {
571 unsigned int color1 = color_sets[i][0];
572 unsigned int color2 = color_sets[i][1];
573 if (CHECK_COLORS_RANDOM (color1, color2))
574 vfprintf_fail (formats[FMT_RANDOM], tables[color1].desc, color_names[color1]->orig, "cannot be combined with", color_names[color2]->orig);
575 }
576 }
577
578 find_color_entries (color_names, colors);
579 free_color_names (color_names);
580
581 if (!colors[FOREGROUND]->code && colors[BACKGROUND] && colors[BACKGROUND]->code)
582 {
583 struct color_name color_name;
584 color_name.name = color_name.orig = "default";
585
586 find_color_entry (&color_name, FOREGROUND, colors);
587 }
588
589 process_file_arg (file_string, file, stream);
590 }
591
592 static void
593 process_file_arg (const char *file_string, const char **file, FILE **stream)
594 {
595 if (file_string)
596 {
597 if (streq (file_string, "-"))
598 *stream = stdin;
599 else
600 {
601 FILE *s;
602 const char *file = file_string;
603 struct stat sb;
604 int errno, ret;
605
606 errno = 0;
607 ret = stat (file, &sb);
608
609 if (ret == -1)
610 vfprintf_fail (formats[FMT_FILE], file, strerror (errno));
611
612 if (!(S_ISREG (sb.st_mode) || S_ISLNK (sb.st_mode) || S_ISFIFO (sb.st_mode)))
613 vfprintf_fail (formats[FMT_FILE], file, "unrecognized file type");
614
615 errno = 0;
616
617 s = fopen (file, "r");
618 if (!s)
619 vfprintf_fail (formats[FMT_FILE], file, strerror (errno));
620 *stream = s;
621 }
622 *file = file_string;
623 }
624 else
625 {
626 *stream = stdin;
627 *file = "stdin";
628 }
629
630 assert (*stream);
631 assert (*file);
632 }
633
634 #define MERGE_PRINT_LINE(part_line, line, flags, check_eof) do { \
635 char *current_line, *merged_line = NULL; \
636 if (part_line) \
637 { \
638 merged_line = str_concat (part_line, line); \
639 free_null (part_line); \
640 } \
641 current_line = merged_line ? merged_line : (char *)line; \
642 if (!check_eof || *current_line != '\0') \
643 print_line (bold, colors, current_line, flags); \
644 free (merged_line); \
645 } while (false)
646
647 static void
648 read_print_stream (bool bold, const struct color **colors, const char *file, FILE *stream)
649 {
650 char buf[BUF_SIZE + 1], *part_line = NULL;
651 unsigned int flags = 0;
652
653 while (!feof (stream))
654 {
655 size_t bytes_read;
656 char *eol;
657 const char *line;
658 memset (buf, '\0', BUF_SIZE + 1);
659 bytes_read = fread (buf, 1, BUF_SIZE, stream);
660 if (bytes_read != BUF_SIZE && ferror (stream))
661 vfprintf_fail (formats[FMT_ERROR], BUF_SIZE, "read");
662 line = buf;
663 while ((eol = strpbrk (line, "\n\r")))
664 {
665 char *p;
666 flags &= ~(CR|LF);
667 if (*eol == '\r')
668 {
669 flags |= CR;
670 if (*(eol + 1) == '\n')
671 flags |= LF;
672 }
673 else if (*eol == '\n')
674 flags |= LF;
675 else
676 vfprintf_fail (formats[FMT_FILE], file, "unrecognized line ending");
677 p = eol + SKIP_LINE_ENDINGS (flags);
678 *eol = '\0';
679 MERGE_PRINT_LINE (part_line, line, flags, false);
680 line = p;
681 }
682 if (feof (stream)) {
683 MERGE_PRINT_LINE (part_line, line, 0, true);
684 }
685 else if (*line != '\0')
686 {
687 if (!clean && !clean_all) /* efficiency */
688 print_line (bold, colors, line, 0);
689 else if (!part_line)
690 part_line = xstrdup (line);
691 else
692 {
693 char *merged_line = str_concat (part_line, line);
694 free (part_line);
695 part_line = merged_line;
696 }
697 }
698 }
699 }
700
701 static void
702 find_color_entries (struct color_name **color_names, const struct color **colors)
703 {
704 struct timeval tv;
705 unsigned int index;
706
707 /* randomness */
708 gettimeofday (&tv, NULL);
709 srand (tv.tv_usec * tv.tv_sec);
710
711 for (index = 0; color_names[index]; index++)
712 {
713 const char *color_name = color_names[index]->name;
714
715 const unsigned int count = tables[index].count;
716 const struct color *const color_entries = tables[index].entries;
717
718 if (streq (color_name, "random"))
719 {
720 bool excludable;
721 unsigned int i;
722 do {
723 excludable = false;
724 i = rand() % (count - 2) + 1; /* omit color none and default */
725 switch (index)
726 {
727 case FOREGROUND:
728 /* --exclude-random */
729 if (exclude && streq (exclude, color_entries[i].name))
730 excludable = true;
731 else if (color_names[BACKGROUND] && streq (color_names[BACKGROUND]->name, color_entries[i].name))
732 excludable = true;
733 break;
734 case BACKGROUND:
735 if (streq (colors[FOREGROUND]->name, color_entries[i].name))
736 excludable = true;
737 break;
738 default: /* never reached */
739 ABORT_TRACE ();
740 }
741 } while (excludable);
742 colors[index] = (struct color *)&color_entries[i];
743 }
744 else
745 find_color_entry (color_names[index], index, colors);
746 }
747 }
748
749 static void
750 find_color_entry (const struct color_name *color_name, unsigned int index, const struct color **colors)
751 {
752 bool found = false;
753 unsigned int i;
754
755 const unsigned int count = tables[index].count;
756 const struct color *const color_entries = tables[index].entries;
757
758 for (i = 0; i < count; i++)
759 if (streq (color_name->name, color_entries[i].name))
760 {
761 colors[index] = (struct color *)&color_entries[i];
762 found = true;
763 break;
764 }
765 if (!found)
766 vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color_name->orig, "not recognized");
767 }
768
769 static void
770 print_line (bool bold, const struct color **colors, const char *const line, unsigned int flags)
771 {
772 /* --clean[-all] */
773 if (clean || clean_all)
774 print_clean (line);
775 else
776 {
777 /* Foreground color code is guaranteed to be set when background color code is present. */
778 if (colors[BACKGROUND] && colors[BACKGROUND]->code)
779 printf ("\033[%s", colors[BACKGROUND]->code);
780 if (colors[FOREGROUND]->code)
781 printf ("\033[%s%s%s\033[0m", bold ? "1;" : "", colors[FOREGROUND]->code, line);
782 else
783 printf (formats[FMT_GENERIC], line);
784 }
785 if (flags & CR)
786 putchar ('\r');
787 if (flags & LF)
788 putchar ('\n');
789 }
790
791 static void
792 print_clean (const char *line)
793 {
794 const char *p;
795 char ***offsets = NULL;
796 unsigned int count = 0, i = 0;
797
798 for (p = line; *p;)
799 {
800 /* ESC[ */
801 if (*p == 27 && *(p + 1) == '[')
802 {
803 const char *begin = p;
804 p += 2;
805 if (clean_all)
806 {
807 while (isdigit (*p) || *p == ';')
808 p++;
809 }
810 else if (clean)
811 {
812 bool check_values;
813 unsigned int iter = 0;
814 const char *digit;
815 do {
816 check_values = false;
817 iter++;
818 if (!isdigit (*p))
819 goto DISCARD;
820 digit = p;
821 while (isdigit (*p))
822 p++;
823 if (p - digit > 2)
824 goto DISCARD;
825 else /* check range */
826 {
827 char val[3];
828 int value;
829 unsigned int i;
830 const unsigned int digits = p - digit;
831 for (i = 0; i < digits; i++)
832 val[i] = *digit++;
833 val[i] = '\0';
834 value = atoi (val);
835 if (value == 0) /* reset */
836 {
837 if (iter > 1)
838 goto DISCARD;
839 goto END;
840 }
841 else if (value == 1) /* bold */
842 {
843 bool discard = false;
844 if (iter > 1)
845 discard = true;
846 else if (*p != ';')
847 discard = true;
848 if (discard)
849 goto DISCARD;
850 p++;
851 check_values = true;
852 }
853 else if ((value >= 30 && value <= 37) || value == 39) /* foreground colors */
854 goto END;
855 else if ((value >= 40 && value <= 47) || value == 49) /* background colors */
856 {
857 if (iter > 1)
858 goto DISCARD;
859 goto END;
860 }
861 else
862 goto DISCARD;
863 }
864 } while (iter == 1 && check_values);
865 }
866 END: if (*p == 'm')
867 {
868 const char *end = p++;
869 if (!offsets)
870 offsets = xmalloc (++count * sizeof (char **));
871 else
872 offsets = xrealloc (offsets, ++count * sizeof (char **));
873 offsets[i] = xmalloc (2 * sizeof (char *));
874 offsets[i][0] = (char *)begin; /* ESC */
875 offsets[i][1] = (char *)end; /* m */
876 i++;
877 continue;
878 }
879 DISCARD:
880 continue;
881 }
882 p++;
883 }
884
885 if (offsets)
886 print_free_offsets (line, offsets, count);
887 else
888 printf (formats[FMT_GENERIC], line);
889 }
890
891 #define SET_CHAR(offset, new, old) \
892 *old = *offset; \
893 *offset = new; \
894
895 #define RESTORE_CHAR(offset, old) \
896 *offset = old; \
897
898 static void
899 print_free_offsets (const char *line, char ***offsets, unsigned int count)
900 {
901 char ch;
902 unsigned int i;
903
904 SET_CHAR (offsets[0][0], '\0', &ch);
905 printf (formats[FMT_GENERIC], line);
906 RESTORE_CHAR (offsets[0][0], ch);
907
908 for (i = 0; i < count; i++)
909 {
910 char ch;
911 bool next_offset = false;
912 if (i + 1 < count)
913 {
914 SET_CHAR (offsets[i + 1][0], '\0', &ch);
915 next_offset = true;
916 }
917 printf (formats[FMT_GENERIC], offsets[i][1] + 1);
918 if (next_offset)
919 RESTORE_CHAR (offsets[i + 1][0], ch);
920 }
921 for (i = 0; i < count; i++)
922 free_null (offsets[i]);
923 free_null (offsets);
924 }
925
926 static void *
927 malloc_wrap (size_t size)
928 {
929 void *p = malloc (size);
930 if (!p)
931 MEM_ALLOC_FAIL ();
932 return p;
933 }
934
935 static void *
936 calloc_wrap (size_t nmemb, size_t size)
937 {
938 void *p = calloc (nmemb, size);
939 if (!p)
940 MEM_ALLOC_FAIL ();
941 return p;
942 }
943
944 static void *
945 realloc_wrap (void *ptr, size_t size)
946 {
947 void *p = realloc (ptr, size);
948 if (!p)
949 MEM_ALLOC_FAIL ();
950 return p;
951 }
952
953 static void *
954 malloc_wrap_debug (size_t size, const char *file, unsigned int line)
955 {
956 void *p = malloc (size);
957 if (!p)
958 MEM_ALLOC_FAIL_DEBUG (file, line);
959 return p;
960 }
961
962 static void *
963 calloc_wrap_debug (size_t nmemb, size_t size, const char *file, unsigned int line)
964 {
965 void *p = calloc (nmemb, size);
966 if (!p)
967 MEM_ALLOC_FAIL_DEBUG (file, line);
968 return p;
969 }
970
971 static void *
972 realloc_wrap_debug (void *ptr, size_t size, const char *file, unsigned int line)
973 {
974 void *p = realloc (ptr, size);
975 if (!p)
976 MEM_ALLOC_FAIL_DEBUG (file, line);
977 return p;
978 }
979
980 static void
981 free_wrap (void **ptr)
982 {
983 free (*ptr);
984 *ptr = NULL;
985 }
986
987 static char *
988 strdup_wrap (const char *str)
989 {
990 const size_t len = strlen (str) + 1;
991 char *p = xmalloc (len);
992 strncpy (p, str, len);
993 return p;
994 }
995
996 static char *
997 str_concat (const char *str1, const char *str2)
998 {
999 const size_t len = strlen (str1) + strlen (str2) + 1;
1000 char *p, *str;
1001
1002 p = str = xmalloc (len);
1003 strncpy (p, str1, strlen (str1));
1004 p += strlen (str1);
1005 strncpy (p, str2, strlen (str2));
1006 p += strlen (str2);
1007 *p = '\0';
1008
1009 return str;
1010 }
1011
1012 static bool
1013 has_color_name (const char *str, const char *name)
1014 {
1015 char *p;
1016
1017 assert (strlen (str));
1018 assert (strlen (name));
1019
1020 if (!(*str == *name || *str == toupper (*name)))
1021 return false;
1022 else if (*(name + 1) != '\0'
1023 && !((p = strstr (str + 1, name + 1)) && p == str + 1))
1024 return false;
1025
1026 return true;
1027 }
1028
1029 #define DO_VFPRINTF(fmt) \
1030 va_list ap; \
1031 fprintf (stderr, "%s: ", program_name); \
1032 va_start (ap, fmt); \
1033 vfprintf (stderr, fmt, ap); \
1034 va_end (ap); \
1035 fprintf (stderr, "\n"); \
1036
1037 static void
1038 vfprintf_diag (const char *fmt, ...)
1039 {
1040 DO_VFPRINTF (fmt);
1041 }
1042
1043 static void
1044 vfprintf_fail (const char *fmt, ...)
1045 {
1046 DO_VFPRINTF (fmt);
1047 exit (EXIT_FAILURE);
1048 }
1049
1050 static void
1051 stack_var (void ***list, unsigned int *stacked, unsigned int index, void *ptr)
1052 {
1053 /* nothing to stack */
1054 if (ptr == NULL)
1055 return;
1056 if (!*list)
1057 *list = xmalloc (sizeof (void *));
1058 else
1059 {
1060 unsigned int i;
1061 for (i = 0; i < *stacked; i++)
1062 if (!(*list)[i])
1063 {
1064 (*list)[i] = ptr;
1065 return; /* reused */
1066 }
1067 *list = xrealloc (*list, (*stacked + 1) * sizeof (void *));
1068 }
1069 (*list)[index] = ptr;
1070 (*stacked)++;
1071 }
1072
1073 static void
1074 release_var (void **list, unsigned int stacked, void **ptr)
1075 {
1076 unsigned int i;
1077 /* nothing to release */
1078 if (*ptr == NULL)
1079 return;
1080 for (i = 0; i < stacked; i++)
1081 if (list[i] == *ptr)
1082 {
1083 free (*ptr);
1084 *ptr = NULL;
1085 list[i] = NULL;
1086 return;
1087 }
1088 }