Use getopt_config to load options
I'm really getting a lot of use out of this config.c huh.master
parent
6ca54617ce
commit
27eaddb6b9
1
Makefile
1
Makefile
|
@ -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
|
||||||
|
|
41
catgirl.1
41
catgirl.1
|
@ -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
20
chat.c
|
@ -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
7
chat.h
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue