From 8edacd0bb63b1c6e00e3fafaa61e28c989fa1c36 Mon Sep 17 00:00:00 2001 From: ostrich <570911+ostrich@users.noreply.github.com> Date: Sun, 22 Mar 2026 09:00:33 -0400 Subject: [PATCH 1/8] build: fix Unix config detection and build wiring --- Makefile | 9 +++++++++ femto.mk | 24 +++++++++++++++++++++++- libpbat/Makefile | 4 +++- modules/batbox/Makefile | 7 ++++--- modules/sample/Makefile | 5 +++-- pbat/Makefile | 8 +++++++- pbat/linenoise/linenoise.c | 7 ++++--- pbatize/Makefile | 2 +- scripts/pbat_start | 18 ++++++++++++++++++ tea/Makefile | 2 +- 10 files changed, 73 insertions(+), 13 deletions(-) create mode 100755 scripts/pbat_start diff --git a/Makefile b/Makefile index a6f9ceea..f404896f 100644 --- a/Makefile +++ b/Makefile @@ -53,6 +53,11 @@ VERSION = $(shell date +%Y | sed s/0//).$(shell date +%m) all: $(SUBDIRS) $(MDFILES) +libpbat: microgettext +pbat: microgettext libpbat libfasteval +pbatize tea modules: libpbat +po: pbat + $(SUBDIRS): $(MAKE) -C $@ @@ -86,6 +91,10 @@ textfiles: $(TEXTFILES) $(MDFILES) man/en_US/readme.tea : README.tpl cat $< doc.ft | sed -e s,\{doc/,\{,g > $@ +tea/tea$(EXEC_SUFFIX): tea + +$(TEXTFILES) $(MDFILES): tea/tea$(EXEC_SUFFIX) + doc.md: README.tpl cat README.tpl doc.ft > .README.tea ./tea/tea$(EXEC_SUFFIX) -e:utf-8 -o:md .README.tea .doc.md diff --git a/femto.mk b/femto.mk index 37038e2a..4e0c3c49 100644 --- a/femto.mk +++ b/femto.mk @@ -61,7 +61,29 @@ $(LIBS): echo "lib_$@ = 0" >> femto-config.mk; \ fi; -$(FUNCTIONS): +WIN32: + @echo "Looking for : $@ ..." + @case "$(HOST)" in \ + *mingw*|*cygwin*|*msys*) \ + echo "fn_$@ = 1" >> femto-config.mk; \ + echo " found";; \ + *) \ + echo "fn_$@ = 0" >> femto-config.mk; \ + echo " none";; \ + esac + +MINGW_W64: + @echo "Looking for : $@ ..." + @case "$(HOST)" in \ + *w64-mingw32*) \ + echo "fn_$@ = 1" >> femto-config.mk; \ + echo " found";; \ + *) \ + echo "fn_$@ = 0" >> femto-config.mk; \ + echo " none";; \ + esac + +$(filter-out WIN32 MINGW_W64,$(FUNCTIONS)): @echo "Looking for : $@ ..." @sed -e 's,[@]fn[@],$@,g' -e 's,[@]fnp[@],$(shell echo $@ | sed -e 's,/,_,g' -e 's,[.],_,g'),g'< config.c.in > config.c @if $(CC) -o femto-test.out config.c $(CFLAGS) $(LDFLAGS) -O0 -s 2> /dev/null; then \ diff --git a/libpbat/Makefile b/libpbat/Makefile index e7407d20..f24ac59e 100644 --- a/libpbat/Makefile +++ b/libpbat/Makefile @@ -41,4 +41,6 @@ libpBat.a: $(OBJ_FILES) clean: rm -f $(OBJ_FILES) libpBat.a -.PHONY: all tea clean +bin: libpBat.a + +.PHONY: all tea clean bin diff --git a/modules/batbox/Makefile b/modules/batbox/Makefile index 9ee58a5a..160a1674 100644 --- a/modules/batbox/Makefile +++ b/modules/batbox/Makefile @@ -8,6 +8,7 @@ OBJ_FILES = $(SOURCES:.c=.o) ../lib/pBat_Module.o LDFLAGS += $(LIBPBAT_LD) CFLAGS += $(LIBPBAT_INC) -I../lib/ +MODULE_LDFLAGS = $(filter-out -static,$(LDFLAGS)) all: $(MODULE) @@ -18,7 +19,7 @@ bin: $(MODULE) clean: rm -rf $(OBJ_FILES) -$(MODULE)$(EXEEXT): $(OBJ_FILES) - $(CC) -shared -fvisibility=hidden -o $(MODULE)$(EXEEXT) $(OBJ_FILES) $(LDFLAGS) +$(MODULE)$(EXEEXT): $(OBJ_FILES) ../../libpbat/libpBat.a + $(CC) -shared -fvisibility=hidden -o $(MODULE)$(EXEEXT) $(OBJ_FILES) $(MODULE_LDFLAGS) -.PHONY: all clean bin \ No newline at end of file +.PHONY: all clean bin diff --git a/modules/sample/Makefile b/modules/sample/Makefile index 27094cc9..9e76e2ab 100644 --- a/modules/sample/Makefile +++ b/modules/sample/Makefile @@ -7,6 +7,7 @@ OBJ_FILES = $(SOURCES:.c=.o) ../lib/pBat_Module.o LDFLAGS += $(LIBPBAT_LD) CFLAGS += $(LIBPBAT_INC) -I../lib/ +MODULE_LDFLAGS = $(filter-out -static,$(LDFLAGS)) all: sample bin: sample @@ -16,5 +17,5 @@ bin: sample clean: rm -rf $(OBJ_FILES) -sample: $(OBJ_FILES) - $(CC) -shared -fvisibility=hidden -o sample $(OBJ_FILES) $(LDFLAGS) +sample: $(OBJ_FILES) ../../libpbat/libpBat.a + $(CC) -shared -fvisibility=hidden -o sample $(OBJ_FILES) $(MODULE_LDFLAGS) diff --git a/pbat/Makefile b/pbat/Makefile index 6f86bcfd..6f218c81 100644 --- a/pbat/Makefile +++ b/pbat/Makefile @@ -27,6 +27,12 @@ LDFLAGS += $(MICROGETTEXT_LD) $(LIBPBAT_LD) \ $(LIBMATHEVAL_LD) $(LIBCU8_LD) \ $(LPTHREAD_LD) $(LM_LD) $(LDL_LD) +ifneq ($(fn_WIN32),1) +ifeq ($(filter -lm,$(LDFLAGS)),) + LDFLAGS += -lm +endif +endif + ifeq ($(use_libcu8),1) LDFLAGS += -lshlwapi endif @@ -84,7 +90,7 @@ clean: bin: pbat cp pbat$(EXEC_SUFFIX) $(BINDIR)/ -pbat: $(OBJ_FILES) +pbat: $(OBJ_FILES) ../microgettext/libmicrogettext.a ../libpbat/libpBat.a ../libfasteval/libfasteval.a $(CC) -o pbat$(EXEC_SUFFIX) $(OBJ_FILES) $(LDFLAGS) .rc.o: diff --git a/pbat/linenoise/linenoise.c b/pbat/linenoise/linenoise.c index 905c02b2..5cd5351a 100644 --- a/pbat/linenoise/linenoise.c +++ b/pbat/linenoise/linenoise.c @@ -532,10 +532,11 @@ void xlibcu8_completion_insert(const char* completion, static int completeLine(struct linenoiseState *ls, char *cbuf, size_t cbuf_len, int *c) { - char *item, *completion, + char *item, *orig_buf = ls->buf, *pos = orig_buf + ls->pos, *buf = orig_buf + ls->len; + const char *completion; size_t size = ls->buflen - ls->len; @@ -547,7 +548,7 @@ static int completeLine(struct linenoiseState *ls, char *cbuf, size_t cbuf_len, completionCallback(item, &completion); - if (completion == (char*)-1) { + if (completion == (const char*)-1) { /* Well, apparently a list is to be printed. Make a simple assertion : The prompt is the same length as the previous one ... */ @@ -568,7 +569,7 @@ static int completeLine(struct linenoiseState *ls, char *cbuf, size_t cbuf_len, ls->buf[ls->len] = '\0'; - free(completion); + free((void*)completion); } diff --git a/pbatize/Makefile b/pbatize/Makefile index 443048d6..c1a57bfa 100644 --- a/pbatize/Makefile +++ b/pbatize/Makefile @@ -25,7 +25,7 @@ SRC_FILES = pbatize.c pairs.c all: pbatize -pbatize: +pbatize: ../libpbat/libpBat.a $(CC) -o pbatize$(EXEC_SUFFIX) $(SRC_FILES) $(LDFLAGS) $(CFLAGS) clean: diff --git a/scripts/pbat_start b/scripts/pbat_start new file mode 100755 index 00000000..53da07c2 --- /dev/null +++ b/scripts/pbat_start @@ -0,0 +1,18 @@ +#!/bin/sh + +file=$1 + +if [ -z "$file" ]; then + exit 1 +fi + +if command -v mimeopen >/dev/null 2>&1; then + exec mimeopen "$file" +fi + +if command -v xdg-open >/dev/null 2>&1; then + exec xdg-open "$file" +fi + +echo "pbat_start: no file association helper found (need mimeopen or xdg-open)" >&2 +exit 127 diff --git a/tea/Makefile b/tea/Makefile index d571b5ce..302f82c1 100644 --- a/tea/Makefile +++ b/tea/Makefile @@ -33,7 +33,7 @@ OBJ_FILES = $(SRC_FILES:.c=.o) all: tea -tea: $(OBJ_FILES) +tea: $(OBJ_FILES) ../libpbat/libpBat.a $(CC) -o tea$(EXEC_SUFFIX) $(OBJ_FILES) $(LDFLAGS) clean: From 73ddebd49787711233a6a3162d92dd7a1b8cbdc6 Mon Sep 17 00:00:00 2001 From: ostrich <570911+ostrich@users.noreply.github.com> Date: Sun, 22 Mar 2026 08:49:30 -0400 Subject: [PATCH 2/8] core: fix pipe, redirection, and copy runtime bugs --- dump/dump.c | 19 +++++++++++++++++-- pbat/command/pBat_Copy.c | 14 ++++++++++++-- pbat/core/pBat_Run.c | 38 +++++++++++++++++++++++++++++++++----- pbat/core/pBat_Stream.c | 4 ++-- 4 files changed, 64 insertions(+), 11 deletions(-) diff --git a/dump/dump.c b/dump/dump.c index efcb24b4..3df57705 100644 --- a/dump/dump.c +++ b/dump/dump.c @@ -25,6 +25,7 @@ #include #include #include +#include #ifdef WIN32 #include @@ -228,7 +229,21 @@ int dump_fd(int fd, struct dump_t* context) spaces = (context->flags & DUMP_QUIET) ? "" : " "; - while ((count = read(fd, data, size))) { + while (1) { + count = read(fd, data, size); + + if (count < 0) { + if (errno == EINTR) + continue; + + fprintf(stderr, "dump: read error.\n"); + free(chars); + free(data); + return -1; + } + + if (count == 0) + break; if (count < size) { /* we reached eof and count is not a multiple of the @@ -362,7 +377,7 @@ int main(int argc, char** argv) flags &= ~ DUMP_DEFAULT; - while (*argv) { + while (**argv) { switch (**argv) { case 'a': diff --git a/pbat/command/pBat_Copy.c b/pbat/command/pBat_Copy.c index 184b7880..ba0c0bb1 100644 --- a/pbat/command/pBat_Copy.c +++ b/pbat/command/pBat_Copy.c @@ -25,6 +25,12 @@ #include #include +#if defined(WIN32) +#include +#else +#include +#endif + #include #include "../core/pBat_Core.h" @@ -89,7 +95,7 @@ int pBat__EndWithDirectoryMark(const char* dir) { - char* c=""; + const char* c = ""; while (*dir) c = dir ++; @@ -136,7 +142,11 @@ int pBat_CmdCopy(char* line) if (!stricmp("/Y", str) || !stricmp("/-Y", str)) { - flags |= (*(str+1)=='-') ? PBAT_COPY_SILENCE : 0; + if (*(str+1) == '-') { + flags &= ~PBAT_COPY_SILENCE; + } else { + flags |= PBAT_COPY_SILENCE; + } } else if (!strnicmp("/A", str, 2)) { diff --git a/pbat/core/pBat_Run.c b/pbat/core/pBat_Run.c index 35f27ed2..434d9a95 100644 --- a/pbat/core/pBat_Run.c +++ b/pbat/core/pBat_Run.c @@ -20,7 +20,7 @@ #if !defined(WIN32) && !defined(_X_OPEN_SOURCE) #define _XOPEN_SOURCE 700 #endif - +#include #include #include #include @@ -49,6 +49,27 @@ #include "../errors/pBat_Errors.h" #include "../lang/pBat_Lang.h" +static FILE* pBat_DupFileStream(FILE* stream, const char* mode, const char* caller) +{ + int fd = dup(fileno(stream)); + FILE* dup_stream; + + if (fd == -1) + pBat_ShowErrorMessage(PBAT_UNABLE_DUPLICATE_FD | PBAT_PRINT_C_ERROR, + caller, -1); + + dup_stream = fdopen(fd, mode); + if (dup_stream == NULL) { + close(fd); + pBat_ShowErrorMessage(PBAT_FAILED_ALLOCATION | PBAT_PRINT_C_ERROR, + caller, -1); + } + + pBat_SetStdInheritance(dup_stream, 0); + + return dup_stream; +} + int pBat_RunBatch(INPUT_FILE* pIn) { ESTR* lpLine=pBat_EsInit_Cached(); @@ -174,8 +195,10 @@ int pBat_ExecOperators(PARSED_LINE** lpLine) __FILE__ "/pBat_ExecOperators()", -1); /* prepare data to launch threads */ - infos->out = pipef[1]; - infos->in = lastin; + infos->out = pBat_DupFileStream(pipef[1], "wb", + __FILE__ "/pBat_ExecOperators()"); + infos->in = lastin ? pBat_DupFileStream(lastin, "rb", + __FILE__ "/pBat_ExecOperators()") : NULL; infos->str = pBat_EsInit(); pBat_EsCpyE(infos->str, line->lpCmdLine); @@ -187,6 +210,11 @@ int pBat_ExecOperators(PARSED_LINE** lpLine) res = pBat_CloneInstance((void(*)(void*))pBat_LaunchPipe, infos); pBat_CloseThread(&res); + /* The child thread owns the write end now. Keeping it open in the + parent prevents downstream readers from ever seeing EOF. */ + fclose(pipef[1]); + if (lastin != NULL) + fclose(lastin); lastin = pipef[0]; if (line->lppsNode) line = *lpLine = line->lppsNode; @@ -207,7 +235,7 @@ void pBat_LaunchPipe(struct pipe_launch_data_t* infos) lppsStreamStack = pBat_OpenOutputF(lppsStreamStack, infos->out, PBAT_STDOUT); - if (infos->in == NULL) + if (infos->in != NULL) lppsStreamStack = pBat_OpenOutputF(lppsStreamStack, infos->in, PBAT_STDIN); bIgnoreExit = TRUE; @@ -759,7 +787,7 @@ int pBat_RunExternalBatch(char* lpFileName, char* lpFullLine, char** lpArguments if (pBat_WaitForThread(&th, &ret) != 0) pBat_CloseThread(&th); - return (int)ret; + return (int)(intptr_t)ret; } /* Refactors a PARSED_LINE that contains a lookAHead command. diff --git a/pbat/core/pBat_Stream.c b/pbat/core/pBat_Stream.c index 73b48bd1..29a1d378 100644 --- a/pbat/core/pBat_Stream.c +++ b/pbat/core/pBat_Stream.c @@ -71,8 +71,8 @@ STREAMSTACK* pBat_OpenOutput(STREAMSTACK* stack, char* name, int mode) FILE* f; char *fmode; - switch (mode & (PBAT_STDIN | PBAT_STDOUT | PBAT_STDERR - | PBAT_STREAM_MODE_ADD | PBAT_STREAM_MODE_ADD)) { + switch (mode & (PBAT_STDIN | PBAT_STDOUT | PBAT_STDERR + | PBAT_STREAM_MODE_ADD | PBAT_STREAM_MODE_TRUNCATE)) { case PBAT_STDOUT | PBAT_STREAM_MODE_ADD: case PBAT_STDERR | PBAT_STREAM_MODE_ADD: From 8dc5084e7ba12cfe310effcffc3880a4259fccb4 Mon Sep 17 00:00:00 2001 From: ostrich <570911+ostrich@users.noreply.github.com> Date: Sun, 22 Mar 2026 08:49:39 -0400 Subject: [PATCH 3/8] core: fix CALL and IF ERRORLEVEL compatibility --- pbat/command/pBat_Call.c | 14 ++++++-------- pbat/command/pBat_If.h | 5 ++++- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/pbat/command/pBat_Call.c b/pbat/command/pBat_Call.c index caac7327..745e8549 100644 --- a/pbat/command/pBat_Call.c +++ b/pbat/command/pBat_Call.c @@ -194,13 +194,10 @@ int pBat_CmdCall(char* lpLine) determine ``file'' extension, to choose wether it should be executed inside or outside pBat */ - if (pBat_GetFilePath(lpFullName, lpFile, sizeof(lpFullName)) == -1) { - - pBat_ShowErrorMessage(PBAT_COMMAND_ERROR, lpFile, 0); - - status = PBAT_COMMAND_ERROR; - goto error; - } + if (pBat_GetFilePath(lpFullName, lpFile, sizeof(lpFullName)) == -1) { + status = pBat_CmdCallExternal(lpFile, lpLine); + goto error; + } pBat_SplitPath(lpFullName, NULL, NULL, NULL, lpExt); @@ -269,6 +266,7 @@ int pBat_CmdCallFile(char* lpFile, char* lpFull, char* lpLabel, char* lpCmdLine) ifIn.bEof=FALSE; /* the file is not at EOF */ ifIn.iPos=0; /* places the cursor at the origin */ + ifIn.batch.curr = ifIn.batch.cmds; snprintf(ifIn.lpFileName, sizeof(ifIn.lpFileName), "%s", @@ -409,7 +407,7 @@ int pBat_CmdCallExternal(char* lpFile, char* lpCh) pBat_EsCat(lpEsLine, " "); pBat_EsCat(lpEsLine, lpCh); - /* Offer a free new expansion turn */ + /* Offer a free new expansion turn */ pBat_ReplaceVars(lpEsLine); bkInfo.lpBegin=pBat_EsToChar(lpEsLine); diff --git a/pbat/command/pBat_If.h b/pbat/command/pBat_If.h index 16c279e4..ec516081 100644 --- a/pbat/command/pBat_If.h +++ b/pbat/command/pBat_If.h @@ -55,7 +55,10 @@ ret = (pBat_GetEnv(env, var) != NULL); #define PBAT_IF_ERRORLEVEL_TEST(ret, val, env) \ - ret = !stricmp(val, pBat_GetEnv(env, "ERRORLEVEL")); + do { \ + const char* _pbat_errorlevel = pBat_GetEnv(env, "ERRORLEVEL"); \ + ret = atoi((_pbat_errorlevel != NULL) ? _pbat_errorlevel : "0") >= atoi(val); \ + } while (0) int pBat_PerformExtendedTest(const char* lpCmp, const char* lpParam1, const char* lpParam2, int iFlag); From 51b8edace50e7fea06ce334cc973487c84801a4a Mon Sep 17 00:00:00 2001 From: ostrich <570911+ostrich@users.noreply.github.com> Date: Sun, 22 Mar 2026 08:50:04 -0400 Subject: [PATCH 4/8] core: fix portability and correctness issues --- libpbat/cmdlib/pBat_String.c | 10 +++++----- libpbat/dir/pBat_Dir.c | 10 +++++----- libpbat/dir/pBat_UnicodeDir.c | 4 ++-- libpbat/libpBat.h | 2 +- pbat/command/pBat_Ask.c | 4 ++++ pbat/command/pBat_Dir.c | 6 ++++++ pbat/command/pBat_Exit.c | 3 ++- pbat/command/pBat_Find.c | 10 ++++++++-- pbat/command/pBat_For.c | 14 +++++++++----- pbat/command/pBat_Mod.c | 10 +++++----- pbat/command/pBat_More.c | 5 ++++- pbat/command/pBat_Rmdir.c | 6 ++++++ pbat/command/pBat_Set.c | 6 ++++++ pbat/command/pBat_Start.c | 2 +- pbat/command/pBat_Type.c | 6 ++++++ pbat/command/pBat_Wc.c | 2 ++ pbat/command/pBat_Xargs.c | 2 ++ pbat/core/pBat_Clone.c | 5 +++-- pbat/core/pBat_Completion.c | 9 ++++++--- pbat/core/pBat_Exec.c | 18 +++++++++--------- pbat/core/pBat_FilePath.c | 11 ++++++----- pbat/core/pBat_Prompt.c | 3 ++- pbat/core/pBat_Read.c | 6 ++++++ pbat/errors/pBat_Errors.c | 1 + pbat/init/pBat_Init.c | 6 ++++++ 25 files changed, 113 insertions(+), 48 deletions(-) diff --git a/libpbat/cmdlib/pBat_String.c b/libpbat/cmdlib/pBat_String.c index 76b5170c..b828c277 100644 --- a/libpbat/cmdlib/pBat_String.c +++ b/libpbat/cmdlib/pBat_String.c @@ -51,7 +51,7 @@ LIBPBAT char* pBat_SkipAllBlanks(const char* lpCh) LIBPBAT char* pBat_SearchChar(const char* lpCh, int cChar) { - char* lpNxt; + const char* lpNxt; char ok; int i; @@ -88,7 +88,7 @@ LIBPBAT char* pBat_SearchChar(const char* lpCh, int cChar) LIBPBAT char* pBat_SearchLastChar(const char* lpCh, int cChar) { - char *lpLastMatch=NULL, + const char *lpLastMatch = NULL, *lpNxt; char ok; int i; @@ -160,7 +160,7 @@ LIBPBAT char* pBat_GetNextNonEscaped(const char* lpCh) LIBPBAT char* pBat_SearchToken(const char* restrict lpCh, const char* restrict lpDelims) { - char* lpNxt; + const char* lpNxt; char ok; int i; @@ -226,7 +226,7 @@ LIBPBAT char* pBat_SearchLastToken(const char* restrict lpCh, const char* restri LIBPBAT char* pBat_SearchChar_OutQuotes(const char* lpCh, int cChar) { - char *lpNxt, + const char *lpNxt, *lpNextQuote; int i; @@ -332,7 +332,7 @@ LIBPBAT char* pBat_SearchChar_OutQuotes(const char* lpCh, int cChar) LIBPBAT char* pBat_SearchToken_OutQuotes(const char* restrict lpCh, const char* restrict lpDelims) { - char *lpNxt, + const char *lpNxt, *lpNextQuote; int i; diff --git a/libpbat/dir/pBat_Dir.c b/libpbat/dir/pBat_Dir.c index 4b952676..ad83771a 100644 --- a/libpbat/dir/pBat_Dir.c +++ b/libpbat/dir/pBat_Dir.c @@ -160,7 +160,7 @@ static FILELIST* pBat_AddMatch(char* name, FILELIST* files, struct match_args_t* static int /* inline */ pBat_EndWithDirectoryMark(const char *dir) { - char *c = NULL; + const char *c = NULL; while (*dir) c = dir++; @@ -563,7 +563,7 @@ LIBPBAT LPFILELIST pBat_GetMatchFileList(char* lpPathMatch, int iFlag) return file; } -LIBPBAT int pBat_GetMatchFileCallback(char* lpPathMatch, int iFlag, void(*pCallBack)(FILELIST*)) +LIBPBAT LPFILELIST pBat_GetMatchFileCallback(char* lpPathMatch, int iFlag, void(*pCallBack)(FILELIST*)) { struct match_args_t args; FILELIST* file; @@ -581,7 +581,7 @@ LIBPBAT int pBat_GetMatchFileCallback(char* lpPathMatch, int iFlag, void(*pCallB } - return (int)file; + return file; } #endif @@ -632,7 +632,7 @@ LIBPBAT int pBat_EndWithPattern(const char* restrict match, const char* restrict LIBPBAT int pBat_RegExpMatch(const char* restrict regexp, const char* restrict match) { - char* next; + const char* next; size_t size; //printf("*** Comparing \"%s\" et \"%s\"\n", regexp, match); @@ -751,7 +751,7 @@ LIBPBAT int pBat_EndWithCasePattern(const char* restrict match, const char* rest LIBPBAT int pBat_RegExpCaseMatch(const char* restrict regexp, const char* restrict match) { - char* next; + const char* next; size_t size; //printf("*** Comparing \"%s\" et \"%s\"\n", regexp, match); diff --git a/libpbat/dir/pBat_UnicodeDir.c b/libpbat/dir/pBat_UnicodeDir.c index 25382291..ed9585e7 100644 --- a/libpbat/dir/pBat_UnicodeDir.c +++ b/libpbat/dir/pBat_UnicodeDir.c @@ -586,7 +586,7 @@ LIBPBAT LPFILELIST pBat_GetMatchFileList(char* lpPathMatch, int iFlag) return file; } -LIBPBAT int pBat_GetMatchFileCallback(char* lpPathMatch, int iFlag, void(*pCallBack)(FILELIST*)) +LIBPBAT LPFILELIST pBat_GetMatchFileCallback(char* lpPathMatch, int iFlag, void(*pCallBack)(FILELIST*)) { struct match_args_t args; FILELIST* file; @@ -613,7 +613,7 @@ LIBPBAT int pBat_GetMatchFileCallback(char* lpPathMatch, int iFlag, void(*pCallB free(wpath); - return (int)file; + return file; } #endif diff --git a/libpbat/libpBat.h b/libpbat/libpBat.h index 1967cd79..80afc072 100644 --- a/libpbat/libpBat.h +++ b/libpbat/libpBat.h @@ -428,7 +428,7 @@ typedef struct FILELIST { LIBPBAT int pBat_RegExpMatch(const char* restrict lpRegExp, const char* restrict lpMatch); LIBPBAT int pBat_RegExpCaseMatch(const char* restrict lpRegExp, const char* restrict lpMatch); LIBPBAT LPFILELIST pBat_GetMatchFileList(char* lpPathMatch, int iFlag); -LIBPBAT int pBat_GetMatchFileCallback(char* lpPathMatch, int iFlag, void(*pCallBack)(FILELIST*)); +LIBPBAT LPFILELIST pBat_GetMatchFileCallback(char* lpPathMatch, int iFlag, void(*pCallBack)(FILELIST*)); LIBPBAT int pBat_FreeFileList(LPFILELIST lpflFileList); LIBPBAT int pBat_FormatFileSize (char* lpBuf, int iLenght, unsigned int iSize); LIBPBAT int pBat_GetStaticPart(const char* lpPathMatch, char* lpStaticPart, size_t size); diff --git a/pbat/command/pBat_Ask.c b/pbat/command/pBat_Ask.c index cfa28f9a..99cf5ce2 100644 --- a/pbat/command/pBat_Ask.c +++ b/pbat/command/pBat_Ask.c @@ -28,6 +28,10 @@ #include #include +#if !defined(WIN32) +#include +#endif + #include #include "pBat_Ask.h" diff --git a/pbat/command/pBat_Dir.c b/pbat/command/pBat_Dir.c index 3b5bc9a3..00ad100a 100644 --- a/pbat/command/pBat_Dir.c +++ b/pbat/command/pBat_Dir.c @@ -25,6 +25,12 @@ #include #include +#if defined(WIN32) +#include +#else +#include +#endif + #include #include "../core/pBat_Core.h" diff --git a/pbat/command/pBat_Exit.c b/pbat/command/pBat_Exit.c index cccd8398..67a87ee8 100644 --- a/pbat/command/pBat_Exit.c +++ b/pbat/command/pBat_Exit.c @@ -25,6 +25,7 @@ #include #include #include +#include #include @@ -101,7 +102,7 @@ int pBat_CmdExit(char* lpLine) /* End thread or the program */ if (bIgnoreExit) - pBat_EndThread((void*)ret); + pBat_EndThread((void*)(intptr_t)ret); else { exit(ret); } diff --git a/pbat/command/pBat_Find.c b/pbat/command/pBat_Find.c index a58cfa43..eab4411b 100644 --- a/pbat/command/pBat_Find.c +++ b/pbat/command/pBat_Find.c @@ -32,6 +32,12 @@ #include #include +#if defined(WIN32) +#include +#else +#include +#endif + #include #include "../core/pBat_Core.h" @@ -42,12 +48,12 @@ char* pBat_FindRegExpMatch(const char* restrict s, const char* restrict exp) { - return (char*)pBat_RegExpMatch(exp, s); + return pBat_RegExpMatch(exp, s) ? (char*)s : NULL; } char* pBat_FindRegExpCaseMatch(const char* restrict s, const char* restrict exp) { - return (char*)pBat_RegExpCaseMatch(exp, s); + return pBat_RegExpCaseMatch(exp, s) ? (char*)s : NULL; } /* This function come from FreeBSD (and, BSD I think) implementation of diff --git a/pbat/command/pBat_For.c b/pbat/command/pBat_For.c index 42b8b8ec..f660c7ac 100644 --- a/pbat/command/pBat_For.c +++ b/pbat/command/pBat_For.c @@ -170,8 +170,10 @@ int pBat_CmdFor(char* lpLine, PARSED_LINE** lpplLine) if (pBat_TestLocalVarName(cVarName)) { + char badVarName[] = { cVarName, '\0' }; + pBat_ShowErrorMessage(PBAT_SPECIAL_VAR_NON_ASCII, - (void*)cVarName, + badVarName, FALSE); status = PBAT_SPECIAL_VAR_NON_ASCII; @@ -356,7 +358,7 @@ int pBat_CmdFor(char* lpLine, PARSED_LINE** lpplLine) *lpplLine = NULL; if (!lpplLine && line) - pBat_FreeParsedStream(line); + pBat_FreeParsedLine(line); return status; } @@ -726,7 +728,7 @@ int pBat_ForIsSpecifier(const char* restrict p) char* pBat_ForGetSpecifier(const char* restrict in, ESTR* restrict out, int* restrict type) { - char *next = NULL; + const char *next = NULL; *type = pBat_ForIsSpecifier(in); @@ -768,7 +770,7 @@ char* pBat_ForGetSpecifier(const char* restrict in, ESTR* restrict out, int* res pBat_EsCpyN(out, in, next - in); - return *next ? next + 1 : next; + return (char*)(*next ? next + 1 : next); } int pBat_ForMakeTokens (char* p, FORINFO* infos) @@ -1162,8 +1164,10 @@ int pBat_ForVarCheckAssignment(FORINFO* lpfrInfo) if ((pBat_GetLocalVarPointer(lpvLocalVars, cVarName))) { + char varName[] = { cVarName, '\0' }; + pBat_ShowErrorMessage(PBAT_FOR_TRY_REASSIGN_VAR, - (char*)(cVarName), FALSE); + varName, FALSE); return PBAT_FOR_TRY_REASSIGN_VAR; } diff --git a/pbat/command/pBat_Mod.c b/pbat/command/pBat_Mod.c index 79e0117f..bd3d5673 100644 --- a/pbat/command/pBat_Mod.c +++ b/pbat/command/pBat_Mod.c @@ -37,7 +37,7 @@ void pBat_ModSetEnv(const char* name, const char* content) { - pBat_SetEnv(lpeEnv, name, content); + pBat_SetEnv(lpeEnv, (char*)name, content); } char* pBat_ModGetEnv(const char* name) @@ -52,7 +52,7 @@ const char* pBat_ModGetCurrentDir(void) int pBat_ModSetCurrentDir(const char* dir) { - return pBat_SetCurrentDir(dir); + return pBat_SetCurrentDir((char*)dir); } FILE* pBat_ModGetfInput(void) @@ -77,7 +77,7 @@ int pBat_ModGetbIsScript(void) char* pBat_ModGetNextParameterEs(const char* line, ESTR* recv) { - return pBat_GetNextParameterEs(line, recv); + return pBat_GetNextParameterEs((char*)line, recv); } char* pBat_ModEsToFullPath(ESTR* str) @@ -92,11 +92,11 @@ int pBat_ModRegisterCommand(const char* name, int(*handler)(char*)) void* p; int status = PBAT_NO_ERROR; - command.ptrCommandName = name; + command.ptrCommandName = (char*)name; command.lpCommandProc = handler; command.cfFlag = strlen(name); - if (pBat_GetCommandProc(name, lpclCommands, &p) != -1) { + if (pBat_GetCommandProc((char*)name, lpclCommands, &p) != -1) { pBat_ShowErrorMessage(PBAT_TRY_REDEFINE_COMMAND, name, diff --git a/pbat/command/pBat_More.c b/pbat/command/pBat_More.c index 8dabdb0b..9f3a5176 100644 --- a/pbat/command/pBat_More.c +++ b/pbat/command/pBat_More.c @@ -27,7 +27,10 @@ #include #include -#ifndef WIN32 +#if defined(WIN32) +#include +#else +#include #include #endif diff --git a/pbat/command/pBat_Rmdir.c b/pbat/command/pBat_Rmdir.c index c8c9065f..81d67eb3 100644 --- a/pbat/command/pBat_Rmdir.c +++ b/pbat/command/pBat_Rmdir.c @@ -25,6 +25,12 @@ #include #include +#if defined(WIN32) +#include +#else +#include +#endif + #include diff --git a/pbat/command/pBat_Set.c b/pbat/command/pBat_Set.c index 8443b6ce..036cf6b5 100644 --- a/pbat/command/pBat_Set.c +++ b/pbat/command/pBat_Set.c @@ -32,6 +32,12 @@ #include #include +#if defined(WIN32) +#include +#else +#include +#endif + #include #include diff --git a/pbat/command/pBat_Start.c b/pbat/command/pBat_Start.c index cb0e40f8..19a54cca 100644 --- a/pbat/command/pBat_Start.c +++ b/pbat/command/pBat_Start.c @@ -354,7 +354,7 @@ int pBat_CmdStart(char* line) args[n] = NULL; - info.args = args; + info.args = (const char* const*)args; if (info.dir == NULL) info.dir = lpCurrentDir; diff --git a/pbat/command/pBat_Type.c b/pbat/command/pBat_Type.c index 87af0ff0..153c49dd 100644 --- a/pbat/command/pBat_Type.c +++ b/pbat/command/pBat_Type.c @@ -30,6 +30,12 @@ #include #include +#if defined(WIN32) +#include +#else +#include +#endif + #include #include "../core/pBat_Core.h" diff --git a/pbat/command/pBat_Wc.c b/pbat/command/pBat_Wc.c index 8c882cd1..335e519f 100644 --- a/pbat/command/pBat_Wc.c +++ b/pbat/command/pBat_Wc.c @@ -32,6 +32,8 @@ #if defined(WIN32) #include +#else +#include #endif #include diff --git a/pbat/command/pBat_Xargs.c b/pbat/command/pBat_Xargs.c index b14622dc..87b52482 100644 --- a/pbat/command/pBat_Xargs.c +++ b/pbat/command/pBat_Xargs.c @@ -27,6 +27,8 @@ #if defined(WIN32) #include +#else +#include #endif #include diff --git a/pbat/core/pBat_Clone.c b/pbat/core/pBat_Clone.c index 60a379ff..dba7c3a8 100644 --- a/pbat/core/pBat_Clone.c +++ b/pbat/core/pBat_Clone.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -103,9 +104,9 @@ void* pBat_CloneTrampoline(void* data) fclose(fOutput); fclose(fError); - pBat_EndThread((void*)iErrorLevel); + pBat_EndThread((void*)(intptr_t)iErrorLevel); - return (void*)iErrorLevel; + return (void*)(intptr_t)iErrorLevel; } /* Duplicate pBat internal structures */ diff --git a/pbat/core/pBat_Completion.c b/pbat/core/pBat_Completion.c index 3f5da4d4..d5185a39 100644 --- a/pbat/core/pBat_Completion.c +++ b/pbat/core/pBat_Completion.c @@ -255,9 +255,12 @@ void pBat_CompletionHandler(const char* in, const char** subst) } else if (match > len) { /* a partial match ! */ - *subst = malloc(match - len + 1); - if (*subst) - snprintf(*subst, match - len + 1, "%s", files->lpFileName + len); + char* completion = malloc(match - len + 1); + + if (completion) { + snprintf(completion, match - len + 1, "%s", files->lpFileName + len); + *subst = completion; + } } else *subst = (char*)-1; diff --git a/pbat/core/pBat_Exec.c b/pbat/core/pBat_Exec.c index b2f6aa5b..e176916e 100644 --- a/pbat/core/pBat_Exec.c +++ b/pbat/core/pBat_Exec.c @@ -313,7 +313,7 @@ int pBat_RunFile(EXECINFO* info, int* error) close(fds[0]); /* close read end */ - if ( execv(info->file, info->args) == -1) { + if ( execv(info->file, (char* const*)info->args) == -1) { /* if we got here, we can't set ERRORLEVEL variable anymore. @@ -545,7 +545,7 @@ int pBat_StartFile(EXECINFO* info, int* error) pid_t pid; ESTR* tmp; - char **arg, *script; + const char **arg, *script; int status=0, i; script = pBat_GetEnv(lpeEnv, "PBAT_START_SCRIPT"); @@ -570,31 +570,31 @@ int pBat_StartFile(EXECINFO* info, int* error) while (info->args[i]) i ++; - if (!(arg = malloc(sizeof(char*)*(i+2)))) + if (!(arg = malloc(sizeof(char*)*(i+2)))) pBat_ShowErrorMessage(PBAT_FAILED_ALLOCATION | PBAT_PRINT_C_ERROR, __FILE__ "/pBat_StartFile()", -1); - arg[0] = script; - arg[1] = info->file; + arg[0] = script; + arg[1] = info->file; i=1; while (info->args[i] != NULL) { - arg[i + 1] = info->args[i]; + arg[i + 1] = info->args[i]; i ++; } arg[i + 1] = NULL; - lppsStreamStack = pBat_OpenOutput(lppsStreamStack, "/dev/null", - PBAT_STDOUT, 0); + lppsStreamStack = pBat_OpenOutput(lppsStreamStack, "/dev/null", + PBAT_STDOUT); /* apply pBat internal environment variables */ pBat_ApplyEnv(lpeEnv); pBat_ApplyStreams(lppsStreamStack); - if (execv(script, arg) == -1) { + if (execv(script, (char* const*)arg) == -1) { pBat_ShowErrorMessage(PBAT_COMMAND_ERROR | PBAT_PRINT_C_ERROR, script, -1); diff --git a/pbat/core/pBat_FilePath.c b/pbat/core/pBat_FilePath.c index 3cd435a8..f188ca9c 100644 --- a/pbat/core/pBat_FilePath.c +++ b/pbat/core/pBat_FilePath.c @@ -33,7 +33,7 @@ int pBat_GetFileFullPath(char* full, const char* p, size_t size) { - char* partial = TRANS(p); + const char* partial = TRANS(p); if (PBAT_TEST_ABSOLUTE_PATH(partial)) { @@ -65,7 +65,7 @@ int pBat_GetFileFullPath(char* full, const char* p, size_t size) void __inline__ pBat_MakeFullPath(char* full, const char* p, size_t size) { - char* partial = TRANS(p); + const char* partial = TRANS(p); if (PBAT_TEST_ABSOLUTE_PATH(partial)) { @@ -87,7 +87,7 @@ void __inline__ pBat_MakeFullPath(char* full, const char* p, size_t size) void __inline__ pBat_MakeFullPathEs(ESTR* full, const char* p) { char begin[] = "c:/"; - char* partial = TRANS(p); + const char* partial = TRANS(p); if (PBAT_TEST_ABSOLUTE_PATH(partial)) { @@ -136,7 +136,7 @@ __inline__ char* pBat_EsToFullPath(ESTR* full) memmove(full->str + 2, p, len); *(full->str) = *lpCurrentDir; - *(full->str) = ':'; + *(full->str + 1) = ':'; } #endif // WIN32 @@ -168,7 +168,8 @@ __inline__ char* pBat_EsToFullPath(ESTR* full) __inline__ char* pBat_FullPathDup(const char* p) { - char *ret, *path = TRANS(p); + char *ret; + const char *path = TRANS(p); size_t needed = strlen(path) + 1; if (PBAT_TEST_ABSOLUTE_PATH(path)) { diff --git a/pbat/core/pBat_Prompt.c b/pbat/core/pBat_Prompt.c index a30e5ea5..1e32818b 100644 --- a/pbat/core/pBat_Prompt.c +++ b/pbat/core/pBat_Prompt.c @@ -28,7 +28,8 @@ int pBat_GetColorCode(char* lpArg); void pBat_OutputPromptString(const char* prompt) { - char *pch, *t; + const char *pch; + char *t; size_t count, dir_count; char buf[30]; int code; diff --git a/pbat/core/pBat_Read.c b/pbat/core/pBat_Read.c index 76d8f10b..7eb89648 100644 --- a/pbat/core/pBat_Read.c +++ b/pbat/core/pBat_Read.c @@ -21,6 +21,12 @@ #include #include +#if defined(WIN32) +#include +#else +#include +#endif + #include #include "pBat_Core.h" diff --git a/pbat/errors/pBat_Errors.c b/pbat/errors/pBat_Errors.c index 5753112a..e021d796 100644 --- a/pbat/errors/pBat_Errors.c +++ b/pbat/errors/pBat_Errors.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include diff --git a/pbat/init/pBat_Init.c b/pbat/init/pBat_Init.c index 04c6bb4a..ad9b18aa 100644 --- a/pbat/init/pBat_Init.c +++ b/pbat/init/pBat_Init.c @@ -30,6 +30,12 @@ #include #include +#if defined(WIN32) +#include +#else +#include +#endif + #include "../../config.h" #if defined WIN32 From 0c406d8077f28a1167fafb32ac963486c3196599 Mon Sep 17 00:00:00 2001 From: ostrich <570911+ostrich@users.noreply.github.com> Date: Sun, 22 Mar 2026 09:00:51 -0400 Subject: [PATCH 5/8] command: add CHOICE builtin --- pbat/Makefile | 2 +- pbat/command/pBat_Choice.c | 455 ++++++++++++++++++++++++++++++++ pbat/command/pBat_Choice.h | 26 ++ pbat/command/pBat_CommandInfo.c | 1 + pbat/command/pBat_Commands.h | 1 + pbat/lang/pBat_ShowHelp.c | 6 + pbat/lang/pBat_ShowHelp.h | 3 +- 7 files changed, 492 insertions(+), 2 deletions(-) create mode 100644 pbat/command/pBat_Choice.c create mode 100644 pbat/command/pBat_Choice.h diff --git a/pbat/Makefile b/pbat/Makefile index 6f218c81..28aeec99 100644 --- a/pbat/Makefile +++ b/pbat/Makefile @@ -62,7 +62,7 @@ SRC_FILES := \ ./core/pBat_EsCache.c ./core/pBat_Pipe.c \ ./command/pBat_Def.c ./command/pBat_Ask.c \ ./command/pBat_Block.c ./command/pBat_Call.c ./command/pBat_Cd.c \ - ./command/pBat_Cls.c ./command/pBat_Color.c ./command/pBat_Copy.c \ + ./command/pBat_Choice.c ./command/pBat_Cls.c ./command/pBat_Color.c ./command/pBat_Copy.c \ ./command/pBat_Del.c ./command/pBat_Dir.c ./command/pBat_Echo.c \ ./command/pBat_Exit.c ./command/pBat_For.c ./command/pBat_Goto.c \ ./command/pBat_Help.c ./command/pBat_If.c ./command/pBat_Mkdir.c \ diff --git a/pbat/command/pBat_Choice.c b/pbat/command/pBat_Choice.c new file mode 100644 index 00000000..ca900287 --- /dev/null +++ b/pbat/command/pBat_Choice.c @@ -0,0 +1,455 @@ +/* + * + * pBat - A Free, Cross-platform command prompt - The pBat project + * Copyright (C) 2010-2018 Romain GARBI + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include +#include +#include +#include + +#if !defined(WIN32) +#include +#include +#include +#endif + +#include + +#include "../core/pBat_Core.h" + +#include "pBat_Choice.h" + +#include "../lang/pBat_ShowHelp.h" + +#include "../errors/pBat_Errors.h" + +#define PBAT_CHOICE_SLEEP 100 +#define PBAT_CHOICE_DELIMS " ;\t\"\n" +#define PBAT_CHOICE_MAX 254 + +static int pBat_ChoiceHasDuplicates(const char* choices, int case_sensitive) +{ + int i, j; + + for (i = 0; choices[i]; i++) { + int lhs = (unsigned char)choices[i]; + + if (!case_sensitive) + lhs = toupper(lhs); + + for (j = i + 1; choices[j]; j++) { + int rhs = (unsigned char)choices[j]; + + if (!case_sensitive) + rhs = toupper(rhs); + + if (lhs == rhs) + return 1; + } + } + + return 0; +} + +static void pBat_ChoiceWritePrompt(const char* message, const char* choices) +{ + int i; + + if (message && *message) { + fputs(message, fOutput); + fputc(' ', fOutput); + } + + fputc('[', fOutput); + for (i = 0; choices[i]; i++) { + if (i) + fputc(',', fOutput); + fputc(choices[i], fOutput); + } + fputs("]? ", fOutput); + fflush(fOutput); +} + +static void pBat_ChoiceWriteMessage(const char* message) +{ + if (!message || !*message) + return; + + fputs(message, fOutput); + fputc(' ', fOutput); + fflush(fOutput); +} + +static int pBat_ChoiceParseSeconds(const char* arg, double* seconds) +{ + char* endptr; + long value; + + value = strtol(arg, &endptr, 10); + if (endptr == NULL || *endptr || value < 0 || value > 9999) + return 0; + + *seconds = (double)value; + return 1; +} + +static int pBat_ChoiceFind(const char* choices, int key, int case_sensitive) +{ + int i; + int lhs = key; + + if (!case_sensitive) + lhs = toupper((unsigned char)lhs); + + for (i = 0; choices[i]; i++) { + int rhs = choices[i]; + if (!case_sensitive) + rhs = toupper((unsigned char)rhs); + if (lhs == rhs) + return i + 1; + } + + return 0; +} + +static int pBat_ChoiceParseDefault(const char* choices, char def, int case_sensitive) +{ + return pBat_ChoiceFind(choices, def, case_sensitive); +} + +static int pBat_ChoiceReadWithTimeout(double timeout_seconds) +{ +#if !defined(WIN32) + struct termios oldattr, rawattr; + int fd = fileno(fInput); + int restore = 0; + + if (isatty(fd) && tcgetattr(fd, &oldattr) == 0) { + rawattr = oldattr; + rawattr.c_lflag &= ~(ICANON | ECHO); + rawattr.c_cc[VMIN] = 0; + rawattr.c_cc[VTIME] = 0; + if (tcsetattr(fd, TCSANOW, &rawattr) == 0) + restore = 1; + } + + if (timeout_seconds < 0.0) { + for (;;) { + fd_set rfds; + + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + + if (select(fd + 1, &rfds, NULL, NULL, NULL) > 0 && FD_ISSET(fd, &rfds)) { + int ch = pBat_Getch(fInput); + + if (restore) + tcsetattr(fd, TCSANOW, &oldattr); + return ch; + } + } + } + + while (timeout_seconds > 0.0) { + fd_set rfds; + struct timeval tv; + double wait_seconds = timeout_seconds; + + if (wait_seconds > (PBAT_CHOICE_SLEEP / 1000.0)) + wait_seconds = (PBAT_CHOICE_SLEEP / 1000.0); + + tv.tv_sec = (time_t)wait_seconds; + tv.tv_usec = (suseconds_t)((wait_seconds - tv.tv_sec) * 1000000.0); + + FD_ZERO(&rfds); + FD_SET(fd, &rfds); + + if (select(fd + 1, &rfds, NULL, NULL, &tv) > 0 && FD_ISSET(fd, &rfds)) { + int ch = pBat_Getch(fInput); + + if (restore) + tcsetattr(fd, TCSANOW, &oldattr); + return ch; + } + + timeout_seconds -= wait_seconds; + } + + if (restore) + tcsetattr(fd, TCSANOW, &oldattr); + return -1; +#else + if (timeout_seconds < 0.0) { + while (!pBat_Kbhit(fInput)) + pBat_Sleep(PBAT_CHOICE_SLEEP); + return pBat_Getch(fInput); + } + + while (timeout_seconds > 0.0) { + if (pBat_Kbhit(fInput)) + return pBat_Getch(fInput); + + if (timeout_seconds > (PBAT_CHOICE_SLEEP / 1000.0)) { + pBat_Sleep(PBAT_CHOICE_SLEEP); + timeout_seconds -= (PBAT_CHOICE_SLEEP / 1000.0); + } else { + pBat_Sleep((unsigned int)(timeout_seconds * 1000.0)); + timeout_seconds = 0.0; + } + } + + return -1; +#endif +} + +int pBat_CmdChoice(char* lpLine) +{ + int status = PBAT_NO_ERROR; + int hide_prompt = 0; + int case_sensitive = 0; + int choice_index = 0; + int have_timeout = 0; + int have_default = 0; + int explicit_default = 0; + int default_index = 0; + double timeout_seconds = -1.0; + char default_choice = '\0'; + const char* choices = "YN"; + const char* prompt = NULL; + ESTR* param = pBat_EsInit_Cached(); + ESTR* choices_buf = pBat_EsInit_Cached(); + ESTR* prompt_buf = pBat_EsInit_Cached(); + char* next; + + lpLine += 6; + + while ((next = pBat_GetNextParameterEsD(lpLine, param, PBAT_CHOICE_DELIMS))) { + char* payload = NULL; + + lpLine = next; + + if (!stricmp(param->str, "/?")) { + pBat_ShowInternalHelp(PBAT_HELP_CHOICE); + goto end; + } + + if (param->str[0] != '/') { + pBat_ShowErrorMessage(PBAT_UNEXPECTED_ELEMENT, param->str, 0); + status = PBAT_UNEXPECTED_ELEMENT; + goto end; + } + + if (!strnicmp(param->str, "/C", 2)) { + payload = param->str + 2; + if (*payload == ':') + payload++; + + if (!*payload) { + if (!(lpLine = pBat_GetNextParameterEsD(lpLine, param, PBAT_CHOICE_DELIMS))) { + pBat_ShowErrorMessage(PBAT_EXPECTED_MORE, "CHOICE", FALSE); + status = PBAT_EXPECTED_MORE; + goto end; + } + payload = param->str; + } + + if (!*payload) { + pBat_ShowErrorMessage(PBAT_UNEXPECTED_ELEMENT, "/C", 0); + status = PBAT_UNEXPECTED_ELEMENT; + goto end; + } + + pBat_EsCpy(choices_buf, payload); + choices = choices_buf->str; + continue; + } + + if (!stricmp(param->str, "/N")) { + hide_prompt = 1; + continue; + } + + if (!stricmp(param->str, "/CS")) { + case_sensitive = 1; + continue; + } + + if (!strnicmp(param->str, "/M", 2)) { + payload = param->str + 2; + if (*payload == ':') + payload++; + + if (!*payload) { + if (!(lpLine = pBat_GetNextParameterEsD(lpLine, param, PBAT_CHOICE_DELIMS))) { + pBat_ShowErrorMessage(PBAT_EXPECTED_MORE, "CHOICE", FALSE); + status = PBAT_EXPECTED_MORE; + goto end; + } + payload = param->str; + } + + if (!*payload) { + pBat_ShowErrorMessage(PBAT_UNEXPECTED_ELEMENT, "/M", 0); + status = PBAT_UNEXPECTED_ELEMENT; + goto end; + } + + pBat_EsCpy(prompt_buf, payload); + prompt = prompt_buf->str; + continue; + } + + if (!strnicmp(param->str, "/D", 2)) { + payload = param->str + 2; + if (*payload == ':') + payload++; + + if (!*payload) { + if (!(lpLine = pBat_GetNextParameterEsD(lpLine, param, PBAT_CHOICE_DELIMS))) { + pBat_ShowErrorMessage(PBAT_EXPECTED_MORE, "CHOICE", FALSE); + status = PBAT_EXPECTED_MORE; + goto end; + } + payload = param->str; + } + + if (!payload[0] || payload[1]) { + pBat_ShowErrorMessage(PBAT_UNEXPECTED_ELEMENT, payload, 0); + status = PBAT_UNEXPECTED_ELEMENT; + goto end; + } + + default_choice = payload[0]; + have_default = 1; + explicit_default = 1; + continue; + } + + if (!strnicmp(param->str, "/T", 2)) { + payload = param->str + 2; + if (*payload == ':') + payload++; + + if (!*payload) { + if (!(lpLine = pBat_GetNextParameterEsD(lpLine, param, PBAT_CHOICE_DELIMS))) { + pBat_ShowErrorMessage(PBAT_EXPECTED_MORE, "CHOICE", FALSE); + status = PBAT_EXPECTED_MORE; + goto end; + } + payload = param->str; + } + + if (payload[0] && payload[1] == ',' && payload[2]) { + default_choice = payload[0]; + have_default = 1; + explicit_default = 1; + payload += 2; + } + + if (!pBat_ChoiceParseSeconds(payload, &timeout_seconds)) { + pBat_ShowErrorMessage(PBAT_UNEXPECTED_ELEMENT, payload, 0); + status = PBAT_UNEXPECTED_ELEMENT; + goto end; + } + + have_timeout = 1; + continue; + } + + pBat_ShowErrorMessage(PBAT_UNEXPECTED_ELEMENT, param->str, 0); + status = PBAT_UNEXPECTED_ELEMENT; + goto end; + } + + if (!choices[0]) { + pBat_ShowErrorMessage(PBAT_UNEXPECTED_ELEMENT, "/C", 0); + status = PBAT_UNEXPECTED_ELEMENT; + goto end; + } + + if ((int)strlen(choices) > PBAT_CHOICE_MAX) { + pBat_ShowErrorMessage(PBAT_UNEXPECTED_ELEMENT, "/C", 0); + status = PBAT_UNEXPECTED_ELEMENT; + goto end; + } + + if (pBat_ChoiceHasDuplicates(choices, case_sensitive)) { + pBat_ShowErrorMessage(PBAT_UNEXPECTED_ELEMENT, "/C", 0); + status = PBAT_UNEXPECTED_ELEMENT; + goto end; + } + + if (!have_default) { + default_choice = choices[0]; + have_default = 1; + } + + if (explicit_default && !have_timeout) { + pBat_ShowErrorMessage(PBAT_UNEXPECTED_ELEMENT, "/D", 0); + status = PBAT_UNEXPECTED_ELEMENT; + goto end; + } + + default_index = pBat_ChoiceParseDefault(choices, default_choice, case_sensitive); + if (!default_index) { + pBat_ShowErrorMessage(PBAT_UNEXPECTED_ELEMENT, "default choice", 0); + status = PBAT_UNEXPECTED_ELEMENT; + goto end; + } + + if (!hide_prompt) + pBat_ChoiceWritePrompt(prompt, choices); + else + pBat_ChoiceWriteMessage(prompt); + + for (;;) { + int key = pBat_ChoiceReadWithTimeout(have_timeout ? timeout_seconds : -1.0); + + if (key < 0) { + choice_index = default_index; + break; + } + + if (key == 3) { + choice_index = 0; + break; + } + + if (key == '\r' || key == '\n') + continue; + + choice_index = pBat_ChoiceFind(choices, key, case_sensitive); + if (choice_index) + break; + + fputc('\a', fOutput); + fflush(fOutput); + } + + if (!hide_prompt) + fputs(PBAT_NL, fOutput); + + status = choice_index; + +end: + pBat_EsFree_Cached(prompt_buf); + pBat_EsFree_Cached(choices_buf); + pBat_EsFree_Cached(param); + return status; +} diff --git a/pbat/command/pBat_Choice.h b/pbat/command/pBat_Choice.h new file mode 100644 index 00000000..34473feb --- /dev/null +++ b/pbat/command/pBat_Choice.h @@ -0,0 +1,26 @@ +/* + * + * pBat - A Free, Cross-platform command prompt - The pBat project + * Copyright (C) 2010-2018 Romain GARBI + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef PBAT_CHOICE_H +#define PBAT_CHOICE_H + +int pBat_CmdChoice(char* lpLine); + +#endif diff --git a/pbat/command/pBat_CommandInfo.c b/pbat/command/pBat_CommandInfo.c index e2976be5..4d44d3c4 100644 --- a/pbat/command/pBat_CommandInfo.c +++ b/pbat/command/pBat_CommandInfo.c @@ -39,6 +39,7 @@ COMMANDINFO lpCmdInfo[]= { {"HELP", pBat_CmdHelp, STRLEN("HELP")}, {"REM", pBat_CmdRem, STRLEN("REM")}, {"CLS", pBat_CmdCls, STRLEN("CLS")}, + {"CHOICE", pBat_CmdChoice, STRLEN("CHOICE")}, {"COLOR", pBat_CmdColor, STRLEN("COLOR")}, {"TITLE", pBat_CmdTitle, STRLEN("TITLE")}, {"TYPE", pBat_CmdType, STRLEN("TYPE")}, diff --git a/pbat/command/pBat_Commands.h b/pbat/command/pBat_Commands.h index 536ce9e7..57050de1 100644 --- a/pbat/command/pBat_Commands.h +++ b/pbat/command/pBat_Commands.h @@ -27,6 +27,7 @@ #include "pBat_Call.h" #include "pBat_Cd.h" #include "pBat_Cls.h" +#include "pBat_Choice.h" #include "pBat_Color.h" #include "pBat_Echo.h" #include "pBat_Exit.h" diff --git a/pbat/lang/pBat_ShowHelp.c b/pbat/lang/pBat_ShowHelp.c index 5a83b3ea..dd1db46e 100644 --- a/pbat/lang/pBat_ShowHelp.c +++ b/pbat/lang/pBat_ShowHelp.c @@ -19,6 +19,7 @@ */ #include #include +#include #include @@ -57,6 +58,11 @@ void pBat_LoadInternalHelp(void) =gettext("Clear console screen\n" "Usage: CLS\n"); + lpInternalHelp[PBAT_HELP_CHOICE] + =gettext("Prompt the user for a single-character choice.\n" + "Usage: CHOICE [/C[:]choices] [/N] [/CS] [/M[:]message] [/T[:]seconds] [/D[:]choice]\n" + " CHOICE [/C[:]choices] [/N] [/CS] [/M[:]message] [/T[:]choice,seconds]\n"); + lpInternalHelp[PBAT_HELP_COLOR] =gettext("Change console color to the given code.\n" "Usage: COLOR [code]\n"); diff --git a/pbat/lang/pBat_ShowHelp.h b/pbat/lang/pBat_ShowHelp.h index 66a5b424..f3891817 100644 --- a/pbat/lang/pBat_ShowHelp.h +++ b/pbat/lang/pBat_ShowHelp.h @@ -36,6 +36,7 @@ #define PBAT_HELP_REM 13 #define PBAT_HELP_DIR 14 #define PBAT_HELP_CLS 16 +#define PBAT_HELP_CHOICE 15 #define PBAT_HELP_TITLE 17 #define PBAT_HELP_REN 18 #define PBAT_HELP_TYPE 19 @@ -61,7 +62,7 @@ #define PBAT_HELP_START 39 #define PBAT_HELP_LOCALE 40 -#define PBAT_HELP_ARRAY_SIZE 41 +#define PBAT_HELP_ARRAY_SIZE 42 /* this is to instanciate a little embedded command From daf370b8a1048a702eca5c8787700bfe4c6557ec Mon Sep 17 00:00:00 2001 From: ostrich <570911+ostrich@users.noreply.github.com> Date: Sun, 22 Mar 2026 08:51:20 -0400 Subject: [PATCH 6/8] tests: add batch regression coverage --- pbat/command/pBat_Type.c | 6 +++++- tests/Makefile | 4 ++-- tests/append_output.txt | 2 ++ tests/basics/pipeext.out.ok | 24 ++++++++++----------- tests/commands/choice.bat | 11 ++++++++++ tests/commands/choice.out.ok | 5 +++++ tests/commands/dir_basic.bat | 6 ++++++ tests/commands/dir_basic.out.ok | 2 ++ tests/commands/external_batch.bat | 2 ++ tests/commands/external_batch.out.ok | 1 + tests/commands/find_count.bat | 5 +++++ tests/commands/find_count.out.ok | 1 + tests/commands/find_insensitive.bat | 3 +++ tests/commands/find_insensitive.out.ok | 1 + tests/commands/find_invert.bat | 5 +++++ tests/commands/find_invert.out.ok | 1 + tests/commands/find_number.bat | 5 +++++ tests/commands/find_number.out.ok | 2 ++ tests/commands/pecho_prompt.bat | 2 ++ tests/commands/pecho_prompt.out.ok | 1 + tests/commands/set_basic.bat | 3 +++ tests/commands/set_basic.out.ok | 1 + tests/commands/setlocal_endlocal.bat | 7 ++++++ tests/commands/setlocal_endlocal.out.ok | 2 ++ tests/commands/type_two_files.bat | 4 ++++ tests/commands/type_two_files.out.ok | 4 ++++ tests/commands/wc_counts.bat | 3 +++ tests/commands/wc_counts.out.ok | 1 + tests/expansion/escaped_delayed.bat | 4 ++++ tests/expansion/escaped_delayed.out.ok | 1 + tests/expansion/replace_expansion.bat | 3 +++ tests/expansion/replace_expansion.out.ok | 1 + tests/expansion/substring_expansion.bat | 3 +++ tests/expansion/substring_expansion.out.ok | 1 + tests/expansion/undefined_var.bat | 2 ++ tests/expansion/undefined_var.out.ok | 1 + tests/files/copy_single.bat | 4 ++++ tests/files/copy_single.out.ok | 1 + tests/files/ren_single.bat | 4 ++++ tests/files/ren_single.out.ok | 1 + tests/find_count.txt | 3 +++ tests/find_i.txt | 1 + tests/find_n.txt | 3 +++ tests/find_v.txt | 3 +++ tests/flow/and_failure.bat | 3 +++ tests/flow/and_failure.out.ok | 1 + tests/flow/and_success.bat | 2 ++ tests/flow/and_success.out.ok | 2 ++ tests/flow/call_label.bat | 8 +++++++ tests/flow/call_label.out.ok | 2 ++ tests/flow/call_percent0_dispatch.bat | 14 ++++++++++++ tests/flow/call_percent0_dispatch.out.ok | 2 ++ tests/flow/call_set_second_expansion.bat | 5 +++++ tests/flow/call_set_second_expansion.out.ok | 1 + tests/flow/for_block.bat | 4 ++++ tests/flow/for_block.out.ok | 2 ++ tests/flow/for_l.bat | 2 ++ tests/flow/for_l.out.ok | 3 +++ tests/flow/for_simple.bat | 2 ++ tests/flow/for_simple.out.ok | 2 ++ tests/flow/goto_label.bat | 5 +++++ tests/flow/goto_label.out.ok | 1 + tests/flow/if_case_insensitive.bat | 2 ++ tests/flow/if_case_insensitive.out.ok | 1 + tests/flow/if_errorlevel.bat | 8 +++++++ tests/flow/if_errorlevel.out.ok | 3 +++ tests/flow/if_false_else.bat | 6 ++++++ tests/flow/if_false_else.out.ok | 1 + tests/flow/if_true.bat | 2 ++ tests/flow/if_true.out.ok | 1 + tests/flow/or_failure.bat | 2 ++ tests/flow/or_failure.out.ok | 1 + tests/flow/or_success.bat | 2 ++ tests/flow/or_success.out.ok | 1 + tests/flow/seq_operator.bat | 2 ++ tests/flow/seq_operator.out.ok | 2 ++ tests/flow/shift_basic.bat | 9 ++++++++ tests/flow/shift_basic.out.ok | 2 ++ tests/helpers/ext_echo.bat | 2 ++ tests/input_find.txt | 1 + tests/math/seta_basic.bat | 3 +++ tests/math/seta_basic.out.ok | 1 + tests/math/seta_shift.bat | 3 +++ tests/math/seta_shift.out.ok | 1 + tests/redirection/append_output.bat | 4 ++++ tests/redirection/append_output.out.ok | 2 ++ tests/redirection/input_find.bat | 3 +++ tests/redirection/input_find.out.ok | 1 + tests/redirection/pipe_chain.bat | 3 +++ tests/redirection/pipe_chain.out.ok | 1 + tests/redirection/truncate_output.bat | 4 ++++ tests/redirection/truncate_output.out.ok | 1 + tests/run-test.cmd | 7 +++++- tests/truncate_output.txt | 1 + tests/type_one.txt | 1 + tests/type_two.txt | 1 + tests/wc_counts.txt | 1 + 97 files changed, 281 insertions(+), 16 deletions(-) create mode 100644 tests/append_output.txt create mode 100644 tests/commands/choice.bat create mode 100644 tests/commands/choice.out.ok create mode 100644 tests/commands/dir_basic.bat create mode 100644 tests/commands/dir_basic.out.ok create mode 100644 tests/commands/external_batch.bat create mode 100644 tests/commands/external_batch.out.ok create mode 100644 tests/commands/find_count.bat create mode 100644 tests/commands/find_count.out.ok create mode 100644 tests/commands/find_insensitive.bat create mode 100644 tests/commands/find_insensitive.out.ok create mode 100644 tests/commands/find_invert.bat create mode 100644 tests/commands/find_invert.out.ok create mode 100644 tests/commands/find_number.bat create mode 100644 tests/commands/find_number.out.ok create mode 100644 tests/commands/pecho_prompt.bat create mode 100644 tests/commands/pecho_prompt.out.ok create mode 100644 tests/commands/set_basic.bat create mode 100644 tests/commands/set_basic.out.ok create mode 100644 tests/commands/setlocal_endlocal.bat create mode 100644 tests/commands/setlocal_endlocal.out.ok create mode 100644 tests/commands/type_two_files.bat create mode 100644 tests/commands/type_two_files.out.ok create mode 100644 tests/commands/wc_counts.bat create mode 100644 tests/commands/wc_counts.out.ok create mode 100644 tests/expansion/escaped_delayed.bat create mode 100644 tests/expansion/escaped_delayed.out.ok create mode 100644 tests/expansion/replace_expansion.bat create mode 100644 tests/expansion/replace_expansion.out.ok create mode 100644 tests/expansion/substring_expansion.bat create mode 100644 tests/expansion/substring_expansion.out.ok create mode 100644 tests/expansion/undefined_var.bat create mode 100644 tests/expansion/undefined_var.out.ok create mode 100644 tests/files/copy_single.bat create mode 100644 tests/files/copy_single.out.ok create mode 100644 tests/files/ren_single.bat create mode 100644 tests/files/ren_single.out.ok create mode 100644 tests/find_count.txt create mode 100644 tests/find_i.txt create mode 100644 tests/find_n.txt create mode 100644 tests/find_v.txt create mode 100644 tests/flow/and_failure.bat create mode 100644 tests/flow/and_failure.out.ok create mode 100644 tests/flow/and_success.bat create mode 100644 tests/flow/and_success.out.ok create mode 100644 tests/flow/call_label.bat create mode 100644 tests/flow/call_label.out.ok create mode 100644 tests/flow/call_percent0_dispatch.bat create mode 100644 tests/flow/call_percent0_dispatch.out.ok create mode 100644 tests/flow/call_set_second_expansion.bat create mode 100644 tests/flow/call_set_second_expansion.out.ok create mode 100644 tests/flow/for_block.bat create mode 100644 tests/flow/for_block.out.ok create mode 100644 tests/flow/for_l.bat create mode 100644 tests/flow/for_l.out.ok create mode 100644 tests/flow/for_simple.bat create mode 100644 tests/flow/for_simple.out.ok create mode 100644 tests/flow/goto_label.bat create mode 100644 tests/flow/goto_label.out.ok create mode 100644 tests/flow/if_case_insensitive.bat create mode 100644 tests/flow/if_case_insensitive.out.ok create mode 100644 tests/flow/if_errorlevel.bat create mode 100644 tests/flow/if_errorlevel.out.ok create mode 100644 tests/flow/if_false_else.bat create mode 100644 tests/flow/if_false_else.out.ok create mode 100644 tests/flow/if_true.bat create mode 100644 tests/flow/if_true.out.ok create mode 100644 tests/flow/or_failure.bat create mode 100644 tests/flow/or_failure.out.ok create mode 100644 tests/flow/or_success.bat create mode 100644 tests/flow/or_success.out.ok create mode 100644 tests/flow/seq_operator.bat create mode 100644 tests/flow/seq_operator.out.ok create mode 100644 tests/flow/shift_basic.bat create mode 100644 tests/flow/shift_basic.out.ok create mode 100644 tests/helpers/ext_echo.bat create mode 100644 tests/input_find.txt create mode 100644 tests/math/seta_basic.bat create mode 100644 tests/math/seta_basic.out.ok create mode 100644 tests/math/seta_shift.bat create mode 100644 tests/math/seta_shift.out.ok create mode 100644 tests/redirection/append_output.bat create mode 100644 tests/redirection/append_output.out.ok create mode 100644 tests/redirection/input_find.bat create mode 100644 tests/redirection/input_find.out.ok create mode 100644 tests/redirection/pipe_chain.bat create mode 100644 tests/redirection/pipe_chain.out.ok create mode 100644 tests/redirection/truncate_output.bat create mode 100644 tests/redirection/truncate_output.out.ok create mode 100644 tests/truncate_output.txt create mode 100644 tests/type_one.txt create mode 100644 tests/type_two.txt create mode 100644 tests/wc_counts.txt diff --git a/pbat/command/pBat_Type.c b/pbat/command/pBat_Type.c index 153c49dd..19fc5a2f 100644 --- a/pbat/command/pBat_Type.c +++ b/pbat/command/pBat_Type.c @@ -200,8 +200,12 @@ int pBat_CmdType(char* lpLine) pTmp = pBegin; while (pTmp) { + char name[_MAX_FNAME] = ""; + char ext[_MAX_EXT] = ""; - fprintf(fOutput, "---------- %s" PBAT_NL , pTmp->lpFileName); + pBat_SplitPath(pTmp->lpFileName, NULL, NULL, name, ext); + + fprintf(fOutput, "---------- %s%s" PBAT_NL , name, ext); status |= pBat_TypeFile(pTmp->lpFileName); pTmp = pTmp->lpflNext; diff --git a/tests/Makefile b/tests/Makefile index 8d1041b9..de030c22 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -19,7 +19,7 @@ ROOTDIR=.. BINDIR?=$(ROOTDIR)/bin PBAT=$(BINDIR)/pbat -TESTS=$(shell find . -name "*.bat") +TESTS=$(shell find . -path "./helpers" -prune -o -name "*.bat" -print) TESTS.OUT=$(TESTS:.bat=.out) @@ -40,4 +40,4 @@ test-clean: *) echo " ERROR";false;; \ esac; .PHONY: test test-clean -.SUFFIXES: .bat .out \ No newline at end of file +.SUFFIXES: .bat .out diff --git a/tests/append_output.txt b/tests/append_output.txt new file mode 100644 index 00000000..814f4a42 --- /dev/null +++ b/tests/append_output.txt @@ -0,0 +1,2 @@ +one +two diff --git a/tests/basics/pipeext.out.ok b/tests/basics/pipeext.out.ok index bb4b2334..a67ef3d5 100644 --- a/tests/basics/pipeext.out.ok +++ b/tests/basics/pipeext.out.ok @@ -1,14 +1,14 @@ 4c 6f 72 65 6d 20 69 70 73 75 6d 20 64 6f 6c 6f 72 20 73 69 74 20 61 6d 65 74 2c 20 63 6f 6e 73 65 63 74 65 74 75 72 20 61 64 69 70 69 73 63 69 6e 67 20 65 -6c 69 74 2e 0d 0a 43 75 72 61 62 69 74 75 72 20 65 67 65 74 20 6a 75 73 74 6f -20 63 6f 6e 76 61 6c 6c 69 73 2c 20 73 6f 6c 6c 69 63 69 74 75 64 69 6e 20 6d -61 73 73 61 20 75 74 2c 20 0d 0a 63 6f 6e 64 69 6d 65 6e 74 75 6d 20 6c 61 63 -75 73 2e 20 45 74 69 61 6d 20 73 61 67 69 74 74 69 73 20 73 75 73 63 69 70 69 -74 20 6e 69 73 6c 20 76 69 74 61 65 20 0d 0a 63 6f 6e 64 69 6d 65 6e 74 75 6d -2e 20 50 68 61 73 65 6c 6c 75 73 20 6d 6f 6c 6c 69 73 2c 20 76 65 6c 69 74 20 -6e 65 63 20 65 6c 65 69 66 65 6e 64 20 6d 61 74 74 69 73 2c 0d 0a 74 6f 72 74 -6f 72 20 74 75 72 70 69 73 20 69 6e 74 65 72 64 75 6d 20 73 65 6d 2c 20 6d 61 -6c 65 73 75 61 64 61 20 6c 61 63 69 6e 69 61 20 65 6e 69 6d 20 70 75 72 75 73 -0d 0a 6e 65 63 20 6e 69 73 6c 2e 20 55 74 20 73 65 6d 70 65 72 20 61 20 74 6f -72 74 6f 72 20 66 69 6e 69 62 75 73 20 61 6c 69 71 75 65 74 2e 20 49 6e 74 65 -72 64 75 6d 20 0d 0a +6c 69 74 2e 0a 43 75 72 61 62 69 74 75 72 20 65 67 65 74 20 6a 75 73 74 6f 20 +63 6f 6e 76 61 6c 6c 69 73 2c 20 73 6f 6c 6c 69 63 69 74 75 64 69 6e 20 6d 61 +73 73 61 20 75 74 2c 20 0a 63 6f 6e 64 69 6d 65 6e 74 75 6d 20 6c 61 63 75 73 +2e 20 45 74 69 61 6d 20 73 61 67 69 74 74 69 73 20 73 75 73 63 69 70 69 74 20 +6e 69 73 6c 20 76 69 74 61 65 20 0a 63 6f 6e 64 69 6d 65 6e 74 75 6d 2e 20 50 +68 61 73 65 6c 6c 75 73 20 6d 6f 6c 6c 69 73 2c 20 76 65 6c 69 74 20 6e 65 63 +20 65 6c 65 69 66 65 6e 64 20 6d 61 74 74 69 73 2c 0a 74 6f 72 74 6f 72 20 74 +75 72 70 69 73 20 69 6e 74 65 72 64 75 6d 20 73 65 6d 2c 20 6d 61 6c 65 73 75 +61 64 61 20 6c 61 63 69 6e 69 61 20 65 6e 69 6d 20 70 75 72 75 73 0a 6e 65 63 +20 6e 69 73 6c 2e 20 55 74 20 73 65 6d 70 65 72 20 61 20 74 6f 72 74 6f 72 20 +66 69 6e 69 62 75 73 20 61 6c 69 71 75 65 74 2e 20 49 6e 74 65 72 64 75 6d 20 +0a diff --git a/tests/commands/choice.bat b/tests/commands/choice.bat new file mode 100644 index 00000000..a37d3c97 --- /dev/null +++ b/tests/commands/choice.bat @@ -0,0 +1,11 @@ +@echo off +choice /c:ABC /n /t:1 /d:B +echo T1=%errorlevel% +choice /c:XYZ /n /m "Pick one" /t:1 /d:Z +echo T2=%errorlevel% +choice /c:Q /n /t:Q,1 +echo T3=%errorlevel% +choice /c:AAB /n /t:1 /d:A +echo T4=%errorlevel% +choice /c:AB /d:A +echo T5=%errorlevel% diff --git a/tests/commands/choice.out.ok b/tests/commands/choice.out.ok new file mode 100644 index 00000000..dff42f88 --- /dev/null +++ b/tests/commands/choice.out.ok @@ -0,0 +1,5 @@ +T1=2 +Pick one T2=3 +T3=1 +T4=4 +T5=4 diff --git a/tests/commands/dir_basic.bat b/tests/commands/dir_basic.bat new file mode 100644 index 00000000..980c19d4 --- /dev/null +++ b/tests/commands/dir_basic.bat @@ -0,0 +1,6 @@ +@echo off +mkdir dir_basic_dir +echo x>dir_basic_dir/a.txt +echo y>dir_basic_dir/b.txt +cd dir_basic_dir +dir /b diff --git a/tests/commands/dir_basic.out.ok b/tests/commands/dir_basic.out.ok new file mode 100644 index 00000000..87122ecd --- /dev/null +++ b/tests/commands/dir_basic.out.ok @@ -0,0 +1,2 @@ +a.txt +b.txt diff --git a/tests/commands/external_batch.bat b/tests/commands/external_batch.bat new file mode 100644 index 00000000..153471fb --- /dev/null +++ b/tests/commands/external_batch.bat @@ -0,0 +1,2 @@ +@echo off +helpers/ext_echo.bat AA BB diff --git a/tests/commands/external_batch.out.ok b/tests/commands/external_batch.out.ok new file mode 100644 index 00000000..a85a770c --- /dev/null +++ b/tests/commands/external_batch.out.ok @@ -0,0 +1 @@ +EXT AA BB diff --git a/tests/commands/find_count.bat b/tests/commands/find_count.bat new file mode 100644 index 00000000..206644ef --- /dev/null +++ b/tests/commands/find_count.bat @@ -0,0 +1,5 @@ +@echo off +echo alpha>find_count.txt +echo beta>>find_count.txt +echo alpha>>find_count.txt +find /c alpha find_count.txt diff --git a/tests/commands/find_count.out.ok b/tests/commands/find_count.out.ok new file mode 100644 index 00000000..0cfbf088 --- /dev/null +++ b/tests/commands/find_count.out.ok @@ -0,0 +1 @@ +2 diff --git a/tests/commands/find_insensitive.bat b/tests/commands/find_insensitive.bat new file mode 100644 index 00000000..023447ec --- /dev/null +++ b/tests/commands/find_insensitive.bat @@ -0,0 +1,3 @@ +@echo off +echo Alpha>find_i.txt +find /i alpha find_i.txt diff --git a/tests/commands/find_insensitive.out.ok b/tests/commands/find_insensitive.out.ok new file mode 100644 index 00000000..52197340 --- /dev/null +++ b/tests/commands/find_insensitive.out.ok @@ -0,0 +1 @@ +Alpha diff --git a/tests/commands/find_invert.bat b/tests/commands/find_invert.bat new file mode 100644 index 00000000..9da48d0c --- /dev/null +++ b/tests/commands/find_invert.bat @@ -0,0 +1,5 @@ +@echo off +echo alpha>find_v.txt +echo beta>>find_v.txt +echo alpha>>find_v.txt +find /v alpha find_v.txt diff --git a/tests/commands/find_invert.out.ok b/tests/commands/find_invert.out.ok new file mode 100644 index 00000000..65b2df87 --- /dev/null +++ b/tests/commands/find_invert.out.ok @@ -0,0 +1 @@ +beta diff --git a/tests/commands/find_number.bat b/tests/commands/find_number.bat new file mode 100644 index 00000000..3d35754b --- /dev/null +++ b/tests/commands/find_number.bat @@ -0,0 +1,5 @@ +@echo off +echo alpha>find_n.txt +echo beta>>find_n.txt +echo alpha>>find_n.txt +find /n alpha find_n.txt diff --git a/tests/commands/find_number.out.ok b/tests/commands/find_number.out.ok new file mode 100644 index 00000000..dee94f7c --- /dev/null +++ b/tests/commands/find_number.out.ok @@ -0,0 +1,2 @@ +[1]alpha +[3]alpha diff --git a/tests/commands/pecho_prompt.bat b/tests/commands/pecho_prompt.bat new file mode 100644 index 00000000..0b3ea955 --- /dev/null +++ b/tests/commands/pecho_prompt.bat @@ -0,0 +1,2 @@ +@echo off +pecho User$g diff --git a/tests/commands/pecho_prompt.out.ok b/tests/commands/pecho_prompt.out.ok new file mode 100644 index 00000000..6c34d2f8 --- /dev/null +++ b/tests/commands/pecho_prompt.out.ok @@ -0,0 +1 @@ +User> diff --git a/tests/commands/set_basic.bat b/tests/commands/set_basic.bat new file mode 100644 index 00000000..5bed2c84 --- /dev/null +++ b/tests/commands/set_basic.bat @@ -0,0 +1,3 @@ +@echo off +set NAME=World +echo Hello %NAME% diff --git a/tests/commands/set_basic.out.ok b/tests/commands/set_basic.out.ok new file mode 100644 index 00000000..557db03d --- /dev/null +++ b/tests/commands/set_basic.out.ok @@ -0,0 +1 @@ +Hello World diff --git a/tests/commands/setlocal_endlocal.bat b/tests/commands/setlocal_endlocal.bat new file mode 100644 index 00000000..807bb901 --- /dev/null +++ b/tests/commands/setlocal_endlocal.bat @@ -0,0 +1,7 @@ +@echo off +set VAR=outer +setlocal +set VAR=inner +echo A=%VAR% +endlocal +echo B=%VAR% diff --git a/tests/commands/setlocal_endlocal.out.ok b/tests/commands/setlocal_endlocal.out.ok new file mode 100644 index 00000000..3f22e295 --- /dev/null +++ b/tests/commands/setlocal_endlocal.out.ok @@ -0,0 +1,2 @@ +A=inner +B=outer diff --git a/tests/commands/type_two_files.bat b/tests/commands/type_two_files.bat new file mode 100644 index 00000000..c2775d56 --- /dev/null +++ b/tests/commands/type_two_files.bat @@ -0,0 +1,4 @@ +@echo off +echo one>type_one.txt +echo two>type_two.txt +type type_one.txt type_two.txt diff --git a/tests/commands/type_two_files.out.ok b/tests/commands/type_two_files.out.ok new file mode 100644 index 00000000..d670c397 --- /dev/null +++ b/tests/commands/type_two_files.out.ok @@ -0,0 +1,4 @@ +---------- type_one.txt +one +---------- type_two.txt +two diff --git a/tests/commands/wc_counts.bat b/tests/commands/wc_counts.bat new file mode 100644 index 00000000..8d825b9e --- /dev/null +++ b/tests/commands/wc_counts.bat @@ -0,0 +1,3 @@ +@echo off +echo hello>wc_counts.txt +wc /l /c wc_counts.txt diff --git a/tests/commands/wc_counts.out.ok b/tests/commands/wc_counts.out.ok new file mode 100644 index 00000000..f54496ff --- /dev/null +++ b/tests/commands/wc_counts.out.ok @@ -0,0 +1 @@ +1 6 wc_counts.txt diff --git a/tests/expansion/escaped_delayed.bat b/tests/expansion/escaped_delayed.bat new file mode 100644 index 00000000..9c70cb0a --- /dev/null +++ b/tests/expansion/escaped_delayed.bat @@ -0,0 +1,4 @@ +@echo off +setlocal enabledelayedexpansion +set A=hello +echo ^!A^! diff --git a/tests/expansion/escaped_delayed.out.ok b/tests/expansion/escaped_delayed.out.ok new file mode 100644 index 00000000..1b5cc57a --- /dev/null +++ b/tests/expansion/escaped_delayed.out.ok @@ -0,0 +1 @@ +!A! diff --git a/tests/expansion/replace_expansion.bat b/tests/expansion/replace_expansion.bat new file mode 100644 index 00000000..9e331269 --- /dev/null +++ b/tests/expansion/replace_expansion.bat @@ -0,0 +1,3 @@ +@echo off +set VALUE=abcabc +echo %VALUE:ab=XY% diff --git a/tests/expansion/replace_expansion.out.ok b/tests/expansion/replace_expansion.out.ok new file mode 100644 index 00000000..0ebafc20 --- /dev/null +++ b/tests/expansion/replace_expansion.out.ok @@ -0,0 +1 @@ +XYcXYc diff --git a/tests/expansion/substring_expansion.bat b/tests/expansion/substring_expansion.bat new file mode 100644 index 00000000..dbd926cd --- /dev/null +++ b/tests/expansion/substring_expansion.bat @@ -0,0 +1,3 @@ +@echo off +set VALUE=abcdef +echo %VALUE:~1,3% diff --git a/tests/expansion/substring_expansion.out.ok b/tests/expansion/substring_expansion.out.ok new file mode 100644 index 00000000..a48a3b33 --- /dev/null +++ b/tests/expansion/substring_expansion.out.ok @@ -0,0 +1 @@ +bcd diff --git a/tests/expansion/undefined_var.bat b/tests/expansion/undefined_var.bat new file mode 100644 index 00000000..4b3379d8 --- /dev/null +++ b/tests/expansion/undefined_var.bat @@ -0,0 +1,2 @@ +@echo off +echo X%NO_SUCH_TEST_VAR%Y diff --git a/tests/expansion/undefined_var.out.ok b/tests/expansion/undefined_var.out.ok new file mode 100644 index 00000000..28ff5acf --- /dev/null +++ b/tests/expansion/undefined_var.out.ok @@ -0,0 +1 @@ +XY diff --git a/tests/files/copy_single.bat b/tests/files/copy_single.bat new file mode 100644 index 00000000..404cd1af --- /dev/null +++ b/tests/files/copy_single.bat @@ -0,0 +1,4 @@ +@echo off +echo copy>copy_src.txt +copy /Y copy_src.txt copy_dst.txt +type copy_dst.txt diff --git a/tests/files/copy_single.out.ok b/tests/files/copy_single.out.ok new file mode 100644 index 00000000..00ae42f9 --- /dev/null +++ b/tests/files/copy_single.out.ok @@ -0,0 +1 @@ +copy diff --git a/tests/files/ren_single.bat b/tests/files/ren_single.bat new file mode 100644 index 00000000..5fc76b98 --- /dev/null +++ b/tests/files/ren_single.bat @@ -0,0 +1,4 @@ +@echo off +echo rename>ren_src.txt +ren ren_src.txt ren_dst.txt +type ren_dst.txt diff --git a/tests/files/ren_single.out.ok b/tests/files/ren_single.out.ok new file mode 100644 index 00000000..a4ec98f2 --- /dev/null +++ b/tests/files/ren_single.out.ok @@ -0,0 +1 @@ +rename diff --git a/tests/find_count.txt b/tests/find_count.txt new file mode 100644 index 00000000..b65aabf2 --- /dev/null +++ b/tests/find_count.txt @@ -0,0 +1,3 @@ +alpha +beta +alpha diff --git a/tests/find_i.txt b/tests/find_i.txt new file mode 100644 index 00000000..52197340 --- /dev/null +++ b/tests/find_i.txt @@ -0,0 +1 @@ +Alpha diff --git a/tests/find_n.txt b/tests/find_n.txt new file mode 100644 index 00000000..b65aabf2 --- /dev/null +++ b/tests/find_n.txt @@ -0,0 +1,3 @@ +alpha +beta +alpha diff --git a/tests/find_v.txt b/tests/find_v.txt new file mode 100644 index 00000000..b65aabf2 --- /dev/null +++ b/tests/find_v.txt @@ -0,0 +1,3 @@ +alpha +beta +alpha diff --git a/tests/flow/and_failure.bat b/tests/flow/and_failure.bat new file mode 100644 index 00000000..093890a4 --- /dev/null +++ b/tests/flow/and_failure.bat @@ -0,0 +1,3 @@ +@echo off +missing_command_42 && echo BAD +echo done diff --git a/tests/flow/and_failure.out.ok b/tests/flow/and_failure.out.ok new file mode 100644 index 00000000..19f86f49 --- /dev/null +++ b/tests/flow/and_failure.out.ok @@ -0,0 +1 @@ +done diff --git a/tests/flow/and_success.bat b/tests/flow/and_success.bat new file mode 100644 index 00000000..bddbb070 --- /dev/null +++ b/tests/flow/and_success.bat @@ -0,0 +1,2 @@ +@echo off +echo ok && echo after diff --git a/tests/flow/and_success.out.ok b/tests/flow/and_success.out.ok new file mode 100644 index 00000000..6ae366c2 --- /dev/null +++ b/tests/flow/and_success.out.ok @@ -0,0 +1,2 @@ +ok +after diff --git a/tests/flow/call_label.bat b/tests/flow/call_label.bat new file mode 100644 index 00000000..66221ef1 --- /dev/null +++ b/tests/flow/call_label.bat @@ -0,0 +1,8 @@ +@echo off +call :sub hello +echo done +goto end +:sub +echo sub %1 +exit /b +:end diff --git a/tests/flow/call_label.out.ok b/tests/flow/call_label.out.ok new file mode 100644 index 00000000..f044796d --- /dev/null +++ b/tests/flow/call_label.out.ok @@ -0,0 +1,2 @@ +sub hello +done diff --git a/tests/flow/call_percent0_dispatch.bat b/tests/flow/call_percent0_dispatch.bat new file mode 100644 index 00000000..429f3d29 --- /dev/null +++ b/tests/flow/call_percent0_dispatch.bat @@ -0,0 +1,14 @@ +@echo off +if "%1"=="row" goto row +goto main + +:main +call %0 row 1 +echo done +goto end + +:row +echo row %2 +goto end + +:end diff --git a/tests/flow/call_percent0_dispatch.out.ok b/tests/flow/call_percent0_dispatch.out.ok new file mode 100644 index 00000000..65769cbd --- /dev/null +++ b/tests/flow/call_percent0_dispatch.out.ok @@ -0,0 +1,2 @@ +row 1 +done diff --git a/tests/flow/call_set_second_expansion.bat b/tests/flow/call_set_second_expansion.bat new file mode 100644 index 00000000..23f8aa5c --- /dev/null +++ b/tests/flow/call_set_second_expansion.bat @@ -0,0 +1,5 @@ +@echo off +set C1_1=0 +set ARG=1 +call set V1=%%C1_%ARG%%% +echo V1=%V1% diff --git a/tests/flow/call_set_second_expansion.out.ok b/tests/flow/call_set_second_expansion.out.ok new file mode 100644 index 00000000..e56d6689 --- /dev/null +++ b/tests/flow/call_set_second_expansion.out.ok @@ -0,0 +1 @@ +V1=0 diff --git a/tests/flow/for_block.bat b/tests/flow/for_block.bat new file mode 100644 index 00000000..6c7df7bb --- /dev/null +++ b/tests/flow/for_block.bat @@ -0,0 +1,4 @@ +@echo off +for %%A in (sun moon) do ( +echo item %%A +) diff --git a/tests/flow/for_block.out.ok b/tests/flow/for_block.out.ok new file mode 100644 index 00000000..d723bbcd --- /dev/null +++ b/tests/flow/for_block.out.ok @@ -0,0 +1,2 @@ +item sun +item moon diff --git a/tests/flow/for_l.bat b/tests/flow/for_l.bat new file mode 100644 index 00000000..047b8ca0 --- /dev/null +++ b/tests/flow/for_l.bat @@ -0,0 +1,2 @@ +@echo off +for /l %%I in (1,1,3) do echo %%I diff --git a/tests/flow/for_l.out.ok b/tests/flow/for_l.out.ok new file mode 100644 index 00000000..01e79c32 --- /dev/null +++ b/tests/flow/for_l.out.ok @@ -0,0 +1,3 @@ +1 +2 +3 diff --git a/tests/flow/for_simple.bat b/tests/flow/for_simple.bat new file mode 100644 index 00000000..0e77971c --- /dev/null +++ b/tests/flow/for_simple.bat @@ -0,0 +1,2 @@ +@echo off +for %%A in (red blue) do echo %%A diff --git a/tests/flow/for_simple.out.ok b/tests/flow/for_simple.out.ok new file mode 100644 index 00000000..836673fa --- /dev/null +++ b/tests/flow/for_simple.out.ok @@ -0,0 +1,2 @@ +red +blue diff --git a/tests/flow/goto_label.bat b/tests/flow/goto_label.bat new file mode 100644 index 00000000..0d00783e --- /dev/null +++ b/tests/flow/goto_label.bat @@ -0,0 +1,5 @@ +@echo off +goto target +echo skipped +:target +echo landed diff --git a/tests/flow/goto_label.out.ok b/tests/flow/goto_label.out.ok new file mode 100644 index 00000000..0e08268d --- /dev/null +++ b/tests/flow/goto_label.out.ok @@ -0,0 +1 @@ +landed diff --git a/tests/flow/if_case_insensitive.bat b/tests/flow/if_case_insensitive.bat new file mode 100644 index 00000000..160c0798 --- /dev/null +++ b/tests/flow/if_case_insensitive.bat @@ -0,0 +1,2 @@ +@echo off +if /i AbC==aBc echo match diff --git a/tests/flow/if_case_insensitive.out.ok b/tests/flow/if_case_insensitive.out.ok new file mode 100644 index 00000000..02b1d1a9 --- /dev/null +++ b/tests/flow/if_case_insensitive.out.ok @@ -0,0 +1 @@ +match diff --git a/tests/flow/if_errorlevel.bat b/tests/flow/if_errorlevel.bat new file mode 100644 index 00000000..cca7dc57 --- /dev/null +++ b/tests/flow/if_errorlevel.bat @@ -0,0 +1,8 @@ +@echo off +set ERRORLEVEL=0 +if errorlevel 1 echo bad1 +set ERRORLEVEL=3 +if errorlevel 4 echo bad2 +if errorlevel 3 echo ge3 +if errorlevel 2 echo ge2 +if errorlevel 1 echo ge1 diff --git a/tests/flow/if_errorlevel.out.ok b/tests/flow/if_errorlevel.out.ok new file mode 100644 index 00000000..db9da70d --- /dev/null +++ b/tests/flow/if_errorlevel.out.ok @@ -0,0 +1,3 @@ +ge3 +ge2 +ge1 diff --git a/tests/flow/if_false_else.bat b/tests/flow/if_false_else.bat new file mode 100644 index 00000000..4a3f6446 --- /dev/null +++ b/tests/flow/if_false_else.bat @@ -0,0 +1,6 @@ +@echo off +if a==b ( +echo wrong +) else ( +echo right +) diff --git a/tests/flow/if_false_else.out.ok b/tests/flow/if_false_else.out.ok new file mode 100644 index 00000000..c376d892 --- /dev/null +++ b/tests/flow/if_false_else.out.ok @@ -0,0 +1 @@ +right diff --git a/tests/flow/if_true.bat b/tests/flow/if_true.bat new file mode 100644 index 00000000..185ef59a --- /dev/null +++ b/tests/flow/if_true.bat @@ -0,0 +1,2 @@ +@echo off +if a==a echo yes diff --git a/tests/flow/if_true.out.ok b/tests/flow/if_true.out.ok new file mode 100644 index 00000000..7cfab5b0 --- /dev/null +++ b/tests/flow/if_true.out.ok @@ -0,0 +1 @@ +yes diff --git a/tests/flow/or_failure.bat b/tests/flow/or_failure.bat new file mode 100644 index 00000000..c947b772 --- /dev/null +++ b/tests/flow/or_failure.bat @@ -0,0 +1,2 @@ +@echo off +missing_command_43 || echo fallback diff --git a/tests/flow/or_failure.out.ok b/tests/flow/or_failure.out.ok new file mode 100644 index 00000000..55cb19ad --- /dev/null +++ b/tests/flow/or_failure.out.ok @@ -0,0 +1 @@ +fallback diff --git a/tests/flow/or_success.bat b/tests/flow/or_success.bat new file mode 100644 index 00000000..283e3ce3 --- /dev/null +++ b/tests/flow/or_success.bat @@ -0,0 +1,2 @@ +@echo off +echo ok || echo never diff --git a/tests/flow/or_success.out.ok b/tests/flow/or_success.out.ok new file mode 100644 index 00000000..8106169c --- /dev/null +++ b/tests/flow/or_success.out.ok @@ -0,0 +1 @@ +ok diff --git a/tests/flow/seq_operator.bat b/tests/flow/seq_operator.bat new file mode 100644 index 00000000..0a786663 --- /dev/null +++ b/tests/flow/seq_operator.bat @@ -0,0 +1,2 @@ +@echo off +echo one & echo two diff --git a/tests/flow/seq_operator.out.ok b/tests/flow/seq_operator.out.ok new file mode 100644 index 00000000..ed5b88fb --- /dev/null +++ b/tests/flow/seq_operator.out.ok @@ -0,0 +1,2 @@ +one +two diff --git a/tests/flow/shift_basic.bat b/tests/flow/shift_basic.bat new file mode 100644 index 00000000..23798b5b --- /dev/null +++ b/tests/flow/shift_basic.bat @@ -0,0 +1,9 @@ +@echo off +call :show first second third +goto end +:show +echo 1=%1 2=%2 +shift +echo 1=%1 2=%2 +exit /b +:end diff --git a/tests/flow/shift_basic.out.ok b/tests/flow/shift_basic.out.ok new file mode 100644 index 00000000..44c16b1e --- /dev/null +++ b/tests/flow/shift_basic.out.ok @@ -0,0 +1,2 @@ +1=first 2=second +1=second 2=third diff --git a/tests/helpers/ext_echo.bat b/tests/helpers/ext_echo.bat new file mode 100644 index 00000000..b926c760 --- /dev/null +++ b/tests/helpers/ext_echo.bat @@ -0,0 +1,2 @@ +@echo off +echo EXT %1 %2 diff --git a/tests/input_find.txt b/tests/input_find.txt new file mode 100644 index 00000000..ce013625 --- /dev/null +++ b/tests/input_find.txt @@ -0,0 +1 @@ +hello diff --git a/tests/math/seta_basic.bat b/tests/math/seta_basic.bat new file mode 100644 index 00000000..18ff4592 --- /dev/null +++ b/tests/math/seta_basic.bat @@ -0,0 +1,3 @@ +@echo off +set /a NUM=1+2*3 +echo %NUM% diff --git a/tests/math/seta_basic.out.ok b/tests/math/seta_basic.out.ok new file mode 100644 index 00000000..7f8f011e --- /dev/null +++ b/tests/math/seta_basic.out.ok @@ -0,0 +1 @@ +7 diff --git a/tests/math/seta_shift.bat b/tests/math/seta_shift.bat new file mode 100644 index 00000000..a394bdd1 --- /dev/null +++ b/tests/math/seta_shift.bat @@ -0,0 +1,3 @@ +@echo off +set /a NUM=(10+6)/2 +echo %NUM% diff --git a/tests/math/seta_shift.out.ok b/tests/math/seta_shift.out.ok new file mode 100644 index 00000000..45a4fb75 --- /dev/null +++ b/tests/math/seta_shift.out.ok @@ -0,0 +1 @@ +8 diff --git a/tests/redirection/append_output.bat b/tests/redirection/append_output.bat new file mode 100644 index 00000000..aa5a585f --- /dev/null +++ b/tests/redirection/append_output.bat @@ -0,0 +1,4 @@ +@echo off +echo one>append_output.txt +echo two>>append_output.txt +type append_output.txt diff --git a/tests/redirection/append_output.out.ok b/tests/redirection/append_output.out.ok new file mode 100644 index 00000000..814f4a42 --- /dev/null +++ b/tests/redirection/append_output.out.ok @@ -0,0 +1,2 @@ +one +two diff --git a/tests/redirection/input_find.bat b/tests/redirection/input_find.bat new file mode 100644 index 00000000..2cc6f419 --- /dev/null +++ b/tests/redirection/input_find.bat @@ -0,0 +1,3 @@ +@echo off +echo hello>input_find.txt +find hello < input_find.txt diff --git a/tests/redirection/input_find.out.ok b/tests/redirection/input_find.out.ok new file mode 100644 index 00000000..ce013625 --- /dev/null +++ b/tests/redirection/input_find.out.ok @@ -0,0 +1 @@ +hello diff --git a/tests/redirection/pipe_chain.bat b/tests/redirection/pipe_chain.bat new file mode 100644 index 00000000..18ad3223 --- /dev/null +++ b/tests/redirection/pipe_chain.bat @@ -0,0 +1,3 @@ +@echo off +(echo alpha +echo beta) | find a | find h diff --git a/tests/redirection/pipe_chain.out.ok b/tests/redirection/pipe_chain.out.ok new file mode 100644 index 00000000..4a580070 --- /dev/null +++ b/tests/redirection/pipe_chain.out.ok @@ -0,0 +1 @@ +alpha diff --git a/tests/redirection/truncate_output.bat b/tests/redirection/truncate_output.bat new file mode 100644 index 00000000..009fb1b3 --- /dev/null +++ b/tests/redirection/truncate_output.bat @@ -0,0 +1,4 @@ +@echo off +echo old>truncate_output.txt +echo new>truncate_output.txt +type truncate_output.txt diff --git a/tests/redirection/truncate_output.out.ok b/tests/redirection/truncate_output.out.ok new file mode 100644 index 00000000..3e757656 --- /dev/null +++ b/tests/redirection/truncate_output.out.ok @@ -0,0 +1 @@ +new diff --git a/tests/run-test.cmd b/tests/run-test.cmd index 30916042..5d45eb5d 100644 --- a/tests/run-test.cmd +++ b/tests/run-test.cmd @@ -1,2 +1,7 @@ @echo off -%1 > %2 \ No newline at end of file +if %PBAT_OS%==WINDOWS ( +set PATH=../dump;!PATH! +) else ( +set PATH=!PATH!:../dump +) +%1 > %2 diff --git a/tests/truncate_output.txt b/tests/truncate_output.txt new file mode 100644 index 00000000..3e757656 --- /dev/null +++ b/tests/truncate_output.txt @@ -0,0 +1 @@ +new diff --git a/tests/type_one.txt b/tests/type_one.txt new file mode 100644 index 00000000..5626abf0 --- /dev/null +++ b/tests/type_one.txt @@ -0,0 +1 @@ +one diff --git a/tests/type_two.txt b/tests/type_two.txt new file mode 100644 index 00000000..f719efd4 --- /dev/null +++ b/tests/type_two.txt @@ -0,0 +1 @@ +two diff --git a/tests/wc_counts.txt b/tests/wc_counts.txt new file mode 100644 index 00000000..ce013625 --- /dev/null +++ b/tests/wc_counts.txt @@ -0,0 +1 @@ +hello From 9234eb5df43bc3ad8c49ba49d35cf721c2af8e51 Mon Sep 17 00:00:00 2001 From: ostrich <570911+ostrich@users.noreply.github.com> Date: Mon, 23 Mar 2026 10:41:48 -0400 Subject: [PATCH 7/8] core: polish interactive prompt and command output --- pbat/command/pBat_Choice.c | 9 +++++++-- pbat/command/pBat_Dir.c | 25 ++++++++++++------------- pbat/core/pBat_Prompt.c | 1 + 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/pbat/command/pBat_Choice.c b/pbat/command/pBat_Choice.c index ca900287..d6dc63d1 100644 --- a/pbat/command/pBat_Choice.c +++ b/pbat/command/pBat_Choice.c @@ -22,6 +22,7 @@ #include #include #include +#include #if !defined(WIN32) #include @@ -427,8 +428,12 @@ int pBat_CmdChoice(char* lpLine) } if (key == 3) { - choice_index = 0; - break; +#if !defined(WIN32) + raise(SIGINT); +#endif + bAbortCommand = PBAT_ABORT_EXECUTION_LEVEL; + status = PBAT_BREAK_ERROR; + goto end; } if (key == '\r' || key == '\n') diff --git a/pbat/command/pBat_Dir.c b/pbat/command/pBat_Dir.c index 00ad100a..d573c291 100644 --- a/pbat/command/pBat_Dir.c +++ b/pbat/command/pBat_Dir.c @@ -66,7 +66,7 @@ int pBat_CmdDir(char* lpLine) char bSimple; size_t nSize = 0; - char lpType[]="D RHSA ", + char lpType[]="D RHSA ", lpSize[16], lpTime[30]; @@ -158,8 +158,8 @@ int pBat_CmdDir(char* lpLine) if (!bSimple) { fputs(PBAT_NL, fOutput); - fputs(lpDirListTitle, fOutput); - fputs(PBAT_NL, fOutput); + fprintf(fOutput, "%-19s %-10s %-7s %s" PBAT_NL, + "Last change", "Size", "Attr.", "Name"); } @@ -187,12 +187,11 @@ int pBat_CmdDir(char* lpLine) lpType[0]='D'; iDirNb++; - strcpy(lpSize, "\t"); + snprintf(lpSize, sizeof(lpSize), "%s", ""); } else { - strcpy(lpSize, " "); - pBat_FormatFileSize(lpSize+7, 8, pBat_GetFileSize(item)); + pBat_FormatFileSize(lpSize, sizeof(lpSize), pBat_GetFileSize(item)); iFileNb++; @@ -214,11 +213,11 @@ int pBat_CmdDir(char* lpLine) localtime_r(&(pBat_GetModifTime(item)), &lTime); strftime(lpTime, sizeof(lpTime), "%x %X", &lTime); - fprintf(fOutput, "%s %s\t%s\t%s" PBAT_NL, lpTime, - lpSize, - lpType, - item->lpFileName + nSize - ); + fprintf(fOutput, "%-19s %-10s %-7s %s" PBAT_NL, + lpTime, + lpSize, + lpType, + item->lpFileName + nSize); } else { @@ -236,8 +235,8 @@ int pBat_CmdDir(char* lpLine) pBat_FreeFileList(files); if (!bSimple) - fprintf(fOutput, "\t\t\t\t%d %s" PBAT_NL "\t\t\t\t%d %s" PBAT_NL, - iFileNb, lpDirFile, iDirNb, lpDirDir); + fprintf(fOutput, "%30s%d %s" PBAT_NL "%30s%d %s" PBAT_NL, + "", iFileNb, lpDirFile, "", iDirNb, lpDirDir); } diff --git a/pbat/core/pBat_Prompt.c b/pbat/core/pBat_Prompt.c index 1e32818b..d07bf1c2 100644 --- a/pbat/core/pBat_Prompt.c +++ b/pbat/core/pBat_Prompt.c @@ -174,6 +174,7 @@ void pBat_OutputPromptString(const char* prompt) count = strlen(prompt); fwrite(prompt, 1, count, fOutput); + fflush(fOutput); } From 320e337f24958025ae37169f1a364d27a0e6545b Mon Sep 17 00:00:00 2001 From: ostrich <570911+ostrich@users.noreply.github.com> Date: Mon, 23 Mar 2026 21:30:15 -0400 Subject: [PATCH 8/8] command: address CHOICE review feedback --- pbat/command/pBat_Choice.c | 50 +++++++++++++++++++------------------- pbat/lang/pBat_ShowHelp.h | 4 +-- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/pbat/command/pBat_Choice.c b/pbat/command/pBat_Choice.c index d6dc63d1..b54036ef 100644 --- a/pbat/command/pBat_Choice.c +++ b/pbat/command/pBat_Choice.c @@ -97,7 +97,7 @@ static void pBat_ChoiceWriteMessage(const char* message) fflush(fOutput); } -static int pBat_ChoiceParseSeconds(const char* arg, double* seconds) +static int pBat_ChoiceParseSeconds(const char* arg, int* timeout_ms) { char* endptr; long value; @@ -106,7 +106,7 @@ static int pBat_ChoiceParseSeconds(const char* arg, double* seconds) if (endptr == NULL || *endptr || value < 0 || value > 9999) return 0; - *seconds = (double)value; + *timeout_ms = (int)(value * 1000); return 1; } @@ -134,7 +134,7 @@ static int pBat_ChoiceParseDefault(const char* choices, char def, int case_sensi return pBat_ChoiceFind(choices, def, case_sensitive); } -static int pBat_ChoiceReadWithTimeout(double timeout_seconds) +static int pBat_ChoiceReadWithTimeout(int timeout_ms) { #if !defined(WIN32) struct termios oldattr, rawattr; @@ -150,7 +150,7 @@ static int pBat_ChoiceReadWithTimeout(double timeout_seconds) restore = 1; } - if (timeout_seconds < 0.0) { + if (timeout_ms < 0) { for (;;) { fd_set rfds; @@ -167,16 +167,16 @@ static int pBat_ChoiceReadWithTimeout(double timeout_seconds) } } - while (timeout_seconds > 0.0) { + while (timeout_ms > 0) { fd_set rfds; struct timeval tv; - double wait_seconds = timeout_seconds; + int wait_ms = timeout_ms; - if (wait_seconds > (PBAT_CHOICE_SLEEP / 1000.0)) - wait_seconds = (PBAT_CHOICE_SLEEP / 1000.0); + if (wait_ms > PBAT_CHOICE_SLEEP) + wait_ms = PBAT_CHOICE_SLEEP; - tv.tv_sec = (time_t)wait_seconds; - tv.tv_usec = (suseconds_t)((wait_seconds - tv.tv_sec) * 1000000.0); + tv.tv_sec = wait_ms / 1000; + tv.tv_usec = (suseconds_t)((wait_ms % 1000) * 1000); FD_ZERO(&rfds); FD_SET(fd, &rfds); @@ -189,29 +189,29 @@ static int pBat_ChoiceReadWithTimeout(double timeout_seconds) return ch; } - timeout_seconds -= wait_seconds; + timeout_ms -= wait_ms; } if (restore) tcsetattr(fd, TCSANOW, &oldattr); return -1; #else - if (timeout_seconds < 0.0) { + if (timeout_ms < 0) { while (!pBat_Kbhit(fInput)) pBat_Sleep(PBAT_CHOICE_SLEEP); return pBat_Getch(fInput); } - while (timeout_seconds > 0.0) { + while (timeout_ms > 0) { if (pBat_Kbhit(fInput)) return pBat_Getch(fInput); - if (timeout_seconds > (PBAT_CHOICE_SLEEP / 1000.0)) { + if (timeout_ms > PBAT_CHOICE_SLEEP) { pBat_Sleep(PBAT_CHOICE_SLEEP); - timeout_seconds -= (PBAT_CHOICE_SLEEP / 1000.0); + timeout_ms -= PBAT_CHOICE_SLEEP; } else { - pBat_Sleep((unsigned int)(timeout_seconds * 1000.0)); - timeout_seconds = 0.0; + pBat_Sleep((unsigned int)timeout_ms); + timeout_ms = 0; } } @@ -229,7 +229,7 @@ int pBat_CmdChoice(char* lpLine) int have_default = 0; int explicit_default = 0; int default_index = 0; - double timeout_seconds = -1.0; + int timeout_ms = -1; char default_choice = '\0'; const char* choices = "YN"; const char* prompt = NULL; @@ -256,6 +256,11 @@ int pBat_CmdChoice(char* lpLine) goto end; } + if (!stricmp(param->str, "/CS")) { + case_sensitive = 1; + continue; + } + if (!strnicmp(param->str, "/C", 2)) { payload = param->str + 2; if (*payload == ':') @@ -286,11 +291,6 @@ int pBat_CmdChoice(char* lpLine) continue; } - if (!stricmp(param->str, "/CS")) { - case_sensitive = 1; - continue; - } - if (!strnicmp(param->str, "/M", 2)) { payload = param->str + 2; if (*payload == ':') @@ -363,7 +363,7 @@ int pBat_CmdChoice(char* lpLine) payload += 2; } - if (!pBat_ChoiceParseSeconds(payload, &timeout_seconds)) { + if (!pBat_ChoiceParseSeconds(payload, &timeout_ms)) { pBat_ShowErrorMessage(PBAT_UNEXPECTED_ELEMENT, payload, 0); status = PBAT_UNEXPECTED_ELEMENT; goto end; @@ -420,7 +420,7 @@ int pBat_CmdChoice(char* lpLine) pBat_ChoiceWriteMessage(prompt); for (;;) { - int key = pBat_ChoiceReadWithTimeout(have_timeout ? timeout_seconds : -1.0); + int key = pBat_ChoiceReadWithTimeout(have_timeout ? timeout_ms : -1); if (key < 0) { choice_index = default_index; diff --git a/pbat/lang/pBat_ShowHelp.h b/pbat/lang/pBat_ShowHelp.h index f3891817..3b382986 100644 --- a/pbat/lang/pBat_ShowHelp.h +++ b/pbat/lang/pBat_ShowHelp.h @@ -35,8 +35,8 @@ #define PBAT_HELP_SETLOCAL 12 #define PBAT_HELP_REM 13 #define PBAT_HELP_DIR 14 -#define PBAT_HELP_CLS 16 #define PBAT_HELP_CHOICE 15 +#define PBAT_HELP_CLS 16 #define PBAT_HELP_TITLE 17 #define PBAT_HELP_REN 18 #define PBAT_HELP_TYPE 19 @@ -62,7 +62,7 @@ #define PBAT_HELP_START 39 #define PBAT_HELP_LOCALE 40 -#define PBAT_HELP_ARRAY_SIZE 42 +#define PBAT_HELP_ARRAY_SIZE 41 /* this is to instanciate a little embedded command