Subject: [PATCH 1/8] Import $LS_COLORS parsing code from coreutils

From: Nguyễn Thái Ngọc Duy <[email-masked]>
Date: Thu, 20 Mar 2014 17:15:44 +0700

This could could help highlight files in ls-files or status output, or
even diff --name-only (but that's questionable).

This code is from coreutils.git commit
7326d1f1a67edf21947ae98194f98c38b6e9e527 file src/ls.c. This is the
last GPL-2 commit before coreutils turns to GPL-3.

The code is reformatted to fit Git coding style, which is more than
just adding and removing spaces. For example, "bool" is replaced with
"int", or true/false replaced with 1/0, or the use of git's error()
instead of error(3). There are also two "#if 0" to make it build with
git-compat-util.h.

Signed-off-by: Nguyễn Thái Ngọc Duy <[email-masked]>
---
 Makefile          |   1 +
 ls_colors.c (new) | 477 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 ls_colors.h (new) |  20 +++
 3 files changed, 498 insertions(+)
 create mode 100644 ls_colors.c
 create mode 100644 ls_colors.h

diff --git a/Makefile b/Makefile
index f818eec..f6a6e14 100644
--- a/Makefile
+++ b/Makefile
@@ -819,6 +819,7 @@ LIB_OBJS += list-objects.o
 LIB_OBJS += ll-merge.o
 LIB_OBJS += lockfile.o
 LIB_OBJS += log-tree.o
+LIB_OBJS += ls_colors.o
 LIB_OBJS += mailmap.o
 LIB_OBJS += match-trees.o
 LIB_OBJS += merge.o
diff --git a/ls_colors.c b/ls_colors.c
new file mode 100644
index 0000000..6385446
--- /dev/null
+++ b/ls_colors.c
@@ -0,0 +1,477 @@
+#include "git-compat-util.h"
+#include "gettext.h"
+#include "ls_colors.h"
+
+#define STREQ(a, b) (strcmp(a, b) == 0)
+
+enum indicator_no {
+	C_LEFT, C_RIGHT, C_END, C_NORM, C_FILE, C_DIR, C_LINK, C_FIFO, C_SOCK,
+	C_BLK, C_CHR, C_MISSING, C_ORPHAN, C_EXEC, C_DOOR, C_SETUID, C_SETGID,
+	C_STICKY, C_OTHER_WRITABLE, C_STICKY_OTHER_WRITABLE
+};
+
+#define FILETYPE_INDICATORS				\
+  {							\
+    C_ORPHAN, C_FIFO, C_CHR, C_DIR, C_BLK, C_FILE,	\
+    C_LINK, C_SOCK, C_FILE, C_DIR			\
+  }
+
+struct bin_str {
+	size_t len;			/* Number of bytes */
+	const char *string;		/* Pointer to the same */
+};
+
+struct color_ext_type {
+	struct bin_str ext;		/* The extension we're looking for */
+	struct bin_str seq;		/* The sequence to output when we do */
+	struct color_ext_type *next;	/* Next in list */
+};
+
+static const char *const indicator_name[]= {
+	"lc", "rc", "ec", "no", "fi", "di", "ln", "pi", "so",
+	"bd", "cd", "mi", "or", "ex", "do", "su", "sg", "st",
+	"ow", "tw", NULL
+};
+
+#define LEN_STR_PAIR(s) sizeof(s) - 1, s
+static struct bin_str color_indicator[] = {
+	{ LEN_STR_PAIR("\033[") },	/* lc: Left of color sequence */
+	{ LEN_STR_PAIR("m") },		/* rc: Right of color sequence */
+	{ 0, NULL },			/* ec: End color (replaces lc+no+rc) */
+	{ LEN_STR_PAIR("0") },		/* no: Normal */
+	{ LEN_STR_PAIR("0") },		/* fi: File: default */
+	{ LEN_STR_PAIR("01;34") },	/* di: Directory: bright blue */
+	{ LEN_STR_PAIR("01;36") },	/* ln: Symlink: bright cyan */
+	{ LEN_STR_PAIR("33") },		/* pi: Pipe: yellow/brown */
+	{ LEN_STR_PAIR("01;35") },	/* so: Socket: bright magenta */
+	{ LEN_STR_PAIR("01;33") },	/* bd: Block device: bright yellow */
+	{ LEN_STR_PAIR("01;33") },	/* cd: Char device: bright yellow */
+	{ 0, NULL },			/* mi: Missing file: undefined */
+	{ 0, NULL },			/* or: Orphaned symlink: undefined */
+	{ LEN_STR_PAIR("01;32") },	/* ex: Executable: bright green */
+	{ LEN_STR_PAIR("01;35") },	/* do: Door: bright magenta */
+	{ LEN_STR_PAIR("37;41") },	/* su: setuid: white on red */
+	{ LEN_STR_PAIR("30;43") },	/* sg: setgid: black on yellow */
+	{ LEN_STR_PAIR("37;44") },	/* st: sticky: black on blue */
+	{ LEN_STR_PAIR("34;42") },	/* ow: other-writable: blue on green */
+	{ LEN_STR_PAIR("30;42") },	/* tw: ow w/ sticky: black on green */
+};
+
+static struct color_ext_type *color_ext_list = NULL;
+/* Buffer for color sequences */
+static char *color_buf;
+
+/*
+ * True means use colors to mark types.  Also define the different
+ * colors as well as the stuff for the LS_COLORS environment variable.
+ * The LS_COLORS variable is now in a termcap-like format.
+ */
+static int print_with_color;
+
+/*
+ * When true, in a color listing, color each symlink name according to the
+ * type of file it points to.  Otherwise, color them according to the `ln'
+ * directive in LS_COLORS.  Dangling (orphan) symlinks are treated specially,
+ * regardless.  This is set when `ln=target' appears in LS_COLORS.
+ */
+static int color_symlink_as_referent;
+
+/*
+ * Parse a string as part of the LS_COLORS variable; this may involve
+ * decoding all kinds of escape characters.  If equals_end is set an
+ * unescaped equal sign ends the string, otherwise only a : or \0
+ * does.  Set *OUTPUT_COUNT to the number of bytes output.  Return
+ * true if successful.
+ *
+ * The resulting string is *not* null-terminated, but may contain
+ * embedded nulls.
+ *
+ * Note that both dest and src are char **; on return they point to
+ * the first free byte after the array and the character that ended
+ * the input string, respectively.
+ */
+static int get_funky_string(char **dest, const char **src, int equals_end,
+			    size_t *output_count)
+{
+	char num;			/* For numerical codes */
+	size_t count;			/* Something to count with */
+	enum {
+		ST_GND, ST_BACKSLASH, ST_OCTAL, ST_HEX,
+		ST_CARET, ST_END, ST_ERROR
+	} state;
+	const char *p;
+	char *q;
+
+	p = *src;			/* We don't want to double-indirect */
+	q = *dest;			/* the whole darn time.  */
+
+	count = 0;			/* No characters counted in yet.  */
+	num = 0;
+
+	state = ST_GND;		/* Start in ground state.  */
+	while (state < ST_END) {
+		switch (state) {
+		case ST_GND:		/* Ground state (no escapes) */
+			switch (*p) {
+			case ':':
+			case '\0':
+				state = ST_END;	/* End of string */
+				break;
+			case '\\':
+				state = ST_BACKSLASH; /* Backslash scape sequence */
+				++p;
+				break;
+			case '^':
+				state = ST_CARET; /* Caret escape */
+				++p;
+				break;
+			case '=':
+				if (equals_end) {
+					state = ST_END; /* End */
+					break;
+				}
+				/* else fall through */
+			default:
+				*(q++) = *(p++);
+				++count;
+				break;
+			}
+			break;
+
+		case ST_BACKSLASH:	/* Backslash escaped character */
+			switch (*p) {
+			case '0':
+			case '1':
+			case '2':
+			case '3':
+			case '4':
+			case '5':
+			case '6':
+			case '7':
+				state = ST_OCTAL;	/* Octal sequence */
+				num = *p - '0';
+				break;
+			case 'x':
+			case 'X':
+				state = ST_HEX;	/* Hex sequence */
+				num = 0;
+				break;
+			case 'a':		/* Bell */
+				num = '\a';
+				break;
+			case 'b':		/* Backspace */
+				num = '\b';
+				break;
+			case 'e':		/* Escape */
+				num = 27;
+				break;
+			case 'f':		/* Form feed */
+				num = '\f';
+				break;
+			case 'n':		/* Newline */
+				num = '\n';
+				break;
+			case 'r':		/* Carriage return */
+				num = '\r';
+				break;
+			case 't':		/* Tab */
+				num = '\t';
+				break;
+			case 'v':		/* Vtab */
+				num = '\v';
+				break;
+			case '?':		/* Delete */
+				num = 127;
+				break;
+			case '_':		/* Space */
+				num = ' ';
+				break;
+			case '\0':		/* End of string */
+				state = ST_ERROR;	/* Error! */
+				break;
+			default:		/* Escaped character like \ ^ : = */
+				num = *p;
+				break;
+			}
+			if (state == ST_BACKSLASH) {
+				*(q++) = num;
+				++count;
+				state = ST_GND;
+			}
+			++p;
+			break;
+
+		case ST_OCTAL:		/* Octal sequence */
+			if (*p < '0' || *p > '7') {
+				*(q++) = num;
+				++count;
+				state = ST_GND;
+			} else
+				num = (num << 3) + (*(p++) - '0');
+			break;
+
+		case ST_HEX:		/* Hex sequence */
+			switch (*p) {
+			case '0':
+			case '1':
+			case '2':
+			case '3':
+			case '4':
+			case '5':
+			case '6':
+			case '7':
+			case '8':
+			case '9':
+				num = (num << 4) + (*(p++) - '0');
+				break;
+			case 'a':
+			case 'b':
+			case 'c':
+			case 'd':
+			case 'e':
+			case 'f':
+				num = (num << 4) + (*(p++) - 'a') + 10;
+				break;
+			case 'A':
+			case 'B':
+			case 'C':
+			case 'D':
+			case 'E':
+			case 'F':
+				num = (num << 4) + (*(p++) - 'A') + 10;
+				break;
+			default:
+				*(q++) = num;
+				++count;
+				state = ST_GND;
+				break;
+			}
+			break;
+
+		case ST_CARET:		/* Caret escape */
+			state = ST_GND;	/* Should be the next state... */
+			if (*p >= '@' && *p <= '~') {
+				*(q++) = *(p++) & 037;
+				++count;
+			} else if (*p == '?') {
+				*(q++) = 127;
+				++count;
+			} else
+				state = ST_ERROR;
+			break;
+
+		default:
+			abort();
+		}
+	}
+
+	*dest = q;
+	*src = p;
+	*output_count = count;
+
+	return state != ST_ERROR;
+}
+
+void parse_ls_color(void)
+{
+	const char *p;			/* Pointer to character being parsed */
+	char *buf;			/* color_buf buffer pointer */
+	int state;			/* State of parser */
+	int ind_no;			/* Indicator number */
+	char label[3];			/* Indicator label */
+	struct color_ext_type *ext;	/* Extension we are working on */
+
+	if ((p = getenv("LS_COLORS")) == NULL || *p == '\0')
+		return;
+
+	ext = NULL;
+	strcpy (label, "??");
+
+	/*
+	 * This is an overly conservative estimate, but any possible
+	 * LS_COLORS string will *not* generate a color_buf longer
+	 * than itself, so it is a safe way of allocating a buffer in
+	 * advance.
+	 */
+	buf = color_buf = xstrdup(p);
+
+	state = 1;
+	while (state > 0) {
+		switch (state) {
+		case 1:		/* First label character */
+			switch (*p) {
+			case ':':
+				++p;
+				break;
+
+			case '*':
+				/*
+				 * Allocate new extension block and add to head of
+				 * linked list (this way a later definition will
+				 * override an earlier one, which can be useful for
+				 * having terminal-specific defs override global).
+				 */
+
+				ext = xmalloc(sizeof *ext);
+				ext->next = color_ext_list;
+				color_ext_list = ext;
+
+				++p;
+				ext->ext.string = buf;
+
+				state = (get_funky_string(&buf, &p, 1, &ext->ext.len)
+					 ? 4 : -1);
+				break;
+
+			case '\0':
+				state = 0;	/* Done! */
+				break;
+
+			default:	/* Assume it is file type label */
+				label[0] = *(p++);
+				state = 2;
+				break;
+			}
+			break;
+
+		case 2:		/* Second label character */
+			if (*p) {
+				label[1] = *(p++);
+				state = 3;
+			} else
+				state = -1;	/* Error */
+			break;
+
+		case 3:		/* Equal sign after indicator label */
+			state = -1;	/* Assume failure...  */
+			if (*(p++) == '=') { /* It *should* be...  */
+				for (ind_no = 0; indicator_name[ind_no] != NULL; ++ind_no) {
+					if (STREQ (label, indicator_name[ind_no])) {
+						color_indicator[ind_no].string = buf;
+						state = (get_funky_string(&buf, &p, 0,
+									  &color_indicator[ind_no].len)
+							 ? 1 : -1);
+						break;
+					}
+				}
+				if (state == -1)
+					error(_("unrecognized prefix: %s"), label);
+			}
+			break;
+
+		case 4:		/* Equal sign after *.ext */
+			if (*(p++) == '=') {
+				ext->seq.string = buf;
+				state = (get_funky_string(&buf, &p, 0, &ext->seq.len)
+					 ? 1 : -1);
+			} else
+				state = -1;
+			break;
+		}
+	}
+
+	if (state < 0) {
+		struct color_ext_type *e;
+		struct color_ext_type *e2;
+
+		error(_("unparsable value for LS_COLORS environment variable"));
+		free(color_buf);
+		for (e = color_ext_list; e != NULL; /* empty */) {
+			e2 = e;
+			e = e->next;
+			free(e2);
+		}
+		print_with_color = 0;
+	}
+
+	if (color_indicator[C_LINK].len == 6 &&
+	    !strncmp(color_indicator[C_LINK].string, "target", 6))
+		color_symlink_as_referent = 1;
+}
+
+/* Output a color indicator (which may contain nulls).  */
+static void put_indicator(const struct bin_str *ind)
+{
+	size_t i;
+	const char *p;
+
+	p = ind->string;
+
+	for (i = ind->len; i != 0; --i)
+		putchar(*(p++));
+}
+
+void print_color_indicator(const char *name, mode_t mode, int linkok,
+			   int stat_ok, enum filetype filetype)
+{
+	int type;
+	struct color_ext_type *ext;	/* Color extension */
+	size_t len;			/* Length of name */
+
+	/* Is this a nonexistent file?  If so, linkok == -1.  */
+
+	if (linkok == -1 && color_indicator[C_MISSING].string != NULL)
+		type = C_MISSING;
+	else if (!stat_ok) {
+		static enum indicator_no filetype_indicator[] = FILETYPE_INDICATORS;
+		type = filetype_indicator[filetype];
+	} else {
+		if (S_ISREG(mode)) {
+			type = C_FILE;
+			if ((mode & S_ISUID) != 0)
+				type = C_SETUID;
+			else if ((mode & S_ISGID) != 0)
+				type = C_SETGID;
+#if 0
+			else if ((mode & S_IXUGO) != 0)
+				type = C_EXEC;
+#endif
+		} else if (S_ISDIR(mode)) {
+			if ((mode & S_ISVTX) && (mode & S_IWOTH))
+				type = C_STICKY_OTHER_WRITABLE;
+			else if ((mode & S_IWOTH) != 0)
+				type = C_OTHER_WRITABLE;
+			else if ((mode & S_ISVTX) != 0)
+				type = C_STICKY;
+			else
+				type = C_DIR;
+		} else if (S_ISLNK(mode))
+			type = ((!linkok && color_indicator[C_ORPHAN].string)
+				? C_ORPHAN : C_LINK);
+		else if (S_ISFIFO(mode))
+			type = C_FIFO;
+		else if (S_ISSOCK(mode))
+			type = C_SOCK;
+		else if (S_ISBLK(mode))
+			type = C_BLK;
+		else if (S_ISCHR(mode))
+			type = C_CHR;
+#if 0
+		else if (S_ISDOOR(mode))
+			type = C_DOOR;
+#endif
+		else {
+			/* Classify a file of some other type as C_ORPHAN.  */
+			type = C_ORPHAN;
+		}
+	}
+
+	/* Check the file's suffix only if still classified as C_FILE.  */
+	ext = NULL;
+	if (type == C_FILE) {
+		/* Test if NAME has a recognized suffix.  */
+
+		len = strlen(name);
+		name += len;		/* Pointer to final \0.  */
+		for (ext = color_ext_list; ext != NULL; ext = ext->next) {
+			if (ext->ext.len <= len
+			    && strncmp(name - ext->ext.len, ext->ext.string,
+				       ext->ext.len) == 0)
+				break;
+		}
+	}
+
+	put_indicator(&color_indicator[C_LEFT]);
+	put_indicator(ext ? &(ext->seq) : &color_indicator[type]);
+	put_indicator(&color_indicator[C_RIGHT]);
+}
diff --git a/ls_colors.h b/ls_colors.h
new file mode 100644
index 0000000..3201be6
--- /dev/null
+++ b/ls_colors.h
@@ -0,0 +1,20 @@
+#ifndef LS_COLORS_H
+#define LS_COLORS_H
+
+enum filetype {
+	unknown,
+	fifo,
+	chardev,
+	directory,
+	blockdev,
+	normal,
+	symbolic_link,
+	sock,
+	whiteout,
+	arg_directory
+};
+
+void parse_ls_color(void);
+void print_color_indicator(const char *name, mode_t mode, int linkok,
+			   int stat_ok, enum filetype filetype);
+#endif
-- 
1.9.0.40.gaa8c3ea