Use getopt_config to load options

I'm really getting a lot of use out of this config.c huh.
master
C. McEnroe 2020-02-05 22:49:56 -05:00
parent 6ca54617ce
commit 27eaddb6b9
5 changed files with 234 additions and 13 deletions

View File

@ -7,6 +7,7 @@ LDLIBS = -lcurses -lcrypto -ltls
OBJS += chat.o OBJS += chat.o
OBJS += command.o OBJS += command.o
OBJS += config.o
OBJS += edit.o OBJS += edit.o
OBJS += handle.o OBJS += handle.o
OBJS += irc.o OBJS += irc.o

View File

@ -19,6 +19,7 @@
.Op Fl r Ar real .Op Fl r Ar real
.Op Fl u Ar user .Op Fl u Ar user
.Op Fl w Ar pass .Op Fl w Ar pass
.Op Ar config ...
. .
.Sh DESCRIPTION .Sh DESCRIPTION
The The
@ -27,9 +28,25 @@ program is a curses
TLS-only IRC client. TLS-only IRC client.
. .
.Pp .Pp
Options can be loaded from files
listed on the command line.
Files are searched for in
.Pa $XDG_CONFIG_DIRS/catgirl
unless the path starts with
.Ql /
or
.Ql \&. .
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: The arguments are as follows:
.Bl -tag -width Ds .Bl -tag -width Ds
.It Fl a Ar user Ns : Ns Ar pass .It Fl a Ar user Ns : Ns Ar pass , Cm sasl-plain = Ar user Ns : Ns Ar pass
Authenticate as Authenticate as
.Ar user .Ar user
with with
@ -40,7 +57,7 @@ in plain text,
it is recommended to use SASL EXTERNAL instead with it is recommended to use SASL EXTERNAL instead with
.Fl e . .Fl e .
. .
.It Fl c Ar path .It Fl c Ar path , Cm cert = Ar path
Load the TLS client certificate from Load the TLS client certificate from
.Ar path . .Ar path .
If the private key is in a separate file, If the private key is in a separate file,
@ -50,52 +67,52 @@ With
.Fl e , .Fl e ,
authenticate using SASL EXTERNAL. authenticate using SASL EXTERNAL.
. .
.It Fl e .It Fl e , Cm sasl-external
Authenticate using SASL EXTERNAL, Authenticate using SASL EXTERNAL,
also known as CertFP. also known as CertFP.
The TLS client certificate is loaded with The TLS client certificate is loaded with
.Fl c . .Fl c .
. .
.It Fl h Ar host .It Fl h Ar host , Cm host = Ar host
Connect to Connect to
.Ar host . .Ar host .
. .
.It Fl j Ar join .It Fl j Ar join , Cm join = Ar join
Join the comma-separated list of channels Join the comma-separated list of channels
.Ar join . .Ar join .
. .
.It Fl k Ar path .It Fl k Ar path , Cm priv = Ar priv
Load the TLS client private key from Load the TLS client private key from
.Ar path . .Ar path .
. .
.It Fl n Ar nick .It Fl n Ar nick , Cm nick = Ar nick
Set nickname to Set nickname to
.Ar nick . .Ar nick .
The default nickname is the user's name. The default nickname is the user's name.
. .
.It Fl p Ar port .It Fl p Ar port , Cm port = Ar port
Connect to Connect to
.Ar port . .Ar port .
The default port is 6697. The default port is 6697.
. .
.It Fl r Ar real .It Fl r Ar real , Cm real = Ar real
Set realname to Set realname to
.Ar real . .Ar real .
The default realname is the same as the nickname. The default realname is the same as the nickname.
. .
.It Fl u Ar user .It Fl u Ar user , Cm user = Ar user
Set username to Set username to
.Ar user . .Ar user .
The default username is the same as the nickname. The default username is the same as the nickname.
. .
.It Fl v .It Fl v , Cm debug
Log raw IRC messages to the Log raw IRC messages to the
.Sy <debug> .Sy <debug>
window window
as well as standard error as well as standard error
if it is not a terminal. if it is not a terminal.
. .
.It Fl w Ar pass .It Fl w Ar pass , Cm pass = Ar pass
Log in with the server password Log in with the server password
.Ar pass . .Ar pass .
.El .El

