* colorize - Read text from standard input stream or file and print
* it colorized through use of ANSI escape sequences
*
- * Copyright (c) 2011-2014 Steven Schubiger
+ * Copyright (c) 2011-2015 Steven Schubiger
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
#define _BSD_SOURCE
#define _XOPEN_SOURCE 700
+#define _FILE_OFFSET_BITS 64
#include <assert.h>
#include <ctype.h>
#include <errno.h>
&& (streq (color_names[color2]->name, "none") \
|| streq (color_names[color2]->name, "default")) \
+#define ALLOC_COMPLETE_PART_LINE 8
+
#define COLOR_SEP_CHAR '/'
-#define VERSION "0.54"
+#define DEBUG_FILE "debug.txt"
+
+#define VERSION "0.57"
typedef enum { false, true } bool;
FMT_TYPE
};
static const char *formats[] = {
- "%s", /* generic */
- "%s '%s'", /* string */
- "%s `%s' %s", /* quote */
- "%s color '%s' %s", /* color */
- "%s color '%s' %s '%s'", /* random */
- "less than %u bytes %s", /* error */
- "%s: %s", /* file */
- "%s: %s: %s", /* type */
+ "%s", /* generic */
+ "%s '%s'", /* string */
+ "%s `%s' %s", /* quote */
+ "%s color '%s' %s", /* color */
+ "%s color '%s' %s '%s'", /* random */
+ "less than %lu bytes %s", /* error */
+ "%s: %s", /* file */
+ "%s: %s: %s", /* type */
};
enum { FOREGROUND, BACKGROUND };
{ bg_colors, sizeof (bg_colors) / sizeof (struct color), "background" },
};
-static FILE *stream = NULL;
+static FILE *stream;
+#if DEBUG
+static FILE *log;
+#endif
-static unsigned int stacked_vars = 0;
-static void **vars_list = NULL;
+static unsigned int stacked_vars;
+static void **vars_list;
-static bool clean = false;
-static bool clean_all = false;
+static bool clean;
+static bool clean_all;
-static char *exclude = NULL;
+static char *exclude;
static const char *program_name;
+static void process_opts (int, char **);
static void print_hint (void);
static void print_help (void);
static void print_version (void);
static void process_args (unsigned int, char **, bool *, const struct color **, const char **, FILE **);
static void process_file_arg (const char *, const char **, FILE **);
static void read_print_stream (bool, const struct color **, const char *, FILE *);
+static void merge_print_line (bool, const struct color **, const char *, const char *, FILE *);
+static void complete_part_line (const char *, char **, FILE *);
+static bool get_next_char (char *, const char **, FILE *, bool *);
+static void save_char (char, char **, unsigned long *, size_t *);
static void find_color_entries (struct color_name **, const struct color **);
static void find_color_entry (const struct color_name *, unsigned int, const struct color **);
static void print_line (bool, const struct color **, const char * const, unsigned int);
static void print_clean (const char *);
-static void print_free_offsets (const char *, char ***, unsigned int);
+static bool is_esc (const char *);
+static const char *get_end_of_esc (const char *);
+static const char *get_end_of_text (const char *);
+static void print_text (const char *, size_t);
+static bool gather_esc_offsets (const char *, const char **, const char **);
+static bool validate_esc_clean_all (const char **);
+static bool validate_esc_clean (int, unsigned int, const char **, bool *);
+static bool is_reset (int, unsigned int, const char **);
+static bool is_bold (int, unsigned int, const char **);
+static bool is_fg_color (int, const char **);
+static bool is_bg_color (int, unsigned int, const char **);
#if !DEBUG
static void *malloc_wrap (size_t);
static void *calloc_wrap (size_t, size_t);
static bool get_bytes_size (unsigned long, struct bytes_size *);
static char *get_file_type (mode_t);
static bool has_color_name (const char *, const char *);
+static FILE *open_file (const char *, const char *);
static void vfprintf_diag (const char *, ...);
static void vfprintf_fail (const char *, ...);
static void stack_var (void ***, unsigned int *, unsigned int, void *);
static void release_var (void **, unsigned int, void **);
+extern int optind;
+
+int
+main (int argc, char **argv)
+{
+ unsigned int arg_cnt = 0;
+
+ bool bold = false;
+
+ const struct color *colors[2] = {
+ NULL, /* foreground */
+ NULL, /* background */
+ };
+
+ const char *file = NULL;
+
+ program_name = argv[0];
+ atexit (cleanup);
+
+ setvbuf (stdout, NULL, _IOLBF, 0);
+
+#if DEBUG
+ log = open_file (DEBUG_FILE, "w");
+#endif
+
+ process_opts (argc, argv);
+
+ arg_cnt = argc - optind;
+
+ if (clean || clean_all)
+ {
+ if (clean && clean_all)
+ vfprintf_fail (formats[FMT_GENERIC], "--clean and --clean-all switch are mutually exclusive");
+ if (arg_cnt > 1)
+ {
+ const char *format = "%s %s";
+ const char *message = "switch cannot be used with more than one file";
+ if (clean)
+ vfprintf_fail (format, "--clean", message);
+ else if (clean_all)
+ vfprintf_fail (format, "--clean-all", message);
+ }
+ }
+ else
+ {
+ if (arg_cnt == 0 || arg_cnt > 2)
+ {
+ vfprintf_diag ("%u arguments provided, expected 1-2 arguments or clean option", arg_cnt);
+ print_hint ();
+ exit (EXIT_FAILURE);
+ }
+ }
+
+ if (clean || clean_all)
+ process_file_arg (argv[optind], &file, &stream);
+ else
+ process_args (arg_cnt, &argv[optind], &bold, colors, &file, &stream);
+ read_print_stream (bold, colors, file, stream);
+
+ RELEASE_VAR (exclude);
+
+ exit (EXIT_SUCCESS);
+}
+
#define SET_OPT_TYPE(type) \
opt_type = type; \
opt = 0; \
goto PARSE_OPT; \
extern char *optarg;
-extern int optind;
+static int opt_type;
-static int opt_type = 0;
-
-int
-main (int argc, char **argv)
+static void
+process_opts (int argc, char **argv)
{
- unsigned int arg_cnt = 0;
-
enum {
OPT_CLEAN = 1,
OPT_CLEAN_ALL,
{ NULL, 0, NULL, 0 },
};
- bool bold = false;
-
- const struct color *colors[2] = {
- NULL, /* foreground */
- NULL, /* background */
- };
-
- const char *file = NULL;
-
- program_name = argv[0];
- atexit (cleanup);
-
- setvbuf (stdout, NULL, _IOLBF, 0);
-
- while ((opt = getopt_long (argc, argv, "hv", long_opts, NULL)) != -1)
+ while ((opt = getopt_long (argc, argv, "hV", long_opts, NULL)) != -1)
{
PARSE_OPT:
switch (opt)
break;
case 'h':
SET_OPT_TYPE (OPT_HELP);
- case 'v':
+ case 'V':
SET_OPT_TYPE (OPT_VERSION);
case '?':
print_hint ();
ABORT_TRACE ();
}
}
-
- arg_cnt = argc - optind;
-
- if (clean || clean_all)
- {
- if (clean && clean_all)
- vfprintf_fail (formats[FMT_GENERIC], "--clean and --clean-all switch are mutually exclusive");
- if (arg_cnt > 1)
- {
- const char *format = "%s %s";
- const char *message = "switch cannot be used with more than one file";
- if (clean)
- vfprintf_fail (format, "--clean", message);
- else if (clean_all)
- vfprintf_fail (format, "--clean-all", message);
- }
- }
- else
- {
- if (arg_cnt == 0 || arg_cnt > 2)
- {
- vfprintf_diag ("%u arguments provided, expected 1-2 arguments or clean option", arg_cnt);
- print_hint ();
- exit (EXIT_FAILURE);
- }
- }
-
- if (clean || clean_all)
- process_file_arg (argv[optind], &file, &stream);
- else
- process_args (arg_cnt, &argv[optind], &bold, colors, &file, &stream);
- read_print_stream (bold, colors, file, stream);
-
- RELEASE_VAR (exclude);
-
- exit (EXIT_SUCCESS);
}
static void
printf ("\t\t --clean-all\n");
printf ("\t\t --exclude-random\n");
printf ("\t\t-h, --help\n");
- printf ("\t\t-v, --version\n\n");
+ printf ("\t\t-V, --version\n\n");
}
static void
if (stream && fileno (stream) != STDIN_FILENO)
fclose (stream);
+#if DEBUG
+ if (log)
+ fclose (log);
+#endif
if (vars_list)
{
unsigned int i;
for (i = 0; i < stacked_vars; i++)
if (vars_list[i])
- free_null (vars_list[i]);
+ free (vars_list[i]);
free_null (vars_list);
}
unsigned int i;
for (i = 0; color_names[i]; i++)
{
- free_null (color_names[i]->name);
- free_null (color_names[i]->orig);
+ free (color_names[i]->name);
+ free (color_names[i]->orig);
free_null (color_names[i]);
}
}
/* Ensure that we don't fail if there's a file with one or more
color names in its path. */
- if (ret != -1)
+ if (ret == 0) /* success */
{
bool have_file;
unsigned int c;
break;
case BACKGROUND:
vfprintf_fail (formats[FMT_COLOR], tables[BACKGROUND].desc, color, "cannot be bold");
- break;
default: /* never reached */
ABORT_TRACE ();
}
if (color_names[BACKGROUND])
{
unsigned int i;
- unsigned int color_sets[2][2] = { { FOREGROUND, BACKGROUND }, { BACKGROUND, FOREGROUND } };
+ const unsigned int color_sets[2][2] = { { FOREGROUND, BACKGROUND }, { BACKGROUND, FOREGROUND } };
for (i = 0; i < 2; i++)
{
- unsigned int color1 = color_sets[i][0];
- unsigned int color2 = color_sets[i][1];
+ const unsigned int color1 = color_sets[i][0];
+ const unsigned int color2 = color_sets[i][1];
if (CHECK_COLORS_RANDOM (color1, color2))
vfprintf_fail (formats[FMT_RANDOM], tables[color1].desc, color_names[color1]->orig, "cannot be combined with", color_names[color2]->orig);
}
*stream = stdin;
else
{
- FILE *s;
const char *file = file_string;
struct stat sb;
int ret;
errno = 0;
- ret = lstat (file, &sb);
+ ret = stat (file, &sb);
if (ret == -1)
vfprintf_fail (formats[FMT_FILE], file, strerror (errno));
if (!VALID_FILE_TYPE (sb.st_mode))
vfprintf_fail (formats[FMT_TYPE], file, "unrecognized type", get_file_type (sb.st_mode));
- errno = 0;
-
- s = fopen (file, "r");
- if (!s)
- vfprintf_fail (formats[FMT_FILE], file, strerror (errno));
- *stream = s;
+ *stream = open_file (file, "r");
}
*file = file_string;
}
assert (*file);
}
-#define MERGE_PRINT_LINE(part_line, line, flags, check_eof) do { \
- char *current_line, *merged_line = NULL; \
- if (part_line) \
- { \
- merged_line = str_concat (part_line, line); \
- free_null (part_line); \
- } \
- current_line = merged_line ? merged_line : (char *)line; \
- if (!check_eof || *current_line != '\0') \
- print_line (bold, colors, current_line, flags); \
- free (merged_line); \
-} while (false)
-
static void
read_print_stream (bool bold, const struct color **colors, const char *file, FILE *stream)
{
- char buf[BUF_SIZE + 1], *part_line = NULL;
+ char buf[BUF_SIZE + 1];
unsigned int flags = 0;
while (!feof (stream))
vfprintf_fail (formats[FMT_FILE], file, "unrecognized line ending");
p = eol + SKIP_LINE_ENDINGS (flags);
*eol = '\0';
- MERGE_PRINT_LINE (part_line, line, flags, false);
+ print_line (bold, colors, line, flags);
line = p;
}
- if (feof (stream)) {
- MERGE_PRINT_LINE (part_line, line, 0, true);
- }
- else if (*line != '\0')
+ if (feof (stream))
{
- if (!clean && !clean_all) /* efficiency */
+ if (*line != '\0')
print_line (bold, colors, line, 0);
- else if (!part_line)
- part_line = xstrdup (line);
+ }
+ else if (*line != '\0')
+ {
+ char *p;
+ if ((clean || clean_all) && (p = strrchr (line, '\033')))
+ merge_print_line (bold, colors, line, p, stream);
else
- {
- char *merged_line = str_concat (part_line, line);
- free (part_line);
- part_line = merged_line;
- }
+ print_line (bold, colors, line, 0);
+ }
+ }
+}
+
+static void
+merge_print_line (bool bold, const struct color **colors, const char *line, const char *p, FILE *stream)
+{
+ char *buf = NULL;
+ char *merged_part_line = NULL;
+ const char *part_line;
+
+ complete_part_line (p + 1, &buf, stream);
+
+ if (buf)
+ part_line = merged_part_line = str_concat (line, buf);
+ else
+ part_line = line;
+ free (buf);
+
+#ifdef TEST_MERGE_PART_LINE
+ printf ("%s", part_line);
+ free (merged_part_line);
+ exit (EXIT_SUCCESS);
+#else
+ print_line (bold, colors, part_line, 0);
+ free (merged_part_line);
+#endif
+}
+
+static void
+complete_part_line (const char *p, char **buf, FILE *stream)
+{
+ bool got_next_char = false, read_from_stream;
+ char ch;
+ unsigned long i = 0;
+ size_t size;
+
+ if (get_next_char (&ch, &p, stream, &read_from_stream))
+ {
+ if (ch == '[')
+ {
+ if (read_from_stream)
+ save_char (ch, buf, &i, &size);
+ }
+ else
+ {
+ if (read_from_stream)
+ ungetc ((int)ch, stream);
+ return; /* cancel */
+ }
+ }
+ else
+ return; /* cancel */
+
+ while (get_next_char (&ch, &p, stream, &read_from_stream))
+ {
+ if (isdigit (ch) || ch == ';')
+ {
+ if (read_from_stream)
+ save_char (ch, buf, &i, &size);
+ }
+ else /* read next character */
+ {
+ got_next_char = true;
+ break;
+ }
+ }
+
+ if (got_next_char)
+ {
+ if (ch == 'm')
+ {
+ if (read_from_stream)
+ save_char (ch, buf, &i, &size);
+ }
+ else
+ {
+ if (read_from_stream)
+ ungetc ((int)ch, stream);
+ return; /* cancel */
+ }
+ }
+ else
+ return; /* cancel */
+}
+
+static bool
+get_next_char (char *ch, const char **p, FILE *stream, bool *read_from_stream)
+{
+ if (**p == '\0')
+ {
+ int c;
+ if ((c = fgetc (stream)) != EOF)
+ {
+ *ch = (char)c;
+ *read_from_stream = true;
+ return true;
+ }
+ else
+ {
+ *read_from_stream = false;
+ return false;
}
}
+ else
+ {
+ *ch = **p;
+ (*p)++;
+ *read_from_stream = false;
+ return true;
+ }
+}
+
+static void
+save_char (char ch, char **buf, unsigned long *i, size_t *size)
+{
+ if (!*buf)
+ {
+ *size = ALLOC_COMPLETE_PART_LINE;
+ *buf = xmalloc (*size);
+ }
+ /* +1: effective occupied size of buffer */
+ else if ((*i + 1) == *size)
+ {
+ *size *= 2;
+ *buf = xrealloc (*buf, *size);
+ }
+ (*buf)[*i] = ch;
+ (*buf)[*i + 1] = '\0';
+ (*i)++;
}
static void
static void
print_clean (const char *line)
{
- const char *p;
- char ***offsets = NULL;
- unsigned int count = 0, i = 0;
+ const char *p = line;
- for (p = line; *p;)
+ if (is_esc (p))
+ p = get_end_of_esc (p);
+
+ while (*p != '\0')
{
- /* ESC[ */
- if (*p == 27 && *(p + 1) == '[')
- {
- const char *begin = p;
- p += 2;
- if (clean_all)
- {
- while (isdigit (*p) || *p == ';')
- p++;
- }
- else if (clean)
- {
- bool check_values;
- unsigned int iter = 0;
- const char *digit;
- do {
- check_values = false;
- iter++;
- if (!isdigit (*p))
- goto DISCARD;
- digit = p;
- while (isdigit (*p))
- p++;
- if (p - digit > 2)
- goto DISCARD;
- else /* check range */
- {
- char val[3];
- int value;
- unsigned int i;
- const unsigned int digits = p - digit;
- for (i = 0; i < digits; i++)
- val[i] = *digit++;
- val[i] = '\0';
- value = atoi (val);
- if (value == 0) /* reset */
- {
- if (iter > 1)
- goto DISCARD;
- goto END;
- }
- else if (value == 1) /* bold */
- {
- bool discard = false;
- if (iter > 1)
- discard = true;
- else if (*p != ';')
- discard = true;
- if (discard)
- goto DISCARD;
- p++;
- check_values = true;
- }
- else if ((value >= 30 && value <= 37) || value == 39) /* foreground colors */
- goto END;
- else if ((value >= 40 && value <= 47) || value == 49) /* background colors */
- {
- if (iter > 1)
- goto DISCARD;
- goto END;
- }
- else
- goto DISCARD;
- }
- } while (iter == 1 && check_values);
- }
- END: if (*p == 'm')
- {
- const char *end = p++;
- if (!offsets)
- offsets = xmalloc (++count * sizeof (char **));
- else
- offsets = xrealloc (offsets, ++count * sizeof (char **));
- offsets[i] = xmalloc (2 * sizeof (char *));
- offsets[i][0] = (char *)begin; /* ESC */
- offsets[i][1] = (char *)end; /* m */
- i++;
- continue;
- }
- DISCARD:
- continue;
- }
- p++;
+ const char *text_start = p;
+ const char *text_end = get_end_of_text (p);
+ print_text (text_start, text_end - text_start);
+ p = get_end_of_esc (text_end);
}
+}
- if (offsets)
- print_free_offsets (line, offsets, count);
- else
- printf (formats[FMT_GENERIC], line);
+static bool
+is_esc (const char *p)
+{
+ return gather_esc_offsets (p, NULL, NULL);
}
-#define SET_CHAR(offset, new, old) \
- *old = *offset; \
- *offset = new; \
+static const char *
+get_end_of_esc (const char *p)
+{
+ const char *esc;
+ const char *end = NULL;
+ while ((esc = strchr (p, '\033')))
+ {
+ if (gather_esc_offsets (esc, NULL, &end))
+ break;
+ p = esc + 1;
+ }
+ return end ? end + 1 : p + strlen (p);
+}
-#define RESTORE_CHAR(offset, old) \
- *offset = old; \
+static const char *
+get_end_of_text (const char *p)
+{
+ const char *esc;
+ const char *start = NULL;
+ while ((esc = strchr (p, '\033')))
+ {
+ if (gather_esc_offsets (esc, &start, NULL))
+ break;
+ p = esc + 1;
+ }
+ return start ? start : p + strlen (p);
+}
static void
-print_free_offsets (const char *line, char ***offsets, unsigned int count)
+print_text (const char *p, size_t len)
{
- char ch;
- unsigned int i;
-
- SET_CHAR (offsets[0][0], '\0', &ch);
- printf (formats[FMT_GENERIC], line);
- RESTORE_CHAR (offsets[0][0], ch);
+ size_t bytes_written;
+ bytes_written = fwrite (p, 1, len, stdout);
+ if (bytes_written != len)
+ vfprintf_fail (formats[FMT_ERROR], (unsigned long)len, "written");
+}
- for (i = 0; i < count; i++)
+static bool
+gather_esc_offsets (const char *p, const char **start, const char **end)
+{
+ /* ESC[ */
+ if (*p == 27 && *(p + 1) == '[')
{
- char ch;
- bool next_offset = false;
- if (i + 1 < count)
+ bool valid = false;
+ const char *begin = p;
+ p += 2;
+ if (clean_all)
+ valid = validate_esc_clean_all (&p);
+ else if (clean)
+ {
+ bool check_values;
+ unsigned int iter = 0;
+ const char *digit;
+ do {
+ check_values = false;
+ iter++;
+ if (!isdigit (*p))
+ break;
+ digit = p;
+ while (isdigit (*p))
+ p++;
+ if (p - digit > 2)
+ break;
+ else /* check range */
+ {
+ char val[3];
+ int value;
+ unsigned int i;
+ const unsigned int digits = p - digit;
+ for (i = 0; i < digits; i++)
+ val[i] = *digit++;
+ val[i] = '\0';
+ value = atoi (val);
+ valid = validate_esc_clean (value, iter, &p, &check_values);
+ }
+ } while (check_values);
+ }
+ if (valid)
{
- SET_CHAR (offsets[i + 1][0], '\0', &ch);
- next_offset = true;
+ if (start)
+ *start = begin;
+ if (end)
+ *end = p;
+ return true;
}
- printf (formats[FMT_GENERIC], offsets[i][1] + 1);
- if (next_offset)
- RESTORE_CHAR (offsets[i + 1][0], ch);
}
- for (i = 0; i < count; i++)
- free_null (offsets[i]);
- free_null (offsets);
+ return false;
+}
+
+static bool
+validate_esc_clean_all (const char **p)
+{
+ while (isdigit (**p) || **p == ';')
+ (*p)++;
+ return (**p == 'm');
+}
+
+static bool
+validate_esc_clean (int value, unsigned int iter, const char **p, bool *check_values)
+{
+ if (is_reset (value, iter, p))
+ return true;
+ else if (is_bold (value, iter, p))
+ {
+ (*p)++;
+ *check_values = true;
+ return false; /* partial escape sequence, need another valid value */
+ }
+ else if (is_fg_color (value, p))
+ return true;
+ else if (is_bg_color (value, iter, p))
+ return true;
+ else
+ return false;
+}
+
+static bool
+is_reset (int value, unsigned int iter, const char **p)
+{
+ return (value == 0 && iter == 1 && **p == 'm');
+}
+
+static bool
+is_bold (int value, unsigned int iter, const char **p)
+{
+ return (value == 1 && iter == 1 && **p == ';');
+}
+
+static bool
+is_fg_color (int value, const char **p)
+{
+ return (((value >= 30 && value <= 37) || value == 39) && **p == 'm');
+}
+
+static bool
+is_bg_color (int value, unsigned int iter, const char **p)
+{
+ return (((value >= 40 && value <= 47) || value == 49) && iter == 1 && **p == 'm');
}
#if !DEBUG
void *p = malloc (size);
if (!p)
MEM_ALLOC_FAIL_DEBUG (file, line);
- vfprintf_diag ("malloc'ed %lu bytes [source file %s, line %u]", (unsigned long)size, file, line);
+ fprintf (log, "%s: malloc'ed %lu bytes [source file %s, line %u]\n", program_name, (unsigned long)size, file, line);
return p;
}
void *p = calloc (nmemb, size);
if (!p)
MEM_ALLOC_FAIL_DEBUG (file, line);
- vfprintf_diag ("calloc'ed %lu bytes [source file %s, line %u]", (unsigned long)(nmemb * size), file, line);
+ fprintf (log, "%s: calloc'ed %lu bytes [source file %s, line %u]\n", program_name, (unsigned long)(nmemb * size), file, line);
return p;
}
void *p = realloc (ptr, size);
if (!p)
MEM_ALLOC_FAIL_DEBUG (file, line);
- vfprintf_diag ("realloc'ed %lu bytes [source file %s, line %u]", (unsigned long)size, file, line);
+ fprintf (log, "%s: realloc'ed %lu bytes [source file %s, line %u]\n", program_name, (unsigned long)size, file, line);
return p;
}
#endif /* !DEBUG */
return true;
}
+static FILE *
+open_file (const char *file, const char *mode)
+{
+ FILE *stream;
+
+ errno = 0;
+ stream = fopen (file, mode);
+ if (!stream)
+ vfprintf_fail (formats[FMT_FILE], file, strerror (errno));
+
+ return stream;
+}
+
#define DO_VFPRINTF(fmt) \
va_list ap; \
fprintf (stderr, "%s: ", program_name); \