diff options
author | Rory Bradford <roryrjb@gmail.com> | 2020-08-13 09:07:05 +0100 |
---|---|---|
committer | Rory Bradford <roryrjb@gmail.com> | 2020-08-13 09:07:05 +0100 |
commit | 4d872677b173d5380e5ab63b17e45d876cc3e864 (patch) | |
tree | e4c1dd9a40350e7a4f566539c5036ec15e865153 | |
parent | 71abce4e0725251ee9484e85fa58e325d1cd6407 (diff) | |
download | rf-4d872677b173d5380e5ab63b17e45d876cc3e864.tar.gz |
Add config parser
Signed-off-by: Rory Bradford <roryrjb@gmail.com>
-rw-r--r-- | Makefile | 7 | ||||
-rw-r--r-- | config.c | 82 | ||||
-rw-r--r-- | config.default | 29 | ||||
-rw-r--r-- | config.h | 14 | ||||
-rw-r--r-- | rf.1 | 8 | ||||
-rw-r--r-- | rf.c | 136 | ||||
-rw-r--r-- | rfconfig.5 | 41 |
7 files changed, 294 insertions, 23 deletions
diff --git a/Makefile b/Makefile index 14a80e9..42b5b79 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ BIN = rf VERSION = 0.0.5 -OBJS = rf.o ignore.o +OBJS = rf.o ignore.o config.o PREFIX = /usr/local CC = cc CFLAGS = -std=c99 -pedantic -O2 \ @@ -12,12 +12,12 @@ CFLAGS = -std=c99 -pedantic -O2 \ -DVERSION='"$(VERSION)"' \ -DNAME='"$(BIN)"' -build: $(BIN) +all: $(BIN) $(BIN): $(OBJS) $(CC) $(CFLAGS) -o $(BIN) $(OBJS) -install: build +install: $(BIN) mkdir -p \ $(DESTDIR)$(PREFIX)/bin \ $(DESTDIR)$(PREFIX)/man/man1 \ @@ -25,6 +25,7 @@ install: build install -m755 $(BIN) $(DESTDIR)$(PREFIX)/bin/ install -m444 rf.1 $(DESTDIR)$(PREFIX)/man/man1/ install -m444 rfignore.5 $(DESTDIR)$(PREFIX)/man/man5/ + install -m444 rfconfig.5 $(DESTDIR)$(PREFIX)/man/man5/ clean: rm -vf $(BIN) *.o diff --git a/config.c b/config.c new file mode 100644 index 0000000..52e1b5e --- /dev/null +++ b/config.c @@ -0,0 +1,82 @@ +#define _XOPEN_SOURCE 700 + +#include "config.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +char config_key[KV_STRING_LIMIT] = {'\0'}; +char config_value[KV_STRING_LIMIT] = {'\0'}; + +int config_get(size_t *llen, FILE *fp) { + int i = 0; + int k = 0; + int v = 0; + int len = 0; + int in_key = 1; + char *line = NULL; + + memset(config_key, '\0', KV_STRING_LIMIT); + memset(config_value, '\0', KV_STRING_LIMIT); + + if (getline(&line, llen, fp) < 0) { + free(line); + return -1; + } + + len = strlen(line); + + switch (line[0]) { + case '=': + case '#': + free(line); + return 0; + + default: + if (isspace(line[0])) { + free(line); + return 0; + } + } + + for (i = 0; i < len; i++) { + char c = line[i]; + + switch (c) { + case '=': + if (in_key == 1) { + in_key = 0; + break; + } + + /* fallthrough */ + default: + if (c != '\n') { + if (isspace(c)) { + /* if previous char was '=' then skip */ + if (i > 0 && line[i - 1] == '=') { + break; + } + + /* if next char is '=' then skip */ + if (i + 1 < len && line[i + 1] == '=') { + break; + } + } + + if (in_key) { + config_key[k++] = c; + } else { + config_value[v++] = c; + } + } + + break; + } + } + + free(line); + + return 0; +} diff --git a/config.default b/config.default new file mode 100644 index 0000000..b1f5a8e --- /dev/null +++ b/config.default @@ -0,0 +1,29 @@ +# vim: ft=config +# this is a default config file for rf +# the options as presented here should match the default behaviour +# of rf and should give you an overview of what is configurable + +# read symlinks (true or false) +symlinks = false + +# match whole path by default (true or false) +wholename = false + +# limit number of results (a positive integer) +limit = 0 + +# optionally specify an alternative wildcard character +# (a single ascii character) +# allows you to do something like: +# rf %.jpg +# with a wildcard set to '%' so you don't have to worry about +# shell expansion with the normal '*' character in certain situations +# +# wildcard = + +# by default if there are no matches just exit with 0 exit code +# uncomment 'unmatched error' below to exit with a non-zero exit code +# allowing you to do something like this: +# rf {pattern} || fallback +# +# unmatched error diff --git a/config.h b/config.h new file mode 100644 index 0000000..22eeef6 --- /dev/null +++ b/config.h @@ -0,0 +1,14 @@ +#ifndef RF_CONFIG_H +#define RF_CONFIG_H + +#include <ctype.h> +#include <stdio.h> + +#define KV_STRING_LIMIT 1024 + +extern char config_key[KV_STRING_LIMIT]; +extern char config_value[KV_STRING_LIMIT]; + +int config_get(size_t *len, FILE *fp); + +#endif diff --git a/rf.1 b/rf.1 index 9f58417..a600e69 100644 --- a/rf.1 +++ b/rf.1 @@ -6,6 +6,7 @@ rf \- a tiny and simple file finder .SH SYNOPSIS .SY rf .OP \-d directory +.OP \-c config .OP \-lsvw .OP pattern ... @@ -19,6 +20,10 @@ substring) patterns, while respecting any ignore rules in \fBrfignore\fR files. Search from specified directory, otherwise defaults to current working directory. . .TP +.BI \-c config +Override config file location. +. +.TP .BI \-l count Limit to specified matches count. . @@ -35,7 +40,8 @@ Invert matches. Match wholename, including path. .SH SEE ALSO -.BR rfignore (5) +.BR rfignore (5), +.BR rfconfig (5) .SH COPYRIGHT Copyright \(co 2019, 2020 Rory Bradford <roryrjb@gmail.com>. diff --git a/rf.c b/rf.c index 6fc4981..d2d18aa 100644 --- a/rf.c +++ b/rf.c @@ -15,6 +15,7 @@ #include <sys/types.h> #include <unistd.h> +#include "config.h" #include "ignore.h" extern char *__progname; @@ -24,22 +25,23 @@ struct ignores *config_ignores; struct ignores *local_ignores; struct switches { - int substring; - int wholename; + int count; int invert; int limit; - int count; - int unlink; - char *cmd; + int substring; + int wholename; }; +int read_links = 0; + static void usage(char *error) { if (error != NULL) { fprintf(stderr, "Error: %s\n\n", error); } - fprintf( - stderr, "usage: %s [-d directory] [-lsvw] pattern ...\n", __progname); + fprintf(stderr, + "usage: %s [-d directory] [-c config] [-lsvw] pattern ...\n", + __progname); } static int is_child(char *dirname) { @@ -50,7 +52,11 @@ static int is_child(char *dirname) { return 1; } -static int excluded(char *name) { +static int excluded(const char *name) { + if (!fnmatch("/proc/*", name, 0)) { + return 1; + } + if (global_ignores != NULL) { for (int i = 0; i < global_ignores->size; i++) { int res = fnmatch(global_ignores->list[i], name, 0); @@ -85,8 +91,8 @@ static int excluded(char *name) { } /* return 1 if breaking early (e.g. reaching limit) otherwise return 0 */ -static int recurse_find(char **patterns, int *pattern_count, char *dirname, - struct switches *switches) { +static int recurse_find(char **patterns, int *pattern_count, + const char *dirname, struct switches *switches) { DIR *dir; char path[MAXPATHLEN] = {'\0'}; @@ -111,8 +117,9 @@ static int recurse_find(char **patterns, int *pattern_count, char *dirname, struct stat entry_stat; - if (stat(full_path, &entry_stat)) { + if ((read_links ? stat : lstat)(full_path, &entry_stat)) { perror("stat"); + exit(EXIT_FAILURE); continue; } @@ -177,13 +184,37 @@ static int recurse_find(char **patterns, int *pattern_count, char *dirname, int main(int argc, char **argv) { struct switches switches = { - .substring = 0, .invert = 0, .wholename = 0, .limit = 0, .count = 0}; + .count = 0, + .invert = 0, + .limit = 0, + .substring = 0, + .wholename = 0, + }; int ch; char *remainder; + size_t len = 0; + const char *root = "."; + FILE *fp; + char cwd[MAXPATHLEN]; + int unmatched_error = 0; + char wildcard = 0; + char *override_config_file = NULL; - while ((ch = getopt(argc, argv, "l:svw")) > -1) { + if (getcwd(cwd, MAXPATHLEN) == NULL) { + perror("getcwd"); + exit(EXIT_FAILURE); + } + + char *xdg_config_home = getenv("XDG_CONFIG_HOME"); + char *home = getenv("HOME"); + + while ((ch = getopt(argc, argv, "c:d:l:svw")) > -1) { switch (ch) { + case 'c': + override_config_file = optarg; + break; + case 'd': root = optarg; break; @@ -212,6 +243,7 @@ int main(int argc, char **argv) { usage("Invalid limit."); exit(EXIT_FAILURE); } + switches.limit = limit; } while (0); @@ -219,12 +251,66 @@ int main(int argc, char **argv) { } } - char *home = getenv("HOME"); - char *xdg_config_home = getenv("XDG_CONFIG_HOME"); - char cwd[MAXPATHLEN]; + char config_file[xdg_config_home + ? (strlen(xdg_config_home) + strlen("ignore") + 3) + : (strlen(cwd) + strlen(".rfignore") + 1)]; - if (getcwd(cwd, MAXPATHLEN) == NULL) { - perror("getcwd"); + if (xdg_config_home) { + sprintf(config_file, "%s/rf/%s", xdg_config_home, "config"); + } else { + sprintf(config_file, "%s/.config/rf/%s", home, "config"); + } + + fp = fopen(override_config_file ? override_config_file : config_file, "r"); + + if (fp != NULL) { + while ((config_get(&len, fp)) != -1) { + if (strlen(config_key) && strlen(config_value)) { + if (strcmp(config_key, "symlinks") == 0) { + if (strcmp(config_value, "true") == 0) { + read_links = 1; + } else if (strcmp(config_value, "false") == 0) { + read_links = 0; + } else { + fprintf(stderr, + "'%s' is not a valid value for property: %s.\n", + config_value, config_key); + exit(EXIT_FAILURE); + } + } else if (strcmp(config_key, "wholename") == 0) { + if (strcmp(config_value, "true") == 0) { + switches.wholename = 1; + } else if (strcmp(config_value, "false") == 0) { + /* default */ + } else { + fprintf(stderr, + "'%s' is not a valid value for property: %s.\n", + config_value, config_key); + exit(EXIT_FAILURE); + } + } else if (strcmp(config_key, "limit") == 0) { + int limit = strtol(config_value, &remainder, 10); + + if (limit < 0) { + fprintf(stderr, "Warning: Invalid limit, ignoring."); + } else { + switches.limit = limit; + } + } else if (strcmp(config_key, "unmatched error") == 0) { + unmatched_error = 1; + } else if (strcmp(config_key, "wildcard") == 0) { + wildcard = config_value[0]; + } + } else if (strlen(config_key)) { + fprintf(stderr, + "Warning: Ignoring empty config property '%s'.\n", + config_key); + } + } + + fclose(fp); + } else if (override_config_file) { + perror("fopen"); exit(EXIT_FAILURE); } @@ -256,6 +342,14 @@ int main(int argc, char **argv) { while (optind < argc) { patterns[i++] = argv[optind++]; + + if (wildcard) { + for (size_t j = 0; j < strlen(patterns[i - 1]); j++) { + if (patterns[i - 1][j] == wildcard) { + patterns[i - 1][j] = '*'; + } + } + } } if (recurse_find(patterns, &pattern_count, root, &switches)) { @@ -266,9 +360,13 @@ int main(int argc, char **argv) { free_ignores(global_ignores); free_ignores(config_ignores); free_ignores(local_ignores); + + if (unmatched_error && switches.count == 0) { + exit(EXIT_FAILURE); + } } else { usage(NULL); } - return 0; + exit(EXIT_SUCCESS); } diff --git a/rfconfig.5 b/rfconfig.5 new file mode 100644 index 0000000..8ad7ab0 --- /dev/null +++ b/rfconfig.5 @@ -0,0 +1,41 @@ +.TH rfconfig 5 + +.SH NAME +rfconfig \- configure rf behaviour + +.SH SYNOPSIS +$XDG_CONFIG_HOME/rf/config + +.SH DESCRIPTION +An \fBrfconfig\fR file is used to override default behaviour and configure options. + +.SH FORMAT +The config file format is very simple. Comments are only available for a whole +line and start with #. Any lines that begin with a space are ignored. +Each valid config line consists of a key value pair with the key and value +separated by an = character and optionally a single space either side. Additional +spaces will become part of either the key and/or value. + +.SH OPTIONS +.TP +.B symlinks +Read symlinks (value: "true" or "false") +. +.TP +.B wholename +Match whole path (value "true" or "false") +. +.TP +.B limit +Limit amount of results (value: a positive integer) +. +.TP +.B wildcard +Define a custom wildcard character, used in place of * (value: a single ascii character) +. +.TP +.B unmatched error +Exit with non-zero exit code if there were no matches + +.SH COPYRIGHT +Copyright \(co 2019, 2020 Rory Bradford <roryrjb@gmail.com>. |