summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--bfs.c51
-rw-r--r--color.c281
-rw-r--r--color.h46
4 files changed, 364 insertions, 16 deletions
diff --git a/Makefile b/Makefile
index 77da3e5..df28a54 100644
--- a/Makefile
+++ b/Makefile
@@ -22,7 +22,7 @@ ALL_CPPFLAGS = $(LOCAL_CPPFLAGS) $(CPPFLAGS)
ALL_CFLAGS = $(ALL_CPPFLAGS) $(LOCAL_CFLAGS) $(CFLAGS) $(DEPFLAGS)
ALL_LDFLAGS = $(LDFLAGS)
-bfs: bfs.o bftw.o
+bfs: bfs.o bftw.o color.o
$(CC) $(ALL_CFLAGS) $(ALL_LDFLAGS) $^ -o $@
%.o: %.c
diff --git a/bfs.c b/bfs.c
index 6c0e07c..c637ba3 100644
--- a/bfs.c
+++ b/bfs.c
@@ -10,50 +10,61 @@
*********************************************************************/
#include "bftw.h"
+#include "color.h"
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <unistd.h>
typedef struct {
const char *path;
+ color_table *colors;
bool hidden;
} options;
static int callback(const char *fpath, const struct stat *sb, int typeflag, void *ptr) {
const options *opts = ptr;
- const char *filename = strrchr(fpath, '/');
- if (filename) {
- ++filename;
- } else {
- filename = fpath + strlen(fpath);
- }
-
- if (!opts->hidden && filename[0] == '.') {
- return BFTW_SKIP_SUBTREE;
+ if (!opts->hidden) {
+ const char *filename = strrchr(fpath, '/');
+ if (filename && filename[1] == '.') {
+ return BFTW_SKIP_SUBTREE;
+ }
}
- printf("%s\n", fpath);
+ pretty_print(opts->colors, fpath, sb);
return BFTW_CONTINUE;
}
int main(int argc, char* argv[]) {
+ int ret = EXIT_FAILURE;
+
options opts;
opts.path = NULL;
+ opts.colors = NULL;
opts.hidden = true;
+ bool color = isatty(STDOUT_FILENO);
+
for (int i = 1; i < argc; ++i) {
const char *arg = argv[i];
- if (strcmp(arg, "-hidden") == 0) {
+ if (strcmp(arg, "-color") == 0) {
+ color = true;
+ } else if (strcmp(arg, "-nocolor") == 0) {
+ color = false;
+ } else if (strcmp(arg, "-hidden") == 0) {
opts.hidden = true;
} else if (strcmp(arg, "-nohidden") == 0) {
opts.hidden = false;
+ } else if (arg[0] == '-') {
+ fprintf(stderr, "Unknown option `%s`.", arg);
+ goto done;
} else {
if (opts.path) {
fprintf(stderr, "Duplicate path `%s` on command line.", arg);
- return EXIT_FAILURE;
+ goto done;
}
opts.path = arg;
}
@@ -63,11 +74,21 @@ int main(int argc, char* argv[]) {
opts.path = ".";
}
+ int flags = 0;
+
+ if (color) {
+ flags |= BFTW_STAT;
+ opts.colors = parse_colors(getenv("LS_COLORS"));
+ }
+
// TODO: getrlimit(RLIMIT_NOFILE)
- if (bftw(opts.path, callback, 1024, 0, &opts) != 0) {
+ if (bftw(opts.path, callback, 1024, flags, &opts) != 0) {
perror("bftw()");
- return EXIT_FAILURE;
+ goto done;
}
- return EXIT_SUCCESS;
+ ret = EXIT_SUCCESS;
+done:
+ free_colors(opts.colors);
+ return ret;
}
diff --git a/color.c b/color.c
new file mode 100644
index 0000000..09dfc75
--- /dev/null
+++ b/color.c
@@ -0,0 +1,281 @@
+/*********************************************************************
+ * bfs *
+ * Copyright (C) 2015 Tavian Barnes <tavianator@tavianator.com> *
+ * *
+ * This program is free software. It comes without any warranty, to *
+ * the extent permitted by applicable law. You can redistribute it *
+ * and/or modify it under the terms of the Do What The Fuck You Want *
+ * To Public License, Version 2, as published by Sam Hocevar. See *
+ * the COPYING file or http://www.wtfpl.net/ for more details. *
+ *********************************************************************/
+
+#include "color.h"
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+struct color_table {
+ const char *reset;
+ const char *normal;
+ const char *file;
+ const char *dir;
+ const char *link;
+ const char *multi_hard;
+ const char *pipe;
+ const char *door;
+ const char *block;
+ const char *chardev;
+ const char *orphan;
+ const char *socket;
+ const char *setuid;
+ const char *setgid;
+ const char *capable;
+ const char *sticky_ow;
+ const char *ow;
+ const char *sticky;
+ const char *exec;
+};
+
+color_table *parse_colors(char *ls_colors) {
+ color_table *colors = malloc(sizeof(color_table));
+ if (!colors) {
+ goto done;
+ }
+
+ // Defaults generated by dircolors --print-database
+ colors->reset = "0";
+ colors->normal = NULL;
+ colors->file = NULL;
+ colors->dir = "01;34";
+ colors->link = "01;36";
+ colors->multi_hard = "00";
+ colors->pipe = "40;33";
+ colors->socket = "01;35";
+ colors->door = "01;35";
+ colors->block = "40;33;01";
+ colors->chardev = "40;33;01";
+ colors->orphan = "40;31;01";
+ colors->setuid = "37;41";
+ colors->setgid = "30;43";
+ colors->capable = "30;41";
+ colors->sticky_ow = "30;42";
+ colors->ow = "34;42";
+ colors->sticky = "37;44";
+ colors->exec = "01;32";
+
+ if (!ls_colors) {
+ goto done;
+ }
+
+ char *start = ls_colors;
+ char *end;
+ for (end = strchr(start, ':'); *start && end; start = end + 1, end = strchr(start, ':')) {
+ char *equals = strchr(start, '=');
+ if (!equals) {
+ continue;
+ }
+
+ *equals = '\0';
+ *end = '\0';
+
+ const char *key = start;
+ const char *value = equals + 1;
+
+ switch (key[0]) {
+ case 'b':
+ if (strcmp(key, "bd") == 0) {
+ colors->block = value;
+ }
+ break;
+
+ case 'c':
+ if (strcmp(key, "ca") == 0) {
+ colors->capable = value;
+ } else if (strcmp(key, "cd") == 0) {
+ colors->chardev = value;
+ }
+ break;
+
+ case 'd':
+ if (strcmp(key, "di") == 0) {
+ colors->dir = value;
+ } else if (strcmp(key, "do") == 0) {
+ colors->socket = value;
+ }
+ break;
+
+ case 'e':
+ if (strcmp(key, "ex") == 0) {
+ colors->exec = value;
+ }
+ break;
+
+ case 'f':
+ if (strcmp(key, "fi") == 0) {
+ colors->file = value;
+ }
+ break;
+
+ case 'l':
+ if (strcmp(key, "ln") == 0) {
+ colors->link = value;
+ }
+ break;
+
+ case 'n':
+ if (strcmp(key, "no") == 0) {
+ colors->normal = value;
+ }
+ break;
+
+ case 'o':
+ if (strcmp(key, "or") == 0) {
+ colors->orphan = value;
+ } else if (strcmp(key, "ow") == 0) {
+ colors->ow = value;
+ }
+ break;
+
+ case 'p':
+ if (strcmp(key, "pi") == 0) {
+ colors->pipe = value;
+ }
+ break;
+
+ case 'r':
+ if (strcmp(key, "rs") == 0) {
+ colors->reset = value;
+ }
+ break;
+
+ case 's':
+ if (strcmp(key, "sg") == 0) {
+ colors->setgid = value;
+ } else if (strcmp(key, "so") == 0) {
+ colors->socket = value;
+ } else if (strcmp(key, "st") == 0) {
+ colors->sticky = value;
+ } else if (strcmp(key, "su") == 0) {
+ colors->setuid = value;
+ }
+ break;
+
+ case 't':
+ if (strcmp(key, "tw") == 0) {
+ colors->sticky_ow = value;
+ }
+ break;
+ }
+
+ // TODO: Handle file extensions
+ }
+
+done:
+ return colors;
+}
+
+static const char *file_color(const color_table *colors, const char *filename, const struct stat *sb) {
+ if (!sb) {
+ return colors->orphan;
+ }
+
+ const char *color = NULL;
+
+ switch (sb->st_mode & S_IFMT) {
+ case S_IFREG:
+ if (sb->st_mode & S_ISUID) {
+ color = colors->setuid;
+ } else if (sb->st_mode & S_ISGID) {
+ color = colors->setgid;
+ } else if (sb->st_mode & 0111) {
+ color = colors->exec;
+ }
+
+ if (!color) {
+ color = colors->file;
+ }
+
+ break;
+
+ case S_IFDIR:
+ if (sb->st_mode & S_ISVTX) {
+ if (sb->st_mode & S_IWOTH) {
+ color = colors->sticky_ow;
+ } else {
+ color = colors->sticky;
+ }
+ } else if (sb->st_mode & S_IWOTH) {
+ color = colors->ow;
+ }
+
+ if (!color) {
+ color = colors->dir;
+ }
+
+ break;
+
+ case S_IFLNK:
+ color = colors->link;
+ break;
+ case S_IFBLK:
+ color = colors->block;
+ break;
+ case S_IFCHR:
+ color = colors->chardev;
+ break;
+ case S_IFIFO:
+ color = colors->pipe;
+ break;
+ case S_IFSOCK:
+ color = colors->socket;
+ break;
+ }
+
+ if (!color) {
+ color = colors->normal;
+ }
+
+ return color;
+}
+
+static void printf_color(const color_table *colors, const char *color, const char *format, ...) {
+ if (color) {
+ printf("\033[%sm", color);
+ }
+
+ va_list args;
+ va_start(args, format);
+ vprintf(format, args);
+ va_end(args);
+
+ if (color) {
+ printf("\033[%sm", colors->reset);
+ }
+}
+
+void pretty_print(const color_table *colors, const char *fpath, const struct stat *sb) {
+ if (!colors) {
+ printf("%s\n", fpath);
+ return;
+ }
+
+ const char *filename = strrchr(fpath, '/');
+ if (filename) {
+ ++filename;
+ } else {
+ filename = fpath + strlen(fpath);
+ }
+
+ int dirlen = filename - fpath;
+ printf_color(colors, colors->dir, "%.*s", dirlen, fpath);
+
+ const char *color = file_color(colors, filename, sb);
+ printf_color(colors, color, "%s", filename);
+ printf("\n");
+}
+
+void free_colors(color_table *colors) {
+ free(colors);
+}
diff --git a/color.h b/color.h
new file mode 100644
index 0000000..9bc5b5a
--- /dev/null
+++ b/color.h
@@ -0,0 +1,46 @@
+/*********************************************************************
+ * bfs *
+ * Copyright (C) 2015 Tavian Barnes <tavianator@tavianator.com> *
+ * *
+ * This program is free software. It comes without any warranty, to *
+ * the extent permitted by applicable law. You can redistribute it *
+ * and/or modify it under the terms of the Do What The Fuck You Want *
+ * To Public License, Version 2, as published by Sam Hocevar. See *
+ * the COPYING file or http://www.wtfpl.net/ for more details. *
+ *********************************************************************/
+
+#include <sys/stat.h>
+
+/**
+ * A lookup table for colors.
+ */
+typedef struct color_table color_table;
+
+/**
+ * Parse a color table.
+ *
+ * @param ls_color
+ * A color table in the LS_COLORS environment variable format.
+ * @return The parsed color table.
+ */
+color_table *parse_colors(char *ls_colors);
+
+/**
+ * Pretty-print a file path.
+ *
+ * @param colors
+ * The color table to use.
+ * @param fpath
+ * The file path to print.
+ * @param sb
+ * A stat() buffer for fpath.
+ */
+void pretty_print(const color_table *colors, const char *fpath, const struct stat *sb);
+
+/**
+ * Free a color table.
+ *
+ * @param colors
+ * The color table to free.
+ */
+void free_colors(color_table *colors);