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