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] |
[project] |
||||||
name = "punkshell" |
name = "punkshell" |
||||||
version = "0.22.1" |
version = "0.22.2" |
||||||
license = "BSD-2-Clause" |
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