diff options
-rw-r--r-- | Makefile | 9 | ||||
-rw-r--r-- | config.c | 136 | ||||
-rw-r--r-- | litterbox.1 | 51 | ||||
-rw-r--r-- | litterbox.c | 26 |
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; |