chndlr

Personal fork of spm (simple password manager)

commit f8c09ab6cd674863fe769532f80f0d49f036d9c0
parent 0ea885df081beae2f2fc524ba9c5fb064078e9c4
Author: Bharatvaj Hemanth <bharatvaj@yahoo.com>
Date: Fri, 21 Feb 2025 23:48:40 +0530

Use execvp() in place of system() for better performance

Fix regex ordering and update samples in config.h

Update README with latest features

Add name to LICENSE
5 files changed, 101 insertions(+), 78 deletions(-)
M
LICENSE
|
1
+
M
Makefile
|
6
++----
M
README
|
41
+++++++++++++++++++++++++++++++++--------
M
chndlr.c
|
107
++++++++++++++++++++++++++++++++++++-------------------------------------------
M
config.h
|
24
++++++++++++++++--------
diff --git a/LICENSE b/LICENSE
@@ -1,6 +1,7 @@
 MIT/X Consortium License
 
 © 2014 Laslo Hunhold <dev@frign.de>
+© 2025 Bharatvaj Hemanth <bharatvaj@nonplanar.org>
 
 Permission is hereby granted, free of charge, to any person obtaining a
 copy of this software and associated documentation files (the "Software"),
diff --git a/Makefile b/Makefile
@@ -14,14 +14,12 @@ options:
 	@echo "CC       = ${CC}"
 
 .c.o:
-	@echo CC $<
-	@${CC} -c ${CFLAGS} $<
+	${CC} -c ${CFLAGS} $<
 
 ${OBJ}: config.mk config.h
 
 chndlr: ${OBJ}
-	@echo CC -o $@
-	@${CC} -o $@ ${OBJ} ${LDFLAGS}
+	${CC} -o $@ ${OBJ} ${LDFLAGS}
 
 clean:
 	@echo cleaning
diff --git a/README b/README
@@ -1,7 +1,9 @@
 chndlr
 ======
 
-chndlr is a simple, fast, and suckless replacement for xdg-open. It determines the appropriate application to open a file or URL based on user-defined rules in config.h.
+chndlr is a simple, fast, and suckless replacement for xdg-open.
+It determines the appropriate application to open a file or URL based on
+user-defined rules in config.h.
 
 Features
 --------

@@ -26,19 +28,42 @@ config.h
 --------
 chndlr uses config.h for configuration,
 
+Entries to `pairs` can be added with easy to use macros P and WEB_PREFIX,
+
 	static const Pair pairs[] = {
-		// regex        action
+
+		P( "\\.mp3", "st", "-e", "mplayer", "%s");		
+
+		P( WEB_PREFIX("github.com") "/([^/]+)/([^/]+)/actions",
+						"gh run list --repo %1/%2/%3" )
 		...
-		{ "\\.mp3$",    "st -e mplayer %s" },
-		{ "(https://github.com)/([^/]+)/([^/]+)/actions",
-						"gh run list --repo %1%2/%3/%4" }
-	}
 
-In action, %1 captures the first group (), %2 the second and so on, until 9.
-%s substites the whole text.
+	};
+
+For the input,
+
+	$ chndlr 'https://www.github.com/bharatvaj/chndlr'
+
+We get,
+
+	%1 - https://www.github.com
+	%2 - bharatvaj
+	%3 - chndlr
+
+	%s - https://www.github.com/bharatvaj/chndlr
+
+%0-%9 and %s are available for substituion.
 
 To know more the about capture group sytax, see regex(7).
 
+chndlr uses 'execvp()' instead of 'system()' to speed up launching processes.
+This means that the following would NOT work.
+
+	/* config.h */
+
+	P( "\\.gif$",
+		"wget", "-O", "/tmp/tmp.gif %s && gifview -a /tmp/tmp_gifview.gif")
+
 Credits
 -------
 Forked from soap.
diff --git a/chndlr.c b/chndlr.c
@@ -1,89 +1,80 @@
 /* See LICENSE file for copyright and license details. */
 #include <stdlib.h>
-#include <stdio.h>
 #include <string.h>
+#include <stdio.h>
 #include <regex.h>
+#include <unistd.h>
 
 typedef struct {
 	const char *regex;
-	const char *action;
+	const char **action;
 } Pair;
 
 #include "config.h"
 
