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