From 4120bca8530b87747b44efec0552b946b60d657e Mon Sep 17 00:00:00 2001 From: Rory Bradford Date: Sat, 18 Apr 2020 22:19:52 +0100 Subject: Add wholename option Signed-off-by: Rory Bradford --- Makefile | 52 +++++----------- config.h | 11 ---- ignore.c | 67 ++++++++++++++++++++ ignore.h | 10 +++ rf.1 | 21 +++---- rf.c | 205 +++++++++++++++++++++++++++++-------------------------------- rfignore.5 | 20 ++++++ 7 files changed, 217 insertions(+), 169 deletions(-) delete mode 100644 config.h create mode 100644 ignore.c create mode 100644 ignore.h create mode 100644 rfignore.5 diff --git a/Makefile b/Makefile index e66c045..f499b2d 100644 --- a/Makefile +++ b/Makefile @@ -1,52 +1,30 @@ .POSIX: -.SUFFIXES: .c .o BIN = rf -VERSION = 0.0.3 -OBJS = rf.o -MANPAGE = rf.1 +VERSION = 0.0.5 +OBJS = rf.o ignore.o +PREFIX = /usr/local CC = cc -DEPS = config.h -LIBS = -INC = -CFLAGS := ${CFLAGS} -CFLAGS += -std=c99 \ - -Wpedantic \ - -Wall \ - -Werror=format-security \ - -Werror=implicit-function-declaration \ - -O2 \ - -fstack-protector-strong \ - -fpie \ +CFLAGS = -std=c99 -O2 \ + -Wpedantic -Wall \ + -fstack-protector-strong -fpie \ -D_FORTIFY_SOURCE=2 \ - -D_DEFAULT_SOURCE \ -DVERSION='"$(VERSION)"' \ - -DNAME='"$(BIN)"' \ - $(INC) $(LIBS) -PREFIX ?= /usr/local + -DNAME='"$(BIN)"' build: $(BIN) $(BIN): $(OBJS) - $(CC) $(BIN).c $(CFLAGS) -o $(BIN) - -debug: $(OBJS) - $(CC) $(BIN).c $(CFLAGS) -g -o $(BIN) - -test: clean debug - valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --error-exitcode=1 ./$(BIN) ^$ - valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --error-exitcode=1 ./$(BIN) ^$$ - valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --error-exitcode=1 ./$(BIN) rf - -static: $(OBJS) - $(CC) $(BIN).c $(CFLAGS) -static -o $(BIN) - -%.o: %.c $(DEPS) - $(CC) -c -o $@ $< $(CFLAGS) + $(CC) $(CFLAGS) -o $(BIN) $(OBJS) install: build - install -Dm755 $(BIN) $(DESTDIR)$(PREFIX)/bin/ - install -Dm444 $(MANPAGE) $(DESTDIR)$(PREFIX)/man/man1/$(MANPAGE) + mkdir -p \ + $(DESTDIR)$(PREFIX)/bin \ + $(DESTDIR)$(PREFIX)/man/man1 \ + $(DESTDIR)$(PREFIX)/man/man5 + install -m755 $(BIN) $(DESTDIR)$(PREFIX)/bin/ + install -m444 rf.1 $(DESTDIR)$(PREFIX)/man/man1/ + install -m444 rfignore.5 $(DESTDIR)$(PREFIX)/man/man5/ clean: rm -vf $(BIN) *.o diff --git a/config.h b/config.h deleted file mode 100644 index ad2f869..0000000 --- a/config.h +++ /dev/null @@ -1,11 +0,0 @@ -char *ignored_dirs[] = {"*node_modules*", "*.mypy_cache*", "*.git*", "*.hg*", - "*__pycache__*", "*.eggs*"}; - -size_t ignored_dirs_size = sizeof(ignored_dirs) / sizeof(ignored_dirs[0]); - -char *ignored_extensions[] = {"*.pyc", "*.jpg", "*.jpeg", "*.png", "*.gif", - "*.zip", "*.tar", "*.xz", "*.gz", "*.gzip", "*.jar", "*.apk", "*.deb", - "*.o"}; - -size_t ignored_extensions_size = - sizeof(ignored_extensions) / sizeof(ignored_extensions[0]); diff --git a/ignore.c b/ignore.c new file mode 100644 index 0000000..b4cef61 --- /dev/null +++ b/ignore.c @@ -0,0 +1,67 @@ + +#define _POSIX_C_SOURCE 200809L +#define _XOPEN_SOURCE 600 + +#include +#include +#include +#include + +#include "ignore.h" + +static int total_size; + +struct ignores *init_ignores(char *path) { + total_size = IGNORE_SIZE; + struct ignores *ignores = calloc(sizeof(struct ignores), 1); + ignores->list = calloc(sizeof(char *), IGNORE_SIZE); + ignores->size = 0; + + int i = 0; + FILE *ignore = fopen(path, "r"); + char *line = NULL; + size_t llen = 0; + ssize_t r; + + if (ignore != NULL) { + while ((r = getline(&line, &llen, ignore)) != -1) { + char *l = calloc(sizeof(char *), strlen(line) - 1); + + for (int j = 0, k = 0; j < strlen(line); j++) { + char c = line[j]; + + if (isspace(c)) { + break; + } + + l[k++] = c; + } + + if (i + 1 > total_size) { + ignores->list = realloc( + ignores->list, sizeof(char *) * (total_size + IGNORE_SIZE)); + total_size += IGNORE_SIZE; + } + + ignores->list[i++] = l; + } + + free(line); + fclose(ignore); + } + + ignores->size = i; + + return ignores; +} + +void free_ignores(struct ignores *ignores) { + if (ignores->size > 0) { + for (int i = 0; i < ignores->size; i++) { + free(ignores->list[i]); + } + } + + free(ignores->list); + free(ignores); +} diff --git a/ignore.h b/ignore.h new file mode 100644 index 0000000..9ae5c6f --- /dev/null +++ b/ignore.h @@ -0,0 +1,10 @@ +#define IGNORE_SIZE 100 + +struct ignores { + char **list; + int size; +}; + +struct ignores *init_ignores(char *path); + +void free_ignores(struct ignores *ignores); diff --git a/rf.1 b/rf.1 index 8d04fd6..b4eab3c 100644 --- a/rf.1 +++ b/rf.1 @@ -5,13 +5,12 @@ rf \- a tiny and simple file finder .SH SYNOPSIS .SY rf -.OP \-lsvSU +.OP \-lsvw .OP pattern ... .SH DESCRIPTION -.B rf -will find files or directories based on one or more glob (or optionally -substring) patterns. +\fBrf\fR will find files or directories based on one or more glob (or optionally +substring) patterns, while respecting any ignore rules in \fBrfignore\fR files. .SH OPTIONS .TP @@ -27,13 +26,11 @@ Match using substrings instead of globs. Invert matches. . .TP -.BI \-S command -Run shell command over each file. A %s will be replaced with the relative -path to the current file. Cannot be used with any other options. -. -.TP -.B \-U -Unlink matched file. Cannot be used with any other options. +.B \-w +Match wholename, including path. + +.SH SEE ALSO +.BR rfignore (5) .SH COPYRIGHT -Copyright \(co 2020 Rory Bradford . +Copyright \(co 2019, 2020 Rory Bradford . diff --git a/rf.c b/rf.c index 6d6c2c1..5d412c3 100644 --- a/rf.c +++ b/rf.c @@ -1,6 +1,9 @@ #define _BSD_SOURCE #define _DEFAULT_SOURCE +#define _POSIX_C_SOURCE 200809L +#define _XOPEN_SOURCE 600 + #include #include #include @@ -13,12 +16,17 @@ #include #include -#include "config.h" +#include "ignore.h" extern char *__progname; +struct ignores *global_ignores; +struct ignores *config_ignores; +struct ignores *local_ignores; + struct switches { int substring; + int wholename; int invert; int limit; int count; @@ -31,7 +39,7 @@ static void usage(char *error) { fprintf(stderr, "Error: %s\n\n", error); } - fprintf(stderr, "usage: %s [-lsvSU] pattern ...\n", __progname); + fprintf(stderr, "usage: %s [-lsvw] pattern ...\n", __progname); } static int is_child(char *dirname) { @@ -42,19 +50,25 @@ static int is_child(char *dirname) { return 1; } -static int not_in_array(char **arr, char *dirname, size_t size) { - for (int i = 0; i < size; i++) { - if (fnmatch(arr[i], dirname, 0) == 0) { - return 0; +static int excluded(char *name) { + for (int i = 0; i < global_ignores->size; i++) { + int res = fnmatch(global_ignores->list[i], name, 0); + + if (res == 0) { + return 1; } } - return 1; -} + for (int i = 0; i < config_ignores->size; i++) { + int res = fnmatch(config_ignores->list[i], name, 0); + + if (res == 0) { + return 1; + } + } -static int excluded_extension(char *filename) { - for (int i = 0; i < ignored_extensions_size; i++) { - int res = fnmatch(ignored_extensions[i], filename, 0); + for (int i = 0; i < local_ignores->size; i++) { + int res = fnmatch(local_ignores->list[i], name, 0); if (res == 0) { return 1; @@ -65,48 +79,9 @@ static int excluded_extension(char *filename) { } static void handle_result( - char *path, struct switches *switches, struct dirent *entry) { - int i, j, k = 0; - char cmd[MAXPATHLEN]; - char full_path[MAXPATHLEN]; - - memset(cmd, '\0', MAXPATHLEN); - full_path[0] = '\0'; - - strcat(full_path, path); - strcat(full_path, "/"); - strcat(full_path, entry->d_name); - + char *full_path, struct switches *switches, struct dirent *entry) { if (is_child(entry->d_name) != 0) { - if (switches->unlink) { - int r = unlink(full_path); - - if (r < 0) { - perror("unlink"); - } else { - printf("removed '%s'\n", full_path); - } - } else if (switches->cmd != NULL) { - int l = strlen(switches->cmd); - - for (i = 0, j = 0; i < l; i++) { - char c = switches->cmd[i]; - - if (c == '%' && (i + 1 < l) && switches->cmd[i + 1] == 's') { - i++; - - for (k = 0; k < strlen(full_path); k++) { - cmd[j++] = full_path[k]; - } - } else { - cmd[j++] = c; - } - } - - system(cmd); - } else { - printf("%s\n", full_path); - } + printf("%s\n", full_path); } } @@ -116,66 +91,70 @@ static int recurse_find(char **patterns, int *pattern_count, char *dirname, DIR *dir; char path[MAXPATHLEN] = {'\0'}; - int break_early = 0; strcat(path, dirname); dir = opendir(path); - if (dir != NULL && not_in_array(ignored_dirs, dirname, ignored_dirs_size)) { + if (dir != NULL && !excluded(dirname)) { struct dirent *entry; while ((entry = readdir(dir)) != NULL) { - int matched = switches->invert ? 1 : 0; + int matched = 0; int p = 0; - switch (entry->d_type) { - case DT_DIR: - if (is_child(entry->d_name) && - not_in_array( - ignored_dirs, entry->d_name, ignored_dirs_size)) { - char child_path[MAXPATHLEN] = {'\0'}; - strcat(child_path, path); - strcat(child_path, "/"); - strcat(child_path, entry->d_name); + char full_path[MAXPATHLEN] = {'\0'}; + strcat(full_path, path); + strcat(full_path, "/"); + strcat(full_path, entry->d_name); + + struct stat entry_stat; + + if (stat(full_path, &entry_stat)) { + perror("stat"); + exit(EXIT_FAILURE); + } + + if (entry_stat.st_mode & S_IFDIR) { + if (is_child(entry->d_name) && !excluded(entry->d_name)) { if (recurse_find( - patterns, pattern_count, child_path, switches)) { - break_early = 1; - break; + patterns, pattern_count, full_path, switches)) { + closedir(dir); + return 1; }; } - - break; - case DT_REG: - if (excluded_extension(entry->d_name)) { + } else if (entry_stat.st_mode & S_IFREG) { + if (excluded(entry->d_name)) { matched = 0; - break; + continue; } for (; p < *pattern_count; p++) { char *pattern = patterns[p]; if (switches->substring) { - if (strstr(entry->d_name, pattern) != NULL) { - matched = switches->invert ? 0 : 1; + if (strstr( + switches->wholename ? full_path : entry->d_name, + pattern) != NULL) { + matched = 1; } } else { - if (fnmatch(pattern, entry->d_name, 0) == 0) { - matched = switches->invert ? 0 : 1; + if (fnmatch(pattern, + switches->wholename ? full_path : entry->d_name, + 0) == 0) { + matched = 1; } } } - break; - default: - break; - } /* switch */ - - if (break_early) { - closedir(dir); - return 1; + if (switches->invert) { + if (matched) + matched = 0; + else + matched = 1; + } } if (matched) { - handle_result(path, switches, entry); + handle_result(full_path, switches, entry); if (switches->limit > 0 && ++switches->count == switches->limit) { @@ -193,14 +172,12 @@ static int recurse_find(char **patterns, int *pattern_count, char *dirname, int main(int argc, char **argv) { struct switches switches = { - .substring = 0, .invert = 0, .limit = 0, .unlink = 0, .cmd = NULL}; + .substring = 0, .invert = 0, .wholename = 0, .limit = 0, .count = 0}; - int count = 0; /* used to count how matches we find */ int ch; - char *remainder; - while ((ch = getopt(argc, argv, "l:svS:U")) > -1) { + while ((ch = getopt(argc, argv, "l:svw")) > -1) { switch (ch) { case 'h': usage(NULL); @@ -214,14 +191,8 @@ int main(int argc, char **argv) { switches.invert = 1; break; - case 'S': - if (system(NULL) == 0) { - fprintf(stderr, "A shell isn't available."); - exit(EXIT_FAILURE); - } - - switches.cmd = optarg; - + case 'w': + switches.wholename = 1; break; case 'l': @@ -236,28 +207,41 @@ int main(int argc, char **argv) { } while (0); break; - - case 'U': - switches.unlink = 1; - break; } } - /* sanity check opts for conflicts */ - int printing = switches.invert + switches.limit; + char *home = getenv("HOME"); + char *xdg_config_home = getenv("XDG_CONFIG_HOME"); + char cwd[MAXPATHLEN]; - if (switches.unlink == 1 && printing > 0) { - fprintf(stderr, "Cannot use -U with any of -lsv.\n"); - exit(EXIT_FAILURE); - } else if (switches.cmd != NULL && printing > 0) { - fprintf(stderr, "Cannot use -S with any of -lsv.\n"); + if (getcwd(cwd, MAXPATHLEN) == NULL) { + perror("getcwd"); exit(EXIT_FAILURE); } + char global_ignore_path[(strlen(home) + strlen(".rfignore") + 1)]; + char config_ignore_path[xdg_config_home + ? (strlen(xdg_config_home) + strlen("ignore") + + 3) + : (strlen(cwd) + strlen(".rfignore") + 1)]; + char local_ignore_path[strlen(cwd) + strlen(".rfignore") + 1]; + sprintf(global_ignore_path, "%s/%s", home, ".rfignore"); + sprintf(local_ignore_path, "%s/%s", cwd, ".rfignore"); + + if (xdg_config_home) { + sprintf(config_ignore_path, "%s/rf/%s", xdg_config_home, "ignore"); + } else { + sprintf(config_ignore_path, "%s/.config/rf/%s", home, "ignore"); + } + + global_ignores = init_ignores(global_ignore_path); + config_ignores = init_ignores(config_ignore_path); + local_ignores = init_ignores(local_ignore_path); + if (optind < argc) { int i = 0; int pattern_count = argc - optind; - char **patterns = malloc(sizeof(char *) * pattern_count); + char **patterns = calloc(sizeof(char *), pattern_count); memset(patterns, '\0', optind); @@ -270,6 +254,9 @@ int main(int argc, char **argv) { }; free(patterns); + free_ignores(global_ignores); + free_ignores(config_ignores); + free_ignores(local_ignores); } else { usage(NULL); } diff --git a/rfignore.5 b/rfignore.5 new file mode 100644 index 0000000..ec2524e --- /dev/null +++ b/rfignore.5 @@ -0,0 +1,20 @@ +.TH rfignore 5 + +.SH NAME +rfignore \- ignore specified paths + +.SH SYNOPSIS +$XDG_CONFIG_HOME/rf/ignore, $HOME/.rfignore, .rfignore + +.SH DESCRIPTION +An \fBrfignore\fR file will be used by \fBrf\fR to ignore files and paths +when iterating through directories. Any matching patterns will not be displayed. +.sp +\fBrf\fR will look in all places specified in the synopsis for glob patterns +on each line and take them all into account. + +.SH SEE ALSO +.BR glob (7) + +.SH COPYRIGHT +Copyright \(co 2019, 2020 Rory Bradford . -- cgit 1.4.1-2-gfad0