diff options
author | Krow Savcik <krow@savcik.xyz> | 2025-08-31 15:11:01 +0200 |
---|---|---|
committer | Krow Savcik <krow@savcik.xyz> | 2025-08-31 15:11:01 +0200 |
commit | 0fa7537ce5006a9c99bc7e295a4ae4ff0b920fda (patch) | |
tree | 057a91b30ab61417970c5defe825ce16a094ac39 |
-rw-r--r-- | Makefile | 34 | ||||
-rw-r--r-- | README | 60 | ||||
-rw-r--r-- | config.def.h | 2 | ||||
-rw-r--r-- | config.mk | 6 | ||||
-rw-r--r-- | debug.c | 18 | ||||
-rw-r--r-- | debug.h | 3 | ||||
-rw-r--r-- | renu.1 | 33 | ||||
-rw-r--r-- | renu.c | 387 | ||||
-rw-r--r-- | util.c | 88 | ||||
-rw-r--r-- | util.h | 3 |
10 files changed, 634 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b4990a5 --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +include config.mk + +all: renu + +config.h: + cp config.def.h $@ + +renu: renu.c config.h + ${CC} renu.c util.c debug.c -o renu + +install: renu + mkdir -p ${PREFIX}/bin + cp -f renu ${PREFIX}/bin + chmod 755 ${PREFIX}/bin/renu + mkdir -p ${MANPREFIX}/man1 + sed "s/VERSION/${VERSION}/g" < renu.1 > ${MANPREFIX}/man1/renu.1 + chmod 644 ${MANPREFIX}/man1/renu.1 + +uninstall: + rm -f ${PREFIX}/bin/renu\ + ${MANPREFIX}/man1/renu.1 + +dist: clean + mkdir -p renu-${VERSION} + cp -R Makefile README config.def.h config.mk\ + renu.c util.c util.h debug.c debug.h renu.1 renu-${VERSION} + tar -cf renu-${VERSION}.tar renu-${VERSION} + gzip renu-${VERSION}.tar + rm -rf renu-${VERSION} + +clean: + rm -f renu renu-${VERSION}.tar.gz + +.PHONY: all dist install uninstall clean @@ -0,0 +1,60 @@ +renu - recursive menu +===================== +renu uses a line selection program to choose and exectue options specified in a file. + + +Requirements +------------ +To run renu you will need a program like dmenu or rofi. + + +Installation +------------ +Enter the following command to compile and install: +```sh +make install +``` +By default, everything is installed in /usr/local. To change the +install directory, edit config.mk. + + +Writing a menu file +------------------- +renu reads a file, to determine how the menu(s) should be constructed +and what should they do. Each line represents an option: + +- Menu (m name) + This line represents the start of a menu. All the lines that follow, + until another menu line is found, represent the entries of this menu. + The default name the program will open is 'def'. + +- Empty line and Comment (# ...) + If the line starts with a # or is empty, it is ignored and regarded as + a comment. + +- Submenu (s entry name) + The submenu line will open the menu with the specified name, once + selected. Entry is the displayed name. The line takes two arguments. + +- Print (p entry [value]) + This option will print the value once selected. The value is an + optional argument and, if empty, entry will be printed. + +- Run (r entry command) + This line takes two arguments. The option will run the command as if + it were run with sh -c "command". + +- Arguments + Each line uses one or more arguments. The arguments are separated by + spaces, thus if you want to have spaces in the arguments, you should + either escape the space or use double quotes. You can escape a + character using backslash ('\'). Note that the double quotes ('"') and + backslashes ('\') inside the argument always have to be escaped. + +Configuartion +------------- +The settings for compiling and installing renu are in config.mk + +By default renu uses dmenu for selecting the options. To change the +program, edit the config.h file and recompile. If you don't see the +config.h file, try building renu. diff --git a/config.def.h b/config.def.h new file mode 100644 index 0000000..b636888 --- /dev/null +++ b/config.def.h @@ -0,0 +1,2 @@ +static const char *default_menu = "def"; +static const char *menu_cmd[] = {"dmenu", NULL}; diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..2472f2e --- /dev/null +++ b/config.mk @@ -0,0 +1,6 @@ +VERSION = alpha + +PREFIX = /usr/local +MANPREFIX = ${PREFIX}/share/man + +CC = cc @@ -0,0 +1,18 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> + +#include "debug.h" + +void +_error(const char *file, const int line, const char *fmt, ...) +{ + va_list args; + fprintf(stderr, "%s:%d: ", file, line); + + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); + + exit(1); +} @@ -0,0 +1,3 @@ +#define error(...) _error(__FILE__, __LINE__, __VA_ARGS__) + +void _error(const char *, const int, const char *, ...); @@ -0,0 +1,33 @@ +.TH RENU 1 renu\-VERSION +.SH NAME +renu \- recursive menu +.SH SYNOPSIS +.B renu +.RB [FILE] +.SH DESCRIPTION +Create a recursive menu described in FILE. +.PP +The FILE should have a specific format, each line representing an option. +.SS Menu (m name) +This line represents the start of a menu. +All the lines that follow, until another menu line is found, represent the entries of this menu. +The default name the program will open is 'def'. +.SS Empty line +.SS Comment (# ...) +If the line starts with a # or is empty, it is ignored and regarded as a comment. +.SS Submenu (s entry name) +The submenu line will open the menu with the specified name, once selected. +Entry is the displayed name. +The line takes two arguments. +.SS Print (p entry [value]) +This option will print the value once selected. +The value is an optional argument and, if empty, entry will be printed. +.SS Run (r entry command) +This line takes two arguments. +The option will run the command as if it were run with sh -c "command". +.SS Arguments +Each line uses one or more arguments. +The arguments are separated by spaces, thus if you want to have spaces +in the arguments, you should either escape the space or use double quotes. +You can escape a character using backslash ('\\'). +Note that the double quotes ('"') and backslashes ('\\') inside the argument always have to be escaped. @@ -0,0 +1,387 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <errno.h> + +#include "util.h" +#include "debug.h" +#include "config.h" + +#define MAXLINE 128 + +static char menu[MAXLINE]; + +void +usage() +{ + printf("remu [FILE]\n"); + exit(0); +} + +int +launch_menu(int *pfd, char *prompt) +{ + int pipefd[4]; + pid_t pid; + if (pipe(pipefd)) + return 1; + if (pipe(&pipefd[2])) { + close(pipefd[0]); + close(pipefd[1]); + return 1; + } + + pid = fork(); + + switch (pid) { + case -1: + close(pipefd[0]); + close(pipefd[1]); + close(pipefd[2]); + close(pipefd[3]); + return 1; + case 0: + break; + default: + pfd[0] = pipefd[0]; + pfd[1] = pipefd[3]; + close(pipefd[1]); + close(pipefd[2]); + return 0; + } + + /* Child process */ + close(pipefd[0]); + close(pipefd[3]); + + if (dup2(pipefd[1], STDOUT_FILENO) == -1) + exit(1); + if (dup2(pipefd[2], STDIN_FILENO) == -1) + exit(1); + + execvp(menu_cmd[0], (char **)menu_cmd); + close(pipefd[1]); + close(pipefd[2]); + exit(0); +} + +char * +nextline(char *c) +{ + while (*c != '\n') + c++; + + return ++c; +} + +char * +setline(char *p) +{ + char *end; + end = nextline(p); + end[-1] = 0; + return end; +} + +void +resetline(char *end) +{ + end[-1] = '\n'; +} + +char * +nextarg(char *c) +{ + while (*c == '\0') + c++; + + return c; +} + +char * +process_quoted(char *pnt) +{ + size_t off = 0; + char *ret = NULL; + pnt[0] = 0; + pnt++; + while (pnt[off] != '"') { + if (pnt[off] == '\0' || pnt[off] == '\n') + return NULL; + + if (pnt[off] == '\\') { + if (pnt[off+1] == '\0' || pnt[off+1] == '\n') + return NULL; + off++; + } + pnt[0] = pnt[off]; + pnt++; + } + + for (ret = &(pnt[off+1]); pnt != ret; pnt++) + pnt[0] = 0; + + return ret; +} + +char * +process_unquoted(char *pnt) +{ + size_t off = 0; + char *ret = NULL; + while (pnt[off] && pnt[off] != '\n') { + if (pnt[off] == ' ') { + ret = &pnt[off+1]; + break; + } + + if (pnt[off] == '"') + return NULL; + + if (pnt[off] == '\\') { + if (pnt[off+1] == '\0' || pnt[off+1] == '\n') + return NULL; + off++; + } + pnt[0] = pnt[off]; + pnt++; + } + + ret = ret ? ret : &pnt[off]; + + for (; pnt != ret; pnt++) + pnt[0] = 0; + + return ret; +} + +char * +process_line(char *pnt) +{ + while (*pnt && *pnt != '\n') { + if (*pnt == '"') + pnt = process_quoted(pnt); + else + pnt = process_unquoted(pnt); + + if (pnt == NULL) + return NULL; + } + + return pnt; +} + +unsigned int +preprocess(char *buf) +{ + char *pnt = buf; + unsigned int line = 0; + while (*pnt) { + line++; + if (*pnt == '#' || *pnt == '\n') { + pnt = nextline(pnt); + continue; + } + + if (pnt[1] != ' ') + return line; + + pnt[1] = 0; + pnt = process_line(pnt+2); + if (pnt == NULL) + return line; + pnt = nextline(pnt); + } + + return 0; +} + +int +submenu(char *line) +{ + char *e, *arg, *ea; + arg = nextarg(line+2); + ea = &arg[strlen(arg) - 1]; + if (*ea == '\n') + return 1; + + arg = nextarg(ea+1); + e = setline(line); + strncpy(menu, arg, MAXLINE); + menu[MAXLINE-1] = 0; + resetline(e); + return 0; +} + +int +print(char *line) +{ + char *e, *arg, *ea; + arg = nextarg(line+2); + + ea = &arg[strlen(arg) - 1]; + if (*ea != '\n' && *nextarg(ea+1) != '\n') + arg = nextarg(ea+1); + + e = setline(line); + printf("%s\n", arg); + resetline(e); + return 0; +} + +int +run(char *line) +{ + char *e, *arg, *ea; + arg = nextarg(line+2); + + ea = &arg[strlen(arg) - 1]; + if (*ea == '\n') + return 1; + + arg = nextarg(ea+1); + e = setline(line); + if (execl("/bin/sh", "sh", "-c", arg, NULL)) + error("Error executing process: %s\n", strerror(errno)); + resetline(e); + return 0; +} + +int +main(int argc, char *argv[]) +{ + char *buf, *stop, *pnt, *end, *mpnt, *line; + unsigned int i; + int pfd[2]; + FILE *f[2]; + char option[MAXLINE]; + + strcpy(menu, default_menu); + + if (argc != 2) + usage(); + + buf = f_read(argv[1]); + if (buf == NULL) { + fprintf(stderr, "Couldn't read file %s\n", argv[1]); + return 1; + } + + stop = &buf[strlen(buf)]; + if (stop == buf) { + fprintf(stderr, "Wrong format in %s: Empty file\n", argv[1]); + return 1; + } else if (stop[-1] != '\n') { + fprintf(stderr, "Wrong format in %s: Missing EOL at the end of the file\n", argv[1]); + return 1; + } + + if (i = preprocess(buf)) { + fprintf(stderr, "Wrong format in %s:%u\n", argv[1], i); + return 1; + } + + + while (1) { + for (pnt = buf; pnt != stop; pnt = nextline(pnt)) { + if (*pnt != 'm') + continue; + pnt = nextarg(pnt+1); + end = setline(pnt); + + if (strcmp(menu, pnt) == 0) + break; + resetline(end); + } + + if (pnt == stop) { + fprintf(stderr, "Couldn't find menu %s in file %s\n", menu, argv[1]); + return 1; + } + + resetline(end); + + /* Query option */ + if (launch_menu(pfd, NULL)) { + fprintf(stderr, "Couldn't open the menu\n"); + return 1; + } + + f[0] = fdopen(pfd[0], "r"); + f[1] = fdopen(pfd[1], "w"); + + if (f[0] == NULL || f[1] == NULL) { + fprintf(stderr, "Couldn't open the menu\n"); + return 1; + } + + mpnt = nextline(pnt); + for (pnt = mpnt; pnt != stop; pnt = nextline(pnt)) { + if (*pnt == 'm') + break; + + if (*pnt == '\n' || *pnt == '#') + continue; + + pnt = nextarg(pnt+1); + end = setline(pnt); + fprintf(f[1], "%s\n", pnt); + resetline(end); + } + + fclose(f[1]); + close(pfd[1]); + + if (fgets(option, MAXLINE, f[0]) == NULL) { + fprintf(stderr, "Couldn't open the menu\n"); + return 1; + } + option[strlen(option)-1] = 0; + + for (line = mpnt; line != stop; line = nextline(line)) { + if (*line == 'm') + break; + + if (*line == '\n' || *line == '#') + continue; + + pnt = nextarg(line+1); + + if (*pnt == '\n') { + fprintf(stderr, "Wrong format in %s: Not enough arguments\n", argv[1]); + return 1; + } + + end = setline(pnt); + if (strcmp(pnt, option) == 0) + break; + resetline(end); + } + + /* No option selected */ + if (*line == 'm' || line == stop) + break; + + resetline(end); + switch (line[0]) { + case 's': + if (submenu(line)) { + fprintf(stderr, "Wrong format in %s: Not enough arguments\n", argv[1]); + return 1; + } + break; + case 'p': + print(line); + return 0; + case 'r': + if (run(line)) { + fprintf(stderr, "Wrong format in %s: Not enough arguments\n", argv[1]); + return 1; + } + return 0; + default: + return 0; + } + } +} @@ -0,0 +1,88 @@ +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> + +#include "util.h" +#include "debug.h" /* Probably shouldn't have debug in utils */ + +char * +s_cpy(const char *str) +{ + if (str == NULL) + return NULL; + + char *ret = malloc(strlen(str) + 1); + if (ret == NULL) + error("Not enough memory\n"); + + strcpy(ret, str); + return ret; +} + +char * +s_con(const char *str, ...) +{ + size_t len; + va_list args; + const char *ar; + char *ret, *p; + + len = strlen(str) + 1; + va_start(args, str); + while (ar = va_arg(args, const char*)) + len += strlen(ar); + va_end(args); + + ret = malloc(len); + if (ret == NULL) + error("Not enough memory\n"); + p = stpcpy(ret, str); + + va_start(args, str); + while (ar = va_arg(args, const char*)) + p = stpcpy(p, ar); + va_end(args); + + return ret; +} + +char * +f_read(const char *path) +{ + FILE *f; + char *buf; + long bsize; + buf = NULL; + if ((f = fopen(path, "r")) == NULL) + goto fread_deffer; + + if (fseek(f, 0L, SEEK_END)) + goto fread_deffer; + + if ((bsize = ftell(f)) == -1) + goto fread_deffer; + + buf = calloc(sizeof(*buf), bsize + 1); + + if (buf == NULL) + goto fread_deffer; + + if (fseek(f, 0L, SEEK_SET)) + goto fread_deffer; + + bsize = (long) fread(buf, sizeof(*buf), bsize, f); + if (ferror(f) != 0) + goto fread_deffer; + + buf[bsize] = '\0'; + fclose(f); + + return buf; +fread_deffer: + if (f) + fclose(f); + if (buf) + free(buf); + return NULL; +} @@ -0,0 +1,3 @@ +char * s_cpy(const char *); +char * s_con(const char *, ...); +char * f_read(const char *); |