~metalune/dmenu

bb4424df072332243890714b055e978a5c70adf3 — Connor Lane Smith 9 years ago 8ac44eb
replace lsx with stest
7 files changed, 189 insertions(+), 69 deletions(-)

M Makefile
M config.mk
M dmenu_run
D lsx.1
D lsx.c
A stest.1
A stest.c
M Makefile => Makefile +12 -12
@@ 3,10 3,10 @@

include config.mk

SRC = dmenu.c draw.c lsx.c
SRC = dmenu.c draw.c stest.c
OBJ = ${SRC:.c=.o}

all: options dmenu lsx
all: options dmenu stest

options:
	@echo dmenu build options:


@@ 24,18 24,18 @@ dmenu: dmenu.o draw.o
	@echo CC -o $@
	@${CC} -o $@ dmenu.o draw.o ${LDFLAGS}

lsx: lsx.o
stest: stest.o
	@echo CC -o $@
	@${CC} -o $@ lsx.o ${LDFLAGS}
	@${CC} -o $@ stest.o ${LDFLAGS}

clean:
	@echo cleaning
	@rm -f dmenu lsx ${OBJ} dmenu-${VERSION}.tar.gz
	@rm -f dmenu stest ${OBJ} dmenu-${VERSION}.tar.gz

dist: clean
	@echo creating dist tarball
	@mkdir -p dmenu-${VERSION}
	@cp LICENSE Makefile README config.mk dmenu.1 draw.h dmenu_run lsx.1 ${SRC} dmenu-${VERSION}
	@cp LICENSE Makefile README config.mk dmenu.1 draw.h dmenu_run stest.1 ${SRC} dmenu-${VERSION}
	@tar -cf dmenu-${VERSION}.tar dmenu-${VERSION}
	@gzip dmenu-${VERSION}.tar
	@rm -rf dmenu-${VERSION}


@@ 43,24 43,24 @@ dist: clean
install: all
	@echo installing executables to ${DESTDIR}${PREFIX}/bin
	@mkdir -p ${DESTDIR}${PREFIX}/bin
	@cp -f dmenu dmenu_run lsx ${DESTDIR}${PREFIX}/bin
	@cp -f dmenu dmenu_run stest ${DESTDIR}${PREFIX}/bin
	@chmod 755 ${DESTDIR}${PREFIX}/bin/dmenu
	@chmod 755 ${DESTDIR}${PREFIX}/bin/dmenu_run
	@chmod 755 ${DESTDIR}${PREFIX}/bin/lsx
	@chmod 755 ${DESTDIR}${PREFIX}/bin/stest
	@echo installing manual pages to ${DESTDIR}${MANPREFIX}/man1
	@mkdir -p ${DESTDIR}${MANPREFIX}/man1
	@sed "s/VERSION/${VERSION}/g" < dmenu.1 > ${DESTDIR}${MANPREFIX}/man1/dmenu.1
	@sed "s/VERSION/${VERSION}/g" < lsx.1 > ${DESTDIR}${MANPREFIX}/man1/lsx.1
	@sed "s/VERSION/${VERSION}/g" < stest.1 > ${DESTDIR}${MANPREFIX}/man1/stest.1
	@chmod 644 ${DESTDIR}${MANPREFIX}/man1/dmenu.1
	@chmod 644 ${DESTDIR}${MANPREFIX}/man1/lsx.1
	@chmod 644 ${DESTDIR}${MANPREFIX}/man1/stest.1

uninstall:
	@echo removing executables from ${DESTDIR}${PREFIX}/bin
	@rm -f ${DESTDIR}${PREFIX}/bin/dmenu
	@rm -f ${DESTDIR}${PREFIX}/bin/dmenu_run
	@rm -f ${DESTDIR}${PREFIX}/bin/lsx
	@rm -f ${DESTDIR}${PREFIX}/bin/stest
	@echo removing manual page from ${DESTDIR}${MANPREFIX}/man1
	@rm -f ${DESTDIR}${MANPREFIX}/man1/dmenu.1
	@rm -f ${DESTDIR}${MANPREFIX}/man1/lsx.1
	@rm -f ${DESTDIR}${MANPREFIX}/man1/stest.1

