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