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