]> git.refcnt.org Git - colorize.git/blob - colorize.c
Silence warnings under -pedantic
[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 static int opt_type = 0;
215
216 int
217 main (int argc, char **argv)
218 {
219 unsigned int arg_cnt = 0;
220
221 enum {
222 OPT_CLEAN = 1,
223 OPT_CLEAN_ALL,
224 OPT_EXCLUDE_RANDOM,
225 OPT_HELP,
226 OPT_VERSION
227 };
228
229 int opt;
230 struct option long_opts[] = {
231 { "clean", no_argument, &opt_type, OPT_CLEAN },
232 { "clean-all", no_argument, &opt_type, OPT_CLEAN_ALL },
233 { "exclude-random", required_argument, &opt_type, OPT_EXCLUDE_RANDOM },
234 { "help", no_argument, &opt_type, OPT_HELP },
235 { "version", no_argument, &opt_type, OPT_VERSION },
236 { NULL, 0, NULL, 0 },
237 };
238
239 bool bold = false;
240
241 const struct color *colors[2] = {
242 NULL, /* foreground */
243 NULL, /* background */
244 };
245
246 const char *file = NULL;
247
248 program_name = argv[0];
249 atexit (cleanup);
250
251 setvbuf (stdout, NULL, _IOLBF, 0);
252
253 while ((opt = getopt_long (argc, argv, "hv", long_opts, NULL)) != -1)
254 {
255 PARSE_OPT:
256 switch (opt)
257 {
258 case 0: /* long opts */
259 switch (opt_type)
260 {
261 case OPT_CLEAN:
262 clean = true;
263 break;
264 case OPT_CLEAN_ALL:
265 clean_all = true;
266 break;
267 case OPT_EXCLUDE_RANDOM: {
268 bool valid = false;
269 unsigned int i;
270 exclude = xstrdup (optarg);
271 STACK_VAR (exclude);
272 for (i = 1; i < tables[FOREGROUND].count - 1; i++) /* skip color none and default */
273 {
274 const struct color *entry = &tables[FOREGROUND].entries[i];
275 if (streq (exclude, entry->name))
276 {
277 valid = true;
278 break;
279 }
280 }
281 if (!valid)
282 vfprintf_fail (formats[FMT_GENERIC], "--exclude-random switch must be provided a plain color");
283 break;
284 }
285 case OPT_HELP:
286 print_help ();
287 exit (EXIT_SUCCESS);
288 case OPT_VERSION:
289 print_version ();
290 exit (EXIT_SUCCESS);
291 default: /* never reached */
292 ABORT_TRACE ();
293 }
294 break;
295 case 'h':
296 SET_OPT_TYPE (OPT_HELP);
297 case 'v':
298 SET_OPT_TYPE (OPT_VERSION);
299 case '?':
300 print_hint ();
301 exit (EXIT_FAILURE);
302 default: /* never reached */
303 ABORT_TRACE ();
304 }
305 }
306
307 arg_cnt = argc - optind;
308
309 if (clean || clean_all)
310 {
311 if (clean && clean_all)
312 vfprintf_fail (formats[FMT_GENERIC], "--clean and --clean-all switch are mutually exclusive");
313 if (arg_cnt > 1)
314 {
315 const char *format = "%s %s";
316 const char *message = "switch cannot be used with more than one file";
317 if (clean)
318 vfprintf_fail (format, "--clean", message);
319 else if (clean_all)
320 vfprintf_fail (format, "--clean-all", message);
321 }
322 }
323 else
324 {
325 if (arg_cnt == 0 || arg_cnt > 2)
326 {
327 vfprintf_diag ("%u arguments provided, expected 1-2 arguments or clean option", arg_cnt);
328 print_hint ();
329 exit (EXIT_FAILURE);
330 }
331 }
332
333 if (clean || clean_all)
334 process_file_arg (argv[optind], &file, &stream);
335 else
336 process_args (arg_cnt, &argv[optind], &bold, colors, &file, &stream);
337 read_print_stream (bold, colors, file, stream);
338
339 RELEASE_VAR (exclude);
340
341 exit (EXIT_SUCCESS);
342 }
343
344 static void
345 print_hint (void)
346 {
347 fprintf (stderr, "Type `%s --help' for help screen.\n", program_name);
348 }
349
350 static void
351 print_help (void)
352 {
353 unsigned int i;
354
355 printf ("Usage: %s (foreground) OR (foreground)%c(background) OR --clean[-all] [-|file]\n\n", program_name, COLOR_SEP_CHAR);
356 printf ("\tColors (foreground) (background)\n");
357 for (i = 0; i < tables[FOREGROUND].count; i++)
358 {
359 const struct color *entry = &tables[FOREGROUND].entries[i];
360 const char *name = entry->name;
361 const char *code = entry->code;
362 if (code)
363 printf ("\t\t{\033[%s#\033[0m} [%c%c]%s%*s%s\n",
364 code, toupper (*name), *name, name + 1, 10 - (int)strlen (name), " ", name);
365 else
366 printf ("\t\t{-} %s%*s%s\n", name, 13 - (int)strlen (name), " ", name);
367 }
368 printf ("\t\t{*} [Rr]%s%*s%s [--exclude-random=<foreground color>]\n", "andom", 10 - (int)strlen ("random"), " ", "random");
369
370 printf ("\n\tFirst character of color name in upper case denotes increased intensity,\n");
371 printf ("\twhereas for lower case colors will be of normal intensity.\n");
372
373 printf ("\n\tOptions\n");
374 printf ("\t\t --clean\n");
375 printf ("\t\t --clean-all\n");
376 printf ("\t\t --exclude-random\n");
377 printf ("\t\t-h, --help\n");
378 printf ("\t\t-v, --version\n\n");
379 }
380
381 static void
382 print_version (void)
383 {
384 const char *c_flags;
385 bool debug;
386 #ifdef CFLAGS
387 c_flags = to_str (CFLAGS);
388 #else
389 c_flags = "unknown";
390 #endif
391 #if DEBUG
392 debug = true;
393 #else
394 debug = false;
395 #endif
396 printf ("%s v%s (compiled at %s, %s)\n", "colorize", VERSION, __DATE__, __TIME__);
397 printf ("Compiler flags: %s\n", c_flags);
398 printf ("Buffer size: %u bytes\n", BUF_SIZE);
399 printf ("Debugging: %s\n", debug ? "yes" : "no");
400 }
401
402 static void
403 cleanup (void)
404 {
405 free_color_names (color_names);
406
407 if (stream && fileno (stream) != STDIN_FILENO)
408 fclose (stream);
409
410 if (vars_list)
411 {
412 unsigned int i;
413 for (i = 0; i < stacked_vars; i++)
414 if (vars_list[i])
415 free_null (vars_list[i]);
416
417 free_null (vars_list);
418 }
419 }
420
421 static void
422 free_color_names (struct color_name **color_names)
423 {
424 unsigned int i;
425 for (i = 0; color_names[i]; i++)
426 {
427 free_null (color_names[i]->name);
428 free_null (color_names[i]->orig);
429 free_null (color_names[i]);
430 }
431 }
432
433 static void
434 process_args (unsigned int arg_cnt, char **arg_strings, bool *bold, const struct color **colors, const char **file, FILE **stream)
435 {
436 int ret;
437 unsigned int index;
438 char *color, *p, *str;
439 struct stat sb;
440
441 const char *color_string = arg_cnt >= 1 ? arg_strings[0] : NULL;
442 const char *file_string = arg_cnt == 2 ? arg_strings[1] : NULL;
443
444 assert (color_string);
445
446 if (streq (color_string, "-"))
447 {
448 if (file_string)
449 vfprintf_fail (formats[FMT_GENERIC], "hyphen cannot be used as color string");
450 else
451 vfprintf_fail (formats[FMT_GENERIC], "hyphen must be preceeded by color string");
452 }
453
454 ret = stat (color_string, &sb);
455
456 /* Ensure that we don't fail if there's a file with one or more
457 color names in its path. */
458 if (ret != -1)
459 {
460 bool have_file;
461 unsigned int c;
462 const char *color = color_string;
463
464 for (c = 1; c <= 2 && *color; c++)
465 {
466 bool matched = false;
467 unsigned int i;
468 for (i = 0; i < tables[FOREGROUND].count; i++)
469 {
470 const struct color *entry = &tables[FOREGROUND].entries[i];
471 if (has_color_name (color, entry->name))
472 {
473 color += strlen (entry->name);
474 matched = true;
475 break;
476 }
477 }
478 if (!matched && has_color_name (color, "random"))
479 {
480 color += strlen ("random");
481 matched = true;
482 }
483 if (matched && *color == COLOR_SEP_CHAR && *(color + 1))
484 color++;
485 else
486 break;
487 }
488
489 have_file = (*color != '\0');
490
491 if (have_file)
492 {
493 if (file_string)
494 vfprintf_fail (formats[FMT_GENERIC], "file cannot be used as color string");
495 else
496 vfprintf_fail (formats[FMT_GENERIC], "file must be preceeded by color string");
497 }
498 }
499
500 if ((p = strchr (color_string, COLOR_SEP_CHAR)))
501 {
502 if (p == color_string)
503 vfprintf_fail (formats[FMT_GENERIC], "foreground color missing");
504 else if (p == color_string + strlen (color_string) - 1)
505 vfprintf_fail (formats[FMT_GENERIC], "background color missing");
506 else if (strchr (++p, COLOR_SEP_CHAR))
507 vfprintf_fail (formats[FMT_GENERIC], "one color pair allowed only");
508 }
509
510 str = xstrdup (color_string);
511 STACK_VAR (str);
512
513 for (index = 0, color = str; *color; index++, color = p)
514 {
515 char *ch, *sep;
516
517 p = NULL;
518 if ((sep = strchr (color, COLOR_SEP_CHAR)))
519 {
520 *sep = '\0';
521 p = sep + 1;
522 }
523 else
524 p = color + strlen (color);
525 assert (p);
526
527 for (ch = color; *ch; ch++)
528 if (!isalpha (*ch))
529 vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color, "cannot be made of non-alphabetic characters");
530
531 for (ch = color + 1; *ch; ch++)
532 if (!islower (*ch))
533 vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color, "cannot be in mixed lower/upper case");
534
535 if (streq (color, "None"))
536 vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color, "cannot be bold");
537
538 if (isupper (*color))
539 {
540 switch (index)
541 {
542 case FOREGROUND:
543 *bold = true;
544 break;
545 case BACKGROUND:
546 vfprintf_fail (formats[FMT_COLOR], tables[BACKGROUND].desc, color, "cannot be bold");
547 break;
548 default: /* never reached */
549 ABORT_TRACE ();
550 }
551 }
552
553 color_names[index] = xcalloc (1, sizeof (struct color_name));
554
555 color_names[index]->orig = xstrdup (color);
556
557 for (ch = color; *ch; ch++)
558 *ch = tolower (*ch);
559
560 color_names[index]->name = xstrdup (color);
561 }
562
563 RELEASE_VAR (str);
564
565 assert (color_names[FOREGROUND]);
566
567 if (color_names[BACKGROUND])
568 {
569 unsigned int i;
570 unsigned int color_sets[2][2] = { { FOREGROUND, BACKGROUND }, { BACKGROUND, FOREGROUND } };
571 for (i = 0; i < 2; i++)
572 {
573 unsigned int color1 = color_sets[i][0];
574 unsigned int color2 = color_sets[i][1];
575 if (CHECK_COLORS_RANDOM (color1, color2))
576 vfprintf_fail (formats[FMT_RANDOM], tables[color1].desc, color_names[color1]->orig, "cannot be combined with", color_names[color2]->orig);
577 }
578 }
579
580 find_color_entries (color_names, colors);
581 free_color_names (color_names);
582
583 if (!colors[FOREGROUND]->code && colors[BACKGROUND] && colors[BACKGROUND]->code)
584 {
585 struct color_name color_name;
586 color_name.name = color_name.orig = "default";
587
588 find_color_entry (&color_name, FOREGROUND, colors);
589 }
590
591 process_file_arg (file_string, file, stream);
592 }
593
594 static void
595 process_file_arg (const char *file_string, const char **file, FILE **stream)
596 {
597 if (file_string)
598 {
599 if (streq (file_string, "-"))
600 *stream = stdin;
601 else
602 {
603 FILE *s;
604 const char *file = file_string;
605 struct stat sb;
606 int errno, ret;
607
608 errno = 0;
609 ret = stat (file, &sb);
610
611 if (ret == -1)
612 vfprintf_fail (formats[FMT_FILE], file, strerror (errno));
613
614 if (!(S_ISREG (sb.st_mode) || S_ISLNK (sb.st_mode) || S_ISFIFO (sb.st_mode)))
615 vfprintf_fail (formats[FMT_FILE], file, "unrecognized file type");
616
617 errno = 0;
618
619 s = fopen (file, "r");
620 if (!s)
621 vfprintf_fail (formats[FMT_FILE], file, strerror (errno));
622 *stream = s;
623 }
624 *file = file_string;
625 }
626 else
627 {
628 *stream = stdin;
629 *file = "stdin";
630 }
631
632 assert (*stream);
633 assert (*file);
634 }
635
636 #define MERGE_PRINT_LINE(part_line, line, flags, check_eof) do { \
637 char *current_line, *merged_line = NULL; \
638 if (part_line) \
639 { \
640 merged_line = str_concat (part_line, line); \
641 free_null (part_line); \
642 } \
643 current_line = merged_line ? merged_line : (char *)line; \
644 if (!check_eof || *current_line != '\0') \
645 print_line (bold, colors, current_line, flags); \
646 free (merged_line); \
647 } while (false)
648
649 static void
650 read_print_stream (bool bold, const struct color **colors, const char *file, FILE *stream)
651 {
652 char buf[BUF_SIZE + 1], *part_line = NULL;
653 unsigned int flags = 0;
654
655 while (!feof (stream))
656 {
657 size_t bytes_read;
658 char *eol;
659 const char *line;
660 memset (buf, '\0', BUF_SIZE + 1);
661 bytes_read = fread (buf, 1, BUF_SIZE, stream);
662 if (bytes_read != BUF_SIZE && ferror (stream))
663 vfprintf_fail (formats[FMT_ERROR], BUF_SIZE, "read");
664 line = buf;
665 while ((eol = strpbrk (line, "\n\r")))
666 {
667 char *p;
668 flags &= ~(CR|LF);
669 if (*eol == '\r')
670 {
671 flags |= CR;
672 if (*(eol + 1) == '\n')
673 flags |= LF;
674 }
675 else if (*eol == '\n')
676 flags |= LF;
677 else
678 vfprintf_fail (formats[FMT_FILE], file, "unrecognized line ending");
679 p = eol + SKIP_LINE_ENDINGS (flags);
680 *eol = '\0';
681 MERGE_PRINT_LINE (part_line, line, flags, false);
682 line = p;
683 }
684 if (feof (stream)) {
685 MERGE_PRINT_LINE (part_line, line, 0, true);
686 }
687 else if (*line != '\0')
688 {
689 if (!clean && !clean_all) /* efficiency */
690 print_line (bold, colors, line, 0);
691 else if (!part_line)
692 part_line = xstrdup (line);
693 else
694 {
695 char *merged_line = str_concat (part_line, line);
696 free (part_line);
697 part_line = merged_line;
698 }
699 }
700 }
701 }
702
703 static void
704 find_color_entries (struct color_name **color_names, const struct color **colors)
705 {
706 struct timeval tv;
707 unsigned int index;
708
709 /* randomness */
710 gettimeofday (&tv, NULL);
711 srand (tv.tv_usec * tv.tv_sec);
712
713 for (index = 0; color_names[index]; index++)
714 {
715 const char *color_name = color_names[index]->name;
716
717 const unsigned int count = tables[index].count;
718 const struct color *const color_entries = tables[index].entries;
719
720 if (streq (color_name, "random"))
721 {
722 bool excludable;
723 unsigned int i;
724 do {
725 excludable = false;
726 i = rand() % (count - 2) + 1; /* omit color none and default */
727 switch (index)
728 {
729 case FOREGROUND:
730 /* --exclude-random */
731 if (exclude && streq (exclude, color_entries[i].name))
732 excludable = true;
733 else if (color_names[BACKGROUND] && streq (color_names[BACKGROUND]->name, color_entries[i].name))
734 excludable = true;
735 break;
736 case BACKGROUND:
737 if (streq (colors[FOREGROUND]->name, color_entries[i].name))
738 excludable = true;
739 break;
740 default: /* never reached */
741 ABORT_TRACE ();
742 }
743 } while (excludable);
744 colors[index] = (struct color *)&color_entries[i];
745 }
746 else
747 find_color_entry (color_names[index], index, colors);
748 }
749 }
750
751 static void
752 find_color_entry (const struct color_name *color_name, unsigned int index, const struct color **colors)
753 {
754 bool found = false;
755 unsigned int i;
756
757 const unsigned int count = tables[index].count;
758 const struct color *const color_entries = tables[index].entries;
759
760 for (i = 0; i < count; i++)
761 if (streq (color_name->name, color_entries[i].name))
762 {
763 colors[index] = (struct color *)&color_entries[i];
764 found = true;
765 break;
766 }
767 if (!found)
768 vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color_name->orig, "not recognized");
769 }
770
771 static void
772 print_line (bool bold, const struct color **colors, const char *const line, unsigned int flags)
773 {
774 /* --clean[-all] */
775 if (clean || clean_all)
776 print_clean (line);
777 else
778 {
779 /* Foreground color code is guaranteed to be set when background color code is present. */
780 if (colors[BACKGROUND] && colors[BACKGROUND]->code)
781 printf ("\033[%s", colors[BACKGROUND]->code);
782 if (colors[FOREGROUND]->code)
783 printf ("\033[%s%s%s\033[0m", bold ? "1;" : "", colors[FOREGROUND]->code, line);
784 else
785 printf (formats[FMT_GENERIC], line);
786 }
787 if (flags & CR)
788 putchar ('\r');
789 if (flags & LF)
790 putchar ('\n');
791 }
792
793 static void
794 print_clean (const char *line)
795 {
796 const char *p;
797 char ***offsets = NULL;
798 unsigned int count = 0, i = 0;
799
800 for (p = line; *p;)
801 {
802 /* ESC[ */
803 if (*p == 27 && *(p + 1) == '[')
804 {
805 const char *begin = p;
806 p += 2;
807 if (clean_all)
808 {
809 while (isdigit (*p) || *p == ';')
810 p++;
811 }
812 else if (clean)
813 {
814 bool check_values;
815 unsigned int iter = 0;
816 const char *digit;
817 do {
818 check_values = false;
819 iter++;
820 if (!isdigit (*p))
821 goto DISCARD;
822 digit = p;
823 while (isdigit (*p))
824 p++;
825 if (p - digit > 2)
826 goto DISCARD;
827 else /* check range */
828 {
829 char val[3];
830 int value;
831 unsigned int i;
832 const unsigned int digits = p - digit;
833 for (i = 0; i < digits; i++)
834 val[i] = *digit++;
835 val[i] = '\0';
836 value = atoi (val);
837 if (value == 0) /* reset */
838 {
839 if (iter > 1)
840 goto DISCARD;
841 goto END;
842 }
843 else if (value == 1) /* bold */
844 {
845 bool discard = false;
846 if (iter > 1)
847 discard = true;
848 else if (*p != ';')
849 discard = true;
850 if (discard)
851 goto DISCARD;
852 p++;
853 check_values = true;
854 }
855 else if ((value >= 30 && value <= 37) || value == 39) /* foreground colors */
856 goto END;
857 else if ((value >= 40 && value <= 47) || value == 49) /* background colors */
858 {
859 if (iter > 1)
860 goto DISCARD;
861 goto END;
862 }
863 else
864 goto DISCARD;
865 }
866 } while (iter == 1 && check_values);
867 }
868 END: if (*p == 'm')
869 {
870 const char *end = p++;
871 if (!offsets)
872 offsets = xmalloc (++count * sizeof (char **));
873 else
874 offsets = xrealloc (offsets, ++count * sizeof (char **));
875 offsets[i] = xmalloc (2 * sizeof (char *));
876 offsets[i][0] = (char *)begin; /* ESC */
877 offsets[i][1] = (char *)end; /* m */
878 i++;
879 continue;
880 }
881 DISCARD:
882 continue;
883 }
884 p++;
885 }
886
887 if (offsets)
888 print_free_offsets (line, offsets, count);
889 else
890 printf (formats[FMT_GENERIC], line);
891 }
892
893 #define SET_CHAR(offset, new, old) \
894 *old = *offset; \
895 *offset = new; \
896
897 #define RESTORE_CHAR(offset, old) \
898 *offset = old; \
899
900 static void
901 print_free_offsets (const char *line, char ***offsets, unsigned int count)
902 {
903 char ch;
904 unsigned int i;
905
906 SET_CHAR (offsets[0][0], '\0', &ch);
907 printf (formats[FMT_GENERIC], line);
908 RESTORE_CHAR (offsets[0][0], ch);
909
910 for (i = 0; i < count; i++)
911 {
912 char ch;
913 bool next_offset = false;
914 if (i + 1 < count)
915 {
916 SET_CHAR (offsets[i + 1][0], '\0', &ch);
917 next_offset = true;
918 }
919 printf (formats[FMT_GENERIC], offsets[i][1] + 1);
920 if (next_offset)
921 RESTORE_CHAR (offsets[i + 1][0], ch);
922 }
923 for (i = 0; i < count; i++)
924 free_null (offsets[i]);
925 free_null (offsets);
926 }
927
928 static void *
929 malloc_wrap (size_t size)
930 {
931 void *p = malloc (size);
932 if (!p)
933 MEM_ALLOC_FAIL ();
934 return p;
935 }
936
937 static void *
938 calloc_wrap (size_t nmemb, size_t size)
939 {
940 void *p = calloc (nmemb, size);
941 if (!p)
942 MEM_ALLOC_FAIL ();
943 return p;
944 }
945
946 static void *
947 realloc_wrap (void *ptr, size_t size)
948 {
949 void *p = realloc (ptr, size);
950 if (!p)
951 MEM_ALLOC_FAIL ();
952 return p;
953 }
954
955 static void *
956 malloc_wrap_debug (size_t size, const char *file, unsigned int line)
957 {
958 void *p = malloc (size);
959 if (!p)
960 MEM_ALLOC_FAIL_DEBUG (file, line);
961 return p;
962 }
963
964 static void *
965 calloc_wrap_debug (size_t nmemb, size_t size, const char *file, unsigned int line)
966 {
967 void *p = calloc (nmemb, size);
968 if (!p)
969 MEM_ALLOC_FAIL_DEBUG (file, line);
970 return p;
971 }
972
973 static void *
974 realloc_wrap_debug (void *ptr, size_t size, const char *file, unsigned int line)
975 {
976 void *p = realloc (ptr, size);
977 if (!p)
978 MEM_ALLOC_FAIL_DEBUG (file, line);
979 return p;
980 }
981
982 static void
983 free_wrap (void **ptr)
984 {
985 free (*ptr);
986 *ptr = NULL;
987 }
988
989 static char *
990 strdup_wrap (const char *str)
991 {
992 const size_t len = strlen (str) + 1;
993 char *p = xmalloc (len);
994 strncpy (p, str, len);
995 return p;
996 }
997
998 static char *
999 str_concat (const char *str1, const char *str2)
1000 {
1001 const size_t len = strlen (str1) + strlen (str2) + 1;
1002 char *p, *str;
1003
1004 p = str = xmalloc (len);
1005 strncpy (p, str1, strlen (str1));
1006 p += strlen (str1);
1007 strncpy (p, str2, strlen (str2));
1008 p += strlen (str2);
1009 *p = '\0';
1010
1011 return str;
1012 }
1013
1014 static bool
1015 has_color_name (const char *str, const char *name)
1016 {
1017 char *p;
1018
1019 assert (strlen (str));
1020 assert (strlen (name));
1021
1022 if (!(*str == *name || *str == toupper (*name)))
1023 return false;
1024 else if (*(name + 1) != '\0'
1025 && !((p = strstr (str + 1, name + 1)) && p == str + 1))
1026 return false;
1027
1028 return true;
1029 }
1030
1031 #define DO_VFPRINTF(fmt) \
1032 va_list ap; \
1033 fprintf (stderr, "%s: ", program_name); \
1034 va_start (ap, fmt); \
1035 vfprintf (stderr, fmt, ap); \
1036 va_end (ap); \
1037 fprintf (stderr, "\n"); \
1038
1039 static void
1040 vfprintf_diag (const char *fmt, ...)
1041 {
1042 DO_VFPRINTF (fmt);
1043 }
1044
1045 static void
1046 vfprintf_fail (const char *fmt, ...)
1047 {
1048 DO_VFPRINTF (fmt);
1049 exit (EXIT_FAILURE);
1050 }
1051
1052 static void
1053 stack_var (void ***list, unsigned int *stacked, unsigned int index, void *ptr)
1054 {
1055 /* nothing to stack */
1056 if (ptr == NULL)
1057 return;
1058 if (!*list)
1059 *list = xmalloc (sizeof (void *));
1060 else
1061 {
1062 unsigned int i;
1063 for (i = 0; i < *stacked; i++)
1064 if (!(*list)[i])
1065 {
1066 (*list)[i] = ptr;
1067 return; /* reused */
1068 }
1069 *list = xrealloc (*list, (*stacked + 1) * sizeof (void *));
1070 }
1071 (*list)[index] = ptr;
1072 (*stacked)++;
1073 }
1074
1075 static void
1076 release_var (void **list, unsigned int stacked, void **ptr)
1077 {
1078 unsigned int i;
1079 /* nothing to release */
1080 if (*ptr == NULL)
1081 return;
1082 for (i = 0; i < stacked; i++)
1083 if (list[i] == *ptr)
1084 {
1085 free (*ptr);
1086 *ptr = NULL;
1087 list[i] = NULL;
1088 return;
1089 }
1090 }