+int i;
+char cmd[BUFSIZ], *cmdv[BUFSIZ/16];
+regmatch_t match[9];
+
+int
+reexec(char *uri, const char **args) {
+	const char *arg;
+	char *p = cmd;
+	int len;
+
+	while (*args) {
+		arg = *args;
+		cmdv[args - pairs[i].action] = p;
+
+		while (*arg) {
+			if (*arg == '%') {
+				unsigned char nc = *(arg + 1);
+				/* check if N in %N is between 0 and 9 */
+				int group = nc - '0';
+				if (group <= 9) {
+					len = match[group].rm_eo - match[group].rm_so;
+					snprintf(p, len + 1, "%.*s", len, uri + match[group].rm_so);
+				} else /* if (nc == 's') */ {
+					len = sprintf(p, "%s", uri);
+				}
+				arg += 2;
+				p += len;
+			} else {
+				*p++ = *arg++;
+			}
+		}
+
+		*p++ = '\0';
+		args++;
+	}
+
+	return execvp(*cmdv, cmdv);
+}
+
+
 int
 main(int argc, char *argv[]){
-	int g, h, i;
-	char cmd[BUFSIZ], sharg[BUFSIZ];
-	memset(cmd, '\0', BUFSIZ);
 	regex_t regex;
-	regmatch_t match[9];
 
 	/* we only take one argument */
 	if (argc != 2)
 		return EXIT_FAILURE;
 
-	/* make the argument shell-ready
-	 *   1) start with '
-	 *   2) escape ' to '\''
-	 *   3) close with '\0
-	 */
-	sharg[0] = '\'';
-	for (g=0, h=1; argv[1][g] && h < BUFSIZ-1-3-2; ++g, ++h) {
-		sharg[h] = argv[1][g];
-		if (argv[1][g] == '\'') {
-			sharg[++h] = '\\';
-			sharg[++h] = '\'';
-			sharg[++h] = '\'';
-		}
-	}
-	sharg[h] = '\'';
-	sharg[++h] = 0;
-
-	int len;
 	/* check regex and launch action if it matches argv[1] */
 	for (i=0; i < sizeof(pairs)/sizeof(*pairs); ++i) {
 		if (regcomp(&regex, pairs[i].regex, REG_EXTENDED))
 			fprintf(stderr, "invalid regex: %s\n", pairs[i].regex);
 
 		if (regexec(&regex, argv[1], 9, match, 0) == 0) {
-			const char *action = pairs[i].action;
-			char *p = cmd;
-
-			while (*action) {
-				/* check if N in %N is between 0 and 9 */
-				if (*action == '%'){
-					unsigned char nc = *(action + 1);
-					if (nc - '0' <= 9) {
-						int group = nc - '0';
-						len = match[group].rm_eo - match[group].rm_so;
-						snprintf(p, len + 1, "%.*s", len, argv[1] + match[group].rm_so);
-					} else /* if (nc == 's') */ {
-						len = strlen(argv[1]);
-						snprintf(p, len + 1, "%.*s", len, argv[1]);
-					}
-					action += 2;
-					p += len;
-				} else {
-					*p++ = *action++;
-				}
-			}
-			*p = '\0';
-
-			printf("Executing: %s\n", cmd);
-			system(cmd);
-			return EXIT_SUCCESS;
-		}
-		/*
-		else {
-			printf("Regex no match %s\n", pairs[i].regex);
+			regfree(&regex);
+			return reexec(argv[1], pairs[i].action);
 		}
-		*/
-		regfree(&regex);
 	}
 
-	/* alternatively, fall back to SOAP_BROWSER */
-	snprintf(cmd, sizeof cmd, CHNDLR_FALLBACK_CMD " %s", sharg);
+	regfree(&regex);
+
+	/* alternatively, fall back to chndlr_fallback_cmd */
+	sprintf(cmd, "%s%s", chndlr_fallback_cmd, argv[1]);
 	system(cmd);
 	return EXIT_SUCCESS;
 }
diff --git a/config.h b/config.h
@@ -1,20 +1,28 @@
 /* See LICENSE file for copyright and license details. */
 
-#define CHNDLR_FALLBACK_CMD "firefox"
+const char* chndlr_fallback_cmd = "firefox ";
+
+#define P(RE,...) { RE, (const char*[]) { __VA_ARGS__, NULL} }
+
+#define WEB_PREFIX(URL) "^(https?://www\\." URL "|https?://" URL ")"
 
 static const Pair pairs[] = {
 	/* regex                  action */
+
 	/* files */
-	{ "\\.(jpg|png|tiff)$",    "nsxiv %s"        },
-	{ "\\.gif$",               "wget -O /tmp/tmp_gifview.gif %s && gifview -a /tmp/tmp_gifview.gif" },
-	{ "\\.mp3$",               "st -e mplayer %s" },
+	P( "\\.(jpg|png|tiff|gif)$","nsxiv", "%s"        ),
+	P( "\\.mp3$",               "st", "-e", "mplayer", "%s"),
 
 	/* web */
-#define WEB_PREFIX "^(http|https)://?(www\.)?"
+
 	/* youtube */
-	{ WEB_PREFIX "(youtube.com/watch\?|youtu\.be/)", "mpv %s" },
+	P( WEB_PREFIX("youtube.com/watch\\?|youtu\\.be/"), "mpv", "%s"),
 
 	/* github */
-	{ WEB_PREFIX "(github.com)/([^/]+)/([^/]+)/actions", "gh run list --repo %1://%2%3/%4/%5" },
-	{ WEB_PREFIX "(github.com)", "lynx %s" },
+	P( WEB_PREFIX("github.com") "/([^/]+)/([^/]+)/actions",
+								"gh", "run", "list", "--repo", "%1/%2/%3" ),
+	P( WEB_PREFIX("github.com"), "lynx", "%s" ),
 };
+
+#undef P
+#undef WEB_PREFIX