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