Browse Source
Startup policy: NO_COLOR > PUNK_FORCE_COLOR/FORCE_COLOR > stdout -winsize tty probe (Tcl 8.7+/9); Tcl 8.6 fails OFF. Piped/unknown non-forced runs push an ansistrip transform on stdout+stderr - zero ESC bytes from every emitter incl. module-side (punk::ansi's colour_disabled deliberately keeps non-colour effects; design finding recorded in the goal file). shell pops the transform. sgr_cache cleared for punk-exe-hosted pre-loaded interps per the punk::ansi contract. define_global_ansi fully gated; raw-SGR literals routed through new ::punkboot::sgr gate (fixes a malformed \x1b\31m). check reports the policy. make.tcl converted CRLF->LF. Verified: tclsh90 + punk91 kit piped zero-ESC (check/help/workflow/modules); force overrides re-colour piped output; NO_COLOR always suppresses colour; 8.6 tcl86-plain; real Windows console mode=tty (hidden-console evidence); WSL pty mode=tty vs piped-plain (tclkit-902-Linux64). New suite punkexe/maketclcolour.test 3/3. src/AGENTS.md bullet revised; punkshell 0.22.2. Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.commaster
10 changed files with 873 additions and 55 deletions
@ -1,4 +1,4 @@
|
||||
[project] |
||||
name = "punkshell" |
||||
version = "0.22.1" |
||||
version = "0.22.2" |
||||
license = "BSD-2-Clause" |
||||
|
||||
@ -0,0 +1,150 @@
|
||||
package require tcltest |
||||
|
||||
#Piped-output colour characterization for make.tcl's terminal-aware colour policy |
||||
#(goal G-113). Runs the WORKING TREE's src/make.tcl under the built punk executable's |
||||
#'script' subcommand with output captured through a pipe, and pins: |
||||
# - piped output with NO_COLOR unset is fully ESC-free (the ansistrip transform) |
||||
# - the check subcommand reports the policy decision (mode=piped-plain, ansistrip=1) |
||||
# - FORCE_COLOR re-enables ANSI/colour on piped output (mode=forced) |
||||
# - NO_COLOR=1 piped output remains fully ESC-free |
||||
#The interactive (tty) side cannot be pinned from a piped harness - evidence for it |
||||
#is recorded in the G-113 goal file (hidden-console + WSL pty runs, mode=tty). |
||||
# |
||||
#Target executable resolved from env(PUNK_SHELL_TEST_EXE), else <projectroot>/bin/punk902z.exe |
||||
#then <projectroot>/bin/punkshell902. Skipped (constraint punkexeavailable) if none found. |
||||
|
||||
namespace eval ::testspace { |
||||
namespace import ::tcltest::* |
||||
|
||||
variable testdir [file dirname [file normalize [info script]]] |
||||
#<projectroot>/src/tests/shell/testsuites/punkexe -> 5 levels up to <projectroot> |
||||
variable projectroot [file normalize [file join $testdir .. .. .. .. ..]] |
||||
variable maketcl [file join $projectroot src make.tcl] |
||||
|
||||
variable punkexe "" |
||||
if {[info exists ::env(PUNK_SHELL_TEST_EXE)] && $::env(PUNK_SHELL_TEST_EXE) ne ""} { |
||||
set punkexe [file normalize $::env(PUNK_SHELL_TEST_EXE)] |
||||
} else { |
||||
foreach candidate [list [file join $projectroot bin punk902z.exe] [file join $projectroot bin punkshell902]] { |
||||
if {[file exists $candidate]} { |
||||
set punkexe $candidate |
||||
break |
||||
} |
||||
} |
||||
} |
||||
testConstraint punkexeavailable [expr {$punkexe ne "" && [file exists $punkexe]}] |
||||
|
||||
variable maketcl_run_timeout_ms 60000 |
||||
|
||||
variable runstate |
||||
array set runstate {} |
||||
|
||||
proc maketcl_run_read {chan} { |
||||
variable runstate |
||||
append runstate(output) [read $chan] |
||||
if {[chan eof $chan]} { |
||||
chan event $chan readable {} |
||||
set runstate(done) eof |
||||
} |
||||
} |
||||
|
||||
#Run <punkexe> script src/make.tcl <subcommand...> with a controlled colour env. |
||||
#envoverrides is a dict: name value pairs applied to ::env for the child (a value |
||||
#of "" means unset). Saved values are restored after the run. Returns dict: |
||||
#timedout 0|1, exitcode <int|"">, output <combined stdout+stderr>. |
||||
proc maketcl_run {cmdargs envoverrides} { |
||||
variable runstate |
||||
variable maketcl_run_timeout_ms |
||||
variable punkexe |
||||
variable maketcl |
||||
array unset runstate |
||||
set runstate(output) "" |
||||
set runstate(done) "" |
||||
|
||||
set saved [dict create] |
||||
dict for {evar eval_} $envoverrides { |
||||
if {[info exists ::env($evar)]} { |
||||
dict set saved $evar [list 1 $::env($evar)] |
||||
} else { |
||||
dict set saved $evar [list 0 {}] |
||||
} |
||||
if {$eval_ eq ""} { |
||||
unset -nocomplain ::env($evar) |
||||
} else { |
||||
set ::env($evar) $eval_ |
||||
} |
||||
} |
||||
try { |
||||
set chan [open |[list $punkexe script $maketcl {*}$cmdargs 2>@1] r+] |
||||
chan configure $chan -blocking 0 -translation binary |
||||
catch {chan close $chan write} ;#no stdin for the child - immediate EOF |
||||
set timerid [after $maketcl_run_timeout_ms [list set [namespace current]::runstate(done) timeout]] |
||||
chan event $chan readable [list [namespace current]::maketcl_run_read $chan] |
||||
while {$runstate(done) eq ""} { |
||||
vwait [namespace current]::runstate(done) |
||||
} |
||||
after cancel $timerid |
||||
set timedout [expr {$runstate(done) eq "timeout"}] |
||||
set exitcode "" |
||||
if {$timedout} { |
||||
catch {exec {*}[auto_execok taskkill] /F /T /PID [lindex [pid $chan] 0]} |
||||
catch {chan close $chan} |
||||
} else { |
||||
chan configure $chan -blocking 1 |
||||
if {[catch {chan close $chan} errdata errdict]} { |
||||
set exitcode [lindex [dict get $errdict -errorcode] end] |
||||
} else { |
||||
set exitcode 0 |
||||
} |
||||
} |
||||
return [dict create timedout $timedout exitcode $exitcode output $runstate(output)] |
||||
} finally { |
||||
dict for {evar restore} $saved { |
||||
lassign $restore existed oldval |
||||
if {$existed} { |
||||
set ::env($evar) $oldval |
||||
} else { |
||||
unset -nocomplain ::env($evar) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
proc esc_count {text} { |
||||
return [regexp -all {\x1b} $text] |
||||
} |
||||
|
||||
#added 2026-07-25 (agent, G-113) |
||||
test maketcl_colour_piped_plain_zero_esc {piped make.tcl check with NO_COLOR unset: zero ESC bytes, policy line reports piped-plain with ansistrip} -constraints {punkexeavailable} -body { |
||||
set r [maketcl_run {check} {NO_COLOR "" FORCE_COLOR "" PUNK_FORCE_COLOR ""}] |
||||
set result [list] |
||||
lappend result timedout [dict get $r timedout] exitcode [dict get $r exitcode] |
||||
lappend result esc [esc_count [dict get $r output]] |
||||
lappend result policyline [regexp {colour policy \(G-113\): mode=piped-plain colour_disabled=1 ansistrip=1} [dict get $r output]] |
||||
set result |
||||
} -result {timedout 0 exitcode 0 esc 0 policyline 1} |
||||
|
||||
#added 2026-07-25 (agent, G-113) |
||||
test maketcl_colour_forced_piped_has_colour {FORCE_COLOR=1 re-enables ANSI colour on piped make.tcl help output} -constraints {punkexeavailable} -body { |
||||
set r [maketcl_run {help} {NO_COLOR "" FORCE_COLOR 1 PUNK_FORCE_COLOR ""}] |
||||
set result [list] |
||||
lappend result timedout [dict get $r timedout] exitcode [dict get $r exitcode] |
||||
lappend result has_esc [expr {[esc_count [dict get $r output]] > 0}] |
||||
#at least one SGR sequence carrying a colour parameter (30-49 / 90-107 fg-bg range or 38;/48; extended) |
||||
lappend result has_colour [regexp {\x1b\[[0-9;:]*(?:3[0-9]|4[0-9]|9[0-7]|10[0-7])[;m]} [dict get $r output]] |
||||
set result |
||||
} -result {timedout 0 exitcode 0 has_esc 1 has_colour 1} |
||||
|
||||
#added 2026-07-25 (agent, G-113) |
||||
test maketcl_colour_nocolor_piped_zero_esc {NO_COLOR=1 piped make.tcl check output is fully ESC-free (strip transform applies)} -constraints {punkexeavailable} -body { |
||||
set r [maketcl_run {check} {NO_COLOR 1 FORCE_COLOR "" PUNK_FORCE_COLOR ""}] |
||||
set result [list] |
||||
lappend result timedout [dict get $r timedout] exitcode [dict get $r exitcode] |
||||
lappend result esc [esc_count [dict get $r output]] |
||||
lappend result policyline [regexp {colour policy \(G-113\): mode=nocolor colour_disabled=1 ansistrip=1} [dict get $r output]] |
||||
set result |
||||
} -result {timedout 0 exitcode 0 esc 0 policyline 1} |
||||
|
||||
cleanupTests |
||||
} |
||||
namespace delete ::testspace |
||||
Loading…
Reference in new issue