From 963a5a788b36a0dffb5060c75222fac8c01e2aa6 Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Thu, 18 Sep 2025 03:22:58 +1000 Subject: [PATCH] make.tcl and console rawmode fixes --- .gitignore | 3 +- bin/fetchruntime.cmd | 1046 ----------------- bin/fetchruntime_old.cmd | 612 ---------- bin/runtime.cmd | 43 +- bin/sdx.bat | 4 - bin/sdx.kit | Bin 119312 -> 119191 bytes scriptlib/stdout_per_second.tcl | 59 +- scriptlib/utils/pwsh/consolemode.ps1 | 201 ++++ .../utils/pwsh/consolemode_enableraw.ps1 | 91 ++ scriptlib/utils/pwsh/consolemode_server.ps1 | 144 +++ .../utils/pwsh/consolemode_server_async.2ps1 | 244 ++++ .../utils/pwsh/consolemode_server_async.ps1 | 262 +++++ .../utils/pwsh/consolemode_server_async1.ps1 | 266 +++++ scriptlib/utils/pwsh/echotest.ps1 | 1 + src/bootsupport/modules/punk/args-0.2.tm | 5 +- .../modules/punk/args/tclcore-0.1.0.tm | 2 +- src/bootsupport/modules/punk/console-0.1.1.tm | 666 ++++++----- .../modules/punk/libunknown-0.1.tm | 58 +- src/bootsupport/modules/punk/repl-0.1.2.tm | 64 +- src/bootsupport/modules_tcl8/Thread-2.8.9.tm | Bin 14253 -> 0 bytes .../modules_tcl8/include_modules.config | 3 - .../platform/win32_x86_64_tcl8-2.8.9.tm | Bin 79939 -> 0 bytes .../modules_tcl8/win32_x86_64_tcl8-2.8.9.tm | Bin 79939 -> 0 bytes src/lib/app-punk/repl.tcl | 16 +- src/lib/app-punkshell/punkshell.tcl | 1 + src/make.tcl | 157 ++- src/modules/punk/args-999999.0a1.0.tm | 5 +- src/modules/punk/args/tclcore-999999.0a1.0.tm | 2 +- src/modules/punk/console-999999.0a1.0.tm | 666 ++++++----- src/modules/punk/libunknown-0.1.tm | 58 +- .../utility/scriptappwrappers/multishell.cmd | 39 +- src/modules/punk/repl-999999.0a1.0.tm | 64 +- .../custom/_project/punk.basic/src/make.tcl | 157 ++- .../src/bootsupport/modules/punk/args-0.2.tm | 5 +- .../modules/punk/args/tclcore-0.1.0.tm | 2 +- .../bootsupport/modules/punk/console-0.1.1.tm | 666 ++++++----- .../modules/punk/libunknown-0.1.tm | 58 +- .../bootsupport/modules/punk/repl-0.1.2.tm | 64 +- .../_project/punk.project-0.1/src/make.tcl | 157 ++- .../src/bootsupport/modules/punk/args-0.2.tm | 5 +- .../modules/punk/args/tclcore-0.1.0.tm | 2 +- .../bootsupport/modules/punk/console-0.1.1.tm | 666 ++++++----- .../modules/punk/libunknown-0.1.tm | 58 +- .../bootsupport/modules/punk/repl-0.1.2.tm | 64 +- .../_project/punk.shell-0.1/src/make.tcl | 157 ++- src/runtime/mapvfs.config | 4 +- src/scriptapps/bin/readme.txt | 1 + src/scriptapps/example_out.bat | 743 ------------ src/scriptapps/example_wrap.toml | 25 +- 49 files changed, 3621 insertions(+), 3995 deletions(-) delete mode 100644 bin/fetchruntime.cmd delete mode 100644 bin/fetchruntime_old.cmd delete mode 100644 bin/sdx.bat create mode 100644 scriptlib/utils/pwsh/consolemode.ps1 create mode 100644 scriptlib/utils/pwsh/consolemode_enableraw.ps1 create mode 100644 scriptlib/utils/pwsh/consolemode_server.ps1 create mode 100644 scriptlib/utils/pwsh/consolemode_server_async.2ps1 create mode 100644 scriptlib/utils/pwsh/consolemode_server_async.ps1 create mode 100644 scriptlib/utils/pwsh/consolemode_server_async1.ps1 create mode 100644 scriptlib/utils/pwsh/echotest.ps1 delete mode 100644 src/bootsupport/modules_tcl8/Thread-2.8.9.tm delete mode 100644 src/bootsupport/modules_tcl8/thread/platform/win32_x86_64_tcl8-2.8.9.tm delete mode 100644 src/bootsupport/modules_tcl8/win32_x86_64_tcl8-2.8.9.tm create mode 100644 src/scriptapps/bin/readme.txt delete mode 100644 src/scriptapps/example_out.bat diff --git a/.gitignore b/.gitignore index 8ecbb93a..7fa98142 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ -*.lastrun +/*.lastrun +/*.ps1 #/bin/ /bin/* diff --git a/bin/fetchruntime.cmd b/bin/fetchruntime.cmd deleted file mode 100644 index f496014c..00000000 --- a/bin/fetchruntime.cmd +++ /dev/null @@ -1,1046 +0,0 @@ -: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set S;proc Hide x {proc $x 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 + \ -: "[Hide @GOTO; Hide =begin; Hide @REM] #not necessary but can help avoid errs in testing" + -: << 'HEREDOC1B_HIDE_FROM_BASH_AND_SH' -: STRONG SUGGESTION: DO NOT MODIFY FIRST LINE OF THIS SCRIPT - except for first double quoted section. -: shebang line is not required on unix or windows and will reduce functionality and/or portability. -: Even comment lines can be part of the functionality of this script (both on unix and windows) - modify with care. -@GOTO :skip_perl_pod_start ^; -=begin excludeperl -: skip_perl_pod_start -: 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 It should remain portable between unix-like OSes & windows if the proper structure is maintained. -@REM ############################################################################################################################ -@REM Change the value of nextshell to one of the supported types, and add code within payload sections for tcl,sh,bash,powershell as appropriate. -@REM This wrapper can be edited manually (carefully!) - or bash,tcl,perl,powershell scripts can be wrapped using the Tcl-based punkshell system -@REM e.g from within a running punkshell: dev scriptwrap.multishell -outputfolder -@REM Call with sh, bash, perl, or tclsh. (powershell untested on unix) -@REM Due to lack of shebang (#! line) Unix-like systems will hopefully default to a flavour of sh that can divert to bash if the script is called without an interpreter - but it may depend on the shell in use when called. -@REM If you find yourself really wanting/needing to add a shebang line - do so on the basis that the script will exist on unix-like systems only. -@REM in batch scripts - array syntax with square brackets is a simulation of arrays or associative arrays. -@REM note that many shells linked as sh do not support substition syntax and may fail - e.g dash etc - generally bash should be used in this context -@SETLOCAL EnableExtensions EnableDelayedExpansion -@SET "validshelltypes= pwsh____________ powershell______ sh______________ wslbash_________ bash____________ tcl_____________ perl____________ none____________" -@REM for batch - only win32 is relevant - but other scripts on other platforms also parse the nextshell block to determine next shell to launch -@REM nextshellpath and nextshelltype indices (underscore-padded to 16wide) are "other" plus those returned by Tcl platform pkg e.g win32,linux,freebsd,macosx -@REM The horrible underscore-padded fixed-widths are to keep the batch labels aligned whilst allowing values to be set -@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______________________________________________________" -@SET "nextshelltype[win32___________]=powershell______" -@SET "nextshellpath[dragonflybsd____]=/usr/bin/env bash_______________________________________________" -@SET "nextshelltype[dragonflybsd____]=bash____________" -@SET "nextshellpath[freebsd_________]=/usr/bin/env bash_______________________________________________" -@SET "nextshelltype[freebsd_________]=bash____________" -@SET "nextshellpath[netbsd__________]=/usr/bin/env bash_______________________________________________" -@SET "nextshelltype[netbsd__________]=bash____________" -@SET "nextshellpath[linux___________]=/usr/bin/env bash_______________________________________________" -@SET "nextshelltype[linux___________]=bash____________" -@SET "nextshellpath[macosx__________]=/usr/bin/env bash_______________________________________________" -@SET "nextshelltype[macosx__________]=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). -: <> -@SET "asadmin=0" -: <> -@REM @ECHO nextshelltype is %nextshelltype[win32___________]% -@REM @SET "selected_shelltype=%nextshelltype[win32___________]%" -@SET "selected_shelltype=%nextshelltype[win32___________]%" -@REM @ECHO selected_shelltype %selected_shelltype% -@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 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 -@REM ## ### ### ### ### ### ### ### ### ### ### ### ### ### -@REM -- cmd/batch file section (ignored on unix but should be left in place) -@REM -- This section intended mainly to launch the next shell (and to escalate privileges if necessary) -@REM -- Avoid customising this if you are not familiar with batch scripting. cmd/batch script can be useful, but is probably the least expressive language and most error prone. -@REM -- For example - as this file needs to use unix-style lf line-endings - the label scanner is susceptible to the 512Byte boundary issue: https://www.dostips.com/forum/viewtopic.php?t=8988#p58888 -@REM -- This label issue can be triggered/abused in files with crlf line endings too - but it is less likely to happen accidentaly. -@REm -- See also: https://stackoverflow.com/questions/4094699/how-does-the-windows-command-interpreter-cmd-exe-parse-scripts/4095133#4095133 -@REM ############################################################################################################################ -@REM -- Due to this issue -seemingly trivial edits of the batch file section can break the script! (for Windows anyway) -@REM -- Even something as simple as adding or removing an @REM -@REM -- From within punkshell - use: -@REM -- deck scriptwrap.checkfile -@REM -- to check your templates or final wrapped scripts for byte boundary issues -@REM -- It will report any labels that are on boundaries -@REM -- This is why the nextshell value above is a 2 digit key instead of a string - so that editing the value doesn't change the byte offsets. -@REM -- Editing your sh,bash,tcl,pwsh payloads is much less likely to cause an issue. There is the possibility of the final batch :exit_multishell label spanning a boundary - so testing using deck scriptwrap.checkfile is still recommended. -@REM -- Alternatively, as you should do anyway - test the final script on windows -@REM -- Aside from adding comments/whitespace to tweak the location of labels - you can try duplicating the label (e.g just add the label on a line above) but this is not guaranteed to work in all situations. -@REM -- '@REM' is a safer comment mechanism than a leading colon - which is used sparingly here. -@REM -- A colon anywhere in the script that happens to land on a 512 Byte boundary (from file start or from a callsite) could be misinterpreted as a label -@REM -- It is unknown what versions of cmd interpreters behave this way - and deck scriptwrap.checkfile doesn't check all such boundaries. -@REm -- For this reason, batch labels should be chosen to be relatively unlikely to collide with other strings in the file, and simple names such as :exit or :end should probably be avoided -@REM ############################################################################################################################ -@REM -- custom windows payloads should be in powershell,tclsh (or sh/bash if available) code sections -@REM ## ### ### ### ### ### ### ### ### ### ### ### ### ### -@SET "winpath=%~dp0" -@SET "fname=%~nx0" -@REM @ECHO fname %fname% -@REM @ECHO winpath %winpath% -@REM @ECHO commandlineascalled %0 -@REM @ECHO commandlineresolved %~f0 -@CALL :getNormalizedScriptTail nftail -@REM @ECHO normalizedscripttail %nftail% -@CALL :getFileTail %0 clinetail -@REM @ECHO clinetail %clinetail% -@CALL :stringToUpper %~nx0 capscripttail -@REM @ECHO capscriptname: %capscripttail% - -@IF "%nftail%"=="%capscripttail%" ( - @ECHO forcing asadmin=1 due to file name on filesystem being uppercase - @SET "asadmin=1" -) else ( - @CALL :stringToUpper %clinetail% capcmdlinetail - @REM @ECHO capcmdlinetail !capcmdlinetail! - IF "%clinetail%"=="!capcmdlinetail!" ( - @ECHO forcing asadmin=1 due to cmdline scriptname in uppercase - @set "asadmin=1" - ) -) -@SET "vbsGetPrivileges=%temp%\punk_bat_elevate_%fname%.vbs" -@SET arglist=%* -@SET "qstrippedargs=args%arglist%" -@SET "qstrippedargs=%qstrippedargs:"=%" -@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" ( - GOTO :gotPrivileges -) -@IF !asadmin!==1 ( - net file 1>NUL 2>NUL - @IF '!errorlevel!'=='0' ( GOTO :gotPrivileges ) else ( GOTO :getPrivileges ) -) -@REM padding -@REM padding -@REM padding -@REM padding -@REM padding -@REM padding -@REM padding -@REM padding -@REM padding -@REM padding -@REM padding -@REM padding -@GOTO skip_privileges -: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 For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%" -@ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%" -@ECHO Next >> "%vbsGetPrivileges%" -@ECHO UAC.ShellExecute "%~dp0%~n0%~x0", args, "", "runas", 1 >> "%vbsGetPrivileges%" -@ECHO Launching script in new window due to administrator elevation -@"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %* -@EXIT /B - -:gotPrivileges -@REM setlocal & pushd . -@PUSHD . -@cd /d %~dp0 -@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" ( - @DEL "%vbsGetPrivileges%" 1>nul 2>nul - @SET arglist=%arglist:~14% -) - -: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" ( - @SET need_ps1=1 -) ELSE ( - fc "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >nul || goto different - @REM @ECHO "files same" - @SET need_ps1=0 -) -@GOTO :pscontinue -:different -@REM @ECHO "files differ" -@SET need_ps1=1 -:pscontinue -@IF !need_ps1!==1 ( - COPY "%~dp0%~n0%~x0" "%~dp0%~n0.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 -) -@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 - 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 ( - pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.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 -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist% - powershell -nop -nol -ExecutionPolicy Bypass -c "%~dp0%~n0.ps1" %arglist% - SET task_exitcode=!errorlevel! - ) -) ELSE ( - IF "!selected_shelltype_trimmed!"=="powershell" ( - powershell -nop -nol -ExecutionPolicy Bypass -c "%~dp0%~n0.ps1" %arglist% - SET task_exitcode=!errorlevel! - ) ELSE ( - IF "!selected_shelltype_trimmed!"=="wslbash" ( - CALL :getWslPath %winpath% wslpath - REM ECHO wslfullpath "!wslpath!%fname%" - %selected_shellpath_trimmed% "!wslpath!%fname%" %arglist% - SET task_exitcode=!errorlevel! - ) ELSE ( - REM perl or tcl or sh or bash - IF NOT "x%keyRemoved%"=="x%validshelltypes%" ( - 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% "%~dp0%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 -@REM boundary padding -@GOTO :endlib - -:getWslPath -@SETLOCAL - @SET "_path=%~p1" - @SET "name=%~nx1" - @SET "drive=%~d1" - @SET "rtrn=%~2" - @REM Although drive letters on windows are normally upper case wslbash seems to expect lower case drive letters - @CALL :stringToLower %drive ldrive - @SET "result=/mnt/%ldrive:~0,1%%_path:\=/%%name%" -@ENDLOCAL & ( - @if "%~2" neq "" ( - SET "%rtrn%=%result%" - ) ELSE ( - ECHO %result% - ) -) -@EXIT /B - -: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 -@REM This function is designed explicitly to preserve capitalisation -@REM accepts full paths with either / or \ as delimiters - or -@SETLOCAL - @SET "rtrn=%~2" - @SET "arg=%~1" - @REM @SET "result=%_arg:*/=%" - @REM @SET "result=%~1" - @SET LF=^ - - - : The above 2 empty lines are important. Don't remove - @CALL :stringContains "!arg!" "\" hasBackSlash - @IF "!hasBackslash!"=="true" ( - @for %%A in ("!LF!") do @( - @FOR /F %%B in ("!arg:\=%%~A!") do @set "result=%%B" - ) - ) ELSE ( - @CALL :stringContains "!arg!" "/" hasForwardSlash - @IF "!hasForwardSlash!"=="true" ( - @FOR %%A in ("!LF!") do @( - @FOR /F %%B in ("!arg:/=%%~A!") do @set "result=%%B" - ) - ) ELSE ( - @set "result=%arg%" - ) - ) -@ENDLOCAL & ( - @if "%~2" neq "" ( - @SET "%rtrn%=%result%" - ) ELSE ( - @ECHO %result% - ) -) -@EXIT /B -@REM boundary padding -@REM boundary padding -:getNormalizedScriptTail -@SETLOCAL - @SET "result=%~nx0" - @SET "rtrn=%~1" -@ENDLOCAL & ( - @IF "%~1" neq "" ( - @SET "%rtrn%=%result%" - ) ELSE ( - @ECHO %result% - ) -) -@EXIT /B - -:getNormalizedFileTailFromPath -@REM warn via echo, and do not set return variable if path not found -@REM note that %~nx1 does not preserve case of provided path - hence the name 'normalized' -@REM boundary padding -@REM boundary padding -@REM boundary padding -@REM boundary padding -@SETLOCAL - @CALL :stringContains %~1 "\" hasBackSlash - @CALL :stringContains %~1 "/" hasForwardSlash - @IF "%hasBackslash%-%hasForwardslash%"=="false-false" ( - @SET "P=%cd%%~1" - @CALL :getNormalizedFileTailFromPath "!P!" ftail2 - @SET "result=!ftail2!" - ) else ( - @IF EXIST "%~1" ( - @SET "result=%~nx1" - ) else ( - @ECHO error getNormalizedFileTailFromPath file not found: %~1 - @EXIT /B 1 - ) - ) - @SET "rtrn=%~2" -@ENDLOCAL & ( - @IF "%~2" neq "" ( - SET "%rtrn%=%result%" - ) ELSE ( - @ECHO getNormalizedFileTailFromPath %1 result: %result% - ) -) -@EXIT /B - -:stringContains -@REM usage: @CALL:stringContains string needle returnvarname -@SETLOCAL - @SET "rtrn=%~3" - @SET "string=%~1" - @SET "needle=%~2" - @IF "!string:%needle%=!"=="!string!" @( - @SET "result=false" - ) ELSE ( - @SET "result=true" - ) -@ENDLOCAL & ( - @IF "%~3" neq "" ( - @SET "%rtrn%=%result%" - ) ELSE ( - @ECHO stringContains %string% %needle% result: %result% - ) -) -@EXIT /B -@REM boundary padding -@REM boundary padding -:stringToUpper -@SETLOCAL - @SET "rtrn=%~2" - @SET "string=%~1" - @SET "capstring=%~1" - @FOR %%A in (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) DO @( - @SET "capstring=!capstring:%%A=%%A!" - ) - @SET "result=!capstring!" -@ENDLOCAL & ( - @IF "%~2" neq "" ( - @SET "%rtrn%=%result%" - ) ELSE ( - @ECHO stringToUpper %string% result: %result% - ) -) -@EXIT /B -:stringToLower -@SETLOCAL - @SET "rtrn=%~2" - @SET "string=%~1" - @SET "retstring=%~1" - @FOR %%A in (a b c d e f g h i j k l m n o p q r s t u v w x y z) DO @( - @SET "retstring=!retstring:%%A=%%A!" - ) - @SET "result=!retstring!" -@ENDLOCAL & ( - @IF "%~2" neq "" ( - @SET "%rtrn%=%result%" - ) ELSE ( - @ECHO stringToLower %string% result: %result% - ) -) -@EXIT /B -@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 - @SET "trimstring=%trimstring%###" - @SET "trimstring=%trimstring:________________________________###=###%" - @SET "trimstring=%trimstring:________________###=###%" - @SET "trimstring=%trimstring:________###=###%" - @SET "trimstring=%trimstring:____###=###%" - @SET "trimstring=%trimstring:__###=###%" - @SET "trimstring=%trimstring:_###=###%" - @SET "trimstring=%trimstring:###=%" - @SET "result=!trimstring!" -@ENDLOCAL & ( - @IF "%~2" neq "" ( - @SET "%rtrn%=%result%" - ) ELSE ( - @ECHO stringTrimTrailingUnderscores %string% result: %result% - ) -) -@EXIT /B -:isNumeric -@SETLOCAL - @SET "notnumeric="&FOR /F "delims=0123456789" %%i in ("%1") do set "notnumeric=%%i" - @IF defined notnumeric ( - @SET "result=false" - ) else ( - @SET "result=true" - ) - @SET "rtrn=%~2" -@ENDLOCAL & ( - @IF "%~2" neq "" ( - @SET "%rtrn%=%result%" - ) ELSE ( - @ECHO %result% - ) -) -@EXIT /B - -:endlib -: \ -@REM padding -@REM padding -@REM @SET taskexit_code=!errorlevel! & goto :exit_multishell -@GOTO :exit_multishell -# } -# -*- tcl -*- -# ## ### ### ### ### ### ### ### ### ### ### ### ### ### -# -- 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 -# -- i.e it is a polyglot file. -# -- 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 ./filename.polypunk.cmd in sh or bash -# -- e.g tclsh filename.cmd -# -- -# ## ### ### ### ### ### ### ### ### ### ### ### ### ### -rename set ""; rename S set; set k {-- "$@" "a}; if {[info exists ::env($k)]} {unset ::env($k)} ;# tidyup and restore -Hide :exit_multishell;Hide {<#};Hide '@ -namespace eval ::punk::multishell { - set last_script_root [file dirname [file normalize ${::argv0}/__]] - set last_script [file dirname [file normalize [info script]/__]] - if {[info exists ::argv0] && - $last_script eq $last_script_root - } { - set ::punk::multishell::is_main($last_script) 1 ;#run as executable/script - likely desirable to launch application and return an exitcode - } else { - set ::punk::multishell::is_main($last_script) 0 ;#sourced - likely to be being used as a library - no launch, no exit. Can use return. - } - if {"::punk::multishell::is_main" ni [info commands ::punk::multishell::is_main]} { - proc ::punk::multishell::is_main {{script_name {}}} { - if {$script_name eq ""} { - set script_name [file dirname [file normalize [info script]/--]] - } - if {![info exists ::punk::multishell::is_main($script_name)]} { - #e.g a .dll or something else unanticipated - puts stderr "Warning punk::multishell didn't recognize info script result: $script_name - will treat as if sourced and return instead of exiting" - puts stderr "Info: script_root: [file dirname [file normalize ${::argv0}/__]]" - return 0 - } - return [set ::punk::multishell::is_main($script_name)] - } - } -} -# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin Tcl Payload -#puts "script : [info script]" -#puts "argcount : $::argc" -#puts "argvalues: $::argv" -#puts "argv0 : $::argv0" -# -- --- --- --- --- --- --- --- --- --- --- --- - -# - -set url_kitbase "https://www.gitea1.intx.com.au/jn/punkbin/raw/branch/master" - -package require http -package require tls -http::register https 443 [list ::tls::socket -autoservername true] -package require platform -set plat [platform::identify] -set os [lindex [split $plat -] 0] -set runtime_available 0 -set scriptdir [file dirname [info script]] -switch -- $os { - "win32" { - set url "$url_kitbase/win32-x86_64/tclsh902z.exe" - set output [file join $scriptdir "../src/runtime/tclsh902z.exe"] - set runtime_available 1 - } - "linux" { - switch -glob -- $plat { - *x86_64 { - set url "$url_kitbase/linux-x86_64/tclkit-902-Linux64-intel-dyn" - set output [file join $scriptdir "../src/runtime/tclkit-902-Linux64-intel-dyn"] - set runtime_available 1 - } - *arm { - set url "$url_kitbase/linux-x86_64/tclkit-902-Linux64-arm-dyn" - set output [file join $scriptdir "../src/runtime/tclkit-902-Linux64-arm-dyn"] - set runtime_available 1 - } - default { - # - puts stderr "No runtime currently available for linux $::tcl_platform(machine)" - } - } - } - "macosx" { - set url "$url_kitbase/macosx/tclkit-902-Darwin64-dyn" - set output [file join $scriptdir "../src/runtime/tclkit-902-Darwin64-dyn"] - set runtime_available 1 - } - "freebsd" { - puts stderr "No runtime currently available for freebsd" - } - default { - puts stderr "No runtime currently available for $os" - } -} - -if {$runtime_available} { - if {[file exists $output]} { - puts stderr "Runtime already found at $output" - exit 1 - } - puts stdout "Attempting to download $url" - set fd [open $output wb] - set tok [http::geturl $url -channel $fd -binary 1] - close $fd - if {[http::status $tok] eq "ok" && [http::ncode $tok] == 200} { - puts "Download complete." - } - http::cleanup $tok -} - -# - -# -# - -# -# - - -# -# - - -# -- --- --- --- --- --- --- --- --- --- --- --- -# -- Best practice is to always return or exit above, or just by leaving the below defaults in place. -# -- If the multishell script is modified to have Tcl below the Tcl Payload section, -# -- then Tcl bracket balancing needs to be carefully managed in the shell and powershell sections below. -# -- Only the # in front of the two relevant if statements below needs to be removed to enable Tcl below -# -- but the sh/bash 'then' and 'fi' would also need to be uncommented. -# -- This facility left in place for experiments on whether configuration payloads etc can be appended -# -- to tail of file - possibly binary with ctrl-z char - but utility is dependent on which other interpreters/shells -# -- can be made to ignore/cope with such data. -if {[::punk::multishell::is_main]} { - exit 0 -} else { - return -} -# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end Tcl Payload -# end hide from unix shells \ -HEREDOC1B_HIDE_FROM_BASH_AND_SH -# csh/tcsh/sh/bash use oldschool backticks and sed lowest common denominator \ -echo "script: `echo $0 | sed 's/^-//'`" -# csh/tcsh/sh/bash use oldschool backticks and sed lowest common denominator \ -echo "shell: " `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` -#csh/tcsh diversion \ -test "$argv[*]" != "[*]" && ( /usr/bin/env bash $argv[*]; exit ) -#other non-bash diversion \ -test `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` != "bash" && /usr/bin/env bash $0 -#review \ -test `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` != "bash" && exit -# sh/bash \ -shift && set -- "${@:1:$#-1}" - -#echo "shell:" `ps -o args= $$ | sed -E 's/^.*\/|^-//' | awk '{print $1}'` -#------------------------------------------------------ -# -- This if block only needed if Tcl didn't exit or return above. -if false==false # else { - then - : # -# ## ### ### ### ### ### ### ### ### ### ### ### ### ### -# -- sh/bash script section -# -- leave as is if all that is required is launching the Tcl payload" -# -- -# -- Note that sh/bash script isn't called when running a .bat/.cmd from cmd.exe on windows by default -# -- adjust the %nextshell% value above -# -- if sh/bash scripting needs to run on windows too. -# -- -# ## ### ### ### ### ### ### ### ### ### ### ### ### ### - -if [[ "$OSTYPE" == "linux"* ]]; then - os="linux" -elif [[ "$OSTYPE" == "darwin"* ]]; then - os="macosx" -elif [[ "$OSTYPE" == "freebsd"* ]]; then - os="freebsd" -elif [[ "$OSTYPE" == "dragonflybsd"* ]]; then - os="dragonflybsd" -elif [[ "$OSTYPE" == "netbsd"* ]]; then - os="netbsd" -elif [[ "$OSTYPE" == "win32" ]]; then - os="win32" -elif [[ "$OSTYPE" == "msys" ]]; then - echo MSYS - os="win32" - #review - need ps/sed/awk to determine shell? - 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}}" -else - #os="$OSTYPE" - os="other" -fi -echo ostype: $OSTYPE -shellconfigline=$( sed -n "/: <>/{:a;n;/: <>/q;p;ba}" "$0" | grep $os) -#echo $shellconfigline; -if [[ $shellconfigline == *"nextshelltype"* ]]; then - echo "found config for os $os" - split1="${shellconfigline#*=}" #remove everything through the first '=' - #echo "split1: $split1" - pathraw="${split1%%\"*}" #take everything before the quote - use %% to get longest match - pathraw="${pathraw//\"/}" #remove quote - nextshellpath="${pathraw/%_*/}" #remove trailing underscores (% = must match at end) - #echo "nextshellpath: $nextshellpath" - split2="${split1#*=}" - #echo "split2: $split2" - split2="${split2//\"/}" - nextshelltype="${split2/%_*/}" - echo "nextshelltype: $nextshelltype" -else - echo "unable to find config for os $os" - echo "shellconfigline: $shellconfigline" - nextshellpath="" - nextshelltype="" -fi -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 $nextshellpath on "$0" - #/usr/bin/env tclsh "$0" "$@" - ${nextshellpath} "$0" "$@" - - exitcode=$? - #echo "sh/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" - : -fi -# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload -#printf "start of bash or sh code" - -# - -url_kitbase="https://www.gitea1.intx.com.au/jn/punkbin/raw/branch/master" - -wdir="$(pwd)"; [ "$(pwd)" = "/" ] && wdir="" -case "$0" in - /*) scriptpath="${0}";; - *) scriptpath="$wdir/${0#./}";; -esac -scriptdir="${scriptpath%/*}" -scriptdir=$(realpath $scriptdir) -scriptpath=$(realpath $scriptpath) -basename=$(basename "$scriptpath") #e.g fetchruntime.bash -scriptroot="${basename%.*}" #e.g "fetchruntime" - - -runtime_available=0 -if [[ "$OSTYPE" == "linux"* ]]; then - arch=$(uname -i) - if [[ "$arch" == "x86_64"* ]]; then - url="${url_kitbase}/linux-x86_64/tclkit-902-Linux64-intel-dyn" - outdir="${scriptdir}/runtime/linux-x86_64"; mkdir -p $outdir - output="${outdir}/tclkit-902-Linux64-intel-dyn" - runtime_available=1 - elif [[ "$arch" == "arm"* ]]; then - url="${url_kitbase}/linux-arm/tclkit-902-Linux64-arm-dyn" - outdir="${scriptdir}/runtime/linux-arm"; mkdir -p $outdir - output="${outdir}/tclkit-902-Linux64-arm-dyn" - runtime_available=1 - fi - if [[ "$runtime_available" -eq 1 ]]; then - echo "Please ensure libxFt.so.2 is available" - echo "e.g on Ubuntu: sudo apt-get install libxft2" - fi - os="linux" -elif [[ "$OSTYPE" == "darwin"* ]]; then - os="macosx" - #assumed to be Mach-O 'universal binaries' for both x86-64 and arm? - REVIEW - url="${url_kitbase}/macosx/tclkit-902-Darwin64-dyn" - outdir="${scriptdir}/runtime/macosx/"; mkdir -p $outdir - output="${outdir}/tclkit-902-Darwin64-dyn" - runtime_available=1 -elif [[ "$OSTYPE" == "freebsd"* ]]; then - os="freebsd" -elif [[ "$OSTYPE" == "dragonflybsd"* ]]; then - os="dragonflybsd" -elif [[ "$OSTYPE" == "netbsd"* ]]; then - os="netbsd" -elif [[ "$OSTYPE" == "win32" ]]; then - os="win32" - url="${url_kitbase}/win32-x86_64/tclsh902z.exe" - outdir="${scriptdir}/runtime/win32-x86_64/"; mkdir -p $outdir - output="${outdir}/tcsh902z.exe" - runtime_available=1 -elif [[ "$OSTYPE" == "msys" ]]; then - echo MSYS - os="win32" - #use 'command -v' (shell builtin preferred over external which) - interp = `ps -p $$ | awk '$1 != "PID" {print $(NF)}' | tr -d '()' | sed -E 's/^.*\/|^-//'` - 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}}" - url="${url_kitbase}/win32-x86_64/tclsh902z.exe" - outdir="${scriptdir}/runtime/win32-x86_64/tclsh902z.exe"; mkdir -p $outdir - output="${outdir}/tclsh902z.exe" - runtime_available=1 -else - #os="$OSTYPE" - os="other" -fi - -if [[ "$runtime_available" -eq 1 ]]; then - #test win32 - echo "Attempting to download $url" - #wget $url -O $output - curl -SL --output "$output" "$url" - if [[ $? -eq 0 ]]; then - echo "File downloaded to $output" - chmod +x $output - else - echo "Error: Failed to download to $output" - fi -else - echo "No runtime currently available for $os" -fi - -# - -# -# - -# -- --- --- --- --- --- --- --- -# -#-- 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 -#------------------------------------------------------ -fi -exit ${exitcode} -# ## ### ### ### ### ### ### ### ### ### ### ### ### ### -# -- Perl script section -# -- leave the script below as is, if all that is required is launching the Tcl payload" -# -- -# -- Note that perl script isn't called by default when simply running this script by name -# -- adjust the nextshell value at the top of the script to point to perl -# -- -# ## ### ### ### ### ### ### ### ### ### ### ### ### ### -=cut -#!/user/bin/perl -my $exit_code = 0; -use Cwd qw(abs_path); -my $scriptname = abs_path($0); -#print "perl $scriptname\n"; -my $os = "$^O"; -if ($os eq "MSWin32") { - $os = "win32"; -} elsif ($os eq "darwin") { - $os = "macosx"; -} -print "os $os\n"; -# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin perl Payload -#use ExtUtils::Installed; -#my $installed = ExtUtils::Installed->new(); -#my @modules = $installed->modules(); -#print "Modules:\n"; -#foreach my $m (@modules) { -# print "$m\n"; -#} -# -- --- --- - - - -my $i =1; -foreach my $a(@ARGV) { - print "Arg # $i: $a\n"; -} - -# -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; -__END__ - -# end hide sh/bash/perl block from Tcl -# This comment with closing brace should stay in place whether if commented or not } -#------------------------------------------------------ -# begin hide powershell-block from Tcl - only needed if Tcl didn't exit or return above -if 0 { -: end heredoc1 - end hide from powershell \ -'@ -# ## ### ### ### ### ### ### ### ### ### ### ### ### ### -# -- powershell/pwsh section -# -- Do not edit if current file is the .ps1 -# -- Edit the corresponding .cmd and it will autocopy -# -- unbalanced braces { } here *even in comments* will cause problems if there was no Tcl exit or return above -# -- custom script should generally go below the begin_powershell_payload line -# ## ### ### ### ### ### ### ### ### ### ### ### ### ### -function GetScriptName { $myInvocation.ScriptName } -$scriptname = GetScriptName -function GetDynamicParamDictionary { - [CmdletBinding()] - param( - [Parameter(ValueFromPipeline=$true, Mandatory=$true)] - [string] $CommandName - ) - - begin { - # Get a list of params that should be ignored (they're common to all advanced functions) - $CommonParameterNames = [System.Runtime.Serialization.FormatterServices]::GetUninitializedObject([type] [System.Management.Automation.Internal.CommonParameters]) | - Get-Member -MemberType Properties | - Select-Object -ExpandProperty Name - } - - process { - # Create the dictionary that this scriptblock will return: - $DynParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary - - # Convert to object array and get rid of Common params: - (Get-Command $CommandName | select -exp Parameters).GetEnumerator() | - Where-Object { $CommonParameterNames -notcontains $_.Key } | - ForEach-Object { - $DynamicParameter = New-Object System.Management.Automation.RuntimeDefinedParameter ( - $_.Key, - $_.Value.ParameterType, - $_.Value.Attributes - ) - $DynParamDictionary.Add($_.Key, $DynamicParameter) - } - - # Return the dynamic parameters - return $DynParamDictionary - } -} -# GetDynamicParamDictionary -# - This can make it easier to share a single set of param definitions between functions -# - sample usage -#function ParameterDefinitions { -# param( -# [Parameter(Mandatory)][string] $myargument -# ) -#} -#function psmain { -# [CmdletBinding()] -# param() -# dynamicparam { GetDynamicParamDictionary ParameterDefinitions } -# process { -# #called once with $PSBoundParameters dictionary -# #can be used to validate arguments, or set a simpler variable name for access -# switch ($PSBoundParameters.keys) { -# 'myargumentname' { -# Set-Variable -Name $_ -Value $PSBoundParameters."$_" -# } -# #... -# } -# foreach ($boundparam in $PSBoundParameters.GetEnumerator()) { -# #... -# } -# } -# end { -# #Main function logic -# Write-Host "myargumentname value is: $myargumentname" -# #myotherfunction @PSBoundParameters -# } -#} -#psmain @args -#"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host -#"Script Name : {0}" -f $scriptname | write-host -#"Powershell Version: {0}" -f $PSVersionTable.PSVersion.Major | write-host -#"powershell args : {0}" -f ($args -join ", ") | write-host -# -- --- --- --- -$startTag = ": <>" -$endTag = ": <>" -$fileContent = Get-Content $scriptname -Raw -$pattern = "(?s)$startTag(.*?)$endTag" -$matches = [regex]::Matches($fileContent,$pattern) -$admininfo = $matches[0].Groups[1].Value -$asadmin = 0 -if ($matches.count) { - $asadmin = $admininfo.Contains("asadmin=1") - if ($asadmin) { - 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 - 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 - } - } -} -# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload - -# - -$url = "https://www.gitea1.intx.com.au/jn/punkbin/raw/branch/master/win32-x86_64/tclsh902z.exe" - -$outbase = $PSScriptRoot -$outbase = Resolve-Path -Path $outbase -#expected script location is the bin folder of a punk project -$rtfolder = Join-Path -Path $outbase -ChildPath "runtime" -#$output = "$(join-path $PSScriptRoot "..\src\runtime\tclsh902z.exe")" -$output = "$(join-path $rtfolder "win32-x86_64\tclsh902z.exe")" - -$container = split-path -Path $output -Parent -new-item -Path $container -ItemType Directory -force #create with intermediary folders if not already present - -if (-not(Test-Path -Path $output -PathType Leaf)) { - Write-Host "Downloading from $url ..." - Invoke-WebRequest -Uri $url -OutFile $output - Write-Host "Runtime saved at $output" -} else { - Write-Host "Runtime already found at $output" -} - - - -# - -# -# - - -# -- --- --- --- --- --- --- --- -# -#tclsh $scriptname $args -#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host -# -# -- --- --- --- --- --- --- --- - - -# -# - -# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload -Exit $LASTEXITCODE -# heredoc2 for powershell to ignore block below -$1 = @' -' -: comment end hide powershell-block from Tcl \ -# This comment with closing brace should stay in place whether 'if' commented or not } -: multishell doubled-up cmd exit label - return exitcode -:exit_multishell -:exit_multishell -: \ -@REM @ECHO exitcode: !task_exitcode! -: \ -@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (echo. & @cmd /k echo elevated prompt: type exit to quit) -: \ -@EXIT /B !task_exitcode! -# cmd has exited -: comment end heredoc2 \ -'@ -<# -# id:tailblock0 -# -- powershell multiline comment -#> -<# -no script engine should try to run me -# id:tailblock1 -# - -# -# -- unreachable by tcl directly if ctrl-z character is in the section above. (but file can be read and split on \x1A) -# -- Potential for zip and/or base64 contents, but we can't stop pwsh parser from slurping in the data -# -- so for example a plain text tar archive could cause problems depending on the content. -# -- final line in file must be the powershell multiline comment terminator or other data it can handle. -# -- e.g plain # comment lines will work too -# -- (for example a powershell digital signature is a # commented block of data at the end of the file) -#> - - - diff --git a/bin/fetchruntime_old.cmd b/bin/fetchruntime_old.cmd deleted file mode 100644 index 558c0341..00000000 --- a/bin/fetchruntime_old.cmd +++ /dev/null @@ -1,612 +0,0 @@ -: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set s;proc Hide x {proc $x 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 + \ -: "[Hide @GOTO; Hide =begin; Hide @REM] #not necessary but can help avoid errs in testing" + -: << 'HEREDOC1B_HIDE_FROM_BASH_AND_SH' -: STRONG SUGGESTION: DO NOT MODIFY FIRST LINE OF THIS SCRIPT - except for first double quoted section. -: shebang line is not required on unix or windows and will reduce functionality and/or portability. -: Even comment lines can be part of the functionality of this script (both on unix and windows) - modify with care. -@GOTO :skip_perl_pod_start ^; -=begin excludeperl -: skip_perl_pod_start -: 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, sh 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 On windows, change the value of nextshell to one of the listed 2 digit values if desired, and add code within payload sections for tcl,sh,bash,powershell as appropriate. -@REM This wrapper can be edited manually (carefully!) - or sh,bash,tcl,powershell scripts can be wrapped using the Tcl-based punkshell system -@REM e.g from within a running punkshell: pmix scriptwrap.multishell -outputfolder -@REM On unix-like systems, call with sh, bash or tclsh. (powershell untested on unix - and requires wscript if security elevation is used) -@REM Due to lack of shebang (#! line) Unix-like systems will probably (hopefully) default to sh if the script is called without an interpreter - but it may depend on the shell in use when called. -@REM If you find yourself really wanting/needing to add a shebang line - do so on the basis that the script will exist on unix-like systems only. -@SETLOCAL EnableExtensions EnableDelayedExpansion -@SET "validshells= ^(10^) 'pwsh' ^(11^) 'sh' (^12^) 'bash' (^13^) 'tclsh' (^14^) 'perl'" -@SET "shells[10]=pwsh" -@SET "shells[11]=sh" -@set "shells[12]=bash" -@SET "shells[13]=tclsh" -@SET "shells[14]=perl" -: -@SET "nextshell=10" -: -@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). -: -@SET "asadmin=0" -: -@REM nextshell set to index for validshells .eg 10 for pwsh -@REM @ECHO nextshell is %nextshell% -@SET "selected=!shells[%nextshell%]!" -@REM @ECHO selected %selected% -@CALL SET "keyRemoved=%%validshells:'!selected!'=%%" -@REM @ECHO keyremoved %keyRemoved% -@REM Note that 'powershell' e.g v5 is just a fallback for when pwsh is not available -@REM ## ### ### ### ### ### ### ### ### ### ### ### ### ### -@REM -- cmd/batch file section (ignored on unix but should be left in place) -@REM -- This section intended mainly to launch the next shell (and to escalate privileges if necessary) -@REM -- Avoid customising this if you are not familiar with batch scripting. cmd/batch script can be useful, but is probably the least expressive language and most error prone. -@REM -- For example - as this file needs to use unix-style lf line-endings - the label scanner is susceptible to the 512Byte boundary issue: https://www.dostips.com/forum/viewtopic.php?t=8988#p58888 -@REM -- This label issue can be triggered/abused in files with crlf line endings too - but it is less likely to happen accidentaly. -@REm -- See also: https://stackoverflow.com/questions/4094699/how-does-the-windows-command-interpreter-cmd-exe-parse-scripts/4095133#4095133 -@REM ############################################################################################################################ -@REM -- Due to this issue -seemingly trivial edits of the batch file section can break the script! (for Windows anyway) -@REM -- Even something as simple as adding or removing an @REM -@REM -- From within punkshell - use: -@REM -- pmix scriptwrap.checkoutput -@REM -- to check your templates or final wrapped scripts for byte boundary issues -@REM -- It will report any labels that are on boundaries -@REM -- This is why the nextshell value above is a 2 digit key instead of a string - so that editing the value doesn't change the byte offsets. -@REM -- Editing your sh,bash,tcl,pwsh payloads is much less likely to cause an issue. There is the possibility of the final batch :exit_multishell label spanning a boundary - so testing using pmix scriptwrap.checkoutput is still recommended. -@REM -- Alternatively, as you should do anyway - test the final script on windows -@REM -- Aside from adding comments/whitespace to tweak the location of labels - you can try duplicating the label (e.g just add the label on a line above) but this is not guaranteed to work in all situations. -@REM -- '@REM' is a safer comment mechanism than a leading colon - which is used sparingly here. -@REM -- A colon anywhere in the script that happens to land on a 512 Byte boundary (from file start or from a callsite) could be misinterpreted as a label -@REM -- It is unknown what versions of cmd interpreters behave this way - and pmix scriptwrap.checkoutput doesn't check all such boundaries. -@REm -- For this reason, batch labels should be chosen to be relatively unlikely to collide with other strings in the file, and simple names such as :exit or :end should probably be avoided -@REM ############################################################################################################################ -@REM -- custom windows payloads should be in powershell,tclsh (or sh/bash if available) code sections -@REM ## ### ### ### ### ### ### ### ### ### ### ### ### ### -@SET "winpath=%~dp0" -@SET "fname=%~nx0" -@REM @ECHO fname %fname% -@REM @ECHO winpath %winpath% -@REM @ECHO commandlineascalled %0 -@REM @ECHO commandlineresolved %~f0 -@CALL :getNormalizedScriptTail nftail -@REM @ECHO normalizedscripttail %nftail% -@CALL :getFileTail %0 clinetail -@REM @ECHO clinetail %clinetail% -@CALL :stringToUpper %~nx0 capscripttail -@REM @ECHO capscriptname: %capscripttail% - -@IF "%nftail%"=="%capscripttail%" ( - @ECHO forcing asadmin=1 due to file name on filesystem being uppercase - @SET "asadmin=1" -) else ( - @CALL :stringToUpper %clinetail% capcmdlinetail - @REM @ECHO capcmdlinetail !capcmdlinetail! - IF "%clinetail%"=="!capcmdlinetail!" ( - @ECHO forcing asadmin=1 due to cmdline scriptname in uppercase - @set "asadmin=1" - ) -) -@SET "vbsGetPrivileges=%temp%\punk_bat_elevate_%fname%.vbs" -@SET arglist=%* -@IF "%1"=="PUNK-ELEVATED" ( - GOTO :gotPrivileges -) -@IF !asadmin!==1 ( - net file 1>NUL 2>NUL - @IF '!errorlevel!'=='0' ( GOTO :gotPrivileges ) else ( GOTO :getPrivileges ) -) -@GOTO skip_privileges -:getPrivileges -@IF '%1'=='PUNK-ELEVATED' (echo PUNK-ELEVATED & shift /1 & goto :gotPrivileges ) -@ECHO Set UAC = CreateObject^("Shell.Application"^) > "%vbsGetPrivileges%" -@ECHO args = "PUNK-ELEVATED " >> "%vbsGetPrivileges%" -@ECHO For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%" -@ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%" -@ECHO Next >> "%vbsGetPrivileges%" -@ECHO UAC.ShellExecute "%~dp0%~n0%~x0", args, "", "runas", 1 >> "%vbsGetPrivileges%" -@ECHO Launching script in new windows due to administrator elevation -@"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %* -@EXIT /B - -:gotPrivileges -@REM setlocal & pushd . -@PUSHD . -@cd /d %~dp0 -@IF "%1"=="PUNK-ELEVATED" ( - @DEL "%vbsGetPrivileges%" 1>nul 2>nul - @SET arglist=%arglist:~14% -) - -: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" ( - @SET need_ps1=1 -) ELSE ( - fc "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >nul || goto different - @REM @ECHO "files same" - @SET need_ps1=0 -) -@GOTO :pscontinue -:different -@REM @ECHO "files differ" -@SET need_ps1=1 -:pscontinue -@IF !need_ps1!==1 ( - COPY "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >NUL -) -@REM avoid using CALL to launch pwsh,tclsh etc - it will intercept some args such as /? -@IF "!shells[%nextshell%]!"=="pwsh" ( - REM pws 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 - pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; 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 ( - pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist% - SET task_exitcode=!errorlevel! - ) ELSE ( - REM CALL powershell -nop -nol -c write-host powershell-found - REM powershell -nop -nol -file "%~dp0%~n0.ps1" %* - powershell -nop -nol -c set-executionpolicy -Scope Process Unrestricted; %~dp0%~n0.ps1" %arglist% - SET task_exitcode=!errorlevel! - ) -) ELSE ( - IF "!shells[%nextshell%]!"=="bash" ( - CALL :getWslPath %winpath% wslpath - REM ECHO wslfullpath "!wslpath!%fname%" - !shells[%nextshell%]! "!wslpath!%fname%" %arglist% - SET task_exitcode=!errorlevel! - ) ELSE ( - REM probably tclsh or sh - IF NOT "x%keyRemoved%"=="x%validshells%" ( - 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 - !shells[%nextshell%]! "%~dp0%fname%" %arglist% - SET task_exitcode=!errorlevel! - ) ELSE ( - ECHO %fname% has invalid nextshell value ^(%nextshell%^) !shells[%nextshell%]! valid options are %validshells% - SET task_exitcode=66 - @REM boundary padding - GOTO :exit_multishell - ) - ) -) -@REM batch file library functions -@REM boundary padding -@GOTO :endlib - -:getWslPath -@SETLOCAL - @SET "_path=%~p1" - @SET "name=%~nx1" - @SET "drive=%~d1" - @SET "rtrn=%~2" - @SET "result=/mnt/%drive:~0,1%%_path:\=/%%name%" -@ENDLOCAL & ( - @if "%~2" neq "" ( - SET "%rtrn%=%result%" - ) ELSE ( - ECHO %result% - ) -) -@EXIT /B - -: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 -@REM This function is designed explicitly to preserve capitalisation -@REM accepts full paths with either / or \ as delimiters - or -@SETLOCAL - @SET "rtrn=%~2" - @SET "arg=%~1" - @REM @SET "result=%_arg:*/=%" - @REM @SET "result=%~1" - @SET LF=^ - - - : The above 2 empty lines are important. Don't remove - @CALL :stringContains "!arg!" "\" hasBackSlash - @IF "!hasBackslash!"=="true" ( - @for %%A in ("!LF!") do @( - @FOR /F %%B in ("!arg:\=%%~A!") do @set "result=%%B" - ) - ) ELSE ( - @CALL :stringContains "!arg!" "/" hasForwardSlash - @IF "!hasForwardSlash!"=="true" ( - @FOR %%A in ("!LF!") do @( - @FOR /F %%B in ("!arg:/=%%~A!") do @set "result=%%B" - ) - ) ELSE ( - @set "result=%arg%" - ) - ) -@ENDLOCAL & ( - @if "%~2" neq "" ( - @SET "%rtrn%=%result%" - ) ELSE ( - @ECHO %result% - ) -) -@EXIT /B -@REM boundary padding -@REM boundary padding -:getNormalizedScriptTail -@SETLOCAL - @SET "result=%~nx0" - @SET "rtrn=%~1" -@ENDLOCAL & ( - @IF "%~1" neq "" ( - @SET "%rtrn%=%result%" - ) ELSE ( - @ECHO %result% - ) -) -@EXIT /B - -:getNormalizedFileTailFromPath -@REM warn via echo, and do not set return variable if path not found -@REM note that %~nx1 does not preserve case of provided path - hence the name 'normalized' -@REM boundary padding -@REM boundary padding -@REM boundary padding -@REM boundary padding -@SETLOCAL - @CALL :stringContains %~1 "\" hasBackSlash - @CALL :stringContains %~1 "/" hasForwardSlash - @IF "%hasBackslash%-%hasForwardslash%"=="false-false" ( - @SET "P=%cd%%~1" - @CALL :getNormalizedFileTailFromPath "!P!" ftail2 - @SET "result=!ftail2!" - ) else ( - @IF EXIST "%~1" ( - @SET "result=%~nx1" - ) else ( - @ECHO error getNormalizedFileTailFromPath file not found: %~1 - @EXIT /B 1 - ) - ) - @SET "rtrn=%~2" -@ENDLOCAL & ( - @IF "%~2" neq "" ( - SET "%rtrn%=%result%" - ) ELSE ( - @ECHO getNormalizedFileTailFromPath %1 result: %result% - ) -) -@EXIT /B - -:stringContains -@REM usage: @CALL:stringContains string needle returnvarname -@SETLOCAL - @SET "rtrn=%~3" - @SET "string=%~1" - @SET "needle=%~2" - @IF "!string:%needle%=!"=="!string!" @( - @SET "result=false" - ) ELSE ( - @SET "result=true" - ) -@ENDLOCAL & ( - @IF "%~3" neq "" ( - @SET "%rtrn%=%result%" - ) ELSE ( - @ECHO stringContains %string% %needle% result: %result% - ) -) -@EXIT /B - -:stringToUpper -@SETLOCAL - @SET "rtrn=%~2" - @SET "string=%~1" - @SET "capstring=%~1" - @FOR %%A in (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) DO @( - @SET "capstring=!capstring:%%A=%%A!" - ) - @SET "result=!capstring!" -@ENDLOCAL & ( - @IF "%~2" neq "" ( - @SET "%rtrn%=%result%" - ) ELSE ( - @ECHO stringToUpper %string% result: %result% - ) -) -@EXIT /B - -:isNumeric -@SETLOCAL - @SET "notnumeric="&FOR /F "delims=0123456789" %%i in ("%1") do set "notnumeric=%%i" - @IF defined notnumeric ( - @SET "result=false" - ) else ( - @SET "result=true" - ) - @SET "rtrn=%~2" -@ENDLOCAL & ( - @IF "%~2" neq "" ( - @SET "%rtrn%=%result%" - ) ELSE ( - @ECHO %result% - ) -) -@EXIT /B - -:endlib -: \ -@REM @SET taskexit_code=!errorlevel! & goto :exit_multishell -@GOTO :exit_multishell -# } -# -*- tcl -*- -# ## ### ### ### ### ### ### ### ### ### ### ### ### ### -# -- 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 -# -- i.e it is a polyglot file. -# -- 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 ./filename.polypunk.cmd in sh or bash -# -- e.g tclsh filename.cmd -# -- -# ## ### ### ### ### ### ### ### ### ### ### ### ### ### -rename set ""; rename s set; set k {-- "$@" "a}; if {[info exists ::env($k)]} {unset ::env($k)} ;# tidyup and restore -Hide :exit_multishell;Hide {<#};Hide '@ -namespace eval ::punk::multishell { - set last_script_root [file dirname [file normalize ${argv0}/__]] - set last_script [file dirname [file normalize [info script]/__]] - if {[info exists argv0] && - $last_script eq $last_script_root - } { - set ::punk::multishell::is_main($last_script) 1 ;#run as executable/script - likely desirable to launch application and return an exitcode - } else { - set ::punk::multishell::is_main($last_script) 0 ;#sourced - likely to be being used as a library - no launch, no exit. Can use return. - } - if {"::punk::multishell::is_main" ni [info commands ::punk::multishell::is_main]} { - proc ::punk::multishell::is_main {{script_name {}}} { - if {$script_name eq ""} { - set script_name [file dirname [file normalize [info script]/--]] - } - if {![info exists ::punk::multishell::is_main($script_name)]} { - #e.g a .dll or something else unanticipated - puts stderr "Warning punk::multishell didn't recognize info script result: $script_name - will treat as if sourced and return instead of exiting" - puts stderr "Info: script_root: [file dirname [file normalize ${argv0}/__]]" - return 0 - } - return [set ::punk::multishell::is_main($script_name)] - } - } -} -# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin Tcl Payload -#puts "script : [info script]" -#puts "argcount : $::argc" -#puts "argvalues: $::argv" -#puts "argv0 : $::argv0" -# -- --- --- --- --- --- --- --- --- --- --- --- - - -# -# - -# -# - - -# -# - - -# -- --- --- --- --- --- --- --- --- --- --- --- -# -- Best practice is to always return or exit above, or just by leaving the below defaults in place. -# -- If the multishell script is modified to have Tcl below the Tcl Payload section, -# -- then Tcl bracket balancing needs to be carefully managed in the shell and powershell sections below. -# -- Only the # in front of the two relevant if statements below needs to be removed to enable Tcl below -# -- but the sh/bash 'then' and 'fi' would also need to be uncommented. -# -- This facility left in place for experiments on whether configuration payloads etc can be appended -# -- to tail of file - possibly binary with ctrl-z char - but utility is dependent on which other interpreters/shells -# -- can be made to ignore/cope with such data. -if {[::punk::multishell::is_main]} { - exit 0 -} else { - return -} -# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end Tcl Payload -# end hide from unix shells \ -HEREDOC1B_HIDE_FROM_BASH_AND_SH -# sh/bash \ -shift && set -- "${@:1:$#-1}" -#------------------------------------------------------ -# -- This if block only needed if Tcl didn't exit or return above. -if false==false # else { - then - : # -# ## ### ### ### ### ### ### ### ### ### ### ### ### ### -# -- sh/bash script section -# -- leave as is if all that is required is launching the Tcl payload" -# -- -# -- Note that sh/bash script isn't called when running a .bat/.cmd from cmd.exe on windows by default -# -- adjust the %nextshell% value above -# -- if sh/bash scripting needs to run on windows too. -# -- -# ## ### ### ### ### ### ### ### ### ### ### ### ### ### -# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload -exitcode=0 -#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 - 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 -#------------------------------------------------------ -fi -exit ${exitcode} -# ## ### ### ### ### ### ### ### ### ### ### ### ### ### -# -- Perl script section -# -- leave the script below as is, if all that is required is launching the Tcl payload" -# -- -# -- Note that perl script isn't called by default when simply running this script by name -# -- adjust the nextshell value at the top of the script to point to perl -# -- -# ## ### ### ### ### ### ### ### ### ### ### ### ### ### -=cut -#!/user/bin/perl -# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin perl Payload -my $exit_code = 0; -#use ExtUtils::Installed; -#my $installed = ExtUtils::Installed->new(); -#my @modules = $installed->modules(); -#print "Modules:\n"; -#foreach my $m (@modules) { -# print "$m\n"; -#} -# -- --- --- - - - -my $scriptname = $0; -print "perl $scriptname\n"; -my $i =1; -foreach my $a(@ARGV) { - print "Arg # $i: $a\n"; -} - -# -# - - - -# -- --- --- --- --- --- --- --- -# -$exit_code=system("tclsh", $scriptname, @ARGV); -#print "perl reporting tcl exitcode: $exit_code"; -# -# -- --- --- --- --- --- --- --- - -# -# - - -# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end perl Payload -exit $exit_code; -__END__ - -# end hide sh/bash/perl block from Tcl -# This comment with closing brace should stay in place whether if commented or not } -#------------------------------------------------------ -# begin hide powershell-block from Tcl - only needed if Tcl didn't exit or return above -if 0 { -: end heredoc1 - end hide from powershell \ -'@ -# ## ### ### ### ### ### ### ### ### ### ### ### ### ### -# -- powershell/pwsh section -# -- Do not edit if current file is the .ps1 -# -- Edit the corresponding .cmd and it will autocopy -# -- unbalanced braces { } here *even in comments* will cause problems if there was no Tcl exit or return above -# ## ### ### ### ### ### ### ### ### ### ### ### ### ### -function GetScriptName { $myInvocation.ScriptName } -$scriptname = getScriptName -# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload -#"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host -#"Script Name : {0}" -f $scriptname | write-host -#"Powershell Version: {0}" -f $PSVersionTable.PSVersion.Major | write-host -#"powershell args : {0}" -f ($args -join ", ") | write-host -# -- --- --- --- - -# -$url = "https://www.gitea1.intx.com.au/jn/punkbin/raw/branch/master/win64/tclkit86bi.exe" -$output = "$(join-path $PSScriptRoot "..\src\runtime\tclkit86bi.exe")" - -if (-not(Test-Path -Path $output -PathType Leaf)) { - try { - #Invoke-WebRequest $url -OutFile - - Import-Module BitsTransfer - Start-BitsTransfer -Source $url -Destination $output - Write-Host "Runtime saved at $output" - } - catch { - throw $_.Exception.Message - } -} -else { - Write-Host "Runtime already found at $output" -} -# - - -# -- --- --- --- --- --- --- --- -# -tclsh $scriptname $args -#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host -# -# -- --- --- --- --- --- --- --- - - -# -# - -# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload -Exit $LASTEXITCODE -# heredoc2 for powershell to ignore block below -$1 = @' -' -: comment end hide powershell-block from Tcl \ -# This comment with closing brace should stay in place whether 'if' commented or not } -: multishell doubled-up cmd exit label - return exitcode -:exit_multishell -:exit_multishell -: \ -@REM @ECHO exitcode: !task_exitcode! -: \ -@IF "%1"=="PUNK-ELEVATED" (echo. & @cmd /k echo elevated prompt: type exit to quit) -: \ -@EXIT /B !task_exitcode! -# cmd has exited -: comment end heredoc2 \ -'@ -<# -# id:tailblock0 -# -- powershell multiline comment -#> -<# -no script engine should try to run me -# id:tailblock1 -# - -# -# -- unreachable by tcl directly if ctrl-z character is in the section above. (but file can be read and split on \x1A) -# -- Potential for zip and/or base64 contents, but we can't stop pwsh parser from slurping in the data -# -- so for example a plain text tar archive could cause problems depending on the content. -# -- final line in file must be the powershell multiline comment terminator or other data it can handle. -# -- e.g plain # comment lines will work too -# -- (for example a powershell digital signature is a # commented block of data at the end of the file) -#> - - diff --git a/bin/runtime.cmd b/bin/runtime.cmd index 167b4f1e..ac5676f9 100755 --- a/bin/runtime.cmd +++ b/bin/runtime.cmd @@ -940,6 +940,7 @@ if {$nextshelltype ne "tcl" && $nextshelltype ne "none"} { #maint: keep this munging in sync with zsh/bash and perl blocks which must also do msys mangling if {[regexp {^cmd$|^cmd[.]exe$} $cmdword]} { #need to deal with msys argument munging + puts stderr "cmd call via msys detected. performing translation of /c to //C" #for now we only deal with /C or /c - todo - other cmd.exe flags? #In this context we would usually only be using cmd.exe /c to launch older 'desktop' powershell to avoid spaced-argument problems - so we aren't expecting other flags set new_nextshellpath [list $cmdword] @@ -1228,13 +1229,14 @@ if [[ "$nextshelltype" != "bash" && "$nextshelltype" != "none" ]]; then #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" + echo "cmd call via msys detected. performing translation of /c to //c and escaping backslashes in script path" >&2 #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? #maint: keep this munging in sync with the tcl block and perl block which must also do msys munging nextshellpath="${nextshellpath// \/[cC] / \/\/c }" # echo "new nextshellpath: ${nextshellpath}" + #review - #don't double quote this script=${script//\\/\\\\} fi @@ -1482,8 +1484,33 @@ if 0 { # -- unbalanced braces { } here *even in comments* will cause problems if there was no Tcl exit or return above # -- custom script should generally go below the begin_powershell_payload line # ## ### ### ### ### ### ### ### ### ### ### ### ### ### -function GetScriptName { $myInvocation.ScriptName } -$scriptname = GetScriptName +#$MyInvocation.ScriptName should probably be considered deprecated +# https://stackoverflow.com/questions/78511229/how-can-i-choose-between-myinvocation-scriptname-and-myinvocation-pscommandpat +$runningscriptname = $PSCommandPath +if (-not $MyInvocation.PSCommandPath) { + $callingscriptname = '' +} else { + $callingscriptname = $MyInvocation.PSCommandPath +} +#The problem with psmodulepath +#https://github.com/PowerShell/PowerShell/issues/18108 +# psmodulepath is shared by powershell and pwsh despite not all ps modules being compatible. +# It is futzed with by powershell/pwsh based on detecting the child process type. +# a psmodulepath that has been futzed with by pwsh will not work for a child powershell 5 process that isn't launched directly +#This is inherently unfriendly to situations where an intervening process may be something else such as cmd.exe,tcl,perl etc +# nevertheless, powershell/pwsh maintainers seem to have taken the MS-centric view of the world that such situations don't exist :/ +# +#symptoms of these shenannigans not working include things like Get-FileHash failing in powershell desktop +# +#We don't know if the original console was pwsh/powershell or cmd.exe, and we need to potentially divert to powershell 5 (desktop) +#via tcl or perl etc - or cmd.exe +if ($PSVersionTable.PSVersion.Major -le 5) { + # For Windows PowerShell, we want to remove any PowerShell 7 paths from PSModulePath + #snipped from https://github.com/PowerShell/DSC/pull/777/commits/af9b99a4d38e0cf1e54c4bbd89cbb6a8a8598c4e + #Presumably users are supposed to know not to have custom paths for powershell desktop containing a 'pwershell' subfolder?? + $env:PSModulePath = ($env:PSModulePath -split ';' | Where-Object { $_ -notlike '*\powershell\*' }) -join ';' +} + function GetDynamicParamDictionary { [CmdletBinding()] param( @@ -1558,11 +1585,11 @@ function GetDynamicParamDictionary { #} #psmain @args #"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host -"Script Name : {0}" -f $scriptname | write-host +#"Running Script Name : {0}" -f $runningscriptname | write-host "Powershell Version: {0}" -f $PSVersionTable.PSVersion.Major | write-host -"powershell args : {0}" -f ($args -join ", ") | write-host +#"powershell args : {0}" -f ($args -join ", ") | write-host # -- --- --- --- -$thisfileContent = Get-Content $scriptname -Raw +$thisfileContent = Get-Content $runningscriptname -Raw $startTag = ": <>" $endTag = ": <>" $pattern = "(?s)`n$startTag[^`n]*`n(.*?)`n$endTag" @@ -1661,7 +1688,7 @@ if ($match.Success) { } if (-not (("pwsh", "powershell", "") -contains $nextshell_type)) { #nextshell diversion exists for this platform - write-host "os: $os pwsh/powershell launching subshell of type: $nextshell_type shellpath: $nextshell_path on script $scriptname" + write-host "os: $os pwsh/powershell launching subshell of type: $nextshell_type shellpath: $nextshell_path on script $runningscriptname" # $arguments = @($($MyInvocation.MyCommand.Path)) # $arguments += $args @@ -1669,7 +1696,7 @@ if ($match.Success) { # $process = (Start-Process -FilePath $nextshell_path -ArgumentList $arguments -NoNewWindow -Wait) # Exit $process.ExitCode - & $nextshell_path $scriptname $args + & $nextshell_path $runningscriptname $args exit $LASTEXITCODE } } diff --git a/bin/sdx.bat b/bin/sdx.bat deleted file mode 100644 index f101ac61..00000000 --- a/bin/sdx.bat +++ /dev/null @@ -1,4 +0,0 @@ -::lindex tcl;#\ -@call "%~dp0..\src\runtime\tclkit86bi.exe" "%~dp0sdx.kit" %* & goto :eof -# --- --- --- --- --- --- --- --- --- --- --- --- ---begin comments only -# \ No newline at end of file diff --git a/bin/sdx.kit b/bin/sdx.kit index 4c70d7e76c42527e90dc43e5c1d05b5942a00eff..f018a39e1a38bfcd5657857f4f8593621feefa64 100644 GIT binary patch delta 3778 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 F(7gc2X)yo* diff --git a/scriptlib/stdout_per_second.tcl b/scriptlib/stdout_per_second.tcl index 51e72163..e55947c0 100644 --- a/scriptlib/stdout_per_second.tcl +++ b/scriptlib/stdout_per_second.tcl @@ -25,21 +25,26 @@ set newline_every_x_seconds 5 #--- chan configure stdout -blocking 1 -buffering none set counter 0 -set ms [expr {1000 / $persec}] -set nl_every [expr {$persec * $newline_every_x_seconds}] +set ms [expr {round(1000 / $persec)}] +set nl_every [expr {round($persec * $newline_every_x_seconds)}] proc schedule {} { upvar ::counter c upvar ::maxcount maxcount + upvar ::ms ms if {$::forever_stdout_per_second} { if {$maxcount > 0 && $c >= $maxcount} { set ::forever_stdout_per_second 0 } else { after idle [list after 0 ::emit] } - tailcall after $::ms ::schedule + if {$ms == 0} { + tailcall after idle ::schedule + } else { + tailcall after $::ms ::schedule + } } else { - after idle [list ::the_end] + after 0 [list ::the_end] } } @@ -55,24 +60,64 @@ proc the_end {} { proc emit {} { upvar ::counter c if {($c > 1) && (($c % $::nl_every) == 0)} { - puts -nonewline stdout " " + puts -nonewline stdout "$::what " flush stdout puts stderr $c flush stderr } else { puts -nonewline stdout $::what } - #flush stdout incr c } + +set original_config [chan configure stdin] chan configure stdin -blocking 0 -buffering none +catch {chan configure stdin -inputmode raw} +variable ::cmdbuffer "" chan event stdin readable [list apply {{chan} { + upvar ::cmdbuffer b set chunk [chan read $chan] if {[string length $chunk]} { if {[string match "*q*" [string tolower $chunk]]} { set ::forever_stdout_per_second 0 chan event $chan readable {} puts stderr "cancelling" + if {$::ms > 500} { + after 0 ::the_end + } + } else { + if {[catch { + package require punk::ansi + puts stderr [punk::ansi::a bold yellow][punk::ansi::ansistring VIEW -lf 1 -cr 1 -crlf 1 $chunk][punk::ansi::a] + } _err]} { + puts stderr $chunk + } + } + if {$chunk in [list "\r" "\n" "\r\n"]} { + if {[string is double -strict $b]} { + if {$b == 0} { + puts stderr "ms must be > 0" + set ::ms 1 + } + set ::ms [expr {round(1000 / $b)}] + set ::nl_every [expr {round($b * $::newline_every_x_seconds)}] + puts stderr "ms: $::ms" + } else { + if {[string match "!*" $b]} { + set cmd [string range $b 1 end] + if {[catch {eval $cmd} result]} { + puts stderr "error: $result" + } else { + puts stderr "ok" + puts stderr $result + } + } else { + puts stderr "cmd: '$b' not understood - use 'q' to quit" + } + } + set b "" + } else { + append b $chunk } } if {[chan eof $chan]} { @@ -84,6 +129,8 @@ schedule vwait ::forever_stdout_per_second vwait ::done_stdout_per_second +catch {chan configure stdin {*}$originalconfig} + diff --git a/scriptlib/utils/pwsh/consolemode.ps1 b/scriptlib/utils/pwsh/consolemode.ps1 new file mode 100644 index 00000000..3782421b --- /dev/null +++ b/scriptlib/utils/pwsh/consolemode.ps1 @@ -0,0 +1,201 @@ +# from github.com/dahlbyk/posh-git +# ------------------------------------------------------------------------------------ +#Copyright (c) 2010-2018 Keith Dahlby, Keith Hill, and contributors + +#Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +#The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# ------------------------------------------------------------------------------------ + +# Always skip setting the console mode on non-Windows platforms. +if (($PSVersionTable.PSVersion.Major -ge 6) -and !$IsWindows) { + function Set-ConsoleMode { + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] + param() + } + + return +} + +$consoleModeSource = @" +using System; +using System.Runtime.InteropServices; + +public class NativeConsoleMethods +{ + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern IntPtr GetStdHandle(int handleId); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool GetConsoleMode(IntPtr hConsoleOutput, out uint dwMode); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool SetConsoleMode(IntPtr hConsoleOutput, uint dwMode); + + public static uint GetConsoleMode(bool input = false) + { + var handle = GetStdHandle(input ? -10 : -11); + uint mode; + if (GetConsoleMode(handle, out mode)) + { + return mode; + } + return 0xffffffff; + } + + public static uint SetConsoleMode(bool input, uint mode) + { + var handle = GetStdHandle(input ? -10 : -11); + if (SetConsoleMode(handle, mode)) + { + return GetConsoleMode(input); + } + return 0xffffffff; + } +} +"@ + +[Flags()] +enum ConsoleModeInputFlags +{ + ENABLE_PROCESSED_INPUT = 0x0001 + ENABLE_LINE_INPUT = 0x0002 + ENABLE_ECHO_INPUT = 0x0004 + ENABLE_WINDOW_INPUT = 0x0008 + ENABLE_MOUSE_INPUT = 0x0010 + ENABLE_INSERT_MODE = 0x0020 + ENABLE_QUICK_EDIT_MODE = 0x0040 + ENABLE_EXTENDED_FLAGS = 0x0080 + ENABLE_AUTO_POSITION = 0x0100 + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0200 +} + +[Flags()] +enum ConsoleModeOutputFlags +{ + ENABLE_PROCESSED_OUTPUT = 0x0001 + ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002 + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 +} + +function Set-ConsoleMode +{ + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] + param( + [Parameter(ParameterSetName = "ANSI")] + [switch] + $ANSI, + + [Parameter(ParameterSetName = "Mode")] + [uint32] + $Mode, + + [switch] + $StandardInput + ) + + begin { + # Module import is speeded up by deferring the Add-Type until the first time this function is called. + # Add the NativeConsoleMethods type but only once per session. + if (!('NativeConsoleMethods' -as [System.Type])) { + Add-Type $consoleModeSource + } + } + + end { + if ($ANSI) + { + $outputMode = [NativeConsoleMethods]::GetConsoleMode($false) + $null = [NativeConsoleMethods]::SetConsoleMode($false, $outputMode -bor [ConsoleModeOutputFlags]::ENABLE_VIRTUAL_TERMINAL_PROCESSING) + + if ($StandardInput) + { + $inputMode = [NativeConsoleMethods]::GetConsoleMode($true) + $null = [NativeConsoleMethods]::SetConsoleMode($true, $inputMode -bor [ConsoleModeInputFlags]::ENABLE_VIRTUAL_TERMINAL_PROCESSING) + } + } + else + { + [NativeConsoleMethods]::SetConsoleMode($StandardInput, $Mode) + } + } +} +function Get-ConsoleMode +{ + [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseShouldProcessForStateChangingFunctions", "")] + param( + [switch] + $StandardInput + ) + + begin { + # Module import is speeded up by deferring the Add-Type until the first time this function is called. + # Add the NativeConsoleMethods type but only once per session. + if (!('NativeConsoleMethods' -as [System.Type])) { + Add-Type $consoleModeSource + } + } + + end { + $mode = [NativeConsoleMethods]::GetConsoleMode($StandardInput) + write-Output $mode + return + } +} +function psmain { + param ( + [validateSet('enableRaw', 'disableRaw')] + [string]$Action + ) + $inputflags = Get-ConsoleMode -StandardInput + $resultflags = $inputflags #default + if (($inputflags -band [ConsoleModeInputFlags]::ENABLE_LINE_INPUT) -eq [ConsoleModeInputFlags]::ENABLE_LINE_INPUT) { + #cooked mode + $initialstate = "cooked" + if ($action -eq "enableraw") { + #disable cooked flags + $disable = [uint32](-bnot [uint32][ConsoleModeInputFlags]::ENABLE_LINE_INPUT) -band ( -bnot [uint32][ConsoleModeInputFlags]::ENABLE_ECHO_INPUT) + $adjustedflags = $inputflags -band ($disable) + $resultflags = [NativeConsoleMethods]::SetConsoleMode($true,$adjustedflags) + } + } else { + #raw mode + $initialstate = "raw" + if ($action -eq "disableraw") { + #set cooked flags + $adjustedflags = $inputflags -bor [ConsoleModeInputFlags]::ENABLE_LINE_INPUT -bor [ConsoleModeInputFlags]::ENABLE_ECHO_INPUT + $resultflags = [NativeConsoleMethods]::SetConsoleMode($true,$adjustedflags) + } + } + #return in format that can act as a tcl dict + write-host "startflags: $inputflags initialstate: $initialstate action: $Action endflags: $resultflags" +} +psmain @args + +#write-host (Get-ConsoleMode) +#Set-ConsoleMode -ANSI -StandardInput +#write-host (Get-ConsoleMode) + +#test toggle +#if ((($inputflags -band [ConsoleModeInputFlags]::ENABLE_QUICK_EDIT_MODE)) -eq [ConsoleModeInputFlags]::ENABLE_QUICK_EDIT_MODE) { +# #quick edit is on +# write-host "quick edit is on" +# $adjustedflags = $inputflags -band (-bnot [uint32][ConsoleModeInputFlags]::ENABLE_QUICK_EDIT_MODE) +# $resultflags = [NativeConsoleMethods]::SetConsoleMode($true, $adjustedflags) +## +#} else { +# #quick edit is off +# write-host "quick edit is off" +# $resultflags = [NativeConsoleMethods]::SetConsoleMode($true, $inputflags -bor [ConsoleModeInputFlags]::ENABLE_QUICK_EDIT_MODE) +#} + + +#todo - parameters so it doesn't act as a toggle +#we want to be able to explicitly set raw vs cooked + +#multi + + + diff --git a/scriptlib/utils/pwsh/consolemode_enableraw.ps1 b/scriptlib/utils/pwsh/consolemode_enableraw.ps1 new file mode 100644 index 00000000..c779ac5c --- /dev/null +++ b/scriptlib/utils/pwsh/consolemode_enableraw.ps1 @@ -0,0 +1,91 @@ +#!SEMICOLONS must be placed after each command as scriptdata needs to be sent to powershell directly with the -c parameter! +; + +if ($PSVersionTable.PSVersion.Major -le 5) { + # For Windows PowerShell, we want to remove any PowerShell 7 paths from PSModulePath + #snipped from https://github.com/PowerShell/DSC/pull/777/commits/af9b99a4d38e0cf1e54c4bbd89cbb6a8a8598c4e + #Presumably users are supposed to know not to have custom paths for powershell desktop containing a 'powershell' subfolder?? + ; + $env:PSModulePath = ($env:PSModulePath -split ';' | Where-Object { $_ -notlike '*\powershell\*' }) -join ';'; +}; + +$consoleModeSource = @" +using System; +using System.Runtime.InteropServices; + +public class NativeConsoleMethods +{ + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern IntPtr GetStdHandle(int handleId); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool GetConsoleMode(IntPtr hConsoleOutput, out uint dwMode); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool SetConsoleMode(IntPtr hConsoleOutput, uint dwMode); + + public static uint GetConsoleMode(bool input = false) + { + var handle = GetStdHandle(input ? -10 : -11); + uint mode; + if (GetConsoleMode(handle, out mode)) + { + return mode; + } + return 0xffffffff; + } + + public static uint SetConsoleMode(bool input, uint mode) + { + var handle = GetStdHandle(input ? -10 : -11); + if (SetConsoleMode(handle, mode)) + { + return GetConsoleMode(input); + } + return 0xffffffff; + } +} +"@ +; +[Flags()] +enum ConsoleModeInputFlags +{ + ENABLE_LINE_INPUT = 0x0002 + ENABLE_ECHO_INPUT = 0x0004 +}; + + +function psmain { + param ( + [validateSet('enableRaw', 'disableRaw')] + [string]$Action + ); + # $inputflags = Get-ConsoleMode -StandardInput; + if (!('NativeConsoleMethods' -as [System.Type])) { + Add-Type $consoleModeSource + } + $inputFlags = [NativeConsoleMethods]::GetConsoleMode($true); + $resultflags = $inputflags; + if (($inputflags -band [ConsoleModeInputFlags]::ENABLE_LINE_INPUT) -eq [ConsoleModeInputFlags]::ENABLE_LINE_INPUT) { + #cooked mode + $initialstate = "cooked"; + if ($action -eq "enableraw") { + #disable cooked flags + $disable = [uint32](-bnot [uint32][ConsoleModeInputFlags]::ENABLE_LINE_INPUT) -band ( -bnot [uint32][ConsoleModeInputFlags]::ENABLE_ECHO_INPUT); + $adjustedflags = $inputflags -band ($disable); + $resultflags = [NativeConsoleMethods]::SetConsoleMode($true,$adjustedflags); + } + } else { + #raw mode + $initialstate = "raw"; + if ($action -eq "disableraw") { + #set cooked flags + $adjustedflags = $inputflags -bor [ConsoleModeInputFlags]::ENABLE_LINE_INPUT -bor [ConsoleModeInputFlags]::ENABLE_ECHO_INPUT; + $resultflags = [NativeConsoleMethods]::SetConsoleMode($true,$adjustedflags); + } + } + #return in format that can act as a tcl dict + #write-host "startflags: $inputflags initialstate: $initialstate action: $Action endflags: $resultflags"; +}; +psmain 'enableRaw'; + diff --git a/scriptlib/utils/pwsh/consolemode_server.ps1 b/scriptlib/utils/pwsh/consolemode_server.ps1 new file mode 100644 index 00000000..69451158 --- /dev/null +++ b/scriptlib/utils/pwsh/consolemode_server.ps1 @@ -0,0 +1,144 @@ +#!SEMICOLONS must be placed after each command as scriptdata needs to be sent to powershell directly with the -c parameter! +; + +if ($PSVersionTable.PSVersion.Major -le 5) { + # For Windows PowerShell, we want to remove any PowerShell 7 paths from PSModulePath + #snipped from https://github.com/PowerShell/DSC/pull/777/commits/af9b99a4d38e0cf1e54c4bbd89cbb6a8a8598c4e + #Presumably users are supposed to know not to have custom paths for powershell desktop containing a 'powershell' subfolder?? + ; + $env:PSModulePath = ($env:PSModulePath -split ';' | Where-Object { $_ -notlike '*\powershell\*' }) -join ';'; +}; + +$consoleModeSource = @" +using System; +using System.Runtime.InteropServices; + +public class NativeConsoleMethods +{ + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern IntPtr GetStdHandle(int handleId); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool GetConsoleMode(IntPtr hConsoleOutput, out uint dwMode); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool SetConsoleMode(IntPtr hConsoleOutput, uint dwMode); + + public static uint GetConsoleMode(bool input = false) + { + var handle = GetStdHandle(input ? -10 : -11); + uint mode; + if (GetConsoleMode(handle, out mode)) + { + return mode; + } + return 0xffffffff; + } + + public static uint SetConsoleMode(bool input, uint mode) + { + var handle = GetStdHandle(input ? -10 : -11); + if (SetConsoleMode(handle, mode)) + { + return GetConsoleMode(input); + } + return 0xffffffff; + } +} +"@ +; +[Flags()] +enum ConsoleModeInputFlags +{ + ENABLE_LINE_INPUT = 0x0002 + ENABLE_ECHO_INPUT = 0x0004 +}; + +if (!('NativeConsoleMethods' -as [System.Type])) { + Add-Type $consoleModeSource +} + +function psmain { + param ( + [validateSet('enableRaw', 'disableRaw')] + [string]$Action + ); + # $inputflags = Get-ConsoleMode -StandardInput; + if (!('NativeConsoleMethods' -as [System.Type])) { + Add-Type $consoleModeSource + } + $inputFlags = [NativeConsoleMethods]::GetConsoleMode($true); + $resultflags = $inputflags; + if (($inputflags -band [ConsoleModeInputFlags]::ENABLE_LINE_INPUT) -eq [ConsoleModeInputFlags]::ENABLE_LINE_INPUT) { + #cooked mode + $initialstate = "cooked"; + if ($action -eq "enableraw") { + #disable cooked flags + $disable = [uint32](-bnot [uint32][ConsoleModeInputFlags]::ENABLE_LINE_INPUT) -band ( -bnot [uint32][ConsoleModeInputFlags]::ENABLE_ECHO_INPUT); + $adjustedflags = $inputflags -band ($disable); + $resultflags = [NativeConsoleMethods]::SetConsoleMode($true,$adjustedflags); + } + } else { + #raw mode + $initialstate = "raw"; + if ($action -eq "disableraw") { + #set cooked flags + $adjustedflags = $inputflags -bor [ConsoleModeInputFlags]::ENABLE_LINE_INPUT -bor [ConsoleModeInputFlags]::ENABLE_ECHO_INPUT; + $resultflags = [NativeConsoleMethods]::SetConsoleMode($true,$adjustedflags); + } + } + #return in format that can act as a tcl dict + #write-host "startflags: $inputflags initialstate: $initialstate action: $Action endflags: $resultflags"; +}; +# psmain 'enableRaw'; + +$consoleid = $args[0]; +if ([string]::IsNullOrEmpty($consoleid)) { + $consoleid= "" +}; +$pipeName = "punkshell_ps_consolemode_$consoleid"; +"pipename: $pipeName" +$pipeServer = New-Object System.IO.Pipes.NamedPipeServerStream($pipeName); +try { + while ($true) { + #"Waiting for connection on '$pipeName'"; + + $pipeServer.WaitForConnection(); + #"Connection established"; + $pipeReader = New-Object System.IO.StreamReader($pipeServer); + #$pipeWriter = New-Object System.IO.StreamWriter($pipeServer); + #$pipeWriter.AutoFlush = $true; + $request = $pipeReader.ReadLine(); + # "Received request: $request"; + if ($request -eq "exit") { + "consolemode_server.ps1 Exiting"; + exit; + } elseif ($request -eq "") { + #"Empty input"; + $pipeServer.Disconnect(); + #"Disconnected"; + continue; + } elseif ($request -eq $none) { + "Remote disconnected before sending"; + $pipeServer.Disconnect(); + "Disconnected"; + continue; + } elseif ($request -eq "enableraw") { + #$result = psmain 'enableRaw'; + $null = psmain 'enableRaw' + # "Sending result: '$result'"; + #$pipeWriter.Write($result); + $pipeServer.Disconnect(); + continue; + } else { + "consolemode_server.ps1 ignoring request: $request"; + $pipeServer.Disconnect(); + continue; + } + } +} +finally { + $pipeServer.Dispose(); +}; + + diff --git a/scriptlib/utils/pwsh/consolemode_server_async.2ps1 b/scriptlib/utils/pwsh/consolemode_server_async.2ps1 new file mode 100644 index 00000000..6369058f --- /dev/null +++ b/scriptlib/utils/pwsh/consolemode_server_async.2ps1 @@ -0,0 +1,244 @@ +#!SEMICOLONS must be placed after each command as scriptdata needs to be sent to powershell directly with the -c parameter! +; + +if ($PSVersionTable.PSVersion.Major -le 5) { + # For Windows PowerShell, we want to remove any PowerShell 7 paths from PSModulePath + #snipped from https://github.com/PowerShell/DSC/pull/777/commits/af9b99a4d38e0cf1e54c4bbd89cbb6a8a8598c4e + #Presumably users are supposed to know not to have custom paths for powershell desktop containing a 'powershell' subfolder?? + ; + $env:PSModulePath = ($env:PSModulePath -split ';' | Where-Object { $_ -notlike '*\powershell\*' }) -join ';'; +}; + +$helper = @' +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Management.Automation.Runspaces; + +public class RunspacedDelegateFactory +{ + public static Delegate NewRunspacedDelegate(Delegate _delegate, Runspace runspace) + { + Action setRunspace = () => Runspace.DefaultRunspace = runspace; + return ConcatActionToDelegate(setRunspace, _delegate); + } + + private static Expression ExpressionInvoke(Delegate _delegate, params Expression[] arguments) + { + var invokeMethod = _delegate.GetType().GetMethod("Invoke"); + return Expression.Call(Expression.Constant(_delegate), invokeMethod, arguments); + } + + public static Delegate ConcatActionToDelegate(Action a, Delegate d) + { + var parameters = + d.GetType().GetMethod("Invoke").GetParameters() + .Select(p => Expression.Parameter(p.ParameterType, p.Name)) + .ToArray(); + + Expression body = Expression.Block(ExpressionInvoke(a), ExpressionInvoke(d, parameters)); + var lambda = Expression.Lambda(d.GetType(), body, parameters); + var compiled = lambda.Compile(); + return compiled; + } +} +'@ +add-type -TypeDefinition $helper + +#region console + +$consoleModeSource = @" +using System; +using System.Runtime.InteropServices; + +public class NativeConsoleMethods +{ + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern IntPtr GetStdHandle(int handleId); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool GetConsoleMode(IntPtr hConsoleOutput, out uint dwMode); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool SetConsoleMode(IntPtr hConsoleOutput, uint dwMode); + + public static uint GetConsoleMode(bool input = false) + { + var handle = GetStdHandle(input ? -10 : -11); + uint mode; + if (GetConsoleMode(handle, out mode)) + { + return mode; + } + return 0xffffffff; + } + + public static uint SetConsoleMode(bool input, uint mode) + { + var handle = GetStdHandle(input ? -10 : -11); + if (SetConsoleMode(handle, mode)) + { + return GetConsoleMode(input); + } + return 0xffffffff; + } +} +"@ +; +[Flags()] +enum ConsoleModeInputFlags +{ + ENABLE_LINE_INPUT = 0x0002 + ENABLE_ECHO_INPUT = 0x0004 +}; + +if (!('NativeConsoleMethods' -as [System.Type])) { + Add-Type $consoleModeSource +} + +function psmain { + param ( + [validateSet('enableRaw', 'disableRaw')] + [string]$Action + ); + # $inputflags = Get-ConsoleMode -StandardInput; + if (!('NativeConsoleMethods' -as [System.Type])) { + Add-Type $consoleModeSource + } + $inputFlags = [NativeConsoleMethods]::GetConsoleMode($true); + $resultflags = $inputflags; + if (($inputflags -band [ConsoleModeInputFlags]::ENABLE_LINE_INPUT) -eq [ConsoleModeInputFlags]::ENABLE_LINE_INPUT) { + #cooked mode + $initialstate = "cooked"; + if ($action -eq "enableraw") { + #disable cooked flags + $disable = [uint32](-bnot [uint32][ConsoleModeInputFlags]::ENABLE_LINE_INPUT) -band ( -bnot [uint32][ConsoleModeInputFlags]::ENABLE_ECHO_INPUT); + $adjustedflags = $inputflags -band ($disable); + $resultflags = [NativeConsoleMethods]::SetConsoleMode($true,$adjustedflags); + } + } else { + #raw mode + $initialstate = "raw"; + if ($action -eq "disableraw") { + #set cooked flags + $adjustedflags = $inputflags -bor [ConsoleModeInputFlags]::ENABLE_LINE_INPUT -bor [ConsoleModeInputFlags]::ENABLE_ECHO_INPUT; + $resultflags = [NativeConsoleMethods]::SetConsoleMode($true,$adjustedflags); + } + } + #return in format that can act as a tcl dict + #write-host "startflags: $inputflags initialstate: $initialstate action: $Action endflags: $resultflags"; +}; +# psmain 'enableRaw'; +#endregion console + + +$consoleid = $args[0]; +if ([string]::IsNullOrEmpty($consoleid)) { + $consoleid= "" +}; +$pipeName = "punkshell_ps_consolemode_$consoleid"; +"pipename: $pipeName"; + +$pipeServer = New-Object System.IO.Pipes.NamedPipeServerStream( + $pipeName, + [System.IO.Pipes.PipeDirection]::In, + 1, + [System.IO.Pipes.PipeTransmissionMode]::Byte, + [System.IO.Pipes.PipeOptions]::Asynchronous +); +#$pipeServer = New-Object System.IO.Pipes.NamedPipeServerStream($pipeName); +; + +# Define the callback function for when a client connects +$callback = [System.AsyncCallback]{ + param($asyncResult); + + $se = $asyncResult.AsyncState.Sync; + $ps = $asyncResult.AsyncState.Pipeserver; + $pn = $asyncResult.AsyncState.Pipename; + Write-Host "Client connected - $pn"; + + # End the asynchronous wait operation + ; + $ps.EndWaitForConnection($asyncResult); + #?? + ; + # You can now perform read/write operations with the client + # For example, create a StreamReader and StreamWriter + ; + $streamReader = New-Object System.IO.StreamReader($ps); + #$streamWriter = New-Object System.IO.StreamWriter($pipeServer); + #$streamWriter.AutoFlush = $true; + try { + $message = $streamReader.ReadLine(); + Write-Host "Received: $message"; + ; + #$asyncResult.Message = $message; + ; + } catch { + Write-Error "Error during communication: $($_.Exception.Message)"; + } finally { + # sever connection with client but keep the named pipe + if ($streamReader -ne $null) { + Write-Host "streamreader closing"; + $streamReader.Close(); + Write-Host "streamreader closed"; + } + Write-Host "Client disconnecting. $pn"; + $ps.Disconnect(); + Write-Host "Client disconnected. $pn"; + #$ps.Disconnect(); + #[System.Console]::Out.Flush(); + ; + }; + write-host "HERE"; + + $se.set(); + + # Optionally, you can call BeginWaitForConnection again to listen for another client + # if your server is designed for multiple connections over time. + # $pipeServer.BeginWaitForConnection($callback, $null) + $ps.BeginWaitForConnection($callback, $null) + ; +}; + +$syncEvent = New-Object System.Threading.ManualResetEvent($false); + +$runspacedDelegate = [RunspacedDelegateFactory]::NewRunspacedDelegate($callback, [Runspace]::DefaultRunspace); + +$loop = 0; +while ($loop -lt 15) { + Write-Host "Waiting for client connection on pipe '$pipeName'..."; + # Begin the asynchronous wait for a client connection + ; + $state = [PSCustomObject]@{ + Loop = $loop + Sync = $SyncEvent + Pipeserver = $pipeServer + Pipename = $pipename + Message = "" + }; + $x = $pipeServer.BeginWaitForConnection($runspacedDelegate, $state ); + $syncEvent.WaitOne(10000); + # $x | Get-Member | write-host + ; + write-host "msg: $(${x}.AsyncState.Message)" + if ($x.IsCompleted) { + write-host "completed"; + }; + $SyncEvent.reset(); + + $loop += 1; + #$cli = New-Object System.IO.Pipes.NamedPipeClientStream($pipeName); + #$cli.w +} + +# Keep the script running to allow the asynchronous operation to complete +# In a real-world scenario, you might have a loop or other logic here. +#Read-Host "Press Enter to exit the server." + +# Clean up +$pipeServer.Dispose(); + + diff --git a/scriptlib/utils/pwsh/consolemode_server_async.ps1 b/scriptlib/utils/pwsh/consolemode_server_async.ps1 new file mode 100644 index 00000000..1d188f6b --- /dev/null +++ b/scriptlib/utils/pwsh/consolemode_server_async.ps1 @@ -0,0 +1,262 @@ +#!SEMICOLONS must be placed after each command as scriptdata needs to be sent to powershell directly with the -c parameter! +; + +if ($PSVersionTable.PSVersion.Major -le 5) { + # For Windows PowerShell, we want to remove any PowerShell 7 paths from PSModulePath + #snipped from https://github.com/PowerShell/DSC/pull/777/commits/af9b99a4d38e0cf1e54c4bbd89cbb6a8a8598c4e + #Presumably users are supposed to know not to have custom paths for powershell desktop containing a 'powershell' subfolder?? + ; + $env:PSModulePath = ($env:PSModulePath -split ';' | Where-Object { $_ -notlike '*\powershell\*' }) -join ';'; +}; + +$consoleModeSource = @" +using System; +using System.Runtime.InteropServices; + +public class NativeConsoleMethods +{ + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern IntPtr GetStdHandle(int handleId); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool GetConsoleMode(IntPtr hConsoleOutput, out uint dwMode); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool SetConsoleMode(IntPtr hConsoleOutput, uint dwMode); + + public static uint GetConsoleMode(bool input = false) + { + var handle = GetStdHandle(input ? -10 : -11); + uint mode; + if (GetConsoleMode(handle, out mode)) + { + return mode; + } + return 0xffffffff; + } + + public static uint SetConsoleMode(bool input, uint mode) + { + var handle = GetStdHandle(input ? -10 : -11); + if (SetConsoleMode(handle, mode)) + { + return GetConsoleMode(input); + } + return 0xffffffff; + } +} +"@ +; +[Flags()] +enum ConsoleModeInputFlags +{ + ENABLE_PROCESSED_INPUT = 0x0001 + ENABLE_LINE_INPUT = 0x0002 + ENABLE_ECHO_INPUT = 0x0004 + ENABLE_WINDOW_INPUT = 0x0008 + ENABLE_MOUSE_INPUT = 0x0010 + ENABLE_INSERT_MODE = 0x0020 + ENABLE_QUICK_EDIT_MODE = 0x0040 + ENABLE_EXTENDED_FLAGS = 0x0080 + ENABLE_AUTO_POSITION = 0x0100 + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0200 +}; + +[Flags()] +enum ConsoleModeOutputFlags +{ + ENABLE_PROCESSED_OUTPUT = 0x0001 + ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002 + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 +}; + +if (!('NativeConsoleMethods' -as [System.Type])) { + Add-Type $consoleModeSource +} + +function rawmode { + param ( + [validateSet('enable', 'disable')] + [string]$Action + ); + # $inputflags = Get-ConsoleMode -StandardInput; + if (!('NativeConsoleMethods' -as [System.Type])) { + Add-Type $consoleModeSource + } + $inputFlags = [NativeConsoleMethods]::GetConsoleMode($true); + $resultflags = $inputflags; + if (($inputflags -band [ConsoleModeInputFlags]::ENABLE_LINE_INPUT) -eq [ConsoleModeInputFlags]::ENABLE_LINE_INPUT) { + #cooked mode + $initialstate = "cooked"; + if ($action -eq "enable") { + #disable cooked flags + $disable = [uint32](-bnot [uint32][ConsoleModeInputFlags]::ENABLE_LINE_INPUT) -band ( -bnot [uint32][ConsoleModeInputFlags]::ENABLE_ECHO_INPUT); + $adjustedflags = $inputflags -band ($disable); + $resultflags = [NativeConsoleMethods]::SetConsoleMode($true,$adjustedflags); + } + } else { + #raw mode + $initialstate = "raw"; + if ($action -eq "disable") { + #set cooked flags + $adjustedflags = $inputflags -bor [ConsoleModeInputFlags]::ENABLE_LINE_INPUT -bor [ConsoleModeInputFlags]::ENABLE_ECHO_INPUT; + $resultflags = [NativeConsoleMethods]::SetConsoleMode($true,$adjustedflags); + } + } + #return in format that can act as a tcl dict + #write-host "startflags: $inputflags initialstate: $initialstate action: $Action endflags: $resultflags"; +}; +# rawmode 'enable'; + +$consoleid = $args[0]; +if ([string]::IsNullOrEmpty($consoleid)) { + $consoleid= "" +}; +$pipeName = "punkshell_ps_consolemode_$consoleid"; +"pipename: $pipeName" +#$pipeServer = New-Object System.IO.Pipes.NamedPipeServerStream($pipeName); + +$sharedData = [hashtable]::Synchronized(@{}) #TSV +$scriptblock = { + param($tsv); + Add-Type -AssemblyName System.IO.Pipes; + #$pipeServer = New-Object System.IO.Pipes.NamedPipeServerStream( + # $pipeName, + # [System.IO.Pipes.PipeDirection]::In, + # 1, + # [System.IO.Pipes.PipeTransmissionMode]::Byte, + # [System.IO.Pipes.PipeOptions]::Asynchronous + #); + ; + $serverloop = 0; + while ($true) { + $pipeServer = New-Object System.IO.Pipes.NamedPipeServerStream($pipeName); + $serverloop += 1; + $pipeServer.WaitForConnection(); + #write-host "Connection established"; + $reader = New-Object System.IO.StreamReader($pipeServer); + if ($reader -ne $null) { + $message = $reader.ReadLine(); + $reader.Close(); + $reader.Dispose(); + if ($message -ne $null) { + if ($message -eq "exit") { + #write-host "consolemode_server.ps1 exiting"; + $tsv.State = "done"; + break; + } elseif ($message -eq "enableraw") { + #write-host "RECEIVED: $message"; + $tsv.Message = $message; + #$tsv.Message = "$pipeName serverloop: $serverloop"; + + $tsv.Ping = Get-Date; + $msync.Set(); + }; + } else { + # write-host "consolemode_server.ps1 null-msg"; + $tsv.State = "done"; + break; + }; + }; + $pipeServer.Disconnect(); + $pipeServer.Dispose(); + }; + exit; +}; + +$keepalive_timeout = 20; #number of seconds without ping or other message, after which we terminate the process. +try { + $syncEvent = New-Object System.Threading.ManualResetEvent($false); + + $runspace = [runspacefactory]::CreateRunspace(); + [void]$runspace.Open(); + $runspace.SessionStateProxy.SetVariable("pipeName", $pipeName); + $runspace.SessionStateProxy.SetVariable("pipeServer", $null); + $runspace.SessionStateProxy.SetVariable("msync", $syncEvent); + + $powershell = [System.Management.Automation.PowerShell]::Create(); + $powershell.Runspace = $runspace; + [void]$powershell.Addscript($scriptblock).AddArgument($sharedData); + + $sharedData.State = "running"; + $sharedData.Ping = Get-Date; + $asyncResult = $powershell.BeginInvoke(); + + write-Host "Started named pipe server $pipeName in runspace" + $loop = 0; + while ($true) { + $loop += 1; + #write-host "loop $loop"; + [void]$syncEvent.WaitOne(($keepalive_timeout * 1000 / 2)); + $msg = $sharedData.Message; + #Write-Host "$pipeName Last message: $msg"; + $sharedData.Message = ""; + if ($msg -eq "enableraw") { + $null = rawmode 'enable' + } elseif ($msg -eq "disableraw") { + $null = rawmode 'disable' + } + #write-host "STATE: $(${sharedData}.State)" + if ($(${sharedData}.State) -eq "done") { + break; + }; + $tnow = Get-Date; + $elapsed = New-TimeSpan -Start $sharedData.Ping -End $tnow; + if ($elapsed.TotalSeconds -lt $keepalive_timeout) { + # write-host "ping ok"; + } else { + write-host "ping stale for pipe $pipeName - exiting"; + break; + } + [void]$syncEvent.Reset(); + # start-sleep -Milliseconds 300 + }; +} finally { + # Failing to properly shut down the run process can leave an orphan powershell process + # We need to use a client for the named pipe to send an exit message. + # Write-Host "terminating process for $pipeName"; + try { + # Write-Host "creating cli for $pipeName"; + $cli = New-Object System.IO.Pipes.NamedPipeClientStream($pipeName); + $cli.connect(1000); + #Write-Host "sending exit for $pipeName"; + $writer = new-object System.IO.StreamWriter($cli); + $writer.writeline("exit"); + $writer.flush(); + #Write-Host "disposing of cli for $pipeName"; + $cli.Dispose(); + } catch { + write-host "error during cli tidyup"; + Write-Error "error: $($PSItem.ToString())"; + Write-Host "Detailed Exception Message: $($PSItem.Exception.Message)"; + }; + + + try { + if ($null -ne $runspace) { + #Write-Host "closing runspace for $pipeName"; + $runspace.Close(); + #Write-Host "disposing of runspace for $pipeName"; + $runspace.Dispose(); + }; + } catch { + write-host "error during runspace tidyup"; + Write-Error "error: $($PSItem.ToString())"; + Write-Host "Detailed Exception Message: $($PSItem.Exception.Message)"; + } finally { + }; + + try { + if ($null -ne $powershell) { + #Write-Host "tidying up powershell for $pipeName"; + $powershell.dispose(); + }; + } finally { + }; + +}; +write-host "consolemode_server_async.ps1 shutdown for pipe $pipeName"; +exit 0; + + + diff --git a/scriptlib/utils/pwsh/consolemode_server_async1.ps1 b/scriptlib/utils/pwsh/consolemode_server_async1.ps1 new file mode 100644 index 00000000..2b9cbe01 --- /dev/null +++ b/scriptlib/utils/pwsh/consolemode_server_async1.ps1 @@ -0,0 +1,266 @@ +#!SEMICOLONS must be placed after each command as scriptdata needs to be sent to powershell directly with the -c parameter! +; + +if ($PSVersionTable.PSVersion.Major -le 5) { + # For Windows PowerShell, we want to remove any PowerShell 7 paths from PSModulePath + #snipped from https://github.com/PowerShell/DSC/pull/777/commits/af9b99a4d38e0cf1e54c4bbd89cbb6a8a8598c4e + #Presumably users are supposed to know not to have custom paths for powershell desktop containing a 'powershell' subfolder?? + ; + $env:PSModulePath = ($env:PSModulePath -split ';' | Where-Object { $_ -notlike '*\powershell\*' }) -join ';'; +}; + +$consoleModeSource = @" +using System; +using System.Runtime.InteropServices; + +public class NativeConsoleMethods +{ + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern IntPtr GetStdHandle(int handleId); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool GetConsoleMode(IntPtr hConsoleOutput, out uint dwMode); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern bool SetConsoleMode(IntPtr hConsoleOutput, uint dwMode); + + public static uint GetConsoleMode(bool input = false) + { + var handle = GetStdHandle(input ? -10 : -11); + uint mode; + if (GetConsoleMode(handle, out mode)) + { + return mode; + } + return 0xffffffff; + } + + public static uint SetConsoleMode(bool input, uint mode) + { + var handle = GetStdHandle(input ? -10 : -11); + if (SetConsoleMode(handle, mode)) + { + return GetConsoleMode(input); + } + return 0xffffffff; + } +} +"@ +; +[Flags()] +enum ConsoleModeInputFlags +{ + ENABLE_LINE_INPUT = 0x0002 + ENABLE_ECHO_INPUT = 0x0004 +}; + +if (!('NativeConsoleMethods' -as [System.Type])) { + Add-Type $consoleModeSource +} + +function psmain { + param ( + [validateSet('enableRaw', 'disableRaw')] + [string]$Action + ); + # $inputflags = Get-ConsoleMode -StandardInput; + if (!('NativeConsoleMethods' -as [System.Type])) { + Add-Type $consoleModeSource + } + $inputFlags = [NativeConsoleMethods]::GetConsoleMode($true); + $resultflags = $inputflags; + if (($inputflags -band [ConsoleModeInputFlags]::ENABLE_LINE_INPUT) -eq [ConsoleModeInputFlags]::ENABLE_LINE_INPUT) { + #cooked mode + $initialstate = "cooked"; + if ($action -eq "enableraw") { + #disable cooked flags + $disable = [uint32](-bnot [uint32][ConsoleModeInputFlags]::ENABLE_LINE_INPUT) -band ( -bnot [uint32][ConsoleModeInputFlags]::ENABLE_ECHO_INPUT); + $adjustedflags = $inputflags -band ($disable); + $resultflags = [NativeConsoleMethods]::SetConsoleMode($true,$adjustedflags); + } + } else { + #raw mode + $initialstate = "raw"; + if ($action -eq "disableraw") { + #set cooked flags + $adjustedflags = $inputflags -bor [ConsoleModeInputFlags]::ENABLE_LINE_INPUT -bor [ConsoleModeInputFlags]::ENABLE_ECHO_INPUT; + $resultflags = [NativeConsoleMethods]::SetConsoleMode($true,$adjustedflags); + } + } + #return in format that can act as a tcl dict + #write-host "startflags: $inputflags initialstate: $initialstate action: $Action endflags: $resultflags"; +}; +# psmain 'enableRaw' +; +$consoleid = $args[0]; +if ([string]::IsNullOrEmpty($consoleid)) { + $consoleid= "" +}; +$pipeName = "punkshell_ps_consolemode_$consoleid"; +"pipename: $pipeName" +# Create the NamedPipeServerStream +$pipeServer = New-Object System.IO.Pipes.NamedPipeServerStream( + $pipeName, + [System.IO.Pipes.PipeDirection]::In, + 1, + [System.IO.Pipes.PipeTransmissionMode]::Message, + [System.IO.Pipes.PipeOptions]::Asynchronous +); +; +#$pipeServer = New-Object System.IO.Pipes.NamedPipeServerStream($pipeName); +; + + +# Create a synchronization object (ManualResetEvent) to signal completion +; +$syncEvent = New-Object System.Threading.ManualResetEvent($false) + +# Define the callback function for BeginWaitForConnection +$connectionCallback = [System.AsyncCallback]{ + param($ar,$s); + # Get the NamedPipeServerStream from the AsyncResult object + ; + $server = $ar.AsyncState; + # End the asynchronous wait operation + ; + $server.EndWaitForConnection($ar); + Write-Host "Client connected!"; + # Create a new runspace to handle client communication + ; + $runspace = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace(); + $runspace.Open(); + # Create a PowerShell pipeline within the runspace + ; + $powershell = [System.Management.Automation.PowerShell]::Create(); + $powershell.Runspace = $runspace; + + $scriptBlock = { + param($pipeStream); + $reader = New-Object System.IO.StreamReader($pipeStream); + #$writer = New-Object System.IO.StreamWriter($pipeStream); + #$writer.AutoFlush = $true; + + $message = $reader.ReadLine(); + Write-Host "Received from client: $message"; + + #$response = "Server received: $message" + #$writer.WriteLine($response) + #Write-Host "Sent to client: $response" + + # Disconnect the pipe to allow new connections if desired + ; + $pipeStream.Disconnect(); + }; + # Add the script block to the PowerShell pipeline and pass the pipe stream + ; + $powershell.AddScript($scriptBlock).AddArgument($server); + # Invoke the pipeline asynchronously + ; + $asyncResult = $powershell.BeginInvoke(); + # You can do other work here while the client communication happens in the runspace + ; + ; + # Wait for the pipeline to complete and close the runspace + $powershell.EndInvoke($asyncResult); + $powershell.Dispose(); + $runspace.Close(); + $runspace.Dispose(); + + Write-Host "Client communication handled. Waiting for next connection..."; + + $s.Set(); + + # Begin waiting for the next connection; + #$server.BeginWaitForConnection($callback, $server); + ; +} + + + + + +$global:keep_listening = $true; + + +while ($global:keep_listening) { + # Begin waiting for a client connection asynchronously + ; + + $runspace = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace(); + $runspace.Open(); + # Create a PowerShell pipeline within the runspace + ; + $powershell = [System.Management.Automation.PowerShell]::Create(); + $powershell.Runspace = $runspace; + + $scriptblock = { + param($p,$s); + Write-Host "Waiting for client connection on pipe: $p"; + $p.BeginWaitForConnection($connectionCallback, $p,$s); + $s.WaitOne(10000); + } + $powershell.AddScript($scriptBlock) + [void]$powershell.AddParameter('p',$pipeName); + [void]$powershell.AddParameter('s',$syncEvent); + $asyncResult = $powershell.BeginInvoke(); + # You can do other work here while the client communication happens in the runspace + ; + write-host "interim" + ; + # Wait for the pipeline to complete and close the runspace + $powershell.EndInvoke($asyncResult); + $powershell.Dispose(); + $runspace.Close(); + $runspace.Dispose(); + write-host "looping" +}; + +#$pipeServer.BeginWaitForConnection($connectionCallback, $pipeServer); + +Write-Host "Server shutting down."; +$pipeServer.Dispose(); + + + +#try { +# while ($true) { +# #"Waiting for connection on '$pipeName'"; +# $pipeServer.WaitForConnection(); +# #"Connection established"; +# $pipeReader = New-Object System.IO.StreamReader($pipeServer); +# #$pipeWriter = New-Object System.IO.StreamWriter($pipeServer); +# #$pipeWriter.AutoFlush = $true; +# $request = $pipeReader.ReadLine(); +# # "Received request: $request"; +# if ($request -eq "exit") { +# "consolemode_server.ps1 Exiting"; +# exit; +# } elseif ($request -eq "") { +# #"Empty input"; +# $pipeServer.Disconnect(); +# #"Disconnected"; +# continue; +# } elseif ($request -eq $none) { +# "Remote disconnected before sending"; +# $pipeServer.Disconnect(); +# "Disconnected"; +# continue; +# } elseif ($request -eq "enableraw") { +# #$result = psmain 'enableRaw'; +# $null = psmain 'enableRaw' +# # "Sending result: '$result'"; +# #$pipeWriter.Write($result); +# $pipeServer.Disconnect(); +# continue; +# } else { +# "consolemode_server.ps1 ignoring request: $request"; +# $pipeServer.Disconnect(); +# continue; +# } +# } +#} +#finally { +# $pipeServer.Dispose(); +#}; + + diff --git a/scriptlib/utils/pwsh/echotest.ps1 b/scriptlib/utils/pwsh/echotest.ps1 new file mode 100644 index 00000000..41c74841 --- /dev/null +++ b/scriptlib/utils/pwsh/echotest.ps1 @@ -0,0 +1 @@ +write-host "test" diff --git a/src/bootsupport/modules/punk/args-0.2.tm b/src/bootsupport/modules/punk/args-0.2.tm index a6224c0d..7b6ee228 100644 --- a/src/bootsupport/modules/punk/args-0.2.tm +++ b/src/bootsupport/modules/punk/args-0.2.tm @@ -3036,8 +3036,11 @@ tcl::namespace::eval punk::args { #This mechanism gets less-than-useful results for oo methods #e.g {$obj} proc Get_caller {} { + set depth [info level] + set maxd [expr {min($depth,4)}] + set call_level [expr {-1 * $maxd}] #set call_level -3 ;#for get_dict call - set call_level -4 + #set call_level -4 set cmdinfo [tcl::dict::get [tcl::info::frame $call_level] cmd] #puts "-->$cmdinfo" #puts "-->[tcl::info::frame -3]" diff --git a/src/bootsupport/modules/punk/args/tclcore-0.1.0.tm b/src/bootsupport/modules/punk/args/tclcore-0.1.0.tm index d016c70a..6a4cc626 100644 --- a/src/bootsupport/modules/punk/args/tclcore-0.1.0.tm +++ b/src/bootsupport/modules/punk/args/tclcore-0.1.0.tm @@ -3498,7 +3498,7 @@ tcl::namespace::eval punk::args::tclcore { example, in ${$B}-dictionary${$N} mode, bigBoy sorts between bigbang and bigboy, and x10y sorts between x9y and x11y. Overrides the ${$B}-nocase${$N} option." -integer -type none -help\ - "Convert list elements to integers and use integer comparsion." + "Convert list elements to integers and use integer comparison." -real -type none -help\ "Convert list elements to floating-point values and use floating comparison." -command -type string -help\ diff --git a/src/bootsupport/modules/punk/console-0.1.1.tm b/src/bootsupport/modules/punk/console-0.1.1.tm index ea8d3f77..4d4518d3 100644 --- a/src/bootsupport/modules/punk/console-0.1.1.tm +++ b/src/bootsupport/modules/punk/console-0.1.1.tm @@ -129,57 +129,362 @@ namespace eval punk::console { #e.g external utils system API's. namespace export * } - + if {"windows" eq $::tcl_platform(platform)} { #accept args for all dummy/load functions so we don't have to match/update argument signatures here + set has_twapi [expr {! [catch {package require twapi}]}] + + if {$has_twapi} { + #this is really enableAnsi *processing* + proc enableAnsi {} { + #output handle modes + #Enable virtual terminal processing (sometimes off in older windows terminals) + #ENABLE_PROCESSED_OUTPUT = 0x0001 + #ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002 + #ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 + #DISABLE_NEWLINE_AUTO_RETURN = 0x0008 + if {[catch {twapi::get_console_handle stdout} h_out]} { + puts stderr "enableAnsi failed: twapi cannot get console handle for stdout" + return + } - proc enableAnsi {args} { - #loopavoidancetoken (don't remove) - internal::define_windows_procs - internal::abort_if_loop - tailcall enableAnsi {*}$args - } - #review what raw mode means with regard to a specific channel vs terminal as a whole - proc enableRaw {args} { - #loopavoidancetoken (don't remove) - internal::define_windows_procs - internal::abort_if_loop - tailcall enableRaw {*}$args - } - proc disableRaw {args} { - #loopavoidancetoken (don't remove) - internal::define_windows_procs - internal::abort_if_loop - tailcall disableRaw {*}$args - } - proc enableVirtualTerminal {args} { - #loopavoidancetoken (don't remove) - internal::define_windows_procs - internal::abort_if_loop - tailcall enableVirtualTerminal {*}$args - } - proc disableVirtualTerminal {args} { - #loopavoidancetoken (don't remove) - internal::define_windows_procs - internal::abort_if_loop - tailcall disableVirtualTerminal {*}$args - } - set funcs [list disableAnsi enableProcessedInput disableProcessedInput] - foreach f $funcs { - proc $f {args} [string map [list %f% $f] { - set mybody [info body %f%] - internal::define_windows_procs - set newbody [info body %f%] - if {$newbody ne $mybody} { - tailcall %f% {*}$args + set oldmode_out [twapi::GetConsoleMode $h_out] + set newmode_out [expr {$oldmode_out | 4}] ;#don't enable processed output too, even though it's required. keep symmetrical with disableAnsi? + + twapi::SetConsoleMode $h_out $newmode_out + + #what does window_input have to do with it?? + #input handle modes + #ENABLE_PROCESSED_INPUT 0x0001 ;#set to zero will allow ctrl-c to be reported as keyboard input rather than as a signal + #ENABLE_LINE_INPUT 0x0002 + #ENABLE_ECHO_INPUT 0x0004 + #ENABLE_WINDOW_INPUT 0x0008 (default off when a terminal created) + #ENABLE_MOUSE_INPUT 0x0010 + #ENABLE_INSERT_MODE 0X0020 + #ENABLE_QUICK_EDIT_MODE 0x0040 + #ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200 (default off when a terminal created) (512) + set h_in [twapi::get_console_handle stdin] + set oldmode_in [twapi::GetConsoleMode $h_in] + set newmode_in [expr {$oldmode_in | 8}] + #set newmode_in [expr {$oldmode_in | 0x208}] + + twapi::SetConsoleMode $h_in $newmode_in + + return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]] + } + proc disableAnsi {} { + set h_out [twapi::get_console_handle stdout] + set oldmode_out [twapi::GetConsoleMode $h_out] + set newmode_out [expr {$oldmode_out & ~4}] + twapi::SetConsoleMode $h_out $newmode_out + + #??? review + set h_in [twapi::get_console_handle stdin] + set oldmode_in [twapi::GetConsoleMode $h_in] + set newmode_in [expr {$oldmode_in & ~8}] + twapi::SetConsoleMode $h_in $newmode_in + + + return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]] + } + proc enableVirtualTerminal {{channels {input output}}} { + set ins [list in input stdin] + set outs [list out output stdout stderr] + set known [concat $ins $outs both] + set directions [list] + foreach v $channels { + if {$v in $ins} { + lappend directions input + } elseif {$v in $outs} { + lappend directions output + } elseif {$v eq "both"} { + lappend directions input output + } + if {$v ni $known} { + error "enableVirtualTerminal expected channel values to be one of '$known'. (all values mapped to input and/or output)" + } + } + set channels $directions ;#don't worry about dups. + if {"both" in $channels} { + lappend channels input output + } + set result [dict create] + if {"output" in $channels} { + #note setting stdout makes stderr have the same settings - ie there is really only one output to configure + set h_out [twapi::get_console_handle stdout] + set oldmode [twapi::GetConsoleMode $h_out] + set newmode [expr {$oldmode | 4}] + twapi::SetConsoleMode $h_out $newmode + dict set result output [list from $oldmode to $newmode] + } + + if {"input" in $channels} { + set h_in [twapi::get_console_handle stdin] + set oldmode_in [twapi::GetConsoleMode $h_in] + set newmode_in [expr {$oldmode_in | 0x200}] + twapi::SetConsoleMode $h_in $newmode_in + dict set result input [list from $oldmode_in to $newmode_in] + } + + return $result + } + + proc disableVirtualTerminal {{channels {input output}}} { + set ins [list in input stdin] + set outs [list out output stdout stderr] + set known [concat $ins $outs both] + set directions [list] + foreach v $channels { + if {$v in $ins} { + lappend directions input + } elseif {$v in $outs} { + lappend directions output + } elseif {$v eq "both"} { + lappend directions input output + } + if {$v ni $known} { + error "disableVirtualTerminal expected channel values to be one of '$known'. (all values mapped to input and/or output)" + } + } + set channels $directions ;#don't worry about dups. + if {"both" in $channels} { + lappend channels input output + } + set result [dict create] + if {"output" in $channels} { + #as above - configuring stdout does stderr too + set h_out [twapi::get_console_handle stdout] + set oldmode [twapi::GetConsoleMode $h_out] + set newmode [expr {$oldmode & ~4}] + twapi::SetConsoleMode $h_out $newmode + dict set result output [list from $oldmode to $newmode] + } + if {"input" in $channels} { + set h_in [twapi::get_console_handle stdin] + set oldmode_in [twapi::GetConsoleMode $h_in] + set newmode_in [expr {$oldmode_in & ~0x200}] + twapi::SetConsoleMode $h_in $newmode_in + dict set result input [list from $oldmode_in to $newmode_in] + } + + #return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]] + return $result + } + proc enableProcessedInput {} { + set h_in [twapi::get_console_handle stdin] + set oldmode_in [twapi::GetConsoleMode $h_in] + set newmode_in [expr {$oldmode_in | 1}] + twapi::SetConsoleMode $h_in $newmode_in + return [list stdin [list from $oldmode_in to $newmode_in]] + } + proc disableProcessedInput {} { + set h_in [twapi::get_console_handle stdin] + set oldmode_in [twapi::GetConsoleMode $h_in] + set newmode_in [expr {$oldmode_in & ~1}] + twapi::SetConsoleMode $h_in $newmode_in + return [list stdin [list from $oldmode_in to $newmode_in]] + } + proc enableRaw {{channel stdin}} { + #variable is_raw + variable previous_stty_state_$channel + + if {[catch {twapi::get_console_handle stdin} console_handle]} { + puts stderr "enableRaw error: twapi cannot get console handle for stdin" + #review. If twapi couldn't get a console handle - no point trying other mechanisms(?) + return + } + #returns dictionary + #e.g -processedinput 1 -lineinput 1 -echoinput 1 -windowinput 0 -mouseinput 0 -insertmode 1 -quickeditmode 1 -extendedmode 1 -autoposition 0 + set oldmode [twapi::get_console_input_mode] + twapi::modify_console_input_mode $console_handle -lineinput 0 -echoinput 0 + # Turn off the echo and line-editing bits + #set newmode [dict merge $oldmode [dict create -lineinput 0 -echoinput 0]] + set newmode [twapi::get_console_input_mode] + + tsv::set console is_raw 1 + #don't disable handler - it will detect is_raw + ### twapi::set_console_control_handler {} + return [list stdin [list from $oldmode to $newmode]] + } + + #note: twapi GetStdHandle & GetConsoleMode & SetConsoleCombo unreliable - fails with invalid handle (somewhat intermittent.. after stdin reopened?) + #could be we were missing a step in reopening stdin and console configuration? + + proc disableRaw {{channel stdin}} { + #variable is_raw + variable previous_stty_state_$channel + set ch_state [chan conf $channel] + if {[dict exists $ch_state -inputmode]} { + chan conf $channel -inputmode normal + tsv::set console is_raw 0 + return [list $channel [list from [dict get $ch_state -inputmode] to normal]] } else { - #error vs noop? - puts stderr "Unable to set implementation for %f% - check twapi?" + if {[catch {twapi::get_console_handle stdin} console_handle]} { + #e.g tkcon/wish + puts stderr "disableRaw error: twapi cannot get console handle for stdin" + return ;# ??? + } + set oldmode [twapi::get_console_input_mode] + # Turn on the echo and line-editing bits + twapi::modify_console_input_mode $console_handle -lineinput 1 -echoinput 1 + set newmode [twapi::get_console_input_mode] + tsv::set console is_raw 0 + return [list stdin [list from $oldmode to $newmode]] } - }] + } + + } else { + + variable ps_consolemode_pid + variable ps_consolemode_contents + variable ps_pipename + if {![info exists ps_consolemode_contents]} { + #start persistent powershell consolemode_server.ps1 named pipe server + if {$::argv0 ne ""} { + set pstooldir [file dirname [file dirname [file normalize $::argv0]]]/scriptlib/utils/pwsh + } else { + set pstooldir [pwd] + } + #set ps_script $pstooldir/consolemode_server.ps1 + set ps_script $pstooldir/consolemode_server_async.ps1 + if {[file exists $ps_script]} { + set fd [open $ps_script r] + chan configure $fd -translation binary + set ps_consoleid [pid]-[expr {int(999 * rand())+1}] + set ps_consolemode_contents [string map [list "" $ps_consoleid] [read $fd]] + close $fd + #set ps_consolemode_pipe [twapi::namedpipe_client {//./pipe/punkshell_ps_consolemode} -access write] + #set ps_cmd [auto_execok pwsh.exe] + set ps_cmd [auto_execok pwsh.exe] + if {$ps_cmd eq ""} { + set ps_cmd [auto_execok powershell.exe] + } + if {$ps_cmd ne ""} { + set ps_consolemode_pid [exec {*}$ps_cmd -nop -nol -c $ps_consolemode_contents &] + set ps_pipename {\\.\pipe\punkshell_ps_consolemode_} + append ps_pipename $ps_consoleid + puts stderr "twapi not present, using persistent powershell process: pipename: $ps_pipename pid: $ps_consolemode_pid" + #todo - taskkill /F /PID $ps_consolemode_pid + #when? + #review + #if {[catch {puts "pidinfo: [::tcl::process::status $ps_consolemode_pid]"} errM]} { + # puts stderr "--- failed to get process status for $ps_consolemode_pid\n$errM" + #} + #set p [open {\\.\pipe\punkshell_ps_consolemode} w] + #chan conf $p -buffering none -blocking 1 + #puts $p "" + #close $p + } + } + + } + + + #enableRaw + proc enableRaw {{channel stdin}} { + #puts stderr "punk::console::enableRaw" + #variable is_raw + variable previous_stty_state_$channel + variable ps_consolemode_contents + variable ps_pipename + + + if {[info exists ps_consolemode_contents]} { + #ps_pipename e.g \\.\pipe\punkwinshell_ps_consolemode_12345-1223456 + + set trynum 0 + set wrote 0 + while {$trynum < 5} { + incr trynum + if {![catch { + set pipe [open $ps_pipename w] + } errMsg]} { + chan conf $pipe -buffering line + puts -nonewline $pipe "enableraw\r\n" + #flush $pipe + #after 10 + #close $pipe + set wrote 1 + break + } else { + after 100 + } + } + if {$wrote} { + tsv::set console is_raw 1 + after 100 + close $pipe + } else { + puts stderr "write to $ps_pipename failed trynum: $trynum\n$errMsg" + } + } elseif {[set sttycmd [auto_execok stty]] ne ""} { + #todo - something else entirely + #this approach does not work on windows + #the msys/cygwin stty command is launched as a subprocess - can be used to retrieve info + # but seems to be useless as far as affecting the calling process/console + if {[set previous_stty_state_$channel] eq ""} { + set previous_stty_state_$channel [exec {*}$sttycmd -g <@$channel] + } + + exec {*}$sttycmd raw -echo <@$channel + tsv::set console is_raw 1 + #review - inconsistent return dict + return [dict create stdin [list from [set previous_stty_state_$channel] to "" note "fixme - to state not shown"]] + } else { + error "punk::console::enableRaw Unable to use twapi or stty to set raw mode - aborting" + } + } + + + proc disableRaw {{channel stdin}} { + variable previous_stty_state_$channel + set ch_state [chan conf $channel] + if {[dict exists $ch_state -inputmode]} { + chan conf $channel -inputmode normal + tsv::set console is_raw 0 + return [list $channel [list from [dict get $ch_state -inputmode] to normal]] + } else { + #tcl <= 8.6x doesn't support -inputmode + if {[set sttycmd [auto_execok stty]] ne ""} { + #this doesn't work on windows + #It may seem to - only because running *any* external utility can exit raw mode + set sttycmd [auto_execok stty] + if {[set previous_stty_state_$channel] ne ""} { + exec {*}$sttycmd [set previous_stty_state_$channel] + set previous_stty_state_$channel "" + return restored + } + exec {*}$sttycmd -raw echo <@$channel + tsv::set console is_raw 0 + #do we really want to exec stty yet again to show final 'to' state? + #probably not. We should work out how to read the stty result flags and set a result.. or just limit from,to to showing echo and lineedit states. + return [list stdin [list from "[set previous_stty_state_$channel]" to "" note "fixme - to state not shown"]] + } else { + error "punk::console::disableRaw Unable to use twapi or stty to unset raw mode - aborting" + } + } + } + + #enableAnsi + proc enableAnsi {} { + } + #disableAnsi + proc enableAnsi {} { + } + #enableVirtualTerminal + proc enableVirtualTerminal {{channels {input output}}} { + } + #disableVirtualTerminal + proc disableVirtualTerminal {{channels {input output}}} { + } + #enableProcessedInput + #disableProcessedInput + } } else { + #non-windows platforms + proc enableAnsi {} { #todo? } @@ -190,6 +495,13 @@ namespace eval punk::console { #todo - something better - the 'channel' concept may not really apply on unix, as raw mode is set for input and output modes currently - only valid to set on a readable channel? #on windows they can be set independently (but not with stty) - REVIEW + proc enableVirtualTerminal {{channels {input output}}} { + + } + proc disableVirtualTerminal {args} { + + } + #NOTE - the is_raw is only being set in current interp - but the channel is shared. #this is problematic with the repl thread being separate. - must be a tsv? REVIEW proc enableRaw {{channel stdin}} { @@ -221,12 +533,6 @@ namespace eval punk::console { tsv::set console is_raw 0 return done } - proc enableVirtualTerminal {{channels {input output}}} { - - } - proc disableVirtualTerminal {args} { - - } } #review - document and decide granularity required. should we enable/disable more than one at once? @@ -257,7 +563,6 @@ namespace eval punk::console { #puts -nonewline stdout \x1b\[?25l ;#hide cursor puts -nonewline stdout \x1b\[?1003h\n enable_bracketed_paste - } #todo stop_application_mode {} {} @@ -313,266 +618,10 @@ namespace eval punk::console { } } proc define_windows_procs {} { - package require zzzload - set loadstate [zzzload::pkg_require twapi] - - #loadstate could also be stuck on loading? - review - zzzload not very ripe - #Twapi can be relatively slow to load (on some systems) - can be 1s plus in some cases - and much longer if there are disk performance issues. - if {$loadstate ni [list failed]} { - #possibly still 'loading' - #review zzzload usage - #puts stdout "=========== console loading twapi =============" - set loadstate [zzzload::pkg_wait twapi] ;#can return 'failed' will return version if already loaded or loaded during wait - } - - if {$loadstate ni [list failed]} { - package require twapi ;#should be fast once twapi dll loaded in zzzload thread - set ::punk::console::has_twapi 1 - - #todo - move some of these to the punk::console::local sub-namespace - as they use APIs rather than in-band ANSI to do their work. - #enableAnsi seems like it should be directly under punk::console .. but then it seems inconsistent if other local console-mode setting functions aren't. - #Find a compromise to organise things somewhat sensibly.. - - #this is really enableAnsi *processing* - proc [namespace parent]::enableAnsi {} { - #output handle modes - #Enable virtual terminal processing (sometimes off in older windows terminals) - #ENABLE_PROCESSED_OUTPUT = 0x0001 - #ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002 - #ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 - #DISABLE_NEWLINE_AUTO_RETURN = 0x0008 - set h_out [twapi::get_console_handle stdout] - set oldmode_out [twapi::GetConsoleMode $h_out] - set newmode_out [expr {$oldmode_out | 4}] ;#don't enable processed output too, even though it's required. keep symmetrical with disableAnsi? - - twapi::SetConsoleMode $h_out $newmode_out - - #what does window_input have to do with it?? - #input handle modes - #ENABLE_PROCESSED_INPUT 0x0001 ;#set to zero will allow ctrl-c to be reported as keyboard input rather than as a signal - #ENABLE_LINE_INPUT 0x0002 - #ENABLE_ECHO_INPUT 0x0004 - #ENABLE_WINDOW_INPUT 0x0008 (default off when a terminal created) - #ENABLE_MOUSE_INPUT 0x0010 - #ENABLE_INSERT_MODE 0X0020 - #ENABLE_QUICK_EDIT_MODE 0x0040 - #ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200 (default off when a terminal created) (512) - set h_in [twapi::get_console_handle stdin] - set oldmode_in [twapi::GetConsoleMode $h_in] - set newmode_in [expr {$oldmode_in | 8}] - #set newmode_in [expr {$oldmode_in | 0x208}] - - twapi::SetConsoleMode $h_in $newmode_in - - return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]] - } - proc [namespace parent]::disableAnsi {} { - set h_out [twapi::get_console_handle stdout] - set oldmode_out [twapi::GetConsoleMode $h_out] - set newmode_out [expr {$oldmode_out & ~4}] - twapi::SetConsoleMode $h_out $newmode_out - #??? review - set h_in [twapi::get_console_handle stdin] - set oldmode_in [twapi::GetConsoleMode $h_in] - set newmode_in [expr {$oldmode_in & ~8}] - twapi::SetConsoleMode $h_in $newmode_in - - - return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]] - } - - # - proc [namespace parent]::enableVirtualTerminal {{channels {input output}}} { - set ins [list in input stdin] - set outs [list out output stdout stderr] - set known [concat $ins $outs both] - set directions [list] - foreach v $channels { - if {$v in $ins} { - lappend directions input - } elseif {$v in $outs} { - lappend directions output - } elseif {$v eq "both"} { - lappend directions input output - } - if {$v ni $known} { - error "enableVirtualTerminal expected channel values to be one of '$known'. (all values mapped to input and/or output)" - } - } - set channels $directions ;#don't worry about dups. - if {"both" in $channels} { - lappend channels input output - } - set result [dict create] - if {"output" in $channels} { - #note setting stdout makes stderr have the same settings - ie there is really only one output to configure - set h_out [twapi::get_console_handle stdout] - set oldmode [twapi::GetConsoleMode $h_out] - set newmode [expr {$oldmode | 4}] - twapi::SetConsoleMode $h_out $newmode - dict set result output [list from $oldmode to $newmode] - } - - if {"input" in $channels} { - set h_in [twapi::get_console_handle stdin] - set oldmode_in [twapi::GetConsoleMode $h_in] - set newmode_in [expr {$oldmode_in | 0x200}] - twapi::SetConsoleMode $h_in $newmode_in - dict set result input [list from $oldmode_in to $newmode_in] - } - - return $result - } - proc [namespace parent]::disableVirtualTerminal {{channels {input output}}} { - set ins [list in input stdin] - set outs [list out output stdout stderr] - set known [concat $ins $outs both] - set directions [list] - foreach v $channels { - if {$v in $ins} { - lappend directions input - } elseif {$v in $outs} { - lappend directions output - } elseif {$v eq "both"} { - lappend directions input output - } - if {$v ni $known} { - error "disableVirtualTerminal expected channel values to be one of '$known'. (all values mapped to input and/or output)" - } - } - set channels $directions ;#don't worry about dups. - if {"both" in $channels} { - lappend channels input output - } - set result [dict create] - if {"output" in $channels} { - #as above - configuring stdout does stderr too - set h_out [twapi::get_console_handle stdout] - set oldmode [twapi::GetConsoleMode $h_out] - set newmode [expr {$oldmode & ~4}] - twapi::SetConsoleMode $h_out $newmode - dict set result output [list from $oldmode to $newmode] - } - if {"input" in $channels} { - set h_in [twapi::get_console_handle stdin] - set oldmode_in [twapi::GetConsoleMode $h_in] - set newmode_in [expr {$oldmode_in & ~0x200}] - twapi::SetConsoleMode $h_in $newmode_in - dict set result input [list from $oldmode_in to $newmode_in] - } - - #return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]] - return $result - } - - proc [namespace parent]::enableProcessedInput {} { - set h_in [twapi::get_console_handle stdin] - set oldmode_in [twapi::GetConsoleMode $h_in] - set newmode_in [expr {$oldmode_in | 1}] - twapi::SetConsoleMode $h_in $newmode_in - return [list stdin [list from $oldmode_in to $newmode_in]] - } - proc [namespace parent]::disableProcessedInput {} { - set h_in [twapi::get_console_handle stdin] - set oldmode_in [twapi::GetConsoleMode $h_in] - set newmode_in [expr {$oldmode_in & ~1}] - twapi::SetConsoleMode $h_in $newmode_in - return [list stdin [list from $oldmode_in to $newmode_in]] - } - } else { - - puts stderr "punk::console falling back to stty because twapi load failed" - proc [namespace parent]::enableAnsi {} { - puts stderr "punk::console::enableAnsi todo" - } - proc [namespace parent]::disableAnsi {} { - } - #? - proc [namespace parent]::enableVirtualTerminal {{channels {input output}}} { - } - proc [namespace parent]::disableVirtualTerminal {{channels {input output}}} { - } - proc [namespace parent]::enableProcessedInput {args} { - - } - proc [namespace parent]::disableProcessedInput {args} { - - } - - } - - proc [namespace parent]::enableRaw {{channel stdin}} { - #variable is_raw - variable previous_stty_state_$channel - - if {[package provide twapi] ne ""} { - set console_handle [twapi::get_console_handle stdin] - #returns dictionary - #e.g -processedinput 1 -lineinput 1 -echoinput 1 -windowinput 0 -mouseinput 0 -insertmode 1 -quickeditmode 1 -extendedmode 1 -autoposition 0 - set oldmode [twapi::get_console_input_mode] - twapi::modify_console_input_mode $console_handle -lineinput 0 -echoinput 0 - # Turn off the echo and line-editing bits - #set newmode [dict merge $oldmode [dict create -lineinput 0 -echoinput 0]] - set newmode [twapi::get_console_input_mode] - - tsv::set console is_raw 1 - #don't disable handler - it will detect is_raw - ### twapi::set_console_control_handler {} - return [list stdin [list from $oldmode to $newmode]] - } elseif {[set sttycmd [auto_execok stty]] ne ""} { - if {[set previous_stty_state_$channel] eq ""} { - set previous_stty_state_$channel [exec {*}$sttycmd -g <@$channel] - } - - exec {*}$sttycmd raw -echo <@$channel - tsv::set console is_raw 1 - #review - inconsistent return dict - return [dict create stdin [list from [set previous_stty_state_$channel] to "" note "fixme - to state not shown"]] - } else { - error "punk::console::enableRaw Unable to use twapi or stty to set raw mode - aborting" - } - } - - #note: twapi GetStdHandle & GetConsoleMode & SetConsoleCombo unreliable - fails with invalid handle (somewhat intermittent.. after stdin reopened?) - #could be we were missing a step in reopening stdin and console configuration? - - proc [namespace parent]::disableRaw {{channel stdin}} { - #variable is_raw - variable previous_stty_state_$channel - - if {[package provide twapi] ne ""} { - set console_handle [twapi::get_console_handle stdin] - set oldmode [twapi::get_console_input_mode] - # Turn on the echo and line-editing bits - twapi::modify_console_input_mode $console_handle -lineinput 1 -echoinput 1 - set newmode [twapi::get_console_input_mode] - tsv::set console is_raw 0 - return [list stdin [list from $oldmode to $newmode]] - } elseif {[set sttycmd [auto_execok stty]] ne ""} { - #stty can return info on windows - but doesn't seem to be able to set anything. - #review - is returned info even valid? - - set sttycmd [auto_execok stty] - if {[set previous_stty_state_$channel] ne ""} { - exec {*}$sttycmd [set previous_stty_state_$channel] - set previous_stty_state_$channel "" - return restored - } - exec {*}$sttycmd -raw echo <@$channel - tsv::set console is_raw 0 - #do we really want to exec stty yet again to show final 'to' state? - #probably not. We should work out how to read the stty result flags and set a result.. or just limit from,to to showing echo and lineedit states. - return [list stdin [list from "[set previous_stty_state_$channel]" to "" note "fixme - to state not shown"]] - } else { - error "punk::console::disableRaw Unable to use twapi or stty to unset raw mode - aborting" - } - } - - } lappend PUNKARGS [list { @@ -1803,7 +1852,10 @@ namespace eval punk::console { #don't set ansi_avaliable here - we want to be able to change things, retest etc. if {"windows" eq "$::tcl_platform(platform)"} { if {[package provide twapi] ne ""} { - set h_out [twapi::get_console_handle stdout] + if {[catch {twapi::get_console_handle stdout} h_out]} { + puts stderr "test_can_ansi: twapi cannot get console handle for stdout" + return 0 + } set existing_mode [twapi::GetConsoleMode $h_out] if {[expr {$existing_mode & 4}]} { #virtual terminal processing happens to be enabled - so it's supported diff --git a/src/bootsupport/modules/punk/libunknown-0.1.tm b/src/bootsupport/modules/punk/libunknown-0.1.tm index 3b5d35b0..f9dfaf56 100644 --- a/src/bootsupport/modules/punk/libunknown-0.1.tm +++ b/src/bootsupport/modules/punk/libunknown-0.1.tm @@ -80,16 +80,7 @@ tcl::namespace::eval punk::libunknown { "Experimental set of replacements for default 'package unknown' entries." }] - variable epoch - #if {![info exists epoch]} { - # set tmstate [dict create 0 {}] - # set pkgstate [dict create 0 {}] - # set tminfo [dict create current 0 epochs $tmstate] - # set pkginfo [dict create current 0 epochs $pkgstate] - - # set epoch [dict create tm $tminfo pkg $pkginfo] - #} - + variable epoch ;#don't set - can be pre-set cooperatively variable has_package_files if {[catch {package files foobaz}]} { @@ -111,6 +102,33 @@ tcl::namespace::eval punk::libunknown { #will use standard mechanism for non zipfs paths in the tm list. proc zipfs_tm_UnknownHandler {original name args} { + #------------------------------ + #shortcircuit for builtin static libraries which have no 'package provide' info - review + #This occurs for example when running 'bin\runtime.cmd run src\make.tcl shell' with punk902z.exe + # + #------------------------------ + set loaded [lsearch -inline -index 1 -nocase [info loaded] $name] + if {[llength $loaded] == 2 && [lindex $loaded 0] eq ""} { + lassign $loaded _ cased_name + interp create ptest + ptest eval [list load {} $cased_name] + set static_version [ptest eval [list package provide [string tolower $cased_name]]] + set pname [string tolower $cased_name] + if {$static_version eq ""} { + set static_version [ptest eval [list package provide $cased_name]] + set pname $cased_name + } + if {$static_version ne ""} { + if {[package vsatisfies $static_version {*}$args]} { + package ifneeded $pname $static_version [list load {} $cased_name] + interp delete ptest + return + } + } + interp delete ptest + } + #------------------------------ + # Import the list of paths to search for packages in module form. # Import the pattern used to check package names in detail. variable epoch @@ -1161,7 +1179,12 @@ tcl::namespace::eval punk::libunknown { set callerposn [lsearch $args -caller] if {$callerposn > -1} { set caller [lindex $args $callerposn+1] - #puts stderr "\x1b\[1\;33m punk::libunknown::init - caller:$caller\x1b\[m" + if {[package provide thread] ne ""} { + set tid [thread::id] + } else { + set tid "-" + } + #puts stderr "\x1b\[1\;33m punk::libunknown::init - caller:$caller tid:$tid\x1b\[m" #puts stderr "punk::libunknown::init auto_path : $::auto_path" #puts stderr "punk::libunknown::init tcl::tm::list: [tcl::tm::list]" } @@ -1184,17 +1207,17 @@ tcl::namespace::eval punk::libunknown { puts stderr "punk::libunknown::init - init while empty/unreadable tcl::tm::list and empty/unreadable ::auto_path" } - if {[namespace origin ::package] eq "::punk::libunknown::package"} { - #This is far from conclusive - there may be other renamers (e.g commandstack) + if {[info commands ::punk::libunknown::package] ne ""} { + puts stderr "punk::libunknown::init already done - unnecessary call? info frame -1: [info frame -1]" return } + #if {[namespace origin ::package] eq "::punk::libunknown::package"} { + # #This is far from conclusive - there may be other renamers (e.g commandstack) + # return + #} - if {[info commands ::punk::libunknown::package] ne ""} { - puts stderr "punk::libunknown::init already done - unnecessary call? info frame -1: [info frame -1]" - return - } variable epoch if {![info exists epoch]} { set tmstate [dict create 0 {added {}}] @@ -1222,6 +1245,7 @@ tcl::namespace::eval punk::libunknown { # or suffer additional scans.. or document ?? #ideally init should be called in each interp before any scans for packages so that the list of untracked is minimized. set pkgnames [package names] + #puts stderr "####### punk::libunknown init called with [llength $pkgnames] package names known" foreach p $pkgnames { if {[string tolower $p] in {punk::libunknown tcl::zlib tcloo tcl::oo tcl}} { continue diff --git a/src/bootsupport/modules/punk/repl-0.1.2.tm b/src/bootsupport/modules/punk/repl-0.1.2.tm index 9d199997..11cd9706 100644 --- a/src/bootsupport/modules/punk/repl-0.1.2.tm +++ b/src/bootsupport/modules/punk/repl-0.1.2.tm @@ -20,18 +20,6 @@ if {[dict exists $stdin_info -mode]} { #give up for now set tcl_interactive 1 -#if {[info commands ::tcl::zipfs::root] ne ""} { -# set zr [::tcl::zipfs::root] -# if {[file join $zr app modules] in [tcl::tm::list]} { -# #todo - better way to find latest version - without package require -# set lib [file join $zr app modules punk libunknown.tm] -# if {[file exists $lib]} { -# source $lib -# punk::libunknown::init -# #package unknown {punk::libunknown::zipfs_tm_UnknownHandler punk::libunknown::zipfs_tclPkgUnknown} -# } -# } -#} #------------------------------------------------------------------------------------- if {[package provide punk::libunknown] eq ""} { #maintenance - also in src/vfs/_config/punk_main.tcl @@ -59,7 +47,7 @@ if {[package provide punk::libunknown] eq ""} { } if {$libunknown ne ""} { source $libunknown - if {[catch {punk::libunknown::init -caller repl} errM]} { + if {[catch {punk::libunknown::init -caller triggered_by_repl_package_require} errM]} { puts "error initialising punk::libunknown\n$errM" } } @@ -525,11 +513,11 @@ proc repl::start {inchan args} { set donevalue [set [namespace current]::done] if {[lindex $donevalue 0] eq "quit"} { puts "-->repl::start end $inchan $args result:'$donevalue'" - puts stderr "--> returning [lindex $donevalue 1]" + #puts stderr "repl quit --> returning [lindex $donevalue 1]" return [lindex $donevalue 1] } puts "-->repl::start end $inchan $args result:'$donevalue'" - puts stderr "__> returning 0" + #puts stderr "__> returning 0" return 0 } proc repl::post_operations {} { @@ -1408,7 +1396,6 @@ proc repl::repl_handler {inputchan prompt_config} { if {[dict get $original_input_conf -inputmode] eq "raw"} { #user or script has apparently put stdin into raw mode - update punk::console::is_raw to match set rawmode 1 - #set ::punk::console::is_raw 1 tsv::set console is_raw 1 } else { #set ::punk::console::is_raw 0 @@ -1420,9 +1407,6 @@ proc repl::repl_handler {inputchan prompt_config} { #if it's been set to raw - assume it is deliberately done this way as the user could have alternatively called punk::mode raw or punk::console::enableVirtualTerminal #by not doing this automatically - we assume the caller has a reason. } else { - #JMN FIX! - #this returns 0 in rawmode on 8.6 after repl thread changes - #set rawmode [set ::punk::console::is_raw] set rawmode [tsv::get console is_raw] } @@ -1811,8 +1795,6 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config set infoprompt [dict get $prompt_config infoprompt] set debugprompt [dict get $prompt_config debugprompt] - - #set rawmode [set ::punk::console::is_raw] set rawmode [tsv::get console is_raw] if {!$rawmode} { #puts stderr "-->got [ansistring VIEW -lf 1 $stdinlines]<--" @@ -2615,6 +2597,34 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config } #editbuf + + #after any external command - raw mode as the console sees it can be disabled + #set it to match current state of the tsv + if {[tsv::get console is_raw]} { + if {$::tcl_platform(platform) eq "windows"} { + #review + #we are in parent process - twapi might not be loaded here - even if it is in the code interp + catch {package require twapi} + } + set sinfo [chan configure stdin] + if {[dict exists $sinfo -inputmode]} { + if {[dict get $sinfo -inputmode] ne "raw"} { + set re_enable_required 1 + } else { + set re_enable_required 0 + } + } else { + # -inputmode unavailable + #tcl 8.6 doesn't have -inputmode - meaning it has to call punk:console::enableRaw each time + #enableRaw on windows without twapi involves launching a pwsh process - which gives a noticeable lag in keyboard input. + #enableRaw on Unix involves a call to stty - which is generally fast - but still to be avoided if not required. + set re_enable_required 1 + } + #puts stderr "-here- re-enabling raw" + if {$re_enable_required} { + punk::console::enableRaw + } + } } else { #append commandstr \n if {$::punk::repl::signal_control_c} { @@ -2828,7 +2838,7 @@ namespace eval repl { } if {$libunknown ne ""} { source $libunknown - if {[catch {punk::libunknown::init -caller "repl init_script"} errM]} { + if {[catch {punk::libunknown::init -caller "repl::init init_script parent interp"} errM]} { puts "repl::init problem - error initialising punk::libunknown\n$errM" } #package require punk::lib @@ -2858,10 +2868,10 @@ namespace eval repl { #thread::send to caller defined interp targets (reference?) #snit required for icomm if {[catch {package require snit} errM]} { - puts stdout "punk::repl::initscript lib load fail ---snit $errM" + #puts stdout "punk::repl::initscript: lib load fail ---snit $errM" } if {[catch {package require punk::icomm} errM]} { - puts stdout "punk::repl::initscript lib load fail ---icomm $errM" + #puts stdout "punk::repl::initscript: lib load fail ---icomm $errM" } #----- @@ -2872,7 +2882,7 @@ namespace eval repl { #first use can raise error being a version number e.g 0.1.0 - why? lassign [tcl::chan::fifo2] ::punk::repl::codethread::repltalk replside } errMsg]} { - puts stdout "punk::repl::initscript tcl::chan::fifo2 error: $errM" + puts stdout "punk::repl::initscript tcl::chan::fifo2 error: $errMsg" } else { #experimental? #puts stdout "transferring chan $replside to thread %replthread%" @@ -3519,6 +3529,8 @@ namespace eval repl { #----------------------------------------------------------------------------- if {[package provide punk::libunknown] eq ""} { + namespace eval ::punk::libunknown {} + set ::punk::libunknown::epoch %lib_epoch% set libunks [list] foreach tm_path [tcl::tm::list] { set punkdir [file join $tm_path punk] @@ -3543,7 +3555,7 @@ namespace eval repl { } if {$libunknown ne ""} { source $libunknown - if {[catch {punk::libunknown::init -caller "repl init_script punk"} errM]} { + if {[catch {punk::libunknown::init -caller "repl::init init_script code interp for punk"} errM]} { puts "error initialising punk::libunknown\n$errM" } } diff --git a/src/bootsupport/modules_tcl8/Thread-2.8.9.tm b/src/bootsupport/modules_tcl8/Thread-2.8.9.tm deleted file mode 100644 index 45c8b5c6dce1ba973a158273983bded1bf009295..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14253 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 diff --git a/src/bootsupport/modules_tcl8/include_modules.config b/src/bootsupport/modules_tcl8/include_modules.config index c65f2a8a..090a7cf6 100644 --- a/src/bootsupport/modules_tcl8/include_modules.config +++ b/src/bootsupport/modules_tcl8/include_modules.config @@ -4,9 +4,6 @@ #each entry - base module set bootsupport_modules [list\ - modules_tcl8 thread\ - modules_tcl8 thread::platform::win32_x86_64_tcl8\ ] -# modules_tcl8/thread/platform *\ diff --git a/src/bootsupport/modules_tcl8/thread/platform/win32_x86_64_tcl8-2.8.9.tm b/src/bootsupport/modules_tcl8/thread/platform/win32_x86_64_tcl8-2.8.9.tm deleted file mode 100644 index d50bcf4a035fbd54b899346d25894ee08a9b2cd4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 79939 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�L7_a9QGaPI#|M&CdswhsY9W)_itX|)GFhFa%xTQ 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 diff --git a/src/bootsupport/modules_tcl8/win32_x86_64_tcl8-2.8.9.tm b/src/bootsupport/modules_tcl8/win32_x86_64_tcl8-2.8.9.tm deleted file mode 100644 index d50bcf4a035fbd54b899346d25894ee08a9b2cd4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 79939 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�L7_a9QGaPI#|M&CdswhsY9W)_itX|)GFhFa%xTQ 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 diff --git a/src/lib/app-punk/repl.tcl b/src/lib/app-punk/repl.tcl index 3474eff0..c9fb0ed5 100644 --- a/src/lib/app-punk/repl.tcl +++ b/src/lib/app-punk/repl.tcl @@ -49,16 +49,20 @@ repl::init -safe 0 #flush stderr set replresult [repl::start stdin -title app-punk] -catch { - puts "app-punk ifneeded: [package ifneeded app-punk 1.0]" -} +#catch { +# puts "app-punk ifneeded: [package ifneeded app-punk 1.0]" +#} + #review if {[string is integer -strict $replresult]} { - puts stdout "repl.tcl exiting with numeric code $replresult" + #puts stdout "repl.tcl exiting with numeric code $replresult" exit $replresult } else { - puts stdout "repl.tcl result $replresult" - flush stdout + if {$replresult ne ""} { + #puts stdout "repl.tcl result $replresult" + puts stdout $replresult + flush stdout + } exit 0 } #puts "- repl app done -" diff --git a/src/lib/app-punkshell/punkshell.tcl b/src/lib/app-punkshell/punkshell.tcl index 1559f0ec..828d6da8 100644 --- a/src/lib/app-punkshell/punkshell.tcl +++ b/src/lib/app-punkshell/punkshell.tcl @@ -1,6 +1,7 @@ package provide app-punkshell 1.0 package require Thread +package require punk::lib ;#required for compat - lpop for some early Tcl 8.6 versions package require punk::args package require shellfilter package require punk::ansi diff --git a/src/make.tcl b/src/make.tcl index 1736d3d9..c1d3f906 100644 --- a/src/make.tcl +++ b/src/make.tcl @@ -31,22 +31,28 @@ namespace eval ::punkboot::lib { #for some purposes (whether a source folder is likely to have any useful content) we are interested in non dotfile/dotfolder immediate contents of a folder, but not whether a particular platform #considers them hidden or not. proc folder_nondotted_children {folder} { - if {![file isdirectory $folder]} {error "punkboot::lib::folder_nondotted_children error. Supplied folder '$folder' is not a directory"} - set contents [glob -nocomplain -dir $folder *] + set normfolder [file normalize $folder] + if {![file isdirectory $normfolder]} {error "punkboot::lib::folder_nondotted_children error. Supplied folder '$folder' is not a directory"} + set contents [glob -nocomplain -dir $folder -tails *] #some platforms (windows) return dotted entries with *, although most don't - return [lsearch -all -inline -not $contents .*] + set nondotted_tails [lsearch -all -inline -not $contents .*] + return [lmap ftail $nondotted_tails {file join $folder $ftail}] } proc folder_nondotted_folders {folder} { - if {![file isdirectory $folder]} {error "punkboot::lib::folder_nondotted_folders error. Supplied folder '$folder' is not a directory"} - set contents [glob -nocomplain -dir $folder -types d *] + set normfolder [file normalize $folder] + if {![file isdirectory $normfolder]} {error "punkboot::lib::folder_nondotted_folders error. Supplied folder '$folder' is not a directory"} + set contents [glob -nocomplain -dir $folder -types d -tails *] #some platforms (windows) return dotted entries with *, although most don't - return [lsearch -all -inline -not $contents .*] + set nondotted_tails [lsearch -all -inline -not $contents .*] + return [lmap ftail $nondotted_tails {file join $folder $ftail}] } proc folder_nondotted_files {folder} { - if {![file isdirectory $folder]} {error "punkboot::lib::folder_nondotted_files error. Supplied folder '$folder' is not a directory"} - set contents [glob -nocomplain -dir $folder -types f $folder *] + set normfolder [file normalize $folder] + if {![file isdirectory $normfolder]} {error "punkboot::lib::folder_nondotted_files error. Supplied folder '$folder' is not a directory"} + set contents [glob -nocomplain -dir $folder -types f $folder -tails *] #some platforms (windows) return dotted entries with *, although most don't - return [lsearch -all -inline -not $contents .*] + set nondotted_tails [lsearch -all -inline -not $contents .*] + return [lmap ftail $nondotted_tails {file join $folder $ftail}] } proc tm_version_isvalid {versionpart} { #Needs to be suitable for use with Tcl's 'package vcompare' @@ -289,6 +295,10 @@ if {"::try" ni [info commands ::try]} { # This allows a source update via 'fossil update' 'git pull' etc to pull in a minimal set of support modules for the boot script # and load these in preference to ones that may have been in the interp's tcl::tm::list or auto_path due to environment variables set startdir [pwd] + +set scriptdir [file dirname [file normalize [info script]]] +#puts "SCRIPTDIR: $scriptdir" + #we are focussed on pure-tcl libs/modules in bootsupport for now. #There may be cases where we want to use compiled packages from src/bootsupport/modules_tcl9 etc #REVIEW - punkboot can really speed up with appropriate accelerators and/or external binaries @@ -303,18 +313,22 @@ set bootsupport_library_paths [list] set this_platform_generic [punkboot::lib::platform_generic] #we always create these lists in order of desired precedence. # - this is the same order when adding to auto_path - but will need to be reversed when using tcl:tm::add -if {[file exists [file join $startdir src bootsupport]]} { - lappend bootsupport_module_paths [file join $startdir src bootsupport modules_tcl$::tclmajorv] ;#more version-specific modules slightly higher in precedence order - lappend bootsupport_module_paths [file join $startdir src bootsupport modules] - lappend bootsupport_library_paths [file join $startdir src bootsupport lib_tcl$::tclmajorv/allplatforms] ;#more version-specific pkgs slightly higher in precedence order - lappend bootsupport_library_paths [file join $startdir src bootsupport lib_tcl$::tclmajorv/$this_platform_generic] ;#more version-specific pkgs slightly higher in precedence order - lappend bootsupport_library_paths [file join $startdir src bootsupport lib] +if {[file exists [file join $scriptdir bootsupport]]} { + set bootsupportdir [file join $scriptdir bootsupport] + puts stderr "Using bootsupport dir $bootsupportdir" + + lappend bootsupport_module_paths [file join $bootsupportdir modules_tcl$::tclmajorv] ;#more version-specific modules slightly higher in precedence order + lappend bootsupport_module_paths [file join $bootsupportdir modules] + lappend bootsupport_library_paths [file join $bootsupportdir lib_tcl$::tclmajorv/allplatforms] ;#more version-specific pkgs slightly higher in precedence order + lappend bootsupport_library_paths [file join $bootsupportdir lib_tcl$::tclmajorv/$this_platform_generic] ;#more version-specific pkgs slightly higher in precedence order + lappend bootsupport_library_paths [file join $bootsupportdir lib] } else { - lappend bootsupport_module_paths [file join $startdir bootsupport modules_tcl$::tclmajorv] - lappend bootsupport_module_paths [file join $startdir bootsupport modules] - lappend bootsupport_library_paths [file join $startdir bootsupport lib_tcl$::tclmajorv/allplatforms] - lappend bootsupport_library_paths [file join $startdir bootsupport lib_tcl$::tclmajorv/$this_platform_generic] - lappend bootsupport_library_paths [file join $startdir bootsupport lib] + puts stderr "No bootsupport dir for script [info script] at [file join $scriptdir bootsupport]" + #lappend bootsupport_module_paths [file join $startdir bootsupport modules_tcl$::tclmajorv] + #lappend bootsupport_module_paths [file join $startdir bootsupport modules] + #lappend bootsupport_library_paths [file join $startdir bootsupport lib_tcl$::tclmajorv/allplatforms] + #lappend bootsupport_library_paths [file join $startdir bootsupport lib_tcl$::tclmajorv/$this_platform_generic] + #lappend bootsupport_library_paths [file join $startdir bootsupport lib] } set bootsupport_paths_exist 0 foreach p [list {*}$bootsupport_module_paths {*}$bootsupport_library_paths] { @@ -406,8 +420,8 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} { tcl::tm::add {*}[lreverse $bootsupport_module_paths] {*}[lreverse $sourcesupport_module_paths] ;#tm::add works like LIFO. sourcesupport_module_paths end up earliest in resulting tm list. set ::auto_path [list {*}$sourcesupport_library_paths {*}$bootsupport_library_paths] } - puts "----> auto_path $::auto_path" - puts "----> tcl::tm::list [tcl::tm::list]" + #puts "----> auto_path $::auto_path" + #puts "----> tcl::tm::list [tcl::tm::list]" #maint: also in punk::repl package #-------------------------------------------------------- @@ -435,22 +449,26 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} { } if {$libunknown ne ""} { source $libunknown - if {[catch {punk::libunknown::init -caller main.tcl} errM]} { - puts "error initialising punk::libunknown\n$errM" + if {[catch {punk::libunknown::init -caller make.tcl} errM]} { + puts stderr "error initialising punk::libunknown\n$errM" } + #puts stdout " *** [package names]" + #puts stdout " **** [dict get $::punk::libunknown::epoch pkg untracked]" + } else { + puts stderr "Failed to find punk::libunknown" } #-------------------------------------------------------- #package require Thread - puts "---->tcl_library [info library]" - puts "---->loaded [info loaded]" + #puts "---->tcl_library [info library]" + #puts "---->loaded [info loaded]" # - the full repl requires Threading and punk,shellfilter,shellrun to call and display properly. # tm list already indexed - need 'package forget' to find modules based on current tcl::tm::list #These are strong dependencies - package forget punk::mix - package forget punk::repo - package forget punkcheck + #package forget punk::mix + #package forget punk::repo + #package forget punkcheck package require punk::repo ;#todo - push our requirements to a smaller punk::repo::xxx package with minimal dependencies package require punk::mix @@ -464,6 +482,7 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} { set package_paths_modified 1 #------------------------------------------------------------------------------ + #puts "----> llength package names [llength [package names]]" } set ::punkboot::pkg_requirements_found [list] @@ -479,7 +498,9 @@ set ::punkboot::bootsupport_requirements [dict create\ punkcheck [list]\ fauxlink [list version "0.1.1-"]\ textblock [list version 0.1.1-]\ + fileutil [list]\ fileutil::traverse [list]\ + struct::list [list]\ md5 [list version 2-]\ ] @@ -1282,7 +1303,41 @@ proc ::punkboot::punkboot_gethelp {args} { return $h } + + + set scriptargs $::argv +punk::args::define { + @id -id punkmake + @cmd -name punkmake\ + -summary\ + "Project builder"\ + -help\ + "" + @form -form help + @leaders + subcommand -type "literal(help)" + @opts + @values + what -type string -choices {modules libs shell} + + @form -form modules + subcommand -type "literal(modules)" + + @form -form libs + subcommand -type "literal(libs)" + + @form -form shell + subcommand -type "literal(shell)" + arg -type any -optional 1 -multiple 1 +} +#set argd [punk::args::parse $scriptargs -form 0 withid punkmake] +##lassign [dict values $argd] leaders opts values received +# +#puts stdout [punk::args::usage -scheme nocolour punkmake] +#exit 1 + + set do_help 0 if {![llength $scriptargs]} { set do_help 1 @@ -1294,6 +1349,8 @@ if {![llength $scriptargs]} { } } } + + set commands_found [list] foreach a $scriptargs { if {![string match -* $a]} { @@ -1310,6 +1367,8 @@ if {[llength $commands_found] != 1 } { puts stderr "Unknown command: [lindex $commands_found 0]\n\n" set do_help 1 } + + if {$do_help} { puts stdout "Checking package availability..." set ::punkboot::pkg_availability [::punkboot::check_package_availability -quiet 1 $::punkboot::bootsupport_requirements] @@ -1325,6 +1384,8 @@ if {$do_help} { exit 0 } + + set ::punkboot::command [lindex $commands_found 0] @@ -1414,14 +1475,15 @@ if {$::punkboot::command eq "check"} { if {$package_paths_modified} { set tm_list_boot [tcl::tm::list] tcl::tm::remove {*}$tm_list_boot - foreach p [lreverse $original_tm_list] { + + set lower_prio [list] + foreach p $original_tm_list { if {$p ni $tm_list_boot} { - tcl::tm::add $p + lappend lower_prio $p } } - foreach p [lreverse $tm_list_boot] { - tcl::tm::add $p - } + tcl::tm::add {*}[lreverse $lower_prio] {*}[lreverse $tm_list_boot] + #set ::auto_path [list $bootsupport_lib {*}$original_auto_path] lappend ::auto_path {*}$original_auto_path } @@ -1489,6 +1551,28 @@ if {![array size A]} { punkboot::define_global_ansi } +#puts stderr ">>>>>>+ loaded:[info loaded]" +#puts stderr "llength package names: [llength [package names]]" +if {[info exists ::punk::libunknown::epoch]} { + set untracked [dict get $::punk::libunknown::epoch pkg untracked] + #puts stderr "punk::libunknown::epoch exists" +} else { + set untracked [list] + #puts stderr "punk::libunknown::epoch does not exist" +} +#REVIEW - we shouldn't need to manually set the untracked packages - punk::libunknown::init should have done it? +foreach p [package names] { + if {![dict exists $untracked $p]} { + dict set untracked $p "" + } +} +dict set ::punk::libunknown::epoch pkg untracked $untracked + +if {[package provide punk::libunknown] eq ""} { + puts "punk::libunknown not loaded" +} else { + puts "punk::libunknown loaded" +} dict for {pkg pkginfo} $::punkboot::bootsupport_requirements { set verspec [dict get $pkginfo version] ;#version wanted specification always exists and is empty or normalised if {[catch {package require $pkg {*}$verspec} errM]} { @@ -1557,14 +1641,13 @@ if {$::punkboot::command eq "info"} { if {$::punkboot::command eq "shell"} { - puts stderr ">>>>>> loaded:[info loaded]" + package require struct::list package require punk package require punk::repl #todo - make procs vars etc from this file available? puts stderr "punk boot shell not implemented - dropping into ordinary punk shell." - repl::init set replresult [repl::start stdin -title make.tcl] #review @@ -3059,6 +3142,8 @@ foreach vfstail $vfs_tails { exec {*}$::sdxpath unwrap [file rootname $building_runtime].tail ;#extracts to folder named [file rootname $building_runtime].vfs e.g build_tclkit9.0.2-win64-dyn.vfs #file rename to existing target dir would copy folder into target dir if {![file exists $targetvfs]} { + #delay + after 1000 file rename [file rootname $building_runtime].vfs $targetvfs } else { merge_over [file rootname $building_runtime].vfs $targetvfs diff --git a/src/modules/punk/args-999999.0a1.0.tm b/src/modules/punk/args-999999.0a1.0.tm index 8e55fdd5..fa8b6564 100644 --- a/src/modules/punk/args-999999.0a1.0.tm +++ b/src/modules/punk/args-999999.0a1.0.tm @@ -3036,8 +3036,11 @@ tcl::namespace::eval punk::args { #This mechanism gets less-than-useful results for oo methods #e.g {$obj} proc Get_caller {} { + set depth [info level] + set maxd [expr {min($depth,4)}] + set call_level [expr {-1 * $maxd}] #set call_level -3 ;#for get_dict call - set call_level -4 + #set call_level -4 set cmdinfo [tcl::dict::get [tcl::info::frame $call_level] cmd] #puts "-->$cmdinfo" #puts "-->[tcl::info::frame -3]" diff --git a/src/modules/punk/args/tclcore-999999.0a1.0.tm b/src/modules/punk/args/tclcore-999999.0a1.0.tm index b5ff24ab..13141580 100644 --- a/src/modules/punk/args/tclcore-999999.0a1.0.tm +++ b/src/modules/punk/args/tclcore-999999.0a1.0.tm @@ -3498,7 +3498,7 @@ tcl::namespace::eval punk::args::tclcore { example, in ${$B}-dictionary${$N} mode, bigBoy sorts between bigbang and bigboy, and x10y sorts between x9y and x11y. Overrides the ${$B}-nocase${$N} option." -integer -type none -help\ - "Convert list elements to integers and use integer comparsion." + "Convert list elements to integers and use integer comparison." -real -type none -help\ "Convert list elements to floating-point values and use floating comparison." -command -type string -help\ diff --git a/src/modules/punk/console-999999.0a1.0.tm b/src/modules/punk/console-999999.0a1.0.tm index 3e973dcc..f9f9bcef 100644 --- a/src/modules/punk/console-999999.0a1.0.tm +++ b/src/modules/punk/console-999999.0a1.0.tm @@ -129,57 +129,362 @@ namespace eval punk::console { #e.g external utils system API's. namespace export * } - + if {"windows" eq $::tcl_platform(platform)} { #accept args for all dummy/load functions so we don't have to match/update argument signatures here + set has_twapi [expr {! [catch {package require twapi}]}] + + if {$has_twapi} { + #this is really enableAnsi *processing* + proc enableAnsi {} { + #output handle modes + #Enable virtual terminal processing (sometimes off in older windows terminals) + #ENABLE_PROCESSED_OUTPUT = 0x0001 + #ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002 + #ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 + #DISABLE_NEWLINE_AUTO_RETURN = 0x0008 + if {[catch {twapi::get_console_handle stdout} h_out]} { + puts stderr "enableAnsi failed: twapi cannot get console handle for stdout" + return + } - proc enableAnsi {args} { - #loopavoidancetoken (don't remove) - internal::define_windows_procs - internal::abort_if_loop - tailcall enableAnsi {*}$args - } - #review what raw mode means with regard to a specific channel vs terminal as a whole - proc enableRaw {args} { - #loopavoidancetoken (don't remove) - internal::define_windows_procs - internal::abort_if_loop - tailcall enableRaw {*}$args - } - proc disableRaw {args} { - #loopavoidancetoken (don't remove) - internal::define_windows_procs - internal::abort_if_loop - tailcall disableRaw {*}$args - } - proc enableVirtualTerminal {args} { - #loopavoidancetoken (don't remove) - internal::define_windows_procs - internal::abort_if_loop - tailcall enableVirtualTerminal {*}$args - } - proc disableVirtualTerminal {args} { - #loopavoidancetoken (don't remove) - internal::define_windows_procs - internal::abort_if_loop - tailcall disableVirtualTerminal {*}$args - } - set funcs [list disableAnsi enableProcessedInput disableProcessedInput] - foreach f $funcs { - proc $f {args} [string map [list %f% $f] { - set mybody [info body %f%] - internal::define_windows_procs - set newbody [info body %f%] - if {$newbody ne $mybody} { - tailcall %f% {*}$args + set oldmode_out [twapi::GetConsoleMode $h_out] + set newmode_out [expr {$oldmode_out | 4}] ;#don't enable processed output too, even though it's required. keep symmetrical with disableAnsi? + + twapi::SetConsoleMode $h_out $newmode_out + + #what does window_input have to do with it?? + #input handle modes + #ENABLE_PROCESSED_INPUT 0x0001 ;#set to zero will allow ctrl-c to be reported as keyboard input rather than as a signal + #ENABLE_LINE_INPUT 0x0002 + #ENABLE_ECHO_INPUT 0x0004 + #ENABLE_WINDOW_INPUT 0x0008 (default off when a terminal created) + #ENABLE_MOUSE_INPUT 0x0010 + #ENABLE_INSERT_MODE 0X0020 + #ENABLE_QUICK_EDIT_MODE 0x0040 + #ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200 (default off when a terminal created) (512) + set h_in [twapi::get_console_handle stdin] + set oldmode_in [twapi::GetConsoleMode $h_in] + set newmode_in [expr {$oldmode_in | 8}] + #set newmode_in [expr {$oldmode_in | 0x208}] + + twapi::SetConsoleMode $h_in $newmode_in + + return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]] + } + proc disableAnsi {} { + set h_out [twapi::get_console_handle stdout] + set oldmode_out [twapi::GetConsoleMode $h_out] + set newmode_out [expr {$oldmode_out & ~4}] + twapi::SetConsoleMode $h_out $newmode_out + + #??? review + set h_in [twapi::get_console_handle stdin] + set oldmode_in [twapi::GetConsoleMode $h_in] + set newmode_in [expr {$oldmode_in & ~8}] + twapi::SetConsoleMode $h_in $newmode_in + + + return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]] + } + proc enableVirtualTerminal {{channels {input output}}} { + set ins [list in input stdin] + set outs [list out output stdout stderr] + set known [concat $ins $outs both] + set directions [list] + foreach v $channels { + if {$v in $ins} { + lappend directions input + } elseif {$v in $outs} { + lappend directions output + } elseif {$v eq "both"} { + lappend directions input output + } + if {$v ni $known} { + error "enableVirtualTerminal expected channel values to be one of '$known'. (all values mapped to input and/or output)" + } + } + set channels $directions ;#don't worry about dups. + if {"both" in $channels} { + lappend channels input output + } + set result [dict create] + if {"output" in $channels} { + #note setting stdout makes stderr have the same settings - ie there is really only one output to configure + set h_out [twapi::get_console_handle stdout] + set oldmode [twapi::GetConsoleMode $h_out] + set newmode [expr {$oldmode | 4}] + twapi::SetConsoleMode $h_out $newmode + dict set result output [list from $oldmode to $newmode] + } + + if {"input" in $channels} { + set h_in [twapi::get_console_handle stdin] + set oldmode_in [twapi::GetConsoleMode $h_in] + set newmode_in [expr {$oldmode_in | 0x200}] + twapi::SetConsoleMode $h_in $newmode_in + dict set result input [list from $oldmode_in to $newmode_in] + } + + return $result + } + + proc disableVirtualTerminal {{channels {input output}}} { + set ins [list in input stdin] + set outs [list out output stdout stderr] + set known [concat $ins $outs both] + set directions [list] + foreach v $channels { + if {$v in $ins} { + lappend directions input + } elseif {$v in $outs} { + lappend directions output + } elseif {$v eq "both"} { + lappend directions input output + } + if {$v ni $known} { + error "disableVirtualTerminal expected channel values to be one of '$known'. (all values mapped to input and/or output)" + } + } + set channels $directions ;#don't worry about dups. + if {"both" in $channels} { + lappend channels input output + } + set result [dict create] + if {"output" in $channels} { + #as above - configuring stdout does stderr too + set h_out [twapi::get_console_handle stdout] + set oldmode [twapi::GetConsoleMode $h_out] + set newmode [expr {$oldmode & ~4}] + twapi::SetConsoleMode $h_out $newmode + dict set result output [list from $oldmode to $newmode] + } + if {"input" in $channels} { + set h_in [twapi::get_console_handle stdin] + set oldmode_in [twapi::GetConsoleMode $h_in] + set newmode_in [expr {$oldmode_in & ~0x200}] + twapi::SetConsoleMode $h_in $newmode_in + dict set result input [list from $oldmode_in to $newmode_in] + } + + #return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]] + return $result + } + proc enableProcessedInput {} { + set h_in [twapi::get_console_handle stdin] + set oldmode_in [twapi::GetConsoleMode $h_in] + set newmode_in [expr {$oldmode_in | 1}] + twapi::SetConsoleMode $h_in $newmode_in + return [list stdin [list from $oldmode_in to $newmode_in]] + } + proc disableProcessedInput {} { + set h_in [twapi::get_console_handle stdin] + set oldmode_in [twapi::GetConsoleMode $h_in] + set newmode_in [expr {$oldmode_in & ~1}] + twapi::SetConsoleMode $h_in $newmode_in + return [list stdin [list from $oldmode_in to $newmode_in]] + } + proc enableRaw {{channel stdin}} { + #variable is_raw + variable previous_stty_state_$channel + + if {[catch {twapi::get_console_handle stdin} console_handle]} { + puts stderr "enableRaw error: twapi cannot get console handle for stdin" + #review. If twapi couldn't get a console handle - no point trying other mechanisms(?) + return + } + #returns dictionary + #e.g -processedinput 1 -lineinput 1 -echoinput 1 -windowinput 0 -mouseinput 0 -insertmode 1 -quickeditmode 1 -extendedmode 1 -autoposition 0 + set oldmode [twapi::get_console_input_mode] + twapi::modify_console_input_mode $console_handle -lineinput 0 -echoinput 0 + # Turn off the echo and line-editing bits + #set newmode [dict merge $oldmode [dict create -lineinput 0 -echoinput 0]] + set newmode [twapi::get_console_input_mode] + + tsv::set console is_raw 1 + #don't disable handler - it will detect is_raw + ### twapi::set_console_control_handler {} + return [list stdin [list from $oldmode to $newmode]] + } + + #note: twapi GetStdHandle & GetConsoleMode & SetConsoleCombo unreliable - fails with invalid handle (somewhat intermittent.. after stdin reopened?) + #could be we were missing a step in reopening stdin and console configuration? + + proc disableRaw {{channel stdin}} { + #variable is_raw + variable previous_stty_state_$channel + set ch_state [chan conf $channel] + if {[dict exists $ch_state -inputmode]} { + chan conf $channel -inputmode normal + tsv::set console is_raw 0 + return [list $channel [list from [dict get $ch_state -inputmode] to normal]] } else { - #error vs noop? - puts stderr "Unable to set implementation for %f% - check twapi?" + if {[catch {twapi::get_console_handle stdin} console_handle]} { + #e.g tkcon/wish + puts stderr "disableRaw error: twapi cannot get console handle for stdin" + return ;# ??? + } + set oldmode [twapi::get_console_input_mode] + # Turn on the echo and line-editing bits + twapi::modify_console_input_mode $console_handle -lineinput 1 -echoinput 1 + set newmode [twapi::get_console_input_mode] + tsv::set console is_raw 0 + return [list stdin [list from $oldmode to $newmode]] } - }] + } + + } else { + + variable ps_consolemode_pid + variable ps_consolemode_contents + variable ps_pipename + if {![info exists ps_consolemode_contents]} { + #start persistent powershell consolemode_server.ps1 named pipe server + if {$::argv0 ne ""} { + set pstooldir [file dirname [file dirname [file normalize $::argv0]]]/scriptlib/utils/pwsh + } else { + set pstooldir [pwd] + } + #set ps_script $pstooldir/consolemode_server.ps1 + set ps_script $pstooldir/consolemode_server_async.ps1 + if {[file exists $ps_script]} { + set fd [open $ps_script r] + chan configure $fd -translation binary + set ps_consoleid [pid]-[expr {int(999 * rand())+1}] + set ps_consolemode_contents [string map [list "" $ps_consoleid] [read $fd]] + close $fd + #set ps_consolemode_pipe [twapi::namedpipe_client {//./pipe/punkshell_ps_consolemode} -access write] + #set ps_cmd [auto_execok pwsh.exe] + set ps_cmd [auto_execok pwsh.exe] + if {$ps_cmd eq ""} { + set ps_cmd [auto_execok powershell.exe] + } + if {$ps_cmd ne ""} { + set ps_consolemode_pid [exec {*}$ps_cmd -nop -nol -c $ps_consolemode_contents &] + set ps_pipename {\\.\pipe\punkshell_ps_consolemode_} + append ps_pipename $ps_consoleid + puts stderr "twapi not present, using persistent powershell process: pipename: $ps_pipename pid: $ps_consolemode_pid" + #todo - taskkill /F /PID $ps_consolemode_pid + #when? + #review + #if {[catch {puts "pidinfo: [::tcl::process::status $ps_consolemode_pid]"} errM]} { + # puts stderr "--- failed to get process status for $ps_consolemode_pid\n$errM" + #} + #set p [open {\\.\pipe\punkshell_ps_consolemode} w] + #chan conf $p -buffering none -blocking 1 + #puts $p "" + #close $p + } + } + + } + + + #enableRaw + proc enableRaw {{channel stdin}} { + #puts stderr "punk::console::enableRaw" + #variable is_raw + variable previous_stty_state_$channel + variable ps_consolemode_contents + variable ps_pipename + + + if {[info exists ps_consolemode_contents]} { + #ps_pipename e.g \\.\pipe\punkwinshell_ps_consolemode_12345-1223456 + + set trynum 0 + set wrote 0 + while {$trynum < 5} { + incr trynum + if {![catch { + set pipe [open $ps_pipename w] + } errMsg]} { + chan conf $pipe -buffering line + puts -nonewline $pipe "enableraw\r\n" + #flush $pipe + #after 10 + #close $pipe + set wrote 1 + break + } else { + after 100 + } + } + if {$wrote} { + tsv::set console is_raw 1 + #after 100 + close $pipe + } else { + puts stderr "write to $ps_pipename failed trynum: $trynum\n$errMsg" + } + } elseif {[set sttycmd [auto_execok stty]] ne ""} { + #todo - something else entirely + #this approach does not work on windows + #the msys/cygwin stty command is launched as a subprocess - can be used to retrieve info + # but seems to be useless as far as affecting the calling process/console + if {[set previous_stty_state_$channel] eq ""} { + set previous_stty_state_$channel [exec {*}$sttycmd -g <@$channel] + } + + exec {*}$sttycmd raw -echo <@$channel + tsv::set console is_raw 1 + #review - inconsistent return dict + return [dict create stdin [list from [set previous_stty_state_$channel] to "" note "fixme - to state not shown"]] + } else { + error "punk::console::enableRaw Unable to use twapi or stty to set raw mode - aborting" + } + } + + + proc disableRaw {{channel stdin}} { + variable previous_stty_state_$channel + set ch_state [chan conf $channel] + if {[dict exists $ch_state -inputmode]} { + chan conf $channel -inputmode normal + tsv::set console is_raw 0 + return [list $channel [list from [dict get $ch_state -inputmode] to normal]] + } else { + #tcl <= 8.6x doesn't support -inputmode + if {[set sttycmd [auto_execok stty]] ne ""} { + #this doesn't work on windows + #It may seem to - only because running *any* external utility can exit raw mode + set sttycmd [auto_execok stty] + if {[set previous_stty_state_$channel] ne ""} { + exec {*}$sttycmd [set previous_stty_state_$channel] + set previous_stty_state_$channel "" + return restored + } + exec {*}$sttycmd -raw echo <@$channel + tsv::set console is_raw 0 + #do we really want to exec stty yet again to show final 'to' state? + #probably not. We should work out how to read the stty result flags and set a result.. or just limit from,to to showing echo and lineedit states. + return [list stdin [list from "[set previous_stty_state_$channel]" to "" note "fixme - to state not shown"]] + } else { + error "punk::console::disableRaw Unable to use twapi or stty to unset raw mode - aborting" + } + } + } + + #enableAnsi + proc enableAnsi {} { + } + #disableAnsi + proc enableAnsi {} { + } + #enableVirtualTerminal + proc enableVirtualTerminal {{channels {input output}}} { + } + #disableVirtualTerminal + proc disableVirtualTerminal {{channels {input output}}} { + } + #enableProcessedInput + #disableProcessedInput + } } else { + #non-windows platforms + proc enableAnsi {} { #todo? } @@ -190,6 +495,13 @@ namespace eval punk::console { #todo - something better - the 'channel' concept may not really apply on unix, as raw mode is set for input and output modes currently - only valid to set on a readable channel? #on windows they can be set independently (but not with stty) - REVIEW + proc enableVirtualTerminal {{channels {input output}}} { + + } + proc disableVirtualTerminal {args} { + + } + #NOTE - the is_raw is only being set in current interp - but the channel is shared. #this is problematic with the repl thread being separate. - must be a tsv? REVIEW proc enableRaw {{channel stdin}} { @@ -221,12 +533,6 @@ namespace eval punk::console { tsv::set console is_raw 0 return done } - proc enableVirtualTerminal {{channels {input output}}} { - - } - proc disableVirtualTerminal {args} { - - } } #review - document and decide granularity required. should we enable/disable more than one at once? @@ -257,7 +563,6 @@ namespace eval punk::console { #puts -nonewline stdout \x1b\[?25l ;#hide cursor puts -nonewline stdout \x1b\[?1003h\n enable_bracketed_paste - } #todo stop_application_mode {} {} @@ -313,266 +618,10 @@ namespace eval punk::console { } } proc define_windows_procs {} { - package require zzzload - set loadstate [zzzload::pkg_require twapi] - - #loadstate could also be stuck on loading? - review - zzzload not very ripe - #Twapi can be relatively slow to load (on some systems) - can be 1s plus in some cases - and much longer if there are disk performance issues. - if {$loadstate ni [list failed]} { - #possibly still 'loading' - #review zzzload usage - #puts stdout "=========== console loading twapi =============" - set loadstate [zzzload::pkg_wait twapi] ;#can return 'failed' will return version if already loaded or loaded during wait - } - - if {$loadstate ni [list failed]} { - package require twapi ;#should be fast once twapi dll loaded in zzzload thread - set ::punk::console::has_twapi 1 - - #todo - move some of these to the punk::console::local sub-namespace - as they use APIs rather than in-band ANSI to do their work. - #enableAnsi seems like it should be directly under punk::console .. but then it seems inconsistent if other local console-mode setting functions aren't. - #Find a compromise to organise things somewhat sensibly.. - - #this is really enableAnsi *processing* - proc [namespace parent]::enableAnsi {} { - #output handle modes - #Enable virtual terminal processing (sometimes off in older windows terminals) - #ENABLE_PROCESSED_OUTPUT = 0x0001 - #ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002 - #ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 - #DISABLE_NEWLINE_AUTO_RETURN = 0x0008 - set h_out [twapi::get_console_handle stdout] - set oldmode_out [twapi::GetConsoleMode $h_out] - set newmode_out [expr {$oldmode_out | 4}] ;#don't enable processed output too, even though it's required. keep symmetrical with disableAnsi? - - twapi::SetConsoleMode $h_out $newmode_out - - #what does window_input have to do with it?? - #input handle modes - #ENABLE_PROCESSED_INPUT 0x0001 ;#set to zero will allow ctrl-c to be reported as keyboard input rather than as a signal - #ENABLE_LINE_INPUT 0x0002 - #ENABLE_ECHO_INPUT 0x0004 - #ENABLE_WINDOW_INPUT 0x0008 (default off when a terminal created) - #ENABLE_MOUSE_INPUT 0x0010 - #ENABLE_INSERT_MODE 0X0020 - #ENABLE_QUICK_EDIT_MODE 0x0040 - #ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200 (default off when a terminal created) (512) - set h_in [twapi::get_console_handle stdin] - set oldmode_in [twapi::GetConsoleMode $h_in] - set newmode_in [expr {$oldmode_in | 8}] - #set newmode_in [expr {$oldmode_in | 0x208}] - - twapi::SetConsoleMode $h_in $newmode_in - - return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]] - } - proc [namespace parent]::disableAnsi {} { - set h_out [twapi::get_console_handle stdout] - set oldmode_out [twapi::GetConsoleMode $h_out] - set newmode_out [expr {$oldmode_out & ~4}] - twapi::SetConsoleMode $h_out $newmode_out - #??? review - set h_in [twapi::get_console_handle stdin] - set oldmode_in [twapi::GetConsoleMode $h_in] - set newmode_in [expr {$oldmode_in & ~8}] - twapi::SetConsoleMode $h_in $newmode_in - - - return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]] - } - - # - proc [namespace parent]::enableVirtualTerminal {{channels {input output}}} { - set ins [list in input stdin] - set outs [list out output stdout stderr] - set known [concat $ins $outs both] - set directions [list] - foreach v $channels { - if {$v in $ins} { - lappend directions input - } elseif {$v in $outs} { - lappend directions output - } elseif {$v eq "both"} { - lappend directions input output - } - if {$v ni $known} { - error "enableVirtualTerminal expected channel values to be one of '$known'. (all values mapped to input and/or output)" - } - } - set channels $directions ;#don't worry about dups. - if {"both" in $channels} { - lappend channels input output - } - set result [dict create] - if {"output" in $channels} { - #note setting stdout makes stderr have the same settings - ie there is really only one output to configure - set h_out [twapi::get_console_handle stdout] - set oldmode [twapi::GetConsoleMode $h_out] - set newmode [expr {$oldmode | 4}] - twapi::SetConsoleMode $h_out $newmode - dict set result output [list from $oldmode to $newmode] - } - - if {"input" in $channels} { - set h_in [twapi::get_console_handle stdin] - set oldmode_in [twapi::GetConsoleMode $h_in] - set newmode_in [expr {$oldmode_in | 0x200}] - twapi::SetConsoleMode $h_in $newmode_in - dict set result input [list from $oldmode_in to $newmode_in] - } - - return $result - } - proc [namespace parent]::disableVirtualTerminal {{channels {input output}}} { - set ins [list in input stdin] - set outs [list out output stdout stderr] - set known [concat $ins $outs both] - set directions [list] - foreach v $channels { - if {$v in $ins} { - lappend directions input - } elseif {$v in $outs} { - lappend directions output - } elseif {$v eq "both"} { - lappend directions input output - } - if {$v ni $known} { - error "disableVirtualTerminal expected channel values to be one of '$known'. (all values mapped to input and/or output)" - } - } - set channels $directions ;#don't worry about dups. - if {"both" in $channels} { - lappend channels input output - } - set result [dict create] - if {"output" in $channels} { - #as above - configuring stdout does stderr too - set h_out [twapi::get_console_handle stdout] - set oldmode [twapi::GetConsoleMode $h_out] - set newmode [expr {$oldmode & ~4}] - twapi::SetConsoleMode $h_out $newmode - dict set result output [list from $oldmode to $newmode] - } - if {"input" in $channels} { - set h_in [twapi::get_console_handle stdin] - set oldmode_in [twapi::GetConsoleMode $h_in] - set newmode_in [expr {$oldmode_in & ~0x200}] - twapi::SetConsoleMode $h_in $newmode_in - dict set result input [list from $oldmode_in to $newmode_in] - } - - #return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]] - return $result - } - - proc [namespace parent]::enableProcessedInput {} { - set h_in [twapi::get_console_handle stdin] - set oldmode_in [twapi::GetConsoleMode $h_in] - set newmode_in [expr {$oldmode_in | 1}] - twapi::SetConsoleMode $h_in $newmode_in - return [list stdin [list from $oldmode_in to $newmode_in]] - } - proc [namespace parent]::disableProcessedInput {} { - set h_in [twapi::get_console_handle stdin] - set oldmode_in [twapi::GetConsoleMode $h_in] - set newmode_in [expr {$oldmode_in & ~1}] - twapi::SetConsoleMode $h_in $newmode_in - return [list stdin [list from $oldmode_in to $newmode_in]] - } - } else { - - puts stderr "punk::console falling back to stty because twapi load failed" - proc [namespace parent]::enableAnsi {} { - puts stderr "punk::console::enableAnsi todo" - } - proc [namespace parent]::disableAnsi {} { - } - #? - proc [namespace parent]::enableVirtualTerminal {{channels {input output}}} { - } - proc [namespace parent]::disableVirtualTerminal {{channels {input output}}} { - } - proc [namespace parent]::enableProcessedInput {args} { - - } - proc [namespace parent]::disableProcessedInput {args} { - - } - - } - - proc [namespace parent]::enableRaw {{channel stdin}} { - #variable is_raw - variable previous_stty_state_$channel - - if {[package provide twapi] ne ""} { - set console_handle [twapi::get_console_handle stdin] - #returns dictionary - #e.g -processedinput 1 -lineinput 1 -echoinput 1 -windowinput 0 -mouseinput 0 -insertmode 1 -quickeditmode 1 -extendedmode 1 -autoposition 0 - set oldmode [twapi::get_console_input_mode] - twapi::modify_console_input_mode $console_handle -lineinput 0 -echoinput 0 - # Turn off the echo and line-editing bits - #set newmode [dict merge $oldmode [dict create -lineinput 0 -echoinput 0]] - set newmode [twapi::get_console_input_mode] - - tsv::set console is_raw 1 - #don't disable handler - it will detect is_raw - ### twapi::set_console_control_handler {} - return [list stdin [list from $oldmode to $newmode]] - } elseif {[set sttycmd [auto_execok stty]] ne ""} { - if {[set previous_stty_state_$channel] eq ""} { - set previous_stty_state_$channel [exec {*}$sttycmd -g <@$channel] - } - - exec {*}$sttycmd raw -echo <@$channel - tsv::set console is_raw 1 - #review - inconsistent return dict - return [dict create stdin [list from [set previous_stty_state_$channel] to "" note "fixme - to state not shown"]] - } else { - error "punk::console::enableRaw Unable to use twapi or stty to set raw mode - aborting" - } - } - - #note: twapi GetStdHandle & GetConsoleMode & SetConsoleCombo unreliable - fails with invalid handle (somewhat intermittent.. after stdin reopened?) - #could be we were missing a step in reopening stdin and console configuration? - - proc [namespace parent]::disableRaw {{channel stdin}} { - #variable is_raw - variable previous_stty_state_$channel - - if {[package provide twapi] ne ""} { - set console_handle [twapi::get_console_handle stdin] - set oldmode [twapi::get_console_input_mode] - # Turn on the echo and line-editing bits - twapi::modify_console_input_mode $console_handle -lineinput 1 -echoinput 1 - set newmode [twapi::get_console_input_mode] - tsv::set console is_raw 0 - return [list stdin [list from $oldmode to $newmode]] - } elseif {[set sttycmd [auto_execok stty]] ne ""} { - #stty can return info on windows - but doesn't seem to be able to set anything. - #review - is returned info even valid? - - set sttycmd [auto_execok stty] - if {[set previous_stty_state_$channel] ne ""} { - exec {*}$sttycmd [set previous_stty_state_$channel] - set previous_stty_state_$channel "" - return restored - } - exec {*}$sttycmd -raw echo <@$channel - tsv::set console is_raw 0 - #do we really want to exec stty yet again to show final 'to' state? - #probably not. We should work out how to read the stty result flags and set a result.. or just limit from,to to showing echo and lineedit states. - return [list stdin [list from "[set previous_stty_state_$channel]" to "" note "fixme - to state not shown"]] - } else { - error "punk::console::disableRaw Unable to use twapi or stty to unset raw mode - aborting" - } - } - - } lappend PUNKARGS [list { @@ -1803,7 +1852,10 @@ namespace eval punk::console { #don't set ansi_avaliable here - we want to be able to change things, retest etc. if {"windows" eq "$::tcl_platform(platform)"} { if {[package provide twapi] ne ""} { - set h_out [twapi::get_console_handle stdout] + if {[catch {twapi::get_console_handle stdout} h_out]} { + puts stderr "test_can_ansi: twapi cannot get console handle for stdout" + return 0 + } set existing_mode [twapi::GetConsoleMode $h_out] if {[expr {$existing_mode & 4}]} { #virtual terminal processing happens to be enabled - so it's supported diff --git a/src/modules/punk/libunknown-0.1.tm b/src/modules/punk/libunknown-0.1.tm index 3b5d35b0..f9dfaf56 100644 --- a/src/modules/punk/libunknown-0.1.tm +++ b/src/modules/punk/libunknown-0.1.tm @@ -80,16 +80,7 @@ tcl::namespace::eval punk::libunknown { "Experimental set of replacements for default 'package unknown' entries." }] - variable epoch - #if {![info exists epoch]} { - # set tmstate [dict create 0 {}] - # set pkgstate [dict create 0 {}] - # set tminfo [dict create current 0 epochs $tmstate] - # set pkginfo [dict create current 0 epochs $pkgstate] - - # set epoch [dict create tm $tminfo pkg $pkginfo] - #} - + variable epoch ;#don't set - can be pre-set cooperatively variable has_package_files if {[catch {package files foobaz}]} { @@ -111,6 +102,33 @@ tcl::namespace::eval punk::libunknown { #will use standard mechanism for non zipfs paths in the tm list. proc zipfs_tm_UnknownHandler {original name args} { + #------------------------------ + #shortcircuit for builtin static libraries which have no 'package provide' info - review + #This occurs for example when running 'bin\runtime.cmd run src\make.tcl shell' with punk902z.exe + # + #------------------------------ + set loaded [lsearch -inline -index 1 -nocase [info loaded] $name] + if {[llength $loaded] == 2 && [lindex $loaded 0] eq ""} { + lassign $loaded _ cased_name + interp create ptest + ptest eval [list load {} $cased_name] + set static_version [ptest eval [list package provide [string tolower $cased_name]]] + set pname [string tolower $cased_name] + if {$static_version eq ""} { + set static_version [ptest eval [list package provide $cased_name]] + set pname $cased_name + } + if {$static_version ne ""} { + if {[package vsatisfies $static_version {*}$args]} { + package ifneeded $pname $static_version [list load {} $cased_name] + interp delete ptest + return + } + } + interp delete ptest + } + #------------------------------ + # Import the list of paths to search for packages in module form. # Import the pattern used to check package names in detail. variable epoch @@ -1161,7 +1179,12 @@ tcl::namespace::eval punk::libunknown { set callerposn [lsearch $args -caller] if {$callerposn > -1} { set caller [lindex $args $callerposn+1] - #puts stderr "\x1b\[1\;33m punk::libunknown::init - caller:$caller\x1b\[m" + if {[package provide thread] ne ""} { + set tid [thread::id] + } else { + set tid "-" + } + #puts stderr "\x1b\[1\;33m punk::libunknown::init - caller:$caller tid:$tid\x1b\[m" #puts stderr "punk::libunknown::init auto_path : $::auto_path" #puts stderr "punk::libunknown::init tcl::tm::list: [tcl::tm::list]" } @@ -1184,17 +1207,17 @@ tcl::namespace::eval punk::libunknown { puts stderr "punk::libunknown::init - init while empty/unreadable tcl::tm::list and empty/unreadable ::auto_path" } - if {[namespace origin ::package] eq "::punk::libunknown::package"} { - #This is far from conclusive - there may be other renamers (e.g commandstack) + if {[info commands ::punk::libunknown::package] ne ""} { + puts stderr "punk::libunknown::init already done - unnecessary call? info frame -1: [info frame -1]" return } + #if {[namespace origin ::package] eq "::punk::libunknown::package"} { + # #This is far from conclusive - there may be other renamers (e.g commandstack) + # return + #} - if {[info commands ::punk::libunknown::package] ne ""} { - puts stderr "punk::libunknown::init already done - unnecessary call? info frame -1: [info frame -1]" - return - } variable epoch if {![info exists epoch]} { set tmstate [dict create 0 {added {}}] @@ -1222,6 +1245,7 @@ tcl::namespace::eval punk::libunknown { # or suffer additional scans.. or document ?? #ideally init should be called in each interp before any scans for packages so that the list of untracked is minimized. set pkgnames [package names] + #puts stderr "####### punk::libunknown init called with [llength $pkgnames] package names known" foreach p $pkgnames { if {[string tolower $p] in {punk::libunknown tcl::zlib tcloo tcl::oo tcl}} { continue diff --git a/src/modules/punk/mix/templates/utility/scriptappwrappers/multishell.cmd b/src/modules/punk/mix/templates/utility/scriptappwrappers/multishell.cmd index 23e2b8db..3bf8e0b1 100644 --- a/src/modules/punk/mix/templates/utility/scriptappwrappers/multishell.cmd +++ b/src/modules/punk/mix/templates/utility/scriptappwrappers/multishell.cmd @@ -1345,8 +1345,33 @@ if 0 { # -- unbalanced braces { } here *even in comments* will cause problems if there was no Tcl exit or return above # -- custom script should generally go below the begin_powershell_payload line # ## ### ### ### ### ### ### ### ### ### ### ### ### ### -function GetScriptName { $myInvocation.ScriptName } -$scriptname = GetScriptName +#$MyInvocation.ScriptName should probably be considered deprecated +# https://stackoverflow.com/questions/78511229/how-can-i-choose-between-myinvocation-scriptname-and-myinvocation-pscommandpat +$runningscriptname = $PSCommandPath +if (-not $MyInvocation.PSCommandPath) { + $callingscriptname = '' +} else { + $callingscriptname = $MyInvocation.PSCommandPath +} +#The problem with psmodulepath +#https://github.com/PowerShell/PowerShell/issues/18108 +# psmodulepath is shared by powershell and pwsh despite not all ps modules being compatible. +# It is futzed with by powershell/pwsh based on detecting the child process type. +# a psmodulepath that has been futzed with by pwsh will not work for a child powershell 5 process that isn't launched directly +#This is inherently unfriendly to situations where an intervening process may be something else such as cmd.exe,tcl,perl etc +# nevertheless, powershell/pwsh maintainers seem to have taken the MS-centric view of the world that such situations don't exist :/ +# +#symptoms of these shenannigans not working include things like Get-FileHash failing in powershell desktop +# +#We don't know if the original console was pwsh/powershell or cmd.exe, and we need to potentially divert to powershell 5 (desktop) +#via tcl or perl etc - or cmd.exe +if ($PSVersionTable.PSVersion.Major -le 5) { + # For Windows PowerShell, we want to remove any PowerShell 7 paths from PSModulePath + #snipped from https://github.com/PowerShell/DSC/pull/777/commits/af9b99a4d38e0cf1e54c4bbd89cbb6a8a8598c4e + #Presumably users are supposed to know not to have custom paths for powershell desktop containing a 'powershell' subfolder?? + $env:PSModulePath = ($env:PSModulePath -split ';' | Where-Object { $_ -notlike '*\powershell\*' }) -join ';' +} + function GetDynamicParamDictionary { [CmdletBinding()] param( @@ -1421,11 +1446,11 @@ function GetDynamicParamDictionary { #} #psmain @args #"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host -"Script Name : {0}" -f $scriptname | write-host +#"Running Script Name : {0}" -f $runningscriptname | write-host "Powershell Version: {0}" -f $PSVersionTable.PSVersion.Major | write-host -"powershell args : {0}" -f ($args -join ", ") | write-host +#"powershell args : {0}" -f ($args -join ", ") | write-host # -- --- --- --- -$thisfileContent = Get-Content $scriptname -Raw +$thisfileContent = Get-Content $runningscriptname -Raw $startTag = ": <>" $endTag = ": <>" $pattern = "(?s)`n$startTag[^`n]*`n(.*?)`n$endTag" @@ -1524,7 +1549,7 @@ if ($match.Success) { } if (-not (("pwsh", "powershell", "") -contains $nextshell_type)) { #nextshell diversion exists for this platform - write-host "os: $os pwsh/powershell launching subshell of type: $nextshell_type shellpath: $nextshell_path on script $scriptname" + write-host "os: $os pwsh/powershell launching subshell of type: $nextshell_type shellpath: $nextshell_path on script $runningscriptname" # $arguments = @($($MyInvocation.MyCommand.Path)) # $arguments += $args @@ -1532,7 +1557,7 @@ if ($match.Success) { # $process = (Start-Process -FilePath $nextshell_path -ArgumentList $arguments -NoNewWindow -Wait) # Exit $process.ExitCode - & $nextshell_path $scriptname $args + & $nextshell_path $runningscriptname $args exit $LASTEXITCODE } } diff --git a/src/modules/punk/repl-999999.0a1.0.tm b/src/modules/punk/repl-999999.0a1.0.tm index a8a5afe8..2ccca79d 100644 --- a/src/modules/punk/repl-999999.0a1.0.tm +++ b/src/modules/punk/repl-999999.0a1.0.tm @@ -20,18 +20,6 @@ if {[dict exists $stdin_info -mode]} { #give up for now set tcl_interactive 1 -#if {[info commands ::tcl::zipfs::root] ne ""} { -# set zr [::tcl::zipfs::root] -# if {[file join $zr app modules] in [tcl::tm::list]} { -# #todo - better way to find latest version - without package require -# set lib [file join $zr app modules punk libunknown.tm] -# if {[file exists $lib]} { -# source $lib -# punk::libunknown::init -# #package unknown {punk::libunknown::zipfs_tm_UnknownHandler punk::libunknown::zipfs_tclPkgUnknown} -# } -# } -#} #------------------------------------------------------------------------------------- if {[package provide punk::libunknown] eq ""} { #maintenance - also in src/vfs/_config/punk_main.tcl @@ -59,7 +47,7 @@ if {[package provide punk::libunknown] eq ""} { } if {$libunknown ne ""} { source $libunknown - if {[catch {punk::libunknown::init -caller repl} errM]} { + if {[catch {punk::libunknown::init -caller triggered_by_repl_package_require} errM]} { puts "error initialising punk::libunknown\n$errM" } } @@ -525,11 +513,11 @@ proc repl::start {inchan args} { set donevalue [set [namespace current]::done] if {[lindex $donevalue 0] eq "quit"} { puts "-->repl::start end $inchan $args result:'$donevalue'" - puts stderr "--> returning [lindex $donevalue 1]" + #puts stderr "repl quit --> returning [lindex $donevalue 1]" return [lindex $donevalue 1] } puts "-->repl::start end $inchan $args result:'$donevalue'" - puts stderr "__> returning 0" + #puts stderr "__> returning 0" return 0 } proc repl::post_operations {} { @@ -1408,7 +1396,6 @@ proc repl::repl_handler {inputchan prompt_config} { if {[dict get $original_input_conf -inputmode] eq "raw"} { #user or script has apparently put stdin into raw mode - update punk::console::is_raw to match set rawmode 1 - #set ::punk::console::is_raw 1 tsv::set console is_raw 1 } else { #set ::punk::console::is_raw 0 @@ -1420,9 +1407,6 @@ proc repl::repl_handler {inputchan prompt_config} { #if it's been set to raw - assume it is deliberately done this way as the user could have alternatively called punk::mode raw or punk::console::enableVirtualTerminal #by not doing this automatically - we assume the caller has a reason. } else { - #JMN FIX! - #this returns 0 in rawmode on 8.6 after repl thread changes - #set rawmode [set ::punk::console::is_raw] set rawmode [tsv::get console is_raw] } @@ -1811,8 +1795,6 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config set infoprompt [dict get $prompt_config infoprompt] set debugprompt [dict get $prompt_config debugprompt] - - #set rawmode [set ::punk::console::is_raw] set rawmode [tsv::get console is_raw] if {!$rawmode} { #puts stderr "-->got [ansistring VIEW -lf 1 $stdinlines]<--" @@ -2615,6 +2597,34 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config } #editbuf + + #after any external command - raw mode as the console sees it can be disabled + #set it to match current state of the tsv + if {[tsv::get console is_raw]} { + if {$::tcl_platform(platform) eq "windows"} { + #review + #we are in parent process - twapi might not be loaded here - even if it is in the code interp + catch {package require twapi} + } + set sinfo [chan configure stdin] + if {[dict exists $sinfo -inputmode]} { + if {[dict get $sinfo -inputmode] ne "raw"} { + set re_enable_required 1 + } else { + set re_enable_required 0 + } + } else { + # -inputmode unavailable + #tcl 8.6 doesn't have -inputmode - meaning it has to call punk:console::enableRaw each time + #enableRaw on windows without twapi involves launching a pwsh process - which gives a noticeable lag in keyboard input. + #enableRaw on Unix involves a call to stty - which is generally fast - but still to be avoided if not required. + set re_enable_required 1 + } + #puts stderr "-here- re-enabling raw" + if {$re_enable_required} { + punk::console::enableRaw + } + } } else { #append commandstr \n if {$::punk::repl::signal_control_c} { @@ -2828,7 +2838,7 @@ namespace eval repl { } if {$libunknown ne ""} { source $libunknown - if {[catch {punk::libunknown::init -caller "repl init_script"} errM]} { + if {[catch {punk::libunknown::init -caller "repl::init init_script parent interp"} errM]} { puts "repl::init problem - error initialising punk::libunknown\n$errM" } #package require punk::lib @@ -2858,10 +2868,10 @@ namespace eval repl { #thread::send to caller defined interp targets (reference?) #snit required for icomm if {[catch {package require snit} errM]} { - puts stdout "punk::repl::initscript lib load fail ---snit $errM" + #puts stdout "punk::repl::initscript: lib load fail ---snit $errM" } if {[catch {package require punk::icomm} errM]} { - puts stdout "punk::repl::initscript lib load fail ---icomm $errM" + #puts stdout "punk::repl::initscript: lib load fail ---icomm $errM" } #----- @@ -2872,7 +2882,7 @@ namespace eval repl { #first use can raise error being a version number e.g 0.1.0 - why? lassign [tcl::chan::fifo2] ::punk::repl::codethread::repltalk replside } errMsg]} { - puts stdout "punk::repl::initscript tcl::chan::fifo2 error: $errM" + puts stdout "punk::repl::initscript tcl::chan::fifo2 error: $errMsg" } else { #experimental? #puts stdout "transferring chan $replside to thread %replthread%" @@ -3519,6 +3529,8 @@ namespace eval repl { #----------------------------------------------------------------------------- if {[package provide punk::libunknown] eq ""} { + namespace eval ::punk::libunknown {} + set ::punk::libunknown::epoch %lib_epoch% set libunks [list] foreach tm_path [tcl::tm::list] { set punkdir [file join $tm_path punk] @@ -3543,7 +3555,7 @@ namespace eval repl { } if {$libunknown ne ""} { source $libunknown - if {[catch {punk::libunknown::init -caller "repl init_script punk"} errM]} { + if {[catch {punk::libunknown::init -caller "repl::init init_script code interp for punk"} errM]} { puts "error initialising punk::libunknown\n$errM" } } diff --git a/src/project_layouts/custom/_project/punk.basic/src/make.tcl b/src/project_layouts/custom/_project/punk.basic/src/make.tcl index 1736d3d9..c1d3f906 100644 --- a/src/project_layouts/custom/_project/punk.basic/src/make.tcl +++ b/src/project_layouts/custom/_project/punk.basic/src/make.tcl @@ -31,22 +31,28 @@ namespace eval ::punkboot::lib { #for some purposes (whether a source folder is likely to have any useful content) we are interested in non dotfile/dotfolder immediate contents of a folder, but not whether a particular platform #considers them hidden or not. proc folder_nondotted_children {folder} { - if {![file isdirectory $folder]} {error "punkboot::lib::folder_nondotted_children error. Supplied folder '$folder' is not a directory"} - set contents [glob -nocomplain -dir $folder *] + set normfolder [file normalize $folder] + if {![file isdirectory $normfolder]} {error "punkboot::lib::folder_nondotted_children error. Supplied folder '$folder' is not a directory"} + set contents [glob -nocomplain -dir $folder -tails *] #some platforms (windows) return dotted entries with *, although most don't - return [lsearch -all -inline -not $contents .*] + set nondotted_tails [lsearch -all -inline -not $contents .*] + return [lmap ftail $nondotted_tails {file join $folder $ftail}] } proc folder_nondotted_folders {folder} { - if {![file isdirectory $folder]} {error "punkboot::lib::folder_nondotted_folders error. Supplied folder '$folder' is not a directory"} - set contents [glob -nocomplain -dir $folder -types d *] + set normfolder [file normalize $folder] + if {![file isdirectory $normfolder]} {error "punkboot::lib::folder_nondotted_folders error. Supplied folder '$folder' is not a directory"} + set contents [glob -nocomplain -dir $folder -types d -tails *] #some platforms (windows) return dotted entries with *, although most don't - return [lsearch -all -inline -not $contents .*] + set nondotted_tails [lsearch -all -inline -not $contents .*] + return [lmap ftail $nondotted_tails {file join $folder $ftail}] } proc folder_nondotted_files {folder} { - if {![file isdirectory $folder]} {error "punkboot::lib::folder_nondotted_files error. Supplied folder '$folder' is not a directory"} - set contents [glob -nocomplain -dir $folder -types f $folder *] + set normfolder [file normalize $folder] + if {![file isdirectory $normfolder]} {error "punkboot::lib::folder_nondotted_files error. Supplied folder '$folder' is not a directory"} + set contents [glob -nocomplain -dir $folder -types f $folder -tails *] #some platforms (windows) return dotted entries with *, although most don't - return [lsearch -all -inline -not $contents .*] + set nondotted_tails [lsearch -all -inline -not $contents .*] + return [lmap ftail $nondotted_tails {file join $folder $ftail}] } proc tm_version_isvalid {versionpart} { #Needs to be suitable for use with Tcl's 'package vcompare' @@ -289,6 +295,10 @@ if {"::try" ni [info commands ::try]} { # This allows a source update via 'fossil update' 'git pull' etc to pull in a minimal set of support modules for the boot script # and load these in preference to ones that may have been in the interp's tcl::tm::list or auto_path due to environment variables set startdir [pwd] + +set scriptdir [file dirname [file normalize [info script]]] +#puts "SCRIPTDIR: $scriptdir" + #we are focussed on pure-tcl libs/modules in bootsupport for now. #There may be cases where we want to use compiled packages from src/bootsupport/modules_tcl9 etc #REVIEW - punkboot can really speed up with appropriate accelerators and/or external binaries @@ -303,18 +313,22 @@ set bootsupport_library_paths [list] set this_platform_generic [punkboot::lib::platform_generic] #we always create these lists in order of desired precedence. # - this is the same order when adding to auto_path - but will need to be reversed when using tcl:tm::add -if {[file exists [file join $startdir src bootsupport]]} { - lappend bootsupport_module_paths [file join $startdir src bootsupport modules_tcl$::tclmajorv] ;#more version-specific modules slightly higher in precedence order - lappend bootsupport_module_paths [file join $startdir src bootsupport modules] - lappend bootsupport_library_paths [file join $startdir src bootsupport lib_tcl$::tclmajorv/allplatforms] ;#more version-specific pkgs slightly higher in precedence order - lappend bootsupport_library_paths [file join $startdir src bootsupport lib_tcl$::tclmajorv/$this_platform_generic] ;#more version-specific pkgs slightly higher in precedence order - lappend bootsupport_library_paths [file join $startdir src bootsupport lib] +if {[file exists [file join $scriptdir bootsupport]]} { + set bootsupportdir [file join $scriptdir bootsupport] + puts stderr "Using bootsupport dir $bootsupportdir" + + lappend bootsupport_module_paths [file join $bootsupportdir modules_tcl$::tclmajorv] ;#more version-specific modules slightly higher in precedence order + lappend bootsupport_module_paths [file join $bootsupportdir modules] + lappend bootsupport_library_paths [file join $bootsupportdir lib_tcl$::tclmajorv/allplatforms] ;#more version-specific pkgs slightly higher in precedence order + lappend bootsupport_library_paths [file join $bootsupportdir lib_tcl$::tclmajorv/$this_platform_generic] ;#more version-specific pkgs slightly higher in precedence order + lappend bootsupport_library_paths [file join $bootsupportdir lib] } else { - lappend bootsupport_module_paths [file join $startdir bootsupport modules_tcl$::tclmajorv] - lappend bootsupport_module_paths [file join $startdir bootsupport modules] - lappend bootsupport_library_paths [file join $startdir bootsupport lib_tcl$::tclmajorv/allplatforms] - lappend bootsupport_library_paths [file join $startdir bootsupport lib_tcl$::tclmajorv/$this_platform_generic] - lappend bootsupport_library_paths [file join $startdir bootsupport lib] + puts stderr "No bootsupport dir for script [info script] at [file join $scriptdir bootsupport]" + #lappend bootsupport_module_paths [file join $startdir bootsupport modules_tcl$::tclmajorv] + #lappend bootsupport_module_paths [file join $startdir bootsupport modules] + #lappend bootsupport_library_paths [file join $startdir bootsupport lib_tcl$::tclmajorv/allplatforms] + #lappend bootsupport_library_paths [file join $startdir bootsupport lib_tcl$::tclmajorv/$this_platform_generic] + #lappend bootsupport_library_paths [file join $startdir bootsupport lib] } set bootsupport_paths_exist 0 foreach p [list {*}$bootsupport_module_paths {*}$bootsupport_library_paths] { @@ -406,8 +420,8 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} { tcl::tm::add {*}[lreverse $bootsupport_module_paths] {*}[lreverse $sourcesupport_module_paths] ;#tm::add works like LIFO. sourcesupport_module_paths end up earliest in resulting tm list. set ::auto_path [list {*}$sourcesupport_library_paths {*}$bootsupport_library_paths] } - puts "----> auto_path $::auto_path" - puts "----> tcl::tm::list [tcl::tm::list]" + #puts "----> auto_path $::auto_path" + #puts "----> tcl::tm::list [tcl::tm::list]" #maint: also in punk::repl package #-------------------------------------------------------- @@ -435,22 +449,26 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} { } if {$libunknown ne ""} { source $libunknown - if {[catch {punk::libunknown::init -caller main.tcl} errM]} { - puts "error initialising punk::libunknown\n$errM" + if {[catch {punk::libunknown::init -caller make.tcl} errM]} { + puts stderr "error initialising punk::libunknown\n$errM" } + #puts stdout " *** [package names]" + #puts stdout " **** [dict get $::punk::libunknown::epoch pkg untracked]" + } else { + puts stderr "Failed to find punk::libunknown" } #-------------------------------------------------------- #package require Thread - puts "---->tcl_library [info library]" - puts "---->loaded [info loaded]" + #puts "---->tcl_library [info library]" + #puts "---->loaded [info loaded]" # - the full repl requires Threading and punk,shellfilter,shellrun to call and display properly. # tm list already indexed - need 'package forget' to find modules based on current tcl::tm::list #These are strong dependencies - package forget punk::mix - package forget punk::repo - package forget punkcheck + #package forget punk::mix + #package forget punk::repo + #package forget punkcheck package require punk::repo ;#todo - push our requirements to a smaller punk::repo::xxx package with minimal dependencies package require punk::mix @@ -464,6 +482,7 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} { set package_paths_modified 1 #------------------------------------------------------------------------------ + #puts "----> llength package names [llength [package names]]" } set ::punkboot::pkg_requirements_found [list] @@ -479,7 +498,9 @@ set ::punkboot::bootsupport_requirements [dict create\ punkcheck [list]\ fauxlink [list version "0.1.1-"]\ textblock [list version 0.1.1-]\ + fileutil [list]\ fileutil::traverse [list]\ + struct::list [list]\ md5 [list version 2-]\ ] @@ -1282,7 +1303,41 @@ proc ::punkboot::punkboot_gethelp {args} { return $h } + + + set scriptargs $::argv +punk::args::define { + @id -id punkmake + @cmd -name punkmake\ + -summary\ + "Project builder"\ + -help\ + "" + @form -form help + @leaders + subcommand -type "literal(help)" + @opts + @values + what -type string -choices {modules libs shell} + + @form -form modules + subcommand -type "literal(modules)" + + @form -form libs + subcommand -type "literal(libs)" + + @form -form shell + subcommand -type "literal(shell)" + arg -type any -optional 1 -multiple 1 +} +#set argd [punk::args::parse $scriptargs -form 0 withid punkmake] +##lassign [dict values $argd] leaders opts values received +# +#puts stdout [punk::args::usage -scheme nocolour punkmake] +#exit 1 + + set do_help 0 if {![llength $scriptargs]} { set do_help 1 @@ -1294,6 +1349,8 @@ if {![llength $scriptargs]} { } } } + + set commands_found [list] foreach a $scriptargs { if {![string match -* $a]} { @@ -1310,6 +1367,8 @@ if {[llength $commands_found] != 1 } { puts stderr "Unknown command: [lindex $commands_found 0]\n\n" set do_help 1 } + + if {$do_help} { puts stdout "Checking package availability..." set ::punkboot::pkg_availability [::punkboot::check_package_availability -quiet 1 $::punkboot::bootsupport_requirements] @@ -1325,6 +1384,8 @@ if {$do_help} { exit 0 } + + set ::punkboot::command [lindex $commands_found 0] @@ -1414,14 +1475,15 @@ if {$::punkboot::command eq "check"} { if {$package_paths_modified} { set tm_list_boot [tcl::tm::list] tcl::tm::remove {*}$tm_list_boot - foreach p [lreverse $original_tm_list] { + + set lower_prio [list] + foreach p $original_tm_list { if {$p ni $tm_list_boot} { - tcl::tm::add $p + lappend lower_prio $p } } - foreach p [lreverse $tm_list_boot] { - tcl::tm::add $p - } + tcl::tm::add {*}[lreverse $lower_prio] {*}[lreverse $tm_list_boot] + #set ::auto_path [list $bootsupport_lib {*}$original_auto_path] lappend ::auto_path {*}$original_auto_path } @@ -1489,6 +1551,28 @@ if {![array size A]} { punkboot::define_global_ansi } +#puts stderr ">>>>>>+ loaded:[info loaded]" +#puts stderr "llength package names: [llength [package names]]" +if {[info exists ::punk::libunknown::epoch]} { + set untracked [dict get $::punk::libunknown::epoch pkg untracked] + #puts stderr "punk::libunknown::epoch exists" +} else { + set untracked [list] + #puts stderr "punk::libunknown::epoch does not exist" +} +#REVIEW - we shouldn't need to manually set the untracked packages - punk::libunknown::init should have done it? +foreach p [package names] { + if {![dict exists $untracked $p]} { + dict set untracked $p "" + } +} +dict set ::punk::libunknown::epoch pkg untracked $untracked + +if {[package provide punk::libunknown] eq ""} { + puts "punk::libunknown not loaded" +} else { + puts "punk::libunknown loaded" +} dict for {pkg pkginfo} $::punkboot::bootsupport_requirements { set verspec [dict get $pkginfo version] ;#version wanted specification always exists and is empty or normalised if {[catch {package require $pkg {*}$verspec} errM]} { @@ -1557,14 +1641,13 @@ if {$::punkboot::command eq "info"} { if {$::punkboot::command eq "shell"} { - puts stderr ">>>>>> loaded:[info loaded]" + package require struct::list package require punk package require punk::repl #todo - make procs vars etc from this file available? puts stderr "punk boot shell not implemented - dropping into ordinary punk shell." - repl::init set replresult [repl::start stdin -title make.tcl] #review @@ -3059,6 +3142,8 @@ foreach vfstail $vfs_tails { exec {*}$::sdxpath unwrap [file rootname $building_runtime].tail ;#extracts to folder named [file rootname $building_runtime].vfs e.g build_tclkit9.0.2-win64-dyn.vfs #file rename to existing target dir would copy folder into target dir if {![file exists $targetvfs]} { + #delay + after 1000 file rename [file rootname $building_runtime].vfs $targetvfs } else { merge_over [file rootname $building_runtime].vfs $targetvfs diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/args-0.2.tm b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/args-0.2.tm index a6224c0d..7b6ee228 100644 --- a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/args-0.2.tm +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/args-0.2.tm @@ -3036,8 +3036,11 @@ tcl::namespace::eval punk::args { #This mechanism gets less-than-useful results for oo methods #e.g {$obj} proc Get_caller {} { + set depth [info level] + set maxd [expr {min($depth,4)}] + set call_level [expr {-1 * $maxd}] #set call_level -3 ;#for get_dict call - set call_level -4 + #set call_level -4 set cmdinfo [tcl::dict::get [tcl::info::frame $call_level] cmd] #puts "-->$cmdinfo" #puts "-->[tcl::info::frame -3]" diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/args/tclcore-0.1.0.tm b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/args/tclcore-0.1.0.tm index d016c70a..6a4cc626 100644 --- a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/args/tclcore-0.1.0.tm +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/args/tclcore-0.1.0.tm @@ -3498,7 +3498,7 @@ tcl::namespace::eval punk::args::tclcore { example, in ${$B}-dictionary${$N} mode, bigBoy sorts between bigbang and bigboy, and x10y sorts between x9y and x11y. Overrides the ${$B}-nocase${$N} option." -integer -type none -help\ - "Convert list elements to integers and use integer comparsion." + "Convert list elements to integers and use integer comparison." -real -type none -help\ "Convert list elements to floating-point values and use floating comparison." -command -type string -help\ diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/console-0.1.1.tm b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/console-0.1.1.tm index ea8d3f77..4d4518d3 100644 --- a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/console-0.1.1.tm +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/console-0.1.1.tm @@ -129,57 +129,362 @@ namespace eval punk::console { #e.g external utils system API's. namespace export * } - + if {"windows" eq $::tcl_platform(platform)} { #accept args for all dummy/load functions so we don't have to match/update argument signatures here + set has_twapi [expr {! [catch {package require twapi}]}] + + if {$has_twapi} { + #this is really enableAnsi *processing* + proc enableAnsi {} { + #output handle modes + #Enable virtual terminal processing (sometimes off in older windows terminals) + #ENABLE_PROCESSED_OUTPUT = 0x0001 + #ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002 + #ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 + #DISABLE_NEWLINE_AUTO_RETURN = 0x0008 + if {[catch {twapi::get_console_handle stdout} h_out]} { + puts stderr "enableAnsi failed: twapi cannot get console handle for stdout" + return + } - proc enableAnsi {args} { - #loopavoidancetoken (don't remove) - internal::define_windows_procs - internal::abort_if_loop - tailcall enableAnsi {*}$args - } - #review what raw mode means with regard to a specific channel vs terminal as a whole - proc enableRaw {args} { - #loopavoidancetoken (don't remove) - internal::define_windows_procs - internal::abort_if_loop - tailcall enableRaw {*}$args - } - proc disableRaw {args} { - #loopavoidancetoken (don't remove) - internal::define_windows_procs - internal::abort_if_loop - tailcall disableRaw {*}$args - } - proc enableVirtualTerminal {args} { - #loopavoidancetoken (don't remove) - internal::define_windows_procs - internal::abort_if_loop - tailcall enableVirtualTerminal {*}$args - } - proc disableVirtualTerminal {args} { - #loopavoidancetoken (don't remove) - internal::define_windows_procs - internal::abort_if_loop - tailcall disableVirtualTerminal {*}$args - } - set funcs [list disableAnsi enableProcessedInput disableProcessedInput] - foreach f $funcs { - proc $f {args} [string map [list %f% $f] { - set mybody [info body %f%] - internal::define_windows_procs - set newbody [info body %f%] - if {$newbody ne $mybody} { - tailcall %f% {*}$args + set oldmode_out [twapi::GetConsoleMode $h_out] + set newmode_out [expr {$oldmode_out | 4}] ;#don't enable processed output too, even though it's required. keep symmetrical with disableAnsi? + + twapi::SetConsoleMode $h_out $newmode_out + + #what does window_input have to do with it?? + #input handle modes + #ENABLE_PROCESSED_INPUT 0x0001 ;#set to zero will allow ctrl-c to be reported as keyboard input rather than as a signal + #ENABLE_LINE_INPUT 0x0002 + #ENABLE_ECHO_INPUT 0x0004 + #ENABLE_WINDOW_INPUT 0x0008 (default off when a terminal created) + #ENABLE_MOUSE_INPUT 0x0010 + #ENABLE_INSERT_MODE 0X0020 + #ENABLE_QUICK_EDIT_MODE 0x0040 + #ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200 (default off when a terminal created) (512) + set h_in [twapi::get_console_handle stdin] + set oldmode_in [twapi::GetConsoleMode $h_in] + set newmode_in [expr {$oldmode_in | 8}] + #set newmode_in [expr {$oldmode_in | 0x208}] + + twapi::SetConsoleMode $h_in $newmode_in + + return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]] + } + proc disableAnsi {} { + set h_out [twapi::get_console_handle stdout] + set oldmode_out [twapi::GetConsoleMode $h_out] + set newmode_out [expr {$oldmode_out & ~4}] + twapi::SetConsoleMode $h_out $newmode_out + + #??? review + set h_in [twapi::get_console_handle stdin] + set oldmode_in [twapi::GetConsoleMode $h_in] + set newmode_in [expr {$oldmode_in & ~8}] + twapi::SetConsoleMode $h_in $newmode_in + + + return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]] + } + proc enableVirtualTerminal {{channels {input output}}} { + set ins [list in input stdin] + set outs [list out output stdout stderr] + set known [concat $ins $outs both] + set directions [list] + foreach v $channels { + if {$v in $ins} { + lappend directions input + } elseif {$v in $outs} { + lappend directions output + } elseif {$v eq "both"} { + lappend directions input output + } + if {$v ni $known} { + error "enableVirtualTerminal expected channel values to be one of '$known'. (all values mapped to input and/or output)" + } + } + set channels $directions ;#don't worry about dups. + if {"both" in $channels} { + lappend channels input output + } + set result [dict create] + if {"output" in $channels} { + #note setting stdout makes stderr have the same settings - ie there is really only one output to configure + set h_out [twapi::get_console_handle stdout] + set oldmode [twapi::GetConsoleMode $h_out] + set newmode [expr {$oldmode | 4}] + twapi::SetConsoleMode $h_out $newmode + dict set result output [list from $oldmode to $newmode] + } + + if {"input" in $channels} { + set h_in [twapi::get_console_handle stdin] + set oldmode_in [twapi::GetConsoleMode $h_in] + set newmode_in [expr {$oldmode_in | 0x200}] + twapi::SetConsoleMode $h_in $newmode_in + dict set result input [list from $oldmode_in to $newmode_in] + } + + return $result + } + + proc disableVirtualTerminal {{channels {input output}}} { + set ins [list in input stdin] + set outs [list out output stdout stderr] + set known [concat $ins $outs both] + set directions [list] + foreach v $channels { + if {$v in $ins} { + lappend directions input + } elseif {$v in $outs} { + lappend directions output + } elseif {$v eq "both"} { + lappend directions input output + } + if {$v ni $known} { + error "disableVirtualTerminal expected channel values to be one of '$known'. (all values mapped to input and/or output)" + } + } + set channels $directions ;#don't worry about dups. + if {"both" in $channels} { + lappend channels input output + } + set result [dict create] + if {"output" in $channels} { + #as above - configuring stdout does stderr too + set h_out [twapi::get_console_handle stdout] + set oldmode [twapi::GetConsoleMode $h_out] + set newmode [expr {$oldmode & ~4}] + twapi::SetConsoleMode $h_out $newmode + dict set result output [list from $oldmode to $newmode] + } + if {"input" in $channels} { + set h_in [twapi::get_console_handle stdin] + set oldmode_in [twapi::GetConsoleMode $h_in] + set newmode_in [expr {$oldmode_in & ~0x200}] + twapi::SetConsoleMode $h_in $newmode_in + dict set result input [list from $oldmode_in to $newmode_in] + } + + #return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]] + return $result + } + proc enableProcessedInput {} { + set h_in [twapi::get_console_handle stdin] + set oldmode_in [twapi::GetConsoleMode $h_in] + set newmode_in [expr {$oldmode_in | 1}] + twapi::SetConsoleMode $h_in $newmode_in + return [list stdin [list from $oldmode_in to $newmode_in]] + } + proc disableProcessedInput {} { + set h_in [twapi::get_console_handle stdin] + set oldmode_in [twapi::GetConsoleMode $h_in] + set newmode_in [expr {$oldmode_in & ~1}] + twapi::SetConsoleMode $h_in $newmode_in + return [list stdin [list from $oldmode_in to $newmode_in]] + } + proc enableRaw {{channel stdin}} { + #variable is_raw + variable previous_stty_state_$channel + + if {[catch {twapi::get_console_handle stdin} console_handle]} { + puts stderr "enableRaw error: twapi cannot get console handle for stdin" + #review. If twapi couldn't get a console handle - no point trying other mechanisms(?) + return + } + #returns dictionary + #e.g -processedinput 1 -lineinput 1 -echoinput 1 -windowinput 0 -mouseinput 0 -insertmode 1 -quickeditmode 1 -extendedmode 1 -autoposition 0 + set oldmode [twapi::get_console_input_mode] + twapi::modify_console_input_mode $console_handle -lineinput 0 -echoinput 0 + # Turn off the echo and line-editing bits + #set newmode [dict merge $oldmode [dict create -lineinput 0 -echoinput 0]] + set newmode [twapi::get_console_input_mode] + + tsv::set console is_raw 1 + #don't disable handler - it will detect is_raw + ### twapi::set_console_control_handler {} + return [list stdin [list from $oldmode to $newmode]] + } + + #note: twapi GetStdHandle & GetConsoleMode & SetConsoleCombo unreliable - fails with invalid handle (somewhat intermittent.. after stdin reopened?) + #could be we were missing a step in reopening stdin and console configuration? + + proc disableRaw {{channel stdin}} { + #variable is_raw + variable previous_stty_state_$channel + set ch_state [chan conf $channel] + if {[dict exists $ch_state -inputmode]} { + chan conf $channel -inputmode normal + tsv::set console is_raw 0 + return [list $channel [list from [dict get $ch_state -inputmode] to normal]] } else { - #error vs noop? - puts stderr "Unable to set implementation for %f% - check twapi?" + if {[catch {twapi::get_console_handle stdin} console_handle]} { + #e.g tkcon/wish + puts stderr "disableRaw error: twapi cannot get console handle for stdin" + return ;# ??? + } + set oldmode [twapi::get_console_input_mode] + # Turn on the echo and line-editing bits + twapi::modify_console_input_mode $console_handle -lineinput 1 -echoinput 1 + set newmode [twapi::get_console_input_mode] + tsv::set console is_raw 0 + return [list stdin [list from $oldmode to $newmode]] } - }] + } + + } else { + + variable ps_consolemode_pid + variable ps_consolemode_contents + variable ps_pipename + if {![info exists ps_consolemode_contents]} { + #start persistent powershell consolemode_server.ps1 named pipe server + if {$::argv0 ne ""} { + set pstooldir [file dirname [file dirname [file normalize $::argv0]]]/scriptlib/utils/pwsh + } else { + set pstooldir [pwd] + } + #set ps_script $pstooldir/consolemode_server.ps1 + set ps_script $pstooldir/consolemode_server_async.ps1 + if {[file exists $ps_script]} { + set fd [open $ps_script r] + chan configure $fd -translation binary + set ps_consoleid [pid]-[expr {int(999 * rand())+1}] + set ps_consolemode_contents [string map [list "" $ps_consoleid] [read $fd]] + close $fd + #set ps_consolemode_pipe [twapi::namedpipe_client {//./pipe/punkshell_ps_consolemode} -access write] + #set ps_cmd [auto_execok pwsh.exe] + set ps_cmd [auto_execok pwsh.exe] + if {$ps_cmd eq ""} { + set ps_cmd [auto_execok powershell.exe] + } + if {$ps_cmd ne ""} { + set ps_consolemode_pid [exec {*}$ps_cmd -nop -nol -c $ps_consolemode_contents &] + set ps_pipename {\\.\pipe\punkshell_ps_consolemode_} + append ps_pipename $ps_consoleid + puts stderr "twapi not present, using persistent powershell process: pipename: $ps_pipename pid: $ps_consolemode_pid" + #todo - taskkill /F /PID $ps_consolemode_pid + #when? + #review + #if {[catch {puts "pidinfo: [::tcl::process::status $ps_consolemode_pid]"} errM]} { + # puts stderr "--- failed to get process status for $ps_consolemode_pid\n$errM" + #} + #set p [open {\\.\pipe\punkshell_ps_consolemode} w] + #chan conf $p -buffering none -blocking 1 + #puts $p "" + #close $p + } + } + + } + + + #enableRaw + proc enableRaw {{channel stdin}} { + #puts stderr "punk::console::enableRaw" + #variable is_raw + variable previous_stty_state_$channel + variable ps_consolemode_contents + variable ps_pipename + + + if {[info exists ps_consolemode_contents]} { + #ps_pipename e.g \\.\pipe\punkwinshell_ps_consolemode_12345-1223456 + + set trynum 0 + set wrote 0 + while {$trynum < 5} { + incr trynum + if {![catch { + set pipe [open $ps_pipename w] + } errMsg]} { + chan conf $pipe -buffering line + puts -nonewline $pipe "enableraw\r\n" + #flush $pipe + #after 10 + #close $pipe + set wrote 1 + break + } else { + after 100 + } + } + if {$wrote} { + tsv::set console is_raw 1 + after 100 + close $pipe + } else { + puts stderr "write to $ps_pipename failed trynum: $trynum\n$errMsg" + } + } elseif {[set sttycmd [auto_execok stty]] ne ""} { + #todo - something else entirely + #this approach does not work on windows + #the msys/cygwin stty command is launched as a subprocess - can be used to retrieve info + # but seems to be useless as far as affecting the calling process/console + if {[set previous_stty_state_$channel] eq ""} { + set previous_stty_state_$channel [exec {*}$sttycmd -g <@$channel] + } + + exec {*}$sttycmd raw -echo <@$channel + tsv::set console is_raw 1 + #review - inconsistent return dict + return [dict create stdin [list from [set previous_stty_state_$channel] to "" note "fixme - to state not shown"]] + } else { + error "punk::console::enableRaw Unable to use twapi or stty to set raw mode - aborting" + } + } + + + proc disableRaw {{channel stdin}} { + variable previous_stty_state_$channel + set ch_state [chan conf $channel] + if {[dict exists $ch_state -inputmode]} { + chan conf $channel -inputmode normal + tsv::set console is_raw 0 + return [list $channel [list from [dict get $ch_state -inputmode] to normal]] + } else { + #tcl <= 8.6x doesn't support -inputmode + if {[set sttycmd [auto_execok stty]] ne ""} { + #this doesn't work on windows + #It may seem to - only because running *any* external utility can exit raw mode + set sttycmd [auto_execok stty] + if {[set previous_stty_state_$channel] ne ""} { + exec {*}$sttycmd [set previous_stty_state_$channel] + set previous_stty_state_$channel "" + return restored + } + exec {*}$sttycmd -raw echo <@$channel + tsv::set console is_raw 0 + #do we really want to exec stty yet again to show final 'to' state? + #probably not. We should work out how to read the stty result flags and set a result.. or just limit from,to to showing echo and lineedit states. + return [list stdin [list from "[set previous_stty_state_$channel]" to "" note "fixme - to state not shown"]] + } else { + error "punk::console::disableRaw Unable to use twapi or stty to unset raw mode - aborting" + } + } + } + + #enableAnsi + proc enableAnsi {} { + } + #disableAnsi + proc enableAnsi {} { + } + #enableVirtualTerminal + proc enableVirtualTerminal {{channels {input output}}} { + } + #disableVirtualTerminal + proc disableVirtualTerminal {{channels {input output}}} { + } + #enableProcessedInput + #disableProcessedInput + } } else { + #non-windows platforms + proc enableAnsi {} { #todo? } @@ -190,6 +495,13 @@ namespace eval punk::console { #todo - something better - the 'channel' concept may not really apply on unix, as raw mode is set for input and output modes currently - only valid to set on a readable channel? #on windows they can be set independently (but not with stty) - REVIEW + proc enableVirtualTerminal {{channels {input output}}} { + + } + proc disableVirtualTerminal {args} { + + } + #NOTE - the is_raw is only being set in current interp - but the channel is shared. #this is problematic with the repl thread being separate. - must be a tsv? REVIEW proc enableRaw {{channel stdin}} { @@ -221,12 +533,6 @@ namespace eval punk::console { tsv::set console is_raw 0 return done } - proc enableVirtualTerminal {{channels {input output}}} { - - } - proc disableVirtualTerminal {args} { - - } } #review - document and decide granularity required. should we enable/disable more than one at once? @@ -257,7 +563,6 @@ namespace eval punk::console { #puts -nonewline stdout \x1b\[?25l ;#hide cursor puts -nonewline stdout \x1b\[?1003h\n enable_bracketed_paste - } #todo stop_application_mode {} {} @@ -313,266 +618,10 @@ namespace eval punk::console { } } proc define_windows_procs {} { - package require zzzload - set loadstate [zzzload::pkg_require twapi] - - #loadstate could also be stuck on loading? - review - zzzload not very ripe - #Twapi can be relatively slow to load (on some systems) - can be 1s plus in some cases - and much longer if there are disk performance issues. - if {$loadstate ni [list failed]} { - #possibly still 'loading' - #review zzzload usage - #puts stdout "=========== console loading twapi =============" - set loadstate [zzzload::pkg_wait twapi] ;#can return 'failed' will return version if already loaded or loaded during wait - } - - if {$loadstate ni [list failed]} { - package require twapi ;#should be fast once twapi dll loaded in zzzload thread - set ::punk::console::has_twapi 1 - - #todo - move some of these to the punk::console::local sub-namespace - as they use APIs rather than in-band ANSI to do their work. - #enableAnsi seems like it should be directly under punk::console .. but then it seems inconsistent if other local console-mode setting functions aren't. - #Find a compromise to organise things somewhat sensibly.. - - #this is really enableAnsi *processing* - proc [namespace parent]::enableAnsi {} { - #output handle modes - #Enable virtual terminal processing (sometimes off in older windows terminals) - #ENABLE_PROCESSED_OUTPUT = 0x0001 - #ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002 - #ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 - #DISABLE_NEWLINE_AUTO_RETURN = 0x0008 - set h_out [twapi::get_console_handle stdout] - set oldmode_out [twapi::GetConsoleMode $h_out] - set newmode_out [expr {$oldmode_out | 4}] ;#don't enable processed output too, even though it's required. keep symmetrical with disableAnsi? - - twapi::SetConsoleMode $h_out $newmode_out - - #what does window_input have to do with it?? - #input handle modes - #ENABLE_PROCESSED_INPUT 0x0001 ;#set to zero will allow ctrl-c to be reported as keyboard input rather than as a signal - #ENABLE_LINE_INPUT 0x0002 - #ENABLE_ECHO_INPUT 0x0004 - #ENABLE_WINDOW_INPUT 0x0008 (default off when a terminal created) - #ENABLE_MOUSE_INPUT 0x0010 - #ENABLE_INSERT_MODE 0X0020 - #ENABLE_QUICK_EDIT_MODE 0x0040 - #ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200 (default off when a terminal created) (512) - set h_in [twapi::get_console_handle stdin] - set oldmode_in [twapi::GetConsoleMode $h_in] - set newmode_in [expr {$oldmode_in | 8}] - #set newmode_in [expr {$oldmode_in | 0x208}] - - twapi::SetConsoleMode $h_in $newmode_in - - return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]] - } - proc [namespace parent]::disableAnsi {} { - set h_out [twapi::get_console_handle stdout] - set oldmode_out [twapi::GetConsoleMode $h_out] - set newmode_out [expr {$oldmode_out & ~4}] - twapi::SetConsoleMode $h_out $newmode_out - #??? review - set h_in [twapi::get_console_handle stdin] - set oldmode_in [twapi::GetConsoleMode $h_in] - set newmode_in [expr {$oldmode_in & ~8}] - twapi::SetConsoleMode $h_in $newmode_in - - - return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]] - } - - # - proc [namespace parent]::enableVirtualTerminal {{channels {input output}}} { - set ins [list in input stdin] - set outs [list out output stdout stderr] - set known [concat $ins $outs both] - set directions [list] - foreach v $channels { - if {$v in $ins} { - lappend directions input - } elseif {$v in $outs} { - lappend directions output - } elseif {$v eq "both"} { - lappend directions input output - } - if {$v ni $known} { - error "enableVirtualTerminal expected channel values to be one of '$known'. (all values mapped to input and/or output)" - } - } - set channels $directions ;#don't worry about dups. - if {"both" in $channels} { - lappend channels input output - } - set result [dict create] - if {"output" in $channels} { - #note setting stdout makes stderr have the same settings - ie there is really only one output to configure - set h_out [twapi::get_console_handle stdout] - set oldmode [twapi::GetConsoleMode $h_out] - set newmode [expr {$oldmode | 4}] - twapi::SetConsoleMode $h_out $newmode - dict set result output [list from $oldmode to $newmode] - } - - if {"input" in $channels} { - set h_in [twapi::get_console_handle stdin] - set oldmode_in [twapi::GetConsoleMode $h_in] - set newmode_in [expr {$oldmode_in | 0x200}] - twapi::SetConsoleMode $h_in $newmode_in - dict set result input [list from $oldmode_in to $newmode_in] - } - - return $result - } - proc [namespace parent]::disableVirtualTerminal {{channels {input output}}} { - set ins [list in input stdin] - set outs [list out output stdout stderr] - set known [concat $ins $outs both] - set directions [list] - foreach v $channels { - if {$v in $ins} { - lappend directions input - } elseif {$v in $outs} { - lappend directions output - } elseif {$v eq "both"} { - lappend directions input output - } - if {$v ni $known} { - error "disableVirtualTerminal expected channel values to be one of '$known'. (all values mapped to input and/or output)" - } - } - set channels $directions ;#don't worry about dups. - if {"both" in $channels} { - lappend channels input output - } - set result [dict create] - if {"output" in $channels} { - #as above - configuring stdout does stderr too - set h_out [twapi::get_console_handle stdout] - set oldmode [twapi::GetConsoleMode $h_out] - set newmode [expr {$oldmode & ~4}] - twapi::SetConsoleMode $h_out $newmode - dict set result output [list from $oldmode to $newmode] - } - if {"input" in $channels} { - set h_in [twapi::get_console_handle stdin] - set oldmode_in [twapi::GetConsoleMode $h_in] - set newmode_in [expr {$oldmode_in & ~0x200}] - twapi::SetConsoleMode $h_in $newmode_in - dict set result input [list from $oldmode_in to $newmode_in] - } - - #return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]] - return $result - } - - proc [namespace parent]::enableProcessedInput {} { - set h_in [twapi::get_console_handle stdin] - set oldmode_in [twapi::GetConsoleMode $h_in] - set newmode_in [expr {$oldmode_in | 1}] - twapi::SetConsoleMode $h_in $newmode_in - return [list stdin [list from $oldmode_in to $newmode_in]] - } - proc [namespace parent]::disableProcessedInput {} { - set h_in [twapi::get_console_handle stdin] - set oldmode_in [twapi::GetConsoleMode $h_in] - set newmode_in [expr {$oldmode_in & ~1}] - twapi::SetConsoleMode $h_in $newmode_in - return [list stdin [list from $oldmode_in to $newmode_in]] - } - } else { - - puts stderr "punk::console falling back to stty because twapi load failed" - proc [namespace parent]::enableAnsi {} { - puts stderr "punk::console::enableAnsi todo" - } - proc [namespace parent]::disableAnsi {} { - } - #? - proc [namespace parent]::enableVirtualTerminal {{channels {input output}}} { - } - proc [namespace parent]::disableVirtualTerminal {{channels {input output}}} { - } - proc [namespace parent]::enableProcessedInput {args} { - - } - proc [namespace parent]::disableProcessedInput {args} { - - } - - } - - proc [namespace parent]::enableRaw {{channel stdin}} { - #variable is_raw - variable previous_stty_state_$channel - - if {[package provide twapi] ne ""} { - set console_handle [twapi::get_console_handle stdin] - #returns dictionary - #e.g -processedinput 1 -lineinput 1 -echoinput 1 -windowinput 0 -mouseinput 0 -insertmode 1 -quickeditmode 1 -extendedmode 1 -autoposition 0 - set oldmode [twapi::get_console_input_mode] - twapi::modify_console_input_mode $console_handle -lineinput 0 -echoinput 0 - # Turn off the echo and line-editing bits - #set newmode [dict merge $oldmode [dict create -lineinput 0 -echoinput 0]] - set newmode [twapi::get_console_input_mode] - - tsv::set console is_raw 1 - #don't disable handler - it will detect is_raw - ### twapi::set_console_control_handler {} - return [list stdin [list from $oldmode to $newmode]] - } elseif {[set sttycmd [auto_execok stty]] ne ""} { - if {[set previous_stty_state_$channel] eq ""} { - set previous_stty_state_$channel [exec {*}$sttycmd -g <@$channel] - } - - exec {*}$sttycmd raw -echo <@$channel - tsv::set console is_raw 1 - #review - inconsistent return dict - return [dict create stdin [list from [set previous_stty_state_$channel] to "" note "fixme - to state not shown"]] - } else { - error "punk::console::enableRaw Unable to use twapi or stty to set raw mode - aborting" - } - } - - #note: twapi GetStdHandle & GetConsoleMode & SetConsoleCombo unreliable - fails with invalid handle (somewhat intermittent.. after stdin reopened?) - #could be we were missing a step in reopening stdin and console configuration? - - proc [namespace parent]::disableRaw {{channel stdin}} { - #variable is_raw - variable previous_stty_state_$channel - - if {[package provide twapi] ne ""} { - set console_handle [twapi::get_console_handle stdin] - set oldmode [twapi::get_console_input_mode] - # Turn on the echo and line-editing bits - twapi::modify_console_input_mode $console_handle -lineinput 1 -echoinput 1 - set newmode [twapi::get_console_input_mode] - tsv::set console is_raw 0 - return [list stdin [list from $oldmode to $newmode]] - } elseif {[set sttycmd [auto_execok stty]] ne ""} { - #stty can return info on windows - but doesn't seem to be able to set anything. - #review - is returned info even valid? - - set sttycmd [auto_execok stty] - if {[set previous_stty_state_$channel] ne ""} { - exec {*}$sttycmd [set previous_stty_state_$channel] - set previous_stty_state_$channel "" - return restored - } - exec {*}$sttycmd -raw echo <@$channel - tsv::set console is_raw 0 - #do we really want to exec stty yet again to show final 'to' state? - #probably not. We should work out how to read the stty result flags and set a result.. or just limit from,to to showing echo and lineedit states. - return [list stdin [list from "[set previous_stty_state_$channel]" to "" note "fixme - to state not shown"]] - } else { - error "punk::console::disableRaw Unable to use twapi or stty to unset raw mode - aborting" - } - } - - } lappend PUNKARGS [list { @@ -1803,7 +1852,10 @@ namespace eval punk::console { #don't set ansi_avaliable here - we want to be able to change things, retest etc. if {"windows" eq "$::tcl_platform(platform)"} { if {[package provide twapi] ne ""} { - set h_out [twapi::get_console_handle stdout] + if {[catch {twapi::get_console_handle stdout} h_out]} { + puts stderr "test_can_ansi: twapi cannot get console handle for stdout" + return 0 + } set existing_mode [twapi::GetConsoleMode $h_out] if {[expr {$existing_mode & 4}]} { #virtual terminal processing happens to be enabled - so it's supported diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/libunknown-0.1.tm b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/libunknown-0.1.tm index 3b5d35b0..f9dfaf56 100644 --- a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/libunknown-0.1.tm +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/libunknown-0.1.tm @@ -80,16 +80,7 @@ tcl::namespace::eval punk::libunknown { "Experimental set of replacements for default 'package unknown' entries." }] - variable epoch - #if {![info exists epoch]} { - # set tmstate [dict create 0 {}] - # set pkgstate [dict create 0 {}] - # set tminfo [dict create current 0 epochs $tmstate] - # set pkginfo [dict create current 0 epochs $pkgstate] - - # set epoch [dict create tm $tminfo pkg $pkginfo] - #} - + variable epoch ;#don't set - can be pre-set cooperatively variable has_package_files if {[catch {package files foobaz}]} { @@ -111,6 +102,33 @@ tcl::namespace::eval punk::libunknown { #will use standard mechanism for non zipfs paths in the tm list. proc zipfs_tm_UnknownHandler {original name args} { + #------------------------------ + #shortcircuit for builtin static libraries which have no 'package provide' info - review + #This occurs for example when running 'bin\runtime.cmd run src\make.tcl shell' with punk902z.exe + # + #------------------------------ + set loaded [lsearch -inline -index 1 -nocase [info loaded] $name] + if {[llength $loaded] == 2 && [lindex $loaded 0] eq ""} { + lassign $loaded _ cased_name + interp create ptest + ptest eval [list load {} $cased_name] + set static_version [ptest eval [list package provide [string tolower $cased_name]]] + set pname [string tolower $cased_name] + if {$static_version eq ""} { + set static_version [ptest eval [list package provide $cased_name]] + set pname $cased_name + } + if {$static_version ne ""} { + if {[package vsatisfies $static_version {*}$args]} { + package ifneeded $pname $static_version [list load {} $cased_name] + interp delete ptest + return + } + } + interp delete ptest + } + #------------------------------ + # Import the list of paths to search for packages in module form. # Import the pattern used to check package names in detail. variable epoch @@ -1161,7 +1179,12 @@ tcl::namespace::eval punk::libunknown { set callerposn [lsearch $args -caller] if {$callerposn > -1} { set caller [lindex $args $callerposn+1] - #puts stderr "\x1b\[1\;33m punk::libunknown::init - caller:$caller\x1b\[m" + if {[package provide thread] ne ""} { + set tid [thread::id] + } else { + set tid "-" + } + #puts stderr "\x1b\[1\;33m punk::libunknown::init - caller:$caller tid:$tid\x1b\[m" #puts stderr "punk::libunknown::init auto_path : $::auto_path" #puts stderr "punk::libunknown::init tcl::tm::list: [tcl::tm::list]" } @@ -1184,17 +1207,17 @@ tcl::namespace::eval punk::libunknown { puts stderr "punk::libunknown::init - init while empty/unreadable tcl::tm::list and empty/unreadable ::auto_path" } - if {[namespace origin ::package] eq "::punk::libunknown::package"} { - #This is far from conclusive - there may be other renamers (e.g commandstack) + if {[info commands ::punk::libunknown::package] ne ""} { + puts stderr "punk::libunknown::init already done - unnecessary call? info frame -1: [info frame -1]" return } + #if {[namespace origin ::package] eq "::punk::libunknown::package"} { + # #This is far from conclusive - there may be other renamers (e.g commandstack) + # return + #} - if {[info commands ::punk::libunknown::package] ne ""} { - puts stderr "punk::libunknown::init already done - unnecessary call? info frame -1: [info frame -1]" - return - } variable epoch if {![info exists epoch]} { set tmstate [dict create 0 {added {}}] @@ -1222,6 +1245,7 @@ tcl::namespace::eval punk::libunknown { # or suffer additional scans.. or document ?? #ideally init should be called in each interp before any scans for packages so that the list of untracked is minimized. set pkgnames [package names] + #puts stderr "####### punk::libunknown init called with [llength $pkgnames] package names known" foreach p $pkgnames { if {[string tolower $p] in {punk::libunknown tcl::zlib tcloo tcl::oo tcl}} { continue diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repl-0.1.2.tm b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repl-0.1.2.tm index 9d199997..11cd9706 100644 --- a/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repl-0.1.2.tm +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repl-0.1.2.tm @@ -20,18 +20,6 @@ if {[dict exists $stdin_info -mode]} { #give up for now set tcl_interactive 1 -#if {[info commands ::tcl::zipfs::root] ne ""} { -# set zr [::tcl::zipfs::root] -# if {[file join $zr app modules] in [tcl::tm::list]} { -# #todo - better way to find latest version - without package require -# set lib [file join $zr app modules punk libunknown.tm] -# if {[file exists $lib]} { -# source $lib -# punk::libunknown::init -# #package unknown {punk::libunknown::zipfs_tm_UnknownHandler punk::libunknown::zipfs_tclPkgUnknown} -# } -# } -#} #------------------------------------------------------------------------------------- if {[package provide punk::libunknown] eq ""} { #maintenance - also in src/vfs/_config/punk_main.tcl @@ -59,7 +47,7 @@ if {[package provide punk::libunknown] eq ""} { } if {$libunknown ne ""} { source $libunknown - if {[catch {punk::libunknown::init -caller repl} errM]} { + if {[catch {punk::libunknown::init -caller triggered_by_repl_package_require} errM]} { puts "error initialising punk::libunknown\n$errM" } } @@ -525,11 +513,11 @@ proc repl::start {inchan args} { set donevalue [set [namespace current]::done] if {[lindex $donevalue 0] eq "quit"} { puts "-->repl::start end $inchan $args result:'$donevalue'" - puts stderr "--> returning [lindex $donevalue 1]" + #puts stderr "repl quit --> returning [lindex $donevalue 1]" return [lindex $donevalue 1] } puts "-->repl::start end $inchan $args result:'$donevalue'" - puts stderr "__> returning 0" + #puts stderr "__> returning 0" return 0 } proc repl::post_operations {} { @@ -1408,7 +1396,6 @@ proc repl::repl_handler {inputchan prompt_config} { if {[dict get $original_input_conf -inputmode] eq "raw"} { #user or script has apparently put stdin into raw mode - update punk::console::is_raw to match set rawmode 1 - #set ::punk::console::is_raw 1 tsv::set console is_raw 1 } else { #set ::punk::console::is_raw 0 @@ -1420,9 +1407,6 @@ proc repl::repl_handler {inputchan prompt_config} { #if it's been set to raw - assume it is deliberately done this way as the user could have alternatively called punk::mode raw or punk::console::enableVirtualTerminal #by not doing this automatically - we assume the caller has a reason. } else { - #JMN FIX! - #this returns 0 in rawmode on 8.6 after repl thread changes - #set rawmode [set ::punk::console::is_raw] set rawmode [tsv::get console is_raw] } @@ -1811,8 +1795,6 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config set infoprompt [dict get $prompt_config infoprompt] set debugprompt [dict get $prompt_config debugprompt] - - #set rawmode [set ::punk::console::is_raw] set rawmode [tsv::get console is_raw] if {!$rawmode} { #puts stderr "-->got [ansistring VIEW -lf 1 $stdinlines]<--" @@ -2615,6 +2597,34 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config } #editbuf + + #after any external command - raw mode as the console sees it can be disabled + #set it to match current state of the tsv + if {[tsv::get console is_raw]} { + if {$::tcl_platform(platform) eq "windows"} { + #review + #we are in parent process - twapi might not be loaded here - even if it is in the code interp + catch {package require twapi} + } + set sinfo [chan configure stdin] + if {[dict exists $sinfo -inputmode]} { + if {[dict get $sinfo -inputmode] ne "raw"} { + set re_enable_required 1 + } else { + set re_enable_required 0 + } + } else { + # -inputmode unavailable + #tcl 8.6 doesn't have -inputmode - meaning it has to call punk:console::enableRaw each time + #enableRaw on windows without twapi involves launching a pwsh process - which gives a noticeable lag in keyboard input. + #enableRaw on Unix involves a call to stty - which is generally fast - but still to be avoided if not required. + set re_enable_required 1 + } + #puts stderr "-here- re-enabling raw" + if {$re_enable_required} { + punk::console::enableRaw + } + } } else { #append commandstr \n if {$::punk::repl::signal_control_c} { @@ -2828,7 +2838,7 @@ namespace eval repl { } if {$libunknown ne ""} { source $libunknown - if {[catch {punk::libunknown::init -caller "repl init_script"} errM]} { + if {[catch {punk::libunknown::init -caller "repl::init init_script parent interp"} errM]} { puts "repl::init problem - error initialising punk::libunknown\n$errM" } #package require punk::lib @@ -2858,10 +2868,10 @@ namespace eval repl { #thread::send to caller defined interp targets (reference?) #snit required for icomm if {[catch {package require snit} errM]} { - puts stdout "punk::repl::initscript lib load fail ---snit $errM" + #puts stdout "punk::repl::initscript: lib load fail ---snit $errM" } if {[catch {package require punk::icomm} errM]} { - puts stdout "punk::repl::initscript lib load fail ---icomm $errM" + #puts stdout "punk::repl::initscript: lib load fail ---icomm $errM" } #----- @@ -2872,7 +2882,7 @@ namespace eval repl { #first use can raise error being a version number e.g 0.1.0 - why? lassign [tcl::chan::fifo2] ::punk::repl::codethread::repltalk replside } errMsg]} { - puts stdout "punk::repl::initscript tcl::chan::fifo2 error: $errM" + puts stdout "punk::repl::initscript tcl::chan::fifo2 error: $errMsg" } else { #experimental? #puts stdout "transferring chan $replside to thread %replthread%" @@ -3519,6 +3529,8 @@ namespace eval repl { #----------------------------------------------------------------------------- if {[package provide punk::libunknown] eq ""} { + namespace eval ::punk::libunknown {} + set ::punk::libunknown::epoch %lib_epoch% set libunks [list] foreach tm_path [tcl::tm::list] { set punkdir [file join $tm_path punk] @@ -3543,7 +3555,7 @@ namespace eval repl { } if {$libunknown ne ""} { source $libunknown - if {[catch {punk::libunknown::init -caller "repl init_script punk"} errM]} { + if {[catch {punk::libunknown::init -caller "repl::init init_script code interp for punk"} errM]} { puts "error initialising punk::libunknown\n$errM" } } diff --git a/src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl b/src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl index 1736d3d9..c1d3f906 100644 --- a/src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl +++ b/src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl @@ -31,22 +31,28 @@ namespace eval ::punkboot::lib { #for some purposes (whether a source folder is likely to have any useful content) we are interested in non dotfile/dotfolder immediate contents of a folder, but not whether a particular platform #considers them hidden or not. proc folder_nondotted_children {folder} { - if {![file isdirectory $folder]} {error "punkboot::lib::folder_nondotted_children error. Supplied folder '$folder' is not a directory"} - set contents [glob -nocomplain -dir $folder *] + set normfolder [file normalize $folder] + if {![file isdirectory $normfolder]} {error "punkboot::lib::folder_nondotted_children error. Supplied folder '$folder' is not a directory"} + set contents [glob -nocomplain -dir $folder -tails *] #some platforms (windows) return dotted entries with *, although most don't - return [lsearch -all -inline -not $contents .*] + set nondotted_tails [lsearch -all -inline -not $contents .*] + return [lmap ftail $nondotted_tails {file join $folder $ftail}] } proc folder_nondotted_folders {folder} { - if {![file isdirectory $folder]} {error "punkboot::lib::folder_nondotted_folders error. Supplied folder '$folder' is not a directory"} - set contents [glob -nocomplain -dir $folder -types d *] + set normfolder [file normalize $folder] + if {![file isdirectory $normfolder]} {error "punkboot::lib::folder_nondotted_folders error. Supplied folder '$folder' is not a directory"} + set contents [glob -nocomplain -dir $folder -types d -tails *] #some platforms (windows) return dotted entries with *, although most don't - return [lsearch -all -inline -not $contents .*] + set nondotted_tails [lsearch -all -inline -not $contents .*] + return [lmap ftail $nondotted_tails {file join $folder $ftail}] } proc folder_nondotted_files {folder} { - if {![file isdirectory $folder]} {error "punkboot::lib::folder_nondotted_files error. Supplied folder '$folder' is not a directory"} - set contents [glob -nocomplain -dir $folder -types f $folder *] + set normfolder [file normalize $folder] + if {![file isdirectory $normfolder]} {error "punkboot::lib::folder_nondotted_files error. Supplied folder '$folder' is not a directory"} + set contents [glob -nocomplain -dir $folder -types f $folder -tails *] #some platforms (windows) return dotted entries with *, although most don't - return [lsearch -all -inline -not $contents .*] + set nondotted_tails [lsearch -all -inline -not $contents .*] + return [lmap ftail $nondotted_tails {file join $folder $ftail}] } proc tm_version_isvalid {versionpart} { #Needs to be suitable for use with Tcl's 'package vcompare' @@ -289,6 +295,10 @@ if {"::try" ni [info commands ::try]} { # This allows a source update via 'fossil update' 'git pull' etc to pull in a minimal set of support modules for the boot script # and load these in preference to ones that may have been in the interp's tcl::tm::list or auto_path due to environment variables set startdir [pwd] + +set scriptdir [file dirname [file normalize [info script]]] +#puts "SCRIPTDIR: $scriptdir" + #we are focussed on pure-tcl libs/modules in bootsupport for now. #There may be cases where we want to use compiled packages from src/bootsupport/modules_tcl9 etc #REVIEW - punkboot can really speed up with appropriate accelerators and/or external binaries @@ -303,18 +313,22 @@ set bootsupport_library_paths [list] set this_platform_generic [punkboot::lib::platform_generic] #we always create these lists in order of desired precedence. # - this is the same order when adding to auto_path - but will need to be reversed when using tcl:tm::add -if {[file exists [file join $startdir src bootsupport]]} { - lappend bootsupport_module_paths [file join $startdir src bootsupport modules_tcl$::tclmajorv] ;#more version-specific modules slightly higher in precedence order - lappend bootsupport_module_paths [file join $startdir src bootsupport modules] - lappend bootsupport_library_paths [file join $startdir src bootsupport lib_tcl$::tclmajorv/allplatforms] ;#more version-specific pkgs slightly higher in precedence order - lappend bootsupport_library_paths [file join $startdir src bootsupport lib_tcl$::tclmajorv/$this_platform_generic] ;#more version-specific pkgs slightly higher in precedence order - lappend bootsupport_library_paths [file join $startdir src bootsupport lib] +if {[file exists [file join $scriptdir bootsupport]]} { + set bootsupportdir [file join $scriptdir bootsupport] + puts stderr "Using bootsupport dir $bootsupportdir" + + lappend bootsupport_module_paths [file join $bootsupportdir modules_tcl$::tclmajorv] ;#more version-specific modules slightly higher in precedence order + lappend bootsupport_module_paths [file join $bootsupportdir modules] + lappend bootsupport_library_paths [file join $bootsupportdir lib_tcl$::tclmajorv/allplatforms] ;#more version-specific pkgs slightly higher in precedence order + lappend bootsupport_library_paths [file join $bootsupportdir lib_tcl$::tclmajorv/$this_platform_generic] ;#more version-specific pkgs slightly higher in precedence order + lappend bootsupport_library_paths [file join $bootsupportdir lib] } else { - lappend bootsupport_module_paths [file join $startdir bootsupport modules_tcl$::tclmajorv] - lappend bootsupport_module_paths [file join $startdir bootsupport modules] - lappend bootsupport_library_paths [file join $startdir bootsupport lib_tcl$::tclmajorv/allplatforms] - lappend bootsupport_library_paths [file join $startdir bootsupport lib_tcl$::tclmajorv/$this_platform_generic] - lappend bootsupport_library_paths [file join $startdir bootsupport lib] + puts stderr "No bootsupport dir for script [info script] at [file join $scriptdir bootsupport]" + #lappend bootsupport_module_paths [file join $startdir bootsupport modules_tcl$::tclmajorv] + #lappend bootsupport_module_paths [file join $startdir bootsupport modules] + #lappend bootsupport_library_paths [file join $startdir bootsupport lib_tcl$::tclmajorv/allplatforms] + #lappend bootsupport_library_paths [file join $startdir bootsupport lib_tcl$::tclmajorv/$this_platform_generic] + #lappend bootsupport_library_paths [file join $startdir bootsupport lib] } set bootsupport_paths_exist 0 foreach p [list {*}$bootsupport_module_paths {*}$bootsupport_library_paths] { @@ -406,8 +420,8 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} { tcl::tm::add {*}[lreverse $bootsupport_module_paths] {*}[lreverse $sourcesupport_module_paths] ;#tm::add works like LIFO. sourcesupport_module_paths end up earliest in resulting tm list. set ::auto_path [list {*}$sourcesupport_library_paths {*}$bootsupport_library_paths] } - puts "----> auto_path $::auto_path" - puts "----> tcl::tm::list [tcl::tm::list]" + #puts "----> auto_path $::auto_path" + #puts "----> tcl::tm::list [tcl::tm::list]" #maint: also in punk::repl package #-------------------------------------------------------- @@ -435,22 +449,26 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} { } if {$libunknown ne ""} { source $libunknown - if {[catch {punk::libunknown::init -caller main.tcl} errM]} { - puts "error initialising punk::libunknown\n$errM" + if {[catch {punk::libunknown::init -caller make.tcl} errM]} { + puts stderr "error initialising punk::libunknown\n$errM" } + #puts stdout " *** [package names]" + #puts stdout " **** [dict get $::punk::libunknown::epoch pkg untracked]" + } else { + puts stderr "Failed to find punk::libunknown" } #-------------------------------------------------------- #package require Thread - puts "---->tcl_library [info library]" - puts "---->loaded [info loaded]" + #puts "---->tcl_library [info library]" + #puts "---->loaded [info loaded]" # - the full repl requires Threading and punk,shellfilter,shellrun to call and display properly. # tm list already indexed - need 'package forget' to find modules based on current tcl::tm::list #These are strong dependencies - package forget punk::mix - package forget punk::repo - package forget punkcheck + #package forget punk::mix + #package forget punk::repo + #package forget punkcheck package require punk::repo ;#todo - push our requirements to a smaller punk::repo::xxx package with minimal dependencies package require punk::mix @@ -464,6 +482,7 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} { set package_paths_modified 1 #------------------------------------------------------------------------------ + #puts "----> llength package names [llength [package names]]" } set ::punkboot::pkg_requirements_found [list] @@ -479,7 +498,9 @@ set ::punkboot::bootsupport_requirements [dict create\ punkcheck [list]\ fauxlink [list version "0.1.1-"]\ textblock [list version 0.1.1-]\ + fileutil [list]\ fileutil::traverse [list]\ + struct::list [list]\ md5 [list version 2-]\ ] @@ -1282,7 +1303,41 @@ proc ::punkboot::punkboot_gethelp {args} { return $h } + + + set scriptargs $::argv +punk::args::define { + @id -id punkmake + @cmd -name punkmake\ + -summary\ + "Project builder"\ + -help\ + "" + @form -form help + @leaders + subcommand -type "literal(help)" + @opts + @values + what -type string -choices {modules libs shell} + + @form -form modules + subcommand -type "literal(modules)" + + @form -form libs + subcommand -type "literal(libs)" + + @form -form shell + subcommand -type "literal(shell)" + arg -type any -optional 1 -multiple 1 +} +#set argd [punk::args::parse $scriptargs -form 0 withid punkmake] +##lassign [dict values $argd] leaders opts values received +# +#puts stdout [punk::args::usage -scheme nocolour punkmake] +#exit 1 + + set do_help 0 if {![llength $scriptargs]} { set do_help 1 @@ -1294,6 +1349,8 @@ if {![llength $scriptargs]} { } } } + + set commands_found [list] foreach a $scriptargs { if {![string match -* $a]} { @@ -1310,6 +1367,8 @@ if {[llength $commands_found] != 1 } { puts stderr "Unknown command: [lindex $commands_found 0]\n\n" set do_help 1 } + + if {$do_help} { puts stdout "Checking package availability..." set ::punkboot::pkg_availability [::punkboot::check_package_availability -quiet 1 $::punkboot::bootsupport_requirements] @@ -1325,6 +1384,8 @@ if {$do_help} { exit 0 } + + set ::punkboot::command [lindex $commands_found 0] @@ -1414,14 +1475,15 @@ if {$::punkboot::command eq "check"} { if {$package_paths_modified} { set tm_list_boot [tcl::tm::list] tcl::tm::remove {*}$tm_list_boot - foreach p [lreverse $original_tm_list] { + + set lower_prio [list] + foreach p $original_tm_list { if {$p ni $tm_list_boot} { - tcl::tm::add $p + lappend lower_prio $p } } - foreach p [lreverse $tm_list_boot] { - tcl::tm::add $p - } + tcl::tm::add {*}[lreverse $lower_prio] {*}[lreverse $tm_list_boot] + #set ::auto_path [list $bootsupport_lib {*}$original_auto_path] lappend ::auto_path {*}$original_auto_path } @@ -1489,6 +1551,28 @@ if {![array size A]} { punkboot::define_global_ansi } +#puts stderr ">>>>>>+ loaded:[info loaded]" +#puts stderr "llength package names: [llength [package names]]" +if {[info exists ::punk::libunknown::epoch]} { + set untracked [dict get $::punk::libunknown::epoch pkg untracked] + #puts stderr "punk::libunknown::epoch exists" +} else { + set untracked [list] + #puts stderr "punk::libunknown::epoch does not exist" +} +#REVIEW - we shouldn't need to manually set the untracked packages - punk::libunknown::init should have done it? +foreach p [package names] { + if {![dict exists $untracked $p]} { + dict set untracked $p "" + } +} +dict set ::punk::libunknown::epoch pkg untracked $untracked + +if {[package provide punk::libunknown] eq ""} { + puts "punk::libunknown not loaded" +} else { + puts "punk::libunknown loaded" +} dict for {pkg pkginfo} $::punkboot::bootsupport_requirements { set verspec [dict get $pkginfo version] ;#version wanted specification always exists and is empty or normalised if {[catch {package require $pkg {*}$verspec} errM]} { @@ -1557,14 +1641,13 @@ if {$::punkboot::command eq "info"} { if {$::punkboot::command eq "shell"} { - puts stderr ">>>>>> loaded:[info loaded]" + package require struct::list package require punk package require punk::repl #todo - make procs vars etc from this file available? puts stderr "punk boot shell not implemented - dropping into ordinary punk shell." - repl::init set replresult [repl::start stdin -title make.tcl] #review @@ -3059,6 +3142,8 @@ foreach vfstail $vfs_tails { exec {*}$::sdxpath unwrap [file rootname $building_runtime].tail ;#extracts to folder named [file rootname $building_runtime].vfs e.g build_tclkit9.0.2-win64-dyn.vfs #file rename to existing target dir would copy folder into target dir if {![file exists $targetvfs]} { + #delay + after 1000 file rename [file rootname $building_runtime].vfs $targetvfs } else { merge_over [file rootname $building_runtime].vfs $targetvfs diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/args-0.2.tm b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/args-0.2.tm index a6224c0d..7b6ee228 100644 --- a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/args-0.2.tm +++ b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/args-0.2.tm @@ -3036,8 +3036,11 @@ tcl::namespace::eval punk::args { #This mechanism gets less-than-useful results for oo methods #e.g {$obj} proc Get_caller {} { + set depth [info level] + set maxd [expr {min($depth,4)}] + set call_level [expr {-1 * $maxd}] #set call_level -3 ;#for get_dict call - set call_level -4 + #set call_level -4 set cmdinfo [tcl::dict::get [tcl::info::frame $call_level] cmd] #puts "-->$cmdinfo" #puts "-->[tcl::info::frame -3]" diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/args/tclcore-0.1.0.tm b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/args/tclcore-0.1.0.tm index d016c70a..6a4cc626 100644 --- a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/args/tclcore-0.1.0.tm +++ b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/args/tclcore-0.1.0.tm @@ -3498,7 +3498,7 @@ tcl::namespace::eval punk::args::tclcore { example, in ${$B}-dictionary${$N} mode, bigBoy sorts between bigbang and bigboy, and x10y sorts between x9y and x11y. Overrides the ${$B}-nocase${$N} option." -integer -type none -help\ - "Convert list elements to integers and use integer comparsion." + "Convert list elements to integers and use integer comparison." -real -type none -help\ "Convert list elements to floating-point values and use floating comparison." -command -type string -help\ diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/console-0.1.1.tm b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/console-0.1.1.tm index ea8d3f77..4d4518d3 100644 --- a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/console-0.1.1.tm +++ b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/console-0.1.1.tm @@ -129,57 +129,362 @@ namespace eval punk::console { #e.g external utils system API's. namespace export * } - + if {"windows" eq $::tcl_platform(platform)} { #accept args for all dummy/load functions so we don't have to match/update argument signatures here + set has_twapi [expr {! [catch {package require twapi}]}] + + if {$has_twapi} { + #this is really enableAnsi *processing* + proc enableAnsi {} { + #output handle modes + #Enable virtual terminal processing (sometimes off in older windows terminals) + #ENABLE_PROCESSED_OUTPUT = 0x0001 + #ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002 + #ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 + #DISABLE_NEWLINE_AUTO_RETURN = 0x0008 + if {[catch {twapi::get_console_handle stdout} h_out]} { + puts stderr "enableAnsi failed: twapi cannot get console handle for stdout" + return + } - proc enableAnsi {args} { - #loopavoidancetoken (don't remove) - internal::define_windows_procs - internal::abort_if_loop - tailcall enableAnsi {*}$args - } - #review what raw mode means with regard to a specific channel vs terminal as a whole - proc enableRaw {args} { - #loopavoidancetoken (don't remove) - internal::define_windows_procs - internal::abort_if_loop - tailcall enableRaw {*}$args - } - proc disableRaw {args} { - #loopavoidancetoken (don't remove) - internal::define_windows_procs - internal::abort_if_loop - tailcall disableRaw {*}$args - } - proc enableVirtualTerminal {args} { - #loopavoidancetoken (don't remove) - internal::define_windows_procs - internal::abort_if_loop - tailcall enableVirtualTerminal {*}$args - } - proc disableVirtualTerminal {args} { - #loopavoidancetoken (don't remove) - internal::define_windows_procs - internal::abort_if_loop - tailcall disableVirtualTerminal {*}$args - } - set funcs [list disableAnsi enableProcessedInput disableProcessedInput] - foreach f $funcs { - proc $f {args} [string map [list %f% $f] { - set mybody [info body %f%] - internal::define_windows_procs - set newbody [info body %f%] - if {$newbody ne $mybody} { - tailcall %f% {*}$args + set oldmode_out [twapi::GetConsoleMode $h_out] + set newmode_out [expr {$oldmode_out | 4}] ;#don't enable processed output too, even though it's required. keep symmetrical with disableAnsi? + + twapi::SetConsoleMode $h_out $newmode_out + + #what does window_input have to do with it?? + #input handle modes + #ENABLE_PROCESSED_INPUT 0x0001 ;#set to zero will allow ctrl-c to be reported as keyboard input rather than as a signal + #ENABLE_LINE_INPUT 0x0002 + #ENABLE_ECHO_INPUT 0x0004 + #ENABLE_WINDOW_INPUT 0x0008 (default off when a terminal created) + #ENABLE_MOUSE_INPUT 0x0010 + #ENABLE_INSERT_MODE 0X0020 + #ENABLE_QUICK_EDIT_MODE 0x0040 + #ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200 (default off when a terminal created) (512) + set h_in [twapi::get_console_handle stdin] + set oldmode_in [twapi::GetConsoleMode $h_in] + set newmode_in [expr {$oldmode_in | 8}] + #set newmode_in [expr {$oldmode_in | 0x208}] + + twapi::SetConsoleMode $h_in $newmode_in + + return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]] + } + proc disableAnsi {} { + set h_out [twapi::get_console_handle stdout] + set oldmode_out [twapi::GetConsoleMode $h_out] + set newmode_out [expr {$oldmode_out & ~4}] + twapi::SetConsoleMode $h_out $newmode_out + + #??? review + set h_in [twapi::get_console_handle stdin] + set oldmode_in [twapi::GetConsoleMode $h_in] + set newmode_in [expr {$oldmode_in & ~8}] + twapi::SetConsoleMode $h_in $newmode_in + + + return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]] + } + proc enableVirtualTerminal {{channels {input output}}} { + set ins [list in input stdin] + set outs [list out output stdout stderr] + set known [concat $ins $outs both] + set directions [list] + foreach v $channels { + if {$v in $ins} { + lappend directions input + } elseif {$v in $outs} { + lappend directions output + } elseif {$v eq "both"} { + lappend directions input output + } + if {$v ni $known} { + error "enableVirtualTerminal expected channel values to be one of '$known'. (all values mapped to input and/or output)" + } + } + set channels $directions ;#don't worry about dups. + if {"both" in $channels} { + lappend channels input output + } + set result [dict create] + if {"output" in $channels} { + #note setting stdout makes stderr have the same settings - ie there is really only one output to configure + set h_out [twapi::get_console_handle stdout] + set oldmode [twapi::GetConsoleMode $h_out] + set newmode [expr {$oldmode | 4}] + twapi::SetConsoleMode $h_out $newmode + dict set result output [list from $oldmode to $newmode] + } + + if {"input" in $channels} { + set h_in [twapi::get_console_handle stdin] + set oldmode_in [twapi::GetConsoleMode $h_in] + set newmode_in [expr {$oldmode_in | 0x200}] + twapi::SetConsoleMode $h_in $newmode_in + dict set result input [list from $oldmode_in to $newmode_in] + } + + return $result + } + + proc disableVirtualTerminal {{channels {input output}}} { + set ins [list in input stdin] + set outs [list out output stdout stderr] + set known [concat $ins $outs both] + set directions [list] + foreach v $channels { + if {$v in $ins} { + lappend directions input + } elseif {$v in $outs} { + lappend directions output + } elseif {$v eq "both"} { + lappend directions input output + } + if {$v ni $known} { + error "disableVirtualTerminal expected channel values to be one of '$known'. (all values mapped to input and/or output)" + } + } + set channels $directions ;#don't worry about dups. + if {"both" in $channels} { + lappend channels input output + } + set result [dict create] + if {"output" in $channels} { + #as above - configuring stdout does stderr too + set h_out [twapi::get_console_handle stdout] + set oldmode [twapi::GetConsoleMode $h_out] + set newmode [expr {$oldmode & ~4}] + twapi::SetConsoleMode $h_out $newmode + dict set result output [list from $oldmode to $newmode] + } + if {"input" in $channels} { + set h_in [twapi::get_console_handle stdin] + set oldmode_in [twapi::GetConsoleMode $h_in] + set newmode_in [expr {$oldmode_in & ~0x200}] + twapi::SetConsoleMode $h_in $newmode_in + dict set result input [list from $oldmode_in to $newmode_in] + } + + #return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]] + return $result + } + proc enableProcessedInput {} { + set h_in [twapi::get_console_handle stdin] + set oldmode_in [twapi::GetConsoleMode $h_in] + set newmode_in [expr {$oldmode_in | 1}] + twapi::SetConsoleMode $h_in $newmode_in + return [list stdin [list from $oldmode_in to $newmode_in]] + } + proc disableProcessedInput {} { + set h_in [twapi::get_console_handle stdin] + set oldmode_in [twapi::GetConsoleMode $h_in] + set newmode_in [expr {$oldmode_in & ~1}] + twapi::SetConsoleMode $h_in $newmode_in + return [list stdin [list from $oldmode_in to $newmode_in]] + } + proc enableRaw {{channel stdin}} { + #variable is_raw + variable previous_stty_state_$channel + + if {[catch {twapi::get_console_handle stdin} console_handle]} { + puts stderr "enableRaw error: twapi cannot get console handle for stdin" + #review. If twapi couldn't get a console handle - no point trying other mechanisms(?) + return + } + #returns dictionary + #e.g -processedinput 1 -lineinput 1 -echoinput 1 -windowinput 0 -mouseinput 0 -insertmode 1 -quickeditmode 1 -extendedmode 1 -autoposition 0 + set oldmode [twapi::get_console_input_mode] + twapi::modify_console_input_mode $console_handle -lineinput 0 -echoinput 0 + # Turn off the echo and line-editing bits + #set newmode [dict merge $oldmode [dict create -lineinput 0 -echoinput 0]] + set newmode [twapi::get_console_input_mode] + + tsv::set console is_raw 1 + #don't disable handler - it will detect is_raw + ### twapi::set_console_control_handler {} + return [list stdin [list from $oldmode to $newmode]] + } + + #note: twapi GetStdHandle & GetConsoleMode & SetConsoleCombo unreliable - fails with invalid handle (somewhat intermittent.. after stdin reopened?) + #could be we were missing a step in reopening stdin and console configuration? + + proc disableRaw {{channel stdin}} { + #variable is_raw + variable previous_stty_state_$channel + set ch_state [chan conf $channel] + if {[dict exists $ch_state -inputmode]} { + chan conf $channel -inputmode normal + tsv::set console is_raw 0 + return [list $channel [list from [dict get $ch_state -inputmode] to normal]] } else { - #error vs noop? - puts stderr "Unable to set implementation for %f% - check twapi?" + if {[catch {twapi::get_console_handle stdin} console_handle]} { + #e.g tkcon/wish + puts stderr "disableRaw error: twapi cannot get console handle for stdin" + return ;# ??? + } + set oldmode [twapi::get_console_input_mode] + # Turn on the echo and line-editing bits + twapi::modify_console_input_mode $console_handle -lineinput 1 -echoinput 1 + set newmode [twapi::get_console_input_mode] + tsv::set console is_raw 0 + return [list stdin [list from $oldmode to $newmode]] } - }] + } + + } else { + + variable ps_consolemode_pid + variable ps_consolemode_contents + variable ps_pipename + if {![info exists ps_consolemode_contents]} { + #start persistent powershell consolemode_server.ps1 named pipe server + if {$::argv0 ne ""} { + set pstooldir [file dirname [file dirname [file normalize $::argv0]]]/scriptlib/utils/pwsh + } else { + set pstooldir [pwd] + } + #set ps_script $pstooldir/consolemode_server.ps1 + set ps_script $pstooldir/consolemode_server_async.ps1 + if {[file exists $ps_script]} { + set fd [open $ps_script r] + chan configure $fd -translation binary + set ps_consoleid [pid]-[expr {int(999 * rand())+1}] + set ps_consolemode_contents [string map [list "" $ps_consoleid] [read $fd]] + close $fd + #set ps_consolemode_pipe [twapi::namedpipe_client {//./pipe/punkshell_ps_consolemode} -access write] + #set ps_cmd [auto_execok pwsh.exe] + set ps_cmd [auto_execok pwsh.exe] + if {$ps_cmd eq ""} { + set ps_cmd [auto_execok powershell.exe] + } + if {$ps_cmd ne ""} { + set ps_consolemode_pid [exec {*}$ps_cmd -nop -nol -c $ps_consolemode_contents &] + set ps_pipename {\\.\pipe\punkshell_ps_consolemode_} + append ps_pipename $ps_consoleid + puts stderr "twapi not present, using persistent powershell process: pipename: $ps_pipename pid: $ps_consolemode_pid" + #todo - taskkill /F /PID $ps_consolemode_pid + #when? + #review + #if {[catch {puts "pidinfo: [::tcl::process::status $ps_consolemode_pid]"} errM]} { + # puts stderr "--- failed to get process status for $ps_consolemode_pid\n$errM" + #} + #set p [open {\\.\pipe\punkshell_ps_consolemode} w] + #chan conf $p -buffering none -blocking 1 + #puts $p "" + #close $p + } + } + + } + + + #enableRaw + proc enableRaw {{channel stdin}} { + #puts stderr "punk::console::enableRaw" + #variable is_raw + variable previous_stty_state_$channel + variable ps_consolemode_contents + variable ps_pipename + + + if {[info exists ps_consolemode_contents]} { + #ps_pipename e.g \\.\pipe\punkwinshell_ps_consolemode_12345-1223456 + + set trynum 0 + set wrote 0 + while {$trynum < 5} { + incr trynum + if {![catch { + set pipe [open $ps_pipename w] + } errMsg]} { + chan conf $pipe -buffering line + puts -nonewline $pipe "enableraw\r\n" + #flush $pipe + #after 10 + #close $pipe + set wrote 1 + break + } else { + after 100 + } + } + if {$wrote} { + tsv::set console is_raw 1 + after 100 + close $pipe + } else { + puts stderr "write to $ps_pipename failed trynum: $trynum\n$errMsg" + } + } elseif {[set sttycmd [auto_execok stty]] ne ""} { + #todo - something else entirely + #this approach does not work on windows + #the msys/cygwin stty command is launched as a subprocess - can be used to retrieve info + # but seems to be useless as far as affecting the calling process/console + if {[set previous_stty_state_$channel] eq ""} { + set previous_stty_state_$channel [exec {*}$sttycmd -g <@$channel] + } + + exec {*}$sttycmd raw -echo <@$channel + tsv::set console is_raw 1 + #review - inconsistent return dict + return [dict create stdin [list from [set previous_stty_state_$channel] to "" note "fixme - to state not shown"]] + } else { + error "punk::console::enableRaw Unable to use twapi or stty to set raw mode - aborting" + } + } + + + proc disableRaw {{channel stdin}} { + variable previous_stty_state_$channel + set ch_state [chan conf $channel] + if {[dict exists $ch_state -inputmode]} { + chan conf $channel -inputmode normal + tsv::set console is_raw 0 + return [list $channel [list from [dict get $ch_state -inputmode] to normal]] + } else { + #tcl <= 8.6x doesn't support -inputmode + if {[set sttycmd [auto_execok stty]] ne ""} { + #this doesn't work on windows + #It may seem to - only because running *any* external utility can exit raw mode + set sttycmd [auto_execok stty] + if {[set previous_stty_state_$channel] ne ""} { + exec {*}$sttycmd [set previous_stty_state_$channel] + set previous_stty_state_$channel "" + return restored + } + exec {*}$sttycmd -raw echo <@$channel + tsv::set console is_raw 0 + #do we really want to exec stty yet again to show final 'to' state? + #probably not. We should work out how to read the stty result flags and set a result.. or just limit from,to to showing echo and lineedit states. + return [list stdin [list from "[set previous_stty_state_$channel]" to "" note "fixme - to state not shown"]] + } else { + error "punk::console::disableRaw Unable to use twapi or stty to unset raw mode - aborting" + } + } + } + + #enableAnsi + proc enableAnsi {} { + } + #disableAnsi + proc enableAnsi {} { + } + #enableVirtualTerminal + proc enableVirtualTerminal {{channels {input output}}} { + } + #disableVirtualTerminal + proc disableVirtualTerminal {{channels {input output}}} { + } + #enableProcessedInput + #disableProcessedInput + } } else { + #non-windows platforms + proc enableAnsi {} { #todo? } @@ -190,6 +495,13 @@ namespace eval punk::console { #todo - something better - the 'channel' concept may not really apply on unix, as raw mode is set for input and output modes currently - only valid to set on a readable channel? #on windows they can be set independently (but not with stty) - REVIEW + proc enableVirtualTerminal {{channels {input output}}} { + + } + proc disableVirtualTerminal {args} { + + } + #NOTE - the is_raw is only being set in current interp - but the channel is shared. #this is problematic with the repl thread being separate. - must be a tsv? REVIEW proc enableRaw {{channel stdin}} { @@ -221,12 +533,6 @@ namespace eval punk::console { tsv::set console is_raw 0 return done } - proc enableVirtualTerminal {{channels {input output}}} { - - } - proc disableVirtualTerminal {args} { - - } } #review - document and decide granularity required. should we enable/disable more than one at once? @@ -257,7 +563,6 @@ namespace eval punk::console { #puts -nonewline stdout \x1b\[?25l ;#hide cursor puts -nonewline stdout \x1b\[?1003h\n enable_bracketed_paste - } #todo stop_application_mode {} {} @@ -313,266 +618,10 @@ namespace eval punk::console { } } proc define_windows_procs {} { - package require zzzload - set loadstate [zzzload::pkg_require twapi] - - #loadstate could also be stuck on loading? - review - zzzload not very ripe - #Twapi can be relatively slow to load (on some systems) - can be 1s plus in some cases - and much longer if there are disk performance issues. - if {$loadstate ni [list failed]} { - #possibly still 'loading' - #review zzzload usage - #puts stdout "=========== console loading twapi =============" - set loadstate [zzzload::pkg_wait twapi] ;#can return 'failed' will return version if already loaded or loaded during wait - } - - if {$loadstate ni [list failed]} { - package require twapi ;#should be fast once twapi dll loaded in zzzload thread - set ::punk::console::has_twapi 1 - - #todo - move some of these to the punk::console::local sub-namespace - as they use APIs rather than in-band ANSI to do their work. - #enableAnsi seems like it should be directly under punk::console .. but then it seems inconsistent if other local console-mode setting functions aren't. - #Find a compromise to organise things somewhat sensibly.. - - #this is really enableAnsi *processing* - proc [namespace parent]::enableAnsi {} { - #output handle modes - #Enable virtual terminal processing (sometimes off in older windows terminals) - #ENABLE_PROCESSED_OUTPUT = 0x0001 - #ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002 - #ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 - #DISABLE_NEWLINE_AUTO_RETURN = 0x0008 - set h_out [twapi::get_console_handle stdout] - set oldmode_out [twapi::GetConsoleMode $h_out] - set newmode_out [expr {$oldmode_out | 4}] ;#don't enable processed output too, even though it's required. keep symmetrical with disableAnsi? - - twapi::SetConsoleMode $h_out $newmode_out - - #what does window_input have to do with it?? - #input handle modes - #ENABLE_PROCESSED_INPUT 0x0001 ;#set to zero will allow ctrl-c to be reported as keyboard input rather than as a signal - #ENABLE_LINE_INPUT 0x0002 - #ENABLE_ECHO_INPUT 0x0004 - #ENABLE_WINDOW_INPUT 0x0008 (default off when a terminal created) - #ENABLE_MOUSE_INPUT 0x0010 - #ENABLE_INSERT_MODE 0X0020 - #ENABLE_QUICK_EDIT_MODE 0x0040 - #ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200 (default off when a terminal created) (512) - set h_in [twapi::get_console_handle stdin] - set oldmode_in [twapi::GetConsoleMode $h_in] - set newmode_in [expr {$oldmode_in | 8}] - #set newmode_in [expr {$oldmode_in | 0x208}] - - twapi::SetConsoleMode $h_in $newmode_in - - return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]] - } - proc [namespace parent]::disableAnsi {} { - set h_out [twapi::get_console_handle stdout] - set oldmode_out [twapi::GetConsoleMode $h_out] - set newmode_out [expr {$oldmode_out & ~4}] - twapi::SetConsoleMode $h_out $newmode_out - #??? review - set h_in [twapi::get_console_handle stdin] - set oldmode_in [twapi::GetConsoleMode $h_in] - set newmode_in [expr {$oldmode_in & ~8}] - twapi::SetConsoleMode $h_in $newmode_in - - - return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]] - } - - # - proc [namespace parent]::enableVirtualTerminal {{channels {input output}}} { - set ins [list in input stdin] - set outs [list out output stdout stderr] - set known [concat $ins $outs both] - set directions [list] - foreach v $channels { - if {$v in $ins} { - lappend directions input - } elseif {$v in $outs} { - lappend directions output - } elseif {$v eq "both"} { - lappend directions input output - } - if {$v ni $known} { - error "enableVirtualTerminal expected channel values to be one of '$known'. (all values mapped to input and/or output)" - } - } - set channels $directions ;#don't worry about dups. - if {"both" in $channels} { - lappend channels input output - } - set result [dict create] - if {"output" in $channels} { - #note setting stdout makes stderr have the same settings - ie there is really only one output to configure - set h_out [twapi::get_console_handle stdout] - set oldmode [twapi::GetConsoleMode $h_out] - set newmode [expr {$oldmode | 4}] - twapi::SetConsoleMode $h_out $newmode - dict set result output [list from $oldmode to $newmode] - } - - if {"input" in $channels} { - set h_in [twapi::get_console_handle stdin] - set oldmode_in [twapi::GetConsoleMode $h_in] - set newmode_in [expr {$oldmode_in | 0x200}] - twapi::SetConsoleMode $h_in $newmode_in - dict set result input [list from $oldmode_in to $newmode_in] - } - - return $result - } - proc [namespace parent]::disableVirtualTerminal {{channels {input output}}} { - set ins [list in input stdin] - set outs [list out output stdout stderr] - set known [concat $ins $outs both] - set directions [list] - foreach v $channels { - if {$v in $ins} { - lappend directions input - } elseif {$v in $outs} { - lappend directions output - } elseif {$v eq "both"} { - lappend directions input output - } - if {$v ni $known} { - error "disableVirtualTerminal expected channel values to be one of '$known'. (all values mapped to input and/or output)" - } - } - set channels $directions ;#don't worry about dups. - if {"both" in $channels} { - lappend channels input output - } - set result [dict create] - if {"output" in $channels} { - #as above - configuring stdout does stderr too - set h_out [twapi::get_console_handle stdout] - set oldmode [twapi::GetConsoleMode $h_out] - set newmode [expr {$oldmode & ~4}] - twapi::SetConsoleMode $h_out $newmode - dict set result output [list from $oldmode to $newmode] - } - if {"input" in $channels} { - set h_in [twapi::get_console_handle stdin] - set oldmode_in [twapi::GetConsoleMode $h_in] - set newmode_in [expr {$oldmode_in & ~0x200}] - twapi::SetConsoleMode $h_in $newmode_in - dict set result input [list from $oldmode_in to $newmode_in] - } - - #return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]] - return $result - } - - proc [namespace parent]::enableProcessedInput {} { - set h_in [twapi::get_console_handle stdin] - set oldmode_in [twapi::GetConsoleMode $h_in] - set newmode_in [expr {$oldmode_in | 1}] - twapi::SetConsoleMode $h_in $newmode_in - return [list stdin [list from $oldmode_in to $newmode_in]] - } - proc [namespace parent]::disableProcessedInput {} { - set h_in [twapi::get_console_handle stdin] - set oldmode_in [twapi::GetConsoleMode $h_in] - set newmode_in [expr {$oldmode_in & ~1}] - twapi::SetConsoleMode $h_in $newmode_in - return [list stdin [list from $oldmode_in to $newmode_in]] - } - } else { - - puts stderr "punk::console falling back to stty because twapi load failed" - proc [namespace parent]::enableAnsi {} { - puts stderr "punk::console::enableAnsi todo" - } - proc [namespace parent]::disableAnsi {} { - } - #? - proc [namespace parent]::enableVirtualTerminal {{channels {input output}}} { - } - proc [namespace parent]::disableVirtualTerminal {{channels {input output}}} { - } - proc [namespace parent]::enableProcessedInput {args} { - - } - proc [namespace parent]::disableProcessedInput {args} { - - } - - } - - proc [namespace parent]::enableRaw {{channel stdin}} { - #variable is_raw - variable previous_stty_state_$channel - - if {[package provide twapi] ne ""} { - set console_handle [twapi::get_console_handle stdin] - #returns dictionary - #e.g -processedinput 1 -lineinput 1 -echoinput 1 -windowinput 0 -mouseinput 0 -insertmode 1 -quickeditmode 1 -extendedmode 1 -autoposition 0 - set oldmode [twapi::get_console_input_mode] - twapi::modify_console_input_mode $console_handle -lineinput 0 -echoinput 0 - # Turn off the echo and line-editing bits - #set newmode [dict merge $oldmode [dict create -lineinput 0 -echoinput 0]] - set newmode [twapi::get_console_input_mode] - - tsv::set console is_raw 1 - #don't disable handler - it will detect is_raw - ### twapi::set_console_control_handler {} - return [list stdin [list from $oldmode to $newmode]] - } elseif {[set sttycmd [auto_execok stty]] ne ""} { - if {[set previous_stty_state_$channel] eq ""} { - set previous_stty_state_$channel [exec {*}$sttycmd -g <@$channel] - } - - exec {*}$sttycmd raw -echo <@$channel - tsv::set console is_raw 1 - #review - inconsistent return dict - return [dict create stdin [list from [set previous_stty_state_$channel] to "" note "fixme - to state not shown"]] - } else { - error "punk::console::enableRaw Unable to use twapi or stty to set raw mode - aborting" - } - } - - #note: twapi GetStdHandle & GetConsoleMode & SetConsoleCombo unreliable - fails with invalid handle (somewhat intermittent.. after stdin reopened?) - #could be we were missing a step in reopening stdin and console configuration? - - proc [namespace parent]::disableRaw {{channel stdin}} { - #variable is_raw - variable previous_stty_state_$channel - - if {[package provide twapi] ne ""} { - set console_handle [twapi::get_console_handle stdin] - set oldmode [twapi::get_console_input_mode] - # Turn on the echo and line-editing bits - twapi::modify_console_input_mode $console_handle -lineinput 1 -echoinput 1 - set newmode [twapi::get_console_input_mode] - tsv::set console is_raw 0 - return [list stdin [list from $oldmode to $newmode]] - } elseif {[set sttycmd [auto_execok stty]] ne ""} { - #stty can return info on windows - but doesn't seem to be able to set anything. - #review - is returned info even valid? - - set sttycmd [auto_execok stty] - if {[set previous_stty_state_$channel] ne ""} { - exec {*}$sttycmd [set previous_stty_state_$channel] - set previous_stty_state_$channel "" - return restored - } - exec {*}$sttycmd -raw echo <@$channel - tsv::set console is_raw 0 - #do we really want to exec stty yet again to show final 'to' state? - #probably not. We should work out how to read the stty result flags and set a result.. or just limit from,to to showing echo and lineedit states. - return [list stdin [list from "[set previous_stty_state_$channel]" to "" note "fixme - to state not shown"]] - } else { - error "punk::console::disableRaw Unable to use twapi or stty to unset raw mode - aborting" - } - } - - } lappend PUNKARGS [list { @@ -1803,7 +1852,10 @@ namespace eval punk::console { #don't set ansi_avaliable here - we want to be able to change things, retest etc. if {"windows" eq "$::tcl_platform(platform)"} { if {[package provide twapi] ne ""} { - set h_out [twapi::get_console_handle stdout] + if {[catch {twapi::get_console_handle stdout} h_out]} { + puts stderr "test_can_ansi: twapi cannot get console handle for stdout" + return 0 + } set existing_mode [twapi::GetConsoleMode $h_out] if {[expr {$existing_mode & 4}]} { #virtual terminal processing happens to be enabled - so it's supported diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/libunknown-0.1.tm b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/libunknown-0.1.tm index 3b5d35b0..f9dfaf56 100644 --- a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/libunknown-0.1.tm +++ b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/libunknown-0.1.tm @@ -80,16 +80,7 @@ tcl::namespace::eval punk::libunknown { "Experimental set of replacements for default 'package unknown' entries." }] - variable epoch - #if {![info exists epoch]} { - # set tmstate [dict create 0 {}] - # set pkgstate [dict create 0 {}] - # set tminfo [dict create current 0 epochs $tmstate] - # set pkginfo [dict create current 0 epochs $pkgstate] - - # set epoch [dict create tm $tminfo pkg $pkginfo] - #} - + variable epoch ;#don't set - can be pre-set cooperatively variable has_package_files if {[catch {package files foobaz}]} { @@ -111,6 +102,33 @@ tcl::namespace::eval punk::libunknown { #will use standard mechanism for non zipfs paths in the tm list. proc zipfs_tm_UnknownHandler {original name args} { + #------------------------------ + #shortcircuit for builtin static libraries which have no 'package provide' info - review + #This occurs for example when running 'bin\runtime.cmd run src\make.tcl shell' with punk902z.exe + # + #------------------------------ + set loaded [lsearch -inline -index 1 -nocase [info loaded] $name] + if {[llength $loaded] == 2 && [lindex $loaded 0] eq ""} { + lassign $loaded _ cased_name + interp create ptest + ptest eval [list load {} $cased_name] + set static_version [ptest eval [list package provide [string tolower $cased_name]]] + set pname [string tolower $cased_name] + if {$static_version eq ""} { + set static_version [ptest eval [list package provide $cased_name]] + set pname $cased_name + } + if {$static_version ne ""} { + if {[package vsatisfies $static_version {*}$args]} { + package ifneeded $pname $static_version [list load {} $cased_name] + interp delete ptest + return + } + } + interp delete ptest + } + #------------------------------ + # Import the list of paths to search for packages in module form. # Import the pattern used to check package names in detail. variable epoch @@ -1161,7 +1179,12 @@ tcl::namespace::eval punk::libunknown { set callerposn [lsearch $args -caller] if {$callerposn > -1} { set caller [lindex $args $callerposn+1] - #puts stderr "\x1b\[1\;33m punk::libunknown::init - caller:$caller\x1b\[m" + if {[package provide thread] ne ""} { + set tid [thread::id] + } else { + set tid "-" + } + #puts stderr "\x1b\[1\;33m punk::libunknown::init - caller:$caller tid:$tid\x1b\[m" #puts stderr "punk::libunknown::init auto_path : $::auto_path" #puts stderr "punk::libunknown::init tcl::tm::list: [tcl::tm::list]" } @@ -1184,17 +1207,17 @@ tcl::namespace::eval punk::libunknown { puts stderr "punk::libunknown::init - init while empty/unreadable tcl::tm::list and empty/unreadable ::auto_path" } - if {[namespace origin ::package] eq "::punk::libunknown::package"} { - #This is far from conclusive - there may be other renamers (e.g commandstack) + if {[info commands ::punk::libunknown::package] ne ""} { + puts stderr "punk::libunknown::init already done - unnecessary call? info frame -1: [info frame -1]" return } + #if {[namespace origin ::package] eq "::punk::libunknown::package"} { + # #This is far from conclusive - there may be other renamers (e.g commandstack) + # return + #} - if {[info commands ::punk::libunknown::package] ne ""} { - puts stderr "punk::libunknown::init already done - unnecessary call? info frame -1: [info frame -1]" - return - } variable epoch if {![info exists epoch]} { set tmstate [dict create 0 {added {}}] @@ -1222,6 +1245,7 @@ tcl::namespace::eval punk::libunknown { # or suffer additional scans.. or document ?? #ideally init should be called in each interp before any scans for packages so that the list of untracked is minimized. set pkgnames [package names] + #puts stderr "####### punk::libunknown init called with [llength $pkgnames] package names known" foreach p $pkgnames { if {[string tolower $p] in {punk::libunknown tcl::zlib tcloo tcl::oo tcl}} { continue diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repl-0.1.2.tm b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repl-0.1.2.tm index 9d199997..11cd9706 100644 --- a/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repl-0.1.2.tm +++ b/src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repl-0.1.2.tm @@ -20,18 +20,6 @@ if {[dict exists $stdin_info -mode]} { #give up for now set tcl_interactive 1 -#if {[info commands ::tcl::zipfs::root] ne ""} { -# set zr [::tcl::zipfs::root] -# if {[file join $zr app modules] in [tcl::tm::list]} { -# #todo - better way to find latest version - without package require -# set lib [file join $zr app modules punk libunknown.tm] -# if {[file exists $lib]} { -# source $lib -# punk::libunknown::init -# #package unknown {punk::libunknown::zipfs_tm_UnknownHandler punk::libunknown::zipfs_tclPkgUnknown} -# } -# } -#} #------------------------------------------------------------------------------------- if {[package provide punk::libunknown] eq ""} { #maintenance - also in src/vfs/_config/punk_main.tcl @@ -59,7 +47,7 @@ if {[package provide punk::libunknown] eq ""} { } if {$libunknown ne ""} { source $libunknown - if {[catch {punk::libunknown::init -caller repl} errM]} { + if {[catch {punk::libunknown::init -caller triggered_by_repl_package_require} errM]} { puts "error initialising punk::libunknown\n$errM" } } @@ -525,11 +513,11 @@ proc repl::start {inchan args} { set donevalue [set [namespace current]::done] if {[lindex $donevalue 0] eq "quit"} { puts "-->repl::start end $inchan $args result:'$donevalue'" - puts stderr "--> returning [lindex $donevalue 1]" + #puts stderr "repl quit --> returning [lindex $donevalue 1]" return [lindex $donevalue 1] } puts "-->repl::start end $inchan $args result:'$donevalue'" - puts stderr "__> returning 0" + #puts stderr "__> returning 0" return 0 } proc repl::post_operations {} { @@ -1408,7 +1396,6 @@ proc repl::repl_handler {inputchan prompt_config} { if {[dict get $original_input_conf -inputmode] eq "raw"} { #user or script has apparently put stdin into raw mode - update punk::console::is_raw to match set rawmode 1 - #set ::punk::console::is_raw 1 tsv::set console is_raw 1 } else { #set ::punk::console::is_raw 0 @@ -1420,9 +1407,6 @@ proc repl::repl_handler {inputchan prompt_config} { #if it's been set to raw - assume it is deliberately done this way as the user could have alternatively called punk::mode raw or punk::console::enableVirtualTerminal #by not doing this automatically - we assume the caller has a reason. } else { - #JMN FIX! - #this returns 0 in rawmode on 8.6 after repl thread changes - #set rawmode [set ::punk::console::is_raw] set rawmode [tsv::get console is_raw] } @@ -1811,8 +1795,6 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config set infoprompt [dict get $prompt_config infoprompt] set debugprompt [dict get $prompt_config debugprompt] - - #set rawmode [set ::punk::console::is_raw] set rawmode [tsv::get console is_raw] if {!$rawmode} { #puts stderr "-->got [ansistring VIEW -lf 1 $stdinlines]<--" @@ -2615,6 +2597,34 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config } #editbuf + + #after any external command - raw mode as the console sees it can be disabled + #set it to match current state of the tsv + if {[tsv::get console is_raw]} { + if {$::tcl_platform(platform) eq "windows"} { + #review + #we are in parent process - twapi might not be loaded here - even if it is in the code interp + catch {package require twapi} + } + set sinfo [chan configure stdin] + if {[dict exists $sinfo -inputmode]} { + if {[dict get $sinfo -inputmode] ne "raw"} { + set re_enable_required 1 + } else { + set re_enable_required 0 + } + } else { + # -inputmode unavailable + #tcl 8.6 doesn't have -inputmode - meaning it has to call punk:console::enableRaw each time + #enableRaw on windows without twapi involves launching a pwsh process - which gives a noticeable lag in keyboard input. + #enableRaw on Unix involves a call to stty - which is generally fast - but still to be avoided if not required. + set re_enable_required 1 + } + #puts stderr "-here- re-enabling raw" + if {$re_enable_required} { + punk::console::enableRaw + } + } } else { #append commandstr \n if {$::punk::repl::signal_control_c} { @@ -2828,7 +2838,7 @@ namespace eval repl { } if {$libunknown ne ""} { source $libunknown - if {[catch {punk::libunknown::init -caller "repl init_script"} errM]} { + if {[catch {punk::libunknown::init -caller "repl::init init_script parent interp"} errM]} { puts "repl::init problem - error initialising punk::libunknown\n$errM" } #package require punk::lib @@ -2858,10 +2868,10 @@ namespace eval repl { #thread::send to caller defined interp targets (reference?) #snit required for icomm if {[catch {package require snit} errM]} { - puts stdout "punk::repl::initscript lib load fail ---snit $errM" + #puts stdout "punk::repl::initscript: lib load fail ---snit $errM" } if {[catch {package require punk::icomm} errM]} { - puts stdout "punk::repl::initscript lib load fail ---icomm $errM" + #puts stdout "punk::repl::initscript: lib load fail ---icomm $errM" } #----- @@ -2872,7 +2882,7 @@ namespace eval repl { #first use can raise error being a version number e.g 0.1.0 - why? lassign [tcl::chan::fifo2] ::punk::repl::codethread::repltalk replside } errMsg]} { - puts stdout "punk::repl::initscript tcl::chan::fifo2 error: $errM" + puts stdout "punk::repl::initscript tcl::chan::fifo2 error: $errMsg" } else { #experimental? #puts stdout "transferring chan $replside to thread %replthread%" @@ -3519,6 +3529,8 @@ namespace eval repl { #----------------------------------------------------------------------------- if {[package provide punk::libunknown] eq ""} { + namespace eval ::punk::libunknown {} + set ::punk::libunknown::epoch %lib_epoch% set libunks [list] foreach tm_path [tcl::tm::list] { set punkdir [file join $tm_path punk] @@ -3543,7 +3555,7 @@ namespace eval repl { } if {$libunknown ne ""} { source $libunknown - if {[catch {punk::libunknown::init -caller "repl init_script punk"} errM]} { + if {[catch {punk::libunknown::init -caller "repl::init init_script code interp for punk"} errM]} { puts "error initialising punk::libunknown\n$errM" } } diff --git a/src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl b/src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl index 1736d3d9..c1d3f906 100644 --- a/src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl +++ b/src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl @@ -31,22 +31,28 @@ namespace eval ::punkboot::lib { #for some purposes (whether a source folder is likely to have any useful content) we are interested in non dotfile/dotfolder immediate contents of a folder, but not whether a particular platform #considers them hidden or not. proc folder_nondotted_children {folder} { - if {![file isdirectory $folder]} {error "punkboot::lib::folder_nondotted_children error. Supplied folder '$folder' is not a directory"} - set contents [glob -nocomplain -dir $folder *] + set normfolder [file normalize $folder] + if {![file isdirectory $normfolder]} {error "punkboot::lib::folder_nondotted_children error. Supplied folder '$folder' is not a directory"} + set contents [glob -nocomplain -dir $folder -tails *] #some platforms (windows) return dotted entries with *, although most don't - return [lsearch -all -inline -not $contents .*] + set nondotted_tails [lsearch -all -inline -not $contents .*] + return [lmap ftail $nondotted_tails {file join $folder $ftail}] } proc folder_nondotted_folders {folder} { - if {![file isdirectory $folder]} {error "punkboot::lib::folder_nondotted_folders error. Supplied folder '$folder' is not a directory"} - set contents [glob -nocomplain -dir $folder -types d *] + set normfolder [file normalize $folder] + if {![file isdirectory $normfolder]} {error "punkboot::lib::folder_nondotted_folders error. Supplied folder '$folder' is not a directory"} + set contents [glob -nocomplain -dir $folder -types d -tails *] #some platforms (windows) return dotted entries with *, although most don't - return [lsearch -all -inline -not $contents .*] + set nondotted_tails [lsearch -all -inline -not $contents .*] + return [lmap ftail $nondotted_tails {file join $folder $ftail}] } proc folder_nondotted_files {folder} { - if {![file isdirectory $folder]} {error "punkboot::lib::folder_nondotted_files error. Supplied folder '$folder' is not a directory"} - set contents [glob -nocomplain -dir $folder -types f $folder *] + set normfolder [file normalize $folder] + if {![file isdirectory $normfolder]} {error "punkboot::lib::folder_nondotted_files error. Supplied folder '$folder' is not a directory"} + set contents [glob -nocomplain -dir $folder -types f $folder -tails *] #some platforms (windows) return dotted entries with *, although most don't - return [lsearch -all -inline -not $contents .*] + set nondotted_tails [lsearch -all -inline -not $contents .*] + return [lmap ftail $nondotted_tails {file join $folder $ftail}] } proc tm_version_isvalid {versionpart} { #Needs to be suitable for use with Tcl's 'package vcompare' @@ -289,6 +295,10 @@ if {"::try" ni [info commands ::try]} { # This allows a source update via 'fossil update' 'git pull' etc to pull in a minimal set of support modules for the boot script # and load these in preference to ones that may have been in the interp's tcl::tm::list or auto_path due to environment variables set startdir [pwd] + +set scriptdir [file dirname [file normalize [info script]]] +#puts "SCRIPTDIR: $scriptdir" + #we are focussed on pure-tcl libs/modules in bootsupport for now. #There may be cases where we want to use compiled packages from src/bootsupport/modules_tcl9 etc #REVIEW - punkboot can really speed up with appropriate accelerators and/or external binaries @@ -303,18 +313,22 @@ set bootsupport_library_paths [list] set this_platform_generic [punkboot::lib::platform_generic] #we always create these lists in order of desired precedence. # - this is the same order when adding to auto_path - but will need to be reversed when using tcl:tm::add -if {[file exists [file join $startdir src bootsupport]]} { - lappend bootsupport_module_paths [file join $startdir src bootsupport modules_tcl$::tclmajorv] ;#more version-specific modules slightly higher in precedence order - lappend bootsupport_module_paths [file join $startdir src bootsupport modules] - lappend bootsupport_library_paths [file join $startdir src bootsupport lib_tcl$::tclmajorv/allplatforms] ;#more version-specific pkgs slightly higher in precedence order - lappend bootsupport_library_paths [file join $startdir src bootsupport lib_tcl$::tclmajorv/$this_platform_generic] ;#more version-specific pkgs slightly higher in precedence order - lappend bootsupport_library_paths [file join $startdir src bootsupport lib] +if {[file exists [file join $scriptdir bootsupport]]} { + set bootsupportdir [file join $scriptdir bootsupport] + puts stderr "Using bootsupport dir $bootsupportdir" + + lappend bootsupport_module_paths [file join $bootsupportdir modules_tcl$::tclmajorv] ;#more version-specific modules slightly higher in precedence order + lappend bootsupport_module_paths [file join $bootsupportdir modules] + lappend bootsupport_library_paths [file join $bootsupportdir lib_tcl$::tclmajorv/allplatforms] ;#more version-specific pkgs slightly higher in precedence order + lappend bootsupport_library_paths [file join $bootsupportdir lib_tcl$::tclmajorv/$this_platform_generic] ;#more version-specific pkgs slightly higher in precedence order + lappend bootsupport_library_paths [file join $bootsupportdir lib] } else { - lappend bootsupport_module_paths [file join $startdir bootsupport modules_tcl$::tclmajorv] - lappend bootsupport_module_paths [file join $startdir bootsupport modules] - lappend bootsupport_library_paths [file join $startdir bootsupport lib_tcl$::tclmajorv/allplatforms] - lappend bootsupport_library_paths [file join $startdir bootsupport lib_tcl$::tclmajorv/$this_platform_generic] - lappend bootsupport_library_paths [file join $startdir bootsupport lib] + puts stderr "No bootsupport dir for script [info script] at [file join $scriptdir bootsupport]" + #lappend bootsupport_module_paths [file join $startdir bootsupport modules_tcl$::tclmajorv] + #lappend bootsupport_module_paths [file join $startdir bootsupport modules] + #lappend bootsupport_library_paths [file join $startdir bootsupport lib_tcl$::tclmajorv/allplatforms] + #lappend bootsupport_library_paths [file join $startdir bootsupport lib_tcl$::tclmajorv/$this_platform_generic] + #lappend bootsupport_library_paths [file join $startdir bootsupport lib] } set bootsupport_paths_exist 0 foreach p [list {*}$bootsupport_module_paths {*}$bootsupport_library_paths] { @@ -406,8 +420,8 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} { tcl::tm::add {*}[lreverse $bootsupport_module_paths] {*}[lreverse $sourcesupport_module_paths] ;#tm::add works like LIFO. sourcesupport_module_paths end up earliest in resulting tm list. set ::auto_path [list {*}$sourcesupport_library_paths {*}$bootsupport_library_paths] } - puts "----> auto_path $::auto_path" - puts "----> tcl::tm::list [tcl::tm::list]" + #puts "----> auto_path $::auto_path" + #puts "----> tcl::tm::list [tcl::tm::list]" #maint: also in punk::repl package #-------------------------------------------------------- @@ -435,22 +449,26 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} { } if {$libunknown ne ""} { source $libunknown - if {[catch {punk::libunknown::init -caller main.tcl} errM]} { - puts "error initialising punk::libunknown\n$errM" + if {[catch {punk::libunknown::init -caller make.tcl} errM]} { + puts stderr "error initialising punk::libunknown\n$errM" } + #puts stdout " *** [package names]" + #puts stdout " **** [dict get $::punk::libunknown::epoch pkg untracked]" + } else { + puts stderr "Failed to find punk::libunknown" } #-------------------------------------------------------- #package require Thread - puts "---->tcl_library [info library]" - puts "---->loaded [info loaded]" + #puts "---->tcl_library [info library]" + #puts "---->loaded [info loaded]" # - the full repl requires Threading and punk,shellfilter,shellrun to call and display properly. # tm list already indexed - need 'package forget' to find modules based on current tcl::tm::list #These are strong dependencies - package forget punk::mix - package forget punk::repo - package forget punkcheck + #package forget punk::mix + #package forget punk::repo + #package forget punkcheck package require punk::repo ;#todo - push our requirements to a smaller punk::repo::xxx package with minimal dependencies package require punk::mix @@ -464,6 +482,7 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} { set package_paths_modified 1 #------------------------------------------------------------------------------ + #puts "----> llength package names [llength [package names]]" } set ::punkboot::pkg_requirements_found [list] @@ -479,7 +498,9 @@ set ::punkboot::bootsupport_requirements [dict create\ punkcheck [list]\ fauxlink [list version "0.1.1-"]\ textblock [list version 0.1.1-]\ + fileutil [list]\ fileutil::traverse [list]\ + struct::list [list]\ md5 [list version 2-]\ ] @@ -1282,7 +1303,41 @@ proc ::punkboot::punkboot_gethelp {args} { return $h } + + + set scriptargs $::argv +punk::args::define { + @id -id punkmake + @cmd -name punkmake\ + -summary\ + "Project builder"\ + -help\ + "" + @form -form help + @leaders + subcommand -type "literal(help)" + @opts + @values + what -type string -choices {modules libs shell} + + @form -form modules + subcommand -type "literal(modules)" + + @form -form libs + subcommand -type "literal(libs)" + + @form -form shell + subcommand -type "literal(shell)" + arg -type any -optional 1 -multiple 1 +} +#set argd [punk::args::parse $scriptargs -form 0 withid punkmake] +##lassign [dict values $argd] leaders opts values received +# +#puts stdout [punk::args::usage -scheme nocolour punkmake] +#exit 1 + + set do_help 0 if {![llength $scriptargs]} { set do_help 1 @@ -1294,6 +1349,8 @@ if {![llength $scriptargs]} { } } } + + set commands_found [list] foreach a $scriptargs { if {![string match -* $a]} { @@ -1310,6 +1367,8 @@ if {[llength $commands_found] != 1 } { puts stderr "Unknown command: [lindex $commands_found 0]\n\n" set do_help 1 } + + if {$do_help} { puts stdout "Checking package availability..." set ::punkboot::pkg_availability [::punkboot::check_package_availability -quiet 1 $::punkboot::bootsupport_requirements] @@ -1325,6 +1384,8 @@ if {$do_help} { exit 0 } + + set ::punkboot::command [lindex $commands_found 0] @@ -1414,14 +1475,15 @@ if {$::punkboot::command eq "check"} { if {$package_paths_modified} { set tm_list_boot [tcl::tm::list] tcl::tm::remove {*}$tm_list_boot - foreach p [lreverse $original_tm_list] { + + set lower_prio [list] + foreach p $original_tm_list { if {$p ni $tm_list_boot} { - tcl::tm::add $p + lappend lower_prio $p } } - foreach p [lreverse $tm_list_boot] { - tcl::tm::add $p - } + tcl::tm::add {*}[lreverse $lower_prio] {*}[lreverse $tm_list_boot] + #set ::auto_path [list $bootsupport_lib {*}$original_auto_path] lappend ::auto_path {*}$original_auto_path } @@ -1489,6 +1551,28 @@ if {![array size A]} { punkboot::define_global_ansi } +#puts stderr ">>>>>>+ loaded:[info loaded]" +#puts stderr "llength package names: [llength [package names]]" +if {[info exists ::punk::libunknown::epoch]} { + set untracked [dict get $::punk::libunknown::epoch pkg untracked] + #puts stderr "punk::libunknown::epoch exists" +} else { + set untracked [list] + #puts stderr "punk::libunknown::epoch does not exist" +} +#REVIEW - we shouldn't need to manually set the untracked packages - punk::libunknown::init should have done it? +foreach p [package names] { + if {![dict exists $untracked $p]} { + dict set untracked $p "" + } +} +dict set ::punk::libunknown::epoch pkg untracked $untracked + +if {[package provide punk::libunknown] eq ""} { + puts "punk::libunknown not loaded" +} else { + puts "punk::libunknown loaded" +} dict for {pkg pkginfo} $::punkboot::bootsupport_requirements { set verspec [dict get $pkginfo version] ;#version wanted specification always exists and is empty or normalised if {[catch {package require $pkg {*}$verspec} errM]} { @@ -1557,14 +1641,13 @@ if {$::punkboot::command eq "info"} { if {$::punkboot::command eq "shell"} { - puts stderr ">>>>>> loaded:[info loaded]" + package require struct::list package require punk package require punk::repl #todo - make procs vars etc from this file available? puts stderr "punk boot shell not implemented - dropping into ordinary punk shell." - repl::init set replresult [repl::start stdin -title make.tcl] #review @@ -3059,6 +3142,8 @@ foreach vfstail $vfs_tails { exec {*}$::sdxpath unwrap [file rootname $building_runtime].tail ;#extracts to folder named [file rootname $building_runtime].vfs e.g build_tclkit9.0.2-win64-dyn.vfs #file rename to existing target dir would copy folder into target dir if {![file exists $targetvfs]} { + #delay + after 1000 file rename [file rootname $building_runtime].vfs $targetvfs } else { merge_over [file rootname $building_runtime].vfs $targetvfs diff --git a/src/runtime/mapvfs.config b/src/runtime/mapvfs.config index 0a6c55d1..1545b1b3 100644 --- a/src/runtime/mapvfs.config +++ b/src/runtime/mapvfs.config @@ -13,7 +13,7 @@ #- myproject.vfs #- punk86.vfs #AAA -#tclkit86bi.exe {punk8win.vfs punkbi kit} +tclkit86bi.exe {punk8win.vfs punkbi kit} #c:\tcl.bawt tcl 8.6.13 bawt ##tclkit-win64-dyn.exe {punk86bawt.vfs punkbawt kit} @@ -29,6 +29,8 @@ #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} diff --git a/src/scriptapps/bin/readme.txt b/src/scriptapps/bin/readme.txt new file mode 100644 index 00000000..f8c0d3ab --- /dev/null +++ b/src/scriptapps/bin/readme.txt @@ -0,0 +1 @@ +scriptapps targeting the project's bin directory go here. \ No newline at end of file diff --git a/src/scriptapps/example_out.bat b/src/scriptapps/example_out.bat deleted file mode 100644 index c45adc6c..00000000 --- a/src/scriptapps/example_out.bat +++ /dev/null @@ -1,743 +0,0 @@ -: "punk MULTISHELL - shebangless polyglot for Tcl Perl sh bash cmd pwsh powershell" + "[rename set s;proc Hide x {proc $x 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 + \ -: "[Hide @GOTO; Hide =begin; Hide @REM] #not necessary but can help avoid errs in testing" + -: << 'HEREDOC1B_HIDE_FROM_BASH_AND_SH' -: STRONG SUGGESTION: DO NOT MODIFY FIRST LINE OF THIS SCRIPT - except for first double quoted section. -: shebang line is not required on unix or windows and will reduce functionality and/or portability. -: Even comment lines can be part of the functionality of this script (both on unix and windows) - modify with care. -@GOTO :skip_perl_pod_start ^; -=begin excludeperl -: skip_perl_pod_start -: 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 It should remain portable between unix-like OSes & windows if the proper structure is maintained. -@REM ############################################################################################################################ -@REM On windows, change the value of nextshell to one of the listed 2 digit values if desired, and add code within payload sections for tcl,sh,bash,powershell as appropriate. -@REM This wrapper can be edited manually (carefully!) - or sh,bash,tcl,powershell scripts can be wrapped using the Tcl-based punkshell system -@REM e.g from within a running punkshell: deck scriptwrap.multishell -outputfolder -@REM On unix-like systems, call with sh, bash or tclsh. (powershell untested on unix - and requires wscript if security elevation is used) -@REM Due to lack of shebang (#! line) Unix-like systems will probably (hopefully) default to sh if the script is called without an interpreter - but it may depend on the shell in use when called. -@REM If you find yourself really wanting/needing to add a shebang line - do so on the basis that the script will exist on unix-like systems only. -@REM in batch scripts - array syntax with square brackets is a simulation of arrays or associative arrays. -@REM note that many shells linked as sh do not support substition syntax and may fail - e.g dash etc - generally bash should be used in this context -@SETLOCAL EnableExtensions EnableDelayedExpansion -@SET "validshelltypes= powershell______ sh______________ wslbash_________ bash____________ tcl_____________ perl____________" -@REM for batch - only win32 is relevant - but other scripts on other platforms also parse the nextshell block to determine next shell to launch -@REM nextshellpath and nextshelltype indices (underscore-padded to 16wide) are "other" plus those returned by Tcl platform pkg e.g win32,linux,freebsd,macosx -@REM The horrible underscore-padded fixed-widths are to keep the batch labels aligned whilst allowing values to be set -@REM If more than 32 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___________]=tclsh___________________________" -@SET "nextshelltype[win32___________]=tcl_____________" -@SET "nextshellpath[dragonflybsd____]=/usr/bin/env tclsh______________" -@SET "nextshelltype[dragonflybsd____]=tcl_____________" -@SET "nextshellpath[freebsd_________]=/usr/bin/env tclsh______________" -@SET "nextshelltype[freebsd_________]=tcl_____________" -@SET "nextshellpath[netbsd__________]=/usr/bin/env tclsh______________" -@SET "nextshelltype[netbsd__________]=tcl_____________" -@SET "nextshellpath[linux___________]=/usr/bin/env tclsh______________" -@SET "nextshelltype[linux___________]=tcl_____________" -@SET "nextshellpath[macosx__________]=/usr/bin/env tclsh______________" -@SET "nextshelltype[macosx__________]=tcl_____________" -@SET "nextshellpath[other___________]=/usr/bin/env tclsh______________" -@SET "nextshelltype[other___________]=tcl_____________" -: -@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). -: -@SET "asadmin=0" -: -@REM @ECHO nextshelltype is %nextshelltype[win32___________]% -@REM @SET "selected_shelltype=%nextshelltype[win32___________]%" -@SET "selected_shelltype=%nextshelltype[win32___________]%" -@REM @ECHO selected_shelltype %selected_shelltype% -@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 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 -@REM ## ### ### ### ### ### ### ### ### ### ### ### ### ### -@REM -- cmd/batch file section (ignored on unix but should be left in place) -@REM -- This section intended mainly to launch the next shell (and to escalate privileges if necessary) -@REM -- Avoid customising this if you are not familiar with batch scripting. cmd/batch script can be useful, but is probably the least expressive language and most error prone. -@REM -- For example - as this file needs to use unix-style lf line-endings - the label scanner is susceptible to the 512Byte boundary issue: https://www.dostips.com/forum/viewtopic.php?t=8988#p58888 -@REM -- This label issue can be triggered/abused in files with crlf line endings too - but it is less likely to happen accidentaly. -@REm -- See also: https://stackoverflow.com/questions/4094699/how-does-the-windows-command-interpreter-cmd-exe-parse-scripts/4095133#4095133 -@REM ############################################################################################################################ -@REM -- Due to this issue -seemingly trivial edits of the batch file section can break the script! (for Windows anyway) -@REM -- Even something as simple as adding or removing an @REM -@REM -- From within punkshell - use: -@REM -- deck scriptwrap.checkfile -@REM -- to check your templates or final wrapped scripts for byte boundary issues -@REM -- It will report any labels that are on boundaries -@REM -- This is why the nextshell value above is a 2 digit key instead of a string - so that editing the value doesn't change the byte offsets. -@REM -- Editing your sh,bash,tcl,pwsh payloads is much less likely to cause an issue. There is the possibility of the final batch :exit_multishell label spanning a boundary - so testing using deck scriptwrap.checkfile is still recommended. -@REM -- Alternatively, as you should do anyway - test the final script on windows -@REM -- Aside from adding comments/whitespace to tweak the location of labels - you can try duplicating the label (e.g just add the label on a line above) but this is not guaranteed to work in all situations. -@REM -- '@REM' is a safer comment mechanism than a leading colon - which is used sparingly here. -@REM -- A colon anywhere in the script that happens to land on a 512 Byte boundary (from file start or from a callsite) could be misinterpreted as a label -@REM -- It is unknown what versions of cmd interpreters behave this way - and deck scriptwrap.checkfile doesn't check all such boundaries. -@REm -- For this reason, batch labels should be chosen to be relatively unlikely to collide with other strings in the file, and simple names such as :exit or :end should probably be avoided -@REM ############################################################################################################################ -@REM -- custom windows payloads should be in powershell,tclsh (or sh/bash if available) code sections -@REM ## ### ### ### ### ### ### ### ### ### ### ### ### ### -@SET "winpath=%~dp0" -@SET "fname=%~nx0" -@REM @ECHO fname %fname% -@REM @ECHO winpath %winpath% -@REM @ECHO commandlineascalled %0 -@REM @ECHO commandlineresolved %~f0 -@CALL :getNormalizedScriptTail nftail -@REM @ECHO normalizedscripttail %nftail% -@CALL :getFileTail %0 clinetail -@REM @ECHO clinetail %clinetail% -@CALL :stringToUpper %~nx0 capscripttail -@REM @ECHO capscriptname: %capscripttail% - -@IF "%nftail%"=="%capscripttail%" ( - @ECHO forcing asadmin=1 due to file name on filesystem being uppercase - @SET "asadmin=1" -) else ( - @CALL :stringToUpper %clinetail% capcmdlinetail - @REM @ECHO capcmdlinetail !capcmdlinetail! - IF "%clinetail%"=="!capcmdlinetail!" ( - @ECHO forcing asadmin=1 due to cmdline scriptname in uppercase - @set "asadmin=1" - ) -) -@SET "vbsGetPrivileges=%temp%\punk_bat_elevate_%fname%.vbs" -@SET arglist=%* -@SET "qstrippedargs=args%arglist%" -@SET "qstrippedargs=%qstrippedargs:"=%" -@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" ( - GOTO :gotPrivileges -) -@IF !asadmin!==1 ( - net file 1>NUL 2>NUL - @IF '!errorlevel!'=='0' ( GOTO :gotPrivileges ) else ( GOTO :getPrivileges ) -) -@REM padding -@REM padding -@REM padding -@REM padding -@REM padding -@REM padding -@REM padding -@REM padding -@REM padding -@REM padding -@REM padding -@REM padding -@GOTO skip_privileges -: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 For Each strArg in WScript.Arguments >> "%vbsGetPrivileges%" -@ECHO args = args ^& strArg ^& " " >> "%vbsGetPrivileges%" -@ECHO Next >> "%vbsGetPrivileges%" -@ECHO UAC.ShellExecute "%~dp0%~n0%~x0", args, "", "runas", 1 >> "%vbsGetPrivileges%" -@ECHO Launching script in new windows due to administrator elevation -@"%SystemRoot%\System32\WScript.exe" "%vbsGetPrivileges%" %* -@EXIT /B - -:gotPrivileges -@REM setlocal & pushd . -@PUSHD . -@cd /d %~dp0 -@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" ( - @DEL "%vbsGetPrivileges%" 1>nul 2>nul - @SET arglist=%arglist:~14% -) - -: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" ( - @SET need_ps1=1 -) ELSE ( - fc "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >nul || goto different - @REM @ECHO "files same" - @SET need_ps1=0 -) -@GOTO :pscontinue -:different -@REM @ECHO "files differ" -@SET need_ps1=1 -:pscontinue -@IF !need_ps1!==1 ( - COPY "%~dp0%~n0%~x0" "%~dp0%~n0.ps1" >NUL -) -@REM avoid using CALL to launch pwsh,tclsh etc - it will intercept some args such as /? -@IF "%selected_shelltype_trimmed%"=="powershell" ( - REM pws 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 - pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; 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 ( - pwsh -nop -nol -c set-executionpolicy -Scope Process Unrestricted; "%~dp0%~n0.ps1" %arglist% - SET task_exitcode=!errorlevel! - ) ELSE ( - REM CALL powershell -nop -nol -c write-host powershell-found - REM powershell -nop -nol -file "%~dp0%~n0.ps1" %* - powershell -nop -nol -c set-executionpolicy -Scope Process Unrestricted; %~dp0%~n0.ps1" %arglist% - SET task_exitcode=!errorlevel! - ) -) ELSE ( - IF "%selected_shelltype_trimmed%"=="wslbash" ( - CALL :getWslPath %winpath% wslpath - REM ECHO wslfullpath "!wslpath!%fname%" - %selected_shellpath_trimmed% "!wslpath!%fname%" %arglist% - SET task_exitcode=!errorlevel! - ) ELSE ( - REM perl or tcl or sh or bash - IF NOT "x%keyRemoved%"=="x%validshelltypes%" ( - 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 - %selected_shellpath_trimmed% "%~dp0%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 - @REM boundary padding - @REM boundary padding - @REM boundary padding - GOTO :exit_multishell - ) - ) -) -@REM batch file library functions -@REM boundary padding -@GOTO :endlib - -:getWslPath -@SETLOCAL - @SET "_path=%~p1" - @SET "name=%~nx1" - @SET "drive=%~d1" - @SET "rtrn=%~2" - @REM Although drive letters on windows are normally upper case wslbash seems to expect lower case drive letters - @CALL :stringToLower %drive ldrive - @SET "result=/mnt/%ldrive:~0,1%%_path:\=/%%name%" -@ENDLOCAL & ( - @if "%~2" neq "" ( - SET "%rtrn%=%result%" - ) ELSE ( - ECHO %result% - ) -) -@EXIT /B - -: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 -@REM This function is designed explicitly to preserve capitalisation -@REM accepts full paths with either / or \ as delimiters - or -@SETLOCAL - @SET "rtrn=%~2" - @SET "arg=%~1" - @REM @SET "result=%_arg:*/=%" - @REM @SET "result=%~1" - @SET LF=^ - - - : The above 2 empty lines are important. Don't remove - @CALL :stringContains "!arg!" "\" hasBackSlash - @IF "!hasBackslash!"=="true" ( - @for %%A in ("!LF!") do @( - @FOR /F %%B in ("!arg:\=%%~A!") do @set "result=%%B" - ) - ) ELSE ( - @CALL :stringContains "!arg!" "/" hasForwardSlash - @IF "!hasForwardSlash!"=="true" ( - @FOR %%A in ("!LF!") do @( - @FOR /F %%B in ("!arg:/=%%~A!") do @set "result=%%B" - ) - ) ELSE ( - @set "result=%arg%" - ) - ) -@ENDLOCAL & ( - @if "%~2" neq "" ( - @SET "%rtrn%=%result%" - ) ELSE ( - @ECHO %result% - ) -) -@EXIT /B -@REM boundary padding -@REM boundary padding -:getNormalizedScriptTail -@SETLOCAL - @SET "result=%~nx0" - @SET "rtrn=%~1" -@ENDLOCAL & ( - @IF "%~1" neq "" ( - @SET "%rtrn%=%result%" - ) ELSE ( - @ECHO %result% - ) -) -@EXIT /B - -:getNormalizedFileTailFromPath -@REM warn via echo, and do not set return variable if path not found -@REM note that %~nx1 does not preserve case of provided path - hence the name 'normalized' -@REM boundary padding -@REM boundary padding -@REM boundary padding -@REM boundary padding -@SETLOCAL - @CALL :stringContains %~1 "\" hasBackSlash - @CALL :stringContains %~1 "/" hasForwardSlash - @IF "%hasBackslash%-%hasForwardslash%"=="false-false" ( - @SET "P=%cd%%~1" - @CALL :getNormalizedFileTailFromPath "!P!" ftail2 - @SET "result=!ftail2!" - ) else ( - @IF EXIST "%~1" ( - @SET "result=%~nx1" - ) else ( - @ECHO error getNormalizedFileTailFromPath file not found: %~1 - @EXIT /B 1 - ) - ) - @SET "rtrn=%~2" -@ENDLOCAL & ( - @IF "%~2" neq "" ( - SET "%rtrn%=%result%" - ) ELSE ( - @ECHO getNormalizedFileTailFromPath %1 result: %result% - ) -) -@EXIT /B - -:stringContains -@REM usage: @CALL:stringContains string needle returnvarname -@SETLOCAL - @SET "rtrn=%~3" - @SET "string=%~1" - @SET "needle=%~2" - @IF "!string:%needle%=!"=="!string!" @( - @SET "result=false" - ) ELSE ( - @SET "result=true" - ) -@ENDLOCAL & ( - @IF "%~3" neq "" ( - @SET "%rtrn%=%result%" - ) ELSE ( - @ECHO stringContains %string% %needle% result: %result% - ) -) -@EXIT /B -@REM boundary padding -@REM boundary padding -:stringToUpper -@SETLOCAL - @SET "rtrn=%~2" - @SET "string=%~1" - @SET "capstring=%~1" - @FOR %%A in (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) DO @( - @SET "capstring=!capstring:%%A=%%A!" - ) - @SET "result=!capstring!" -@ENDLOCAL & ( - @IF "%~2" neq "" ( - @SET "%rtrn%=%result%" - ) ELSE ( - @ECHO stringToUpper %string% result: %result% - ) -) -@EXIT /B -:stringToLower -@SETLOCAL - @SET "rtrn=%~2" - @SET "string=%~1" - @SET "retstring=%~1" - @FOR %%A in (a b c d e f g h i j k l m n o p q r s t u v w x y z) DO @( - @SET "retstring=!retstring:%%A=%%A!" - ) - @SET "result=!retstring!" -@ENDLOCAL & ( - @IF "%~2" neq "" ( - @SET "%rtrn%=%result%" - ) ELSE ( - @ECHO stringToLower %string% result: %result% - ) -) -@EXIT /B -@REM boundary padding -@REM boundary padding -:stringTrimTrailingUnderscores -@SETLOCAL - @SET "rtrn=%~2" - @SET "string=%~1" - @SET "trimstring=%~1" - @REM trim up to 31 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:__###=###% - @SET trimstring=%trimstring:_###=###% - @SET trimstring=%trimstring:###=% - @SET "result=!trimstring!" -@ENDLOCAL & ( - @IF "%~2" neq "" ( - @SET "%rtrn%=%result%" - ) ELSE ( - @ECHO stringTrimTrailingUnderscores %string% result: %result% - ) -) -@EXIT /B -:isNumeric -@SETLOCAL - @SET "notnumeric="&FOR /F "delims=0123456789" %%i in ("%1") do set "notnumeric=%%i" - @IF defined notnumeric ( - @SET "result=false" - ) else ( - @SET "result=true" - ) - @SET "rtrn=%~2" -@ENDLOCAL & ( - @IF "%~2" neq "" ( - @SET "%rtrn%=%result%" - ) ELSE ( - @ECHO %result% - ) -) -@EXIT /B - -:endlib -: \ -@REM padding -@REM padding -@REM @SET taskexit_code=!errorlevel! & goto :exit_multishell -@GOTO :exit_multishell -# } -# -*- tcl -*- -# ## ### ### ### ### ### ### ### ### ### ### ### ### ### -# -- 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 -# -- i.e it is a polyglot file. -# -- 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 ./filename.polypunk.cmd in sh or bash -# -- e.g tclsh filename.cmd -# -- -# ## ### ### ### ### ### ### ### ### ### ### ### ### ### -rename set ""; rename s set; set k {-- "$@" "a}; if {[info exists ::env($k)]} {unset ::env($k)} ;# tidyup and restore -Hide :exit_multishell;Hide {<#};Hide '@ -namespace eval ::punk::multishell { - set last_script_root [file dirname [file normalize ${::argv0}/__]] - set last_script [file dirname [file normalize [info script]/__]] - if {[info exists ::argv0] && - $last_script eq $last_script_root - } { - set ::punk::multishell::is_main($last_script) 1 ;#run as executable/script - likely desirable to launch application and return an exitcode - } else { - set ::punk::multishell::is_main($last_script) 0 ;#sourced - likely to be being used as a library - no launch, no exit. Can use return. - } - if {"::punk::multishell::is_main" ni [info commands ::punk::multishell::is_main]} { - proc ::punk::multishell::is_main {{script_name {}}} { - if {$script_name eq ""} { - set script_name [file dirname [file normalize [info script]/--]] - } - if {![info exists ::punk::multishell::is_main($script_name)]} { - #e.g a .dll or something else unanticipated - puts stderr "Warning punk::multishell didn't recognize info script result: $script_name - will treat as if sourced and return instead of exiting" - puts stderr "Info: script_root: [file dirname [file normalize ${::argv0}/__]]" - return 0 - } - return [set ::punk::multishell::is_main($script_name)] - } - } -} -# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin Tcl Payload -#puts "script : [info script]" -#puts "argcount : $::argc" -#puts "argvalues: $::argv" -#puts "argv0 : $::argv0" -# -- --- --- --- --- --- --- --- --- --- --- --- - - -# -# - -# -# - - -# -# - - -# -- --- --- --- --- --- --- --- --- --- --- --- -# -- Best practice is to always return or exit above, or just by leaving the below defaults in place. -# -- If the multishell script is modified to have Tcl below the Tcl Payload section, -# -- then Tcl bracket balancing needs to be carefully managed in the shell and powershell sections below. -# -- Only the # in front of the two relevant if statements below needs to be removed to enable Tcl below -# -- but the sh/bash 'then' and 'fi' would also need to be uncommented. -# -- This facility left in place for experiments on whether configuration payloads etc can be appended -# -- to tail of file - possibly binary with ctrl-z char - but utility is dependent on which other interpreters/shells -# -- can be made to ignore/cope with such data. -if {[::punk::multishell::is_main]} { - exit 0 -} else { - return -} -# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end Tcl Payload -# end hide from unix shells \ -HEREDOC1B_HIDE_FROM_BASH_AND_SH -# sh/bash \ -shift && set -- "${@:1:$#-1}" -#------------------------------------------------------ -# -- This if block only needed if Tcl didn't exit or return above. -if false==false # else { - then - : # -# ## ### ### ### ### ### ### ### ### ### ### ### ### ### -# -- sh/bash script section -# -- leave as is if all that is required is launching the Tcl payload" -# -- -# -- Note that sh/bash script isn't called when running a .bat/.cmd from cmd.exe on windows by default -# -- adjust the %nextshell% value above -# -- if sh/bash scripting needs to run on windows too. -# -- -# ## ### ### ### ### ### ### ### ### ### ### ### ### ### -# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin sh Payload -exitcode=0 -#printf "start of bash or sh code" - -# -echo "output from example.sh wrapped in polyglot script" -# - -# -- --- --- --- --- --- --- --- -# -#-- 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 -#------------------------------------------------------ -fi -exit ${exitcode} -# ## ### ### ### ### ### ### ### ### ### ### ### ### ### -# -- Perl script section -# -- leave the script below as is, if all that is required is launching the Tcl payload" -# -- -# -- Note that perl script isn't called by default when simply running this script by name -# -- adjust the nextshell value at the top of the script to point to perl -# -- -# ## ### ### ### ### ### ### ### ### ### ### ### ### ### -=cut -#!/user/bin/perl -# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin perl Payload -my $exit_code = 0; -#use ExtUtils::Installed; -#my $installed = ExtUtils::Installed->new(); -#my @modules = $installed->modules(); -#print "Modules:\n"; -#foreach my $m (@modules) { -# print "$m\n"; -#} -# -- --- --- - - - -my $scriptname = $0; -print "perl $scriptname\n"; -my $i =1; -foreach my $a(@ARGV) { - print "Arg # $i: $a\n"; -} - -# -# - - - -# -- --- --- --- --- --- --- --- -# -$exit_code=system("tclsh", $scriptname, @ARGV); -#print "perl reporting tcl exitcode: $exit_code"; -# -# -- --- --- --- --- --- --- --- - -# -# - - -# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end perl Payload -exit $exit_code; -__END__ - -# end hide sh/bash/perl block from Tcl -# This comment with closing brace should stay in place whether if commented or not } -#------------------------------------------------------ -# begin hide powershell-block from Tcl - only needed if Tcl didn't exit or return above -if 0 { -: end heredoc1 - end hide from powershell \ -'@ -# ## ### ### ### ### ### ### ### ### ### ### ### ### ### -# -- powershell/pwsh section -# -- Do not edit if current file is the .ps1 -# -- Edit the corresponding .cmd and it will autocopy -# -- unbalanced braces { } here *even in comments* will cause problems if there was no Tcl exit or return above -# -- custom script should generally go below the begin_powershell_payload line -# ## ### ### ### ### ### ### ### ### ### ### ### ### ### -function GetScriptName { $myInvocation.ScriptName } -$scriptname = GetScriptName -function GetDynamicParamDictionary { - [CmdletBinding()] - param( - [Parameter(ValueFromPipeline=$true, Mandatory=$true)] - [string] $CommandName - ) - - begin { - # Get a list of params that should be ignored (they're common to all advanced functions) - $CommonParameterNames = [System.Runtime.Serialization.FormatterServices]::GetUninitializedObject([type] [System.Management.Automation.Internal.CommonParameters]) | - Get-Member -MemberType Properties | - Select-Object -ExpandProperty Name - } - - process { - # Create the dictionary that this scriptblock will return: - $DynParamDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary - - # Convert to object array and get rid of Common params: - (Get-Command $CommandName | select -exp Parameters).GetEnumerator() | - Where-Object { $CommonParameterNames -notcontains $_.Key } | - ForEach-Object { - $DynamicParameter = New-Object System.Management.Automation.RuntimeDefinedParameter ( - $_.Key, - $_.Value.ParameterType, - $_.Value.Attributes - ) - $DynParamDictionary.Add($_.Key, $DynamicParameter) - } - - # Return the dynamic parameters - return $DynParamDictionary - } -} -# GetDynamicParamDictionary -# - This can make it easier to share a single set of param definitions between functions -# - sample usage -#function ParameterDefinitions { -# param( -# [Parameter(Mandatory)][string] $myargument -# ) -#} -#function psmain { -# [CmdletBinding()] -# param() -# dynamicparam { GetDynamicParamDictionary ParameterDefinitions } -# process { -# #called once with $PSBoundParameters dictionary -# #can be used to validate arguments, or set a simpler variable name for access -# switch ($PSBoundParameters.keys) { -# 'myargumentname' { -# Set-Variable -Name $_ -Value $PSBoundParameters."$_" -# } -# #... -# } -# foreach ($boundparam in $PSBoundParameters.GetEnumerator()) { -# #... -# } -# } -# end { -# #Main function logic -# Write-Host "myargumentname value is: $myargumentname" -# #myotherfunction @PSBoundParameters -# } -#} -#psmain @args -# -- --- --- --- --- --- --- --- --- --- --- --- --- ---begin powershell Payload -#"Timestamp : {0,10:yyyy-MM-dd HH:mm:ss}" -f $(Get-Date) | write-host -#"Script Name : {0}" -f $scriptname | write-host -#"Powershell Version: {0}" -f $PSVersionTable.PSVersion.Major | write-host -#"powershell args : {0}" -f ($args -join ", ") | write-host -# -- --- --- --- - -# -# - - -# -- --- --- --- --- --- --- --- -# -tclsh $scriptname $args -#"powershell reporting exitcode: {0}" -f $LASTEXITCODE | write-host -# -# -- --- --- --- --- --- --- --- - - -# -# - -# -- --- --- --- --- --- --- --- --- --- --- --- --- ---end powershell Payload -Exit $LASTEXITCODE -# heredoc2 for powershell to ignore block below -$1 = @' -' -: comment end hide powershell-block from Tcl \ -# This comment with closing brace should stay in place whether 'if' commented or not } -: multishell doubled-up cmd exit label - return exitcode -:exit_multishell -:exit_multishell -: \ -@REM @ECHO exitcode: !task_exitcode! -: \ -@IF "is%qstrippedargs:~4,13%"=="isPUNK-ELEVATED" (echo. & @cmd /k echo elevated prompt: type exit to quit) -: \ -@EXIT /B !task_exitcode! -# cmd has exited -: comment end heredoc2 \ -'@ -<# -# id:tailblock0 -# -- powershell multiline comment -#> -<# -no script engine should try to run me -# id:tailblock1 -# - -# -# -- unreachable by tcl directly if ctrl-z character is in the section above. (but file can be read and split on \x1A) -# -- Potential for zip and/or base64 contents, but we can't stop pwsh parser from slurping in the data -# -- so for example a plain text tar archive could cause problems depending on the content. -# -- final line in file must be the powershell multiline comment terminator or other data it can handle. -# -- e.g plain # comment lines will work too -# -- (for example a powershell digital signature is a # commented block of data at the end of the file) -#> - - diff --git a/src/scriptapps/example_wrap.toml b/src/scriptapps/example_wrap.toml index 5476485e..313beda3 100644 --- a/src/scriptapps/example_wrap.toml +++ b/src/scriptapps/example_wrap.toml @@ -10,17 +10,30 @@ "example.sh" ] - default_outputfile="example_out.sh" - default_nextshellpath="/usr/bin/env tclsh" - default_nextshelltype="tcl" + default_outputfile='example_out.sh' + default_nextshellpath='/usr/bin/env tclsh' + default_nextshelltype='tcl' #valid nextshelltype entries are: tcl perl powershell bash. - #nextshellpath entries must be 64 characters or less. + #review - zsh : bash-like, more appropriate license, but array index-base 1 vs 0? + #nextshellpath entries must be 128 characters or less. - # win32.nextshellpath="c:/program files/git/usr/bin/bash.exe" + #---------------- + #experimental - cmd with spaces + #first level of quoting is for toml - for strings with no internal single quotes and no escaping - x='value' + + # win32.nextshellpath='"c:\program files\git\usr\bin\bash.exe"' # win32.nextshelltype="bash" - # win32.nextshellpath="c:/program files/powershell/7/pwsh.exe" + + # win32.nextshellpath='"c:/program files/powershell/7/pwsh.exe" -nop -nol -ExecutionPolicy bypass -f' + # win32.nextshelltype='pwsh' + #---------------- + + # win32.nextshellpath="pwsh -nop -nol -ExecutionPolicy bypass -f" + # win32.nextshelltype="pwsh" + #cmd /c for older 'desktop' powershell (v5) to preserve arguments with spaces + # win32.nextshellpath="cmd /c powershell -nop -nol -ExecutionPolicy bypass -f" # win32.nextshelltype="powershell" win32.nextshellpath="tclsh"