* 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>
#define streq(s1, s2) (strcmp (s1, s2) == 0)
#if !DEBUG
-# define xmalloc(size) malloc_wrap(size)
-# define xcalloc(nmemb, size) calloc_wrap(nmemb, size)
-# define xrealloc(ptr, size) realloc_wrap(ptr, size)
+# define xmalloc(size) malloc_wrap(size)
+# define xcalloc(nmemb, size) calloc_wrap(nmemb, size)
+# define xrealloc(ptr, size) realloc_wrap(ptr, size)
+# define xstrdup(str) strdup_wrap(str, NULL, 0)
+# define str_concat(str1, str2) str_concat_wrap(str1, str2, NULL, 0)
#else
-# define xmalloc(size) malloc_wrap_debug(size, __FILE__, __LINE__)
-# define xcalloc(nmemb, size) calloc_wrap_debug(nmemb, size, __FILE__, __LINE__)
-# define xrealloc(ptr, size) realloc_wrap_debug(ptr, size, __FILE__, __LINE__)
+# define xmalloc(size) malloc_wrap_debug(size, __FILE__, __LINE__)
+# define xcalloc(nmemb, size) calloc_wrap_debug(nmemb, size, __FILE__, __LINE__)
+# define xrealloc(ptr, size) realloc_wrap_debug(ptr, size, __FILE__, __LINE__)
+# define xstrdup(str) strdup_wrap(str, __FILE__, __LINE__)
+# define str_concat(str1, str2) str_concat_wrap(str1, str2, __FILE__, __LINE__)
#endif
#define free_null(ptr) free_wrap((void **)&ptr)
-#define xstrdup(str) strdup_wrap(str)
-#if BUF_SIZE <= 0 || BUF_SIZE > 65536
+#if defined(BUF_SIZE) && (BUF_SIZE <= 0 || BUF_SIZE > 65536)
# undef BUF_SIZE
#endif
#ifndef BUF_SIZE
#if !DEBUG
# define MEM_ALLOC_FAIL() do { \
fprintf (stderr, "%s: memory allocation failure\n", program_name); \
- exit (2); \
+ exit (EXIT_FAILURE); \
} while (false)
#else
# define MEM_ALLOC_FAIL_DEBUG(file, line) do { \
fprintf (stderr, "Memory allocation failure in source file %s, line %u\n", file, line); \
- exit (2); \
+ exit (EXIT_FAILURE); \
} while (false)
#endif
#define COLOR_SEP_CHAR '/'
-#define VERSION "0.54"
+#define DEBUG_FILE "debug.txt"
+
+#define VERSION "0.56"
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 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 void *realloc_wrap_debug (void *, size_t, const char *, unsigned int);
#endif
static void free_wrap (void **);
-static char *strdup_wrap (const char *);
-static char *str_concat (const char *, const char *);
+static char *strdup_wrap (const char *, const char *, unsigned int);
+static char *str_concat_wrap (const char *, const char *, const char *, unsigned int);
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 *);
extern char *optarg;
extern int optind;
-static int opt_type = 0;
+static int opt_type;
int
main (int argc, char **argv)
setvbuf (stdout, NULL, _IOLBF, 0);
- while ((opt = getopt_long (argc, argv, "hv", long_opts, NULL)) != -1)
+#if DEBUG
+ log = open_file (DEBUG_FILE, "w");
+#endif
+
+ 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 ();
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;
}
static void
print_clean (const char *line)
{
- const char *p;
- char ***offsets = NULL;
- unsigned int count = 0, i = 0;
+ const char *p = line;
+
+ if (is_esc (p))
+ p = get_end_of_esc (p);
- for (p = line; *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)
{
- SET_CHAR (offsets[i + 1][0], '\0', &ch);
- next_offset = true;
+ 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)
+ {
+ 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);
+ 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);
+ 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);
+ fprintf (log, "%s: realloc'ed %lu bytes [source file %s, line %u]\n", program_name, (unsigned long)size, file, line);
return p;
}
#endif /* !DEBUG */
*ptr = NULL;
}
+#if !DEBUG
+# define do_malloc(len, file, line) malloc_wrap(len)
+#else
+# define do_malloc(len, file, line) malloc_wrap_debug(len, file, line)
+#endif
+
static char *
-strdup_wrap (const char *str)
+strdup_wrap (const char *str, const char *file, unsigned int line)
{
const size_t len = strlen (str) + 1;
- char *p = xmalloc (len);
+ char *p = do_malloc (len, file, line);
strncpy (p, str, len);
return p;
}
static char *
-str_concat (const char *str1, const char *str2)
+str_concat_wrap (const char *str1, const char *str2, const char *file, unsigned int line)
{
const size_t len = strlen (str1) + strlen (str2) + 1;
char *p, *str;
- p = str = xmalloc (len);
+ p = str = do_malloc (len, file, line);
strncpy (p, str1, strlen (str1));
p += strlen (str1);
strncpy (p, str2, strlen (str2));
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); \