]> git.refcnt.org Git - colorize.git/commitdiff
Clean text from color escape sequences
authorSteven Schubiger <stsc@refcnt.org>
Thu, 7 Feb 2013 22:50:09 +0000 (23:50 +0100)
committerSteven Schubiger <stsc@refcnt.org>
Thu, 7 Feb 2013 22:50:09 +0000 (23:50 +0100)
In order to handle end-of-buffer reads with partial line chunks,
merge those when cleaning text.  Pass each line then to the offset
extracting function which gathers begin/end offsets of escape
sequences.  Finally, for printing cleaned lines of text, omit all
color escape sequences as defined by their set of offsets (and
restore characters temporarily overwritten with terminating NUL).

Furthermore, add a basic string concatenation function and wrap
the free memory function to have it nullify pointers.

colorize.c

index 2f02bf49ba0bbbcc1e5e564836d11c5e145df8a7..661a550e08952b1b26aae2604144e696f798a5b6 100644 (file)
@@ -43,6 +43,7 @@
 
 #define xmalloc(size)       malloc_wrap(size,       __FILE__, __LINE__)
 #define xrealloc(ptr, size) realloc_wrap(ptr, size, __FILE__, __LINE__)
+#define free_null(ptr)      free_wrap((void **)&ptr                   )
 #define xstrdup(str)        strdup_wrap(str,        __FILE__, __LINE__)
 
 #if !defined BUF_SIZE || BUF_SIZE <= 0
@@ -171,6 +172,7 @@ static FILE *stream = NULL;
 static unsigned int stacked_vars = 0;
 static void **vars_list = NULL;
 
+static bool clean = false;
 static char *exclude = NULL;
 
 static const char *program_name;
@@ -180,13 +182,18 @@ static void print_version (void);
 static void cleanup (void);
 static void free_color_names (struct color_name **);
 static void process_options (unsigned int, char **, bool *, const struct color **, const char **, FILE **);
+static void process_file_option (const char *, const char **, FILE **);
 static void read_print_stream (bool, const struct color **, const char *, FILE *, enum stream_mode);
 static void find_color_entries (struct color_name **, const struct color **);
 static void find_color_entry (const char *const, unsigned int, const struct color **);
 static void print_line (const struct color **, bool, const char * const, unsigned int);
+static void print_clean (const char *);
+static void print_free_offsets (const char *, char ***, unsigned int);
 static void *malloc_wrap (size_t, const char *, unsigned int);
 static void *realloc_wrap (void *, size_t, const char *, unsigned int);
+static void free_wrap (void **);
 static char *strdup_wrap (const char *, const char *, unsigned int);
