From 1dd994069554d9d8dffcee93fdd096cb0df1787a Mon Sep 17 00:00:00 2001 From: Julian Noble Date: Thu, 23 Jul 2026 04:55:24 +1000 Subject: [PATCH] tclsh subcommand: stock arg forms (leading dash, -encoding) + lib: refusal (0.19.0) - a leading '-' argument is no longer treated as a script name: all args stay in ::argv and the subcommand proceeds to the repl (tty) or stdin evaluation (piped), matching stock tclsh - '-encoding name fileName' sources the script with the named encoding; incomplete -encoding forms fall through to the argv path as stock does - lib: scriptlib names are refused with a pointer to the 'script' subcommand (exit 1): the tclsh subcommand keeps plain-tclsh semantics, no punk modules - also carries the piperepl dispatch work-in-progress this session's review validated: TCLSH_PIPEREPL gate default enabled-unless-0 (G-103 patched-runtime policy) and the no-arg branch (tty -> tclsh repl via dorepl; piped -> read stdin + eval with tcl_interactive/dorepl 0) - punkproject 0.19.0 + changelog entry Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.com --- CHANGELOG.md | 12 +++++++ punkproject.toml | 2 +- src/vfs/_config/punk_main.tcl | 63 ++++++++++++++++++++++++++++++----- 3 files changed, 67 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6ce7071..d4800660 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ The latest `## [X.Y.Z]` header must match the `version` field in `punkproject.to Entries are newest-first; one bullet per notable change. See the root `AGENTS.md` "Project Versioning" section for the bump policy. +## [0.19.0] - 2026-07-23 + +- `bin/punk-runtime.cmd`: sha1 hashing (fetch verification, `list -remote` comparison) no longer depends on `Get-FileHash` - a script-defined function in Windows PowerShell 5.1 that vanishes on machines with a damaged PSModulePath while compiled cmdlets keep working (observed in the field: "Get-FileHash ... not recognized" under PS 5, making `list -remote` report spurious UPDATE AVAILABLE and fetch verification fail closed). Hashing now uses .NET directly (`Get-PunkFileSha1`), working on any PowerShell edition/state, with a one-time informational note when the damaged condition is detected. The bash payload's multi-tool sha1 probing was already robust and is unchanged. +- `tclsh` subcommand: stock tclsh argument forms - a leading `-` argument no longer errors as a + script name: all arguments stay in `::argv` and the subcommand proceeds to the repl/stdin + evaluation, matching stock `tclsh - args...`; `-encoding name fileName` is recognised and the + script is sourced with the named encoding (incomplete `-encoding` forms fall through to argv, + as stock). `lib:` scriptlib names are refused with a pointer to the `script` subcommand + (exit 1) - the tclsh subcommand keeps plain tclsh semantics and loads no punk modules. + Pinned by `src/tests/shell/testsuites/punkexe/tclshcmd.test` (new suite, also covering the + piperepl launch-state contract on patched kits). + ## [0.18.9] - 2026-07-22 - punk::mod 0.1.2: punk::apps `latest` app-version selection fixed (version-selection audit) - a plain lexical sort picked the LOWEST version as latest (and misorders 1.10 vs 1.9); now `package vcompare`-sorted highest, empty unversioned-main entries excluded. Audit outcome: all other punk-owned `[package versions]` consumers already sort with vcompare; naive sorts remain only in vendored third-party code (tcllib doctools, pattern-1.2.8.tm) - flagged, not edited, per the vendor no-edit policy. diff --git a/punkproject.toml b/punkproject.toml index fc433ee9..76beb019 100644 --- a/punkproject.toml +++ b/punkproject.toml @@ -1,4 +1,4 @@ [project] name = "punkshell" -version = "0.18.9" +version = "0.19.0" license = "BSD-2-Clause" diff --git a/src/vfs/_config/punk_main.tcl b/src/vfs/_config/punk_main.tcl index b6d15893..bdb28438 100644 --- a/src/vfs/_config/punk_main.tcl +++ b/src/vfs/_config/punk_main.tcl @@ -1216,17 +1216,17 @@ apply { args { #we would like to drop through to standard tclsh repl without launching another process #tclMain.c doesn't allow it unless patched. if {![info exists ::env(TCLSH_PIPEREPL)]} { - set is_tclsh_piperepl_env_true 0 + set is_tclsh_piperepl_env_true 1 } else { if {[string is boolean -strict $::env(TCLSH_PIPEREPL)]} { set is_tclsh_piperepl_env_true $::env(TCLSH_PIPEREPL) } else { - set is_tclsh_piperepl_env_true 0 + set is_tclsh_piperepl_env_true 1 } } if {!$is_tclsh_piperepl_env_true} { puts stderr "tcl_interactive: $::tcl_interactive" - puts stderr "stdin: [chan configure stdin]" + #puts stderr "stdin: [chan configure stdin]" puts stderr "Environment variable TCLSH_PIPEREPL is not set or is false or is not a boolean" } else { #according to env TCLSH_PIPEREPL and our commandline argument - tclsh repl is desired @@ -1235,16 +1235,61 @@ apply { args { puts stderr "error: the runtime doesn't appear to have been compiled with the piperepl patch" } } - set ::tcl_interactive 1 - set ::tclsh(dorepl) 1 - if {[llength $subcommand_arglist]} { - set normscript [file normalize [lindex $subcommand_arglist 0]] + #stock tclsh argument forms (tclMain.c): the only recognised leading option is + #'-encoding name fileName' (and only when fileName does not begin with '-'); + #any other leading '-' argument means NO script file - all arguments stay in + #::argv (already set above) and tclsh proceeds to the repl (tty) or stdin + #evaluation (piped). + set tclsh_have_script 0 + set tclsh_encoding "" + if {[llength $subcommand_arglist] >= 3 && [lindex $subcommand_arglist 0] eq "-encoding" && ![string match -* [lindex $subcommand_arglist 2]]} { + set tclsh_encoding [lindex $subcommand_arglist 1] + set tclsh_script [lindex $subcommand_arglist 2] + set tclsh_scriptargs [lrange $subcommand_arglist 3 end] + set tclsh_have_script 1 + } elseif {[llength $subcommand_arglist] && ![string match -* [lindex $subcommand_arglist 0]]} { + set tclsh_script [lindex $subcommand_arglist 0] + set tclsh_scriptargs [lrange $subcommand_arglist 1 end] + set tclsh_have_script 1 + } + if {$tclsh_have_script} { + if {[string match -nocase lib:* $tclsh_script]} { + #scriptlib resolution is a punk facility - the tclsh subcommand keeps plain + #tclsh semantics (no punk modules loaded), so point at the 'script' subcommand + #instead of failing on a literal 'lib:...' path (illegal on windows filesystems + #anyway; reachable via ./lib:... or an absolute path on other platforms). + set exebase [file rootname [file tail [info nameofexecutable]]] + puts stderr "punk tclsh: 'lib:' scriptlib resolution is not supported by the tclsh subcommand (plain tclsh semantics)" + puts stderr " use: $exebase script $tclsh_script ?args...?" + exit 1 + } + set normscript [file normalize $tclsh_script] info script $normscript set ::argv0 $normscript - set ::argv [lrange $subcommand_arglist 1 end] + set ::argv $tclsh_scriptargs set ::argc [llength $::argv] #we are in an apply context here - so we need to uplevel to get the source to work as expected - uplevel 1 [list source [lindex $subcommand_arglist 0]] + if {$tclsh_encoding ne ""} { + uplevel 1 [list source -encoding $tclsh_encoding $tclsh_script] + } else { + uplevel 1 [list source $tclsh_script] + } + + #default tclsh behaviour is to run the script and exit + #the script can set ::tclsh(dorepl) 1 to force the tclsh repl after the script has run + } else { + if {[info exists ::tclsh(istty)] && $::tclsh(istty)} { + #tclsh piperepl patch applied - stdin is a tty - we can run the tclsh repl + set ::tclsh(dorepl) 1 + set ::tcl_interactive 1 + } else { + #stdin is not a tty - we are being run with input piped in - we will evaluate stdin as a script and exit + set ::tclsh(dorepl) 0 + set ::tcl_interactive 0 + #script on stdin could set ::tclsh(dorepl) 1 to force the tclsh repl after the script has run + set data [read stdin] + uplevel 1 [list eval $data] + } } } shellspy {