]> git.refcnt.org Git - colorize.git/blob - colorize.c
Improve determining buffer size
[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 <= 1 /* BUF_SIZE - 1 */
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 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);
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_args (unsigned int arg_cnt, char **arg_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 ? arg_strings[0] : NULL;
428 const char *file_string = arg_cnt == 2 ? arg_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 if (has_color_name (color, entry->name))
458 {
459 color += strlen (entry->name);
460 matched = true;
461 break;
462 }
463 }
464 if (!matched && has_color_name (color, "random"))
465 {
466 color += strlen ("random");
467 matched = true;
468 }
469 if (matched && *color == COLOR_SEP_CHAR && *(color + 1))
470 color++;
471 else
472 break;
473 }
474
475 have_file = (*color != '\0');
476
477 if (have_file)
478 {
479 if (file_string)
480 vfprintf_fail (formats[FMT_GENERIC], "file cannot be used as color string");
481 else
482 vfprintf_fail (formats[FMT_GENERIC], "file must be preceeded by color string");
483 }
484 }
485
486 if ((p = strchr (color_string, COLOR_SEP_CHAR)))
487 {
488 if (p == color_string)
489 vfprintf_fail (formats[FMT_GENERIC], "foreground color missing");
490 else if (p == color_string + strlen (color_string) - 1)
491 vfprintf_fail (formats[FMT_GENERIC], "background color missing");
492 else if (strchr (++p, COLOR_SEP_CHAR))
493 vfprintf_fail (formats[FMT_GENERIC], "one color pair allowed only");
494 }
495
496 str = xstrdup (color_string);
497 STACK_VAR (str);
498
499 for (index = 0, color = str; *color; index++, color = p)
500 {
501 char *ch, *sep;
502
503 p = NULL;
504 if ((sep = strchr (color, COLOR_SEP_CHAR)))
505 {
506 *sep = '\0';
507 p = sep + 1;
508 }
509 else
510 p = color + strlen (color);
511 assert (p);
512
513 for (ch = color; *ch; ch++)
514 if (!isalpha (*ch))
515 vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color, "cannot be made of non-alphabetic characters");
516
517 for (ch = color + 1; *ch; ch++)
518 if (!islower (*ch))
519 vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color, "cannot be in mixed lower/upper case");
520
521 if (streq (color, "None"))
522 vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color, "cannot be bold");
523
524 if (isupper (*color))
525 {
526 switch (index)
527 {
528 case FOREGROUND:
529 *bold = true;
530 break;
531 case BACKGROUND:
532 vfprintf_fail (formats[FMT_COLOR], tables[BACKGROUND].desc, color, "cannot be bold");
533 break;
534 default: /* never reached */
535 ABORT_TRACE ();
536 }
537 }
538
539 color_names[index] = xcalloc (1, sizeof (struct color_name));
540
541 color_names[index]->orig = xstrdup (color);
542
543 for (ch = color; *ch; ch++)
544 *ch = tolower (*ch);
545
546 color_names[index]->name = xstrdup (color);
547 }
548
549 RELEASE_VAR (str);
550
551 assert (color_names[FOREGROUND]);
552
553 if (color_names[BACKGROUND])
554 {
555 unsigned int i;
556 unsigned int color_sets[2][2] = { { FOREGROUND, BACKGROUND }, { BACKGROUND, FOREGROUND } };
557 for (i = 0; i < 2; i++)
558 {
559 unsigned int color1 = color_sets[i][0];
560 unsigned int color2 = color_sets[i][1];
561 if (CHECK_COLORS_RANDOM (color1, color2))
562 vfprintf_fail (formats[FMT_RANDOM], tables[color1].desc, color_names[color1]->orig, "cannot be combined with", color_names[color2]->orig);
563 }
564 }
565
566 find_color_entries (color_names, colors);
567 free_color_names (color_names);
568
569 if (!colors[FOREGROUND]->code && colors[BACKGROUND] && colors[BACKGROUND]->code)
570 {
571 struct color_name color_name;
572 color_name.name = color_name.orig = "default";
573
574 find_color_entry (&color_name, FOREGROUND, colors);
575 }
576
577 process_file_arg (file_string, file, stream);
578 }
579
580 static void
581 process_file_arg (const char *file_string, const char **file, FILE **stream)
582 {
583 if (file_string)
584 {
585 if (streq (file_string, "-"))
586 *stream = stdin;
587 else
588 {
589 FILE *s;
590 const char *file = file_string;
591 struct stat sb;
592 int errno, ret;
593
594 errno = 0;
595 ret = stat (file, &sb);
596
597 if (ret == -1)
598 vfprintf_fail (formats[FMT_FILE], file, strerror (errno));
599
600 if (!(S_ISREG (sb.st_mode) || S_ISLNK (sb.st_mode) || S_ISFIFO (sb.st_mode)))
601 vfprintf_fail (formats[FMT_FILE], file, "unrecognized file type");
602
603 errno = 0;
604
605 s = fopen (file, "r");
606 if (!s)
607 vfprintf_fail (formats[FMT_FILE], file, strerror (errno));
608 *stream = s;
609 }
610 *file = file_string;
611 }
612 else
613 {
614 *stream = stdin;
615 *file = "stdin";
616 }
617
618 assert (*stream);
619 assert (*file);
620 }
621
622 #define MERGE_PRINT_LINE(part_line, line, flags, check_eof) do { \
623 char *current_line, *merged_line = NULL; \
624 if (part_line) \
625 { \
626 merged_line = str_concat (part_line, line); \
627 free_null (part_line); \
628 } \
629 current_line = merged_line ? merged_line : (char *)line; \
630 if (!check_eof || *current_line != '\0') \
631 print_line (bold, colors, current_line, flags); \
632 free (merged_line); \
633 } while (false)
634
635 static void
636 read_print_stream (bool bold, const struct color **colors, const char *file, FILE *stream)
637 {
638 char buf[BUF_SIZE], *part_line = NULL;
639 unsigned int flags = 0;
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 while ((eol = strpbrk (line, "\n\r")))
652 {
653 char *p;
654 flags &= ~(CR|LF);
655 if (*eol == '\r')
656 {
657 flags |= CR;
658 if (*(eol + 1) == '\n')
659 flags |= LF;
660 }
661 else if (*eol == '\n')
662 flags |= LF;
663 else
664 vfprintf_fail (formats[FMT_FILE], file, "unrecognized line ending");
665 p = eol + SKIP_LINE_ENDINGS (flags);
666 *eol = '\0';
667 MERGE_PRINT_LINE (part_line, line, flags, false);
668 line = p;
669 }
670 if (feof (stream)) {
671 MERGE_PRINT_LINE (part_line, line, 0, true);
672 }
673 else if (*line != '\0')
674 {
675 if (!clean && !clean_all) /* efficiency */
676 print_line (bold, colors, line, 0);
677 else if (!part_line)
678 part_line = xstrdup (line);
679 else
680 {
681 char *merged_line = str_concat (part_line, line);
682 free (part_line);
683 part_line = merged_line;
684 }
685 }
686 }
687 }
688
689 static void
690 find_color_entries (struct color_name **color_names, const struct color **colors)
691 {
692 struct timeval tv;
693 unsigned int index;
694
695 /* randomness */
696 gettimeofday (&tv, NULL);
697 srand (tv.tv_usec * tv.tv_sec);
698
699 for (index = 0; color_names[index]; index++)
700 {
701 const char *color_name = color_names[index]->name;
702
703 const unsigned int count = tables[index].count;
704 const struct color *const color_entries = tables[index].entries;
705
706 if (streq (color_name, "random"))
707 {
708 bool excludable;
709 unsigned int i;
710 do {
711 excludable = false;
712 i = rand() % (count - 2) + 1; /* omit color none and default */
713 switch (index)
714 {
715 case FOREGROUND:
716 /* --exclude-random */
717 if (exclude && streq (exclude, color_entries[i].name))
718 excludable = true;
719 else if (color_names[BACKGROUND] && streq (color_names[BACKGROUND]->name, color_entries[i].name))
720 excludable = true;
721 break;
722 case BACKGROUND:
723 if (streq (colors[FOREGROUND]->name, color_entries[i].name))
724 excludable = true;
725 break;
726 default: /* never reached */
727 ABORT_TRACE ();
728 }
729 } while (excludable);
730 colors[index] = (struct color *)&color_entries[i];
731 }
732 else
733 find_color_entry (color_names[index], index, colors);
734 }
735 }
736
737 static void
738 find_color_entry (const struct color_name *color_name, unsigned int index, const struct color **colors)
739 {
740 bool found = false;
741 unsigned int i;
742
743 const unsigned int count = tables[index].count;
744 const struct color *const color_entries = tables[index].entries;
745
746 for (i = 0; i < count; i++)
747 if (streq (color_name->name, color_entries[i].name))
748 {
749 colors[index] = (struct color *)&color_entries[i];
750 found = true;
751 break;
752 }
753 if (!found)
754 vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color_name->orig, "not recognized");
755 }
756
757 static void
758 print_line (bool bold, const struct color **colors, const char *const line, unsigned int flags)
759 {
760 /* --clean[-all] */
761 if (clean || clean_all)
762 print_clean (line);
763 else
764 {
765 /* Foreground color code is guaranteed to be set when background color code is present. */
766 if (colors[BACKGROUND] && colors[BACKGROUND]->code)
767 printf ("\033[%s", colors[BACKGROUND]->code);
768 if (colors[FOREGROUND]->code)
769 printf ("\033[%s%s%s\033[0m", bold ? "1;" : "", colors[FOREGROUND]->code, line);
770 else
771 printf (formats[FMT_GENERIC], line);
772 }
773 if (flags & CR)
774 putchar ('\r');
775 if (flags & LF)
776 putchar ('\n');
777 }
778
779 static void
780 print_clean (const char *line)
781 {
782 const char *p;
783 char ***offsets = NULL;
784 unsigned int count = 0, i = 0;
785
786 for (p = line; *p;)
787 {
788 /* ESC[ */
789 if (*p == 27 && *(p + 1) == '[')
790 {
791 const char *begin = p;
792 p += 2;
793 if (clean_all)
794 {
795 while (isdigit (*p) || *p == ';')
796 p++;
797 }
798 else if (clean)
799 {
800 bool check_values;
801 unsigned int iter = 0;
802 const char *digit;
803 do {
804 check_values = false;
805 iter++;
806 if (!isdigit (*p))
807 goto DISCARD;
808 digit = p;
809 while (isdigit (*p))
810 p++;
811 if (p - digit > 2)
812 goto DISCARD;
813 else /* check range */
814 {
815 char val[3];
816 int value;
817 unsigned int i;
818 const unsigned int digits = p - digit;
819 for (i = 0; i < digits; i++)
820 val[i] = *digit++;
821 val[i] = '\0';
822 value = atoi (val);
823 if (value == 0) /* reset */
824 {
825 if (iter > 1)
826 goto DISCARD;
827 goto END;
828 }
829 else if (value == 1) /* bold */
830 {
831 bool discard = false;
832 if (iter > 1)
833 discard = true;
834 else if (*p != ';')
835 discard = true;
836 if (discard)
837 goto DISCARD;
838 p++;
839 check_values = true;
840 }
841 else if ((value >= 30 && value <= 37) || value == 39) /* foreground colors */
842 goto END;
843 else if ((value >= 40 && value <= 47) || value == 49) /* background colors */
844 {
845 if (iter > 1)
846 goto DISCARD;
847 goto END;
848 }
849 else
850 goto DISCARD;
851 }
852 } while (iter == 1 && check_values);
853 }
854 END: if (*p == 'm')
855 {
856 const char *end = p++;
857 if (!offsets)
858 offsets = xmalloc (++count * sizeof (char **));
859 else
860 offsets = xrealloc (offsets, ++count * sizeof (char **));
861 offsets[i] = xmalloc (2 * sizeof (char *));
862 offsets[i][0] = (char *)begin; /* ESC */
863 offsets[i][1] = (char *)end; /* m */
864 i++;
865 continue;
866 }
867 DISCARD:
868 continue;
869 }
870 p++;
871 }
872
873 if (offsets)
874 print_free_offsets (line, offsets, count);
875 else
876 printf (formats[FMT_GENERIC], line);
877 }
878
879 #define SET_CHAR(offset, new, old) \
880 *old = *offset; \
881 *offset = new; \
882
883 #define RESTORE_CHAR(offset, old) \
884 *offset = old; \
885
886 static void
887 print_free_offsets (const char *line, char ***offsets, unsigned int count)
888 {
889 char ch;
890 unsigned int i;
891
892 SET_CHAR (offsets[0][0], '\0', &ch);
893 printf (formats[FMT_GENERIC], line);
894 RESTORE_CHAR (offsets[0][0], ch);
895
896 for (i = 0; i < count; i++)
897 {
898 char ch;
899 bool next_offset = false;
900 if (i + 1 < count)
901 {
902 SET_CHAR (offsets[i + 1][0], '\0', &ch);
903 next_offset = true;
904 }
905 printf (formats[FMT_GENERIC], offsets[i][1] + 1);
906 if (next_offset)
907 RESTORE_CHAR (offsets[i + 1][0], ch);
908 }
909 for (i = 0; i < count; i++)
910 free_null (offsets[i]);
911 free_null (offsets);
912 }
913
914 static void *
915 malloc_wrap (size_t size)
916 {
917 void *p = malloc (size);
918 if (!p)
919 MEM_ALLOC_FAIL ();
920 return p;
921 }
922
923 static void *
924 calloc_wrap (size_t nmemb, size_t size)
925 {
926 void *p = calloc (nmemb, size);
927 if (!p)
928 MEM_ALLOC_FAIL ();
929 return p;
930 }
931
932 static void *
933 realloc_wrap (void *ptr, size_t size)
934 {
935 void *p = realloc (ptr, size);
936 if (!p)
937 MEM_ALLOC_FAIL ();
938 return p;
939 }
940
941 static void *
942 malloc_wrap_debug (size_t size, const char *file, unsigned int line)
943 {
944 void *p = malloc (size);
945 if (!p)
946 MEM_ALLOC_FAIL_DEBUG (file, line);
947 return p;
948 }
949
950 static void *
951 calloc_wrap_debug (size_t nmemb, size_t size, const char *file, unsigned int line)
952 {
953 void *p = calloc (nmemb, size);
954 if (!p)
955 MEM_ALLOC_FAIL_DEBUG (file, line);
956 return p;
957 }
958
959 static void *
960 realloc_wrap_debug (void *ptr, size_t size, const char *file, unsigned int line)
961 {
962 void *p = realloc (ptr, size);
963 if (!p)
964 MEM_ALLOC_FAIL_DEBUG (file, line);
965 return p;
966 }
967
968 static void
969 free_wrap (void **ptr)
970 {
971 free (*ptr);
972 *ptr = NULL;
973 }
974
975 static char *
976 strdup_wrap (const char *str)
977 {
978 const size_t len = strlen (str) + 1;
979 char *p = xmalloc (len);
980 strncpy (p, str, len);
981 return p;
982 }
983
984 static char *
985 str_concat (const char *str1, const char *str2)
986 {
987 const size_t len = strlen (str1) + strlen (str2) + 1;
988 char *p, *str;
989
990 p = str = xmalloc (len);
991 strncpy (p, str1, strlen (str1));
992 p += strlen (str1);
993 strncpy (p, str2, strlen (str2));
994 p += strlen (str2);
995 *p = '\0';
996
997 return str;
998 }
999
1000 static bool
1001 has_color_name (const char *str, const char *name)
1002 {
1003 char *p;
1004
1005 assert (strlen (str));
1006 assert (strlen (name));
1007
1008 if (!(*str == *name || *str == toupper (*name)))
1009 return false;
1010 else if (*(name + 1) != '\0'
1011 && !((p = strstr (str + 1, name + 1)) && p == str + 1))
1012 return false;
1013
1014 return true;
1015 }
1016
1017 #define DO_VFPRINTF(fmt) \
1018 va_list ap; \
1019 fprintf (stderr, "%s: ", program_name); \
1020 va_start (ap, fmt); \
1021 vfprintf (stderr, fmt, ap); \
1022 va_end (ap); \
1023 fprintf (stderr, "\n"); \
1024
1025 static void
1026 vfprintf_diag (const char *fmt, ...)
1027 {
1028 DO_VFPRINTF (fmt);
1029 }
1030
1031 static void
1032 vfprintf_fail (const char *fmt, ...)
1033 {
1034 DO_VFPRINTF (fmt);
1035 exit (EXIT_FAILURE);
1036 }
1037
1038 static void
1039 stack_var (void ***list, unsigned int *stacked, unsigned int index, void *ptr)
1040 {
1041 /* nothing to stack */
1042 if (ptr == NULL)
1043 return;
1044 if (!*list)
1045 *list = xmalloc (sizeof (void *));
1046 else
1047 {
1048 unsigned int i;
1049 for (i = 0; i < *stacked; i++)
1050 if (!(*list)[i])
1051 {
1052 (*list)[i] = ptr;
1053 return; /* reused */
1054 }
1055 *list = xrealloc (*list, (*stacked + 1) * sizeof (void *));
1056 }
1057 (*list)[index] = ptr;
1058 (*stacked)++;
1059 }
1060
1061 static void
1062 release_var (void **list, unsigned int stacked, void **ptr)
1063 {
1064 unsigned int i;
1065 /* nothing to release */
1066 if (*ptr == NULL)
1067 return;
1068 for (i = 0; i < stacked; i++)
1069 if (list[i] == *ptr)
1070 {
1071 free (*ptr);
1072 *ptr = NULL;
1073 list[i] = NULL;
1074 return;
1075 }
1076 }