Browse Source
New punk/ansi suites and extended punk/ns corp.test, characterizing the corp -> grepstr -> untabify tab-handling interaction and the standalone behaviours. All console-query paths are mocked (the overtype renderline.test get_tabstops pattern, extended to get_size + punk::console::tabwidth): the -untabify none / -stops terminal / -stops <int> paths consult punk::console::get_tabstops / get_size which emit live terminal queries that block or error in headless/piped environments - mocking is the answer to probing that behaviour deterministically in the test harness. - ansi/untabify.test (10): -stops 8/int/list/terminal(mocked), -with spaces/unicode(U+21E5 + U+00A0)/custom pair, multiline, usage errors, and the EXPERIMENTAL -plastic elastic-tabstop mode: basic expansion, the cross-line column-alignment property, -with composition - pinned as interim behaviour (hardcoded 8-multiple stops marked temp in source) and deliberately retained for possible future repl editbuf use. - ansi/grepstr.test (9): return modes incl summarydict (linemap pinned always-present; -help claims -n-only - reconciliation deferred to the hygiene pass), exact highlight SGR wrapping, -n numbering with * match counts, invert + empty-highlight strip, -C context with break indicators, capture groups, and tab handling: warns once per call on stderr, a simple single-pass tab line survives with correct highlights. - ns/corp.test (+6): basic-highlight ansistrip equivalence with -syntax none, -n numbering, -untabify spaces exact tab-free expansion + proper 4-element -syntax none list, -untabify unicode markers, the KNOWN DEFICIENCY pin for default -untabify none on a tabbed body (5 grepstr warnings; brace overlays mangle tabbed lines - leaked/shifted close-braces pinned exactly under mocked tabstops; flip deliberately if tab handling is fixed or corp moves off grepstr), and a ::tcl::CopyDirectory -untabify spaces smoke test (tab-free, warning-free, constraint-guarded). src/tests/modules/AGENTS.md punk/ansi + punk/ns index entries updated. Verified: punk/ansi 63/63 and punk/ns 63/63 under Tcl 9.0.3; the three new/extended files green under Tcl 8.7 (10/10, 9/9, 13/13). Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.commaster
4 changed files with 546 additions and 2 deletions
@ -0,0 +1,178 @@ |
|||||||
|
package require tcltest |
||||||
|
|
||||||
|
package require punk::ansi |
||||||
|
package require punk::console |
||||||
|
|
||||||
|
#added 2026-07-14 (agent) - punk::ansi::grepstr characterization, precursor to the |
||||||
|
#planned punk::ns hygiene pass (punk::ns::corp's basic syntax highlighter is built |
||||||
|
#on repeated grepstr passes - see ns corp.test for the corp-level interplay). |
||||||
|
#ANSI expectations are literal escape strings (suite convention - results are |
||||||
|
#colour-state independent). term-teal resolves to SGR 38;5;6. |
||||||
|
# |
||||||
|
#Console queries are mocked for every test (renderline.test pattern) so no test |
||||||
|
#can emit a live terminal query in headless/piped environments - relevant for |
||||||
|
#tab-containing input, where highlight overlay machinery may consult tabstops. |
||||||
|
# |
||||||
|
#Tab handling: grepstr does NOT currently handle tab characters - it warns once |
||||||
|
#per call on stderr and highlight columns are computed as if tabs were absent. |
||||||
|
#A single simple tab line survives (pinned below), but multi-pass highlighting |
||||||
|
#over tabbed lines mangles output - that interaction is pinned at the consumer |
||||||
|
#level in punk/ns corp.test (corp -untabify none). |
||||||
|
namespace eval ::testspace { |
||||||
|
namespace import ::tcltest::* |
||||||
|
|
||||||
|
proc mock_console {} { |
||||||
|
if {[llength [info commands ::punk::console::get_tabstops]]} { |
||||||
|
rename ::punk::console::get_tabstops ::testspace::__orig_get_tabstops |
||||||
|
} |
||||||
|
proc ::punk::console::get_tabstops {{inoutchannels {stdin stdout}}} { |
||||||
|
set stops {} |
||||||
|
for {set c 9} {$c <= 201} {incr c 8} {lappend stops $c} |
||||||
|
return $stops |
||||||
|
} |
||||||
|
if {[llength [info commands ::punk::console::get_size]]} { |
||||||
|
rename ::punk::console::get_size ::testspace::__orig_get_size |
||||||
|
} |
||||||
|
proc ::punk::console::get_size {args} { |
||||||
|
return [dict create columns 80 rows 24] |
||||||
|
} |
||||||
|
} |
||||||
|
proc restore_console {} { |
||||||
|
catch {rename ::punk::console::get_tabstops {}} |
||||||
|
if {[llength [info commands ::testspace::__orig_get_tabstops]]} { |
||||||
|
rename ::testspace::__orig_get_tabstops ::punk::console::get_tabstops |
||||||
|
} |
||||||
|
catch {rename ::punk::console::get_size {}} |
||||||
|
if {[llength [info commands ::testspace::__orig_get_size]]} { |
||||||
|
rename ::testspace::__orig_get_size ::punk::console::get_size |
||||||
|
} |
||||||
|
} |
||||||
|
variable csetup { |
||||||
|
set result "" |
||||||
|
::testspace::mock_console |
||||||
|
} |
||||||
|
variable ccleanup { |
||||||
|
::testspace::restore_console |
||||||
|
} |
||||||
|
|
||||||
|
#capture stderr written during script (chan push transform; popped in all paths) |
||||||
|
proc capture_stderr {script} { |
||||||
|
variable caught "" |
||||||
|
chan push stderr {apply {{cmd chan args} { |
||||||
|
switch -- $cmd { |
||||||
|
initialize {return {initialize finalize write}} |
||||||
|
finalize {return} |
||||||
|
write {append ::testspace::caught [lindex $args 0]; return ""} |
||||||
|
} |
||||||
|
}}} |
||||||
|
set code [catch {uplevel 1 $script} r ropts] |
||||||
|
chan pop stderr |
||||||
|
if {$code} { |
||||||
|
return -options $ropts $r |
||||||
|
} |
||||||
|
return $caught |
||||||
|
} |
||||||
|
|
||||||
|
variable haystack "line one\nhas needle here\nline three\nneedle again\nlast line" |
||||||
|
|
||||||
|
|
||||||
|
test grepstr_return_all_highlight_exact {-return all: every line returned, matches wrapped in highlight SGR + reset}\ |
||||||
|
-setup $csetup -body { |
||||||
|
lappend result [punk::ansi::grepstr -return all -highlight term-teal {\{|\}} "abc {def} ghi"] |
||||||
|
}\ |
||||||
|
-cleanup $ccleanup\ |
||||||
|
-result [list\ |
||||||
|
"abc \x1b\[38;5;6m{\x1b\[mdef\x1b\[38;5;6m}\x1b\[m ghi" |
||||||
|
] |
||||||
|
|
||||||
|
test grepstr_default_breaksandmatches {default -return breaksandmatches: matched lines with break indicators for skipped runs}\ |
||||||
|
-setup $csetup -body { |
||||||
|
variable haystack |
||||||
|
lappend result [punk::ansi::ansistrip [punk::ansi::grepstr {needle} $haystack]] |
||||||
|
}\ |
||||||
|
-cleanup $ccleanup\ |
||||||
|
-result [list\ |
||||||
|
"-- 1≠\nhas needle here\n-- 1≠\nneedle again\n-- 1≠" |
||||||
|
] |
||||||
|
|
||||||
|
test grepstr_return_matched {-r m: only matching lines, no break indicators}\ |
||||||
|
-setup $csetup -body { |
||||||
|
variable haystack |
||||||
|
lappend result [punk::ansi::ansistrip [punk::ansi::grepstr -r m {needle} $haystack]] |
||||||
|
}\ |
||||||
|
-cleanup $ccleanup\ |
||||||
|
-result [list\ |
||||||
|
"has needle here\nneedle again" |
||||||
|
] |
||||||
|
|
||||||
|
test grepstr_summarydict {-return summarydict: counts plus linemap}\ |
||||||
|
-setup $csetup -body { |
||||||
|
variable haystack |
||||||
|
#NOTE characterization: the documented behaviour says linemap appears only |
||||||
|
#when -n is also supplied - current behaviour includes linemap always. |
||||||
|
#Pinned as-is; doc/behaviour reconciliation left to the punk::ansi/ns |
||||||
|
#hygiene pass (either fix the -help or gate the key). |
||||||
|
lappend result [punk::ansi::grepstr -return summarydict {needle} $haystack] |
||||||
|
lappend result [punk::ansi::grepstr -return summarydict -n {needle} $haystack] |
||||||
|
}\ |
||||||
|
-cleanup $ccleanup\ |
||||||
|
-result [list\ |
||||||
|
{matches 2 matchlines 2 totallines 5 linemap {2 1 4 1}}\ |
||||||
|
{matches 2 matchlines 2 totallines 5 linemap {2 1 4 1}} |
||||||
|
] |
||||||
|
|
||||||
|
test grepstr_linenumbers {-n -return all: relative line numbers with * match-count indicator}\ |
||||||
|
-setup $csetup -body { |
||||||
|
lappend result [punk::ansi::ansistrip [punk::ansi::grepstr -n -return all {needle} "aaa\nbbb needle\nccc"]] |
||||||
|
}\ |
||||||
|
-cleanup $ccleanup\ |
||||||
|
-result [list\ |
||||||
|
"1 *000 aaa\n2 *001 bbb needle\n3 *000 ccc" |
||||||
|
] |
||||||
|
|
||||||
|
test grepstr_invert_empty_highlight {-v with -highlight "": non-matching lines returned, ANSI-stripped by the empty highlight}\ |
||||||
|
-setup $csetup -body { |
||||||
|
lappend result [punk::ansi::ansistrip [punk::ansi::grepstr -r m -highlight "" -v {^##} "##one\ntwo\n##three\nfour"]] |
||||||
|
}\ |
||||||
|
-cleanup $ccleanup\ |
||||||
|
-result [list\ |
||||||
|
"two\nfour" |
||||||
|
] |
||||||
|
|
||||||
|
test grepstr_context {-C 1: one line of context either side of each match, breaks for skipped runs}\ |
||||||
|
-setup $csetup -body { |
||||||
|
lappend result [punk::ansi::ansistrip [punk::ansi::grepstr -C 1 {needle} "l1\nl2\nl3 needle\nl4\nl5\nl6\nl7 needle\nl8"]] |
||||||
|
}\ |
||||||
|
-cleanup $ccleanup\ |
||||||
|
-result [list\ |
||||||
|
"-- 1≠\nl2\nl3 needle\nl4\n-- 1≠\nl6\nl7 needle\nl8" |
||||||
|
] |
||||||
|
|
||||||
|
test grepstr_capture_group {capturing group: matched line returned with group content intact}\ |
||||||
|
-setup $csetup -body { |
||||||
|
lappend result [punk::ansi::ansistrip [punk::ansi::grepstr -r m {;\s*(#.*)} "set x 1 ;# tail comment"]] |
||||||
|
}\ |
||||||
|
-cleanup $ccleanup\ |
||||||
|
-result [list\ |
||||||
|
"set x 1 ;# tail comment" |
||||||
|
] |
||||||
|
|
||||||
|
test grepstr_tab_warning_and_passthrough {tab input: warns once per call on stderr; a simple single-pass tab line survives with correct highlights}\ |
||||||
|
-setup $csetup -body { |
||||||
|
set errout [capture_stderr { |
||||||
|
set ::testspace::tabres [punk::ansi::grepstr -return all -highlight term-teal {\{|\}} "ab\tcd {ef} gh"] |
||||||
|
}] |
||||||
|
lappend result [regexp -all {Warning: grepstr does not currently handle tab} $errout] |
||||||
|
lappend result $::testspace::tabres |
||||||
|
lappend result [punk::ansi::ansistrip $::testspace::tabres] |
||||||
|
unset ::testspace::tabres |
||||||
|
set result |
||||||
|
}\ |
||||||
|
-cleanup $ccleanup\ |
||||||
|
-result [list\ |
||||||
|
1\ |
||||||
|
"ab\tcd \x1b\[38;5;6m{\x1b\[mef\x1b\[38;5;6m}\x1b\[m gh"\ |
||||||
|
"ab\tcd {ef} gh" |
||||||
|
] |
||||||
|
} |
||||||
|
tcltest::cleanupTests ;#needed to produce test summary line. |
||||||
Loading…
Reference in new issue