Browse Source
- 34 documentation-only PUNKARGS blocks added: punk::ansi::codetype (is_sgr_reset, has_sgr_leadingreset, is_cursor_move_in_line, has_all_effective, get_effective_types, is_gx/is_gx_open/is_gx_close, sgr_merge), punk::ansi::sequence_type (is_Fe7/is_Fe/is_Fe8, is_Fp, is_Fs, is_nF, is_3Fp, is_code7/is_code8/is_code, classify), punk::ansi::ta (detect_in_list, detectcode, detectcode_in_list, detect_g0, detect_open, detect_st_open, detect_csi, detect_sgr, split_at_codes, split_codes, split_codes_single, get_codes_single) and punk::ansi::ansistring (VIEW, COUNT, length, trimleft/trimright/trim, INDEX, INDEXCHAR, RANGE, INSERT, INDEXABSOLUTE, INDEXCOLUMNS, COLUMNINDEX). - ::punk::ansi::sequence_type added to the punk::args::register NAMESPACES list - the escape-form classifiers live there (a separate namespace from codetype) and were previously undiscoverable by the doc system. - New characterization suites: ta.test (12 tests - detect vs detectcode lone-CSI distinction, split_codes/split_codes_single/split_at_codes shapes and join round-trip, length/extract), codetype.test (11 tests - reset/leading-reset semantics, effective-state queries incl. bold-via-intensity, sgr_merge, sequence_type classify taxonomy); ansistring.test extended 1 -> 12 tests (INDEX/INDEXCODE/INDEXCHAR/RANGE/INSERT grapheme indexing with merged SGR-prefix tracking incl. end+1 state-after-string, INDEXCOLUMNS/COLUMNINDEX double-wide column mapping via a CJK fixture, INDEXABSOLUTE resolution, styled-whitespace trim, VIEW control-picture substitution). - Tests use literal escape strings (not a+) so results are independent of the punk::console colour state. Origin notes added to the usage-marking suites' local SGR helpers pointing at these canonical mechanisms and their coverage. - src/tests/modules/AGENTS.md child DOX index: entries added for the punk/ansi, punk/args and punk/ns suites (GAP-pin/goal mapping documented). - Verified: 44/44 ansi suite and the full ansi+args+ns sweep (232 passed, 1 pre-existing skip) on Tcl 9.0.3 and 8.7; make.tcl modules clean. Assisted-by: harness=claude; primary-model=claude-fable-5; api-location=anthropic.commaster
6 changed files with 1228 additions and 25 deletions
@ -1,3 +1,4 @@
|
||||
0.1.1 |
||||
0.1.2 |
||||
#First line must be a semantic version number |
||||
#all other lines are ignored. |
||||
#0.1.2 - documentation-only: PUNKARGS argdoc blocks added for punk::ansi::codetype (is_sgr_reset, has_sgr_leadingreset, is_cursor_move_in_line, has_all_effective, get_effective_types, is_gx/is_gx_open/is_gx_close, sgr_merge), punk::ansi::sequence_type (is_Fe7/is_Fe/is_Fe8, is_Fp, is_Fs, is_nF, is_3Fp, is_code7/is_code8/is_code, classify), punk::ansi::ta (detect_in_list, detectcode, detectcode_in_list, detect_g0, detect_open, detect_st_open, detect_csi, detect_sgr, split_at_codes, split_codes, split_codes_single, get_codes_single) and punk::ansi::ansistring (VIEW, COUNT, length, trimleft/trimright/trim, INDEX, INDEXCHAR, RANGE, INSERT, INDEXABSOLUTE, INDEXCOLUMNS, COLUMNINDEX); ::punk::ansi::sequence_type added to punk::args::register NAMESPACES so its argdocs are discoverable |
||||
|
||||
@ -0,0 +1,163 @@
|
||||
package require tcltest |
||||
|
||||
package require punk::ansi |
||||
|
||||
#Characterization of punk::ansi::codetype (single-code tests, effective-state queries, |
||||
#SGR merging) and punk::ansi::sequence_type (escape-form classification) - added |
||||
#2026-07-10 alongside the PUNKARGS documentation pass (punk::ansi 0.1.2). |
||||
#These are the primitives behind the ANSI-in-effect tracking used by ansistring |
||||
#INDEX/INDEXCODE and the usage-marking machinery, so their semantics are pinned directly. |
||||
#ANSI codes are written as literal escape strings (not generated via a+) so the tests are |
||||
#independent of the punk::console colour on/off state. |
||||
|
||||
namespace eval ::testspace { |
||||
namespace import ::tcltest::* |
||||
variable common { |
||||
set result "" |
||||
} |
||||
|
||||
variable RED "\x1b\[31m" |
||||
variable BOLD "\x1b\[1m" |
||||
variable RESET "\x1b\[0m" |
||||
|
||||
#--- single-code tests -------------------------------------------------------------------- |
||||
|
||||
test codetype_is_sgr {is_sgr: trailing SGR detected; non-SGR CSI is not}\ |
||||
-setup $common -body { |
||||
variable RED |
||||
lappend result [punk::ansi::codetype::is_sgr $RED] |
||||
lappend result [punk::ansi::codetype::is_sgr "\x1b\[2A"] |
||||
lappend result [punk::ansi::codetype::is_sgr "plain"] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list 1 0 0] |
||||
|
||||
test codetype_is_sgr_reset {is_sgr_reset: pure trailing reset only - embedded or non-trailing resets not detected}\ |
||||
-setup $common -body { |
||||
variable RESET |
||||
lappend result [punk::ansi::codetype::is_sgr_reset $RESET] |
||||
lappend result [punk::ansi::codetype::is_sgr_reset "\x1b\[m"] |
||||
#reset amongst other parameters is not a *pure* reset |
||||
lappend result [punk::ansi::codetype::is_sgr_reset "\x1b\[31;0m"] |
||||
#reset not at the very end is not detected |
||||
lappend result [punk::ansi::codetype::is_sgr_reset "${RESET}x"] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list 1 1 0 0] |
||||
|
||||
test codetype_has_sgr_leadingreset {has_sgr_leadingreset: first parameter of the leading SGR must be a reset}\ |
||||
-setup $common -body { |
||||
lappend result [punk::ansi::codetype::has_sgr_leadingreset "\x1b\[0;31m"] |
||||
lappend result [punk::ansi::codetype::has_sgr_leadingreset "\x1b\[31;0m"] |
||||
lappend result [punk::ansi::codetype::has_sgr_leadingreset "\x1b\[m"] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list 1 0 1] |
||||
|
||||
test codetype_is_cursor_move_in_line {is_cursor_move_in_line: within-line moves (C/D/G) accepted, vertical moves not}\ |
||||
-setup $common -body { |
||||
lappend result [punk::ansi::codetype::is_cursor_move_in_line "\x1b\[5C"] |
||||
lappend result [punk::ansi::codetype::is_cursor_move_in_line "\x1b\[3G"] |
||||
lappend result [punk::ansi::codetype::is_cursor_move_in_line "\x1b\[2A"] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list 1 1 0] |
||||
|
||||
test codetype_is_gx_family {is_gx / is_gx_open / is_gx_close for G0/G1 charset sequences}\ |
||||
-setup $common -body { |
||||
lappend result [punk::ansi::codetype::is_gx "\x1b(0abc\x1b(B"] |
||||
lappend result [punk::ansi::codetype::is_gx "\x1b(0abc"] |
||||
lappend result [punk::ansi::codetype::is_gx_open "\x1b(0"] |
||||
lappend result [punk::ansi::codetype::is_gx_close "\x1b(B"] |
||||
lappend result [punk::ansi::codetype::is_gx_open "\x1b(B"] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list 1 0 1 1 0] |
||||
|
||||
#--- effective-state queries --------------------------------------------------------------- |
||||
|
||||
test codetype_has_any_effective {has_any_effective: state must survive to the end of the string}\ |
||||
-setup $common -body { |
||||
variable RED |
||||
variable RESET |
||||
lappend result [punk::ansi::codetype::has_any_effective "${RED}text" fg] |
||||
#a trailing reset makes the red ineffective |
||||
lappend result [punk::ansi::codetype::has_any_effective "${RED}text${RESET}" fg] |
||||
#no bg was ever set |
||||
lappend result [punk::ansi::codetype::has_any_effective "${RED}text" bg] |
||||
#any-semantics: one of the requested states suffices |
||||
lappend result [punk::ansi::codetype::has_any_effective "${RED}text" bg fg] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list 1 0 0 1] |
||||
|
||||
test codetype_has_all_effective {has_all_effective: every requested state must be in effect; bold matches intensity 1}\ |
||||
-setup $common -body { |
||||
variable RED |
||||
variable BOLD |
||||
lappend result [punk::ansi::codetype::has_all_effective "${RED}${BOLD}x" fg bold] |
||||
lappend result [punk::ansi::codetype::has_all_effective "${RED}x" fg bold] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list 1 0] |
||||
|
||||
test codetype_get_effective_types {get_effective_types lists surviving states; a trailing reset clears them (the reset code itself still reports 'sgr')}\ |
||||
-setup $common -body { |
||||
variable RED |
||||
variable BOLD |
||||
variable RESET |
||||
lappend result [punk::ansi::codetype::get_effective_types "${RED}${BOLD}x"] |
||||
lappend result [punk::ansi::codetype::get_effective_types "${RED}x${RESET}"] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list {sgr intensity fg bold} sgr] |
||||
|
||||
test codetype_sgr_merge {sgr_merge combines separate SGR codes into a single code}\ |
||||
-setup $common -body { |
||||
variable RED |
||||
variable BOLD |
||||
lappend result [punk::ansi::codetype::sgr_merge [list $RED $BOLD]] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list "\x1b\[1;31m"] |
||||
|
||||
#--- sequence_type classification ----------------------------------------------------------- |
||||
|
||||
test sequence_type_classify {classify recognises Fp/Fe/Fs/nF/3Fp forms and unknown}\ |
||||
-setup $common -body { |
||||
variable RED |
||||
lappend result [punk::ansi::sequence_type::classify $RED] |
||||
lappend result [punk::ansi::sequence_type::classify "\x1b7"] |
||||
lappend result [punk::ansi::sequence_type::classify "\x1bc"] |
||||
lappend result [punk::ansi::sequence_type::classify "\x1b(0"] |
||||
lappend result [punk::ansi::sequence_type::classify "\x1b#4"] |
||||
lappend result [punk::ansi::sequence_type::classify "abc"] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list Fe Fp Fs 0Fp 3Fp unknown] |
||||
|
||||
test sequence_type_is_forms {is_Fp / is_Fs / is_nF / is_code form tests}\ |
||||
-setup $common -body { |
||||
variable RED |
||||
lappend result [punk::ansi::sequence_type::is_Fp "\x1b7"] |
||||
lappend result [punk::ansi::sequence_type::is_Fs "\x1bc"] |
||||
lappend result [punk::ansi::sequence_type::is_nF "\x1b(0"] |
||||
lappend result [punk::ansi::sequence_type::is_Fe7 $RED] |
||||
lappend result [punk::ansi::sequence_type::is_code $RED] |
||||
lappend result [punk::ansi::sequence_type::is_code "abc"] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list 1 1 1 1 1 0] |
||||
} |
||||
tcltest::cleanupTests ;#needed to produce test summary line. |
||||
@ -0,0 +1,185 @@
|
||||
package require tcltest |
||||
|
||||
package require punk::ansi |
||||
|
||||
#Characterization of punk::ansi::ta (text-ansi) detection and splitting - added 2026-07-10 |
||||
#alongside the PUNKARGS documentation pass over ta/codetype/sequence_type/ansistring |
||||
#(punk::ansi 0.1.2). These functions underpin most ANSI processing in punk (including the |
||||
#character-position code tracking that ansistring INDEX/INDEXCODE and opunk::Str INDEXCODE |
||||
#build on), so their splitting invariants are pinned directly here. |
||||
#ANSI codes are written as literal escape strings (not generated via a+) so the tests are |
||||
#independent of the punk::console colour on/off state. |
||||
|
||||
namespace eval ::testspace { |
||||
namespace import ::tcltest::* |
||||
variable common { |
||||
set result "" |
||||
} |
||||
|
||||
variable RED "\x1b\[31m" |
||||
variable BOLD "\x1b\[1m" |
||||
variable RESET "\x1b\[0m" |
||||
|
||||
#--- detection ---------------------------------------------------------------------------- |
||||
|
||||
test ta_detect_basics {detect: complete codes detected, plain text and truncated sequences are not}\ |
||||
-setup $common -body { |
||||
variable RED |
||||
lappend result [punk::ansi::ta::detect "a${RED}b"] |
||||
lappend result [punk::ansi::ta::detect "plain text"] |
||||
#a lone trailing ESC is not a complete code |
||||
lappend result [punk::ansi::ta::detect "abc\x1b"] |
||||
#a lone CSI opening is not a *complete* code either |
||||
lappend result [punk::ansi::ta::detect "abc\x1b\["] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list 1 0 0 0] |
||||
|
||||
test ta_detectcode_lone_openings {detectcode: detects codes without requiring paired/complete sequences - a lone CSI opening counts}\ |
||||
-setup $common -body { |
||||
variable RED |
||||
lappend result [punk::ansi::ta::detectcode "a${RED}b"] |
||||
lappend result [punk::ansi::ta::detectcode "plain text"] |
||||
#the difference from detect: a lone CSI opening is detected |
||||
lappend result [punk::ansi::ta::detectcode "abc\x1b\["] |
||||
#but a lone ESC alone is not |
||||
lappend result [punk::ansi::ta::detectcode "abc\x1b"] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list 1 0 1 0] |
||||
|
||||
test ta_detect_in_list {detect_in_list/detectcode_in_list: per-element detection}\ |
||||
-setup $common -body { |
||||
variable RED |
||||
lappend result [punk::ansi::ta::detect_in_list [list plain "a${RED}b" more]] |
||||
lappend result [punk::ansi::ta::detect_in_list [list plain more]] |
||||
lappend result [punk::ansi::ta::detectcode_in_list [list plain "abc\x1b\["]] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list 1 0 1] |
||||
|
||||
test ta_detect_csi_vs_sgr {detect_csi detects any CSI sequence; detect_sgr only those ending in m}\ |
||||
-setup $common -body { |
||||
variable RED |
||||
#cursor-up is a CSI but not an SGR |
||||
lappend result [punk::ansi::ta::detect_csi "\x1b\[2A"] |
||||
lappend result [punk::ansi::ta::detect_sgr "\x1b\[2A"] |
||||
lappend result [punk::ansi::ta::detect_csi "a${RED}b"] |
||||
lappend result [punk::ansi::ta::detect_sgr "a${RED}b"] |
||||
#an SGR reset is also an SGR |
||||
lappend result [punk::ansi::ta::detect_sgr "\x1b\[0m"] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list 1 0 1 1 1] |
||||
|
||||
test ta_detect_g0 {detect_g0 requires a complete G0 open..close group}\ |
||||
-setup $common -body { |
||||
lappend result [punk::ansi::ta::detect_g0 "\x1b(0qqq\x1b(B"] |
||||
lappend result [punk::ansi::ta::detect_g0 "\x1b(0qqq"] |
||||
lappend result [punk::ansi::ta::detect_g0 "plain"] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list 1 0 0] |
||||
|
||||
#--- splitting ---------------------------------------------------------------------------- |
||||
|
||||
test ta_split_codes_shape {split_codes: plaintext on even indices, codes on odd; first/last always plaintext; consecutive codes stay together}\ |
||||
-setup $common -body { |
||||
variable RED |
||||
variable BOLD |
||||
variable RESET |
||||
lappend result [punk::ansi::ta::split_codes ""] |
||||
lappend result [punk::ansi::ta::split_codes "a"] |
||||
#string ending in a code gets a trailing empty plaintext element |
||||
lappend result [punk::ansi::ta::split_codes "a${RED}"] |
||||
lappend result [punk::ansi::ta::split_codes "${RED}a${RESET}b"] |
||||
#consecutive codes remain in ONE code element |
||||
lappend result [punk::ansi::ta::split_codes "${RED}${BOLD}b"] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list\ |
||||
{}\ |
||||
[list a]\ |
||||
[list a "\x1b\[31m" ""]\ |
||||
[list "" "\x1b\[31m" a "\x1b\[0m" b]\ |
||||
[list "" "\x1b\[31m\x1b\[1m" b]\ |
||||
] |
||||
|
||||
test ta_split_codes_single_shape {split_codes_single: each code in its own element with empty plaintext between consecutive codes}\ |
||||
-setup $common -body { |
||||
variable RED |
||||
variable BOLD |
||||
lappend result [punk::ansi::ta::split_codes_single "${RED}${BOLD}b"] |
||||
lappend result [punk::ansi::ta::split_codes_single ""] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list\ |
||||
[list "" "\x1b\[31m" "" "\x1b\[1m" b]\ |
||||
{}\ |
||||
] |
||||
|
||||
test ta_split_at_codes_plaintext_only {split_at_codes returns only the plaintext portions}\ |
||||
-setup $common -body { |
||||
variable RED |
||||
variable RESET |
||||
lappend result [punk::ansi::ta::split_at_codes "a${RED}b${RESET}c"] |
||||
lappend result [punk::ansi::ta::split_at_codes ""] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list [list a b c] {}] |
||||
|
||||
test ta_get_codes_single {get_codes_single returns just the codes, one per element}\ |
||||
-setup $common -body { |
||||
variable RED |
||||
variable BOLD |
||||
lappend result [punk::ansi::ta::get_codes_single "a${RED}b${BOLD}c"] |
||||
lappend result [punk::ansi::ta::get_codes_single "plain"] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list [list "\x1b\[31m" "\x1b\[1m"] {}] |
||||
|
||||
test ta_split_roundtrip {joining split_codes / split_codes_single output reconstructs the original string}\ |
||||
-setup $common -body { |
||||
variable RED |
||||
variable BOLD |
||||
variable RESET |
||||
set s "start${RED}${BOLD}mid${RESET}end" |
||||
lappend result [expr {[join [punk::ansi::ta::split_codes $s] ""] eq $s}] |
||||
lappend result [expr {[join [punk::ansi::ta::split_codes_single $s] ""] eq $s}] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list 1 1] |
||||
|
||||
#--- length / extract --------------------------------------------------------------------- |
||||
|
||||
test ta_length_excludes_codes {length counts characters excluding ANSI codes}\ |
||||
-setup $common -body { |
||||
variable RED |
||||
lappend result [punk::ansi::ta::length "a${RED}b"] |
||||
lappend result [punk::ansi::ta::length ""] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list 2 0] |
||||
|
||||
test ta_extract_codes_only {extract returns only the ANSI codes (opposite of strip)}\ |
||||
-setup $common -body { |
||||
variable RED |
||||
variable RESET |
||||
lappend result [punk::ansi::ta::extract "a${RED}b${RESET}"] |
||||
}\ |
||||
-cleanup { |
||||
}\ |
||||
-result [list "\x1b\[31m\x1b\[0m"] |
||||
} |
||||
tcltest::cleanupTests ;#needed to produce test summary line. |
||||
Loading…
Reference in new issue