about summary refs log tree commit diff
path: root/enroll.c
diff options
context:
space:
mode:
authorJune McEnroe <june@causal.agency>2022-03-05 21:40:26 -0500
committerJune McEnroe <june@causal.agency>2022-03-05 22:18:47 -0500
commitefe22577dbcd9606bfbfb9b522654b0bd27c8139 (patch)
tree05d9ad7066520efe223148ec67fdc34832e930f1 /enroll.c
parentPrompt for account password (diff)
downloadcatgirl-efe22577dbcd9606bfbfb9b522654b0bd27c8139.tar.gz
catgirl-efe22577dbcd9606bfbfb9b522654b0bd27c8139.zip
Do basic word-wrapping at runtime enroll
Diffstat (limited to '')
-rw-r--r--enroll.c171
1 files changed, 106 insertions, 65 deletions
diff --git a/enroll.c b/enroll.c
index 9776e80..084e5c1 100644
--- a/enroll.c
+++ b/enroll.c
@@ -25,6 +25,7 @@
  * covered work.
  */
 
+#include <assert.h>
 #include <err.h>
 #include <errno.h>
 #include <limits.h>
@@ -37,6 +38,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/ioctl.h>
 #include <sys/socket.h>
 #include <sys/stat.h>
 #include <sysexits.h>
@@ -57,13 +59,57 @@ static void log(const char *format, ...) {
 	va_end(ap);
 }
 
+static void wrap(size_t margin, const char *str, size_t len) {
+	static size_t cols;
+	if (!cols) {
+		struct winsize ws;
+		int error = ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws);
+		if (error) {
+			cols = 80;
+		} else {
+			cols = ws.ws_col;
+		}
+	}
+	while (len) {
+		if (str[0] == ' ') {
+			str++;
+			len--;
+		}
+		size_t line = cols - margin - 1;
+		if (line > len) {
+			line = len;
+		} else {
+			while (line && str[line] != ' ') line--;
+			if (!line) line = len;
+		}
+		fwrite(str, line, 1, stdout);
+		if (line < len) putchar('\n');
+		str += line;
+		len -= line;
+	}
+}
+
+static void wrapf(const char *format, ...)
+__attribute__((format(printf, 1, 2)));
+static void wrapf(const char *format, ...) {
+	va_list ap;
+	char buf[1024];
+	va_start(ap, format);
+	int len = vsnprintf(buf, sizeof(buf), format, ap);
+	assert((size_t)len < sizeof(buf));
+	va_end(ap);
+	wrap(0, buf, len);
+	putchar('\n');
+}
+
 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 ?: ""));