+static char *str_concat (const char *, 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 **);
@@ -199,10 +206,9 @@ main (int argc, char **argv)
 {
     unsigned int arg_cnt = 0;
 
-    bool invalid_opt = false;
-
     int opt;
     struct option long_opts[] = {
+        { "clean",          no_argument,       NULL, 'c' },
         { "exclude-random", required_argument, NULL, 'e' },
         { "help",           no_argument,       NULL, 'h' },
         { "version",        no_argument,       NULL, 'v' },
@@ -229,6 +235,9 @@ main (int argc, char **argv)
       {
         switch (opt)
           {
+            case 'c':
+              clean = true;
+              break;
             case 'e': {
               char *p;
               exclude = xstrdup (optarg);
@@ -246,8 +255,8 @@ main (int argc, char **argv)
               print_version ();
               exit (EXIT_SUCCESS);
             case '?':
-              invalid_opt = true;
-              break;
+              print_help ();
+              exit (EXIT_FAILURE);
             default: /* never reached */
               ABORT_TRACE ();
           }
@@ -255,13 +264,24 @@ main (int argc, char **argv)
 
     arg_cnt = argc - optind;
 
-    if (arg_cnt == 0 || arg_cnt > 2 || invalid_opt)
+    if (clean)
       {
-        print_help ();
-        exit (EXIT_FAILURE);
+        if (arg_cnt > 1)
+          vfprintf_fail (formats[FMT_GENERIC], "--clean switch cannot be used with more than one file");
+      }
+    else
+      {
+        if (arg_cnt == 0 || arg_cnt > 2)
+          {
+            print_help ();
+            exit (EXIT_FAILURE);
+          }
       }
 
-    process_options (arg_cnt, &argv[optind], &bold, colors, &file, &stream);
+    if (clean)
+      process_file_option (argv[optind], &file, &stream);
+    else
+      process_options (arg_cnt, &argv[optind], &bold, colors, &file, &stream);
     read_print_stream (bold, colors, file, stream, mode);
 
     RELEASE_VAR (exclude);
@@ -274,7 +294,7 @@ print_help (void)
 {
     unsigned int i;
 
-    printf ("Usage: %s (foreground) OR (foreground)/(background) [-|file]\n\n", program_name);
+    printf ("Usage: %s (foreground) OR (foreground)/(background) OR --clean [-|file]\n\n", program_name);
     printf ("\tColors (foreground) (background)\n");
     for (i = 0; i < tables[FOREGROUND].count; i++)
       {
@@ -293,6 +313,8 @@ print_help (void)
     printf ("\twhereas for lower case colors will be of normal intensity.\n");
 
     printf ("\n\tOptions\n");
+    printf ("\t\t    --clean\n");
+    printf ("\t\t    --exclude-random\n");
     printf ("\t\t-h, --help\n");
     printf ("\t\t-v, --version\n\n");
 }
@@ -324,12 +346,8 @@ cleanup (void)
         unsigned int i;
         for (i = 0; i < stacked_vars; i++)
           if (vars_list[i])
-            {
-              free (vars_list[i]);
-              vars_list[i] = NULL;
-            }
-        free (vars_list);
-        vars_list = NULL;
+            free_null (vars_list[i]);
+        free_null (vars_list);
       }
 }
 
@@ -339,12 +357,9 @@ free_color_names (struct color_name **color_names)
     unsigned int i;
     for (i = 0; color_names[i]; i++)
       {
-        free (color_names[i]->name);
-        color_names[i]->name = NULL;
-        free (color_names[i]->orig);
-        color_names[i]->orig = NULL;
-        free (color_names[i]);
-        color_names[i] = NULL;
+        free_null (color_names[i]->name);
+        free_null (color_names[i]->orig);
+        free_null (color_names[i]);
       }
 }
 
@@ -494,6 +509,12 @@ process_options (unsigned int arg_cnt, char **option_strings, bool *bold, const
     if (!colors[FOREGROUND]->code && colors[BACKGROUND] && colors[BACKGROUND]->code)
       find_color_entry ("default", FOREGROUND, colors);
 
+    process_file_option (file_string, file, stream);
+}
+
+static void
+process_file_option (const char *file_string, const char **file, FILE **stream)
+{
     if (file_string)
       {
         if (streq (file_string, "-"))
@@ -532,10 +553,21 @@ process_options (unsigned int arg_cnt, char **option_strings, bool *bold, const
     assert (*stream);
 }
 
+#define MERGE_PRINT_LINE(part_line, line, flags) do {                   \
+    char *merged_line = NULL;                                           \
+    if (part_line)                                                      \
+      {                                                                 \
+        merged_line = str_concat (part_line, line);                     \
+        free_null (part_line);                                          \
+      }                                                                 \
+    print_line (colors, bold, merged_line ? merged_line : line, flags); \
+    free (merged_line);                                                 \
+} while (false);
+
 static void
 read_print_stream (bool bold, const struct color **colors, const char *file, FILE *stream, enum stream_mode mode)
 {
-    char buf[BUF_SIZE];
+    char buf[BUF_SIZE], *part_line = NULL;
     unsigned int flags = 0;
     bool first = false, always = false;
 
@@ -602,10 +634,25 @@ read_print_stream (bool bold, const struct color **colors, const char *file, FIL
                 p = eol + SKIP_LINE_ENDINGS (flags);
               }
             *eol = '\0';
-            print_line (colors, bold, line, flags);
+            MERGE_PRINT_LINE (part_line, line, flags);
             line = p;
           }
-        print_line (colors, bold, line, 0);
+        if (feof (stream)) {
+          MERGE_PRINT_LINE (part_line, line, 0);
+        }
+        else
+          {
+            if (!clean) /* efficiency */
+              print_line (colors, bold, line, 0);
+            else if (!part_line)
+              part_line = xstrdup (line);
+            else
+              {
+                char *merged_line = str_concat (part_line, line);
+                free (part_line);
+                part_line = merged_line;
+              }
+          }
       }
 }
 
@@ -680,18 +727,132 @@ find_color_entry (const char *const color_name, unsigned int index, const struct
 static void
 print_line (const struct color **colors, bool bold, const char *const line, unsigned int flags)
 {
-    if (colors[BACKGROUND] && colors[BACKGROUND]->code)
-      printf ("\033[%s", colors[BACKGROUND]->code);
-    if (colors[FOREGROUND]->code)
-      printf ("\033[%s%s%s\033[0m", bold ? "1;" : "", colors[FOREGROUND]->code, line);
+    /* --clean */
+    if (clean)
+      print_clean (line);
     else
-      printf (formats[FMT_GENERIC], line);
+      {
+        if (colors[BACKGROUND] && colors[BACKGROUND]->code)
+          printf ("\033[%s", colors[BACKGROUND]->code);
+        if (colors[FOREGROUND]->code)
+          printf ("\033[%s%s%s\033[0m", bold ? "1;" : "", colors[FOREGROUND]->code, line);
+        else
+          printf (formats[FMT_GENERIC], line);
+      }
     if (flags & CR)
       putchar ('\r');
     if (flags & LF)
       putchar ('\n');
 }
 
+static void
+print_clean (const char *line)
+{
+    const char *p;
+    char ***offsets = NULL;
+    unsigned int count = 0, i = 0;
+
+    for (p = line; *p; p++)
+      {
+        /* ESC[ */
+        if (*p == 27 && *(p + 1) == '[')
+          {
+            bool check_values, first = true;
+            const char *begin = p;
+            p += 2;
+            if (!isdigit (*p))
+              goto END;
+            do {
+              const char *digit;
+              check_values = false;
+              if (!first && !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 && value <=  8)   /* attributes        */
+                     || (value >= 30 && value <= 37)   /* foreground colors */
+                     || (value >= 40 && value <= 47)   /* background colors */
+                     || (value == 39 || value == 49))) /* default colors    */
+                    goto DISCARD;
+                }
+              if (*p == ';')
+                {
+                  p++;
+                  check_values = true;
+                }
+              first = false;
+            } while (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++;
+              }
+            DISCARD:
+              continue;
+          }
+      }
+
+    if (offsets)
+      print_free_offsets (line, offsets, count);
+    else
+      printf (formats[FMT_GENERIC], line);
+}
+
+#define SET_CHAR(offset, new, old) \
+    *old = *offset;                \
+    *offset = new;                 \
+
+#define RESTORE_CHAR(offset, old)  \
+    *offset = old;                 \
+
+static void
+print_free_offsets (const char *line, char ***offsets, unsigned int count)
+{
+    char ch;
+    unsigned int i;
+
+    SET_CHAR (offsets[0][0], '\0', &ch);
+    printf (formats[FMT_GENERIC], line);
+    RESTORE_CHAR (offsets[0][0], ch);
+
+    for (i = 0; i < count; i++)
+      {
+        char ch;
+        bool next_offset = false;
+        if (i + 1 < count)
+          {
+            SET_CHAR (offsets[i + 1][0], '\0', &ch);
+            next_offset = 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);
+}
+
 static void *
 malloc_wrap (size_t size, const char *file, unsigned int line)
 {
@@ -710,6 +871,13 @@ realloc_wrap (void *ptr, size_t size, const char *file, unsigned int line)
     return p;
 }
 
+static void
+free_wrap (void **ptr)
+{
+    free (*ptr);
+    *ptr = NULL;
+}
+
 static char *
 strdup_wrap (const char *str, const char *file, unsigned int line)
 {
@@ -721,6 +889,22 @@ strdup_wrap (const char *str, const char *file, unsigned int line)
     return p;
 }
 
+static char *
+str_concat (const char *str1, const char *str2)
+{
+    const unsigned long len = strlen (str1) + strlen (str2) + 1;
+    char *p, *str;
+
+    p = str = xmalloc (len);
+    strncpy (p, str1, strlen (str1));
+    p += strlen (str1);
+    strncpy (p, str2, strlen (str2));
+    p += strlen (str2);
+    *p = '\0';
+
+    return str;
+}
+
 static void
 vfprintf_fail (const char *fmt, ...)
 {