]> git.refcnt.org Git - colorize.git/blob - colorize.c
Initial commit.
[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-2012 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 str(arg) #arg
38 #define to_str(arg) str(arg)
39
40 #define streq(s1, s2) (strcmp (s1, s2) == 0)
41
42 #if !defined BUF_SIZE || BUF_SIZE <= 0
43 # undef BUF_SIZE
44 # define BUF_SIZE 4096 + 1
45 #endif
46
47 #define LF 0x01
48 #define CR 0x02
49
50 #define SKIP_LINE_ENDINGS(flags) (((flags) & CR) && ((flags) & LF) ? 2 : 1)
51
52 #define STACK_VAR(ptr) do { \
53 stack_var (&vars_list, &stacked_vars, stacked_vars, ptr); \
54 } while (false);
55
56 #define RELEASE_VAR(ptr) do { \
57 release_var (vars_list, stacked_vars, (void **)&ptr); \
58 } while (false);
59
60 #define ABORT_TRACE() \
61 fprintf (stderr, "aborting in source file %s, line %d\n", __FILE__, __LINE__); \
62 abort (); \
63
64 #define CHECK_COLORS_RANDOM(color1, color2) \
65 streq (color_names[color1]->name, "random") \
66 && (streq (color_names[color2]->name, "none") \
67 || streq (color_names[color2]->name, "default")) \
68
69 #define VERSION "0.47"
70
71 typedef unsigned short bool;
72
73 enum { false, true };
74
75 struct color_name {
76 char *name;
77 char *orig;
78 };
79
80 static struct color_name *color_names[3] = { NULL, NULL, NULL };
81
82 struct color {
83 const char *name;
84 const char *code;
85 };
86
87 static const struct color fg_colors[] = {
88 { "none", NULL },
89 { "black", "30m" },
90 { "red", "31m" },
91 { "green", "32m" },
92 { "yellow", "33m" },
93 { "blue", "34m" },
94 { "cyan", "35m" },
95 { "magenta", "36m" },
96 { "white", "37m" },
97 { "default", "39m" },
98 };
99 static const struct color bg_colors[] = {
100 { "none", NULL },
101 { "black", "40m" },
102 { "red", "41m" },
103 { "green", "42m" },
104 { "yellow", "43m" },
105 { "blue", "44m" },
106 { "cyan", "45m" },
107 { "magenta", "46m" },
108 { "white", "47m" },
109 { "default", "49m" },
110 };
111
112 enum fmts {
113 FMT_GENERIC,
114 FMT_COLOR,
115 FMT_RANDOM,
116 FMT_ERROR,
117 FMT_FILE
118 };
119 static const char *formats[] = {
120 "%s", /* generic */
121 "%s color '%s' %s", /* color */
122 "%s color '%s' %s '%s'", /* random */
123 "less than %d bytes %s", /* error */
124 "%s: %s", /* file */
125 };
126
127 enum { FOREGROUND, BACKGROUND };
128
129 static const struct {
130 struct color const *entries;
131 unsigned int count;
132 const char *desc;
133 } tables[] = {
134 { fg_colors, sizeof (fg_colors) / sizeof (struct color), "foreground" },
135 { bg_colors, sizeof (bg_colors) / sizeof (struct color), "background" },
136 };
137
138 enum stream_mode { SCAN_FIRST = 1, SCAN_ALWAYS };
139
140 struct ending {
141 unsigned int flags;
142 const char newline[3];
143 };
144
145 static const struct ending endings[] = {
146 { CR & LF, "\r\n" },
147 { CR, "\r" },
148 { LF, "\n" },
149 };
150
151 static FILE *stream = NULL;
152
153 static unsigned int stacked_vars = 0;
154 static void **vars_list = NULL;
155
156 static char *exclude = NULL;
157
158 static const char *program_name;
159
160 static void print_help (void);
161 static void print_version (void);
162 static void cleanup (void);
163 static void free_color_names (struct color_name **);
164 static void process_options (unsigned int, char **, bool *, const struct color **, const char **, FILE **);
165 static void read_print_stream (bool, const struct color **, const char *, FILE *, enum stream_mode);
166 static void find_color_entries (struct color_name **, const struct color **);
167 static void find_color_entry (const char *const, unsigned int, const struct color **);
168 static void print_line (const struct color **, bool, const char * const, unsigned int);
169 static char *xstrdup (const char *);
170 static void vfprintf_fail (const char *, ...);
171 static void stack_var (void ***, unsigned int *, unsigned int, void *);
172 static void release_var (void **, unsigned int, void **);
173
174 extern char *optarg;
175 extern int optind;
176
177 int
178 main (int argc, char **argv)
179 {
180 unsigned int arg_cnt = 0;
181
182 bool invalid_opt = false;
183
184 int opt;
185 struct option long_opts[] = {
186 { "exclude-random", required_argument, NULL, 'e' },
187 { "help", no_argument, NULL, 'h' },
188 { "version", no_argument, NULL, 'v' },
189 { 0, 0, 0, 0 },
190 };
191
192 bool bold = false;
193
194 const struct color *colors[2] = {
195 NULL, /* foreground */
196 NULL, /* background */
197 };
198
199 const char *file;
200
201 enum stream_mode mode = SCAN_FIRST;
202
203 program_name = argv[0];
204 atexit (cleanup);
205
206 setvbuf (stdout, NULL, _IOLBF, 0);
207
208 while ((opt = getopt_long (argc, argv, "hv", long_opts, NULL)) != -1)
209 {
210 switch (opt)
211 {
212 case 'e': {
213 char *p;
214 exclude = xstrdup (optarg);
215 STACK_VAR (exclude);
216 for (p = exclude; *p; p++)
217 *p = tolower (*p);
218 if (streq (exclude, "random"))
219 vfprintf_fail (formats[FMT_GENERIC], "--exclude-random switch must be provided a color");
220 break;
221 }
222 case 'h':
223 print_help ();
224 exit (EXIT_SUCCESS);
225 case 'v':
226 print_version ();
227 exit (EXIT_SUCCESS);
228 case '?':
229 invalid_opt = true;
230 break;
231 default: /* never reached */
232 ABORT_TRACE ();
233 }
234 }
235
236 arg_cnt = argc - optind;
237
238 if (arg_cnt == 0 || arg_cnt > 2 || invalid_opt)
239 {
240 print_help ();
241 exit (EXIT_FAILURE);
242 }
243
244 process_options (arg_cnt, &argv[optind], &bold, colors, &file, &stream);
245 read_print_stream (bold, colors, file, stream, mode);
246
247 RELEASE_VAR (exclude);
248
249 exit (EXIT_SUCCESS);
250 }
251
252 static void
253 print_help (void)
254 {
255 unsigned int i;
256
257 printf ("Usage: %s (foreground) OR (foreground)/(background) [-|file]\n\n", program_name);
258 printf ("\tColors (foreground) (background)\n");
259 for (i = 0; i < tables[FOREGROUND].count; i++)
260 {
261 const struct color *entry = &tables[FOREGROUND].entries[i];
262 const char *name = entry->name;
263 const char *code = entry->code;
264 if (code)
265 printf ("\t\t{\033[%s#\033[0m} [%c%c]%s%*s%s\n",
266 code, toupper (*name), *name, name + 1, 10 - (int)strlen (name), " ", name);
267 else
268 printf ("\t\t{-} %s%*s%s\n", name, 13 - (int)strlen (name), " ", name);
269 }
270 printf ("\t\t{*} [Rr]%s%*s%s [--exclude-random=<foreground color>]\n", "andom", 10 - (int)strlen ("random"), " ", "random");
271
272 printf ("\n\tFirst character of color name in upper case denotes increased intensity,\n");
273 printf ("\twhereas for lower case colors will be of normal intensity.\n");
274
275 printf ("\n\tOptions\n");
276 printf ("\t\t-h, --help\n");
277 printf ("\t\t-v, --version\n\n");
278 }
279
280 static void
281 print_version (void)
282 {
283 const char *c_flags;
284 printf ("%s v%s (compiled at %s, %s)\n", "colorize", VERSION, __DATE__, __TIME__);
285 #ifdef CFLAGS
286 c_flags = to_str (CFLAGS);
287 #else
288 c_flags = "unknown";
289 #endif
290 printf ("Compiler flags: %s\n", c_flags);
291 printf ("Buffer size: %d bytes\n", BUF_SIZE - 1);
292 }
293
294 static void
295 cleanup (void)
296 {
297 free_color_names (color_names);
298
299 if (stream && fileno (stream) != STDIN_FILENO)
300 fclose (stream);
301
302 if (vars_list)
303 {
304 unsigned int i;
305 for (i = 0; i < stacked_vars; i++)
306 if (vars_list[i])
307 {
308 free (vars_list[i]);
309 vars_list[i] = NULL;
310 }
311 free (vars_list);
312 vars_list = NULL;
313 }
314 }
315
316 static void
317 free_color_names (struct color_name **color_names)
318 {
319 unsigned int i;
320 for (i = 0; color_names[i]; i++)
321 {
322 free (color_names[i]->name);
323 color_names[i]->name = NULL;
324 free (color_names[i]->orig);
325 color_names[i]->orig = NULL;
326 free (color_names[i]);
327 color_names[i] = NULL;
328 }
329 }
330
331 static void
332 process_options (unsigned int arg_cnt, char **option_strings, bool *bold, const struct color **colors, const char **file, FILE **stream)
333 {
334 int ret;
335 unsigned int index;
336 char *color, *p, *str;
337 struct stat sb;
338
339 const char *color_string = arg_cnt >= 1 ? option_strings[0] : NULL;
340 const char *file_string = arg_cnt == 2 ? option_strings[1] : NULL;
341
342 assert (color_string);
343
344 if (streq (color_string, "-"))
345 {
346 if (file_string)
347 vfprintf_fail (formats[FMT_GENERIC], "hyphen cannot be used as color string");
348 else
349 vfprintf_fail (formats[FMT_GENERIC], "hyphen must be preceeded by color string");
350 }
351
352 ret = stat (color_string, &sb);
353
354 /* Ensure that we don't fail if there's a file with one or more
355 color names in its path. */
356 if (ret != -1)
357 {
358 bool have_file;
359 unsigned int c;
360 const char *color = color_string;
361
362 for (c = 1; c <= 2 && *color; c++)
363 {
364 bool matched = false;
365 unsigned int i;
366 for (i = 0; i < tables[FOREGROUND].count; i++)
367 {
368 const struct color *entry = &tables[FOREGROUND].entries[i];
369 char *p;
370 if ((p = strstr (color, entry->name)) && p == color)
371 {
372 color = p + strlen (entry->name);
373 matched = true;
374 break;
375 }
376 }
377 if (matched && *color == '/' && *(color + 1))
378 color++;
379 else
380 break;
381 }
382
383 have_file = (*color != '\0');
384
385 if (have_file)
386 vfprintf_fail (formats[FMT_GENERIC], "file must be preceeded by color string");
387 }
388
389 if ((p = strchr (color_string, '/')))
390 {
391 if (p == color_string)
392 vfprintf_fail (formats[FMT_GENERIC], "foreground color missing");
393 else if (p == color_string + strlen (color_string) - 1)
394 vfprintf_fail (formats[FMT_GENERIC], "background color missing");
395 else if (strchr (++p, '/'))
396 vfprintf_fail (formats[FMT_GENERIC], "one color pair allowed only");
397 }
398
399 str = xstrdup (color_string);
400 STACK_VAR (str);
401
402 for (index = 0, color = str; *color; index++, color = p)
403 {
404 char *ch, *sep;
405 if ((sep = strchr (color, '/')))
406 {
407 *sep = '\0';
408 p = sep + 1;
409 }
410 else
411 p = color + strlen (color);
412
413 for (ch = color; *ch; ch++)
414 if (!isalpha (*ch))
415 vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color, "cannot be made of non-alphabetic characters");
416
417 for (ch = color + 1; *ch; ch++)
418 if (!islower (*ch))
419 vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color, "cannot be in mixed lower/upper case");
420
421 if (streq (color, "None"))
422 vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color, "cannot be bold");
423
424 if (isupper (*color))
425 {
426 switch (index)
427 {
428 case FOREGROUND:
429 *bold = true;
430 break;
431 case BACKGROUND:
432 vfprintf_fail (formats[FMT_COLOR], tables[BACKGROUND].desc, color, "cannot be bold");
433 break;
434 default: /* never reached */
435 ABORT_TRACE ();
436 }
437 }
438
439 color_names[index] = malloc (sizeof (struct color_name));
440
441 color_names[index]->orig = xstrdup (color);
442
443 for (ch = color; *ch; ch++)
444 *ch = tolower (*ch);
445
446 color_names[index]->name = xstrdup (color);
447 }
448
449 RELEASE_VAR (str);
450
451 assert (color_names[FOREGROUND]);
452
453 if (color_names[BACKGROUND])
454 {
455 unsigned int i;
456 unsigned int color_sets[2][2] = { { FOREGROUND, BACKGROUND }, { BACKGROUND, FOREGROUND } };
457 for (i = 0; i < 2; i++)
458 {
459 unsigned int color1 = color_sets[i][0];
460 unsigned int color2 = color_sets[i][1];
461 if (CHECK_COLORS_RANDOM (color1, color2))
462 vfprintf_fail (formats[FMT_RANDOM], tables[color1].desc, color_names[color1]->orig, "cannot be combined with", color_names[color2]->orig);
463 }
464 }
465
466 find_color_entries (color_names, colors);
467 free_color_names (color_names);
468
469 if (!colors[FOREGROUND]->code && colors[BACKGROUND] && colors[BACKGROUND]->code)
470 find_color_entry ("default", FOREGROUND, colors);
471
472 if (file_string)
473 {
474 if (streq (file_string, "-"))
475 *stream = stdin;
476 else
477 {
478 FILE *s;
479 const char *file = file_string;
480 struct stat sb;
481 int errno, ret;
482
483 errno = 0;
484 ret = stat (file, &sb);
485
486 if (ret == -1)
487 vfprintf_fail (formats[FMT_FILE], file, strerror (errno));
488
489 if (!(S_ISREG (sb.st_mode) || S_ISLNK (sb.st_mode) || S_ISFIFO (sb.st_mode)))
490 vfprintf_fail (formats[FMT_FILE], file, "unrecognized file type");
491
492 errno = 0;
493
494 s = fopen (file, "r");
495 if (!s)
496 vfprintf_fail (formats[FMT_FILE], file, strerror (errno));
497 *stream = s;
498 }
499 *file = file_string;
500 }
501 else
502 {
503 *stream = stdin;
504 *file = "stdin";
505 }
506
507 assert (*stream);
508 }
509
510 static void
511 read_print_stream (bool bold, const struct color **colors, const char *file, FILE *stream, enum stream_mode mode)
512 {
513 char buf[BUF_SIZE];
514 unsigned int flags = 0;
515 bool first = false, always = false;
516
517 switch (mode)
518 {
519 case SCAN_FIRST:
520 first = true;
521 break;
522 case SCAN_ALWAYS:
523 always = true;
524 break;
525 default: /* never reached */
526 ABORT_TRACE ();
527 }
528
529 while (!feof (stream))
530 {
531 size_t bytes_read;
532 char *eol;
533 const char *line;
534 memset (buf, '\0', BUF_SIZE);
535 bytes_read = fread (buf, 1, BUF_SIZE - 1, stream);
536 if (bytes_read != (BUF_SIZE - 1) && ferror (stream))
537 vfprintf_fail (formats[FMT_ERROR], BUF_SIZE - 1, "read");
538 line = buf;
539 LOOP: while ((eol = strpbrk (line, "\n\r")))
540 {
541 char *p;
542 if (first || always)
543 {
544 first = false;
545 flags &= ~(CR|LF);
546 if (*eol == '\r')
547 {
548 flags |= CR;
549 if (*(eol + 1) == '\n')
550 flags |= LF;
551 }
552 else if (*eol == '\n')
553 flags |= LF;
554 else
555 vfprintf_fail (formats[FMT_FILE], file, "unrecognized line ending");
556 }
557 if (always)
558 p = eol + SKIP_LINE_ENDINGS (flags);
559 else /* first */
560 {
561 unsigned int i;
562 unsigned int count = sizeof (endings) / sizeof (struct ending);
563 for (i = 0; i < count; i++)
564 {
565 if (flags & endings[i].flags)
566 {
567 char *p;
568 if ((p = strstr (eol, endings[i].newline)) && p == eol)
569 break;
570 else
571 {
572 always = true;
573 goto LOOP;
574 }
575 }
576 }
577 p = eol + SKIP_LINE_ENDINGS (flags);
578 }
579 *eol = '\0';
580 print_line (colors, bold, line, flags);
581 line = p;
582 }
583 print_line (colors, bold, line, 0);
584 }
585 }
586
587 static void
588 find_color_entries (struct color_name **color_names, const struct color **colors)
589 {
590 struct timeval tv;
591 unsigned int index;
592
593 /* randomness */
594 gettimeofday (&tv, NULL);
595 srand (tv.tv_usec * tv.tv_sec);
596
597 for (index = 0; color_names[index]; index++)
598 {
599 const char *color_name = color_names[index]->name;
600
601 const unsigned int count = tables[index].count;
602 const struct color *const color_entries = tables[index].entries;
603
604 if (streq (color_name, "random"))
605 {
606 bool excludable;
607 unsigned int i;
608 do {
609 excludable = false;
610 i = rand() % (count - 2) + 1; /* omit color none and default */
611 switch (index)
612 {
613 case FOREGROUND:
614 /* --exclude-random */
615 if (exclude && streq (exclude, color_entries[i].name))
616 excludable = true;
617 else if (color_names[BACKGROUND] && streq (color_names[BACKGROUND]->name, color_entries[i].name))
618 excludable = true;
619 break;
620 case BACKGROUND:
621 if (streq (colors[FOREGROUND]->name, color_entries[i].name))
622 excludable = true;
623 break;
624 default: /* never reached */
625 ABORT_TRACE ();
626 }
627 } while (excludable);
628 colors[index] = (struct color *)&color_entries[i];
629 }
630 else
631 find_color_entry (color_name, index, colors);
632 }
633 }
634
635 static void
636 find_color_entry (const char *const color_name, unsigned int index, const struct color **colors)
637 {
638 bool found = false;
639 unsigned int i;
640
641 const unsigned int count = tables[index].count;
642 const struct color *const color_entries = tables[index].entries;
643
644 for (i = 0; i < count; i++)
645 if (streq (color_name, color_entries[i].name))
646 {
647 colors[index] = (struct color *)&color_entries[i];
648 found = true;
649 break;
650 }
651 if (!found)
652 vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color_name, "not recognized");
653 }
654
655 static void
656 print_line (const struct color **colors, bool bold, const char *const line, unsigned int flags)
657 {
658 if (colors[BACKGROUND] && colors[BACKGROUND]->code)
659 printf ("\033[%s", colors[BACKGROUND]->code);
660 if (colors[FOREGROUND]->code)
661 printf ("\033[%s%s%s\033[0m", bold ? "1;" : "", colors[FOREGROUND]->code, line);
662 else
663 printf (formats[FMT_GENERIC], line);
664 if (flags & CR)
665 putchar ('\r');
666 if (flags & LF)
667 putchar ('\n');
668 }
669
670 static char *
671 xstrdup (const char *str)
672 {
673 const unsigned int len = strlen (str) + 1;
674 char *p = malloc (len);
675 assert (p != NULL);
676 strncpy (p, str, len);
677 return p;
678 }
679
680 static void
681 vfprintf_fail (const char *fmt, ...)
682 {
683 va_list ap;
684 fprintf (stderr, "%s: ", program_name);
685 va_start (ap, fmt);
686 vfprintf (stderr, fmt, ap);
687 va_end (ap);
688 fprintf (stderr, "\n");
689 exit (EXIT_FAILURE);
690 }
691
692 static void
693 stack_var (void ***list, unsigned int *stacked, unsigned int index, void *ptr)
694 {
695 /* nothing to stack */
696 if (ptr == NULL)
697 return;
698 if (!*list)
699 *list = malloc (sizeof (void *));
700 else
701 {
702 unsigned int i;
703 for (i = 0; i < *stacked; i++)
704 if (!(*list)[i])
705 {
706 (*list)[i] = ptr;
707 return; /* reused */
708 }
709 *list = realloc (*list, (*stacked + 1) * sizeof (void *));
710 }
711 (*list)[index] = ptr;
712 (*stacked)++;
713 }
714
715 static void
716 release_var (void **list, unsigned int stacked, void **ptr)
717 {
718 unsigned int i;
719 /* nothing to release */
720 if (*ptr == NULL)
721 return;
722 for (i = 0; i < stacked; i++)
723 if (list[i] == *ptr)
724 {
725 free (*ptr);
726 *ptr = NULL;
727 list[i] = NULL;
728 return;
729 }
730 }