From 6df8d7c673c774a2e663f16b62aea3537c62a7ac Mon Sep 17 00:00:00 2001 From: Laurent MAZET Date: Mon, 23 Dec 2024 17:39:23 +0100 Subject: [PATCH] initial commit --- .gitignore | 8 ++ debug.c | 5 ++ debug.h | 21 +++++ display.c | 243 +++++++++++++++++++++++++++++++++++++++++++++++++++++ display.h | 17 ++++ fm.c | 149 ++++++++++++++++++++++++++++++++ function.c | 56 ++++++++++++ function.h | 20 +++++ makefile | 190 +++++++++++++++++++++++++++++++++++++++++ type.h | 31 +++++++ 10 files changed, 740 insertions(+) create mode 100644 .gitignore create mode 100644 debug.c create mode 100644 debug.h create mode 100644 display.c create mode 100644 display.h create mode 100644 fm.c create mode 100644 function.c create mode 100644 function.h create mode 100644 makefile create mode 100644 type.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..68f55a9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +*.d +*.exe +*.gcda +*.gcno +*.gcov +*.ld +*.o +*.log diff --git a/debug.c b/debug.c new file mode 100644 index 0000000..b9eba44 --- /dev/null +++ b/debug.c @@ -0,0 +1,5 @@ +#include "debug.h" + +int verbose = 0; + +/* vim: set ts=4 sw=4 et: */ diff --git a/debug.h b/debug.h new file mode 100644 index 0000000..32dc0ca --- /dev/null +++ b/debug.h @@ -0,0 +1,21 @@ +#ifndef __DEBUG_H__ +#define __DEBUG_H__ + +/* constants */ + +#define DEBUG 3 +#define INFO 2 +#define WARNING 1 +#define ERROR 0 + +/* macros */ + +#define VERBOSE(level, statement...) do { if (level <= verbose) { statement; } } while(0) + +/* gobal variables */ + +extern int verbose; + +#endif /* __DEBUG_H__ */ + +/* vim: set ts=4 sw=4 et: */ diff --git a/display.c b/display.c new file mode 100644 index 0000000..fdbc5e5 --- /dev/null +++ b/display.c @@ -0,0 +1,243 @@ +#include +#include +#include +#include + +#include "debug.h" +#include "function.h" + +#include "display.h" + +typedef enum { + white = 1, + red, + green, + blue, + cyan, + magenta, + yellow, + bred, + bgreen, + bblue, + bcyan, + bmagenta, + byellow, + black, + wred, + wgreen, + wblue, + wcyan, + wmagenta, + wyellow, +} color_t; + +void set_color (color_t color) +{ + static int init = 1; + + if (init) { + init_pair (white, COLOR_WHITE, COLOR_BLACK); + init_pair (red, COLOR_RED, COLOR_BLACK); + init_pair (green, COLOR_GREEN, COLOR_BLACK); + init_pair (blue, COLOR_BLUE, COLOR_BLACK); + init_pair (magenta, COLOR_MAGENTA, COLOR_BLACK); + init_pair (yellow, COLOR_YELLOW, COLOR_BLACK); + init_pair (cyan, COLOR_CYAN, COLOR_BLACK); + init_pair (bred, COLOR_BLACK, COLOR_RED); + init_pair (bgreen, COLOR_BLACK, COLOR_GREEN); + init_pair (bblue, COLOR_BLACK, COLOR_BLUE); + init_pair (bmagenta, COLOR_BLACK, COLOR_MAGENTA); + init_pair (byellow, COLOR_BLACK, COLOR_YELLOW); + init_pair (bcyan, COLOR_BLACK, COLOR_CYAN); + init_pair (black, COLOR_BLACK, COLOR_WHITE); + init_pair (wred, COLOR_WHITE, COLOR_RED); + init_pair (wgreen, COLOR_WHITE, COLOR_GREEN); + init_pair (wblue, COLOR_WHITE, COLOR_BLUE); + init_pair (wcyan, COLOR_WHITE, COLOR_CYAN); + init_pair (wmagenta, COLOR_WHITE, COLOR_MAGENTA); + init_pair (wyellow, COLOR_WHITE, COLOR_YELLOW); + init = 0; + } + + attrset (COLOR_PAIR(color)); +} + +int _helpwindow (char *msg, int xoffset, int yoffset, int length) +{ + int i = 0; + int j = 0; + while ((msg) && (*msg != '\0')) { + if ((*msg == '\n') || (i == length)) { + i = 0; + j++; + } + if (*msg != '\n') { + mvaddch (yoffset + j, xoffset + i, *msg); + i++; + } + msg++; + } + return j; +} + +void _displaytitle (char *title, int xoffset, int yoffset) +{ + int i; + for (i = 0; title[i] != '\0'; i++) { + mvaddch (yoffset, xoffset + i, title[i]); + mvaddch (yoffset + 1, xoffset + i, ACS_HLINE); + } +} + +void _dobound (int xsize, int ysize, int xoffset, int yoffset) +{ + int i, j; + + for (i = 0; i < xsize; i++) { + mvaddch (yoffset - 1, xoffset + i, ACS_HLINE); + mvaddch (yoffset + ysize, xoffset + i, ACS_HLINE); + } + for (j = 0; j < ysize; j++) { + mvaddch (yoffset + j, xoffset - 1, ACS_VLINE); + mvaddch (yoffset + j, xoffset + xsize, ACS_VLINE); + } + mvaddch (yoffset - 1, xoffset - 1, ACS_ULCORNER); + mvaddch (yoffset + ysize, xoffset - 1, ACS_LLCORNER); + mvaddch (yoffset - 1, xoffset + xsize, ACS_URCORNER); + mvaddch (yoffset + ysize, xoffset + xsize, ACS_LRCORNER); +} + +int helpwindow (char *msg, int xoffset, int yoffset) +{ + _displaytitle ("Help message", xoffset, yoffset); + int length = strmaxlen (msg, '\n'); + int j = 2; + j += _helpwindow (msg, xoffset, yoffset + j, length); + + return j; +} + +char *getwindow (int length, int xoffset, int yoffset) +{ + char *name = (char *) calloc (1, length + 1); + CHECKALLOC (name); + memset (name, ' ', length); + + set_color (black); + _dobound (length, 1, xoffset, yoffset); + set_color (white); + + int i = 0, j; + int stop = 0; + while (!stop) { + for (j = 0; j < length; j++) { + set_color ((j == i) ? yellow : black); + mvaddch (yoffset, xoffset + j, name[j]); + set_color (white); + } + int ch = getch (); + switch (ch) { + case '\n': + case '\r': + stop = 1; + break; + case KEY_BACKSPACE: + case KEY_DELETE: + case 127: + case '\b': + name[i] = ' '; + i--; + break; + case KEY_LEFT: + i--; + break; + case KEY_RIGHT: + i++; + break; + case KEY_ESC: + free (name); + name = NULL; + stop = 1; + break; + default: + if ((ch >= 32) && ( ch < 128)) { + name[i] = ch; + i++; + } + } + + if (i < 0) { + i = 0; + } + if (i >= length) { + i = length - 1; + } + } + + if (name) { + for (j = length - 1; j >= 0; j--) { + if (name[j] == ' ') { + name[j] = '\0'; + } + } + if (*name == '\0') { + free (name); + name = NULL; + } + } + + return name; +} + +void msgwindow (char *msg, int xoffset, int yoffset, int length) +{ + set_color (black); + _dobound ((length > 0) ? length : (int)strlen (msg), 1, xoffset, yoffset); + set_color (white); + mvaddstr (yoffset, xoffset + ((length > 0) ? (length - (int)strlen (msg)) / 2 : 0), msg); +} + +int askwindow (char *msg, int xoffset, int yoffset, char *ok, char *ko) +{ + size_t i; + + msgwindow (msg, xoffset, yoffset, 0); + + int stop = 0; + while (!stop) { + int ch = getch (); + + for (i = 0; i < strlen (ok); i++) { + if (ch == ok[i]) { + stop = 1; + break; + } + } + + for (i = 0; i < strlen (ko); i++) { + if (ch == ko[i]) { + stop = -1; + break; + } + } + + switch (ch) { + case ' ': + case '\n': + case '\r': + stop = 1; + break; + case KEY_BACKSPACE: + case KEY_DELETE: + case 127: + case '\b': + case KEY_ESC: + stop = -1; + break; + } + } + + return stop; +} + +/* vim: set ts=4 sw=4 et: */ diff --git a/display.h b/display.h new file mode 100644 index 0000000..07f7102 --- /dev/null +++ b/display.h @@ -0,0 +1,17 @@ +#ifndef __DISPLAY_H__ +#define __DISPLAY_H__ + +#define KEY_ESC 0x1b +#define KEY_DELETE 0x014a + +int helpwindow (char *msg, int xoffset, int yoffset); + +char *getwindow (int length, int xoffset, int yoffset); + +void msgwindow (char *msg, int xoffset, int yoffset, int length); + +int askwindow (char *msg, int xoffset, int yoffset, char *ok, char *ko); + +#endif /* __DISPLAY_H__ */ + +/* vim: set ts=4 sw=4 et: */ diff --git a/fm.c b/fm.c new file mode 100644 index 0000000..5bdd955 --- /dev/null +++ b/fm.c @@ -0,0 +1,149 @@ +/* depend: */ +/* cflags: */ +/* linker: display.o function.o -lcurses */ +/* doslnk: display.o function.o -lpdc~1 */ +/* winlnk: display.o function.o -lpdcurses */ + +#include +#include +#include + +#include "debug.h" +#include "display.h" +#include "function.h" + +/* static variables */ +char *progname = NULL; +char *version = "0.1"; + +int wide = 0; + +char *help = + " Move up\n" + " Move left\n" + " Move down\n" + " Move right\n" + " Quit\n" + ; + +int usage (int ret) +{ + FILE *fd = ret ? stderr : stdout; + fprintf (fd, "usage: %s [-h] [-w]\n", progname); + fprintf (fd, " -h: help message\n"); + fprintf (fd, " -w: wide board (%d)\n", wide); + fprintf (fd, "%s version %s\n", progname, version); + + return ret; +} + +/* main function */ +int main (int argc, char *argv[]) +{ + + /* get basename */ + char *pt = progname = argv[0]; + while (*pt) { + if ((*pt == '/') || (*pt == '\\')) { + progname = pt + 1; + } + pt++; + } + + /* process argument */ + while (argc-- > 1) { + char *arg = *(++argv); + if (arg[0] != '-') { + VERBOSE (ERROR, fprintf (stderr, "%s: invalid option -- %s\n", progname, arg)); + return usage (1); + } + char c = arg[1]; + switch (c) { + case 'v': + arg = (arg[2]) ? arg + 2 : (--argc > 0) ? *(++argv) : NULL; + if (arg == NULL) { + VERBOSE (ERROR, fprintf (stderr, "%s: missing verbose level\n", progname)); + return usage (1); + } + verbose = atoi (arg); + break; + case 'w': + wide = 1; + break; + case 'h': + default: + return usage (c != 'h'); + } + } + + /* init curses window */ + initscr (); + noecho (); + cbreak (); + nonl (); + keypad (stdscr, TRUE); + curs_set (0); + start_color (); + + /* event loop */ + int stop = 0; + while (!stop) { + char *ptr = NULL; + + boardwindow (current); + + int ch = getch (); + switch (ch) { + case KEY_UP: + case 'i': + dir = 0; + break; + case KEY_LEFT: + case 'j': + dir = 3; + break; + case KEY_DOWN: + case 'k': + dir = 2; + break; + case KEY_RIGHT: + case 'l': + dir = 1; + break; + case KEY_ESC: + case 'q': + if (askwindow (" Restart (Y/N) ", max (board->xoffset + (board->xsize - savelen) / 2, 1), board->yoffset + (board->ysize - 1) / 2, "Yy", "Nn") == 1) { + stop = 1; + } + break; + } + } + + endwin (); + + freeboard (board); + + return 0; +} + +/* test: sokoban.exe -f 2>&1 | grep 'no file' */ +/* test: sokoban.exe -f nofile.sok 2>&1 | grep "can't read file" */ +/* test: sokoban.exe -f bogus.sok 2>&1 | grep 'incorrect file' */ +/* test: sokoban.exe -h | grep usage */ +/* test: sokoban.exe -l 2>&1 | grep specified */ +/* test: sokoban.exe -l -1 | grep level: */ +/* test: sokoban.exe -l 98 2>&1 | grep defined */ +/* test: sokoban.exe -s 2>&1 | grep specified */ +/* test: sokoban.exe -s 4 2>&1 | grep incorrect */ +/* test: sokoban.exe -v 2>&1 | grep missing */ +/* test: sokoban.exe _ 2>&1 | grep invalid */ +/* test: { sleep 1; echo -n k; sleep 1; echo -n q; } | sokoban.exe -f test.sok -s 0 */ +/* test: { sleep 1; echo -n k; sleep 1; echo -n q; } | sokoban.exe -f test.sok -s 1 */ +/* test: { sleep 1; echo -n k; sleep 1; echo -n q; } | sokoban.exe -f test.sok -s 2 */ +/* test: { sleep 1; echo -n k; sleep 1; echo -n q; } | sokoban.exe -f test.sok -s 3 */ +/* test: { sleep 1; echo -n k; sleep 1; echo -ne 'a.sok\e'; sleep 1; echo -e 'sab\b.sok'; sleep 1; echo q; } | sokoban.exe -v 3 -f test.sok */ +/* test: { sleep 1; echo s; sleep 1; echo q; } | sokoban.exe -f a.sok && rm a.sok && test \! -f b.sok */ +/* test: { sleep 1; echo -n kkklll; sleep 1; echo -n jjjiiillkk; sleep 1; echo -n iijjkkkll; sleep 3; echo -ne '\nq'; } | sokoban.exe -f test.sok -s 3 */ +/* test: { sleep 1; echo -n kkklll; sleep 1; echo -n jjjiiillkk; sleep 1; echo -n iijjkkkll; sleep 1; echo; echo -n ijjjjjjjj; sleep 1; echo -n r; sleep 1; echo -n y; sleep 1; echo -n illl; sleep 1; echo -n r; sleep 1; echo -n n; sleep 1; echo -n r; sleep 1; echo -en '\e'; sleep 1; echo -n q; sleep 1; } | sokoban.exe */ +/* test: for l in `seq 1 97`; do { sleep 1; echo -n q; } | sokoban.exe -l $l ; done */ +/* vim: set ts=4 sw=4 et: */ diff --git a/function.c b/function.c new file mode 100644 index 0000000..a99ce68 --- /dev/null +++ b/function.c @@ -0,0 +1,56 @@ +#include +#include +#include + +#include "debug.h" +#include "type.h" + +#include "function.h" + +int strmaxlen (char *str, char ch) +{ + int len = 0; + char *end = NULL; + while ((end = strchr (str, ch)) != NULL) { + int l = (int)(end - str); + if (l > len) { + len = l; + } + str = end + 1; + } + return len; +} + +list_t *alloclist () +{ + list_t *list = CHECKALLOC (list_t); + list->nb = 0; + list->tab = NULL; + + return list; +} + +list_t *addelement (list_t *list, char *path) +{ + +} + +list_t *exporedir (char *dirname) +{ + DIR *dir = opendir (dirname); + if (dir == NULL) { + VERBOSE (WARNING, fprintf (stderr, "can't read directory '%s'\n", dirname)); + return NULL; + } + + list_t *list = alloclist (); + + struct dirent *dp = NULL; + while ((dp = readdir(dir)) != NULL) { + } + closedir(dirp); + + return list; +} + +/* vim: set ts=4 sw=4 et: */ diff --git a/function.h b/function.h new file mode 100644 index 0000000..a73de3a --- /dev/null +++ b/function.h @@ -0,0 +1,20 @@ +#ifndef __FUNCTION_H__ +#define __FUNCTION_H__ + +#define CHECKALLOC(ptr) \ + do { \ + if ((ptr) == NULL) { \ + VERBOSE (ERROR, fprintf (stderr, "can't get enough memory for '%s'\n", #ptr)); \ + exit (1); \ + } \ + } while (0) + +#define max(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a > _b ? _a : _b; }) + +#define min(a,b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; }) + +int strmaxlen (char *str, char ch); + +#endif /* __FUNCTION_H__ */ + +/* vim: set ts=4 sw=4 et: */ diff --git a/makefile b/makefile new file mode 100644 index 0000000..042962e --- /dev/null +++ b/makefile @@ -0,0 +1,190 @@ +# Default flags + +CC = gcc + +#INCLUDES = -I../debug -D__MEMORY_ALLOCATION__ +INCLUDES = +OFLAGS = -O4 -Os +#OFLAGS = -O4 -ffast-math -finline-functions +#OFLAGS = -O4 -finline-functions +#OFLAGS += -mtune=pentium3 -mmmx -msse -msse2 -m3dnow +#OFLAGS += -minline-all-stringops -fsingle-precision-constant +#OFLAGS += -malign-double +CFLAGS += -W -Wall -Wextra -g +#CFLAGS += -std=c99 -D_XOPEN_SOURCE=500 +CFLAGS += $(OFLAGS) $(INCLUDES) $(OPTIONS) +LDFLAGS += -g $(LDOPTS) $(OPTIONS) + +LDOPT = linker +MV = mv +ifneq (, $(findstring linux, $(MAKE_HOST))) +# Linux +else ifneq (, $(findstring mingw, $(MAKE_HOST))) +# Windows MinGw +#LDLIBS += -lws2_32 +LDOPT = winlnk +else ifneq (, $(findstring cygwin, $(MAKE_HOST))) +# Windows CygWin +LDOPT = winlnk +else ifneq (, $(findstring msdos, $(MAKE_HOST))) +# MSDOS +LDOPT = doslnk +MV = move +endif + +# Targets + +ALLEXE = +ALLEXE += fm + +SHELL = bash + +#MAKE = mingw32-make +MAKEFLAGS += -s +include $(wildcard .makefile) + +# Functions + +TITLE = echo -en "\033[0;1m$(strip $(1))\033[0;0m:\t" +PASS = echo -e "\033[1;32m$(strip $(1))\033[0;0m" +WARN = echo -e "\033[1;33m$(strip $(1))\033[0;0m" +FAIL = echo -e "\033[1;31m$(strip $(1))\033[0;0m" + +MKDIR = mkdir -p $(1) && chmod a+rx,go-w $(1) + +INSTALL = test -d `dirname $(2)` || $(call MKDIR, `dirname $(2)`) && cp -pa $(1) $(2) && chmod a+rX,go-w $(2) + +VALID = $(call TITLE, $(1)) && $(2) && $(call PASS, SUCCESS) || { $(call FAIL, FAILED); test; } + +GETCOMMENTS = awk '/\/\*\s*$(1):/,/\*\// { sub(/.*\/\*\s*$(1):/, ""); sub (/\s*\*\/.*/, ""); print } /\/\/\s*$(1):/ {sub (/.*\/\/\s*$(1):/, ""); print }' $(2) +#GETCOMMENTS = perl -- getcomments.pl -p='$(1):\s' -f='%' $(2) + +## Generic rules + +all: depends + $(MAKE) $(ALLEXE:%=%.exe) + +count: + wc $(wildcard *.c *.h) $(MAKEFILE_LIST) + +clean: + $(call TITLE, "Cleaning") + touch clean + rm -f clean $(wildcard *.d *.ld *.log *.o *.test *~ .exec_* gmon.out _) + $(call PASS, SUCCESS) + +depends: $(patsubst %.c, %.d, $(wildcard *.c)) $(patsubst %, %.ld, $(ALLEXE)) + +gcovs: + $(MAKE) $(addprefix gcov_,$(ALLEXE)) + +gprofs: + $(MAKE) $(addprefix gprof_,$(ALLEXE)) + +purge: clean + $(call TITLE, "Purging") + touch purge + rm -f purge $(ALLEXE:%=%.exe) + $(call PASS, SUCCESS) + +valgrinds: + $(MAKE) all + $(MAKE) $(addprefix valgrind_,$(ALLEXE)) + +wipe: purge + $(call TITLE, "Wiping") + touch wipe + rm -f wipe $(wildcard *.gcda *.gcno *.gcov *.glog) + $(call PASS, SUCCESS) + +tests: + -rm -f $(ALLEXE) + $(MAKE) all + $(MAKE) $(addprefix test_,$(ALLEXE)) + +## Main rules + +include $(wildcard *.d) +include $(wildcard *.ld) + +gcov_%: + $(MAKE) purge + $(MAKE) depends + OPTIONS="-coverage -O0" $(MAKE) ${@:gcov_%=%}.exe + $(MAKE) test_$(@:gcov_%=%) + gcov `sed -e 's/\.exe:/.c/;s/\.o/.c/g' $(@:gcov_%=%.ld)` + touch gcov + rm -f gcov $(wildcard *.gcda *.gcno) + $(MAKE) purge + grep '^ *#####' *.c.gcov || true + +gprof_%: + $(MAKE) purge + $(MAKE) depends + OPTIONS="-pg" $(MAKE) ${@:gprof_%=%}.exe + $(MAKE) ${@:gprof_%=%}.test + IFS=$$'\n'; id=1; \ + for test in `cat ${@:gprof_%=%}.test | sed 's,${@:gprof_%=%}.exe,./${@:gprof_%=%}.exe,g'`; do \ + log=${@:gprof_%=%}.prof-$$id.glog; \ + $(call TITLE, test: $$test); \ + echo $$test > $$log; \ + eval $$test >> $$log; \ + [ $$? -eq 0 ] \ + && echo -e "\033[1;32mSUCCESS\033[0;0m" \ + || echo -e "\033[1;31mFAILED\033[0;0m"; \ + [ -f gmon.out ] && { gprof ${@:gprof_%=%}.exe gmon.out >> $$log; rm gmon.out; }; \ + let id++; \ + done; + $(MAKE) purge + +%.test: %.c + $(call TITLE, "Building $@") + $(call GETCOMMENTS,test, $<) > $@ + $(call PASS, SUCCESS) + -rm -f _ + +test_%: %.test %.exe + IFS=$$'\n'; RC=0; \ + for test in `cat $< | sed 's,${<:.test=.exe},$(VALGRIND) ./${<:.test=.exe},g'`; do \ + echo "=== $$test ==="; \ + eval $$test; \ + [ $$? -eq 0 ] && echo -e "\033[1;32mSUCCESS\033[0;0m" \ + || { echo -e "\033[1;31mFAILED\033[0;0m"; RC=1; }; \ + done; \ + test "$$RC" -ne 1 + +valgrind_%: %.exe + VALGRIND="valgrind -v --leak-check=full --log-fd=3"; \ + export VALGRIND; \ + $(MAKE) $(@:valgrind_%=test_%) 3>$@.log + +%.d: %.c + $(call TITLE, "Building $@") + $(CC) $(INCLUDES) -MM $< -o $@~ + echo ${<:.c=.o}: $(shell $(call GETCOMMENTS,depends, $<)) >> $@~ + $(MV) $@~ $@ + $(call PASS, SUCCESS) + +%.ld: %.c + $(call TITLE, "Building $@") + echo ${<:.c=.exe}: $(shell $(call GETCOMMENTS,$(LDOPT), $<) | awk '{for (i=1;i<=NF;i++) if ($$(i) ~ /.o$$/) printf " %s", $$(i)}') > $@ + $(call PASS, SUCCESS) + +%.o: %.c + $(call TITLE, "Building $@") + $(CC) $(CFLAGS) $(INCLUDES) $(shell $(call GETCOMMENTS,cflags, $<)) -c $< -o $@ + $(call PASS, SUCCESS) + + +%.exe: %.o %.d + $(call TITLE, "Building $@") + $(CC) $(LDFLAGS) $< $(shell $(call GETCOMMENTS,$(LDOPT), ${<:.o=.c})) $(LDLIBS) -o $@ + $(call PASS, SUCCESS) + +## Phony + +.PHONY: all clean count depends gcovs purge tests + +## Precious + +.PRECIOUS: %.d %.o diff --git a/type.h b/type.h new file mode 100644 index 0000000..f4b8938 --- /dev/null +++ b/type.h @@ -0,0 +1,31 @@ +#ifndef __TYPE_H__ +#define __TYPE_H__ + +typedef enum { + unkn_e, + block_e, + char_e, + dir_e, + pipe_e, + symb_e, + reg_e, + socket_e +} type_e; + +typedef struct { + char *gid; + unsigned short mode; + char *name; + size_t size; + type_e type; + char *uid; +} elem_t; + +typedef struct { + int nb; + elem_t *tab; +} list_t; + +#endif /* __TYPE_H__ */ + +/* vim: set ts=4 sw=4 et: */ -- 2.30.2