about summary refs log tree commit diff
diff options
context:
space:
mode:
authorJune McEnroe <june@causal.agency>2022-03-02 21:43:51 -0500
committerJune McEnroe <june@causal.agency>2022-03-02 21:43:51 -0500
commitc7fda93adf254e69e895770557080be9e7c434e2 (patch)
treef764cdf6129b1d64779b3162461c663cefffa67e
parentAdd empty enroll program (diff)
downloadcatgirl-c7fda93adf254e69e895770557080be9e7c434e2.tar.gz
catgirl-c7fda93adf254e69e895770557080be9e7c434e2.zip
Add skeleton of enroll flow and first two questions
-rw-r--r--enroll.c220
1 files changed, 219 insertions, 1 deletions
diff --git a/enroll.c b/enroll.c
index dea1951..62b72b7 100644
--- a/enroll.c
+++ b/enroll.c
@@ -25,12 +25,230 @@
  * covered work.
  */
 
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <err.h>
+#include <string.h>
+#include <sys/stat.h>
 #include <sysexits.h>
+#include <unistd.h>
 
 char *readpassphrase(const char *prompt, char *buf, size_t bufsiz, int flags);
 
+static bool verbose;
+static void log(const char *format, ...) __attribute__((format(printf, 1, 2)));
+static void log(const char *format, ...) {
+	va_list ap;
+	if (!verbose) return;
+	va_start(ap, format);
+	fprintf(stderr, "# ");
+	vfprintf(stderr, format, ap);
+	fprintf(stderr, "\n");
+	va_end(ap);
+}
+
+static int prompt1(char **resp, const char *def, const char *desc) {
+	static char *buf;
+	static size_t cap;
+
+	size_t head = strcspn(desc, "\n");
+	for (*resp = NULL; !*resp;) {
+		printf("%.*s [%s] ", (int)head, desc, (def ?: ""));
+		ssize_t len = getline(&buf, &cap, stdin);
+		if (len < 0 && feof(stdin)) {
+			printf("\n");
+			clearerr(stdin);
+			return -1;
+		} else if (len < 0) {
+			err(EX_IOERR, "getline");
+		}
+		if (len && buf[len-1] == '\n') buf[len-1] = '\0';
+
+		if (buf[0] == '?') {
+			printf("\n%s\n", &desc[head+1]);
+		} else if (buf[0]) {
+			*resp = strdup(buf);
+			if (!*resp) err(EX_OSERR, "strdup");
+		} else if (def) {
+			*resp = strdup(def);
+			if (!*resp) err(EX_OSERR, "strdup");
+		}
+	}
+	return 0;
+}
+
+static int prompt(char **resp, const char *def, const char *format, ...)
+__attribute__((format(printf, 3, 4)));
+static int prompt(char **resp, const char *def, const char *format, ...) {
+	va_list ap;
+	char desc[1024];
+	va_start(ap, format);
+	vsnprintf(desc, sizeof(desc), format, ap);
+	va_end(ap);
+	return prompt1(resp, def, desc);
+}
+
+static int yesno(bool *resp, bool def, const char *format, ...)
+__attribute__((format(printf, 3, 4)));
+static int yesno(bool *resp, bool def, const char *format, ...) {
+	va_list ap;
+	char desc[1024];
+	va_start(ap, format);
+	vsnprintf(desc, sizeof(desc), format, ap);
+	va_end(ap);
+	char *str;
+	for (;;) {
+		int pop = prompt1(&str, (def ? "yes" : "no"), desc);
+		if (pop) return pop;
+		char yn = str[0];
+		free(str);
+		if (yn == 'Y' || yn == 'y') {
+			*resp = true;
+			return 0;
+		} else if (yn == 'N' || yn == 'n') {
+			*resp = false;
+			return 0;
+		}
+	}
+}
+
+static struct {
+	char *network;
+	char *config;
+} info;
+
+static int getNetwork(const char *def) {
+	return prompt(
+		&info.network, def,
+		"Which network would you like to connect to?\n"
+		"Enter the domain name of the IRC network you wish to connect to.\n"
+		"Examples: tilde.chat, libera.chat\n"
+	);
+}
+
+static int getConfig(const char *configHome) {
+	char *name = strdup(info.network);
+	if (!name) err(EX_OSERR, "strdup");
+	char *prev = NULL, *next = NULL;
+	while (name) {
+		prev = next;
+		next = strsep(&name, ".");
+	}
+
+	const char *home = getenv("HOME");
+	const char *suffix = configHome;
+	size_t homeLen = strlen(home);
+	if (!strncmp(configHome, home, homeLen)) {
+		suffix += homeLen;
+	}
+	int pop = prompt(
+		&info.config, prev,
+		"What would you like to name this configuration?\n"
+		"This is the name you will pass to catgirl to connect to %s.\n"
+		"Example: $ catgirl %s\n"
+		"The configuration will be written to: %s%s/catgirl\n",
+		info.network, prev, (suffix != configHome ? "~" : ""), suffix
+	);
+	if (pop) return pop;
+
+	char path[PATH_MAX];
+	snprintf(path, sizeof(path), "%s/catgirl/%s", configHome, info.config);
+	if (!access(path, F_OK)) {
+		printf(
+			"There is already a configuration named %s.\n"
+			"Please remove or rename the file: %s%s/catgirl/%s\n"
+			"Otherwise, enter a different name.\n",
+			info.config, (suffix != configHome ? "~" : ""), suffix, info.config
+		);
+		free(info.config);
+		info.config = NULL;
+	}
+	return 0;
+}
+
+static void unset(char **resp) {
+	free(*resp);
+	*resp = NULL;
+}
+
 int main(int argc, char *argv[]) {
+	for (int opt; 0 < (opt = getopt(argc, argv, "v"));) {
+		switch (opt) {
+			break; case 'v': verbose = true;
+			break; default: return EX_USAGE;
+		}
+	}
+
+	char configHome[PATH_MAX];
+	if (getenv("XDG_CONFIG_HOME")) {
+		snprintf(
+			configHome, sizeof(configHome), "%s", getenv("XDG_CONFIG_HOME")
+		);
+	} else if (getenv("HOME")) {
+		snprintf(configHome, sizeof(configHome), "%s/.config", getenv("HOME"));
+	} else {
+		errx(EX_USAGE, "HOME unset");
+	}
+
+	char path[PATH_MAX];
+	snprintf(path, sizeof(path), "%s/catgirl", configHome);
+	int error = mkdir(path, S_IRWXU);
+	if (error && errno != EEXIST) err(EX_CANTCREAT, "%s", path);
+	umask(S_IRWXG | S_IRWXO);
+
+	printf(
+		"Welcome to catgirl enrollment! This tool should get you connected to\n"
+		"IRC using catgirl :3\n"
+		"\n"
+		"Type ? at a prompt for a more detailed explanation.\n"
+		"The default response is shown in [brackets].\n"
+		"Press enter at a prompt to select the default response.\n"
+		"Press control-D at a prompt to return to the previous one.\n"
+		"\n"
+	);
+
+	int pop;
+	for (;;) {
+		if (!info.network) {
+			pop = getNetwork((optind < argc ? argv[optind] : NULL));
+			if (pop) return EX_USAGE;
+		} else if (!info.config) {
+			pop = getConfig(configHome);
+			if (pop) unset(&info.network);
+		} else {
+			break;
+		}
+	}
+
+	snprintf(path, sizeof(path), "%s/catgirl/%s", configHome, info.config);
+	log("Writing configuration to: %s", path);
+	FILE *file = fopen(path, "w");
+	if (!file) err(EX_CANTCREAT, "%s", path);
+
+	fprintf(file, "host = %s\n", info.network); // FIXME: info.host
+
+	error = fclose(file);
+	if (error) err(EX_IOERR, "%s", path);
+
+	printf(
+		"\n"
+		"You're all set! You can connect to %s using:\n"
+		"$ catgirl %s\n"
+		"\n",
+		info.network, info.config
+	);
+	bool run;
+	pop = yesno(
+		&run, true,
+		"Connect now?\n"
+		"There's nothing left to do. This will start catgirl.\n"
+	);
+	if (pop || !run) return EX_OK;
+
+	execlp("catgirl", "catgirl", info.config, NULL);
+	err(EX_UNAVAILABLE, "catgirl");
 }