.PHONY: all options clean dist install uninstall

M config.mk => config.mk +1 -1
@@ 17,7 17,7 @@ INCS = -I${X11INC}
LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS}

# flags
CPPFLAGS = -D_BSD_SOURCE -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS}
CPPFLAGS = -D_BSD_SOURCE -D_POSIX_C_SOURCE=2 -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS}
CFLAGS   = -ansi -pedantic -Wall -Os ${INCS} ${CPPFLAGS}
LDFLAGS  = -s ${LIBS}


M dmenu_run => dmenu_run +4 -2
@@ 5,8 5,10 @@ if [ ! -d "`dirname "$CACHE"`" ]; then
fi
(
	IFS=:
	if [ "`ls -dt $PATH "$CACHE" | head -n 1`" != "$CACHE" ]; then
		lsx $PATH | sort -u > "$CACHE"
	if ls -d $PATH | stest -q -n "$CACHE"; then
		for dir in $PATH; do
			ls $dir | stest -C $dir -fx
		done | sort -u > "$CACHE"
	fi
)
cmd=`dmenu "$@" < "$CACHE"` && exec sh -c "$cmd"

D lsx.1 => lsx.1 +0 -11
@@ 1,11 0,0 @@
.TH LSX 1 dmenu\-VERSION
.SH NAME
lsx \- list executables
.SH SYNOPSIS
.B lsx
.RI [ directory ...]
.SH DESCRIPTION
.B lsx
lists the executables in each
.IR directory .
If none are given the current working directory is used.

D lsx.c => lsx.c +0 -43
@@ 1,43 0,0 @@
/* See LICENSE file for copyright and license details. */
#include <dirent.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>

static void lsx(const char *dir);

static int status = EXIT_SUCCESS;

int
main(int argc, char *argv[]) {
	int i;

	if(argc < 2)
		lsx(".");
	else for(i = 1; i < argc; i++)
		lsx(argv[i]);
	return status;
}

void
lsx(const char *dir) {
	char buf[PATH_MAX];
	struct dirent *d;
	struct stat st;
	DIR *dp;

	for(dp = opendir(dir); dp && (d = readdir(dp)); errno = 0)
		if(snprintf(buf, sizeof buf, "%s/%s", dir, d->d_name) < (int)sizeof buf
		&& access(buf, X_OK) == 0 && stat(buf, &st) == 0 && S_ISREG(st.st_mode))
			puts(d->d_name);

	if(errno != 0) {
		status = EXIT_FAILURE;
		perror(dir);
	}
	if(dp)
		closedir(dp);
}

A stest.1 => stest.1 +87 -0
@@ 0,0 1,87 @@
.TH STEST 1 dmenu\-VERSION
.SH NAME
stest \- filter a list of files by properties
.SH SYNOPSIS
.B stest
.RB [ -bcdefghpqrsuwx ]
.RB [ -C
.IR dir ]
.RB [ -n
.IR file ]
.RB [ -o
.IR file ]
.RI [ file ...]
.SH DESCRIPTION
.B stest
takes a list of files and filters by the files' properties, analogous to
.IR test (1).
Files which pass all tests are printed to stdout. If no files are given as
arguments, stest will read a list of files from stdin, one path per line.
.SH OPTIONS
.TP
.BI \-C " dir"
Tests files relative to directory
.IR dir .
.TP
.B \-b
Test that files are block specials.
.TP
.B \-c
Test that files are character specials.
.TP
.B \-d
Test that files are directories.
.TP
.B \-e
Test that files exist.
.TP
.B \-f
Test that files are regular files.
.TP
.B \-g
Test that files have their set-group-ID flag set.
.TP
.B \-h
Test that files are symbolic links.
.TP
.BI \-n " file"
Test that files are newer than
.IR file .
.TP
.BI \-o " file"
Test that files are older than
.IR file .
.TP
.B \-p
Test that files are named pipes.
.TP
.B \-q
No files are printed, only the exit status is returned.
.TP
.B \-r
Test that files are readable.
.TP
.B \-s
Test that files are not empty.
.TP
.B \-u
Test that files have their set-user-ID flag set.
.TP
.B \-w
Test that files are writable.
.TP
.B \-x
Test that files are executable.
.SH EXIT STATUS
.TP
.B 0
At least one file passed all tests.
.TP
.B 1
No files passed all tests.
.TP
.B 2
An error occurred.
.SH SEE ALSO
.IR dmenu (1),
.IR test (1)

