Browse Source

misc scriptapps and file/folder organisation

master
Julian Noble 2 weeks ago
parent
commit
a7b9f4369f
  1. 13
      .gitignore
  2. 587
      getpunk.ps1
  3. 5
      src/embedded/README.md
  4. 11
      src/modules_tcl8/README.md
  5. 11
      src/modules_tcl9/README.md
  6. 18
      src/runtime/Readme.md
  7. 82
      src/runtime/mapvfs.config
  8. 93
      src/scriptapps/wrappers/README.md
  9. 21
      src/scriptapps/wrappers/sample_basic-multishell.cmd
  10. 23
      src/scriptapps/wrappers/sample_basic-shellbat.bat
  11. 112
      src/scriptapps/wrappers/sample_punk-shellbat.bat

13
.gitignore vendored

@ -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

587
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
: <<nextshell_start>>
@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____________"
: <<nextshell_end>>
@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"
#</tcl-payload>
#<tcl-pre-launch-subprocess>
#</tcl-pre-launch-subprocess>
#<tcl-launch-subprocess>
#</tcl-launch-subprocess>
#<tcl-post-launch-subprocess>
#</tcl-post-launch-subprocess>
# -- --- --- --- --- --- --- --- --- --- --- ---
# -- 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"
#<shell-payload>
@ -1090,29 +1312,9 @@ cd $launchdir #restore original CWD
#</shell-payload>
#<shell-pre-launch-subprocess>
#</shell-pre-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<shell-launch-subprocess>
#-- 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
#</shell-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<shell-post-launch-subprocess>
#</shell-post-launch-subprocess>
#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";
#</perl-payload>
#<perl-pre-launch-subprocess>
#</perl-pre-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<perl-launch-subprocess>
#$exit_code=system("tclsh", $scriptname, @ARGV);
#print "perl reporting tcl exitcode: $exit_code";
#</perl-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<perl-post-launch-subprocess>
#</perl-post-launch-subprocess>
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---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"
#</powershell-payload>
#<powershell-pre-launch-subprocess>
#</powershell-pre-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<powershell-launch-subprocess>
#tclsh $scriptname $args
#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host
#</powershell-launch-subprocess>
# -- --- --- --- --- --- --- ---
#<powershell-post-launch-subprocess>
#</powershell-post-launch-subprocess>
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload
Exit $LASTEXITCODE

5
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)

11
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 <name> 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

11
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 <name> 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

18
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/<os-architecture> 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.

82
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}

93
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
#<tcl-payload>
#</tcl-payload>
yourscriptname.sh (if present) will be substituted between
#<shell-payload-pre-tcl>
#</shell-payload-pre-tcl>
yourscriptname.bash (if present) will be substituted between
#<shell-payload>
#</shell-payload>
yourscriptname.ps1 (if present) will be substituted between
#<powershell-payload-pre-tcl>
#</powershell-payload-pre-tcl>
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:
#<shell-launch-tcl>
#</shell-launch-tcl>
#<shell-payload-post-tcl>
#</shell-payload-post-tcl>
#<powershell-launch-tcl>
#/<powershell-launch-tcl>
#<powershell-payload-post-tcl>
#</powershell-payload-post-tcl>
The .wrapconf file can have comment lines (beginning with # and possibly whitespace)
#<powershell-payload>
#</powershell-payload>
e.g myutility.wrapconf might contain:
#------------------------
tagdata <shell-payload-pre-tcl> file myutility_download-tclkit2.sh
tagdata <shell-launch-tcl> file myutility_launch-with-tclkit2.sh
tagdata <powershell-payload-pre-tcl> file myutility_download-tclkit2.ps1
tagdata <powershell-launch-tcl> file myutility_launch-with-tclkit2.ps1
#------------------------
yourscriptname.pl (if present) will be substituted between
#<perl-payload>
#</perl-payload>
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 <tag/> vs a paired set <tag> .. </tag>
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.
# <mytag> something etc </mytag> # etc
This is because system is designed to allow repeated updates and analysis of existing output files.
# <payload-tcl> something etc </payload-tcl> # 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 <batch-nextshell-line/> line {@set "nextshell=tclsh" & :: @<batch-nextshell-line/>}
#replacement of closing tag of a paired-tag
tagline </powershell-launch-tcl> line {#</powershell-launch-tcl> 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
#------------------------

21
src/scriptapps/wrappers/sample_punk-multishell.cmd → src/scriptapps/wrappers/sample_basic-multishell.cmd

@ -124,6 +124,7 @@ namespace eval ::punk::multishell {
#<tcl-payload>
puts "tcl payload"
#</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"
#<shell-payload-pre-tcl>
#</shell-payload-pre-tcl>
# -- --- --- --- --- --- --- ---
#<shell-launch-tcl>
#<shell-payload>
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
#</shell-launch-tcl>
#</shell-payload>
# -- --- --- --- --- --- --- ---
#<shell-payload-post-tcl>
#</shell-payload-post-tcl>
#printf "sh/bash done \n"
@ -211,20 +208,14 @@ $scriptname = getScriptName
#"powershell args : {0}" -f ($args -join ", ") | write-host
# -- --- --- ---
#<powershell-payload-pre-tcl>
#</powershell-payload-pre-tcl>
# -- --- --- --- --- --- --- ---
#<powershell-launch-tcl>
#<powershell-payload>
tclsh $scriptname $args
#</powershell-launch-tcl>
#</powershell-payload>
# -- --- --- --- --- --- --- ---
#<powershell-payload-post-tcl>
#</powershell-payload-post-tcl>
# unbal }
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload

23
src/scriptapps/wrappers/punk-shellbat.bat → 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"
#<shell-payload-pre-tcl>
#</shell-payload-pre-tcl>
#-- sh/bash launches Tcl here instead of shebang line at top
#<shell-launch-tcl>
#-- bash launches Tcl here instead of shebang line at top
#<shell-payload>
#-- use exec to use exitcode (if any) directly from the tcl script
exec /usr/bin/env tclsh "$0" "$@"
#</shell-launch-tcl>
#</shell-payload>
#-- alternative - if sh/bash script required to run after the tcl call.
#/usr/bin/env tclsh "$0" "$@"
#tcl_exitcode=$?
#echo "tcl_exitcode: ${tcl_exitcode}"
#<shell-payload-post-tcl>
#</shell-payload-post-tcl>
#-- override exitcode example
#exit 66

112
src/scriptapps/wrappers/sample_punk-shellbat.bat

@ -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"
#<tcl-payload>
#<tcl-payload/>
# --- --- --- --- --- --- --- --- --- --- --- --- ---
# 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"
#<shell-payload-pre-tcl>
#</shell-payload-pre-tcl>
#-- sh/bash launches Tcl here instead of shebang line at top
#<shell-launch-tcl>
#-- use exec to use exitcode (if any) directly from the tcl script
exec /usr/bin/env tclsh "$0" "$@"
#</shell-launch-tcl>
#-- alternative - if sh/bash script required to run after the tcl call.
#/usr/bin/env tclsh "$0" "$@"
#tcl_exitcode=$?
#echo "tcl_exitcode: ${tcl_exitcode}"
#<shell-payload-post-tcl>
#</shell-payload-post-tcl>
#-- 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
Loading…
Cancel
Save