chndlr

Personal fork of spm (simple password manager)
git clone git@nonplanar.org:chndlr.git
Log | Files | Refs | README | LICENSE

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:
MLICENSE | 1+
MMakefile | 6++----
MREADME | 41+++++++++++++++++++++++++++++++++--------
Mchndlr.c | 107++++++++++++++++++++++++++++++++++++-------------------------------------------
Mconfig.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(&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