From 22291402d329a49ab8ab4a272344cab902b2ab3b Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 20 Jun 2015 00:04:59 -0400 Subject: Add initial support for colorized output. --- Makefile | 2 +- bfs.c | 51 ++++++++---- color.c | 281 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ color.h | 46 +++++++++++ 4 files changed, 364 insertions(+), 16 deletions(-) create mode 100644 color.c create mode 100644 color.h 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 #include #include #include +#include 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 * + * * + * 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 +#include +#include +#include +#include + +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 * + * * + * 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 + +/** + * 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); -- cgit v1.2.3