X-Git-Url: http://git.refcnt.org/?a=blobdiff_plain;f=colorize.c;h=06ef8fba68707d2a477c0b3042756282b42c664c;hb=f72501505fef26e5ac48fbaa4611f71891f49b38;hp=1325fce1f10326f432fa55d0edf2c5cde168dec1;hpb=20c83df5d03e4b89d3a93803db0f9f9307517faf;p=colorize.git diff --git a/colorize.c b/colorize.c index 1325fce..06ef8fb 100644 --- a/colorize.c +++ b/colorize.c @@ -104,11 +104,13 @@ && (streq (color_names[color2]->name, "none") \ || streq (color_names[color2]->name, "default")) \ +#define ALLOC_COMPLETE_PART_LINE 8 + #define COLOR_SEP_CHAR '/' #define DEBUG_FILE "debug.txt" -#define VERSION "0.55" +#define VERSION "0.56" typedef enum { false, true } bool; @@ -165,14 +167,14 @@ enum fmts { 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 }; @@ -186,21 +188,22 @@ static const struct { { bg_colors, sizeof (bg_colors) / sizeof (struct color), "background" }, }; -static FILE *stream = NULL; +static FILE *stream; #if DEBUG -static FILE *log = NULL; +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); @@ -209,11 +212,25 @@ static void free_color_names (struct color_name **); 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); @@ -235,21 +252,81 @@ 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, @@ -268,25 +345,7 @@ main (int argc, char **argv) { 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); - -#if DEBUG - log = open_file (DEBUG_FILE, "w"); -#endif - - 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) @@ -330,7 +389,7 @@ main (int argc, char **argv) break; case 'h': SET_OPT_TYPE (OPT_HELP); - case 'v': + case 'V': SET_OPT_TYPE (OPT_VERSION); case '?': print_hint (); @@ -339,42 +398,6 @@ main (int argc, char **argv) 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 @@ -411,7 +434,7 @@ print_help (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 @@ -471,7 +494,7 @@ cleanup (void) 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); } @@ -483,8 +506,8 @@ free_color_names (struct color_name **color_names) 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]); } } @@ -610,7 +633,6 @@ process_args (unsigned int arg_cnt, char **arg_strings, bool *bold, const struct break; case BACKGROUND: vfprintf_fail (formats[FMT_COLOR], tables[BACKGROUND].desc, color, "cannot be bold"); - break; default: /* never reached */ ABORT_TRACE (); } @@ -633,11 +655,11 @@ process_args (unsigned int arg_cnt, char **arg_strings, bool *bold, const struct 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); } @@ -666,13 +688,12 @@ process_file_arg (const char *file_string, const char **file, FILE **stream) *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)); @@ -680,12 +701,7 @@ process_file_arg (const char *file_string, const char **file, FILE **stream) 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; } @@ -699,23 +715,10 @@ process_file_arg (const char *file_string, const char **file, FILE **stream) 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)) @@ -744,26 +747,151 @@ read_print_stream (bool bold, const struct color **colors, const char *file, FIL 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 @@ -859,136 +987,164 @@ print_line (bool bold, const struct color **colors, const char *const line, unsi 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