+		wrap(strlen(def ?: "") * 2 + 4, desc, head);
+		printf(" [%s] ", (def ?: ""));
 		ssize_t len = getline(&buf, &cap, stdin);
 		if (len < 0 && feof(stdin)) {
 			printf("\n");
@@ -75,7 +121,9 @@ static int prompt1(char **resp, const char *def, const char *desc) {
 		if (len && buf[len-1] == '\n') buf[len-1] = '\0';
 
 		if (buf[0] == '?') {
-			printf("\n%s\n", &desc[head+1]);
+			printf("\n");
+			wrap(0, &desc[head+1], strlen(&desc[head+1]));
+			printf("\n\n");
 		} else if (buf[0]) {
 			*resp = strdup(buf);
 			if (!*resp) err(EX_OSERR, "strdup");
@@ -188,8 +236,8 @@ 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"
+		"Enter the domain name of the IRC network you wish to connect to. "
+		"Examples: tilde.chat, libera.chat "
 	);
 }
 
@@ -211,11 +259,9 @@ static int getConfig(const char *configHome) {
 	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:\n"
-		"%s%s/catgirl\n",
-		info.network, prev, (suffix != configHome ? "~" : ""), suffix
+		"This is the name you will pass to catgirl to connect to %s. "
+		"The configuration will be written to: %s%s/catgirl ",
+		prev, (suffix != configHome ? "~" : ""), suffix
 	);
 	free(name);
 	if (pop) return pop;
@@ -223,13 +269,12 @@ static int getConfig(const char *configHome) {
 	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:\n"
-			"%s%s/catgirl/%s\n"
-			"Otherwise, enter a different name.\n",
-			info.config, (suffix != configHome ? "~" : ""), suffix, info.config
+		wrapf("There is already a configuration named %s.", info.config);
+		wrapf(
+			"Please remove or rename the file: %s%s/catgirl/%s",
+			(suffix != configHome ? "~" : ""), suffix, info.config
 		);
+		wrapf("Otherwise, enter a different name.");
 		free(info.config);
 		info.config = NULL;
 	}
@@ -293,8 +338,8 @@ static int getHostPort(void) {
 	int pop = prompt(
 		&info.host, ircdot,
 		"Which host should catgirl connect to?\n"
-		"Enter the hostname of the IRC server to connect to. Usually this\n"
-		"starts with \"irc.\".\n"
+		"Enter the hostname of the IRC server to connect to. "
+		"Usually this starts with \"irc.\". "
 	);
 	free(ircdot);
 	if (pop) return pop;
@@ -302,8 +347,8 @@ static int getHostPort(void) {
 	return prompt(
 		&info.port, "6697",
 		"Which port should catgirl connect to?\n"
-		"Enter the port number of the IRC server. This must be the port for\n"
-		"TLS, also called SSL.\n"
+		"Enter the port number of the IRC server. "
+		"This must be the port for TLS, also called SSL. "
 	);
 }
 
@@ -317,7 +362,7 @@ static int getAddr(void) {
 	int error = getaddrinfo(info.host, info.port, &hints, &info.addr);
 	if (error) {
 		log("%s", gai_strerror(error));
-		printf("Could not find %s.\n", info.host);
+		wrapf("Could not find %s.", info.host);
 		return -1;
 	}
 	return 0;
@@ -334,8 +379,8 @@ static int connectSock(void) {
 
 		close(sock);
 	}
-	printf(
-		"Could not connect to %s:%s: %s.\n",
+	wrapf(
+		"Could not connect to %s:%s: %s.",
 		info.host, info.port, strerror(errno)
 	);
 	return -1;
@@ -352,9 +397,8 @@ static struct tls *connectTLS(int sock) {
 		|| tls_handshake(client);
 	if (!error) return client;
 
-	printf(
-		"Could not establish TLS with %s:%s:\n"
-		"%s\n",
+	wrapf(
+		"Could not establish TLS with %s:%s: %s",
 		info.host, info.port, tls_error(client)
 	);
 	tls_close(client);
@@ -386,9 +430,8 @@ static int getTLS(const char *configHome) {
 		close(sock);
 		return 0;
 	}
-	printf(
-		"Could not establish TLS with %s:%s:\n"
-		"%s\n",
+	wrapf(
+		"Could not establish TLS with %s:%s: %s",
 		info.host, info.port, tls_error(client)
 	);
 
@@ -416,11 +459,10 @@ static int getTLS(const char *configHome) {
 	int pop = yesno(
 		&trustIt, true,
 		"Trust the server's self-signed certificate?\n"
-		"If you are sure you are connecting to the right place, answer yes.\n"
-		"The current certificate will be saved and explicitly trusted for\n"
-		"future connections to this network.\n"
-		"The certificate will be written to:\n"
-		"%s%s\n",
+		"If you are sure you are connecting to the right place, answer yes. "
+		"The current certificate will be saved and explicitly trusted for "
+		"future connections to this network. "
+		"The certificate will be written to: %s%s ",
 		(suffix != info.trust ? "~" : ""), suffix
 	);
 	if (pop || !trustIt) goto fail;
@@ -435,8 +477,8 @@ static int getTLS(const char *configHome) {
 	size_t pemLen;
 	const uint8_t *pem = tls_peer_cert_chain_pem(client, &pemLen);
 	if (!pem) {
-		printf(
-			"Could not obtain TLS certificate from %s:%s.\n",
+		wrapf(
+			"Could not obtain TLS certificate from %s:%s.",
 			info.host, info.port
 		);
 		goto fail;
@@ -474,8 +516,9 @@ static int getAccountName(void) {
 	int pop = prompt(
 		&info.account.name, "",
 		"If you already have an account on %s, what is its name?\n"
-		"If you have a NickServ account already, enter the name of the\n"
-		"account. If you aren't sure, leave this blank.\n",
+		"If you have a NickServ account already, "
+		"enter the name of the account. "
+		"If you aren't sure, leave this blank. ",
 		info.network
 	);
 	if (pop) return pop;
@@ -497,9 +540,9 @@ static int getNick(void) {
 	return prompt(
 		&info.nick, getenv("USER"),
 		"What name would you like to use on %s?\n"
-		"This is the name others will see on IRC, called a nick. It must be\n"
-		"unique, and can usually contain letters, digits, underscores and\n"
-		"hyphens, but cannot start with a digit.\n",
+		"This is the name others will see on IRC, called a nick. "
+		"It must be unique, and can usually contain letters, digits, "
+		"underscores and hyphens, but cannot start with a digit. ",
 		info.network
 	);
 }
@@ -508,9 +551,9 @@ static int getPronouns(void) {
 	return prompt(
 		&info.pronouns, NULL,
 		"What are your pronouns?\n"
-		"This will be added to your \"real name\" so that other IRC users can\n"
-		"find out how to refer to you with /whois.\n"
-		"Examples: they/them, she/her, he/him\n"
+		"This will be added to your \"real name\" so that other IRC users can "
+		"find out how to refer to you with /whois. "
+		"Examples: they/them, she/her, he/him "
 	);
 }
 
@@ -539,21 +582,22 @@ int main(int argc, char *argv[]) {
 	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"
+	wrapf(
+		"Welcome to catgirl enrollment! "
+		"This tool should get you connected to IRC using catgirl :3 "
 	);
+	printf("\n");
+	wrapf(
+		"Type ? at a prompt for a more detailed explanation. "
+		"The default response is shown in [brackets]. "
+		"Press enter at a prompt to select the default response. "
+		"Press control-D at a prompt to return to the previous one. "
+	);
+	printf("\n");
 
 	int pop;
 	for (;;) {
 		if (!info.network) {
-			// TODO: Parse ircs: URL in args.
 			pop = getNetwork((optind < argc ? argv[optind] : NULL));
 			if (pop) return EX_USAGE;
 		} else if (!info.config) {
@@ -620,10 +664,10 @@ int main(int argc, char *argv[]) {
 	pop = yesno(
 		&saslPass, true,
 		"Would you like to save your account password?\n"
-		"If yes, your account password will be written to the configuration\n"
-		"file and sent automatically using SASL when catgirl connects.\n"
-		"Otherwise, only your account name will be saved, and catgirl will\n"
-		"prompt you for the password every time.\n"
+		"If yes, your account password will be written to the configuration "
+		"file and sent automatically using SASL when catgirl connects. "
+		"Otherwise, only your account name will be saved, and catgirl will "
+		"prompt you for the password every time. "
 	);
 	if (!pop && saslPass) {
 		fprintf(
@@ -641,18 +685,15 @@ int main(int argc, char *argv[]) {
 	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
-	);
+	printf("\n");
+	wrapf("You're all set! You can connect to %s using:", info.network);
+	printf("$ catgirl %s\n\n", info.config);
 	bool run;
 	pop = yesno(
 		&run, true,
 		"Connect now?\n"
-		"There's nothing left to do. This will start catgirl.\n"
+		"There's nothing left to do. "
+		"This will start catgirl. "
 	);
 	if (pop || !run) return EX_OK;