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 <pclo...@gmail.com>
---
 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

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to