20
chat.c
View File

@ -63,8 +63,26 @@ int main(int argc, char *argv[]) {
const char *user = NULL; const char *user = NULL;
const char *real = NULL; const char *real = NULL;
const char *Opts = "!a:c:eh:j:k:n:p:r:u:vw:";
const struct option LongOpts[] = {
{ "insecure", no_argument, NULL, '!' },
{ "sasl-plain", required_argument, NULL, 'a' },
{ "cert", required_argument, NULL, 'c' },
{ "sasl-external", no_argument, NULL, 'e' },
{ "host", required_argument, NULL, 'h' },
{ "join", required_argument, NULL, 'j' },
{ "priv", required_argument, NULL, 'k' },
{ "nick", required_argument, NULL, 'n' },
{ "port", required_argument, NULL, 'p' },
{ "real", required_argument, NULL, 'r' },
{ "user", required_argument, NULL, 'u' },
{ "debug", no_argument, NULL, 'v' },
{ "pass", required_argument, NULL, 'w' },
{0},
};
int opt; int opt;
while (0 < (opt = getopt(argc, argv, "!a:c:eh:j:k:n:p:r:u:vw:"))) { while (0 < (opt = getopt_config(argc, argv, Opts, LongOpts, NULL))) {
switch (opt) { switch (opt) {
break; case '!': insecure = true; break; case '!': insecure = true;
break; case 'a': sasl = true; self.plain = optarg; break; case 'a': sasl = true; self.plain = optarg;

7
chat.h
View File

@ -15,6 +15,7 @@
*/ */
#include <err.h> #include <err.h>
#include <getopt.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <string.h> #include <string.h>
@ -139,6 +140,12 @@ void edit(size_t id, enum Edit op, wchar_t ch);
char *editHead(void); char *editHead(void);
char *editTail(void); char *editTail(void);
FILE *configOpen(const char *path, const char *mode);
int getopt_config(
int argc, char *const *argv,
const char *optstring, const struct option *longopts, int *longindex
);
static inline enum Color hash(const char *str) { static inline enum Color hash(const char *str) {
if (*str == '~') str++; if (*str == '~') str++;
uint32_t hash = 0; uint32_t hash = 0;

178
config.c 100644
View File

@ -0,0 +1,178 @@
/* 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 <errno.h>
#include <getopt.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "chat.h"
#define CONFIG_DIR "catgirl"
FILE *configOpen(const char *path, const char *mode) {
if (path[0] == '/' || path[0] == '.') goto local;
const char *home = getenv("HOME");
const char *configHome = getenv("XDG_CONFIG_HOME");
const char *configDirs = getenv("XDG_CONFIG_DIRS");
char buf[PATH_MAX];
if (configHome) {
snprintf(buf, sizeof(buf), "%s/" CONFIG_DIR "/%s", configHome, path);
} else {
if (!home) goto local;
snprintf(buf, sizeof(buf), "%s/.config/" CONFIG_DIR "/%s", home, path);
}
FILE *file = fopen(buf, mode);
if (file) return file;
if (errno != ENOENT) return NULL;
if (!configDirs) configDirs = "/etc/xdg";
while (*configDirs) {
size_t len = strcspn(configDirs, ":");
snprintf(
buf, sizeof(buf), "%.*s/" CONFIG_DIR "/%s",
(int)len, configDirs, path
);
file = fopen(buf, mode);
if (file) return file;
if (errno != ENOENT) return NULL;
configDirs += len;
if (*configDirs) configDirs++;
}
local:
return fopen(path, mode);
}
#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 = configOpen(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;
}
}