From a7b9f4369f69bc07ed533fe3f1518580cb83687b Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Wed, 1 Oct 2025 17:48:02 +1000 Subject: [PATCH] misc scriptapps and file/folder organisation --- .gitignore | 13 +- getpunk.ps1 | 587 ++++++++++++------ src/embedded/README.md | 5 + src/modules_tcl8/README.md | 11 + src/modules_tcl9/README.md | 11 + src/runtime/Readme.md | 18 +- src/runtime/mapvfs.config | 82 +-- src/scriptapps/wrappers/README.md | 93 +-- ...ishell.cmd => sample_basic-multishell.cmd} | 21 +- ...shellbat.bat => sample_basic-shellbat.bat} | 23 +- .../wrappers/sample_punk-shellbat.bat | 112 ---- 11 files changed, 508 insertions(+), 468 deletions(-) create mode 100644 src/embedded/README.md create mode 100644 src/modules_tcl8/README.md create mode 100644 src/modules_tcl9/README.md rename src/scriptapps/wrappers/{sample_punk-multishell.cmd => sample_basic-multishell.cmd} (96%) rename src/scriptapps/wrappers/{punk-shellbat.bat => sample_basic-shellbat.bat} (88%) delete mode 100644 src/scriptapps/wrappers/sample_punk-shellbat.bat diff --git a/.gitignore b/.gitignore index 88ba4625..71e14ab1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,21 @@ +#while bin is primarily an output - some items here are for bootstrapping - so while they aren't the source - we install and source-control them. /bin/* !/bin/*.cmd !/bin/*.kit !/bin/*.tcl !/bin/*.sh !/bin/*.bash + + +#The directories for compiled/built Tcl packages (libs and modules) +#These are purely 'output' folders - no need to track /lib/ -#The directory for compiled/built Tcl modules +/lib_tcl8/ +/lib_tcl9/ /modules/ +/modules_tcl8/ +/modules_tcl9/ /vendorbuilds/ #Temporary files e.g from tests @@ -25,6 +33,9 @@ scratch* /doc/ /test* +/src/testdata +/src/scriptapps/test_* + #Built tclkits (if any) *.exe diff --git a/getpunk.ps1 b/getpunk.ps1 index e1bc3be5..6622f87a 100644 --- a/getpunk.ps1 +++ b/getpunk.ps1 @@ -1,4 +1,4 @@ -: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set S;proc Hide shell_not_supported {proc $shell_not_supported args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^ +: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh zsh/bash cmd pwsh powershell" + "[rename set S;proc Hide shell_not_supported {proc $shell_not_supported args {}};Hide :]" + "\$(function : {<#pwsh#>})" + "perlhide" + qw^ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @' : heredoc1 - hide from powershell using @ and squote above. close sqote for unix shells + ' \ : .bat/.cmd launch section, leading colon hides from cmd, trailing slash hides next line from tcl + \ @@ -13,7 +13,7 @@ set -- "$@" "a=[Hide <#;Hide set;S 1 list]"; set -- : "$@";$1 = @' : Continuation char at end of this line and rem with curly-braces used to exlude Tcl from the whole cmd block \ : { @REM ############################################################################################################################ -@REM THIS IS A POLYGLOT SCRIPT - supporting payloads in Tcl, bash, (some sh) and/or powershelll (powershell.exe or pwsh.exe) +@REM THIS IS A POLYGLOT SCRIPT - supporting payloads in Tcl, zsh, bash, (sh diversion) and/or powershelll (powershell.exe or pwsh.exe) @REM It should remain portable between unix-like OSes & windows if the proper structure is maintained. @REM ############################################################################################################################ @rem ------------------------------------------------------------------------------------------------------------------------------- @@ -95,19 +95,19 @@ set ^"endlocal=for %%# in (1 2) do if %%#==2 (%\n% @REM If more than 64 chars needed for a target, it can still be done but overall script padding may need checking/adjusting @REM Supporting more explicit oses than those listed may also require script padding adjustment : <> -@SET "nextshellpath[win32___________]=powershell -nop -nol -ExecutionPolicy ByPass -File______________" +@SET "nextshellpath[win32___________]=cmd.exe /c powershell -nop -nol -ExecutionPolicy ByPass -File___________________________________________________________________" @SET "nextshelltype[win32___________]=powershell______" -@SET "nextshellpath[dragonflybsd____]=/usr/bin/env bash_______________________________________________" +@SET "nextshellpath[dragonflybsd____]=/usr/bin/env bash_______________________________________________________________________________________________________________" @SET "nextshelltype[dragonflybsd____]=bash____________" -@SET "nextshellpath[freebsd_________]=/usr/bin/env bash_______________________________________________" +@SET "nextshellpath[freebsd_________]=/usr/bin/env bash_______________________________________________________________________________________________________________" @SET "nextshelltype[freebsd_________]=bash____________" -@SET "nextshellpath[netbsd__________]=/usr/bin/env bash_______________________________________________" +@SET "nextshellpath[netbsd__________]=/usr/bin/env bash_______________________________________________________________________________________________________________" @SET "nextshelltype[netbsd__________]=bash____________" -@SET "nextshellpath[linux___________]=/usr/bin/env bash_______________________________________________" +@SET "nextshellpath[linux___________]=/usr/bin/env bash_______________________________________________________________________________________________________________" @SET "nextshelltype[linux___________]=bash____________" -@SET "nextshellpath[macosx__________]=/usr/bin/env bash_______________________________________________" +@SET "nextshellpath[macosx__________]=/usr/bin/env bash_______________________________________________________________________________________________________________" @SET "nextshelltype[macosx__________]=bash____________" -@SET "nextshellpath[other___________]=/usr/bin/env bash_______________________________________________" +@SET "nextshellpath[other___________]=/usr/bin/env bash_______________________________________________________________________________________________________________" @SET "nextshelltype[other___________]=bash____________" : <> @rem asadmin is for automatic elevation to administrator. Separate window will be created (seems unavoidable with current elevation mechanism) and user will still get security prompt (probably reasonable). @@ -119,7 +119,7 @@ set ^"endlocal=for %%# in (1 2) do if %%#==2 (%\n% @CALL :stringTrimTrailingUnderscores %selected_shelltype% selected_shelltype_trimmed @REM @ECHO selected_shelltype_trimmed %selected_shelltype_trimmed% @SET "selected_shellpath=%nextshellpath[win32___________]%" -@CALL :stringTrimTrailingUnderscores %selected_shellpath% selected_shellpath_trimmed +@CALL :stringTrimTrailingUnderscores "%selected_shellpath%" selected_shellpath_trimmed @CALL SET "keyRemoved=%%validshelltypes:!selected_shelltype!=%%" @REM @ECHO keyremoved %keyRemoved% @REM Note that 'powershell' e.g v5 is just a fallback for when pwsh is not available @@ -151,6 +151,7 @@ set ^"endlocal=for %%# in (1 2) do if %%#==2 (%\n% @SET "winpath=%~dp0" %= e.g c:\punkshell\bin\ %= @SET "fname=%~nx0" @SET "scriptrootname=%~dp0%~n0" %= e.g c:\punkshell\bin\runtime (full path without extension) unavailable after shift, so store it =% +@SET "fullscriptname=%~dp0%~n0%~x0" @REM @ECHO fname %fname% @REM @ECHO winpath %winpath% @REM @ECHO commandlineascalled %0 @@ -162,6 +163,62 @@ set ^"endlocal=for %%# in (1 2) do if %%#==2 (%\n% @CALL :stringToUpper %~nx0 capscripttail @REM @ECHO capscriptname: %capscripttail% +@goto skip_parameter_wrangling +@set argCount=30 +@rem This is the max number of args we are willing to handle. also bounded by approx 8k char limit of cmd.exe +@rem We do not loop over %* to count args as it is brittle for some inputs e.g will always skip cmd.exe separators e.g comma and semicolon +@rem Set argCount higher if desired, but there is a small amount of additional looping overhead. +@set tmpfile_base=%TEMP%\punkbatch_params +@call :getUniqueFile %tmpfile_base% ".txt" paramfile +@echo %paramfile% + +%= NOTE when we loop like this using the percent-n args and shift, we lose unquoted separators such as comma and semicolon %= +@rem https://stackoverflow.com/questions/26551/how-can-i-pass-arguments-to-a-batch-file/5493124#5493124 +@rem outer loop required to redirect all rem lines at once to file +@for %%x in (1) do @( + @for /L %%f in (1,1,%argCount%) do @( + @set "argnum=%%~nf" + @set "a1=%%1" + @rem @set "argname=%%!argnum!" + @rem @echo argname: !argname! + @call :rem_output !argnum! !a1! + @shift + ) +) > %paramfile% +@echo off + +@set "newcommandline= " + +@(set target=cmd_pwsh) +@if "%target%"=="cmd_pwsh" ( + @for /F "delims=" %%L in (%paramfile%) do @( + SETLOCAL DisableDelayedExpansion + set "param=%%L" + @REM @echo ######### %%L + @rem call :buildcmdline newcommandline param "{" "}" + @rem call :buildcmdline newcommandline param ' ' %= cmd.exe /c powershell ... -c %= + call :buildcmdline newcommandline param %= cmd.exe /c powershell ... -f %= + @rem @echo . + ) +) ELSE ( + @for /F "delims=" %%L in (%paramfile%) do @( + SETLOCAL DisableDelayedExpansion + set "param=%%L" + call :buildcmdline newcommandline param + ) +) +@REM padding +SETLOCAL EnableDelayedExpansion +@echo off +@IF EXIST %paramfile% ( + @DEL /F /Q %paramfile% +) +@IF EXIST %paramfile% ( + echo failed to delete %paramfile% + cat %paramfile% +) +:skip_parameter_wrangling + @IF "%nftail%"=="%capscripttail%" ( @ECHO forcing asadmin=1 due to file name on filesystem being uppercase @SET "asadmin=1" @@ -189,31 +246,112 @@ set ^"endlocal=for %%# in (1 2) do if %%#==2 (%\n% :getPrivileges @IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (echo PUNK-ELEVATED & shift /1 & goto :gotPrivileges ) @ECHO Set UAC = CreateObject^("Shell.Application"^) > "%vbsGetPrivileges%" -@ECHO args = "PUNK-ELEVATED " >> "%vbsGetPrivileges%" +@ECHO pre = "/c %fullscriptname% PUNK-ELEVATED " >> "%vbsGetPrivileges%" +@REM @echo pre = "PUNK-ELEVATED " >> "%vbsGetPrivileges%" +@echo args = pre >> "%vbsGetPrivileges%" @ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%" -@ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%" +@ECHO args = args ^& Chr(34) ^& strArg ^& Chr(34) ^& " " >> "%vbsGetPrivileges%" @ECHO Next >> "%vbsGetPrivileges%" -@ECHO UAC.ShellExecute "%~dp0%~n0%~x0", args, "", "runas", 1 >> "%vbsGetPrivileges%" -@ECHO Launching script in new window due to administrator elevation +@GOTO skiptest + +%= Option Explicit =% +%= We need a child process to locate the current script. =% +@ECHO Const FLAG_PROCESS = "winver.exe" >> "%vbsGetPrivileges%" + +%= ' WMI constants %= +@ECHO Const wbemFlagForwardOnly = 32 >> "%vbsGetPrivileges%" + +%=' Generate a unique value to be used as a flag =% +@ECHO Dim guid >> "%vbsGetPrivileges% +@ECHO guid = Left(CreateObject("Scriptlet.TypeLib").GUID,38) >> "%vbsGetPrivileges%" + +%= ' Start a process using the indicated flag inside its command line =% +@ECHO WScript.CreateObject("WScript.Shell").Run """" ^& FLAG_PROCESS ^& """ " ^& guid, 0, False >> "%vbsGetPrivileges%" + +%= ' To retrieve process information a WMI reference is needed =% +@ECHO Dim wmi >> "%vbsGetPrivileges%" +@ECHO Set wmi = GetObject("winmgmts:{impersonationLevel=impersonate}^!\\.\root\cimv2") >> "%vbsGetPrivileges%" + +%= ' Query the list of processes with the flag in its command line, retrieve the =% +%= ' process ID of its parent process ( our script! ) and terminate the process =% +@ECHO Dim colProcess, process, myProcessID >> "%vbsGetPrivileges%" +@ECHO Set colProcess = wmi.ExecQuery( _>> "%vbsGetPrivileges%" +@ECHO "SELECT ParentProcessID From Win32_Process " ^& _>> "%vbsGetPrivileges%" +@ECHO "WHERE Name='" ^& FLAG_PROCESS ^& "' " ^& _>> "%vbsGetPrivileges%" +@ECHO "AND CommandLine LIKE '%%" ^& guid ^& "%%'" _>> "%vbsGetPrivileges%" +@ECHO ,"WQL" , wbemFlagForwardOnly _>> "%vbsGetPrivileges%" +@ECHO ) >> "%vbsGetPrivileges%" +@ECHO For Each process In colProcess >> "%vbsGetPrivileges%" +@ECHO myProcessID = process.ParentProcessID >> "%vbsGetPrivileges%" +@ECHO process.Terminate >> "%vbsGetPrivileges%" +@ECHO Next >> "%vbsGetPrivileges%" + +%= ' Knowing the process id of our script we can query the process list =% +%= ' and retrieve its command line =% +@ECHO Dim commandLine >> "%vbsGetPrivileges%" +@ECHO set colProcess = wmi.ExecQuery( _>> "%vbsGetPrivileges%" +@ECHO "SELECT CommandLine From Win32_Process " ^& _>> "%vbsGetPrivileges%" +@ECHO "WHERE ProcessID=" ^& myProcessID _>> "%vbsGetPrivileges%" +@ECHO ,"WQL" , wbemFlagForwardOnly _>> "%vbsGetPrivileges%" +@ECHO ) >> "%vbsGetPrivileges%" +@ECHO For Each process In colProcess >> "%vbsGetPrivileges%" +@ECHO commandLine = process.CommandLine >> "%vbsGetPrivileges%" +@ECHO Next >> "%vbsGetPrivileges%" +@ECHO WScript.Echo "raw commandline: " ^& commandLine >>"%vbsGetPrivileges%" + +%= ' Done =% +@ECHO intpos = 0 >> "%vbsGetPrivileges%" +@ECHO intCount = 0 >> "%vbsGetPrivileges%" +@ECHO intstartsearch = 1 >> "%vbsGetPrivileges%" +@ECHO intmax = 100 >> "%vbsGetPrivileges%" +@ECHO do While intCount ^< 4 and intmax ^> 0 >> "%vbsGetPrivileges%" +@ECHO intpos = InStr(intstartsearch, commandline, """") >> "%vbsGetPrivileges%" +@ECHO if intpos ^<^> 0 then >> "%vbsGetPrivileges%" +@ECHO intCount = intCount + 1 >> "%vbsGetPrivileges%" +@ECHO if intcount = 4 then >> "%vbsGetPrivileges%" +@ECHO ' wscript.echo "position: " ^& intpos >> "%vbsGetPrivileges%" +@ECHO commandline = Mid(commandline,intpos+1) >> "%vbsGetPrivileges%" +@ECHO exit do >> "%vbsGetPrivileges%" +@ECHO else >> "%vbsGetPrivileges%" +@ECHO intstartsearch = intpos + 1 >> "%vbsGetPrivileges%" +@ECHO end if >> "%vbsGetPrivileges%" +@ECHO end if >> "%vbsGetPrivileges%" +@ECHO intmax = intmax -1 >> "%vbsGetPrivileges%" +@ECHO Loop >> "%vbsGetPrivileges%" +@ECHO if intcount ^< 4 then >> "%vbsGetPrivileges%" +@ECHO err.raise vbObjectError + 1001, "vbsGetPrivileges", "failed to parse commandline" >> "%vbsGetPrivileges%" +@ECHO end if >> "%vbsGetPrivileges%" +@ECHO commandline = pre ^& commandline >> "%vbsGetPrivileges%" +@ECHO WScript.Echo "commandline: " ^& commandLine >>"%vbsGetPrivileges%" +@ECHO WScript.Echo "args: " ^& args >>"%vbsGetPrivileges%" +:skiptest + +@ECHO UAC.ShellExecute "cmd.exe", args, "", "runas", 1 >> "%vbsGetPrivileges%" +@REM @ECHO UAC.ShellExecute "%fullscriptname%", commandline, "", "runas", 1 >> "%vbsGetPrivileges%" +@ECHO Launching script "%fullscriptname%" in new window due to administrator elevation with args: "%*" @"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %* +@REM @"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" !newcommandline! @EXIT /B +@REM buffer +@REM buffer :gotPrivileges @REM setlocal & pushd . @PUSHD . -@cd /d %~dp0 +@cd /d %winpath% @IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" ( @DEL "%vbsGetPrivileges%" 1>nul 2>nul @SET arglist=%arglist:~14% + @SHIFT ) :skip_privileges @SET need_ps1=0 @REM we want the ps1 to exist even if the nextshell isn't powershell -@if not exist "%~dp0%~n0.ps1" ( +@if not exist "%scriptrootname%.ps1" ( @SET need_ps1=1 ) ELSE ( - fc "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >nul || goto different + fc "%fullscriptname%" "%scriptrootname%.ps1" >nul || goto different @REM @ECHO "files same" @SET need_ps1=0 ) @@ -223,73 +361,13 @@ set ^"endlocal=for %%# in (1 2) do if %%#==2 (%\n% @SET need_ps1=1 :pscontinue @IF !need_ps1!==1 ( - COPY "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >NUL + COPY "%fullscriptname%" "%scriptrootname%.ps1" >NUL ) @REM avoid using CALL to launch pwsh,tclsh etc - it will intercept some args such as /? @IF "!selected_shelltype_trimmed!"=="none" ( SET selected_shelltype_trimmed=pwsh ) - - - -@set argCount=30 -@rem This is the max number of args we are willing to handle. also bounded by approx 8k char limit of cmd.exe -@rem We do not loop over %* to count args as it is brittle for some inputs e.g will always skip cmd.exe separators e.g comma and semicolon -@rem Set argCount higher if desired, but there is a small amount of additional looping overhead. - -@set tmpfile_base=%TEMP%\punkbatch_params -@call :getUniqueFile %tmpfile_base% ".txt" paramfile -@echo %paramfile% - -%= NOTE when we loop like this using the percent-n args, we lose unquoted separators such as comma and semicolon %= -@rem https://stackoverflow.com/questions/26551/how-can-i-pass-arguments-to-a-batch-file/5493124#5493124 -@rem outer loop required to redirect all rem lines at once to file -@for %%x in (1) do @( - @for /L %%f in (1,1,%argCount%) do @( - @set "argnum=%%~nf" - @set "a1=%%1" - @rem @set "argname=%%!argnum!" - @rem @echo argname: !argname! - @call :rem_output !argnum! !a1! - @shift - ) -) > %paramfile% -@echo off - -@set "newcommandline= " - -@(set target=cmd_pwsh) -@if "%target%"=="cmd_pwsh" ( - @for /F "delims=" %%L in (%paramfile%) do @( - SETLOCAL DisableDelayedExpansion - set "param=%%L" - @REM @echo ######### %%L - @rem call :buildcmdline newcommandline param "{" "}" - call :buildcmdline newcommandline param ' ' %= cmd.exe /c powershell %= - @rem @echo . - ) -) ELSE ( - @for /F "delims=" %%L in (%paramfile%) do @( - SETLOCAL DisableDelayedExpansion - set "param=%%L" - call :buildcmdline newcommandline param - ) -) -@REM padding -SETLOCAL EnableDelayedExpansion - -@echo off -@IF EXIST %paramfile% ( - @DEL /F /Q %paramfile% -) -@IF EXIST %paramfile% ( - echo failed to delete %paramfile% - cat %paramfile% -) - - - @REM @SET "squoted_args=" @REM @for %%a in (%*) do @( @REM set "v=%%a" @@ -303,25 +381,31 @@ SETLOCAL EnableDelayedExpansion @IF "!selected_shelltype_trimmed!"=="pwsh" ( REM pwsh vs powershell hasn't been tested because we didn't need to copy cmd to ps1 this time REM test availability of preferred option of powershell7+ pwsh + REM when run without cmd.exe - pwsh will receive the semicolon (for cmd.exe unquoted semicolon and comma are separators that aren't seen in positional arguments) pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted 2>NUL; write-host "statusmessage: pwsh-found" >NUL SET pwshtest_exitcode=!errorlevel! REM ECHO pwshtest_exitcode !pwshtest_exitcode! REM fallback to powershell if pwsh failed IF !pwshtest_exitcode!==0 ( @rem pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%scriptrootname%.ps1" %arglist% - @rem pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted - cmd /c pwsh -nop -nol -ExecutionPolicy bypass -c "%scriptrootname%.ps1" !newcommandline! + @rem pwsh -nop -nologo -ExecutionPolicy bypass -f "%scriptrootname%.ps1" %arglist% %= ok =% + @rem cmd /c pwsh -nop -nologo -ExecutionPolicy bypass -f "%scriptrootname%.ps1" !newcommandline! + !selected_shellpath_trimmed! "%scriptrootname%.ps1" %arglist% SET task_exitcode=!errorlevel! ) ELSE ( REM TODO prompt user with option to call script to install pwsh using winget - @rem powershell -nop -nol -ExecutionPolicy Bypass -c "%scriptrootname%.ps1" %arglist% - cmd /c powershell -nop -nol -ExecutionPolicy Bypass -c "%scriptrootname%.ps1" !newcommandline! + %= powershell with -file flag treats it's arguments differently to pwsh - we need cmd /c to preserve args with spaces =% + cmd /c powershell -nop -nologo -ExecutionPolicy Bypass -f "%scriptrootname%.ps1" %arglist% + @rem cmd /c powershell -nop -nologo -ExecutionPolicy Bypass -f "%scriptrootname%.ps1" !newcommandline! SET task_exitcode=!errorlevel! ) ) ELSE ( IF "!selected_shelltype_trimmed!"=="powershell" ( - @rem powershell -nop -nol -ExecutionPolicy Bypass -c "%scriptrootname%.ps1" %arglist% - cmd /c powershell -nop -nol -ExecutionPolicy Bypass -c "%scriptrootname%.ps1" !newcommandline! + %= powershell with -file flag treats it's arguments differently to pwsh - we need cmd /c to preserve args with spaces =% + @rem @echo powershell - !selected_shellpath_trimmed! "%scriptrootname%.ps1" %arglist% + @rem cmd /c powershell -nop -nologo -ExecutionPolicy Bypass -f "%scriptrootname%.ps1" %arglist% %= ok - this works =% + !selected_shellpath_trimmed! "%scriptrootname%.ps1" %arglist% + @rem cmd /c powershell -nop -nologo -ExecutionPolicy Bypass -f "%scriptrootname%.ps1" !newcommandline! SET task_exitcode=!errorlevel! ) ELSE ( IF "!selected_shelltype_trimmed!"=="wslbash" ( @@ -335,24 +419,23 @@ SETLOCAL EnableDelayedExpansion REM sh on windows uses /c/ instead of /mnt/c - at least if using msys. Todo, review what is the norm on windows with and without msys2,cygwin,wsl REM and what logic if any may be needed. For now sh with /c/xxx seems to work the same as sh with c:/xxx REM The compound statement with trailing call is required to stop batch termination confirmation, whilst still capturing exitcode - @ECHO HERE "!selected_shelltype_trimmed!" "!selected_shellpath_trimmed!" - %selected_shellpath_trimmed% "%winpath%%fname%" %arglist% & SET task_exitcode=!errorlevel! & Call; + @REM @ECHO HERE "!selected_shelltype_trimmed!" "!selected_shellpath_trimmed!" + !selected_shellpath_trimmed! "%winpath%%fname%" %arglist% & SET task_exitcode=!errorlevel! & Call; ) ELSE ( ECHO %fname% has invalid nextshelltype value %selected_shelltype% valid options are %validshelltypes% SET task_exitcode=66 @REM boundary padding - GOTO :exit_multishell ) ) ) ) @REM batch file library functions - @GOTO :endlib @REM padding @REM padding @REM padding +@REM padding %= ---------------------------------------------------------------------- =% @rem courtesy of dbenham @@ -373,6 +456,7 @@ endlocal & set "%~3=%rtn%" exit /b %= ---------------------------------------------------------------------- =% +@REM padding :buildcmdline cmdlinevar paramvar wrapA wrapB %= quoting for cmd.exe /c pwsh -nop !args! =% @SETLOCAL EnableDelayedExpansion @@ -455,6 +539,7 @@ do if not defined param1 set %%~"param1=%2%%~" rem %1 #%2# @exit /b +@rem padding @REM courtesy of: https://stackoverflow.com/users/463115/jeb :strlen stringVar returnVar @( @@ -503,6 +588,8 @@ do if not defined param1 set %%~"param1=%2%%~" ) @EXIT /B +@REM padding +@REM padding :getFileTail @REM return tail of file without any normalization e.g c:/punkshell/bin/Punk.cmd returns Punk.cmd even if file is punk.cmd @REM we can't use things such as %~nx1 as it can change capitalisation @@ -542,6 +629,7 @@ do if not defined param1 set %%~"param1=%2%%~" @EXIT /B @REM boundary padding @REM boundary padding +@REM boundary padding :getNormalizedScriptTail @SETLOCAL @SET "result=%~nx0" @@ -650,13 +738,15 @@ do if not defined param1 set %%~"param1=%2%%~" @REM boundary padding @REM boundary padding @REM boundary padding +@REM boundary padding :stringTrimTrailingUnderscores @SETLOCAL @SET "rtrn=%~2" @SET "string=%~1" @SET "trimstring=%~1" - @REM trim up to 63 underscores from the end of a string using string substitution + @REM trim up to 127 underscores from the end of a string using string substitution @SET "trimstring=%trimstring%###" + @SET "trimstring=%trimstring:________________________________________________________________###=###%" @SET "trimstring=%trimstring:________________________________###=###%" @SET "trimstring=%trimstring:________________###=###%" @SET "trimstring=%trimstring:________###=###%" @@ -702,10 +792,11 @@ do if not defined param1 set %%~"param1=%2%%~" # ## ### ### ### ### ### ### ### ### ### ### ### ### ### # -- tcl script section # -- This is a punk multishell file -# -- Primary payload target is Tcl, with sh,bash,powershell as helpers -# -- but it may equally be used with any of these being the primary script. -# -- It is tuned to run when called as a batch file, a tcl script a sh/bash script or a pwsh/powershell script +# -- It is tuned to run (and possibly divert to different payload shell) when called from cmd.exe as a batch file, tclsh,sh,zsh,bash,perl or pwsh/powershell script # -- i.e it is a polyglot file. +# -- The payload target (by os) is defined in the nextshell block at the top which is constructed when generating the polyglot +# -- using the tcl 'dev scriptwrap.multishell' command in a tcl punk shell +# -- The payload can be tcl,perl,powershell/pwsh or zsh/bash. # -- The specific layout including some lines that appear just as comments is quite sensitive to change. # -- It can be called on unix or windows platforms with or without the interpreter being specified on the commandline. # -- e.g ./scriptname.cmd in sh or zsh or bash @@ -755,15 +846,65 @@ foreach ln [split $scriptdata \n] { } } if {$nextshelltype ne "tcl" && $nextshelltype ne "none"} { + set script_as_called [info script] + set script_rootname [file rootname $script_as_called] if {$nextshelltype in "pwsh powershell"} { - set scrname [file rootname [info script]].ps1 - set arglist [list] - foreach a $::argv { - set a "'$a'" - lappend arglist $a + # experimental + set script_ps1 $script_rootname.ps1 + set arglist $::argv + + if {[file extension $script_as_called] ne ".ps1"} { + #we need to ensure .ps1 is up to date + set needs_updating 0 + if {![file exists $script_ps1]} { + set needs_updating 1 + } else { + #both exist + if {[file size $script_as_called] != [file size $script_ps1]} { + set needs_updating 1 + } else { + #both exist with same size - do full check that they're identical + catch {package require sha256} + if {[package provide sha256] ne ""} { + set h1 [sha2::sha256 -hex -file $script_as_called] + set h2 [sha2::sha256 -hex -file $script_ps1] + if {[string length $h1] != 64 || [string length $h2] != 64} { + set needs_updating 1 + } elseif {$h1 ne $h2} { + set needs_updating 1 + } + } else { + #manually compare - scripts aren't too big, so slurp and string compare is fine + set fd [open $script_as_called] + chan configure $fd -translation binary + set data1 [read $fd] + close $fd + set fd [open $script_ps1] + chan configure $fd -translation binary + set data2 [read $fd] + close $fd + if {![string equal $data1 $data2]} { + set needs_updating 1 + } + } + } + } + + if {$needs_updating} { + file copy -force $script_as_called $script_ps1 + } + } else { + #when called on the .ps1 - we assume it's up to date - review } + set scrname $script_ps1 + + #set arglist [list] + #foreach a $::argv { + # set a "'$a'" + # lappend arglist $a + #} } else { - set scrname [info script] + set scrname $script_as_called set arglist $::argv } puts stdout "tclsh launching subshell of type: $nextshelltype shellpath: $nextshellpath on script $scrname with args: $arglist" @@ -842,16 +983,6 @@ namespace eval ::punk::multishell { puts stderr "No tcl code for this script. Try another program such as zsh or bash or perl" # -# -# - -# -# - - -# -# - # -- --- --- --- --- --- --- --- --- --- --- --- # -- Best practice is to always return or exit above, or just by leaving the below defaults in place. @@ -872,28 +1003,67 @@ if {[::punk::multishell::is_main]} { HEREDOC1B_HIDE_FROM_BASH_AND_SH # Be wary of any non-trivial sed/awk etc - can be brittle to maintain across linux,freebsd,macosx due to differing implementations \ echo "var0: $0 @: $@" -# echo "script: `echo $0 | sed 's/^-//'`" -# use oldschool backticks and sed - lowest common denominator \ -# echo "shell: " `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` -# zsh diversion \ -# if [[ "$argv[*]" != "[*]" ]]; then /usr/bin/env bash "$0" "${argv[@]:2:$((${#argv[@]}-2))}"; exit $?; fi -# \ -ps_shellname=`ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` +# use oldschool backticks and sed (posix - lowest common denominator) \ +# ps_shellname=`ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` \ +# some ps impls will return arguments - so last field not always appropriate \ +# some ps impls don't have -o (e.g gnu cygwin) so ps_shellname may remain empty and emit an error \ +ps_shellname=`ps -o pid,comm -p $$ | awk '$1 != "PID" {print $2}'` # \ -echo "shell from ps: $ps_shellname argc: ${#@} inner: ${@:2:$((${#@}-2))}" -# non-bash-like diversion \ -if [[ "$ps_shellname" != "bash" && "$ps_shellname" != "zsh" ]]; then /usr/bin/env bash "$0" "${@:2:$((${#@}-2))}"; exit $?; fi -# sh/bash (or zsh?) \ -shift && set -- "${@:1:$((${#@}-1))}" -# \ -#echo "shell:" `ps -o args= $$ | sed -E 's/^.*\/|^-//' | awk '{print $1}'` +echo "shell from ps: $ps_shellname" # \ echo "args: $@" +# ------------------------------------------------------------------------------ +# -- This if block wraps posix sh diversion section - only needed if Tcl didn't exit or return above. +if false==false # else { + then + : # + +# https://gist.github.com/fcard/e26c5a1f7c8b0674c17c7554fb0cd35c0 (MIT lic) +# https://stackoverflow.com/questions/63864755/remove-last-argument-in-shell-script-posix +# posix compliant pop +pop() { + __pop_n=$(($1 - ${2:-1})) + if [ -n "$ZSH_VERSION" -o -n "$BASH_VERSION" ]; then + POP_EXPR='set -- "${@:1:'$__pop_n'}"' + elif [ $__pop_n -ge 500 ]; then + POP_EXPR="set -- $(seq -s " " 1 $__pop_n | sed 's/[0-9]\+/"${\0}"/g')" + else + __pop_index=0 + __pop_arguments="" + while [ $__pop_index -lt $__pop_n ]; do + __pop_index=$((__pop_index+1)) + __pop_arguments="$__pop_arguments \"\${$__pop_index}\"" + done + POP_EXPR="set -- $__pop_arguments" + fi +} +# ------------------------------------------------------------------------------ + +# non-bash-like posix diversion +# we don't use $BASH_VERSION/$ZSH_VERSION as these can still be set when for example +# sh is a symlink to bash (posix-mode bash - reduced bashism capabilities?) +# if our ps_shellname didn't contain a result, don't divert and risk looping +if [ -n "$ps_shellname" ] && [ "$ps_shellname" != "bash" ] && [ "$ps_shellname" != "zsh" ] ; then + shift + pop $# + eval "$POP_EXPR" + echo "divert to bash $0 $@" + + /usr/bin/env bash "$0" "$@" + exit $? +fi +# close false==false block +fi +# close tcl wrap } # ------------------------------------------------------ -# -- This if block only needed if Tcl didn't exit or return above. +# -- This if block wraps whole zsh/bash and perl sections - only needed if Tcl didn't exit or return above. if false==false # else { then : # + + +# zsh/bash \ +shift && set -- "${@:1:$((${#@}-1))}" # ## ### ### ### ### ### ### ### ### ### ### ### ### ### # -- sh/bash script section # -- leave as is if all that is required is launching the Tcl payload" @@ -905,7 +1075,7 @@ if false==false # else { # ## ### ### ### ### ### ### ### ### ### ### ### ### ### plat=$(uname -s) #platform/system -if [[ "$plat" = "Linux"* ]]; then +if [[ "$plat" == "Linux"* ]]; then os="linux" elif [[ "$plat" == "Darwin"* ]]; then os="macosx" @@ -917,25 +1087,39 @@ elif [[ "$plat" == "NetBSD"* ]]; then os="netbsd" elif [[ "$plat" == "OpenBSD"* ]]; then os="openbsd" -elif [[ "$plat" = "MINGW32"* ]]; then +elif [[ "$plat" == "MINGW32"* ]]; then os="win32" -elif [[ "$plat" = "MINGW64"* ]]; then +elif [[ "$plat" == "MINGW64"* ]]; then os="win32" -elif [[ "$plat" = "CYGWIN_NT"* ]]; then +elif [[ "$plat" == "CYGWIN_NT"* ]]; then os="win32" elif [[ "$plat" == "MSYS_NT"* ]]; then - #review.. echo MSYS - #win32 binaries - but e.g tclsh installed in msys reports ::tcl_platform(platform) as 'unix' + #review.. + + #Need to consider the difference between when msys2 was launched (which strips some paths and sets up the environment) + # vs if the msys2 sh was called - (actually bash) in which case paths will be different + + #wsl and cygwin or msys2 can commonly be problematic combinations - primarily due to path issues + #e.g "c:/windows/system32/" is quite likely in the path ahead of msys,git etc. + #e.g It means a /usr/bin/env bash call may launch the (linux elf) bash for wsl rather than the msys bash + # + + #msys provides win32 binaries - but e.g tclsh installed in msys reports ::tcl_platform(platform) as 'unix' #bash reports $OSTYPE msys + + #there are statements around the web that cmd /c .. will work under msys2 + # - but from experience, it can be required to use cmd //c ... + # or MSYS2_ARG_CONV_ECL='*' cmd /c .. + # This seems to be because process arguments that look like unix paths are converted to windows paths :/ + #review! + os="win32" #review - need ps/sed/awk to determine shell? - interp = `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` + interp=`ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` #use 'command -v' (shell builtin preferred over external which) shellpath=`command -v $interp` shellfolder="${shellpath%/*}" #avoid dependency on basename or dirname - #"c:/windows/system32/" is quite likely in the path ahead of msys,git etc. - #This breaks calls to various unix utils such as sed etc (wsl related?) export PATH="$shellfolder${PATH:+:${PATH}}" elif [[ "$OSTYPE" == "win32" ]]; then os="win32" @@ -967,34 +1151,72 @@ for ln in "${arr_oslines[@]}"; do pathraw="${splitln%%\"*}" #take everything before the quote - use %% to get longest match #remove trailing underscores (% means must match at end) nextshellpath="${pathraw/%_*/}" - echo "nextshellpath: $nextshellpath" + # echo "nextshellpath: $nextshellpath" elif [[ "$ln" == *"nextshelltype"* ]]; then splitln="${ln#*=}" typeraw="${splitln%%\"*}" nextshelltype="${typeraw/%_*/}" - echo "nextshelltype: $nextshelltype" + # echo "nextshelltype: $nextshelltype" fi done exitcode=0 #-- sh/bash launches nextscript here instead of shebang line at top if [[ "$nextshelltype" != "bash" && "$nextshelltype" != "none" ]]; then - echo bash launching subshell of type: $nextshelltype shellpath: $nextshellpath on "$0" with args "$@" - #e.g /usr/bin/env tclsh "$0" "$@" - ${nextshellpath} "$0" "$@" + echo zsh/bash launching subshell of type: $nextshelltype shellpath: $nextshellpath on "$0" with args "$@" + + script="$0" + if [[ "$nextshelltype" == "pwsh" || "$nextshelltype" == "powershell" ]]; then + #powershell requires the file extension to be .ps1 (always - on windows) + #on other platforms it's not required if a shebang line is used - but this script must be shebangless for portability and to maintain polyglot capabilities. + cmdpattern="[.]cmd$" + if [[ "$script" =~ $cmdpattern ]]; then + ps1script="${script%????}.ps1" + if ! cmp -s "$script" "$ps1script" ; then + #ps1script either different or missing + #on windows - batch script copies .cmd -> .ps1 if not identical + cp -f "$script" "$ps1script" + fi + script=$ps1script + fi + fi + if [[ "$plat" == "MSYS_NT"* ]]; then + + #we need to deal with MSYS argument mangling + cmdpattern="^cmd.exe |^cmd " + #do not double quote cmdpattern - or it will be treated as literal string + if [[ "$nextshellpath" =~ $cmdpattern ]]; then + #for now - tell the user what's going on + echo "cmd call via msys detected. performing translation of /c to //c and escaping backslashes in script path" + #flags to cmd.exe such as /c are interpreted by msys as looking like a unix path + #review - for nextshellpath targets specified in the block for win32 - we don't expect unix paths (?) + #what about other flags? - can we just double up all forward slashes? + nextshellpath="${nextshellpath// \/c / \/\/c }" + echo "new nextshellpath: ${nextshellpath}" + #don't double quote this + script=${script//\\/\\\\} + fi + echo "calling ${nextshellpath} $script $@" + # eval is required here - but why? + eval ${nextshellpath} "$script" "$@" + else + #e.g /usr/bin/env tclsh "$0" "$@" + ${nextshellpath} "$script" "$@" + fi + exitcode=$? - #echo "sh/bash reporting exitcode: ${exitcode}" + #echo "zsh/bash reporting exitcode: ${exitcode}" exit $exitcode #-- override exitcode example #exit 66 else #already in bash - don't launch another process or we would loop - #echo "bash payload" + #echo "zsh/bash payload" : fi -# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload -#printf "start of bash or sh code" +# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin zsh Payload +#printf "start of bash or zsh code" # @@ -1090,29 +1312,9 @@ cd $launchdir #restore original CWD # -# -# - -# -- --- --- --- --- --- --- --- -# -#-- sh/bash launches Tcl here instead of shebang line at top -#-- use exec to use exitcode (if any) directly from the tcl script -#exec /usr/bin/env tclsh "$0" "$@" -#-- alternative - can run sh/bash script after the tcl call. -#/usr/bin/env tclsh "$0" "$@" -#exitcode=$? -#echo "sh/bash reporting tcl exitcode: ${exitcode}" -#-- override exitcode example -#exit 66 -# -# -- --- --- --- --- --- --- --- - -# -# - -#printf "sh/bash done \n" -# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end sh Payload +#printf "zsh/bash done \n" +# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end zsh Payload #------------------------------------------------------ fi exit ${exitcode} @@ -1148,7 +1350,6 @@ print "os $os\n"; # -- --- --- - my $i =1; foreach my $a(@ARGV) { print "Arg # $i: $a\n"; @@ -1158,21 +1359,11 @@ foreach my $a(@ARGV) { print STDERR "No perl code for this script. Try another program such as tcl or bash"; # -# -# - - - # -- --- --- --- --- --- --- --- -# #$exit_code=system("tclsh", $scriptname, @ARGV); #print "perl reporting tcl exitcode: $exit_code"; -# # -- --- --- --- --- --- --- --- -# -# - # -- --- --- --- --- --- --- --- --- --- --- --- --- ---end perl Payload exit $exit_code; @@ -1282,18 +1473,44 @@ if ($match.Success) { $admininfo = $match.Groups[1].Value $asadmin = $admininfo.Contains("asadmin=1") if ($asadmin) { + if ($args[0] -eq "PUNK-ELEVATED") { + # May be present if launch and elevation was done via cmd.exe script + # shift away first arg + $newargs = $args | Select-Object -Skip 1 + } else { + $newargs = $args + } + # -Wait e.g for starting a service or other operations which remainder of script may depend on + $arguments = @("-NoProfile","-NoLogo", "-NoExit", "-ExecutionPolicy", "Bypass") + $arguments += @("-File", $($MyInvocation.MyCommand.Path)) + foreach ($a in $newargs) { + if ($a -match '\s') { + $arguments += "`"$a`"" + } else { + $arguments += $a + } + } if (-not ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { # If not elevated, relaunch with elevated privileges - # -Wait e.g for starting a service or other operations which remainder of script may depend on - $arguments = @("-NoProfile", "-NoExit", "-ExecutionPolicy", "Bypass") - $arguments += @("-File", $($MyInvocation.MyCommand.Path)) - $arguments += $args + Write-Host "Powershell elevating using start-process with -Verb RunAs" if ($PSVersionTable.PSEdition -eq 'Core') { Start-Process -FilePath "pwsh.exe" -ArgumentList $arguments -Wait -Verb RunAs } else { Start-Process -FilePath "powershell.exe" -ArgumentList $arguments -Wait -Verb RunAs } Exit # Exit the current non-elevated process + } else { + if ($args[0] -eq "PUNK-ELEVATED") { + #Already elevated (by cmd.exe) + #.. but it is impossible to modify or reassign the automatic $args variable + # so let's start yet another whole new process just to remove one leading argument so the custom script can operate on parameters cleanly - thanks powershell :/ + if ($PSVersionTable.PSEdition -eq 'Core') { + Start-Process -FilePath "pwsh.exe" -ArgumentList $arguments -NoNewWindow -Wait + } else { + Start-Process -FilePath "powershell.exe" -ArgumentList $arguments -NoNewWindow -Wait + } + Exit + } } } } @@ -1540,20 +1757,6 @@ write-host "`e[92m getpunk done `e[m" # -# -# - - -# -- --- --- --- --- --- --- --- -# -#tclsh $scriptname $args -#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host -# -# -- --- --- --- --- --- --- --- - - -# -# # -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload Exit $LASTEXITCODE diff --git a/src/embedded/README.md b/src/embedded/README.md new file mode 100644 index 00000000..4f99e416 --- /dev/null +++ b/src/embedded/README.md @@ -0,0 +1,5 @@ +Documents and help files (for the repository website) +These are html, markdown, manfiles etc which live within src/embedded and are intended to be checked into source control so they can form part of the online documentation available when browsing the repository. + +These files shouldn't be modified directly as they are built from the files in the src/doc folder +(Using the Kettle build system) diff --git a/src/modules_tcl8/README.md b/src/modules_tcl8/README.md new file mode 100644 index 00000000..20a953a1 --- /dev/null +++ b/src/modules_tcl8/README.md @@ -0,0 +1,11 @@ +Tcl Module Source files for the project. +Consider using the punkshell deck facility to create and manage these. + +deck module.new will create a basic .tm module template and assist in versioning. + +Tcl modules can be namespaced. +For example +> deck module.new mymodule::utils +will create the new module under src/modules/mymodule/utils + + diff --git a/src/modules_tcl9/README.md b/src/modules_tcl9/README.md new file mode 100644 index 00000000..20a953a1 --- /dev/null +++ b/src/modules_tcl9/README.md @@ -0,0 +1,11 @@ +Tcl Module Source files for the project. +Consider using the punkshell deck facility to create and manage these. + +deck module.new will create a basic .tm module template and assist in versioning. + +Tcl modules can be namespaced. +For example +> deck module.new mymodule::utils +will create the new module under src/modules/mymodule/utils + + diff --git a/src/runtime/Readme.md b/src/runtime/Readme.md index 52e39297..155b2a99 100644 --- a/src/runtime/Readme.md +++ b/src/runtime/Readme.md @@ -1,3 +1,15 @@ -Install a tclkit runtime here by running the appropriate fetchruntime script in ../src - -Alternatively the runtime can be downloaded from: https://www.gitea1.intx.com.au/jn/punkbin +Use mapvfs.config to configure building of a kit (metakit or zipfs) executable from customized .vfs folders +within src/vfs + +A runtime must be installed in the appropriate bin/runtime/ folder to build the executable. +These can be installed by running: bin/runtime.cmd fetch +or alternatively by downloading from an artifact source such as : https://www.gitea1.intx.com.au/jn/punkbin + +Appropriate runtimes should be self-contained Tcl executables that are either metakit or zipfs aware, +and have an attached vfs with 'info library' support files. + +A plain tclsh, even if it has zipfs support, is unlikely to work unless it has the tcl_library folder (with init.tcl and others) and possibly some +other required modules such as msgcat placed within the .vfs folder being used to build the executable. + +A plain tclsh may commonly depend on shared libraries elsewhere on the system, and so the resulting executable may not be as portable. + diff --git a/src/runtime/mapvfs.config b/src/runtime/mapvfs.config index 1545b1b3..7bd7b530 100644 --- a/src/runtime/mapvfs.config +++ b/src/runtime/mapvfs.config @@ -12,79 +12,29 @@ #e.g #- myproject.vfs #- punk86.vfs -#AAA -tclkit86bi.exe {punk8win.vfs punkbi kit} - -#c:\tcl.bawt tcl 8.6.13 bawt -##tclkit-win64-dyn.exe {punk86bawt.vfs punkbawt kit} - -#AAA -#tclkit-win64-dyn.exe {punk86bawt.vfs punksys kit} - - - - -#magicsplat tclkit - no Tk -#tclkit8613.exe punk86.vfs - -#magicsplat modified tclkit - added tk, changed icon -##tclkit8613punk.exe punk86.vfs {punk8win.vfs punk86} -##tclkit8613punk.exe punk86.vfs {punk8win.vfs punk86} - - +# -#tclkit87a5.exe {punk86.vfs punk87} {punk.vfs punkmain} -#tclkit87a5.exe {punk8win.vfs punk87} +tclkit86bi.exe {punk8win.vfs punkbi kit} +#tclkit-win64-dyn.exe {punk86bawt.vfs punkbawt kit} +tclkit-win64-dyn.exe {punk86bawt.vfs punksys kit} +#tclkit87a5.exe {punk86.vfs punk87} {punk.vfs punkmain} -################################## #TCL9 -#tclsh90b2 {punk9win.vfs punk90b2 zip} -#tclsh90b4_piperepl.exe {punk9win.vfs punk90b4 zip} -#tclsh901.exe {punk9win.vfs punk901 zip} -#tclsh90magic.exe {punk9magicsplat.vfs punkmagic zipcat} +#tclsh902z.exe {project.vfs project zip} +tclsh90b2 {punk9win.vfs punk90b2 zip} +tclsh90b4_piperepl.exe {punk9win.vfs punk90b4 zip} +#tclsh901t.exe {punk92in.vfs punk901t zip} +#tclsh90magic.exe {punk9magicsplat.vfs punkmagic zipcat} #made with Bawt (2025-08) -#tclkit -#tclkit902.exe {punk9win_for_tkruntime.vfs punk902kit kit} +#tclkit +#tclkit902.exe {punk9win_for_tkruntime.vfs punk902kit kit} #static build - with tk dll and tk lib added to zip -tclsh902z.exe {punk9win_for_tkruntime.vfs punk902z zip} - -#----------------------------------------- -#Testing wrong target kit type for runtime -#tclkit902.exe {punk9win_for_tkruntime.vfs punk902z zip} -#----------------------------------------- - -#AAA -tclsh901t.exe {punk9win.vfs punk901t zipcat} -#tclsh901k.exe {mkzipfix.vfs punktest zip} - - - -#kit won't work with TCL9 til we get something like vfs::mkcl running - which in turn requires vlerq - (single c file to compile) -#Mk4tcl? c++ based - not as attractive. -##tclsh90b4_piperepl.exe {punk9win.vfs punk90b4kit kit} - - - - -#we would require compiled cookfs extension to extract existing vfs from a cookit, or if we wanted to re-write as cookfs -#(possibly upx binary too if compressed - upx is easily attainable on most platforms) -#cookitU.exe {punk9cook.vfs punk9cook cookfs} -#cookitU.exe {punk9cookbasic.vfs punk900cookrt cookfs} - -################################## - -#critcl doesn't seem to work as a zip or metakit in 2024 - gcc paths point to vfs -#critcl needs updating to copy files out to real fs for critcl to work -#Use unwrapped critcl for now -#tclsh90b4_piperepl.exe {critcl-3.3.1.vfs critcl kit} -#tclkit86bi.exe {critcl-3.3.1.vfs critcl kit} - +tclsh902z.exe {punk9win_for_tkruntime.vfs punk902z zip} -#temp hack - todo fix .exe for x-platform -#linux tclsh90 (zip) built with zig.build x-compile on windows -#tclsh90linux.exe {punk9linux.vfs punk90linux kit} -tclkit-902-Linux64-intel-dyn {punk9linux.vfs punk902linux-x86_64 kit} +#---------------------------------------------- +#testing wrong target kit type for runtime +#tclkit902.exe {punk9win_for_tkruntime.vfs punkwrongruntime zip} diff --git a/src/scriptapps/wrappers/README.md b/src/scriptapps/wrappers/README.md index 604fb5be..57db006b 100644 --- a/src/scriptapps/wrappers/README.md +++ b/src/scriptapps/wrappers/README.md @@ -1,90 +1,53 @@ -These wrappers are intended to be used with the pmix wrapper functions to automate wrapping of tcl,sh,powershell scripts into a polyglot script which will run in multiple environments +Scriptwrap 'wrappers' are intended to be used with the 'deck scriptwrap' functions to automate wrapping of tcl,bash,powershell,perl scripts into a polyglot script which will run on multiple platforms. You may also use these to hand-craft polyglot scripts. +Polyglot scripts are potentially somewhat brittle over a longer timespan as languages change - use with caution. -To override the default wrapper provided by the pmix command - you can create copies of the sample_ files and remove just the sample_ part -pmix wrap will then never wrap with latest version from the punk project - but only what you have in your scriptapps/wrappers folder. +The sample_basic-multishell.cmd and sample_basic-shellbat.bat are earlier iterations that don't support perl or powershell and are left as test/learning examples only. +The commandline argument processing and overall operation is likely to need tweaking if these are used as a basis. -Alternatively you can copy the sample_ files and name them anything you like that doesn't begin with "punk-" -Then you can call the pmix wrap functions with the -template option and just the name of your file. -(only the scriptapps/wrappers folder will be used to locate your template) +To override the default 'punk.multishell.cmd' wrapper provided by the deck command - you can create customised copies of the wrapper file located in src/modules/punk/mix/templates/utility/scriptappwrappers. + + +If you name the file punk.multishell.cmd it should take precedence over that provided in the module. +Alternatively you can copy the file and name it anything you like that doesn't begin with "punk-" +Then you can call the deck scriptwrap functions with the -template option and just the name of your file. +(only the scriptapps/wrappers folder will be used to locate your template) +The wrapper name can also be specified in the .toml file +e.g + template="my_multishell_wrapper.cmd" -You can create a yourscriptname.wrapconf file in the scriptapps folder alongside yourscriptname.tcl, yourscriptname.sh etc -This .wrapconf is only required if you need to do more complex wrapping. +You can create a yourscriptname_wrap.toml file in the scriptapps folder alongside yourscriptname.tcl, yourscriptname.sh etc +This .toml file is only required if you need to do more complex wrapping, but it is recommended in order to be more explicit. -By default, with no yourscriptname.wrapconf found: +By default, with no yourscriptname_wrap.toml found: yourscriptname.tcl will be substituted between # # -yourscriptname.sh (if present) will be substituted between -# -# +yourscriptname.bash (if present) will be substituted between +# +# yourscriptname.ps1 (if present) will be substituted between -# -# - - -By providing a yourscriptname.wrapconf -you can specify the exact names of the files (in the scriptapps folder) that you want to include - and use more tags such as: - -# -# - -# -# - - -# -#/ - -# -# - -The .wrapconf file can have comment lines (beginning with # and possibly whitespace) +# +# -e.g myutility.wrapconf might contain: -#------------------------ -tagdata file myutility_download-tclkit2.sh -tagdata file myutility_launch-with-tclkit2.sh -tagdata file myutility_download-tclkit2.ps1 -tagdata file myutility_launch-with-tclkit2.ps1 -#------------------------ +yourscriptname.pl (if present) will be substituted between +# +# -Where tagdata command uses the specified file contents to replace all the lines between the starting tag and corresponding closing tag -It is an error to use the tagdata command on a self-closing tag (aka 'singleton' tag - such as vs a paired set .. -paired tags must have their opening and closing tags on different lines. +paired tags in the wrapper template must have their opening and closing tags on different lines. hence the following line is invalid. -# something etc # etc -This is because system is designed to allow repeated updates and analysis of existing output files. +# something etc # etc +This is because system is designed to allow ease of repeated updates and analysis of existing output files. i.e Tags are only supported in places where the languages will accept/ignore them (generally as part of comments) This means it should be possible to reliably detect which template was used and when template upgrades/fixes can be safely applied in the presence of possibly tweaked non-template script data. -Possible exceptions are cases where 2 templates differ only in the default data on singleton-tag lines or default data between paired tags, and that default data has been replaced. There are of course other more flexible/standard methods (e.g diff) to achieve this sort of thing - but this method was chosen to provide more explicit readability of where the insertion points are. -singleton or paired tags can be replaced. -Failing to include the tag in the resultant line results in an error. -#------------------------ -#replacement of a singleton tag -tagline line {@set "nextshell=tclsh" & :: @} -#replacement of closing tag of a paired-tag -tagline line {# some comment or data} -#------------------------ - - -The .wrapconf could also specify a specific template in your scriptapps/wrappers folder e.g: -#------------------------ -template myutility-multishell.cmd -#------------------------ - -Leave template line out, or specify the defaults if you want to use the wrappers from the punk shell you are using. e.g -#------------------------ -template punk-multishell.cmd -#------------------------ diff --git a/src/scriptapps/wrappers/sample_punk-multishell.cmd b/src/scriptapps/wrappers/sample_basic-multishell.cmd similarity index 96% rename from src/scriptapps/wrappers/sample_punk-multishell.cmd rename to src/scriptapps/wrappers/sample_basic-multishell.cmd index 9d903392..5b0593f2 100644 --- a/src/scriptapps/wrappers/sample_punk-multishell.cmd +++ b/src/scriptapps/wrappers/sample_basic-multishell.cmd @@ -124,6 +124,7 @@ namespace eval ::punk::multishell { # +puts "tcl payload" # @@ -162,13 +163,11 @@ if false==false # else { # -- # ## ### ### ### ### ### ### ### ### ### ### ### ### ### # -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload -#printf "start of bash or sh code" +#printf "start of bash code" -# -# # -- --- --- --- --- --- --- --- -# +# exitcode=0 ;#default assumption #-- sh/bash launches Tcl here instead of shebang line at top #-- use exec to use exitcode (if any) directly from the tcl script @@ -179,11 +178,9 @@ exitcode=$? #echo "tcl exitcode: ${exitcode}" #-- override exitcode example #exit 66 -# +# # -- --- --- --- --- --- --- --- -# -# #printf "sh/bash done \n" @@ -211,20 +208,14 @@ $scriptname = getScriptName #"powershell args : {0}" -f ($args -join ", ") | write-host # -- --- --- --- -# -# - # -- --- --- --- --- --- --- --- -# +# tclsh $scriptname $args -# +# # -- --- --- --- --- --- --- --- -# -# - # unbal } # -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload diff --git a/src/scriptapps/wrappers/punk-shellbat.bat b/src/scriptapps/wrappers/sample_basic-shellbat.bat similarity index 88% rename from src/scriptapps/wrappers/punk-shellbat.bat rename to src/scriptapps/wrappers/sample_basic-shellbat.bat index aa9039a9..72973318 100644 --- a/src/scriptapps/wrappers/punk-shellbat.bat +++ b/src/scriptapps/wrappers/sample_basic-shellbat.bat @@ -10,13 +10,13 @@ # It is tuned to run when called as a batch file, a tcl script, an sh script or a bash script, # so the specific layout and characters used are quite sensitive to change. # It can be called on unix or windows platforms with or without the interpreter being specified on the commandline. -# e.g ./filename.sh.bat in sh or bash or powershell -# e.g filename.sh or filename.sh.bat at windows command prompt -# e.g tclsh filename.sh.bat | sh filename.sh.bat | bash filename.sh.bat +# e.g ./filename.cmd in bash or powershell +# e.g filename.cmd at windows command prompt +# e.g tclsh filename.cmd | bash filename.cmd # In all cases an arbitrary number of arguments are accepted # To avoid the initial commandline on stdout when calling as a batch file on windows, use: -# cmd /Q /c filename.sh.bat -# (because we cannot use @if to silence it, as this isn't understood by tcl,sh or bash) +# cmd /Q /c filename.cmd +# (because we cannot use @if to silence it, as this isn't understood by tcl or bash) # ################################################################################################# #fconfigure stdout -translation crlf # --- --- --- --- --- --- --- --- --- --- --- --- ---begin Tcl Payload @@ -54,25 +54,20 @@ then #-- adjust line 4: @call tclsh ... to something like @call sh ... @call bash .. or @call env sh ... etc as appropriate #-- if sh/bash scripting needs to run on windows too. #-- - #printf "start of bash or sh code" + #printf "start of bash code" - # - # - - #-- sh/bash launches Tcl here instead of shebang line at top - # + #-- bash launches Tcl here instead of shebang line at top + # #-- use exec to use exitcode (if any) directly from the tcl script exec /usr/bin/env tclsh "$0" "$@" - # + # #-- alternative - if sh/bash script required to run after the tcl call. #/usr/bin/env tclsh "$0" "$@" #tcl_exitcode=$? #echo "tcl_exitcode: ${tcl_exitcode}" - # - # #-- override exitcode example #exit 66 diff --git a/src/scriptapps/wrappers/sample_punk-shellbat.bat b/src/scriptapps/wrappers/sample_punk-shellbat.bat deleted file mode 100644 index aa9039a9..00000000 --- a/src/scriptapps/wrappers/sample_punk-shellbat.bat +++ /dev/null @@ -1,112 +0,0 @@ -: "[proc : args {}]" ;# *tcl shellbat - call with sh,bash,tclsh on any platform, or with cmd on windows. -: <<'HIDE_FROM_BASH_AND_SH' -: ;# leading colon hides from .bat, trailing slash hides next line from tcl \ -@call tclsh "%~dp0%~n0.bat" %* -: ;#\ -@set taskexitcode=%errorlevel% & goto :exit -# -*- tcl -*- -# ################################################################################################# -# This is a tcl shellbat file -# It is tuned to run when called as a batch file, a tcl script, an sh script or a bash script, -# so the specific layout and characters used are quite sensitive to change. -# It can be called on unix or windows platforms with or without the interpreter being specified on the commandline. -# e.g ./filename.sh.bat in sh or bash or powershell -# e.g filename.sh or filename.sh.bat at windows command prompt -# e.g tclsh filename.sh.bat | sh filename.sh.bat | bash filename.sh.bat -# In all cases an arbitrary number of arguments are accepted -# To avoid the initial commandline on stdout when calling as a batch file on windows, use: -# cmd /Q /c filename.sh.bat -# (because we cannot use @if to silence it, as this isn't understood by tcl,sh or bash) -# ################################################################################################# -#fconfigure stdout -translation crlf -# --- --- --- --- --- --- --- --- --- --- --- --- ---begin Tcl Payload -#puts "script : [info script]" -#puts "argcount : $::argc" -#puts "argvalues: $::argv" - - -# -# - -# --- --- --- --- --- --- --- --- --- --- --- --- --- -# only exit if needed. see exitcode notes at bottom of file and exit there for consistency across invocation methods -# --- --- --- --- --- --- --- --- --- --- --- --- ---end Tcl Payload -#-- -#-- bash/sh code follows. -#-- protect from tcl using line continuation char on the previous comment for each line, like so: \ -printf "etc" -#-- or alternatively place sh/bash script within the false==false block -#-- whilst being careful to balance braces {} -#-- For more complex needs you should call out to external scripts -#-- -#-- END marker for hide_from_bash_and_sh\ -HIDE_FROM_BASH_AND_SH - -#--------------------------------------------------------- -#-- This if statement hides(mostly) a sh/bash code block from Tcl -if false==false # else { -then -: -#--------------------------------------------------------- - #-- leave as is if all that's required is launching the Tcl payload" - #-- - #-- Note that sh/bash script isn't called when running a .bat from cmd.exe on windows by default - #-- adjust line 4: @call tclsh ... to something like @call sh ... @call bash .. or @call env sh ... etc as appropriate - #-- if sh/bash scripting needs to run on windows too. - #-- - #printf "start of bash or sh code" - - # - # - - - #-- sh/bash launches Tcl here instead of shebang line at top - # - #-- use exec to use exitcode (if any) directly from the tcl script - exec /usr/bin/env tclsh "$0" "$@" - # - - #-- alternative - if sh/bash script required to run after the tcl call. - #/usr/bin/env tclsh "$0" "$@" - #tcl_exitcode=$? - #echo "tcl_exitcode: ${tcl_exitcode}" - - # - # - - #-- override exitcode example - #exit 66 - - #printf "No need for trailing slashes for sh/bash code here\n" -#--------------------------------------------------------- -fi -# closing brace for Tcl } -#--------------------------------------------------------- - -#-- tcl and shell script now both active - -#-- comment for line sample 1 with trailing continuation slash \ -#printf "tcl-invisible sh/bash line sample 1 \n" - -#-- comment for line sample 2 with trailing continuation slash \ -#printf "tcl-invisible sh/bash line sample 2 \n" - - -#-- Consistent exitcode from sh,bash,tclsh or cmd -#-- Call exit in tcl (or sh/bash) code only if explicitly required, otherwise leave this commented out. -#-- (script might be more widely useable without explicit exit. e.g in tcl: set ::argc 1; set ::argv "val"; source filename.sh.bat ) -#-- exit line unprotected by trailing slash will work for tcl and/or sh/bash -#exit 0 -#exit 42 - - - -#-- make sure sh/bash/tcl all skip over .bat style exit \ -: <<'shell_end' -#-- .bat exit with exitcode from tcl process \ -:exit -: ;# \ -@exit /B %taskexitcode% -# .bat has exited \ -shell_end -