Use getopt_config to load options
I'm really getting a lot of use out of this config.c huh.
This commit is contained in:
		
							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;
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										178
									
								
								config.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								config.c
									
									
									
									
									
										Normal 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;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user