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
Diffstat:
M | LICENSE | | | 1 | + |
M | Makefile | | | 6 | ++---- |
M | README | | | 41 | +++++++++++++++++++++++++++++++++-------- |
M | chndlr.c | | | 107 | ++++++++++++++++++++++++++++++++++++------------------------------------------- |
M | config.h | | | 24 | ++++++++++++++++-------- |
5 files changed, 101 insertions(+), 78 deletions(-)
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(®ex, pairs[i].regex, REG_EXTENDED))
fprintf(stderr, "invalid regex: %s\n", pairs[i].regex);
if (regexec(®ex, 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(®ex);
+ return reexec(argv[1], pairs[i].action);
}
- */
- regfree(®ex);
}
- /* alternatively, fall back to SOAP_BROWSER */
- snprintf(cmd, sizeof cmd, CHNDLR_FALLBACK_CMD " %s", sharg);
+ regfree(®ex);
+
+ /* 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