A stest.c => stest.c +85 -0
@@ 0,0 1,85 @@
/* See LICENSE file for copyright and license details. */
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>

#define OPER(x)  (oper[(x)-'a'])

static bool test(const char *);

static bool quiet = false;
static bool oper[26];
static struct stat old, new;

int
main(int argc, char *argv[]) {
	char buf[BUFSIZ], *p;
	bool match = false;
	int opt;

	while((opt = getopt(argc, argv, "C:bcdefghn:o:pqrsuwx")) != -1)
		switch(opt) {
		case 'C': /* tests relative to directory */
			if(chdir(optarg) == -1) {
				perror(optarg);
				exit(2);
			}
			break;
		case 'n': /* newer than file */
		case 'o': /* older than file */
			if(!(OPER(opt) = stat(optarg, (opt == 'n' ? &new : &old)) == 0))
				perror(optarg);
			break;
		case 'q': /* quiet (no output, just status) */
			quiet = true;
			break;
		default:  /* miscellaneous operators */
			OPER(opt) = true;
			break;
		case '?': /* error: unknown flag */
			fprintf(stderr, "usage: %s [-bcdefghpqrsuwx] [-C dir] [-n file] [-o file] [file...]\n", argv[0]);
			exit(2);
		}
	if(optind == argc)
		while(fgets(buf, sizeof buf, stdin)) {
			if(*(p = &buf[strlen(buf)-1]) == '\n')
				*p = '\0';
			match |= test(buf);
		}
	else
		while(optind < argc)
			match |= test(argv[optind++]);

	return match ? 0 : 1;
}

bool
test(const char *path) {
	struct stat st;

	if((!OPER('b') || (stat(path, &st) == 0 && S_ISBLK(st.st_mode)))        /* block special     */
	&& (!OPER('c') || (stat(path, &st) == 0 && S_ISCHR(st.st_mode)))        /* character special */
	&& (!OPER('d') || (stat(path, &st) == 0 && S_ISDIR(st.st_mode)))        /* directory         */
	&& (!OPER('e') || (access(path, F_OK) == 0))                            /* exists            */
	&& (!OPER('f') || (stat(path, &st) == 0 && S_ISREG(st.st_mode)))        /* regular file      */
	&& (!OPER('g') || (stat(path, &st) == 0 && (st.st_mode & S_ISGID)))     /* set-group-id flag */
	&& (!OPER('h') || (lstat(path, &st) == 0 && S_ISLNK(st.st_mode)))       /* symbolic link     */
	&& (!OPER('n') || (stat(path, &st) == 0 && st.st_mtime > new.st_mtime)) /* newer than file   */
	&& (!OPER('o') || (stat(path, &st) == 0 && st.st_mtime < old.st_mtime)) /* older than file   */
	&& (!OPER('p') || (stat(path, &st) == 0 && S_ISFIFO(st.st_mode)))       /* named pipe        */
	&& (!OPER('r') || (access(path, R_OK) == 0))                            /* readable          */
	&& (!OPER('s') || (stat(path, &st) == 0 && st.st_size > 0))             /* not empty         */
	&& (!OPER('u') || (stat(path, &st) == 0 && (st.st_mode & S_ISUID)))     /* set-user-id flag  */
	&& (!OPER('w') || (access(path, W_OK) == 0))                            /* writable          */
	&& (!OPER('x') || (access(path, X_OK) == 0))) {                         /* executable        */
		if(quiet)
			exit(0);
		puts(path);
		return true;
	}
	else
		return false;
}