summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Makefile9
-rw-r--r--config.c136
-rw-r--r--litterbox.151
-rw-r--r--litterbox.c26
4 files changed, 206 insertions, 16 deletions
diff --git a/Makefile b/Makefile
index eae91ce..309dd8f 100644
--- a/Makefile
+++ b/Makefile
@@ -13,20 +13,25 @@ MANS = ${BINS:=.1}
 
 -include config.mk
 
+OBJS_litterbox = litterbox.o config.o
+
 dev: tags all
 
 all: ${BINS}
 
-${BINS:=.o}: database.h
+litterbox: ${OBJS_litterbox}
+	${CC} ${LDFLAGS} ${OBJS_$@} ${LDLIBS} ${LDLIBS_$@} -o $@
 
 .o:
 	${CC} ${LDFLAGS} $< ${LDLIBS} ${LDLIBS_$@} -o $@
 
+${BINS:=.o}: database.h
+
 tags: *.c *.h
 	ctags -w *.c *.h
 
 clean:
-	rm -f tags ${BINS} ${BINS:=.o}
+	rm -f tags ${BINS} ${OBJS_litterbox} ${BINS:=.o}
 
 install: ${BINS} ${MANS}
 	install -d ${PREFIX}/bin ${MANDIR}/man1
diff --git a/config.c b/config.c
new file mode 100644
index 0000000..ce1bd25
--- /dev/null
+++ b/config.c
@@ -0,0 +1,136 @@
+/* Copyright (C) 2019  C. McEnroe <june@causal.agency>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <err.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define WS "\t "
+
+static const char *path;
+static FILE *file;
+static size_t num;
+static char *line;
+static size_t cap;
+
+static int clean(int opt) {
+	if (file) fclose(file);
+	free(line);
+	line = NULL;
+	cap = 0;
+	return opt;
+}
+
+int getopt_config(
+	int argc, char *const *argv,
+	const char *optstring, const struct option *longopts, int *longindex
+) {
+	static int opt;
+	if (opt >= 0) {
+		opt = getopt_long(argc, argv, optstring, longopts, longindex);
+	}
+	if (opt >= 0) return opt;
+
+	for (;;) {
+		if (!file) {
+			if (optind < argc) {
+				num = 0;
+				path = argv[optind++];
+				file = fopen(path, "r");
+				if (!file) {
+					warn("%s", path);
+					return clean('?');
+				}
+			} else {
+				return clean(-1);
+			}
+		}
+
+		for (;;) {
+			ssize_t llen = getline(&line, &cap, file);
+			if (ferror(file)) {
+				warn("%s", path);
+				return clean('?');
+			}
+			if (llen <= 0) break;
+			if (line[llen - 1] == '\n') line[llen - 1] = '\0';
+			num++;
+
+			char *name = line + strspn(line, WS);
+			size_t len = strcspn(name, WS "=");
+			if (!name[0] || name[0] == '#') continue;
+
+			const struct option *option;
+			for (option = longopts; option->name; ++option) {
+				if (strlen(option->name) != len) continue;
+				if (!strncmp(option->name, name, len)) break;
+			}
+			if (!option->name) {
+				warnx(
+					"%s:%zu: unrecognized option `%.*s'",
+					path, num, (int)len, name
+				);
+				return clean('?');
+			}
+
+			char *equal = &name[len] + strspn(&name[len], WS);
+			if (*equal && *equal != '=') {
+				warnx(
+					"%s:%zu: option `%s' missing equals sign",
+					path, num, option->name
+				);
+				return clean('?');
+			}
+			if (option->has_arg == no_argument && *equal) {
+				warnx(
+					"%s:%zu: option `%s' doesn't allow an argument",
+					path, num, option->name
+				);
+				return clean('?');
+			}
+			if (option->has_arg == required_argument && !*equal) {
+				warnx(
+					"%s:%zu: option `%s' requires an argument",
+					path, num, option->name
+				);
+				return clean(':');
+			}
+
+			optarg = NULL;
+			if (*equal) {
+				char *arg = &equal[1] + strspn(&equal[1], WS);
+				optarg = strdup(arg);
+				if (!optarg) {
+					warn("getopt_config");
+					return clean('?');
+				}
+			}
+
+			if (longindex) *longindex = option - longopts;
+			if (option->flag) {
+				*option->flag = option->val;
+				return 0;
+			} else {
+				return option->val;
+			}
+		}
+
+		fclose(file);
+		file = NULL;
+	}
+}
diff --git a/litterbox.1 b/litterbox.1
index 6501850..d8fc311 100644
--- a/litterbox.1
+++ b/litterbox.1
@@ -18,6 +18,7 @@
 .Op Fl p Ar port
 .Op Fl u Ar user
 .Op Fl w Ar pass
+.Op Ar config ...
 .
 .Nm
 .Op Fl d Ar path
@@ -43,16 +44,26 @@ an existing database can be migrated with
 .Fl m .
 .
 .Pp
+Options can be loaded from
+files listed on the command line.
+Each option is placed on a line,
+and lines beginning with
+.Ql #
+are ignored.
+The options are listed below
+following their corresponding flags.
+.
+.Pp
 The arguments are as follows:
 .
 .Bl -tag -width "-h host"
-.It Fl N Ar network
+.It Fl N Ar name , Cm network = Ar name
 Set the network name to be used
 if the server does not send
 .Sy RPL_ISUPPORT NETWORK .
 The default is the server hostname.
 .
-.It Fl Q
+.It Fl Q , Cm public-query
 Enable the public search query interface.
 This allows anyone to perform searches
 in private messages to
@@ -71,24 +82,24 @@ The searchable columns are
 For search query syntax, see
 .Aq Lk https://www.sqlite.org/fts5.html#full_text_query_syntax .
 .
-.It Fl d Ar path
+.It Fl d Ar path , Cm database = Ar path
 Set the path to the database file.
 The possible default paths
 are documented in
 .Sx FILES .
 .
-.It Fl h Ar host
+.It Fl h Ar host , Cm host = Ar host
 Connect to
 .Ar host .
 .
 .It Fl i
 Initialize the database.
 .
-.It Fl j Ar join
+.It Fl j Ar chan , Cm join = Ar chan
 Join the comma-separated list of channels
-.Ar join .
+.Ar chan .
 .
-.It Fl l Ar limit
+.It Fl l Ar limit , Cm limit = Ar limit
 Limit the number of results
 in the search query interface
 enabled by
@@ -100,18 +111,18 @@ The default limit is 10.
 .It Fl m
 Migrate the database to the latest format.
 .
-.It Fl n Ar nick
+.It Fl n Ar nick , Cm nick = Ar nick
 Set the nickname to
 .Ar nick .
 The default nickname is
 .Dq litterbox .
 .
-.It Fl p Ar port
+.It Fl p Ar port , Cm port = Ar port
 Connect to
 .Ar port .
 The default port is 6697.
 .
-.It Fl q
+.It Fl q , Cm private-query
 Enable the private search query interface.
 This allows search queries in private messages to
 .Nm
@@ -136,7 +147,7 @@ The searchable columns are
 For search query syntax, see
 .Aq Lk https://www.sqlite.org/fts5.html#full_text_query_syntax .
 .
-.It Fl u Ar user
+.It Fl u Ar user , Cm user = Ar user
 Set the username to
 .Ar user .
 If connecting to
@@ -146,12 +157,12 @@ the username should begin with hyphen
 to prevent affecting its away status.
 The default username is the same as the nickname.
 .
-.It Fl v
+.It Fl v , Cm verbose
 Write sent and received IRC messages
 as well as SQL INSERT statements
 to standard error.
 .
-.It Fl w Ar pass
+.It Fl w Ar pass , Cm pass = Ar pass
 Log in with the server password
 .Ar pass .
 .El
@@ -171,6 +182,20 @@ usually
 The most likely default path to the database file.
 .El
 .
+.Sh EXAMPLES
+Configuration on the command line:
+.Bd -literal -offset indent
+litterbox -Q -h irc.example.org -j '#example'
+.Ed
+.
+.Pp
+Configuration in a file:
+.Bd -literal -offset indent
+host = irc.example.org
+join = #example
+public-query
+.Ed
+.
 .Sh SEE ALSO
 .Xr scoop 1 ,
 .Xr unscoop 1
diff --git a/litterbox.c b/litterbox.c
index 4c97794..3fac55a 100644
--- a/litterbox.c
+++ b/litterbox.c
@@ -16,6 +16,7 @@
 
 #include <assert.h>
 #include <err.h>
+#include <getopt.h>
 #include <signal.h>
 #include <stdarg.h>
 #include <stdio.h>
@@ -27,6 +28,11 @@
 
 #include "database.h"
 
+int getopt_config(
+	int argc, char *const *argv, const char *optstring,
+	const struct option *longopts, int *longindex
+);
+
 static struct tls *client;
 
 static void clientWrite(const char *ptr, size_t len) {
@@ -624,8 +630,26 @@ int main(int argc, char *argv[]) {
 	const char *user = NULL;
 	const char *pass = NULL;
 
+	const char *Opts = "!N:Qd:h:ij:l:mn:p:qu:vw:";
+	const struct option LongOpts[] = {
+		{ "insecure", no_argument, NULL, '!' },
+		{ "network", required_argument, NULL, 'N' },
+		{ "public-query", no_argument, NULL, 'Q' },
+		{ "database", required_argument, NULL, 'd' },
+		{ "host", required_argument, NULL, 'h' },
+		{ "join", required_argument, NULL, 'j' },
+		{ "limit", required_argument, NULL, 'l' },
+		{ "nick", required_argument, NULL, 'n' },
+		{ "port", required_argument, NULL, 'p' },
+		{ "private-query", no_argument, NULL, 'q' },
+		{ "user", required_argument, NULL, 'u' },
+		{ "verbose", no_argument, NULL, 'v' },
+		{ "pass", required_argument, NULL, 'w' },
+		{0},
+	};
+
 	int opt;
-	while (0 < (opt = getopt(argc, argv, "!N:Qd:h:ij:l:mn:p:qu:vw:"))) {
+	while (0 < (opt = getopt_config(argc, argv, Opts, LongOpts, NULL))) {
 		switch (opt) {
 			break; case '!': insecure = true;
 			break; case 'N': defaultNetwork = optarg;