about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorRory Bradford <roryrjb@gmail.com>2020-04-18 22:19:52 +0100
committerRory Bradford <roryrjb@gmail.com>2020-04-18 22:19:52 +0100
commit4120bca8530b87747b44efec0552b946b60d657e (patch)
tree48e8199baf2ee87d528b7154eef7f76913cf6a54
parent0017f896237b0a9b379ba102acfa8dafd5ea87a1 (diff)
downloadrf-4120bca8530b87747b44efec0552b946b60d657e.tar.gz
Add wholename option
Signed-off-by: Rory Bradford <roryrjb@gmail.com>
-rw-r--r--Makefile52
-rw-r--r--config.h11
-rw-r--r--ignore.c67
-rw-r--r--ignore.h10
-rw-r--r--rf.121
-rw-r--r--rf.c205
-rw-r--r--rfignore.520
7 files changed, 217 insertions, 169 deletions
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 <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#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 <roryrjb@gmail.com>.
+Copyright \(co 2019, 2020 Rory Bradford <roryrjb@gmail.com>.
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 <dirent.h>
 #include <errno.h>
 #include <fnmatch.h>
@@ -13,12 +16,17 @@
 #include <sys/types.h>
 #include <unistd.h>
 
-#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 <roryrjb@gmail.com>.