Implement nopen(1) - noice - small file browser (mirror / fork from 2f30.org)
 (HTM) git clone git://git.codemadness.org/noice
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 50592339bf450037972b85777d1c524e35545aa8
 (DIR) parent 47d659c5fc930f0815c2bf5a24b3c2228b13695e
 (HTM) Author: sin <sin@2f30.org>
       Date:   Fri,  2 Aug 2019 15:43:07 +0100
       
       Implement nopen(1)
       
       Diffstat:
         M Makefile                            |      48 ++++++++++++++++++-------------
         D config.def.h                        |     102 -------------------------------
         M noice.1                             |      24 ++++++++----------------
         M noice.c                             |     113 ++-----------------------------
         A noiceconf.def.h                     |      93 +++++++++++++++++++++++++++++++
         A nopen.1                             |      31 +++++++++++++++++++++++++++++++
         A nopen.c                             |      95 ++++++++++++++++++++++++++++++
         A nopenconf.def.h                     |       9 +++++++++
         A spawn.c                             |      43 ++++++++++++++++++++++++++++++
         M strverscmp.c                        |       2 ++
         M util.h                              |      21 +++++++++++++--------
       
       11 files changed, 327 insertions(+), 254 deletions(-)
       ---
 (DIR) diff --git a/Makefile b/Makefile
       @@ -1,45 +1,53 @@
        VERSION = 0.8
       -
        PREFIX = /usr/local
        MANPREFIX = $(PREFIX)/man
        
        #CPPFLAGS = -DDEBUG
        #CFLAGS = -g
       -LDLIBS = -lcurses
        
       -DISTFILES = noice.c strlcat.c strlcpy.c strverscmp.c util.h config.def.h\
       -    noice.1 Makefile README LICENSE
       -OBJ = noice.o strlcat.o strlcpy.o strverscmp.o
       -BIN = noice
       +NOICELDLIBS = -lcurses
       +NOPENLDLIBS =
       +NOICEOBJ = noice.o spawn.o strlcat.o strlcpy.o strverscmp.o
       +NOPENOBJ = nopen.o spawn.o
       +BIN = noice nopen
       +MAN = noice.1 nopen.1
        
        all: $(BIN)
        
       -$(BIN): $(OBJ)
       -        $(CC) $(CFLAGS) -o $@ $(OBJ) $(LDFLAGS) $(LDLIBS)
       +noice: $(NOICEOBJ)
       +        $(CC) $(CFLAGS) -o $@ $(NOICEOBJ) $(LDFLAGS) $(NOICELDLIBS)
       +
       +nopen: $(NOPENOBJ)
       +        $(CC) $(CFLAGS) -o $@ $(NOPENOBJ) $(LDFLAGS) $(NOPENLDLIBS)
        
       -noice.o: util.h config.h
       +noice.o: noiceconf.h util.h
       +nopen.o: nopenconf.h util.h
       +spawn.o: util.h
        strlcat.o: util.h
        strlcpy.o: util.h
       +strverscmp.o: util.h
       +
       +noiceconf.h:
       +        cp noiceconf.def.h $@
        
       -config.h:
       -        cp config.def.h $@
       +nopenconf.h:
       +        cp nopenconf.def.h $@
        
        install: all
                mkdir -p $(DESTDIR)$(PREFIX)/bin
                cp -f $(BIN) $(DESTDIR)$(PREFIX)/bin
                mkdir -p $(DESTDIR)$(MANPREFIX)/man1
       -        cp -f $(BIN).1 $(DESTDIR)$(MANPREFIX)/man1
       +        cp -f $(MAN) $(DESTDIR)$(MANPREFIX)/man1
        
        uninstall:
       -        rm -f $(DESTDIR)$(PREFIX)/bin/$(BIN)
       -        rm -f $(DESTDIR)$(MANPREFIX)/man1/$(BIN).1
       +        cd $(DESTDIR)$(PREFIX)/bin && rm -f $(BIN)
       +        cd $(DESTDIR)$(MANPREFIX)/man1 && rm -f $(MAN)
        
       -dist:
       +dist: clean
                mkdir -p noice-$(VERSION)
       -        cp $(DISTFILES) noice-$(VERSION)
       -        tar -cf noice-$(VERSION).tar noice-$(VERSION)
       -        gzip noice-$(VERSION).tar
       -        rm -rf noice-$(VERSION)
       +        cp `find . -maxdepth 1 -type f` noice-$(VERSION)
       +        tar -c noice-$(VERSION) | gzip > noice-$(VERSION).tar.gz
        
        clean:
       -        rm -f $(BIN) $(OBJ) noice-$(VERSION).tar.gz
       +        rm -f $(BIN) $(NOICEOBJ) $(NOPENOBJ) noice-$(VERSION).tar.gz
       +        rm -rf noice-$(VERSION)
 (DIR) diff --git a/config.def.h b/config.def.h
       @@ -1,102 +0,0 @@
       -/* See LICENSE file for copyright and license details. */
       -#define CWD   "cwd: "
       -#define CURSR " > "
       -#define EMPTY "   "
       -
       -int dirorder    = 0; /* Set to 1 to sort by directory first */
       -int mtimeorder  = 0; /* Set to 1 to sort by time modified */
       -int icaseorder  = 0; /* Set to 1 to sort by ignoring case */
       -int versorder   = 0; /* Set to 1 to sort by version number */
       -int idletimeout = 0; /* Screensaver timeout in seconds, 0 to disable */
       -int showhidden  = 0; /* Set to 1 to show hidden files by default */
       -int usecolor    = 0; /* Set to 1 to enable color attributes */
       -char *idlecmd   = "rain"; /* The screensaver program */
       -
       -/* See curs_attr(3) for valid video attributes */
       -#define CURSR_ATTR A_NORMAL
       -#define DIR_ATTR   A_NORMAL | COLOR_PAIR(4)
       -#define LINK_ATTR  A_NORMAL | COLOR_PAIR(6)
       -#define SOCK_ATTR  A_NORMAL | COLOR_PAIR(1)
       -#define FIFO_ATTR  A_NORMAL | COLOR_PAIR(5)
       -#define EXEC_ATTR  A_NORMAL | COLOR_PAIR(2)
       -
       -/* Colors to use with COLOR_PAIR(n) as attributes */
       -struct cpair pairs[] = {
       -        { .fg = 0, .bg = 0 },
       -        /* pairs start at 1 */
       -        { COLOR_RED,     -1 },
       -        { COLOR_GREEN,   -1 },
       -        { COLOR_YELLOW,  -1 },
       -        { COLOR_BLUE,    -1 },
       -        { COLOR_MAGENTA, -1 },
       -        { COLOR_CYAN,    -1 },
       -};
       -
       -struct assoc assocs[] = {
       -        { .regex = "\\.(avi|mp4|mkv|mp3|ogg|flac|mov)$", .file = "mpv", .argv = { "mpv", "{}", NULL } },
       -        { .regex = "\\.(png|jpg|gif)$", .file = "sxiv", .argv = { "sxiv", "{}", NULL} },
       -        { .regex = "\\.(html|svg)$", .file = "firefox", .argv = { "firefox", "{}", NULL } },
       -        { .regex = "\\.pdf$", .file = "mupdf", .argv = { "mupdf", "{}", NULL} },
       -        { .regex = "\\.sh$", .file = "sh", .argv = { "sh", "{}", NULL} },
       -        { .regex = ".", .file = "less", .argv = { "less", "{}", NULL } },
       -};
       -
       -struct key bindings[] = {
       -        /* Quit */
       -        { 'q',            SEL_QUIT },
       -        /* Back */
       -        { KEY_BACKSPACE,  SEL_BACK },
       -        { KEY_LEFT,       SEL_BACK },
       -        { 'h',            SEL_BACK },
       -        { CONTROL('H'),   SEL_BACK },
       -        /* Inside */
       -        { KEY_ENTER,      SEL_GOIN },
       -        { '\r',           SEL_GOIN },
       -        { KEY_RIGHT,      SEL_GOIN },
       -        { 'l',            SEL_GOIN },
       -        /* Filter */
       -        { '/',            SEL_FLTR },
       -        { '&',            SEL_FLTR },
       -        /* Next */
       -        { 'j',            SEL_NEXT },
       -        { KEY_DOWN,       SEL_NEXT },
       -        { CONTROL('N'),   SEL_NEXT },
       -        /* Previous */
       -        { 'k',            SEL_PREV },
       -        { KEY_UP,         SEL_PREV },
       -        { CONTROL('P'),   SEL_PREV },
       -        /* Page down */
       -        { KEY_NPAGE,      SEL_PGDN },
       -        { CONTROL('D'),   SEL_PGDN },
       -        /* Page up */
       -        { KEY_PPAGE,      SEL_PGUP },
       -        { CONTROL('U'),   SEL_PGUP },
       -        /* Home */
       -        { KEY_HOME,       SEL_HOME },
       -        { META('<'),      SEL_HOME },
       -        { '^',            SEL_HOME },
       -        /* End */
       -        { KEY_END,        SEL_END },
       -        { META('>'),      SEL_END },
       -        { '$',            SEL_END },
       -        /* Change dir */
       -        { 'c',            SEL_CD },
       -        { '~',            SEL_CDHOME },
       -        /* Toggle hide .dot files */
       -        { '.',            SEL_TOGGLEDOT },
       -        /* Toggle sort by directory first */
       -        { 'd',            SEL_DSORT },
       -        /* Toggle sort by time */
       -        { 't',            SEL_MTIME },
       -        /* Toggle case sensitivity */
       -        { 'i',            SEL_ICASE },
       -        /* Toggle sort by version number */
       -        { 'v',            SEL_VERS },
       -        { CONTROL('L'),   SEL_REDRAW },
       -        /* Run command */
       -        { 'z',            SEL_RUN, "top" },
       -        { '!',            SEL_RUN, "sh", "SHELL" },
       -        /* Run command with argument */
       -        { 'e',            SEL_RUNARG, "vi", "EDITOR" },
       -        { 'p',            SEL_RUNARG, "less", "PAGER" },
       -};
 (DIR) diff --git a/noice.1 b/noice.1
       @@ -1,4 +1,4 @@
       -.Dd March 31, 2019
       +.Dd August 2, 2019
        .Dt NOICE 1
        .Os
        .Sh NAME
       @@ -84,23 +84,13 @@ directory you came out of.
        .Sh CONFIGURATION
        .Nm
        is configured by modifying
       -.Pa config.h
       +.Pa noiceconf.h
        and recompiling the code.
        .Pp
       -The file associations are specified by regexes
       -matching on the currently selected filename.
       -If a match is found the associated program is executed
       -with the filename passed in as the argument.
       -If no match is found the program
       -.Xr less 1
       -is invoked.
       -This is useful for editing text files as one can use the
       -.Ic v
       -command in
       -.Xr less 1
       -to edit the file using the
       -.Ev EDITOR
       -environment variable.
       +.Nm
       +invokes
       +.Xr nopen 1
       +to open a file in the user's preferred application.
        .Sh FILTERS
        Filters allow you to use regexes to display only the matched
        entries in the current directory view.
       @@ -131,6 +121,8 @@ commands respectively.
        If you are using
        .Xr urxvt 1
        you might have to set backspace key to DEC.
       +.Sh SEE ALSO
       +.Xr nopen 1
        .Sh AUTHORS
        .An Lazaros Koromilas Aq Mt lostd@2f30.org ,
        .An Dimitris Papastamos Aq Mt sin@2f30.org .
 (DIR) diff --git a/noice.c b/noice.c
       @@ -1,7 +1,6 @@
        /* See LICENSE file for copyright and license details. */
        #include <sys/stat.h>
        #include <sys/types.h>
       -#include <sys/wait.h>
        
        #include <curses.h>
        #include <dirent.h>
       @@ -20,21 +19,12 @@
        
        #include "util.h"
        
       -#define NR_ARGS        32
       -#define LEN(x) (sizeof(x) / sizeof(*(x)))
        #undef MIN
        #define MIN(x, y) ((x) < (y) ? (x) : (y))
        #define ISODD(x) ((x) & 1)
        #define CONTROL(c) ((c) ^ 0x40)
        #define META(c) ((c) ^ 0x80)
        
       -struct assoc {
       -        char *regex; /* Regex to match on filename */
       -        char *file;
       -        char *argv[NR_ARGS];
       -        regex_t regcomp;
       -};
       -
        struct cpair {
                int fg;
                int bg;
       @@ -71,7 +61,7 @@ struct key {
                char *env;       /* Environment variable to run */
        };
        
       -#include "config.h"
       +#include "noiceconf.h"
        
        struct entry {
                char name[PATH_MAX];
       @@ -167,59 +157,6 @@ xdirname(const char *path)
                return out;
        }
        
       -void
       -spawnvp(char *dir, char *file, char *argv[])
       -{
       -        pid_t pid;
       -        int status;
       -
       -        pid = fork();
       -        if (pid == 0) {
       -                if (dir != NULL)
       -                        chdir(dir);
       -                execvp(file, argv);
       -                _exit(1);
       -        } else {
       -                /* Ignore interruptions */
       -                while (waitpid(pid, &status, 0) == -1)
       -                        DPRINTF_D(status);
       -                DPRINTF_D(pid);
       -        }
       -}
       -
       -void
       -spawnlp(char *dir, char *file, char *argv0, ...)
       -{
       -        char *argv[NR_ARGS];
       -        va_list ap;
       -        int argc;
       -
       -        va_start(ap, argv0);
       -        argv[0] = argv0;
       -        for (argc = 1; argv[argc] = va_arg(ap, char *); argc++)
       -                ;
       -        argv[argc] = NULL;
       -        va_end(ap);
       -        spawnvp(dir, file, argv);
       -}
       -
       -void
       -spawnassoc(struct assoc *assoc, char *arg)
       -{
       -        char *argv[NR_ARGS];
       -        int i;
       -
       -        for (i = 0; assoc->argv[i]; i++) {
       -                if (strcmp(assoc->argv[i], "{}") == 0) {
       -                        argv[i] = arg;
       -                        continue;
       -                }
       -                argv[i] = assoc->argv[i];
       -        }
       -        argv[i] = NULL;
       -        spawnvp(NULL, assoc->file, argv);
       -}
       -
        char *
        xgetenv(char *name, char *fallback)
        {
       @@ -231,21 +168,6 @@ xgetenv(char *name, char *fallback)
                return value && value[0] ? value : fallback;
        }
        
       -struct assoc *
       -openwith(char *file)
       -{
       -        int i;
       -
       -        for (i = 0; i < LEN(assocs); i++) {
       -                if (regexec(&assocs[i].regcomp, file, 0, NULL, 0) == 0) {
       -                        DPRINTF_S(assocs[i].argv[0]);
       -                        return &assocs[i];
       -                }
       -        }
       -
       -        return NULL;
       -}
       -
        int
        setfilter(regex_t *regex, char *filter)
        {
       @@ -671,7 +593,6 @@ browse(char *ipath, char *ifilter)
                char path[PATH_MAX], oldpath[PATH_MAX], newpath[PATH_MAX];
                char fltr[LINE_MAX];
                char *dir, *tmp, *run, *env;
       -        struct assoc *assoc;
                struct stat sb;
                regex_t re;
                int r, fd;
       @@ -744,13 +665,8 @@ nochange:
                                        strlcpy(fltr, ifilter, sizeof(fltr));
                                        goto begin;
                                case S_IFREG:
       -                                assoc = openwith(newpath);
       -                                if (assoc == NULL) {
       -                                        printmsg("No association");
       -                                        goto nochange;
       -                                }
                                        exitcurses();
       -                                spawnassoc(assoc, newpath);
       +                                spawnlp(path, "nopen", "nopen", newpath, (void *)0);
                                        initcurses();
                                        continue;
                                default:
       @@ -869,7 +785,7 @@ nochange:
                                        mkpath(path, dents[cur].name, oldpath, sizeof(oldpath));
                                run = xgetenv(env, run);
                                exitcurses();
       -                        spawnlp(path, run, run, NULL);
       +                        spawnlp(path, run, run, (void *)0);
                                initcurses();
                                goto begin;
                        case SEL_RUNARG:
       @@ -878,7 +794,7 @@ nochange:
                                        mkpath(path, dents[cur].name, oldpath, sizeof(oldpath));
                                run = xgetenv(env, run);
                                exitcurses();
       -                        spawnlp(path, run, run, dents[cur].name, NULL);
       +                        spawnlp(path, run, run, dents[cur].name, (void *)0);
                                initcurses();
                                goto begin;
                        }
       @@ -886,31 +802,13 @@ nochange:
                        if (idletimeout != 0 && idle == idletimeout) {
                                idle = 0;
                                exitcurses();
       -                        spawnlp(NULL, idlecmd, idlecmd, NULL);
       +                        spawnlp(NULL, idlecmd, idlecmd, (void *)0);
                                initcurses();
                        }
                }
        }
        
        void
       -initassocs(void)
       -{
       -        char errbuf[256];
       -        int i, r;
       -
       -        for (i = 0; i < LEN(assocs); i++) {
       -                r = regcomp(&assocs[i].regcomp, assocs[i].regex,
       -                            REG_NOSUB | REG_EXTENDED | REG_ICASE);
       -                if (r != 0) {
       -                        regerror(r, &assocs[i].regcomp, errbuf, sizeof(errbuf));
       -                        fprintf(stderr, "invalid regex assocs[%d]: %s: %s\n",
       -                                i, assocs[i].regex, errbuf);
       -                        exit(1);
       -                }
       -        }
       -}
       -
       -void
        usage(char *argv0)
        {
                fprintf(stderr, "usage: %s [dir]\n", argv0);
       @@ -954,7 +852,6 @@ main(int argc, char *argv[])
        
                /* Set locale before curses setup */
                setlocale(LC_ALL, "");
       -        initassocs();
                initcurses();
                browse(ipath, ifilter);
                exitcurses();
 (DIR) diff --git a/noiceconf.def.h b/noiceconf.def.h
       @@ -0,0 +1,93 @@
       +/* See LICENSE file for copyright and license details. */
       +#define CWD   "cwd: "
       +#define CURSR " > "
       +#define EMPTY "   "
       +
       +int dirorder    = 0; /* Set to 1 to sort by directory first */
       +int mtimeorder  = 0; /* Set to 1 to sort by time modified */
       +int icaseorder  = 0; /* Set to 1 to sort by ignoring case */
       +int versorder   = 0; /* Set to 1 to sort by version number */
       +int idletimeout = 0; /* Screensaver timeout in seconds, 0 to disable */
       +int showhidden  = 0; /* Set to 1 to show hidden files by default */
       +int usecolor    = 0; /* Set to 1 to enable color attributes */
       +char *idlecmd   = "rain"; /* The screensaver program */
       +
       +/* See curs_attr(3) for valid video attributes */
       +#define CURSR_ATTR A_NORMAL
       +#define DIR_ATTR   A_NORMAL | COLOR_PAIR(4)
       +#define LINK_ATTR  A_NORMAL | COLOR_PAIR(6)
       +#define SOCK_ATTR  A_NORMAL | COLOR_PAIR(1)
       +#define FIFO_ATTR  A_NORMAL | COLOR_PAIR(5)
       +#define EXEC_ATTR  A_NORMAL | COLOR_PAIR(2)
       +
       +/* Colors to use with COLOR_PAIR(n) as attributes */
       +struct cpair pairs[] = {
       +        { .fg = 0, .bg = 0 },
       +        /* pairs start at 1 */
       +        { COLOR_RED,     -1 },
       +        { COLOR_GREEN,   -1 },
       +        { COLOR_YELLOW,  -1 },
       +        { COLOR_BLUE,    -1 },
       +        { COLOR_MAGENTA, -1 },
       +        { COLOR_CYAN,    -1 },
       +};
       +
       +struct key bindings[] = {
       +        /* Quit */
       +        { 'q',            SEL_QUIT },
       +        /* Back */
       +        { KEY_BACKSPACE,  SEL_BACK },
       +        { KEY_LEFT,       SEL_BACK },
       +        { 'h',            SEL_BACK },
       +        { CONTROL('H'),   SEL_BACK },
       +        /* Inside */
       +        { KEY_ENTER,      SEL_GOIN },
       +        { '\r',           SEL_GOIN },
       +        { KEY_RIGHT,      SEL_GOIN },
       +        { 'l',            SEL_GOIN },
       +        /* Filter */
       +        { '/',            SEL_FLTR },
       +        { '&',            SEL_FLTR },
       +        /* Next */
       +        { 'j',            SEL_NEXT },
       +        { KEY_DOWN,       SEL_NEXT },
       +        { CONTROL('N'),   SEL_NEXT },
       +        /* Previous */
       +        { 'k',            SEL_PREV },
       +        { KEY_UP,         SEL_PREV },
       +        { CONTROL('P'),   SEL_PREV },
       +        /* Page down */
       +        { KEY_NPAGE,      SEL_PGDN },
       +        { CONTROL('D'),   SEL_PGDN },
       +        /* Page up */
       +        { KEY_PPAGE,      SEL_PGUP },
       +        { CONTROL('U'),   SEL_PGUP },
       +        /* Home */
       +        { KEY_HOME,       SEL_HOME },
       +        { META('<'),      SEL_HOME },
       +        { '^',            SEL_HOME },
       +        /* End */
       +        { KEY_END,        SEL_END },
       +        { META('>'),      SEL_END },
       +        { '$',            SEL_END },
       +        /* Change dir */
       +        { 'c',            SEL_CD },
       +        { '~',            SEL_CDHOME },
       +        /* Toggle hide .dot files */
       +        { '.',            SEL_TOGGLEDOT },
       +        /* Toggle sort by directory first */
       +        { 'd',            SEL_DSORT },
       +        /* Toggle sort by time */
       +        { 't',            SEL_MTIME },
       +        /* Toggle case sensitivity */
       +        { 'i',            SEL_ICASE },
       +        /* Toggle sort by version number */
       +        { 'v',            SEL_VERS },
       +        { CONTROL('L'),   SEL_REDRAW },
       +        /* Run command */
       +        { 'z',            SEL_RUN, "top" },
       +        { '!',            SEL_RUN, "sh", "SHELL" },
       +        /* Run command with argument */
       +        { 'e',            SEL_RUNARG, "vi", "EDITOR" },
       +        { 'p',            SEL_RUNARG, "less", "PAGER" },
       +};
 (DIR) diff --git a/nopen.1 b/nopen.1
       @@ -0,0 +1,31 @@
       +.Dd August 2, 2019
       +.Dt NOPEN 1
       +.Os
       +.Sh NAME
       +.Nm nopen
       +.Nd opens a file in the user's preferred application
       +.Sh SYNOPSIS
       +.Nm
       +.Ar file...
       +.Sh DESCRIPTION
       +.Nm
       +opens the files provided as arguments with the user's proferred
       +application.
       +.Pp
       +The file associations are specified by regexes
       +matching on the currently selected filename.
       +If a match is found the associated program is executed
       +with the filename passed in as the argument.
       +If no match is found the program
       +.Xr less 1
       +is invoked.
       +.Sh CONFIGURATION
       +.Nm
       +is configured by modifying
       +.Pa nopenconf.h
       +and recompiling the code.
       +.Sh SEE ALSO
       +.Xr noice 1
       +.Sh AUTHORS
       +.An Lazaros Koromilas Aq Mt lostd@2f30.org ,
       +.An Dimitris Papastamos Aq Mt sin@2f30.org .
 (DIR) diff --git a/nopen.c b/nopen.c
       @@ -0,0 +1,95 @@
       +/* See LICENSE file for copyright and license details. */
       +#include <sys/types.h>
       +#include <sys/wait.h>
       +
       +#include <err.h>
       +#include <regex.h>
       +#include <stdio.h>
       +#include <stdlib.h>
       +#include <string.h>
       +#include <unistd.h>
       +
       +#include "util.h"
       +
       +struct assoc {
       +        char *regex; /* Regex to match on filename */
       +        char *file;
       +        char *argv[NR_ARGS];
       +        regex_t regcomp;
       +};
       +
       +#include "nopenconf.h"
       +
       +void
       +spawnassoc(struct assoc *assoc, char *arg)
       +{
       +        char *argv[NR_ARGS];
       +        int i;
       +
       +        for (i = 0; assoc->argv[i]; i++) {
       +                if (strcmp(assoc->argv[i], "{}") == 0) {
       +                        argv[i] = arg;
       +                        continue;
       +                }
       +                argv[i] = assoc->argv[i];
       +        }
       +        argv[i] = NULL;
       +        spawnvp(NULL, assoc->file, argv);
       +}
       +
       +struct assoc *
       +openwith(char *file)
       +{
       +        int i;
       +
       +        for (i = 0; i < LEN(assocs); i++) {
       +                if (regexec(&assocs[i].regcomp, file, 0, NULL, 0) == 0)
       +                        return &assocs[i];
       +        }
       +
       +        return NULL;
       +}
       +
       +void
       +initassocs(void)
       +{
       +        char errbuf[256];
       +        int i, r;
       +
       +        for (i = 0; i < LEN(assocs); i++) {
       +                r = regcomp(&assocs[i].regcomp, assocs[i].regex,
       +                            REG_NOSUB | REG_EXTENDED | REG_ICASE);
       +                if (r != 0) {
       +                        regerror(r, &assocs[i].regcomp, errbuf, sizeof(errbuf));
       +                        fprintf(stderr, "invalid regex assocs[%d]: %s: %s\n",
       +                                i, assocs[i].regex, errbuf);
       +                        exit(1);
       +                }
       +        }
       +}
       +
       +void
       +usage(char *argv0)
       +{
       +        fprintf(stderr, "usage: %s file...\n", argv0);
       +        exit(1);
       +}
       +
       +int
       +main(int argc, char *argv[])
       +{
       +        if (argc == 1)
       +                usage(argv[0]);
       +        argc--;
       +        argv++;
       +        initassocs();
       +        for (; *argv != NULL; argv++) {
       +                struct assoc *assoc;
       +
       +                assoc = openwith(argv[0]);
       +                if (assoc == NULL)
       +                        continue;
       +                spawnassoc(assoc, argv[0]);
       +        }
       +        return 0;
       +}
 (DIR) diff --git a/nopenconf.def.h b/nopenconf.def.h
       @@ -0,0 +1,9 @@
       +/* See LICENSE file for copyright and license details. */
       +struct assoc assocs[] = {
       +        { .regex = "\\.(avi|mp4|mkv|mp3|ogg|flac|mov)$", .file = "mpv", .argv = { "mpv", "{}", NULL } },
       +        { .regex = "\\.(png|jpg|gif)$", .file = "sxiv", .argv = { "sxiv", "{}", NULL} },
       +        { .regex = "\\.(html|svg)$", .file = "firefox", .argv = { "firefox", "{}", NULL } },
       +        { .regex = "\\.pdf$", .file = "mupdf", .argv = { "mupdf", "{}", NULL} },
       +        { .regex = "\\.sh$", .file = "sh", .argv = { "sh", "{}", NULL} },
       +        { .regex = ".", .file = "less", .argv = { "less", "{}", NULL } },
       +};
 (DIR) diff --git a/spawn.c b/spawn.c
       @@ -0,0 +1,43 @@
       +/* See LICENSE file for copyright and license details. */
       +#include <sys/types.h>
       +#include <sys/wait.h>
       +
       +#include <stdarg.h>
       +#include <unistd.h>
       +
       +#include "util.h"
       +
       +void
       +spawnvp(char *dir, char *file, char *argv[])
       +{
       +        pid_t pid;
       +        int status;
       +
       +        pid = fork();
       +        if (pid == 0) {
       +                if (dir != NULL)
       +                        chdir(dir);
       +                execvp(file, argv);
       +                _exit(1);
       +        } else {
       +                /* Ignore interruptions */
       +                while (waitpid(pid, &status, 0) == -1)
       +                        ;
       +        }
       +}
       +
       +void
       +spawnlp(char *dir, char *file, char *argv0, ...)
       +{
       +        char *argv[NR_ARGS];
       +        va_list ap;
       +        int argc;
       +
       +        va_start(ap, argv0);
       +        argv[0] = argv0;
       +        for (argc = 1; argv[argc] = va_arg(ap, char *); argc++)
       +                ;
       +        argv[argc] = NULL;
       +        va_end(ap);
       +        spawnvp(dir, file, argv);
       +}
 (DIR) diff --git a/strverscmp.c b/strverscmp.c
       @@ -1,6 +1,8 @@
       +/* See LICENSE file for copyright and license details. */
        #include <ctype.h>
        #include <stdlib.h>
        #include <string.h>
       +
        #include "util.h"
        
        int
 (DIR) diff --git a/util.h b/util.h
       @@ -1,12 +1,6 @@
        /* See LICENSE file for copyright and license details. */
       -#undef strlcat
       -size_t strlcat(char *, const char *, size_t);
       -#undef strlcpy
       -size_t strlcpy(char *, const char *, size_t);
       -#undef dprintf
       -int dprintf(int, const char *, ...);
       -#undef strverscmp
       -int strverscmp(const char *, const char *);
       +#define LEN(x) (sizeof(x) / sizeof(*(x)))
       +#define NR_ARGS        32
        
        #ifdef DEBUG
        #define DEBUG_FD 8
       @@ -22,3 +16,14 @@ int strverscmp(const char *, const char *);
        #define DPRINTF_P(x)
        #define DPRINTF_LLU(x)
        #endif /* DEBUG */
       +
       +#undef strlcat
       +size_t strlcat(char *, const char *, size_t);
       +#undef strlcpy
       +size_t strlcpy(char *, const char *, size_t);
       +#undef dprintf
       +int dprintf(int, const char *, ...);
       +
       +int strverscmp(const char *, const char *);
       +void spawnvp(char *, char *, char *[]);
       +void spawnlp(char *, char *, char *, ...);