aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--HACKING14
-rw-r--r--Makefile5
-rw-r--r--README6
-rw-r--r--README.cgit90
-rw-r--r--TODO1
-rw-r--r--colorize.156
-rw-r--r--colorize.c603
-rw-r--r--debian/changelog18
-rw-r--r--debian/compat1
-rw-r--r--debian/control7
-rw-r--r--debian/copyright6
-rw-r--r--doc/colorize.html3
-rwxr-xr-xreadme.pl30
-rwxr-xr-xt/conf/attr_clear.t31
-rwxr-xr-xt/conf/color.t34
-rwxr-xr-xt/conf/endings.t36
-rwxr-xr-xt/conf/fail.t59
-rwxr-xr-xt/conf/param.t51
-rwxr-xr-xt/conf/parse/fail.t64
-rwxr-xr-xt/conf/parse/success.t74
-rwxr-xr-xt/conf/use.t57
-rwxr-xr-xt/merge.t4
-rwxr-xr-xtest.pl16
23 files changed, 1135 insertions, 131 deletions
diff --git a/HACKING b/HACKING
index 95d150b..ea2ab12 100644
--- a/HACKING
+++ b/HACKING
@@ -7,3 +7,17 @@ The colorize sources ought to be made available as a Git repository,
otherwise no detailed git version hash may be included in the version
output of colorize. Even versions of older commits will no longer be
accessible. See version.pl for some in depth code.
+
+Debian-Release instruction hints
+--------------------------------
+$ tar cvvzf ../colorize_0.vv.orig.tar.gz --exclude=debian --exclude-vcs .
+$ dpkg-buildpackage -uc -us 2>&1 | tee ../dpkg-buildpackage.log
+$ lintian
+$ lintian --pedantic
+$ duck
+$ hardening-check ./debian/colorize/usr/bin/colorize
+$ blhc ../dpkg-buildpackage.log
+$ bls-standalone check ../dpkg-buildpackage.log
+$ adequate colorize
+/usr/share/doc/debian-policy/upgrading-checklist.txt.gz
+https://people.debian.org/~abe/sponsoring/
diff --git a/Makefile b/Makefile
index 868ec4c..33b66a7 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-.PHONY: check check_valgrind install clean release
+.PHONY: check check_valgrind install clean release readme
.SUFFIXES:
.SUFFIXES: .c .o
@@ -30,3 +30,6 @@ clean:
release:
sh ./release.sh
+
+readme:
+ perl ./readme.pl
diff --git a/README b/README
index 4fcf139..20c1f00 100644
--- a/README
+++ b/README
@@ -56,6 +56,12 @@ file provided here for the `tee' invocation will be populated
with the captured output from both standard output and error
stream.
+Configuration File
+------------------
+A user configuration file may be populated with options and
+according values. See man page source file `colorize.1' for
+details.
+
Documentation
-------------
See man page source file: colorize.1.
diff --git a/README.cgit b/README.cgit
new file mode 100644
index 0000000..6568c1b
--- /dev/null
+++ b/README.cgit
@@ -0,0 +1,90 @@
+<pre>
+colorize
+========
+
+Description
+-----------
+Colorize aims at being a small, independent and handy command-line
+text colorizing tool. It emits ANSI escape sequences in order to
+color lines of text; also, sequences emitted by colorize or foreign
+programs may be cleared.
+
+The main code is written in C (c89 mostly), whereas the test script
+consists of Perl code.
+
+Colorize is known to build and test successfully on Linux and
+Net/Open/MirBSD. Other platforms are untested, so be prepared for
+it to eventually not work as expected there.
+
+Requirements
+------------
+gcc
+make
+perl
+valgrind (optional)
+
+Build instructions
+------------------
+Issue `make' to build colorize.
+
+Once completed, run the tests with `make check'.
+
+Then you should most likely have a working binary.
+
+Next, install it with `make install' (may require elevated
+user permissions).
+
+Finally, clean up the working directory through `make clean'.
+
+Customizing instructions
+------------------------
+The default character ('/') which separates the foreground
+from the background color may be redefined:
+
+`make FLAGS=-DCOLOR_SEP_CHAR_COLON' -&gt; defines as ':'
+`make FLAGS=-DCOLOR_SEP_CHAR_SLASH' -&gt; defines as '/'
+
+Debugging instructions
+----------------------
+For the sake of completeness, colorize can be also built with
+debugging output by issuing `make FLAGS=-DDEBUG'. The intention
+is to provide some memory allocation diagnostics (and might be
+extended in future). Usually, a debugging build is not required.
+
+Furthermore, tests can be run through valgrind by issuing, for
+example, `make check_valgrind 2&gt;&1 | tee valgrind.out'. The
+file provided here for the `tee' invocation will be populated
+with the captured output from both standard output and error
+stream.
+
+Configuration File
+------------------
+A user configuration file may be populated with options and
+according values. See man page source file `colorize.1' for
+details.
+
+Documentation
+-------------
+See man page source file: colorize.1.
+
+Usage example
+-------------
+In ~/.bashrc:
+
+| ls_color() {
+| ls "$@" | colorize green -
+| }
+| alias ls=ls_color
+
+This excerpt defines an alias which will set the color being
+printed for literal ls invocations to green.
+
+Afterword
+---------
+Let me know, if you have ideas, bug reports, patches, etc.
+
+Author
+------
+Steven Schubiger &lt;stsc@refcnt.org&gt;
+
+</pre>
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..73f8eca
--- /dev/null
+++ b/TODO
@@ -0,0 +1 @@
+- fix broken OpenBSD build (wordexp.h missing)
diff --git a/colorize.1 b/colorize.1
index f3341ed..5168dac 100644
--- a/colorize.1
+++ b/colorize.1
@@ -1,4 +1,4 @@
-.TH COLORIZE 1 "2019-02-23" "colorize v0.64" "User Commands"
+.TH COLORIZE 1 "2019-09-01" "colorize v0.65" "User Commands"
.SH NAME
colorize \- colorize text on terminal with ANSI escape sequences
.SH SYNOPSIS
@@ -40,6 +40,9 @@ clean text from color escape sequences emitted by colorize
.BR \-\-clean\-all
clean text from all valid color escape sequences
.TP
+.BR \-c ", " \-\-config=\fIPATH\fR
+alternate configuration file location
+.TP
.BR \-\-exclude\-random=\fICOLOR\fR
text color to be excluded when selecting a random foreground color
.TP
@@ -51,6 +54,57 @@ show help screen and exit
.TP
.BR \-V ", " \-\-version
display version data and exit
+.SH FILES
+.TP
+.B ~/.colorize.conf
+user configuration file
+.PP
+.RS
+If the aforementioned file exists, it is read, parsed and processed
+prior to handling the command-line options. Command-line options
+override configuration values, but are currently not capable of
+unsetting them. If unsetting is desired, comment them out or remove
+them.
+.RE
+.PP
+.RS
+.B Sample configuration file
+.RS
+.nf
+# ~/.colorize.conf
+attr = bold,underscore
+color = magenta # favorite one
+exclude-random = black
+omit-color-empty = yes
+.fi
+.RE
+.RE
+.PP
+.RS
+.B Configurable options and values
+.RS
+.nf
+attr (values same as command-line option)
+color (value same as command-line colors)
+exclude-random (value same as command-line option)
+omit-color-empty (yes/no)
+.fi
+.RE
+.RE
+.PP
+.RS
+.B Syntax
+.RS
+Each line ought to start with a name of an option, followed by = and
+an optional value. Leaving the value blank, unsets the option.
+.PP
+Whitespace is allowed before the option name, after the option name,
+before the option value and after the option value.
+.PP
+Comments may be placed before or after the option is set. Everything
+following the '#' sign is treated as being commented out.
+.RE
+.RE
.SH NOTES
The list of color escape sequence codes being emitted and omitted is
as follows:
diff --git a/colorize.c b/colorize.c
index 59cab41..a1e1398 100644
--- a/colorize.c
+++ b/colorize.c
@@ -2,7 +2,7 @@
* colorize - Read text from standard input stream or file and print
* it colorized through use of ANSI escape sequences
*
- * Copyright (c) 2011-2018 Steven Schubiger
+ * Copyright (c) 2011-2019 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
@@ -27,6 +27,7 @@
#include <ctype.h>
#include <errno.h>
#include <getopt.h>
+#include <pwd.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
@@ -36,6 +37,7 @@
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
+#include <wordexp.h>
#ifndef DEBUG
# define DEBUG 0
@@ -79,12 +81,14 @@
#define VALID_FILE_TYPE(mode) (S_ISREG (mode) || S_ISLNK (mode) || S_ISFIFO (mode))
-#define STACK_VAR(ptr) do { \
- stack_var (&vars_list, &stacked_vars, stacked_vars, ptr); \
+#define STACK_VAR(ptr) do { \
+ stack (&vars_list, &stacked_vars, stacked_vars, ptr, IS_GENERIC); \
} while (false)
-
-#define RELEASE_VAR(ptr) do { \
- release_var (vars_list, stacked_vars, (void **)&ptr); \
+#define STACK_FILE(ptr) do { \
+ stack (&vars_list, &stacked_vars, stacked_vars, ptr, IS_FILE); \
+} while (false)
+#define RELEASE(ptr) do { \
+ release (vars_list, stacked_vars, (void **)&ptr); \
} while (false)
#if !DEBUG
@@ -118,6 +122,8 @@
# define COLOR_SEP_CHAR '/'
#endif
+#define CONF_FILE ".colorize.conf"
+
#if DEBUG
# define DEBUG_FILE "debug.txt"
#endif
@@ -126,10 +132,19 @@
#define PROGRAM_NAME "colorize"
-#define VERSION "0.64"
+#define VERSION "0.65"
typedef enum { false, true } bool;
+struct conf {
+ char *attr;
+ char *color;
+ char *exclude_random;
+ char *omit_color_empty;
+};
+
+enum { DESC_OPTION, DESC_CONF };
+
struct color_name {
char *name;
char *orig;
@@ -178,17 +193,21 @@ enum {
FMT_RANDOM,
FMT_ERROR,
FMT_FILE,
- FMT_TYPE
+ FMT_TYPE,
+ FMT_CONF,
+ FMT_CONF_FILE
};
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 %lu 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 */
+ "%s: option '%s' %s", /* conf */
+ "config file %s: %s" /* conf file */
};
enum { GENERIC, FOREGROUND = 0, BACKGROUND };
@@ -202,10 +221,22 @@ static const struct {
{ bg_colors, COUNT_OF (bg_colors, struct color), "background" },
};
+static unsigned int opts_set;
+enum opt_set {
+ OPT_ATTR_SET = 0x01,
+ OPT_EXCLUDE_RANDOM_SET = 0x02,
+ OPT_OMIT_COLOR_EMPTY_SET = 0x04
+};
+static struct {
+ char *attr;
+ char *exclude_random;
+} opts_arg = { NULL, NULL };
+
enum {
OPT_ATTR = 1,
OPT_CLEAN,
OPT_CLEAN_ALL,
+ OPT_CONFIG,
OPT_EXCLUDE_RANDOM,
OPT_OMIT_COLOR_EMPTY,
OPT_HELP,
@@ -216,6 +247,7 @@ static const struct option long_opts[] = {
{ "attr", required_argument, &opt_type, OPT_ATTR },
{ "clean", no_argument, &opt_type, OPT_CLEAN },
{ "clean-all", no_argument, &opt_type, OPT_CLEAN_ALL },
+ { "config", required_argument, &opt_type, OPT_CONFIG },
{ "exclude-random", required_argument, &opt_type, OPT_EXCLUDE_RANDOM },
{ "omit-color-empty", no_argument, &opt_type, OPT_OMIT_COLOR_EMPTY },
{ "help", no_argument, &opt_type, OPT_HELP },
@@ -236,13 +268,23 @@ struct attr {
enum attr_type type;
};
+enum var_type {
+ IS_GENERIC,
+ IS_FILE,
+ IS_UNUSED
+};
+struct var_list {
+ void *ptr;
+ enum var_type type;
+};
+
static FILE *stream;
#if DEBUG
static FILE *log;
#endif
static unsigned int stacked_vars;
-static void **vars_list;
+static struct var_list *vars_list;
static bool clean;
static bool clean_all;
@@ -253,18 +295,27 @@ static char *exclude;
static const char *program_name;
+#if DEBUG
static void print_tstamp (FILE *);
-static void process_opts (int, char **);
-static void process_opt_attr (const char *);
-static void write_attr (const struct attr *, unsigned int *);
+#endif
+static void process_opts (int, char **, char **);
+static void conf_file_path (char **);
+static void process_opt_attr (const char *, const bool);
+static void write_attr (const struct attr *, unsigned int *, const bool);
+static void process_opt_exclude_random (const char *, const bool);
+static void parse_conf (const char *, struct conf *);
+static void assign_conf (const char *, struct conf *, const char *, char *);
+static void init_conf_vars (const struct conf *);
+static void init_opts_vars (void);
static void print_hint (void);
static void print_help (void);
static void print_version (void);
static void cleanup (void);
static void free_color_names (struct color_name **);
-static void process_args (unsigned int, char **, char *, const struct color **, const char **, FILE **);
+static void free_conf (struct conf *);
+static void process_args (unsigned int, char **, char *, const struct color **, const char **, FILE **, struct conf *);
static void process_file_arg (const char *, const char **, FILE **);
-static void skip_path_colors (const char *, const char *, const struct stat *);
+static bool skip_path_colors (const char *, const char *, const struct stat *, const bool);
static void gather_color_names (const char *, char *, struct color_name **);
static void read_print_stream (const char *, const struct color **, const char *, FILE *);
static void merge_print_line (const char *, const char *, FILE *);
@@ -298,16 +349,15 @@ static void *realloc_wrap_debug (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_wrap (const char *, const char *, const char *, unsigned int);
+static char *expand_string (const char *);
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;
+static void stack (struct var_list **, unsigned int *, unsigned int, void *, enum var_type);
+static void release (struct var_list *, unsigned int, void **);
int
main (int argc, char **argv)
@@ -321,6 +371,9 @@ main (int argc, char **argv)
const char *file = NULL;
+ char *conf_file = NULL;
+ struct conf config = { NULL, NULL, NULL, NULL };
+
program_name = argv[0];
atexit (cleanup);
@@ -329,11 +382,49 @@ main (int argc, char **argv)
#if DEBUG
log = open_file (DEBUG_FILE, "w");
print_tstamp (log);
+ /* We're in debugging mode, hence we can't invoke STACK_FILE()
+ prior to print_tstamp(), because both cause text to be written
+ to the same logfile which is expected to have the timestamp
+ first. */
+ STACK_FILE (log);
#endif
attr[0] = '\0';
- process_opts (argc, argv);
+ process_opts (argc, argv, &conf_file);
+
+#ifdef CONF_FILE_TEST
+ conf_file = to_str (CONF_FILE_TEST);
+#elif !defined(TEST)
+ if (conf_file == NULL)
+ {
+ conf_file_path (&conf_file);
+ STACK_VAR (conf_file);
+ }
+ else
+ {
+ char *s;
+ if ((s = expand_string (conf_file)))
+ {
+ free (conf_file);
+ conf_file = s;
+ }
+ STACK_VAR (conf_file);
+ errno = 0;
+ if (access (conf_file, F_OK) == -1)
+ vfprintf_fail (formats[FMT_CONF_FILE], conf_file, strerror (errno));
+ }
+#endif
+#if defined(CONF_FILE_TEST) || !defined(TEST)
+ if (access (conf_file, F_OK) != -1)
+ parse_conf (conf_file, &config);
+#endif
+#if !defined(CONF_FILE_TEST) && !defined(TEST)
+ RELEASE (conf_file);
+#endif
+ init_conf_vars (&config);
+
+ init_opts_vars ();
arg_cnt = argc - optind;
@@ -342,20 +433,27 @@ main (int argc, char **argv)
if (clean && clean_all)
vfprintf_fail (formats[FMT_GENERIC], "--clean and --clean-all switch are mutually exclusive");
if (arg_cnt > 1)
- {
- const char *const format = "%s %s";
- const char *const 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);
- }
+ vfprintf_fail ("--clean%s switch cannot be used with more than one file", clean_all ? "-all" : "");
+ {
+ unsigned int i;
+ const struct option_set {
+ const char *option;
+ enum opt_set set;
+ } options[] = {
+ { "attr", OPT_ATTR_SET },
+ { "exclude-random", OPT_EXCLUDE_RANDOM_SET },
+ { "omit-color-empty", OPT_OMIT_COLOR_EMPTY_SET },
+ };
+ for (i = 0; i < COUNT_OF (options, struct option_set); i++)
+ if (opts_set & options[i].set)
+ vfprintf_diag ("--%s switch has no meaning with --clean%s", options[i].option, clean_all ? "-all" : "");
+ }
}
else
{
if (arg_cnt == 0 || arg_cnt > 2)
{
- vfprintf_diag ("%u arguments provided, expected 1-2 arguments or clean option", arg_cnt);
+ vfprintf_diag ("%u arguments provided, expected 1-2 arguments or --clean[-all]", arg_cnt);
print_hint ();
exit (EXIT_FAILURE);
}
@@ -364,14 +462,17 @@ main (int argc, char **argv)
if (clean || clean_all)
process_file_arg (argv[optind], &file, &stream);
else
- process_args (arg_cnt, &argv[optind], &attr[0], colors, &file, &stream);
+ process_args (arg_cnt, &argv[optind], &attr[0], colors, &file, &stream, &config);
read_print_stream (&attr[0], colors, file, stream);
- RELEASE_VAR (exclude);
+ free_conf (&config);
+
+ RELEASE (exclude);
exit (EXIT_SUCCESS);
}
+#if DEBUG
static void
print_tstamp (FILE *log)
{
@@ -396,6 +497,11 @@ print_tstamp (FILE *log)
fprintf (log, "=");
fprintf (log, "\n");
}
+#endif
+
+#define DUP_CONFIG() \
+ *conf_file = xstrdup (optarg); \
+ break;
#define PRINT_HELP_EXIT() \
print_help (); \
@@ -405,13 +511,11 @@ print_tstamp (FILE *log)
print_version (); \
exit (EXIT_SUCCESS);
-extern char *optarg;
-
static void
-process_opts (int argc, char **argv)
+process_opts (int argc, char **argv, char **conf_file)
{
int opt;
- while ((opt = getopt_long (argc, argv, "hV", long_opts, NULL)) != -1)
+ while ((opt = getopt_long (argc, argv, "c:hV", long_opts, NULL)) != -1)
{
switch (opt)
{
@@ -419,7 +523,9 @@ process_opts (int argc, char **argv)
switch (opt_type)
{
case OPT_ATTR:
- process_opt_attr (optarg);
+ opts_set |= OPT_ATTR_SET;
+ opts_arg.attr = xstrdup (optarg);
+ STACK_VAR (opts_arg.attr);
break;
case OPT_CLEAN:
clean = true;
@@ -427,26 +533,15 @@ process_opts (int argc, char **argv)
case OPT_CLEAN_ALL:
clean_all = true;
break;
- case OPT_EXCLUDE_RANDOM: {
- bool valid = false;
- unsigned int i;
- exclude = xstrdup (optarg);
- STACK_VAR (exclude);
- for (i = 1; i < tables[GENERIC].count - 1; i++) /* skip color none and default */
- {
- const struct color *entry = &tables[GENERIC].entries[i];
- if (streq (exclude, entry->name))
- {
- valid = true;
- break;
- }
- }
- if (!valid)
- vfprintf_fail (formats[FMT_GENERIC], "--exclude-random switch must be provided a plain color");
+ case OPT_CONFIG:
+ DUP_CONFIG ();
+ case OPT_EXCLUDE_RANDOM:
+ opts_set |= OPT_EXCLUDE_RANDOM_SET;
+ opts_arg.exclude_random = xstrdup (optarg);
+ STACK_VAR (opts_arg.exclude_random);
break;
- }
case OPT_OMIT_COLOR_EMPTY:
- omit_color_empty = true;
+ opts_set |= OPT_OMIT_COLOR_EMPTY_SET;
break;
case OPT_HELP:
PRINT_HELP_EXIT ();
@@ -456,6 +551,8 @@ process_opts (int argc, char **argv)
ABORT_TRACE ();
}
break;
+ case 'c':
+ DUP_CONFIG ();
case 'h':
PRINT_HELP_EXIT ();
case 'V':
@@ -470,7 +567,33 @@ process_opts (int argc, char **argv)
}
static void
-process_opt_attr (const char *p)
+conf_file_path (char **conf_file)
+{
+ char *path;
+ uid_t uid;
+ struct passwd *passwd;
+ size_t size;
+
+ uid = getuid ();
+ errno = 0;
+ if ((passwd = getpwuid (uid)) == NULL)
+ {
+ if (errno == 0)
+ vfprintf_diag ("password file entry for uid %lu not found", (unsigned long)uid);
+ else
+ perror ("getpwuid");
+ exit (EXIT_FAILURE);
+ }
+ /* getpwuid() leaks memory */
+ size = strlen (passwd->pw_dir) + 1 + strlen (CONF_FILE) + 1;
+ path = xmalloc (size);
+ snprintf (path, size, "%s/%s", passwd->pw_dir, CONF_FILE);
+
+ *conf_file = path;
+}
+
+static void
+process_opt_attr (const char *p, const bool is_opt)
{
/* If attributes are added to this "list", also increase MAX_ATTRIBUTE_CHARS! */
const struct attr attrs[] = {
@@ -481,17 +604,19 @@ process_opt_attr (const char *p)
{ "concealed", 8, ATTR_CONCEALED },
};
unsigned int attr_types = 0;
+ const char *desc_type[2] = { "--attr switch", "attr conf option" };
+ const unsigned int DESC_TYPE = is_opt ? DESC_OPTION : DESC_CONF;
while (*p)
{
const char *s;
- if (!isalnum (*p))
- vfprintf_fail (formats[FMT_GENERIC], "--attr switch must be provided a string");
+ if (!isalnum ((unsigned char)*p))
+ vfprintf_fail ("%s must be provided a string", desc_type[DESC_TYPE]);
s = p;
- while (isalnum (*p))
+ while (isalnum ((unsigned char)*p))
p++;
if (*p != '\0' && *p != ',')
- vfprintf_fail (formats[FMT_GENERIC], "--attr switch must have strings separated by ,");
+ vfprintf_fail ("%s must have strings separated by ,", desc_type[DESC_TYPE]);
else
{
bool valid_attr = false;
@@ -501,7 +626,7 @@ process_opt_attr (const char *p)
const size_t name_len = strlen (attrs[i].name);
if ((size_t)(p - s) == name_len && strneq (s, attrs[i].name, name_len))
{
- write_attr (&attrs[i], &attr_types);
+ write_attr (&attrs[i], &attr_types, is_opt);
valid_attr = true;
break;
}
@@ -512,8 +637,8 @@ process_opt_attr (const char *p)
STACK_VAR (attr_invalid);
strncpy (attr_invalid, s, p - s);
attr_invalid[p - s] = '\0';
- vfprintf_fail ("--attr switch attribute '%s' is not valid", attr_invalid);
- RELEASE_VAR (attr_invalid); /* never reached */
+ vfprintf_fail ("%s attribute '%s' is not valid", desc_type[DESC_TYPE], attr_invalid);
+ RELEASE (attr_invalid); /* never reached */
}
}
if (*p)
@@ -522,19 +647,182 @@ process_opt_attr (const char *p)
}
static void
-write_attr (const struct attr *attr_i, unsigned int *attr_types)
+write_attr (const struct attr *attr_i, unsigned int *attr_types, const bool is_opt)
{
const unsigned int val = attr_i->val;
const enum attr_type attr_type = attr_i->type;
const char *attr_name = attr_i->name;
if (*attr_types & attr_type)
- vfprintf_fail ("--attr switch has attribute '%s' twice or more", attr_name);
+ vfprintf_fail ("%s has attribute '%s' twice or more",
+ is_opt ? "--attr switch" : "attr conf option", attr_name);
snprintf (attr + strlen (attr), 3, "%u;", val);
*attr_types |= attr_type;
}
static void
+process_opt_exclude_random (const char *s, const bool is_opt)
+{
+ bool valid = false;
+ unsigned int i;
+ RELEASE (exclude);
+ exclude = xstrdup (s);
+ STACK_VAR (exclude);
+ for (i = 1; i < tables[GENERIC].count - 1; i++) /* skip color none and default */
+ {
+ const struct color *entry = &tables[GENERIC].entries[i];
+ if (streq (exclude, entry->name))
+ {
+ valid = true;
+ break;
+ }
+ }
+ if (!valid)
+ vfprintf_fail ("%s must be provided a plain color",
+ is_opt ? "--exclude-random switch" : "exclude-random conf option");
+}
+
+static void
+init_opts_vars (void)
+{
+ if (opts_set & OPT_ATTR_SET)
+ {
+ attr[0] = '\0'; /* Clear attr string to discard values from the config file. */
+ process_opt_attr (opts_arg.attr, true);
+ }
+ if (opts_set & OPT_EXCLUDE_RANDOM_SET)
+ process_opt_exclude_random (opts_arg.exclude_random, true);
+ if (opts_set & OPT_OMIT_COLOR_EMPTY_SET)
+ omit_color_empty = true;
+
+ RELEASE (opts_arg.attr);
+ RELEASE (opts_arg.exclude_random);
+}
+
+#define IS_SPACE(c) ((c) == ' ' || (c) == '\t')
+
+static void
+parse_conf (const char *conf_file, struct conf *config)
+{
+ unsigned int cnt = 0;
+ char line[256 + 1];
+ FILE *conf;
+
+ conf = open_file (conf_file, "r");
+ STACK_FILE (conf);
+
+ while (fgets (line, sizeof (line), conf))
+ {
+ char *cfg, *val;
+ char *assign, *comment, *opt, *value;
+ char *p;
+
+ cnt++;
+ if ((p = strchr (line, '\r')) && *(p + 1) != '\n')
+ vfprintf_fail ("%s: CR ending of line %u is not supported, switch to CRLF/LF instead", conf_file, cnt);
+ if (strlen (line) > (sizeof (line) - 2))
+ vfprintf_fail ("%s: line %u exceeds maximum of %u characters", conf_file, cnt, (unsigned int)(sizeof (line) - 2));
+ if ((p = strpbrk (line, "\n\r")))
+ *p = '\0';
+/* NAME PARSING (start) */
+ p = line;
+ /* skip leading spaces and tabs for name */
+ while (IS_SPACE (*p))
+ p++;
+ /* skip line if a) string end, b) comment, [cd]) newline */
+ if (*p == '\0' || *p == '#' || *p == '\n' || *p == '\r')
+ continue;
+ opt = p;
+ if (!(assign = strchr (opt, '='))) /* check for = */
+ {
+ char *s;
+ if ((s = strpbrk (opt, "# ")))
+ *s = '\0';
+ vfprintf_fail (formats[FMT_CONF], conf_file, opt, "not followed by =");
+ }
+ p = assign;
+ /* skip trailing spaces and tabs for name */
+ while (IS_SPACE (*(p - 1)))
+ p--;
+ *p = '\0';
+/* NAME PARSING (end) */
+/* NAME VALIDATION (start) */
+ for (p = opt; *p; p++)
+ if (!isalnum ((unsigned char)*p) && *p != '-')
+ vfprintf_fail (formats[FMT_CONF], conf_file, opt, "cannot be made of non-option characters");
+/* NAME VALIDATION (end) */
+/* VALUE PARSING (start) */
+ p = assign + 1;
+ /* skip leading spaces and tabs for value */
+ while (IS_SPACE (*p))
+ p++;
+ /* skip line if comment */
+ if (*p == '#')
+ continue;
+ value = p;
+ if ((comment = strchr (p, '#')))
+ p = comment;
+ else
+ p += strlen (p);
+ /* skip trailing spaces and tabs for value */
+ while (IS_SPACE (*(p - 1)))
+ p--;
+ *p = '\0';
+/* VALUE PARSING (end) */
+
+ /* save option name */
+ cfg = xstrdup (opt);
+ STACK_VAR (cfg);
+ /* save option value (allow empty ones) */
+ val = strlen (value) ? xstrdup (value) : NULL;
+ STACK_VAR (val);
+
+ assign_conf (conf_file, config, cfg, val);
+ RELEASE (cfg);
+ }
+
+ RELEASE (conf);
+}
+
+#define ASSIGN_CONF(str,val) do { \
+ RELEASE (str); \
+ str = val; \
+} while (false)
+
+static void
+assign_conf (const char *conf_file, struct conf *config, const char *cfg, char *val)
+{
+ if (streq (cfg, "attr"))
+ ASSIGN_CONF (config->attr, val);
+ else if (streq (cfg, "color"))
+ ASSIGN_CONF (config->color, val);
+ else if (streq (cfg, "exclude-random"))
+ ASSIGN_CONF (config->exclude_random, val);
+ else if (streq (cfg, "omit-color-empty"))
+ ASSIGN_CONF (config->omit_color_empty, val);
+ else
+ vfprintf_fail (formats[FMT_CONF], conf_file, cfg, "not recognized");
+}
+
+static void
+init_conf_vars (const struct conf *config)
+{
+ if (config->attr)
+ process_opt_attr (config->attr, false);
+ if (config->exclude_random)
+ process_opt_exclude_random (config->exclude_random, false);
+ if (config->omit_color_empty)
+ {
+ if (streq (config->omit_color_empty, "yes"))
+ omit_color_empty = true;
+ else if (streq (config->omit_color_empty, "no"))
+ omit_color_empty = false;
+ else
+ vfprintf_fail (formats[FMT_GENERIC], "omit-color-empty conf option is not valid");
+ }
+}
+
+static void
print_hint (void)
{
fprintf (stderr, "Type `%s --help' for help screen.\n", program_name);
@@ -550,6 +838,7 @@ print_help (void)
};
const struct opt_data opts_data[] = {
{ "attr", NULL, "=ATTR1,ATTR2,..." },
+ { "config", "c", "=PATH" },
{ "exclude-random", NULL, "=COLOR" },
{ "help", "h", NULL },
{ "version", "V", NULL },
@@ -566,7 +855,7 @@ print_help (void)
const char *code = entry->code;
if (code)
printf ("\t\t{\033[%s#\033[0m} [%c%c]%s%*s%s\n",
- code, toupper (*name), *name, name + 1, 10 - (int)strlen (name), " ", name);
+ code, toupper ((unsigned char)*name), *name, name + 1, 10 - (int)strlen (name), " ", name);
else
printf ("\t\t{-} %s%*s%s\n", name, 13 - (int)strlen (name), " ", name);
}
@@ -589,9 +878,12 @@ print_help (void)
if (opt_data)
{
if (opt_data->short_opt)
- printf ("\t\t-%s, --%s\n", opt_data->short_opt, opt->name);
+ printf ("\t\t-%s, --%s", opt_data->short_opt, opt->name);
else
- printf ("\t\t --%s%s\n", opt->name, opt_data->arg);
+ printf ("\t\t --%s", opt->name);
+ if (opt_data->arg)
+ printf ("%s", opt_data->arg);
+ printf ("\n");
}
else
printf ("\t\t --%s\n", opt->name);
@@ -657,17 +949,32 @@ static void
cleanup (void)
{
if (stream && fileno (stream) != STDIN_FILENO)
- fclose (stream);
+ RELEASE (stream);
#if DEBUG
if (log)
- fclose (log);
+ RELEASE (log);
#endif
if (vars_list)
{
unsigned int i;
for (i = 0; i < stacked_vars; i++)
- free (vars_list[i]);
+ {
+ struct var_list *var = &vars_list[i];
+ switch (var->type)
+ {
+ case IS_GENERIC:
+ free (var->ptr);
+ break;
+ case IS_FILE:
+ fclose (var->ptr);
+ break;
+ case IS_UNUSED:
+ break;
+ default: /* never reached */
+ ABORT_TRACE ();
+ }
+ }
free_null (vars_list);
}
}
@@ -678,15 +985,25 @@ free_color_names (struct color_name **color_names)
unsigned int i;
for (i = 0; color_names[i]; i++)
{
- RELEASE_VAR (color_names[i]->name);
- RELEASE_VAR (color_names[i]->orig);
- RELEASE_VAR (color_names[i]);
+ RELEASE (color_names[i]->name);
+ RELEASE (color_names[i]->orig);
+ RELEASE (color_names[i]);
}
}
static void
-process_args (unsigned int arg_cnt, char **arg_strings, char *attr, const struct color **colors, const char **file, FILE **stream)
+free_conf (struct conf *config)
{
+ RELEASE (config->attr);
+ RELEASE (config->color);
+ RELEASE (config->exclude_random);
+ RELEASE (config->omit_color_empty);
+}
+
+static void
+process_args (unsigned int arg_cnt, char **arg_strings, char *attr, const struct color **colors, const char **file, FILE **stream, struct conf *config)
+{
+ bool has_hyphen, use_conf_color;
int ret;
char *p;
struct stat sb;
@@ -701,20 +1018,31 @@ process_args (unsigned int arg_cnt, char **arg_strings, char *attr, const struct
assert (color_string != NULL);
- if (streq (color_string, "-"))
+ has_hyphen = streq (color_string, "-");
+
+ if (has_hyphen)
{
if (file_string)
vfprintf_fail (formats[FMT_GENERIC], "hyphen cannot be used as color string");
- else
+ else if (!config->color)
vfprintf_fail (formats[FMT_GENERIC], "hyphen must be preceded by color string");
}
- ret = lstat (color_string, &sb);
+ if (!has_hyphen && (ret = lstat (color_string, &sb)) == 0) /* exists */
+ /* Ensure that we don't fail if there's a file with one or more
+ color names in its path. */
+ use_conf_color = skip_path_colors (color_string, file_string, &sb, !!config->color);
+ else if (has_hyphen)
+ use_conf_color = true;
+ else
+ use_conf_color = false;
- /* Ensure that we don't fail if there's a file with one or more
- color names in its path. */
- if (ret == 0) /* success */
- skip_path_colors (color_string, file_string, &sb);
+ /* Use color from config file. */
+ if (arg_cnt == 1 && use_conf_color)
+ {
+ file_string = color_string;
+ color_string = config->color;
+ }
if ((p = strchr (color_string, COLOR_SEP_CHAR)))
{
@@ -782,6 +1110,7 @@ process_file_arg (const char *file_string, const char **file, FILE **stream)
vfprintf_fail (formats[FMT_TYPE], file, "unrecognized type", get_file_type (sb.st_mode));
*stream = open_file (file, "r");
+ STACK_FILE (*stream);
}
*file = file_string;
}
@@ -795,8 +1124,8 @@ process_file_arg (const char *file_string, const char **file, FILE **stream)
assert (*file != NULL);
}
-static void
-skip_path_colors (const char *color_string, const char *file_string, const struct stat *sb)
+static bool
+skip_path_colors (const char *color_string, const char *file_string, const struct stat *sb, const bool has_conf)
{
bool have_file;
unsigned int c;
@@ -838,11 +1167,16 @@ skip_path_colors (const char *color_string, const char *file_string, const struc
else
{
if (VALID_FILE_TYPE (mode))
- vfprintf_fail (formats[FMT_QUOTE], get_file_type (mode), file_existing, "must be preceded by color string");
+ {
+ if (has_conf)
+ return true;
+ vfprintf_fail (formats[FMT_QUOTE], get_file_type (mode), file_existing, "must be preceded by color string");
+ }
else
vfprintf_fail (formats[FMT_QUOTE], get_file_type (mode), file_existing, "is not a valid file type");
}
}
+ return false;
}
static void
@@ -869,17 +1203,17 @@ gather_color_names (const char *color_string, char *attr, struct color_name **co
assert (p != NULL);
for (ch = color; *ch; ch++)
- if (!isalpha (*ch))
+ if (!isalpha ((unsigned char)*ch))
vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color, "cannot be made of non-alphabetic characters");
for (ch = color + 1; *ch; ch++)
- if (!islower (*ch))
+ if (!islower ((unsigned char)*ch))
vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color, "cannot be in mixed lower/upper case");
if (streq (color, "None"))
vfprintf_fail (formats[FMT_COLOR], tables[index].desc, color, "cannot be bold");
- if (isupper (*color))
+ if (isupper ((unsigned char)*color))
{
switch (index)
{
@@ -888,6 +1222,7 @@ gather_color_names (const char *color_string, char *attr, struct color_name **co
break;
case BACKGROUND:
vfprintf_fail (formats[FMT_COLOR], tables[BACKGROUND].desc, color, "cannot be bold");
+ break;
default: /* never reached */
ABORT_TRACE ();
}
@@ -900,13 +1235,13 @@ gather_color_names (const char *color_string, char *attr, struct color_name **co
STACK_VAR (color_names[index]->orig);
for (ch = color; *ch; ch++)
- *ch = tolower (*ch);
+ *ch = tolower ((unsigned char)*ch);
color_names[index]->name = xstrdup (color);
STACK_VAR (color_names[index]->name);
}
- RELEASE_VAR (str);
+ RELEASE (str);
}
static void
@@ -1266,10 +1601,10 @@ gather_esc_offsets (const char *p, const char **start, const char **end)
do {
check_values = false;
iter++;
- if (!isdigit (*p))
+ if (!isdigit ((unsigned char)*p))
break;
digit = p;
- while (isdigit (*p))
+ while (isdigit ((unsigned char)*p))
p++;
if (p - digit > 2)
break;
@@ -1302,7 +1637,7 @@ gather_esc_offsets (const char *p, const char **start, const char **end)
static bool
validate_esc_clean_all (const char **p)
{
- while (isdigit (**p) || **p == ';')
+ while (isdigit ((unsigned char)**p) || **p == ';')
(*p)++;
return (**p == 'm');
}
@@ -1449,6 +1784,20 @@ str_concat_wrap (const char *str1, const char *str2, const char *file, unsigned
return str;
}
+static char *
+expand_string (const char *str)
+{
+ char *s = NULL;
+ wordexp_t p;
+
+ wordexp (str, &p, 0);
+ if (p.we_wordc >= 1)
+ s = xstrdup (p.we_wordv[0]);
+ wordfree (&p);
+
+ return s;
+}
+
static bool
get_bytes_size (unsigned long bytes, struct bytes_size *bytes_size)
{
@@ -1496,7 +1845,7 @@ has_color_name (const char *str, const char *name)
assert (strlen (str) > 0);
assert (strlen (name) > 0);
- if (!(*str == *name || *str == toupper (*name)))
+ if (!(*str == *name || *str == toupper ((unsigned char)*name)))
return false;
else if (*(name + 1) != '\0'
&& !((p = strstr (str + 1, name + 1)) && p == str + 1))
@@ -1540,41 +1889,63 @@ vfprintf_fail (const char *fmt, ...)
}
static void
-stack_var (void ***list, unsigned int *stacked, unsigned int index, void *ptr)
+stack (struct var_list **list, unsigned int *stacked, unsigned int index, void *ptr, enum var_type type)
{
+ struct var_list *var;
/* nothing to stack */
if (ptr == NULL)
return;
if (!*list)
- *list = xmalloc (sizeof (void *));
+ *list = xmalloc (sizeof (struct var_list));
else
{
unsigned int i;
for (i = 0; i < *stacked; i++)
- if (!(*list)[i])
- {
- (*list)[i] = ptr;
- return; /* reused */
- }
- *list = xrealloc (*list, (*stacked + 1) * sizeof (void *));
+ {
+ var = &(*list)[i];
+ if (var->type == IS_UNUSED)
+ {
+ var->ptr = ptr;
+ var->type = type;
+ return; /* reused */
+ }
+ }
+ *list = xrealloc (*list, (*stacked + 1) * sizeof (struct var_list));
}
- (*list)[index] = ptr;
+ var = &(*list)[index];
+ var->ptr = ptr;
+ var->type = type;
(*stacked)++;
}
static void
-release_var (void **list, unsigned int stacked, void **ptr)
+release (struct var_list *list, unsigned int stacked, void **ptr)
{
unsigned int i;
/* nothing to release */
if (*ptr == NULL)
return;
for (i = 0; i < stacked; i++)
- if (list[i] == *ptr)
- {
- free (*ptr);
- *ptr = NULL;
- list[i] = NULL;
- return;
+ {
+ struct var_list *var = &list[i];
+ if (var->type != IS_UNUSED
+ && var->ptr == *ptr)
+ {
+ switch (var->type)
+ {
+ case IS_GENERIC:
+ free (*ptr);
+ break;
+ case IS_FILE:
+ fclose (*ptr);
+ break;
+ default: /* never reached */
+ ABORT_TRACE ();
+ }
+ *ptr = NULL;
+ var->ptr = NULL;
+ var->type = IS_UNUSED;
+ return;
}
+ }
}
diff --git a/debian/changelog b/debian/changelog
index acb96c2..37ca47b 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,21 @@
+colorize (0.65-2) UNRELEASED; urgency=medium
+
+ * debian/control: update Homepage link.
+
+ -- Steven Schubiger <stsc@refcnt.org> Tue, 23 Jun 2020 14:09:26 +0200
+
+colorize (0.65-1) unstable; urgency=low
+
+ * New upstream release.
+ * Declare compliance with Debian Policy 4.5.0.
+ (No changes needed.)
+ * debian/copyright: amend years.
+ * debian/control: use v12 mode of debhelper-compat package.
+ * debian/control: declare "Rules-Requires-Root: no".
+ * debian/copyright: update link to HTTPS, reported by duck(1).
+
+ -- Steven Schubiger <stsc@refcnt.org> Fri, 20 Mar 2020 22:42:04 +0100
+
colorize (0.64-1) unstable; urgency=low
* New upstream release.
diff --git a/debian/compat b/debian/compat
deleted file mode 100644
index ec63514..0000000
--- a/debian/compat
+++ /dev/null
@@ -1 +0,0 @@
-9
diff --git a/debian/control b/debian/control
index 1fd2752..00bfb3d 100644
--- a/debian/control
+++ b/debian/control
@@ -2,9 +2,10 @@ Source: colorize
Section: utils
Priority: optional
Maintainer: Steven Schubiger <stsc@refcnt.org>
-Build-Depends: debhelper (>= 9)
-Standards-Version: 4.1.4
-Homepage: http://cgit.refcnt.org/colorize.git/tree/README
+Build-Depends: debhelper-compat (= 12)
+Standards-Version: 4.5.0
+Rules-Requires-Root: no
+Homepage: http://cgit.refcnt.org/colorize.git/about/
Vcs-Git: git://refcnt.org/colorize.git
Vcs-Browser: http://cgit.refcnt.org/colorize.git/
diff --git a/debian/copyright b/debian/copyright
index ca3078c..e38c26b 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -3,11 +3,11 @@ Upstream-Name: colorize
Source: http://cgit.refcnt.org/colorize.git/
Files: *
-Copyright: 2011-2018 Steven Schubiger <stsc@refcnt.org>
+Copyright: 2011-2020 Steven Schubiger <stsc@refcnt.org>
License: GPL-3.0+
Files: debian/*
-Copyright: 2016-2018 Steven Schubiger <stsc@refcnt.org>
+Copyright: 2016-2020 Steven Schubiger <stsc@refcnt.org>
License: GPL-3.0+
License: GPL-3.0+
@@ -22,7 +22,7 @@ License: GPL-3.0+
GNU General Public License for more details.
.
You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
.
On Debian systems, the complete text of the GNU General
Public License version 3 can be found in "/usr/share/common-licenses/GPL-3".
diff --git a/doc/colorize.html b/doc/colorize.html
index c32c762..381ddb0 100644
--- a/doc/colorize.html
+++ b/doc/colorize.html
@@ -31,6 +31,7 @@ Usage: ./colorize (foreground) OR (foreground)/(background) OR --clean[-all] [-|
--attr=ATTR1,ATTR2,...
--clean
--clean-all
+ -c, --config=PATH
--exclude-random=COLOR
--omit-color-empty
-h, --help
@@ -102,7 +103,7 @@ permitted by applicable law.
</pre>
<pre>
<span style="color:yellow;font-weight:bold;">[sts@apollo ~/colorize]$</span> ./colorize --version
-colorize v0.63-5-gadd8a17 (compiled at Feb 1 2018, 17:05:52)
+colorize v0.64-17-ga3c5c67 (compiled at Sep 10 2019, 21:32:03)
Compiler flags: &quot;-ansi -pedantic &quot;
Linker flags: &quot;&quot;
Preprocessor flags: &quot;&quot;
diff --git a/readme.pl b/readme.pl
new file mode 100755
index 0000000..877364b
--- /dev/null
+++ b/readme.pl
@@ -0,0 +1,30 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+my $readme = 'README';
+my $readme_cgit = 'README.cgit';
+
+die "$0: $readme does not exist\n" unless -e $readme;
+
+open(my $fh, '<', $readme) or die "Cannot open $readme for reading: $!\n";
+my $text = do { local $/; <$fh> };
+close($fh);
+
+$text = do {
+ local $_ = $text;
+ s/</&lt;/g;
+ s/>/&gt;/g;
+ $_
+};
+
+print "Writing $readme_cgit\n";
+
+open($fh, '>', $readme_cgit) or die "Cannot open $readme_cgit for writing: $!\n";
+print {$fh} <<"CGIT";
+<pre>
+$text
+</pre>
+CGIT
+close($fh);
diff --git a/t/conf/attr_clear.t b/t/conf/attr_clear.t
new file mode 100755
index 0000000..7bbe5f9
--- /dev/null
+++ b/t/conf/attr_clear.t
@@ -0,0 +1,31 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use lib qw(lib);
+
+use Colorize::Common qw(:defaults $write_to_tmpfile);
+use File::Temp qw(tmpnam);
+use Test::More;
+
+my $tests = 1;
+
+plan tests => $tests;
+
+SKIP: {
+ my $program = tmpnam();
+ my $conf_file = tmpnam();
+
+ skip 'compiling failed (attr clear)', $tests unless system(qq($compiler -DTEST -DCONF_FILE_TEST=\"$conf_file\" -o $program $source)) == 0;
+
+ my $infile = $write_to_tmpfile->('foo');
+
+ open(my $fh, '>', $conf_file) or die "Cannot open `$conf_file' for writing: $!\n";
+ print {$fh} "attr=blink\n";
+ close($fh);
+
+ is(qx($program default --attr=bold $infile), "\e[1;39mfoo\e[0m", 'discard conf attr string');
+
+ unlink $program;
+ unlink $conf_file;
+}
diff --git a/t/conf/color.t b/t/conf/color.t
new file mode 100755
index 0000000..fc3ab71
--- /dev/null
+++ b/t/conf/color.t
@@ -0,0 +1,34 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use lib qw(lib);
+
+use Colorize::Common qw(:defaults $write_to_tmpfile);
+use File::Temp qw(tmpnam);
+use Test::More;
+
+my $tests = 4;
+
+plan tests => $tests;
+
+SKIP: {
+ my $program = tmpnam();
+ my $conf_file = tmpnam();
+
+ skip 'compiling failed (color)', $tests unless system(qq($compiler -DTEST -DCONF_FILE_TEST=\"$conf_file\" -o $program $source)) == 0;
+
+ my $infile = $write_to_tmpfile->('foo');
+
+ open(my $fh, '>', $conf_file) or die "Cannot open `$conf_file' for writing: $!\n";
+ print {$fh} "color=green\n";
+ close($fh);
+
+ is(qx(printf %s "foo" | $program -), "\e[32mfoo\e[0m", 'color from config (stdin)');
+ is(qx($program $infile), "\e[32mfoo\e[0m", 'color from config (file)');
+ is(qx(printf %s "foo" | $program none/none), 'foo', 'read from stdin');
+ is(qx($program none/none $infile), 'foo', 'read from file');
+
+ unlink $program;
+ unlink $conf_file;
+}
diff --git a/t/conf/endings.t b/t/conf/endings.t
new file mode 100755
index 0000000..35abe20
--- /dev/null
+++ b/t/conf/endings.t
@@ -0,0 +1,36 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use lib qw(lib);
+
+use Colorize::Common qw(:defaults $write_to_tmpfile);
+use File::Temp qw(tmpnam);
+use Test::More;
+
+my $tests = 1;
+
+my $conf = <<'EOT';
+attr=reverse
+color=red
+EOT
+
+plan tests => $tests;
+
+SKIP: {
+ my $program = tmpnam();
+ my $conf_file = tmpnam();
+
+ skip 'compiling failed (endings)', $tests unless system(qq($compiler -DTEST -DCONF_FILE_TEST=\"$conf_file\" -o $program $source)) == 0;
+
+ my $infile = $write_to_tmpfile->('foo');
+
+ open(my $fh, '>', $conf_file) or die "Cannot open `$conf_file' for writing: $!\n";
+ print {$fh} join "\015\012", split /\n/, $conf;
+ close($fh);
+
+ is(qx($program $infile), "\e[7;31mfoo\e[0m", 'CRLF line endings');
+
+ unlink $program;
+ unlink $conf_file;
+}
diff --git a/t/conf/fail.t b/t/conf/fail.t
new file mode 100755
index 0000000..d3b8852
--- /dev/null
+++ b/t/conf/fail.t
@@ -0,0 +1,59 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use lib qw(lib);
+use constant true => 1;
+use constant false => 0;
+
+use Colorize::Common qw(:defaults $write_to_tmpfile);
+use File::Temp qw(tmpnam);
+use IPC::Open3 qw(open3);
+use Symbol qw(gensym);
+use Test::More;
+
+my $tests = 7;
+
+my $run_program_fail = sub
+{
+ my ($program, $message, $infile) = @_;
+
+ my $err = gensym;
+
+ my $pid = open3(gensym, gensym, $err, $program, qw(default), $infile);
+ waitpid($pid, 0);
+
+ my $output = do { local $/; <$err> };
+
+ return ($? >> 8 == 1 && $output =~ /$message/) ? true : false;
+};
+
+plan tests => $tests;
+
+SKIP: {
+ my $program = tmpnam();
+ my $conf_file = tmpnam();
+
+ skip 'compiling failed (config failure exit)', $tests unless system(qq($compiler -DTEST -DCONF_FILE_TEST=\"$conf_file\" -o $program $source)) == 0;
+
+ my $infile = $write_to_tmpfile->('');
+
+ my @set = (
+ [ 'attr=:', 'attr conf option must be provided a string' ],
+ [ 'attr=bold:underscore', 'attr conf option must have strings separated by ,' ],
+ [ 'attr=b0ld', 'attr conf option attribute \'b0ld\' is not valid' ],
+ [ 'attr=b0ld,underscore', 'attr conf option attribute \'b0ld\' is not valid' ], # handle comma
+ [ 'attr=bold,bold', 'attr conf option has attribute \'bold\' twice or more' ],
+ [ 'exclude-random=random', 'exclude-random conf option must be provided a plain color' ],
+ [ 'omit-color-empty=unsure', 'omit-color-empty conf option is not valid' ],
+ );
+ foreach my $set (@set) {
+ open(my $fh, '>', $conf_file) or die "Cannot open `$conf_file' for writing: $!\n";
+ print {$fh} $set->[0], "\n";
+ close($fh);
+ ok($run_program_fail->($program, $set->[1], $infile), $set->[1]);
+ }
+
+ unlink $program;
+ unlink $conf_file;
+}
diff --git a/t/conf/param.t b/t/conf/param.t
new file mode 100755
index 0000000..520df2b
--- /dev/null
+++ b/t/conf/param.t
@@ -0,0 +1,51 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use lib qw(lib);
+
+use Colorize::Common qw(:defaults $write_to_tmpfile);
+use File::Temp qw(tmpnam);
+use Test::More;
+
+my $tests = 2;
+
+my $conf = <<'EOT';
+attr=bold
+color=blue
+omit-color-empty=yes
+EOT
+
+my $expected = <<"EOT";
+\e[1;34mfoo\e[0m
+
+\e[1;34mbar\e[0m
+
+\e[1;34mbaz\e[0m
+EOT
+
+plan tests => $tests;
+
+SKIP: {
+ my $program = tmpnam();
+ my $conf_file = tmpnam();
+ # -DTEST omitted on purpose
+ skip 'compiling failed (config param)', $tests unless system(qq($compiler -o $program $source)) == 0;
+
+ my $infile = $write_to_tmpfile->(<<'EOT');
+foo
+
+bar
+
+baz
+EOT
+ open(my $fh, '>', $conf_file) or die "Cannot open `$conf_file' for writing: $!\n";
+ print {$fh} $conf;
+ close($fh);
+
+ is(qx($program -c $conf_file $infile), $expected, 'short option');
+ is(qx($program --config=$conf_file $infile), $expected, 'long option');
+
+ unlink $program;
+ unlink $conf_file;
+}
diff --git a/t/conf/parse/fail.t b/t/conf/parse/fail.t
new file mode 100755
index 0000000..d2988e4
--- /dev/null
+++ b/t/conf/parse/fail.t
@@ -0,0 +1,64 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use lib qw(lib);
+use constant true => 1;
+use constant false => 0;
+
+use Colorize::Common qw(:defaults $write_to_tmpfile);
+use File::Temp qw(tmpnam);
+use IPC::Open3 qw(open3);
+use Symbol qw(gensym);
+use Test::More;
+
+my $tests = 9;
+
+my $run_program_fail = sub
+{
+ my ($program, $message, $infile) = @_;
+
+ my $err = gensym;
+
+ my $pid = open3(gensym, gensym, $err, $program, qw(default), $infile);
+ waitpid($pid, 0);
+
+ my $output = do { local $/; <$err> };
+
+ return ($? >> 8 == 1 && $output =~ /$message/) ? true : false;
+};
+
+plan tests => $tests;
+
+SKIP: {
+ my $program = tmpnam();
+ my $conf_file = tmpnam();
+
+ skip 'compiling failed (config parse failure)', $tests unless system(qq($compiler -DTEST -DCONF_FILE_TEST=\"$conf_file\" -o $program $source)) == 0;
+
+ my $infile = $write_to_tmpfile->('');
+
+ my $chars_exceed = 'x' x 256;
+
+ my @set = (
+ [ '[attr=bold', 'option \'\[attr\' cannot be made of non-option characters' ],
+ [ 'attr1=bold', 'option \'attr1\' not recognized' ],
+ [ 'color1=magenta', 'option \'color1\' not recognized' ],
+ [ 'exclude-random1=black', 'option \'exclude-random1\' not recognized' ],
+ [ 'omit-color-empty1=yes', 'option \'omit-color-empty1\' not recognized' ],
+ [ 'attr', 'option \'attr\' not followed by =' ],
+ [ 'attr#', 'option \'attr\' not followed by =' ],
+ [ 'attr bold', 'option \'attr\' not followed by =' ],
+ [ "color=$chars_exceed", 'line 1 exceeds maximum of' ],
+ );
+
+ foreach my $set (@set) {
+ open(my $fh, '>', $conf_file) or die "Cannot open `$conf_file' for writing: $!\n";
+ print {$fh} $set->[0], "\n";
+ close($fh);
+ ok($run_program_fail->($program, $set->[1], $infile), $set->[1]);
+ }
+
+ unlink $program;
+ unlink $conf_file;
+}
diff --git a/t/conf/parse/success.t b/t/conf/parse/success.t
new file mode 100755
index 0000000..fe2d46d
--- /dev/null
+++ b/t/conf/parse/success.t
@@ -0,0 +1,74 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use lib qw(lib);
+use constant true => 1;
+use constant false => 0;
+
+use Colorize::Common qw(:defaults $write_to_tmpfile);
+use File::Temp qw(tmpnam);
+use IPC::Open3 qw(open3);
+use Symbol qw(gensym);
+use Test::More;
+
+my $tests = 21;
+
+my $conf = <<'EOT';
+# comment
+ # comment
+ # comment
+attr=bold
+attr =bold
+attr= bold
+attr =bold
+attr= bold
+attr = bold
+ color=green
+color=green
+ color=green
+color=green
+exclude-random=black
+omit-color-empty=yes
+attr=bold # comment
+attr=bold # comment
+attr=
+color=
+exclude-random=
+omit-color-empty=
+EOT
+
+my $run_program_succeed = sub
+{
+ my ($program, $infile) = @_;
+
+ my $err = gensym;
+
+ my $pid = open3(gensym, gensym, $err, $program, qw(default), $infile);
+ waitpid($pid, 0);
+
+ my $output = do { local $/; <$err> };
+
+ return ($? >> 8 == 0 && $output eq '') ? true : false;
+};
+
+plan tests => $tests;
+
+SKIP: {
+ my $program = tmpnam();
+ my $conf_file = tmpnam();
+
+ skip 'compiling failed (config parse success)', $tests unless system(qq($compiler -DTEST -DCONF_FILE_TEST=\"$conf_file\" -o $program $source)) == 0;
+
+ my $infile = $write_to_tmpfile->('');
+
+ foreach my $line (split /\n/, $conf) {
+ open(my $fh, '>', $conf_file) or die "Cannot open `$conf_file' for writing: $!\n";
+ print {$fh} $line, "\n";
+ close($fh);
+ ok($run_program_succeed->($program, $infile), $line);
+ }
+
+ unlink $program;
+ unlink $conf_file;
+}
diff --git a/t/conf/use.t b/t/conf/use.t
new file mode 100755
index 0000000..0a695df
--- /dev/null
+++ b/t/conf/use.t
@@ -0,0 +1,57 @@
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+use lib qw(lib);
+
+use Colorize::Common qw(:defaults $write_to_tmpfile);
+use File::Temp qw(tmpnam);
+use Test::More;
+
+my $tests = 21;
+
+my $conf = <<'EOT';
+attr=underscore
+color=yellow # tested also in color.t
+omit-color-empty=yes
+EOT
+
+plan tests => $tests;
+
+SKIP: {
+ my $program = tmpnam();
+ my $conf_file = tmpnam();
+
+ skip 'compiling failed (use config)', $tests unless system(qq($compiler -DTEST -DCONF_FILE_TEST=\"$conf_file\" -o $program $source)) == 0;
+
+ my $infile1 = $write_to_tmpfile->(<<'EOT');
+foo
+
+bar
+
+baz
+EOT
+ open(my $fh, '>', $conf_file) or die "Cannot open `$conf_file' for writing: $!\n";
+ print {$fh} $conf;
+ close($fh);
+
+ is(qx($program $infile1), <<"EOT", 'use config');
+\e[4;33mfoo\e[0m
+
+\e[4;33mbar\e[0m
+
+\e[4;33mbaz\e[0m
+EOT
+ my $infile2 = $write_to_tmpfile->('foo');
+
+ open($fh, '>', $conf_file) or die "Cannot open `$conf_file' for writing: $!\n";
+ print {$fh} "exclude-random=black\n";
+ close($fh);
+
+ for (my $i = 1; $i <= 20; $i++) {
+ like(qx($program random $infile2), qr/^\e\[3[1-7]mfoo\e\[0m$/, 'use exclude-random');
+ }
+
+ unlink $program;
+ unlink $conf_file;
+}
diff --git a/t/merge.t b/t/merge.t
index 848c022..a234b12 100755
--- a/t/merge.t
+++ b/t/merge.t
@@ -103,7 +103,7 @@ my $compile = sub
my ($buf_size) = @_;
return true if exists $programs{$buf_size};
my $program = tmpnam();
- return false unless system("$compiler -DTEST_MERGE_PART_LINE -DBUF_SIZE=$buf_size -o $program $source") == 0;
+ return false unless system("$compiler -DTEST -DTEST_MERGE_PART_LINE -DBUF_SIZE=$buf_size -o $program $source") == 0;
$programs{$buf_size} = $program;
return true; # compiling succeeded
};
@@ -147,7 +147,7 @@ foreach my $test (@pushback) {
my $buf_size = $test->[1];
SKIP: {
my $program = tmpnam();
- skip $compiling_failed_msg, 1 unless system("$compiler -DBUF_SIZE=$buf_size -o $program $source") == 0;
+ skip $compiling_failed_msg, 1 unless system("$compiler -DTEST -DBUF_SIZE=$buf_size -o $program $source") == 0;
ok(qx(printf %s "$test->[0]" | $program --clean) eq $test->[0], 'pushback: ' . $test_name->($test->[0], $buf_size));
unlink $program;
}
diff --git a/test.pl b/test.pl
index 85e12b5..928c698 100755
--- a/test.pl
+++ b/test.pl
@@ -7,12 +7,13 @@ use constant true => 1;
use constant false => 0;
use Colorize::Common qw(:defaults $compiler_flags %BUF_SIZE $valgrind_command $write_to_tmpfile);
+use File::Find;
use File::Temp qw(tmpnam);
use Getopt::Long qw(:config no_auto_abbrev no_ignore_case);
use Test::Harness qw(runtests);
use Test::More;
-my $tests = 30;
+my $tests = 32;
my $valgrind_cmd = '';
{
@@ -28,8 +29,9 @@ my $valgrind_cmd = '';
}
{
- my @test_files = glob('t/*.t');
- eval { runtests(@test_files) } or warn $@;
+ my @test_files;
+ find ({ wanted => sub { push @test_files, $File::Find::name if /\.t$/ } }, 't');
+ eval { runtests(sort @test_files) } or warn $@;
}
plan tests => $tests;
@@ -96,6 +98,14 @@ SKIP: {
}
ok(qx(printf %s "\e[\e[33m" | $valgrind_cmd$program $switch) eq "\e[", "$type with invalid sequence");
+
+ {
+ my $ok = true;
+ foreach my $option (qw(--attr=bold --exclude-random=black --omit-color-empty)) {
+ $ok &= qx($valgrind_cmd$program $option $switch $infile1 2>&1 >/dev/null) =~ /switch has no meaning with/;
+ }
+ ok($ok, "$type strict options");
+ }
};
$check_clean->($_) foreach qw(clean clean-all);