Browse Source

misc ansi and console fixes

master
Julian Noble 1 week ago
parent
commit
9ad29680c3
  1. 2
      src/bootsupport/modules/overtype-1.6.5.tm
  2. 46
      src/bootsupport/modules/punk-0.1.tm
  3. 562
      src/bootsupport/modules/punk/ansi-0.1.1.tm
  4. 143
      src/bootsupport/modules/punk/args-0.1.0.tm
  5. 102
      src/bootsupport/modules/punk/char-0.1.0.tm
  6. 326
      src/bootsupport/modules/punk/console-0.1.1.tm
  7. 41
      src/bootsupport/modules/punk/lib-0.1.1.tm
  8. 2
      src/bootsupport/modules/punk/mix/base-0.1.tm
  9. 2
      src/bootsupport/modules/punk/mix/util-0.1.0.tm
  10. 6
      src/bootsupport/modules/punk/nav/fs-0.1.0.tm
  11. 2
      src/bootsupport/modules/punk/packagepreference-0.1.0.tm
  12. 9
      src/bootsupport/modules/punk/repl/codethread-0.1.0.tm
  13. 287
      src/bootsupport/modules/punk/repl/codethread-0.1.1.tm
  14. 2
      src/bootsupport/modules/punk/repo-0.1.1.tm
  15. 11
      src/bootsupport/modules/punk/tdl-0.1.0.tm
  16. 4
      src/bootsupport/modules/punkcheck-0.1.0.tm
  17. 9
      src/bootsupport/modules/shellfilter-0.1.9.tm
  18. 120
      src/bootsupport/modules/textblock-0.1.2.tm
  19. 2
      src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm
  20. 19
      src/modules/patternpunk-1.1.tm
  21. 46
      src/modules/punk-0.1.tm
  22. 562
      src/modules/punk/ansi-999999.0a1.0.tm
  23. 143
      src/modules/punk/args-999999.0a1.0.tm
  24. 102
      src/modules/punk/char-999999.0a1.0.tm
  25. 326
      src/modules/punk/console-999999.0a1.0.tm
  26. 41
      src/modules/punk/lib-999999.0a1.0.tm
  27. 2
      src/modules/punk/mix/base-0.1.tm
  28. 2
      src/modules/punk/mix/templates/layouts/project/src/make.tcl
  29. 2
      src/modules/punk/mix/util-999999.0a1.0.tm
  30. 6
      src/modules/punk/nav/fs-999999.0a1.0.tm
  31. 2
      src/modules/punk/packagepreference-999999.0a1.0.tm
  32. 30
      src/modules/punk/repl-0.1.tm
  33. 30
      src/modules/punk/repl/codethread-999999.0a1.0.tm
  34. 2
      src/modules/punk/repl/codethread-buildversion.txt
  35. 2
      src/modules/punk/repo-999999.0a1.0.tm
  36. 322
      src/modules/punk/sixel-999999.0a1.0.tm
  37. 3
      src/modules/punk/sixel-buildversion.txt
  38. 11
      src/modules/punk/tdl-999999.0a1.0.tm
  39. 16
      src/modules/punk/uc-999999.0a1.0.tm
  40. 4
      src/modules/punkcheck-0.1.0.tm
  41. 9
      src/modules/shellfilter-0.1.9.tm
  42. 120
      src/modules/textblock-999999.0a1.0.tm
  43. 2
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/overtype-1.6.5.tm
  44. 46
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk-0.1.tm
  45. 562
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/ansi-0.1.1.tm
  46. 143
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/args-0.1.0.tm
  47. 102
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/char-0.1.0.tm
  48. 326
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/console-0.1.1.tm
  49. 41
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/lib-0.1.1.tm
  50. 2
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/base-0.1.tm
  51. 2
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/util-0.1.0.tm
  52. 6
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/nav/fs-0.1.0.tm
  53. 2
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/packagepreference-0.1.0.tm
  54. 9
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repl/codethread-0.1.0.tm
  55. 287
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repl/codethread-0.1.1.tm
  56. 2
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repo-0.1.1.tm
  57. 11
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/tdl-0.1.0.tm
  58. 4
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punkcheck-0.1.0.tm
  59. 9
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/shellfilter-0.1.9.tm
  60. 120
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/textblock-0.1.2.tm
  61. 2
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/overtype-1.6.5.tm
  62. 46
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk-0.1.tm
  63. 562
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/ansi-0.1.1.tm
  64. 143
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/args-0.1.0.tm
  65. 102
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/char-0.1.0.tm
  66. 326
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/console-0.1.1.tm
  67. 41
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/lib-0.1.1.tm
  68. 2
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/base-0.1.tm
  69. 2
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/util-0.1.0.tm
  70. 6
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/nav/fs-0.1.0.tm
  71. 2
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/packagepreference-0.1.0.tm
  72. 9
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repl/codethread-0.1.0.tm
  73. 287
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repl/codethread-0.1.1.tm
  74. 2
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repo-0.1.1.tm
  75. 11
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/tdl-0.1.0.tm
  76. 4
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punkcheck-0.1.0.tm
  77. 9
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/shellfilter-0.1.9.tm
  78. 120
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/textblock-0.1.2.tm
  79. 41
      src/runtime/mapvfs.config
  80. 88
      src/scriptapps/punk.tcl
  81. 1
      src/testansi/3-sixels.six
  82. 0
      src/testansi/harley_quinn_large.png
  83. 1
      src/testansi/image.six
  84. 1
      src/testansi/jw_carina_nircam.six
  85. 1
      src/testansi/lady-of-shalott.six
  86. 25
      src/testansi/text-test.six
  87. 19
      src/vfs/_vfscommon.vfs/modules/patternpunk-1.1.tm
  88. 46
      src/vfs/_vfscommon.vfs/modules/punk-0.1.tm
  89. 562
      src/vfs/_vfscommon.vfs/modules/punk/ansi-0.1.1.tm
  90. 143
      src/vfs/_vfscommon.vfs/modules/punk/args-0.1.0.tm
  91. 102
      src/vfs/_vfscommon.vfs/modules/punk/char-0.1.0.tm
  92. 326
      src/vfs/_vfscommon.vfs/modules/punk/console-0.1.1.tm
  93. 41
      src/vfs/_vfscommon.vfs/modules/punk/lib-0.1.1.tm
  94. 2
      src/vfs/_vfscommon.vfs/modules/punk/mix/base-0.1.tm
  95. 2
      src/vfs/_vfscommon.vfs/modules/punk/mix/templates/layouts/project/src/make.tcl
  96. 2
      src/vfs/_vfscommon.vfs/modules/punk/mix/util-0.1.0.tm
  97. 6
      src/vfs/_vfscommon.vfs/modules/punk/nav/fs-0.1.0.tm
  98. 2
      src/vfs/_vfscommon.vfs/modules/punk/packagepreference-0.1.0.tm
  99. 30
      src/vfs/_vfscommon.vfs/modules/punk/repl-0.1.tm
  100. 10
      src/vfs/_vfscommon.vfs/modules/punk/repl/codethread-0.1.0.tm
  101. Some files were not shown because too many files have changed in this diff Show More

2
src/bootsupport/modules/overtype-1.6.5.tm

@ -1771,7 +1771,7 @@ tcl::namespace::eval overtype {
#-returnextra enables returning of overflow and length #-returnextra enables returning of overflow and length
#review - use punk::ansi::ta::detect to short-circuit processing and do simpler string calcs as an optimisation? #review - use punk::ansi::ta::detect to short-circuit processing and do simpler string calcs as an optimisation?
#review - DECSWL/DECDWL double width line codes - very difficult/impossible to align and compose with other elements #review - DECSWL/DECDWL double width line codes - very difficult/impossible to align and compose with other elements
#(could render it by faking it with sixels and a lot of work - find/make a sixel font and ensure it's exactly 2 cols per char? #(could render it by faking it with sixels and a lot of work - find/make a sixel font (converter?) and ensure it's exactly 2 cols per char?
# This would probably be impractical to support for different fonts) # This would probably be impractical to support for different fonts)
#todo - review transparency issues with single/double width characters #todo - review transparency issues with single/double width characters
#bidi - need a base direction and concept of directional runs for RTL vs LTR - may be best handled at another layer? #bidi - need a base direction and concept of directional runs for RTL vs LTR - may be best handled at another layer?

46
src/bootsupport/modules/punk-0.1.tm

@ -7428,6 +7428,7 @@ namespace eval punk {
} }
if {$topic in [list console terminal]} { if {$topic in [list console terminal]} {
set indent [string repeat " " [string length "WARNING: "]]
lappend cstring_tests [dict create\ lappend cstring_tests [dict create\
type "PM "\ type "PM "\
msg "PRIVACY MESSAGE"\ msg "PRIVACY MESSAGE"\
@ -7472,6 +7473,51 @@ namespace eval punk {
append warningblock \n "WARNING: terminal doesn't hide all [dict get $test type] control strings: $d $d8" append warningblock \n "WARNING: terminal doesn't hide all [dict get $test type] control strings: $d $d8"
} }
} }
if {![catch {punk::console::check::has_bug_legacysymbolwidth} result]} {
if {$result} {
append warningblock \n "WARNING: terminal has legacysymbolwidth bug - screen position for symbol reports 2 wide but displays 1 wide."
append warningblock \n $indent "Layout using 'legacy symbols for computing' affected."
append warningblock \n $indent "(e.g textblock frametype block2 unsupported)"
append warningblock \n $indent "This can cause extreme layout deformation when ANSI is present"
append warningblock \n $indent "In some cases unwanted spacing effects occur at a distance from the characters causing it"
}
} else {
append warningblock \n "WARNING: terminal unable to check for legacysymbolwidth bug. err:$result"
}
if {![catch {punk::console::check::has_bug_zwsp} result]} {
if {$result} {
append warningblock \n "WARNING: terminal has zero width space (\\u200b) bug - cursor position incremented when it shouldn't be."
append warningblock \n $indent "The zwsp may or may not be displayed. zwsp contributes to line length and wrapping point"
}
} else {
append warningblock \n "WARNING: terminal unable to check for zwsp bug. err:$result"
}
set grapheme_support [punk::console::grapheme_cluster_support]
#mode, 1 = set, 2 = unset. (0 = mode not recognised, 3 = permanently set, 4 = permanently unset)
if {![dict size $grapheme_support] || [dict get $grapheme_support mode] eq "unsupported" } {
append warningblock \n "WARNING: terminal either doesn't support grapheme clusters, or doesn't report so via decmode 2027 query."
if {[dict size $grapheme_support] && [dict get $grapheme_support available]} {
append warningblock \n $indent "(but punk::console::grapheme_cluster_support has determined it is probably available)"
}
} else {
if {![dict get $grapheme_support available]} {
switch -- [dict get $grapheme_support mode] {
"unset" {
append warningblock \n "WARNING: terminal reports via decmode 2027 that grapheme cluster support is off."
}
"permanently_unset" {
append warningblock \n "WARNING: terminal reports via decmode 2027 that grapheme cluster support is permanently off."
}
"BAD_RESPONSE" {
append warningblock \n "WARNING: terminal doesn't seem to recognize decmode 2027 query. No grapheme cluster support."
}
}
}
}
} }
lappend chunks [list stderr $warningblock] lappend chunks [list stderr $warningblock]

562
src/bootsupport/modules/punk/ansi-0.1.1.tm

@ -556,21 +556,21 @@ tcl::namespace::eval punk::ansi {
} }
proc example {args} { proc example {args} {
set base [punk::repo::find_project] set base [punk::repo::find_project]
set default_ansibase [file join $base src/testansi] set default_ansifolder [file join $base src/testansi]
set argd [punk::args::get_dict [tstr -return string { set argd [punk::args::get_dict [tstr -return string {
*proc -name punk::ansi::example -help "Display .ans image files in a grid that will fit in console *proc -name punk::ansi::example -help "Display .ans image files in a grid that will fit in console
" "
-colwidth -default 82 -help "Width of each column - default of 82 will fit a standard 80wide ansi image (when framed) -colwidth -default 82 -help "Width of each column - default of 82 will fit a standard 80wide ansi image (when framed)
You can specify a narrower width to truncate images on the right side" You can specify a narrower width to truncate images on the right side"
-folder -default "${$default_ansibase}" -help "Base folder for files if relative paths are used. -folder -default "${$default_ansifolder}" -help "Base folder for files if relative paths are used.
Defaults to <projectbase>/src/testansi - where projectbase is determined from current directory. Defaults to <projectbase>/src/testansi - where projectbase is determined from current directory.
" "
*values -min 0 -max -1 *values -min 0 -max -1
files -default {belinda.ans bot.ans flower.ans fish.ans} -multiple true -help "List of filenames - leave empty to display 4 defaults" files -default {belinda.ans bot.ans flower.ans fish.ans} -multiple true -help "List of filenames - leave empty to display 4 defaults"
}] $args] }] $args]
set colwidth [dict get $argd opts -colwidth] set colwidth [dict get $argd opts -colwidth]
set ansibase [file normalize [dict get $argd opts -folder]] set ansifolder [file normalize [dict get $argd opts -folder]]
set fnames [dict get $argd values files] set fnames [dict get $argd values files]
#assumes fixed column widths e.g 80col images will fit in 82-width frames (common standard for old ansi art) (of arbitrary height) #assumes fixed column widths e.g 80col images will fit in 82-width frames (common standard for old ansi art) (of arbitrary height)
@ -579,8 +579,8 @@ tcl::namespace::eval punk::ansi {
package require punk::repo package require punk::repo
package require punk::console package require punk::console
if {![file exists $ansibase]} { if {![file exists $ansifolder]} {
puts stderr "Missing folder at $ansibase" puts stderr "Missing folder at $ansifolder"
puts stderr "Ensure ansi test files exist: $fnames" puts stderr "Ensure ansi test files exist: $fnames"
#error "punk::ansi::example Cannot find example files" #error "punk::ansi::example Cannot find example files"
} }
@ -588,7 +588,7 @@ tcl::namespace::eval punk::ansi {
set pics [list] set pics [list]
foreach f $fnames { foreach f $fnames {
if {[file pathtype $f] ne "absolute"} { if {[file pathtype $f] ne "absolute"} {
set filepath [file normalize $ansibase/$f] set filepath [file normalize $ansifolder/$f]
} else { } else {
set filepath [file normalize $f] set filepath [file normalize $f]
} }
@ -621,7 +621,7 @@ tcl::namespace::eval punk::ansi {
set subtitle [tcl::dict::get $picinfo status] set subtitle [tcl::dict::get $picinfo status]
} }
set title [tcl::dict::get $picinfo filename] set title [tcl::dict::get $picinfo filename]
set fr [textblock::frame -checkargs 0 -width $colwidth -subtitle $subtitle -title $title [tcl::dict::get $picinfo pic]] set fr [textblock::frame -width $colwidth -subtitle $subtitle -title $title [tcl::dict::get $picinfo pic]]
# -- --- --- --- # -- --- --- ---
#we need the max height of a row element to use join_basic instead of join below #we need the max height of a row element to use join_basic instead of join below
# -- --- --- --- # -- --- --- ---
@ -2096,7 +2096,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
} }
} }
} }
append out "\nNote: The 256 term colours especially 0-15 may be altered by terminal pallette settings or ansi OSC 4 codes, so specific RGB values are unavailable" append out "\nNote: The 256 term colours especially 0-15 may be altered by terminal palette settings or ansi OSC 4 codes, so specific RGB values are unavailable"
return $out return $out
} }
web { web {
@ -2126,8 +2126,25 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
switch -- $f4 { switch -- $f4 {
web- - Web- - WEB- { web- - Web- - WEB- {
set tail [tcl::string::tolower [tcl::string::trim [tcl::string::range $i 4 end] -]] set tail [tcl::string::tolower [tcl::string::trim [tcl::string::range $i 4 end] -]]
if {[tcl::dict::exists $WEB_colour_map $tail]} { set cont [string range $tail end-11 end]
set dec [tcl::dict::get $WEB_colour_map $tail] switch -- $cont {
-contrasting - -contrastive {
set cname [string range $tail 0 end-12]
}
default {
set cname $tail
}
}
if {[tcl::dict::exists $WEB_colour_map $cname]} {
set dec [tcl::dict::get $WEB_colour_map $cname]
switch -- $cont {
-contrasting {
set dec [join [punk::ansi::colour::contrasting {*}[split $dec -]] -]
}
-contrastive {
set dec [join [lindex [punk::ansi::colour::contrast_pair {*}[split $dec -]] 0] -]
}
}
set hex [colour_dec2hex $dec] set hex [colour_dec2hex $dec]
set descr "$hex $dec" set descr "$hex $dec"
} else { } else {
@ -2170,25 +2187,60 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
RGB0 - RGB1 - RGB2 - RGB3 - RGB4 - RGB5 - RGB6 - RGB7 - RGB8 - RGB9 - RGB0 - RGB1 - RGB2 - RGB3 - RGB4 - RGB5 - RGB6 - RGB7 - RGB8 - RGB9 -
rgb# - Rgb# - RGB# - rgb# - Rgb# - RGB# -
und# - und- { und# - und- {
if {[tcl::string::index $i 3] eq "#"} { set cont [string range $i end-11 end]
set tail [tcl::string::range $i 4 end] switch -- $cont {
-contrasting - -contrastive {
set iplain [string range $i 0 end-12]
}
default {
set iplain $i
}
}
if {[tcl::string::index $iplain 3] eq "#"} {
set tail [tcl::string::range $iplain 4 end]
set hex $tail set hex $tail
set dec [colour_hex2dec $hex] set dec [colour_hex2dec $hex]
set info $dec ;#show opposite type as first line of info col
switch -- $cont {
-contrasting {
set decfinal [join [punk::ansi::colour::contrasting {*}[split $dec -]] -]
set hexfinal [colour_dec2hex $decfinal]
}
-contrastive {
set decfinal [join [lindex [punk::ansi::colour::contrast_pair {*}[split $dec -]] 0] -]
set hexfinal [colour_dec2hex $decfinal]
}
default {
set hexfinal $hex
set decfinal $dec
}
}
set info "$hexfinal $decfinal" ;#show opposite type as first line of info col
} else { } else {
set tail [tcl::string::trim [tcl::string::range $i 3 end] -] set tail [tcl::string::trim [tcl::string::range $iplain 3 end] -]
set dec $tail set dec $tail
set hex [colour_dec2hex $dec] switch -- $cont {
set info $hex -contrasting {
set decfinal [join [punk::ansi::colour::contrasting {*}[split $dec -]] -]
}
-contrastive {
set decfinal [join [lindex [punk::ansi::colour::contrast_pair {*}[split $dec -]] 0] -]
}
default {
set decfinal $dec
}
}
set hexfinal [colour_dec2hex $decfinal]
set info "$hexfinal $decfinal"
} }
set webcolours_i [lsearch -all $WEB_colour_map $dec] set webcolours_i [lsearch -all $WEB_colour_map $decfinal]
set webcolours [list] set webcolours [list]
foreach ci $webcolours_i { foreach ci $webcolours_i {
lappend webcolours [lindex $WEB_colour_map $ci-1] lappend webcolours [lindex $WEB_colour_map $ci-1]
} }
set x11colours [list] set x11colours [list]
set x11colours_i [lsearch -all $X11_colour_map $dec] set x11colours_i [lsearch -all $X11_colour_map $decfinal]
foreach ci $x11colours_i { foreach ci $x11colours_i {
set c [lindex $X11_colour_map $ci-1] set c [lindex $X11_colour_map $ci-1]
if {$c ni $webcolours} { if {$c ni $webcolours} {
@ -2205,12 +2257,15 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
} }
unde { unde {
switch -- $i { switch -- $i {
undercurly - underdotted - underdashed - undersingle - underdouble { undercurly - undercurl - underdotted - underdot - underdashed - underdash - undersingle - underdouble {
$t add_row [list $i extended $s [ansistring VIEW $s]] $t add_row [list $i extended $s [ansistring VIEW $s]]
} }
underline { underline {
$t add_row [list $i "SGR 4" $s [ansistring VIEW $s]] $t add_row [list $i "SGR 4" $s [ansistring VIEW $s]]
} }
underlinedefault {
$t add_row [list $i "SGR 59" $s [ansistring VIEW $s]]
}
default { default {
$t add_row [list $i UNKNOWN $s [ansistring VIEW $s]] $t add_row [list $i UNKNOWN $s [ansistring VIEW $s]]
} }
@ -2362,10 +2417,31 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#variable WEB_colour_map #variable WEB_colour_map
#upvar ::punk::ansi::WEB_colour_map WEB_colour_map #upvar ::punk::ansi::WEB_colour_map WEB_colour_map
#foreground web colour #foreground web colour
set cname [tcl::string::tolower [tcl::string::range $i 4 end]] set tail [tcl::string::tolower [tcl::string::range $i 4 end]]
#-contrasting
#-contrastive
set cont [string range $tail end-11 end]
switch -- $cont {
-contrasting - -contrastive {
set cname [string range $tail 0 end-12]
}
default {
set cname $tail
}
}
if {[tcl::dict::exists $WEB_colour_map $cname]} { if {[tcl::dict::exists $WEB_colour_map $cname]} {
set rgbdash [tcl::dict::get $WEB_colour_map $cname] set rgbdash [tcl::dict::get $WEB_colour_map $cname]
set rgb [tcl::string::map { - ;} $rgbdash] switch -- $cont {
-contrasting {
set rgb [join [punk::ansi::colour::contrasting {*}[split $rgbdash -]] {;}]
}
-contrastive {
set rgb [join [lindex [punk::ansi::colour::contrast_pair {*}[split $rgbdash -]] 0] {;}]
}
default {
set rgb [tcl::string::map { - ;} $rgbdash]
}
}
lappend t "38;2;$rgb" lappend t "38;2;$rgb"
} else { } else {
puts stderr "ansi web colour unmatched: '$i' in call 'a+ $args'" puts stderr "ansi web colour unmatched: '$i' in call 'a+ $args'"
@ -2375,9 +2451,30 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#variable WEB_colour_map #variable WEB_colour_map
#upvar ::punk::ansi::WEB_colour_map WEB_colour_map #upvar ::punk::ansi::WEB_colour_map WEB_colour_map
#background web colour #background web colour
set cname [tcl::string::tolower [tcl::string::range $i 4 end]] set tail [tcl::string::tolower [tcl::string::range $i 4 end]]
set cont [string range $tail end-11 end]
switch -- $cont {
-contrasting - -contrastive {
set cname [string range $tail 0 end-12]
}
default {
set cname $tail
}
}
if {[tcl::dict::exists $WEB_colour_map $cname]} { if {[tcl::dict::exists $WEB_colour_map $cname]} {
lappend t "48;2;[tcl::string::map {- ;} [tcl::dict::get $WEB_colour_map $cname]]" set rgbdash [tcl::dict::get $WEB_colour_map $cname]
switch -- $cont {
-contrasting {
set rgb [join [punk::ansi::colour::contrasting {*}[split $rgbdash -]] {;}]
}
-contrastive {
set rgb [join [lindex [punk::ansi::colour::contrast_pair {*}[split $rgbdash -]] 0] {;}]
}
default {
set rgb [tcl::string::map { - ;} $rgbdash]
}
}
lappend t "48;2;$rgb"
} else { } else {
puts stderr "ansi Web colour unmatched: '$i' in call 'a+ $args'" puts stderr "ansi Web colour unmatched: '$i' in call 'a+ $args'"
} }
@ -2407,6 +2504,9 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
underline { underline {
lappend t 4 ;#underline lappend t 4 ;#underline
} }
underlinedefault {
lappend t 59
}
underextendedoff { underextendedoff {
#lremove any existing 4:1 etc #lremove any existing 4:1 etc
#NOTE struct::set result order can differ depending on whether tcl/critcl imp used #NOTE struct::set result order can differ depending on whether tcl/critcl imp used
@ -2420,13 +2520,13 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
underdouble { underdouble {
lappend e 4:2 lappend e 4:2
} }
undercurly { undercurly - undercurl {
lappend e 4:3 lappend e 4:3
} }
underdotted { underdotted - underdot {
lappend e 4:4 lappend e 4:4
} }
underdashed { underdashed - underdash {
lappend e 4:5 lappend e 4:5
} }
default { default {
@ -2542,45 +2642,109 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
} }
} }
} }
rgb- - rgb0 - rgb1 - rgb2 - rgb3 - rgb4 - rgb5 - rgb6 - rgb7 - rgb8 - rgb9 { rgb- - rgb0 - rgb1 - rgb2 - rgb3 - rgb4 - rgb5 - rgb6 - rgb7 - rgb8 - rgb9 -
#decimal rgb foreground
#allow variants rgb-xxx-xxx-xxx and rgbxxx-xxx-xxx
set rgbspec [tcl::string::trim [tcl::string::range $i 3 end] -]
set rgb [tcl::string::map [list - {;} , {;}] $rgbspec]
lappend t "38;2;$rgb"
}
Rgb- - RGB- - Rgb0 - Rgb1 - Rgb2 - Rgb3 - Rgb4 - Rgb5 - Rgb6 - Rgb7 - Rgb8 - Rgb9 - RGB0 - RGB1 - RGB2 - RGB3 - RGB4 - RGB5 - RGB6 - RGB7 - RGB8 - RGB9 { Rgb- - RGB- - Rgb0 - Rgb1 - Rgb2 - Rgb3 - Rgb4 - Rgb5 - Rgb6 - Rgb7 - Rgb8 - Rgb9 - RGB0 - RGB1 - RGB2 - RGB3 - RGB4 - RGB5 - RGB6 - RGB7 - RGB8 - RGB9 {
#decimal rgb background #decimal rgb foreground/background
set rgbspec [tcl::string::trim [tcl::string::range $i 3 end] -] #allow variants rgb-xxx-xxx-xxx and rgbxxx-xxx-xxx
set rgb [tcl::string::map [list - {;} , {;}] $rgbspec]
lappend t "48;2;$rgb" set cont [string range $i end-11 end]
} switch -- $cont {
"rgb#" { -contrasting - -contrastive {
#hex rgb foreground set iplain [string range $i 0 end-12]
set hex6 [tcl::string::trim [tcl::string::range $i 4 end] -] }
set rgb [join [::scan $hex6 %2X%2X%2X] {;}] default {
lappend t "38;2;$rgb" set iplain $i
}
}
set rgbspec [tcl::string::trim [tcl::string::range $iplain 3 end] -]
set RGB [tcl::string::map [list - { } , { } {;} { }] $rgbspec] ;#RGB as list
switch -- $cont {
-contrasting {
set rgbfinal [join [punk::ansi::colour::contrasting {*}$RGB] {;}]
}
-contrastive {
set rgbfinal [join [lindex [punk::ansi::colour::contrast_pair {*}$RGB] 0] {;}]
}
default {
set rgbfinal [join $RGB {;}]
}
}
if {[tcl::string::index $i 0] eq "r"} {
#fg
lappend t "38;2;$rgbfinal"
} else {
#bg
lappend t "48;2;$rgbfinal"
}
} }
"Rgb#" - "RGB#" { "rgb#" - "Rgb#" - "RGB#" {
#hex rgb background
set hex6 [tcl::string::trim [tcl::string::range $i 4 end] -] set hex6 [tcl::string::trim [tcl::string::range $i 4 end] -]
set rgb [join [::scan $hex6 %2X%2X%2X] {;}] #set rgb [join [::scan $hex6 %2X%2X%2X] {;}]
lappend t "48;2;$rgb" set RGB [::scan $hex6 %2X%2X%2X]
set cont [string range $i end-11 end]
switch -- $cont {
-contrasting {
set rgbfinal [join [punk::ansi::colour::contrasting {*}$RGB] {;}]
}
-contrastive {
set rgbfinal [join [lindex [punk::ansi::colour::contrast_pair {*}$RGB] 0] {;}]
}
default {
set rgbfinal [join $RGB {;}]
}
}
if {[tcl::string::index $i 0] eq "r"} {
#hex rgb foreground
lappend t "38;2;$rgbfinal"
} else {
#hex rgb background
lappend t "48;2;$rgbfinal"
}
} }
und- - und0 - und1 - und2 - und3 - und4 - und5 - und6 - und7 - und8 - und9 { und- - und0 - und1 - und2 - und3 - und4 - und5 - und6 - und7 - und8 - und9 {
#decimal rgb underline #decimal rgb underline
#allow variants und-xxx-xxx-xxx and undxxx-xxx-xxx #allow variants und-xxx-xxx-xxx and undxxx-xxx-xxx
#https://wezfurlong.org/wezterm/escape-sequences.html#csi-582-underline-color-rgb
set rgbspec [tcl::string::trim [tcl::string::range $i 3 end] -] set rgbspec [tcl::string::trim [tcl::string::range $i 3 end] -]
set rgb [tcl::string::map [list - {:} , {:}] $rgbspec] set RGB [lrange [tcl::string::map [list - { } , { } {;} { }] $rgbspec] 0 2]
lappend e "58:2::$rgb" #puts "---->'$RGB'<----"
set cont [string range $i end-11 end]
switch -- $cont {
-contrasting {
set rgbfinal [join [punk::ansi::colour::contrasting {*}$RGB] {:}]
}
-contrastive {
set rgbfinal [join [lindex [punk::ansi::colour::contrast_pair {*}$RGB] 0] {:}]
}
default {
set rgbfinal [join $RGB {:}]
}
}
#lappend e "58:2:$rgbfinal" ;# - no colorspace ID - some terminals support? which?
lappend e "58:2::$rgbfinal"
} }
"und#" { "und#" {
#hex rgb underline - (e.g kitty, wezterm) - uses colons as separators #hex rgb underline - (e.g kitty, wezterm) - uses colons as separators
set hex6 [tcl::string::trim [tcl::string::range $i 4 end] -] set hex6 [tcl::string::trim [tcl::string::range $i 4 end] -]
set rgb [join [::scan $hex6 %2X%2X%2X] {:}] #set rgb [join [::scan $hex6 %2X%2X%2X] {:}]
lappend e "58:2::$rgb" set RGB [::scan $hex6 %2X%2X%2X]
set cont [string range $i end-11 end]
switch -- $cont {
-contrasting {
set rgbfinal [join [punk::ansi::colour::contrasting {*}$RGB] {:}]
}
-contrastive {
set rgbfinal [join [lindex [punk::ansi::colour::contrast_pair {*}$RGB] 0] {:}]
}
default {
set rgbfinal [join $RGB {:}]
}
}
lappend e "58:2::$rgbfinal"
} }
undt { undt {
#CSI 58:5 UNDERLINE COLOR PALETTE INDEX
#CSI 58 : 5 : INDEX m
#variable TERM_colour_map #variable TERM_colour_map
#256 colour underline by Xterm name or by integer #256 colour underline by Xterm name or by integer
#name is xterm name or colour index from 0 - 255 #name is xterm name or colour index from 0 - 255
@ -2762,6 +2926,9 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
underline { underline {
lappend t 4 ;#underline lappend t 4 ;#underline
} }
underlinedefault {
lappend t 59
}
underextendedoff { underextendedoff {
#lremove any existing 4:1 etc #lremove any existing 4:1 etc
#use of struct::set with critcl is fast, but will reorder e (with differences depending on tcl vs critcl) #use of struct::set with critcl is fast, but will reorder e (with differences depending on tcl vs critcl)
@ -2775,13 +2942,13 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
underdouble { underdouble {
lappend e 4:2 lappend e 4:2
} }
undercurly { undercurly - undercurl {
lappend e 4:3 lappend e 4:3
} }
underdotted { underdotted - underdot {
lappend e 4:4 lappend e 4:4
} }
underdashed { underdashed - underdash {
lappend e 4:5 lappend e 4:5
} }
default { default {
@ -3262,6 +3429,8 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#tput rmam #tput rmam
return \x1b\[?7l return \x1b\[?7l
} }
proc query_mode_line_wrap {} { proc query_mode_line_wrap {} {
#*** !doctools #*** !doctools
#[call [fun query_mode_line_wrap]] #[call [fun query_mode_line_wrap]]
@ -3274,6 +3443,31 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
# \x1b\[?7\;2\$y # \x1b\[?7\;2\$y
#where 1 = set, 2 = unset. (0 = mode not recognised, 3 = permanently set, 4 = permanently unset) #where 1 = set, 2 = unset. (0 = mode not recognised, 3 = permanently set, 4 = permanently unset)
#names for other alt_screen mechanisms: 1047,1048 vs 1049?
variable decmode_names [dict create\
line_wrap 7\
LNM 20\
alt_screen 1049\
grapheme_clusters 2027\
bracketed_paste 2004\
mouse_sgr_extended 1006\
mouse_urxvt 1015\
mouse_sgr 1016\
]
proc query_mode {num_or_name} {
if {[string is integer -strict $num_or_name]} {
set m $num_or_name
} else {
variable decmode_names
if {[dict exists $decmode_names $num_or_name]} {
set m [dict get $decmode_names $num_or_name]
} else {
error "punk::ansi::query_mode unrecognised mode '$num_or_name'. Known mode names: [dict keys $decmode_names]"
}
}
return "\x1b\[?$m\$p"
}
#Alt screen buffer - smcup/rmcup ti/te #Alt screen buffer - smcup/rmcup ti/te
#Note \x1b\[?47h doesn't work at all in some terminals (e.g alacritty,cmd on windows and reportedly kitty) #Note \x1b\[?47h doesn't work at all in some terminals (e.g alacritty,cmd on windows and reportedly kitty)
@ -3658,7 +3852,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
} }
proc ansistrip2 {text} { proc ansistrip2 {text} {
#*** !doctools #*** !doctools
#[call [fun ansistrip] [arg text] ] #[call [fun ansistrip2] [arg text] ]
#[para]Return a string with ansi codes stripped out #[para]Return a string with ansi codes stripped out
#[para]Alternate graphics chars are replaced with modern unicode equivalents (e.g boxdrawing glyphs) #[para]Alternate graphics chars are replaced with modern unicode equivalents (e.g boxdrawing glyphs)
@ -6016,6 +6210,10 @@ tcl::namespace::eval punk::ansi::ansistring {
SP [list \x20 \u2420]\ SP [list \x20 \u2420]\
DEL [list \x7f \u2421]\ DEL [list \x7f \u2421]\
] ]
set map_c0 [dict create]
dict for {k v} $visuals_c0 {
dict set map_c0 {*}$v
}
#alternate symbols for space #alternate symbols for space
# \u2422 Blank Symbol (b with forwardslash overly) # \u2422 Blank Symbol (b with forwardslash overly)
@ -6051,6 +6249,9 @@ tcl::namespace::eval punk::ansi::ansistring {
#miscellaneous debug code brackets #miscellaneous debug code brackets
set obm \u27e6 ;set cbm \u27e7 ;#square double brackets from Miscellaneous Mathematical Symbols-A set obm \u27e6 ;set cbm \u27e7 ;#square double brackets from Miscellaneous Mathematical Symbols-A
#unicode Tags block brackets
set obt \u2993 ;set cbt \u2994
#this private range so rarely supported in fonts - and visuals are unknown, so we will make up some 2-letter codes for now #this private range so rarely supported in fonts - and visuals are unknown, so we will make up some 2-letter codes for now
#set visuals_c1 [tcl::dict::create\ #set visuals_c1 [tcl::dict::create\
# BPH [list \x82 "${ob8}\ue022 $cb8"]\ # BPH [list \x82 "${ob8}\ue022 $cb8"]\
@ -6120,9 +6321,21 @@ tcl::namespace::eval punk::ansi::ansistring {
APC [list \x9f "${ob8}AP$cb8"]\ APC [list \x9f "${ob8}AP$cb8"]\
] ]
#unicode Tags block - nonprinting mapped to ascii 0-127
set visuals_tags [tcl::dict::create]
for {set i 917504} {$i < 917632} {incr i} {
set asciidec [expr {$i - 917504}]
set vis [format %c $asciidec]
if {[dict exists $map_c0 $vis]} {
set vis [dict get $map_c0 $vis]
}
tcl::dict::set visuals_tags TAG$asciidec [list [format %c $i] "${obt}$vis${cbt}"]
}
set hack [tcl::dict::create] set hack [tcl::dict::create]
tcl::dict::set hack BOM1 [list \uFEFF "${obm}\U1f4a3$cbm"] ;#byte order mark/ ZWNBSP (ZWNBSP usage generally deprecated) - a picture of a bomb(2wide glyph) tcl::dict::set hack BOM1 [list \uFEFF "${obm}\U1f4a3$cbm"] ;#byte order mark/ ZWNBSP (ZWNBSP usage generally deprecated) - a picture of a bomb(2wide glyph)
tcl::dict::set hack ZWSP [list \u200B "${obm}ZWSP$cbm"]
#review - other boms? Encoding dependent? #review - other boms? Encoding dependent?
tcl::dict::set hack DCS [list \x90 "${ob8}\u2328 $cb8"] ;#keyboard from Miscellaneous Technical - 1 wide + pad. tcl::dict::set hack DCS [list \x90 "${ob8}\u2328 $cb8"] ;#keyboard from Miscellaneous Technical - 1 wide + pad.
@ -6133,7 +6346,7 @@ tcl::namespace::eval punk::ansi::ansistring {
tcl::dict::set hack PM [list \x9e "${ob8}PM$cb8"] tcl::dict::set hack PM [list \x9e "${ob8}PM$cb8"]
tcl::dict::set hack APC [list \x9f "${ob8}\U1f534$cb8"] ;#bright red ball from Miscellaneoust Symbols and Pictographs - 2 wide (APC also noted as a potential security risk) tcl::dict::set hack APC [list \x9f "${ob8}\U1f534$cb8"] ;#bright red ball from Miscellaneoust Symbols and Pictographs - 2 wide (APC also noted as a potential security risk)
set debug_visuals [tcl::dict::merge $visuals_c0 $visuals_c1 $hack] set debug_visuals [tcl::dict::merge $visuals_c0 $visuals_c1 $hack $visuals_tags]
#for repeated interaction with the same ANSI string - a mechanism to store state is more efficient #for repeated interaction with the same ANSI string - a mechanism to store state is more efficient
proc NEW {string} { proc NEW {string} {
@ -6165,7 +6378,7 @@ tcl::namespace::eval punk::ansi::ansistring {
-sp 1\ -sp 1\
] ]
set argopts [lrange $args 0 end-1] set argopts [lrange $args 0 end-1]
if {[llength $argopts] % 2 != 0} { if {[llength $argopts] % 2} {
error "ansistring VIEW options must be option-value pairs, received '$argopts'. Known opts [tcl::dict::keys $defaults]" error "ansistring VIEW options must be option-value pairs, received '$argopts'. Known opts [tcl::dict::keys $defaults]"
} }
set opts [tcl::dict::merge $defaults $argopts] set opts [tcl::dict::merge $defaults $argopts]
@ -6760,7 +6973,240 @@ tcl::namespace::eval punk::ansi::ansistring {
#*** !doctools #*** !doctools
#[list_end] [comment {--- end definitions namespace punk::ansi::ta ---}] #[list_end] [comment {--- end definitions namespace punk::ansi::ta ---}]
} }
tcl::namespace::eval punk::ansi::control {
proc APC {args} {
return \x1b_[join $args {;}]\x1b\\
}
proc APC8 {args} {
return \x9f[join $args {;}]\x9c
}
proc CSI {args} {
set finalarg [lindex $args end]
set finalbyte [string index $finalarg end]
if {![regexp {[\x40-\x73]} $finalbyte]} {
error "::punk::ansi::control::CSI final byte must be one in the set @A-Z\[\\\]^_`a-z\{|\}~"
}
if {$finalarg eq $finalbyte} {
return \x1b\[[join [lrange $args 0 end-1] {;}]$finalbyte
} else {
return \x1b\[[join $args {;}]
}
}
proc CSI8 {args} {
set finalarg [lindex $args end]
set finalbyte [string index $finalarg end]
if {![regexp {[\x40-\x73]} $finalbyte]} {
error "::punk::ansi::control::CSI final byte must be one in the set @A-Z\[\\\]^_`a-z\{|\}~"
}
if {$finalarg eq $finalbyte} {
return \x9b[join [lrange $args 0 end-1] {;}]$finalbyte
} else {
return \x9b[join $args {;}]
}
}
proc DCS {args} {
return \x1bP[join $args {;}]\x1b\\
}
proc DCS8 {args} {
return \x90[join $args {;}]\x9c
}
proc OSC {args} {
return \x1b\][join $args {;}]\x1b\\
}
proc OSC8 {args} {
return \x9d[join $args {;}]\x9c
}
}
namespace eval punk::ansi::colour {
package require punk::assertion
if {[catch {namespace import ::punk::assertion::assert} errM]} {
puts stderr "punk error importing punk::assertion::assert\n$errM"
puts stderr "punk::a* commands:[info commands ::punk::a*]"
}
punk::assertion::active on
#see also colors package
#https://sourceforge.net/p/irrational-numbers/code/HEAD/tree/pkgs/Colors/trunk/colors.tcl#l159
# classic formula for luminance (0.0 .. 100.0)
proc luminance {R G B} {
return [expr {(0.3*$R + 0.59*$G + 0.11*$B)/255.0}]
}
#New colour's luminance is dark if orig-colour is bright, and viceversa
#(note not all colours are invertable to return original)
proc contrasting {R G B} {
set lum [luminance $R $G $B]
if {$lum < 0.597} {
set lum 0.9
} else {
set lum 0.2
}
lassign [RGB2hsl $R $G $B] h s l
return [hsl2RGB $h $s $lum]
}
proc contrast_pair {R G B} {
set contra [contrasting $R $G $B]
set back [contrasting {*}$contra]
return [list $back $contra] ;#back may or may not equal original R G B
}
proc hsl2RGB { H S L } {
if { $L < 0.5 } {
set Q [expr {$L*(1.0+$S)}]
} else {
set Q [expr {$L+$S-($L*$S)}]
}
set P [expr {2.0*$L-$Q}]
set Hk [expr {$H/360.0}]
set T(R) [expr {$Hk+1.0/3.0}]
set T(G) $Hk
set T(B) [expr {$Hk-1.0/3.0}]
# normalize
foreach c {R G B} {
if {$T($c) < 0.0} { set T($c) [expr {$T($c)+1.0}] }
if {$T($c) > 1.0} { set T($c) [expr {$T($c)-1.0}] }
}
foreach c {R G B} {
if {$T($c) < [expr {1.0/6.0}]} {
set T($c) [expr {$P+($Q-$P)*6.0*$T($c)}]
} elseif {$T($c) < 0.5} {
set T($c) $Q
} elseif {$T($c) < [expr {2.0/3.0}]} {
set T($c) [expr {$P+($Q-$P)*(2.0/3.0-$T($c))*6.0}]
} else {
set T($c) $P
}
set T($c) [expr {round($T($c)*255)}]
}
return [list $T(R) $T(G) $T(B)]
}
proc RGB2hsl { R G B } {
set r [expr {$R/255.0}]
set g [expr {$G/255.0}]
set b [expr {$B/255.0}]
set max $r
set min $r
if { $g > $max } { set max $g }
if { $g < $min } { set min $g }
if { $b > $max } { set max $b }
if { $b < $min } { set min $b }
if { $max == $min } {
set H 0.0
} elseif { $b == $max } {
set H [expr {60* ($r-$g)/($max-$min)+240}]
} elseif { $g == $max } {
set H [expr {60* ($b-$r)/($max-$min)+120}]
} else {
# $r == $max
if { $g >= $b } {
set H [expr {60* ($g-$b)/($max-$min)}]
} else {
set H [expr {60* ($g-$b)/($max-$min)+360}]
}
}
set L [expr {($max+$min)/2}]
if { $L == 0.0 || $max == $min } {
set S 0.0
} elseif { $L <= 0.5 } {
set S [expr {($max-$min)/($max+$min)}]
} else {
set S [expr {($max-$min)/(2.0-($max+$min))}]
}
return [list $H $S $L]
}
#red green blue to hsl (hue saturation luminance)
#https://www.rapidtables.com/convert/color/rgb-to-hsl.html
proc jexer_rgb_to_hsl {red green blue} {
#algorithm port from Jexer LegacySixelEncode.java - with thanks to Autumn Lamonte (MIT lic)
assert {$red >=0 && $red <= 255}
assert {$green >=0 && $green <= 255}
assert {$blue >=0 && $blue <= 255}
set R [expr {$red / 255.0}]
set G [expr {$green / 255.0}]
set B [expr {$blue / 255.0}]
set Rmax 0
set Gmax 0
set Bmax 0
set min [expr {$R < $G ? $R : $G}]
set min [expr {$min < $B ? $min : $B}]
set max 0
if {($R >= $G) && ($R >= $B)} {
set max $R
set Rmax 1
} elseif {($G >= $R) && ($G >= $B)} {
set max $G
set Gmax 1
} elseif {($B >= $G) && ($B >= $R)} {
set max $B
set Bmax 1
}
set L [expr {($min + $max) / 2.0}]
set H 0.0
set S 0.0
#REVIEW - java allows floating point division by 0.0 - producing positive infinity, negative infinity or NaN
#This makes the original java algorithm a little more obscure
if {$min != $max} {
#no divide by zero issues due to min != max
if {$L < 0.5} {
set S [expr {($max - $min) / ($max + $min)}]
} else {
set S [expr {($max - $min) / (2.0 - $max - $min)}]
}
}
if {$Rmax} {
#puts "G'$G' B'$B' max'$max' min'$min'"
assert {$Gmax == 0}
assert {$Bmax == 0}
if {($max - $min) == 0} {
set H 0.0 ;#review
} else {
set H [expr {($G - $B) / ($max - $min)}]
}
} elseif {$Gmax} {
assert {$Rmax == 0}
assert {$Bmax == 0}
if {($max - $min) == 0} {
set H 2.0
} else {
set H [expr {2.0 + ($B - $R) / ($max - $min)}]
}
} elseif {$Bmax} {
assert {$Rmax == 0}
assert {$Gmax == 0}
if {($max - $min) == 0} {
set H 4.0
} else {
set H [expr {4.0 + ($R - $G) / ($max - $min)}]
}
}
if {$H < 0.0} {
set H [expr {$H + 6.0}]
}
#Tcl mathfunc round vs int (which rounds down)
set hue [expr {round($H * 60)}]
set sat [expr {round($S * 100)}]
set lum [expr {round($L * 100)}]
assert {$hue >= 0 && $hue <= 360}
assert {$sat >= 0 && $sat <= 100}
assert {$lum >= 0 && $lum <= 100}
return [list $hue $sat $lum]
}
}
tcl::namespace::eval punk::ansi::internal { tcl::namespace::eval punk::ansi::internal {
proc splitn {str {len 1}} { proc splitn {str {len 1}} {
#from textutil::split::splitn #from textutil::split::splitn
@ -6837,7 +7283,7 @@ tcl::namespace::eval punk::ansi::internal {
if {$2digithexchars eq ""} { if {$2digithexchars eq ""} {
return "" return ""
} }
if {[tcl::string::length $2digithexchars] % 2 != 0} { if {[tcl::string::length $2digithexchars] % 2} {
error "hex2str requires an even number of hex digits (2 per character)" error "hex2str requires an even number of hex digits (2 per character)"
} }
set 2str "" set 2str ""

143
src/bootsupport/modules/punk/args-0.1.0.tm

@ -1,6 +1,6 @@
# -*- tcl -*- # -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'pmix make' or bin/punkmake to update from <pkg>-buildversion.txt # Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'pmix make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.2.tm # module template: punkshell/src/decktemplates/vendor/punk/modules/template_module-0.0.2.tm
# #
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. # Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. # Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
@ -202,6 +202,7 @@
#[list_begin itemized] #[list_begin itemized]
package require Tcl 8.6- package require Tcl 8.6-
#optional? punk::trie #optional? punk::trie
#optional? punk::textblock
#*** !doctools #*** !doctools
#[item] [package {Tcl 8.6-}] #[item] [package {Tcl 8.6-}]
@ -267,7 +268,10 @@ tcl::namespace::eval punk::args {
#[para] Core API functions for punk::args #[para] Core API functions for punk::args
#[list_begin definitions] #[list_begin definitions]
#todo - some sort of punk::args::cherrypick operation to get spec from an existing set
#todo - doctools output from definition
#dict get value with default wrapper for tcl 8.6
if {[info commands ::tcl::dict::getdef] eq ""} { if {[info commands ::tcl::dict::getdef] eq ""} {
#package require punk::lib #package require punk::lib
#interp alias "" ::punk::args::Dict_getdef "" ::punk::lib::dict_getdef #interp alias "" ::punk::args::Dict_getdef "" ::punk::lib::dict_getdef
@ -301,7 +305,7 @@ tcl::namespace::eval punk::args {
#review - how to make work with trie prefix e.g -corner -aliases {-corners} #review - how to make work with trie prefix e.g -corner -aliases {-corners}
#We mightn't want the prefix to be longer just because of an alias #We mightn't want the prefix to be longer just because of an alias
proc Get_argspecs {optionspecs args} { proc definition {optionspecs args} {
variable argspec_cache variable argspec_cache
#variable argspecs ;#REVIEW!! #variable argspecs ;#REVIEW!!
variable argspec_ids variable argspec_ids
@ -434,6 +438,7 @@ tcl::namespace::eval punk::args {
} }
} }
set proc_info {} set proc_info {}
set id_info {} ;#e.g -children <list> ??
set opt_any 0 set opt_any 0
set val_min 0 set val_min 0
set val_max -1 ;#-1 for no limit set val_max -1 ;#-1 for no limit
@ -444,8 +449,8 @@ tcl::namespace::eval punk::args {
"" - # {continue} "" - # {continue}
} }
set linespecs [lassign $trimln argname] set linespecs [lassign $trimln argname]
if {$argname ne "*id" && [llength $linespecs] %2 != 0} { if {$argname ne "*id" && [llength $linespecs] % 2} {
error "punk::args::get_dict - bad optionspecs line for record '$argname' Remaining items on line must be in paired option-value format - received '$linespecs'" error "punk::args::definition - bad optionspecs line for record '$argname' Remaining items on line must be in paired option-value format - received '$linespecs'"
} }
set firstchar [tcl::string::index $argname 0] set firstchar [tcl::string::index $argname 0]
set secondchar [tcl::string::index $argname 1] set secondchar [tcl::string::index $argname 1]
@ -454,14 +459,18 @@ tcl::namespace::eval punk::args {
switch -- [tcl::string::range $argname 1 end] { switch -- [tcl::string::range $argname 1 end] {
id { id {
#id line must have single entry - a unique id assigned by the user - an id will be allocated if no id line present or the value is "auto" #id line must have single entry - a unique id assigned by the user - an id will be allocated if no id line present or the value is "auto"
if {[llength $starspecs] != 1} { if {[llength $starspecs] == 0} {
error "punk::args::Get_argspecs - *id line must have a single entry following *id." error "punk::args::definition - *id line must have at least a single entry following *id."
} }
if {$spec_id ne ""} { if {$spec_id ne ""} {
#disallow duplicate *id line #disallow duplicate *id line
error "punk::args::Get_argspecs - *id already set. Existing value $spec_id" error "punk::args::definition - *id already set. Existing value $spec_id"
}
set spec_id [lindex $starspecs 0]
set id_info [lrange $starspecs 1 end]
if {[llength $id_info] %2} {
error "punk::args::definition - bad *id line. Remaining items on line after *id <id> must be in paired option-value format - received '$linespecs'"
} }
set spec_id $starspecs
} }
proc { proc {
#allow arbitrary - review #allow arbitrary - review
@ -523,7 +532,7 @@ tcl::namespace::eval punk::args {
-type -optional -allow_ansi -validate_ansistripped -strip_ansi -multiple\ -type -optional -allow_ansi -validate_ansistripped -strip_ansi -multiple\
-regexprepass -regexprefail -validationtransform\ -regexprepass -regexprefail -validationtransform\
} }
error "punk::args::Get_argspecs - unrecognised key '$k' in *opts line. Known keys: $known" error "punk::args::definition - unrecognised key '$k' in *opts line. Known keys: $known"
} }
} }
} }
@ -534,14 +543,14 @@ tcl::namespace::eval punk::args {
-min - -min -
-minvalues { -minvalues {
if {$v < 0} { if {$v < 0} {
error "punk::args::Get_argspecs - minimum acceptable value for key '$k' in *opts line is 0. got $v" error "punk::args::definition - minimum acceptable value for key '$k' in *opts line is 0. got $v"
} }
set val_min $v set val_min $v
} }
-max - -max -
-maxvalues { -maxvalues {
if {$v < -1} { if {$v < -1} {
error "punk::args::Get_argspecs - minimum acceptable value for key '$k' in *opts line is -1 (indicating unlimited). got $v" error "punk::args::definition - minimum acceptable value for key '$k' in *opts line is -1 (indicating unlimited). got $v"
} }
set val_max $v set val_max $v
} }
@ -594,14 +603,14 @@ tcl::namespace::eval punk::args {
-type -optional -allow_ansi -validate_ansistripped -strip_ansi -multiple\ -type -optional -allow_ansi -validate_ansistripped -strip_ansi -multiple\
-regexprepass -regexprefail -validationtransform\ -regexprepass -regexprefail -validationtransform\
} }
error "punk::args::Get_argspecs - unrecognised key '$k' in *values line. Known keys: $known" error "punk::args::definition - unrecognised key '$k' in *values line. Known keys: $known"
} }
} }
} }
} }
default { default {
error "punk::args::Get_argspecs - unrecognised * line in '$ln'. Expected *proc *opts or *values - use **name if paramname needs to be *name" error "punk::args::definition - unrecognised * line in '$ln'. Expected *proc *opts or *values - use **name if paramname needs to be *name"
} }
} }
continue continue
@ -654,7 +663,7 @@ tcl::namespace::eval punk::args {
lappend opt_solos $argname lappend opt_solos $argname
} else { } else {
#-solo only valid for flags #-solo only valid for flags
error "punk::args::get_dict - invalid -type 'none' for positional argument positional argument '$argname'" error "punk::args::definition - invalid -type 'none' for positional argument positional argument '$argname'"
} }
} }
any - anything { any - anything {
@ -681,8 +690,8 @@ tcl::namespace::eval punk::args {
} }
-validationtransform { -validationtransform {
#string is dict only 8.7/9+ #string is dict only 8.7/9+
if {([llength $specval] % 2) != 0} { if {[llength $specval] % 2} {
error "punk::args::get_dict - invalid value for key '$spec' in specifications for argument '$argname' - value must be a dictionary" error "punk::args::definition - invalid value for key '$spec' in specifications for argument '$argname' - value must be a dictionary"
} }
dict for {tk tv} $specval { dict for {tk tv} $specval {
switch -- $tk { switch -- $tk {
@ -690,7 +699,7 @@ tcl::namespace::eval punk::args {
} }
default { default {
set known_transform_keys [list -function -type -minlen -maxlen -range] ;#-choices etc? set known_transform_keys [list -function -type -minlen -maxlen -range] ;#-choices etc?
error "punk::args::get_dict - invalid subkey $tk for key '$spec' in specifications for argument '$argname' must be one of: $known_transform_keys" error "punk::args::definition - invalid subkey $tk for key '$spec' in specifications for argument '$argname' must be one of: $known_transform_keys"
} }
} }
} }
@ -701,7 +710,7 @@ tcl::namespace::eval punk::args {
-nocase -optional -multiple -validate_ansistripped -allow_ansi -strip_ansi -help\ -nocase -optional -multiple -validate_ansistripped -allow_ansi -strip_ansi -help\
-regexprepass -regexprefail -validationtransform\ -regexprepass -regexprefail -validationtransform\
] ]
error "punk::args::get_dict - unrecognised key '$spec' in specifications for argument '$argname' Known option specification keys: $known_argspecs" error "punk::args::definition - unrecognised key '$spec' in specifications for argument '$argname' Known option specification keys: $known_argspecs"
} }
} }
} }
@ -764,6 +773,7 @@ tcl::namespace::eval punk::args {
valspec_defaults $valspec_defaults\ valspec_defaults $valspec_defaults\
val_checks_defaults $val_checks_defaults\ val_checks_defaults $val_checks_defaults\
proc_info $proc_info\ proc_info $proc_info\
id_info $id_info\
] ]
tcl::dict::set argspec_cache $cache_key $result tcl::dict::set argspec_cache $cache_key $result
#tcl::dict::set argspecs $spec_id $optionspecs #tcl::dict::set argspecs $spec_id $optionspecs
@ -817,6 +827,7 @@ tcl::namespace::eval punk::args {
} }
set caller [regexp -inline {\S+} $cmdinfo] set caller [regexp -inline {\S+} $cmdinfo]
if {$caller eq "namespace"} { if {$caller eq "namespace"} {
# review - message?
set cmdinfo "punk::args::get_dict called from namespace" set cmdinfo "punk::args::get_dict called from namespace"
} }
return $cmdinfo return $cmdinfo
@ -825,6 +836,7 @@ tcl::namespace::eval punk::args {
#basic recursion blocker #basic recursion blocker
variable arg_error_isrunning 0 variable arg_error_isrunning 0
proc arg_error {msg spec_dict {badarg ""}} { proc arg_error {msg spec_dict {badarg ""}} {
#limit colours to standard 16 so that themes can apply to help output
variable arg_error_isrunning variable arg_error_isrunning
if {$arg_error_isrunning} { if {$arg_error_isrunning} {
error "arg_error already running - error in arg_error?\n triggering errmsg: $badarg" error "arg_error already running - error in arg_error?\n triggering errmsg: $badarg"
@ -843,20 +855,17 @@ tcl::namespace::eval punk::args {
set procname [::punk::args::Dict_getdef $spec_dict proc_info -name ""] set procname [::punk::args::Dict_getdef $spec_dict proc_info -name ""]
set prochelp [::punk::args::Dict_getdef $spec_dict proc_info -help ""] set prochelp [::punk::args::Dict_getdef $spec_dict proc_info -help ""]
#set t [textblock::class::table new [a+ web-yellow]Usage[a]]
set t [textblock::class::table new [a+ brightyellow]Usage[a]] set t [textblock::class::table new [a+ brightyellow]Usage[a]]
set blank_header_col [list ""] set blank_header_col [list ""]
if {$procname ne ""} { if {$procname ne ""} {
lappend blank_header_col "" lappend blank_header_col ""
#set procname_display [a+ web-white]$procname[a]
set procname_display [a+ brightwhite]$procname[a] set procname_display [a+ brightwhite]$procname[a]
} else { } else {
set procname_display "" set procname_display ""
} }
if {$prochelp ne ""} { if {$prochelp ne ""} {
lappend blank_header_col "" lappend blank_header_col ""
#set prochelp_display [a+ web-white]$prochelp[a]
set prochelp_display [a+ brightwhite]$prochelp[a] set prochelp_display [a+ brightwhite]$prochelp[a]
} else { } else {
set prochelp_display "" set prochelp_display ""
@ -880,12 +889,19 @@ tcl::namespace::eval punk::args {
$t configure_header 2 -values {Arg Type Default Multiple Help} $t configure_header 2 -values {Arg Type Default Multiple Help}
} }
#set c_default [a+ web-white Web-limegreen]
set c_default [a+ brightwhite Brightgreen] set RST [a]
#set c_badarg [a+ web-crimson] #set A_DEFAULT [a+ brightwhite Brightgreen]
set c_badarg [a+ brightred] set A_DEFAULT ""
#set greencheck [a+ web-limegreen]\u2713[a] set A_BADARG [a+ brightred]
set greencheck [a+ brightgreen]\u2713[a] set greencheck [a+ brightgreen]\u2713[a]
set A_PREFIX [a+ green] ;#use a+ so colour off can apply
if {$A_PREFIX eq ""} {
set A_PREFIX [a+ underline]
set A_PREFIXEND [a+ nounderline]\u200B ;#padding will take ANSI from last char - so add a zero width space
} else {
set A_PREFIXEND $RST
}
set opt_names [list] set opt_names [list]
set opt_names_display [list] set opt_names_display [list]
@ -894,8 +910,6 @@ tcl::namespace::eval punk::args {
set trie [punk::trie::trieclass new {*}[dict get $spec_dict opt_names]] set trie [punk::trie::trieclass new {*}[dict get $spec_dict opt_names]]
set idents [dict get [$trie shortest_idents ""] scanned] set idents [dict get [$trie shortest_idents ""] scanned]
$trie destroy $trie destroy
set M "\x1b\[32m" ;#mark in green
set RST "\x1b\[m"
foreach c [dict get $spec_dict opt_names] { foreach c [dict get $spec_dict opt_names] {
set id [dict get $idents $c] set id [dict get $idents $c]
#REVIEW #REVIEW
@ -907,7 +921,7 @@ tcl::namespace::eval punk::args {
set prefix [string range $c 0 $idlen-1] set prefix [string range $c 0 $idlen-1]
set tail [string range $c $idlen end] set tail [string range $c $idlen end]
} }
lappend opt_names_display $M$prefix$RST$tail lappend opt_names_display $A_PREFIX$prefix$A_PREFIXEND$tail
#lappend opt_names_display $M[ansistring VIEW $prefix]$RST[ansistring VIEW $tail] #lappend opt_names_display $M[ansistring VIEW $prefix]$RST[ansistring VIEW $tail]
lappend opt_names $c lappend opt_names $c
} }
@ -916,18 +930,31 @@ tcl::namespace::eval punk::args {
set opt_names_display $opt_names set opt_names_display $opt_names
} }
} }
set val_names [dict get $spec_dict val_names] set trailing_val_names [dict get $spec_dict val_names] ;#temporarily assign all as trailing
set val_names_display $val_names set leading_val_names [list]
dict for {argname info} [tcl::dict::get $spec_dict arg_info] {
if {![string match -* $argname]} {
lappend leading_val_names [lpop trailing_val_names 0]
} else {
break
}
}
if {![llength $leading_val_names] && ![llength $opt_names]} {
#all vals were actually trailing - no opts
set trailing_val_names $leading_val_names
set leading_val_names {}
}
set leading_val_names_display $leading_val_names
set trailing_val_names_display $trailing_val_names
#display options first then values #display options first then values
foreach argumentset [list [list $opt_names_display $opt_names] [list $val_names_display $val_names]] { foreach argumentset [list [list $leading_val_names_display $leading_val_names] [list $opt_names_display $opt_names] [list $trailing_val_names_display $trailing_val_names]] {
lassign $argumentset argnames_display argnames lassign $argumentset argnames_display argnames
foreach argshow $argnames_display arg $argnames { foreach argshow $argnames_display arg $argnames {
set arginfo [dict get $spec_dict arg_info $arg] set arginfo [dict get $spec_dict arg_info $arg]
if {[dict exists $arginfo -default]} { if {[dict exists $arginfo -default]} {
#set default $c_default[dict get $arginfo -default] set default $A_DEFAULT[dict get $arginfo -default]$RST
set default [dict get $arginfo -default]
} else { } else {
set default "" set default ""
} }
@ -954,8 +981,6 @@ tcl::namespace::eval punk::args {
set trie [punk::trie::trieclass new {*}[dict get $arginfo -choices]] set trie [punk::trie::trieclass new {*}[dict get $arginfo -choices]]
set idents [dict get [$trie shortest_idents ""] scanned] set idents [dict get [$trie shortest_idents ""] scanned]
$trie destroy $trie destroy
set M "\x1b\[32m" ;#mark in green
set RST "\x1b\[m"
foreach c [dict get $arginfo -choices] { foreach c [dict get $arginfo -choices] {
set id [dict get $idents $c] set id [dict get $idents $c]
if {$id eq $c} { if {$id eq $c} {
@ -966,7 +991,7 @@ tcl::namespace::eval punk::args {
set prefix [string range $c 0 $idlen-1] set prefix [string range $c 0 $idlen-1]
set tail [string range $c $idlen end] set tail [string range $c $idlen end]
} }
lappend formattedchoices "$M[ansistring VIEW $prefix]$RST[ansistring VIEW $tail]" lappend formattedchoices "$A_PREFIX[ansistring VIEW $prefix]$A_PREFIXEND[ansistring VIEW $tail]"
} }
} errM]} { } errM]} {
puts stderr "prefix marking failed\n$errM" puts stderr "prefix marking failed\n$errM"
@ -999,7 +1024,7 @@ tcl::namespace::eval punk::args {
} }
$t add_row [list $argshow [dict get $arginfo -type] $default $multiple $help] $t add_row [list $argshow [dict get $arginfo -type] $default $multiple $help]
if {$arg eq $badarg} { if {$arg eq $badarg} {
$t configure_row [expr {[$t row_count]-1}] -ansibase $c_badarg $t configure_row [expr {[$t row_count]-1}] -ansibase $A_BADARG
} }
} }
} }
@ -1033,10 +1058,10 @@ tcl::namespace::eval punk::args {
#provide ability to look up and reuse definitions from ids etc #provide ability to look up and reuse definitions from ids etc
# #
proc get_dict_by_id {id {arglist ""}} { proc get_by_id {id {arglist ""}} {
set spec [get_spec $id] set spec [get_spec $id]
if {$spec eq ""} { if {$spec eq ""} {
error "punk::args::get_dict_by_id - no such id: $id" error "punk::args::get_by_id - no such id: $id"
} }
return [get_dict $spec $arglist] return [get_dict $spec $arglist]
} }
@ -1121,7 +1146,7 @@ tcl::namespace::eval punk::args {
} }
set argspecs [Get_argspecs $optionspecs] set argspecs [definition $optionspecs]
tcl::dict::with argspecs {} ;#turn keys into vars tcl::dict::with argspecs {} ;#turn keys into vars
#puts "-arg_info->$arg_info" #puts "-arg_info->$arg_info"
set flagsreceived [list] ;#for checking if required flags satisfied set flagsreceived [list] ;#for checking if required flags satisfied
@ -1132,11 +1157,24 @@ tcl::namespace::eval punk::args {
#todo: -minmultiple -maxmultiple ? #todo: -minmultiple -maxmultiple ?
# -- --- --- ---
# Handle leading positionals
# todo - consider allowing last leading positional to have -multiple 1 but only if there exists an eopts marker later (--) ?
set opts $opt_defaults set opts $opt_defaults
set pre_values {}
dict for {a info} $arg_info {
#todo - flag for possible subhandler - whether leading - or not (shellfilter concept)
if {![string match -* $a]} {
lappend pre_values [lpop rawargs 0]
} else {
break
}
}
#assert - rawargs has been reduced by leading positionals
if {$id ne "jtest"} { if {$id ne "jtest"} {
set arglist {} set arglist {}
set values {} set post_values {}
#val_min, val_max #val_min, val_max
#puts stderr "rawargs: $rawargs" #puts stderr "rawargs: $rawargs"
#puts stderr "arg_info: $arg_info" #puts stderr "arg_info: $arg_info"
@ -1157,7 +1195,7 @@ tcl::namespace::eval punk::args {
if {$remaining_args_including_this <= $val_min} { if {$remaining_args_including_this <= $val_min} {
# if current arg is -- it will pass through as a value here # if current arg is -- it will pass through as a value here
set arglist [lrange $rawargs 0 $i-1] set arglist [lrange $rawargs 0 $i-1]
set values [lrange $rawargs $i end] set post_values [lrange $rawargs $i end]
break break
} }
@ -1169,19 +1207,19 @@ tcl::namespace::eval punk::args {
if {$remaining_args_including_this == $val_max} { if {$remaining_args_including_this == $val_max} {
#assume it's a value. #assume it's a value.
set arglist [lrange $rawargs 0 $i-1] set arglist [lrange $rawargs 0 $i-1]
set values [lrange $rawargs $i end] set post_values [lrange $rawargs $i end]
} else { } else {
#assume it's an end-of-options marker #assume it's an end-of-options marker
lappend flagsreceived -- lappend flagsreceived --
set arglist [lrange $rawargs 0 $i] set arglist [lrange $rawargs 0 $i]
set values [lrange $rawargs $i+1 end] set post_values [lrange $rawargs $i+1 end]
} }
} else { } else {
#unlimited number of values accepted #unlimited number of post_values accepted
#treat this as eopts - we don't care if remainder look like options or not #treat this as eopts - we don't care if remainder look like options or not
lappend flagsreceived -- lappend flagsreceived --
set arglist [lrange $rawargs 0 $i] set arglist [lrange $rawargs 0 $i]
set values [lrange $rawargs $i+1 end] set post_values [lrange $rawargs $i+1 end]
} }
break break
} else { } else {
@ -1194,7 +1232,7 @@ tcl::namespace::eval punk::args {
#if no optvalue following - assume it's a value #if no optvalue following - assume it's a value
#(caller should probably have used -- before it) #(caller should probably have used -- before it)
set arglist [lrange $rawargs 0 $i-1] set arglist [lrange $rawargs 0 $i-1]
set values [lrange $rawargs $i end] set post_values [lrange $rawargs $i end]
break break
} }
@ -1242,7 +1280,7 @@ tcl::namespace::eval punk::args {
#unmatched option in right position to be considered a value - treat like eopts #unmatched option in right position to be considered a value - treat like eopts
#review - document that an unspecified arg within range of possible values will act like eopts -- #review - document that an unspecified arg within range of possible values will act like eopts --
set arglist [lrange $rawargs 0 $i-1] set arglist [lrange $rawargs 0 $i-1]
set values [lrange $rawargs $i end] set post_values [lrange $rawargs $i end]
break break
} }
if {$opt_any} { if {$opt_any} {
@ -1284,12 +1322,13 @@ tcl::namespace::eval punk::args {
} else { } else {
#not flaglike #not flaglike
set arglist [lrange $rawargs 0 $i-1] set arglist [lrange $rawargs 0 $i-1]
set values [lrange $rawargs $i end] set post_values [lrange $rawargs $i end]
break break
} }
} }
set values [list {*}$pre_values {*}$post_values]
} else { } else {
set values $rawargs ;#no -flags detected set values [list {*}$pre_values {*}$rawargs] ;#no -flags detected
set arglist [list] set arglist [list]
} }
#puts stderr "--> arglist: $arglist" #puts stderr "--> arglist: $arglist"

102
src/bootsupport/modules/punk/char-0.1.0.tm

@ -1912,19 +1912,32 @@ tcl::namespace::eval punk::char {
tailcall ansifreestring_width $text tailcall ansifreestring_width $text
} }
#faster than textutil::wcswidth (at least for string up to a few K in length) #todo - consider disallowing/erroring out when \r \n in string?
# - tab/vtab?
# - compare with wcswidth returning -1 for entire string containing such in python,perl
proc wcswidth {string} { proc wcswidth {string} {
#faster than textutil::wcswidth (at least for string up to a few K in length)
#..but - 'scan' is horrible for 400K+
#TODO
set codes [scan $string [tcl::string::repeat %c [tcl::string::length $string]]] set codes [scan $string [tcl::string::repeat %c [tcl::string::length $string]]]
set width 0 set width 0
foreach c $codes { foreach c $codes {
if {$c <= 255} { #unicode Tags block zero width
incr width if {$c < 917504 || $c > 917631} {
} else { if {$c <= 255} {
set w [textutil::wcswidth_char $c] #review - non-printing ascii? why does textutil::wcswidth report 1 ??
if {$w < 0} { #todo - compare with python or other lang wcwidth
return -1 if {!($c < 31 || $c == 127)} {
incr width
}
} else { } else {
#TODO - various other joiners and non-printing chars
set w [textutil::wcswidth_char $c]
if {$w < 0} {
return -1
} else {
incr width $w incr width $w
}
} }
} }
} }
@ -2029,7 +2042,8 @@ tcl::namespace::eval punk::char {
# return [tcl::string::length $text] # return [tcl::string::length $text]
#} #}
if {![regexp "\[\uFF-\U10FFFF\]" $text]} { if {![regexp "\[\uFF-\U10FFFF\]" $text]} {
return [tcl::string::length $text] #return [tcl::string::length $text]
return [punk::char::wcswidth $text] ;#still use our wcswidth to account for non-printable ascii
} }
#split just to get the standalone character widths - and then scan for other combiners (?) - or scan for clusters first? #split just to get the standalone character widths - and then scan for other combiners (?) - or scan for clusters first?
@ -2052,7 +2066,7 @@ tcl::namespace::eval punk::char {
#fall back to textutil::wcswidth (which doesn't for example handle diactricts/combiners so we can't use until these and other things such as \u200b and diacritics are already stripped/accounted for) #fall back to textutil::wcswidth (which doesn't for example handle diactricts/combiners so we can't use until these and other things such as \u200b and diacritics are already stripped/accounted for)
#todo - find something that understands grapheme clusters - needed also for grapheme_split #todo - find something that understands grapheme clusters - needed also for grapheme_split
#use punk::char::wcswidth - faster than the string split in textutil::wcswidth but still uses textutil::wcswidth_char #use punk::char::wcswidth - faster than the string split in textutil::wcswidth but still uses textutil::wcswidth_char
incr len [wcswidth $uc] incr len [punk::char::wcswidth $uc]
} }
#todo - work out what to do with anomalies like grave combiner which print at different lengths on different terminals (fonts?) and for which cursor-query escape sequence lies. #todo - work out what to do with anomalies like grave combiner which print at different lengths on different terminals (fonts?) and for which cursor-query escape sequence lies.
return $len return $len
@ -2229,7 +2243,68 @@ tcl::namespace::eval punk::char {
return [tcl::string::map $map $str] return [tcl::string::map $map $str]
} }
#todo - lookup from unicode tables
variable flags [dict create\
AU \U1F1E6\U1F1FA\
US \U1F1FA\U1F1F8\
ZW \U1F1FF\U1F1FC
]
variable rflags
dict for {k v} $flags {
dict set rflags $v $k
}
proc flag_from_ascii {code} {
variable flags
if {[regexp {^[A-Z]{2}$} $code]} {
if {[dict exists $flags $code]} {
return [dict get $flags $code]
} else {
error "unsupported flags code: $code"
}
} else {
#try as subregion
#e.g gbeng,gbwls,gbsct
return \U1f3f4[tag_from_ascii $code]\Ue007f
}
}
proc flag_to_ascii {charsequence} {
variable rflags
if {[dict exists $rflags $charsequence]} {
return [dict get $rflags $charsequence]
}
if {[string index $charsequence 0] eq "\U1F3F4" && [string index $charsequence end] eq "\UE007F"} {
#subdivision flag
set tag [string range $charsequence 1 end-1]
return [tag_to_ascii $tag]
}
error "unknown flag $charsequence"
}
proc tag_to_ascii {t} {
set fmt [string repeat %c [string length $t]]
set declist [scan $t $fmt]
#unicode Tags block - e0000 to e007f
set declist [lmap dec $declist {
if {$dec < 917504 || $dec > 917631} {
error "char [ansistring VIEW -lf 1 -cr 1 -vt 1 [format %c $dec]] has decimal value $dec. Not in unicode Tags block range 917504-917631 (e0000-e007f)"
}
incr dec -917504
}]
return [format $fmt {*}$declist]
}
proc tag_from_ascii {a} {
set fmt [string repeat %c [string length $a]]
set declist [scan $a $fmt]
set declist [lmap dec $declist {
if {$dec > 127} {
error "char [ansistring VIEW -lf 1 -cr 1 -vt 1 [format %c $dec]] has decimal value $dec. Not in ascii range 0-127"
}
incr dec 917504
}]
return [format $fmt {*}$declist]
}
#split into plaintext and runs of combiners (combining diacritical marks - not ZWJ or ZWJNJ) #split into plaintext and runs of combiners (combining diacritical marks - not ZWJ or ZWJNJ)
proc combiner_split {text} { proc combiner_split {text} {
@ -2250,15 +2325,12 @@ tcl::namespace::eval punk::char {
#puts "->start $start ->match $matchStart $matchEnd" #puts "->start $start ->match $matchStart $matchEnd"
lappend list [tcl::string::range $text $start $matchStart-1] [tcl::string::range $text $matchStart $matchEnd] lappend list [tcl::string::range $text $start $matchStart-1] [tcl::string::range $text $matchStart $matchEnd]
set start [expr {$matchEnd+1}] set start [expr {$matchEnd+1}]
#if {$start >= [tcl::string::length $text]} {
# break
#}
} }
lappend list [tcl::string::range $text $start end] lappend list [tcl::string::range $text $start end]
} }
#ZWJ ZWNJ ? #ZWJ ZWNJ ?
#SWSP ?
#1st shot - basic diacritics #1st shot - basic diacritics
#todo - become aware of unicode grapheme cluster boundaries #todo - become aware of unicode grapheme cluster boundaries
@ -2269,6 +2341,8 @@ tcl::namespace::eval punk::char {
#should also handle the ZWJ (and the variation selectors? eg \uFE0F) character which should account for emoji clusters #should also handle the ZWJ (and the variation selectors? eg \uFE0F) character which should account for emoji clusters
#Note - emoji cluster could be for example 11 code points/41 bytes (family emoji with skin tone modifiers for each member, 3 ZWJs) #Note - emoji cluster could be for example 11 code points/41 bytes (family emoji with skin tone modifiers for each member, 3 ZWJs)
#This still leaves a whole class of clusters.. korean etc unhandled :/ #This still leaves a whole class of clusters.. korean etc unhandled :/
#todo - tk::startOfCluster / tk::endOfCluster - try to get it brought into Tcl
#https://core.tcl-lang.org/tcl/tktview/a4c7eeaf63
proc grapheme_split {text} { proc grapheme_split {text} {
set graphemes [list] set graphemes [list]
set csplits [combiner_split $text] set csplits [combiner_split $text]
@ -2287,7 +2361,7 @@ tcl::namespace::eval punk::char {
set graphemes [list] set graphemes [list]
set csplits [combiner_split $text] set csplits [combiner_split $text]
foreach {pt combiners} [lrange $csplits 0 end-1] { foreach {pt combiners} [lrange $csplits 0 end-1] {
set pt_decs [scan $pt [tcl::string::repeat %c [tcl::string::length $pt]]] set pt_decs [scan $pt [tcl::string::repeat %c [tcl::string::length $pt]]] ;#warning scan %c... slow for v large strings (e.g 400k+)
set combiner_decs [scan $combiners [tcl::string::repeat %c [tcl::string::length $combiners]]] set combiner_decs [scan $combiners [tcl::string::repeat %c [tcl::string::length $combiners]]]
lset pt_decs end [concat [lindex $pt_decs end] $combiner_decs] lset pt_decs end [concat [lindex $pt_decs end] $combiner_decs]
lappend graphemes {*}$pt_decs lappend graphemes {*}$pt_decs

326
src/bootsupport/modules/punk/console-0.1.1.tm

@ -46,9 +46,12 @@
package require Tcl 8.6- package require Tcl 8.6-
package require Thread ;#tsv required to sync is_raw package require Thread ;#tsv required to sync is_raw
package require punk::ansi package require punk::ansi
package require punk::args
#*** !doctools #*** !doctools
#[item] [package {Tcl 8.6-}] #[item] [package {Tcl 8.6-}]
#[item] [package {Thread}]
#[item] [package {punk::ansi}] #[item] [package {punk::ansi}]
#[item] [package {punk::args}]
#*** !doctools #*** !doctools
@ -109,6 +112,8 @@ namespace eval punk::console {
variable ansi_wanted 2 ;#2 for default assumed yes, will be set to -1 for automatically unwanted when ansi unavailable values of 0 or 1 won't be autoset variable ansi_wanted 2 ;#2 for default assumed yes, will be set to -1 for automatically unwanted when ansi unavailable values of 0 or 1 won't be autoset
#punk::console namespace - contains *directly* acting functions - some based on ansi escapes from the 'ansi' sub namespace, some on local system calls or executable calls wrapped in the 'local' sub namespace #punk::console namespace - contains *directly* acting functions - some based on ansi escapes from the 'ansi' sub namespace, some on local system calls or executable calls wrapped in the 'local' sub namespace
#directly acting means they write to stdout to cause the console to peform the action, or they perform the action immediately via other means. #directly acting means they write to stdout to cause the console to peform the action, or they perform the action immediately via other means.
#punk::console::ansi contains a subset of punk::ansi, but with emission to stdout as opposed to simply returning the ansi sequence. #punk::console::ansi contains a subset of punk::ansi, but with emission to stdout as opposed to simply returning the ansi sequence.
@ -255,6 +260,8 @@ namespace eval punk::console {
enable_bracketed_paste enable_bracketed_paste
} }
#todo stop_application_mode {} {}
proc mode {{raw_or_line query}} { proc mode {{raw_or_line query}} {
#variable is_raw #variable is_raw
variable ansi_available variable ansi_available
@ -634,7 +641,7 @@ namespace eval punk::console {
#todo - make timeout configurable? #todo - make timeout configurable?
set waitvarname "::punk::console::ansi_response_wait($callid)" set waitvarname "::punk::console::ansi_response_wait($callid)"
#500ms is generally plenty for a terminal to respond.. but not in some cases. e.g event loop busy with stdin keypress?? review #500ms is generally plenty for a terminal to respond.. but not in some cases. e.g event loop busy with stdin keypress?? review
set timeoutid($callid) [after 2000 [list set $waitvarname timedout]] set timeoutid($callid) [after 1000 [list set $waitvarname timedout]]
#JMN #JMN
# - stderr vs stdout # - stderr vs stdout
@ -1040,6 +1047,64 @@ namespace eval punk::console {
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels] set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload return $payload
} }
variable last_da1_result ""
#TODO - 22? 28? 32?
#1 132 columns
#2 Printer port extension
#4 Sixel extension
#6 Selective erase
#7 DRCS
#8 UDK
#9 NRCS
#12 SCS extension
#15 Technical character set
#18 Windowing capability
#21 Horizontal scrolling
#23 Greek extension
#24 Turkish extension
#42 ISO Latin 2 character set
#44 PCTerm
#45 Soft key map
#46 ASCII emulation
#https://vt100.net/docs/vt510-rm/DA1.html
#
proc get_device_attributes {{inoutchannels {stdin stdout}}} {
#DA1
variable last_da1_result
#first element in result is the terminal's architectural class 61,62,63,64.. ?
#for vt100 we get things like: "ESC\[?1;0c"
#for vt102 "ESC\[?6c"
#set capturingregex {(.*)(\x1b\[\?6[0-9][;]*([;0-9]+)c)$} ;#must capture prefix,entire-response,response-payload
set capturingregex {(.*)(\x1b\[\?([0-9]*[;0-9]+)c)$} ;#must capture prefix,entire-response,response-payload
set request "\x1b\[c"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
set last_da1_result $payload
return $payload
}
#https://vt100.net/docs/vt510-rm/DA2.html
proc get_device_attributes_secondary {{inoutchannels {stdin stdout}}} {
#DA2
set capturingregex {(.*)(\x1b\[\>([0-9]*[;][0-9]*[;][0-1]c))$} ;#must capture prefix,entire-response,response-payload
#expect CSI > X;10|20;0|1c - docs suggest X should be something like 61. In practice we see 0 or 1 (windows) REVIEW
set request "\x1b\[>c"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload
}
proc get_device_attributes_tertiary {{inoutchannels {stdin stdout}}} {
#DA3
set capturingregex {(.*)(\x1bP!\|([0-9]{8})\x1b\\)$} ;#must capture prefix,entire-response,response-payload
set request "\x1b\[=c"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload
}
proc get_terminal_id {{inoutchannels {stdin stdout}}} {
#DA3 - alias
get_device_attributes_tertiary $inoutchannels
}
proc get_tabstops {{inoutchannels {stdin stdout}}} { proc get_tabstops {{inoutchannels {stdin stdout}}} {
#DECTABSR \x1b\[2\$w #DECTABSR \x1b\[2\$w
#response example " ^[P2$u9/17/25/33/41/49/57/65/73/81^[\ " (where ^[ is \x1b) #response example " ^[P2$u9/17/25/33/41/49/57/65/73/81^[\ " (where ^[ is \x1b)
@ -1110,6 +1175,55 @@ namespace eval punk::console {
return [split [get_cursor_pos $inoutchannels] ";"] return [split [get_cursor_pos $inoutchannels] ";"]
} }
#todo - work out how to query terminal and set cell size in pixels
#for now use the windows default
variable cell_size
set cell_size ""
set cell_size_fallback 10x20
#todo - change -inoutchannels to -terminalobject with prebuilt default
punk::args::definition {
*id punk::console::cell_size
-inoutchannels -default {stdin stdout} -type list
*values -min 0 -max 1
newsize -default ""
}
proc cell_size {args} {
set argd [punk::args::get_by_id punk::console::cell_size $args]
set inoutchannels [dict get $argd opts -inoutchannels]
set newsize [dict get $argd values newsize]
variable cell_size
if {$newsize eq ""} {
#query existing setting
if {$cell_size eq ""} {
#not set - try to query terminal's overall dimensions
set pixeldict [punk::console::get_xterm_pixels $inoutchannels]
lassign $pixeldict _w sw _h sh
if {[string is integer -strict $sw] && [string is integer -strict $sh]} {
lassign [punk::console::get_size] _cols columns _rows rows
#review - is returned size in pixels always a multiple of rows and cols?
set w [expr {$sw / $columns}]
set h [expr {$sh / $rows}]
set cell_size ${w}x${h}
return $cell_size
} else {
set cell_size $::punk::console::cell_size_fallback
puts stderr "punk::console::cell_size unable to query terminal for pixel data - using default $cell_size"
return $cell_size
}
}
return $cell_size
}
#newsize supplied - try to set
lassign [split [string tolower $newsize] x] w h
if {![string is integer -strict $w] || ![string is integer -strict $h] || $w < 1 || $h < 1} {
error "punk::sixel::cell_size error - expected format WxH where W and H are positive integers - got '$newsize'"
}
set cell_size ${w}x${h}
}
#todo - determine cursor on/off state before the call to restore properly. #todo - determine cursor on/off state before the call to restore properly.
proc get_size {{inoutchannels {stdin stdout}}} { proc get_size {{inoutchannels {stdin stdout}}} {
lassign $inoutchannels in out lassign $inoutchannels in out
@ -1202,13 +1316,19 @@ namespace eval punk::console {
lassign [split $payload {;}] rows cols lassign [split $payload {;}] rows cols
return [list columns $cols rows $rows] return [list columns $cols rows $rows]
} }
proc get_xterm_pixels {{inoutchannels {stdin stdout}}} {
set capturingregex {(.*)(\x1b\[4;([0-9]+;[0-9]+)t)$} ;#must capture prefix,entire-response,response-payload
set request "\x1b\[14t"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
lassign [split $payload {;}] height width
return [list width $width height $height]
}
proc get_mode_line_wrap {{inoutchannels {stdin stdout}}} { proc get_mode_line_wrap {{inoutchannels {stdin stdout}}} {
set capturingregex {(.*)(\x1b\[\?7;([0-9]+)\$y)$} ;#must capture prefix,entire-response,response-payload set capturingregex {(.*)(\x1b\[\?7;([0-9]+)\$y)$} ;#must capture prefix,entire-response,response-payload
set request "\x1b\[?7\$p" set request "\x1b\[?7\$p"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels] set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload return $payload
} }
#Terminals generally default to LNM being reset (off) ie enter key sends a lone <cr> #Terminals generally default to LNM being reset (off) ie enter key sends a lone <cr>
#Terminals tested on windows either don't respond to this query, or respond with 0 (meaning mode not understood) #Terminals tested on windows either don't respond to this query, or respond with 0 (meaning mode not understood)
#I presume from this that almost nobody is using LNM 1 (which sends both <cr> and <lf>) #I presume from this that almost nobody is using LNM 1 (which sends both <cr> and <lf>)
@ -1218,11 +1338,59 @@ namespace eval punk::console {
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels] set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload return $payload
} }
#DECRPM responses e.g:
# \x1b\[?7\;1\$y
# \x1b\[?7\;2\$y
#where 1 = set, 2 = unset. (0 = mode not recognised, 3 = permanently set, 4 = permanently unset)
proc get_mode {num_or_name {inoutchannels {stdin stdout}}} {
if {[string is integer -strict $num_or_name]} {
set m $num_or_name
} else {
upvar ::punk::ansi::decmode_names decmode_names
if {[dict exists $decmode_names $num_or_name]} {
set m [dict get $decmode_names $num_or_name]
} else {
error "punk::console::get_mode unrecognised mode '$num_or_name'. Known mode names: [dict keys $decmode_names]"
}
}
set capturingregex [string map [list %MODE% $m] {(.*)(\x1b\[\?%MODE%;([0-9]+)\$y)$}] ;#must capture prefix,entire-response,response-payload
set request "\x1b\[?$m\$p"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload
}
proc set_mode {num_or_name {inoutchannels {stdin stdout}}} {
if {[string is integer -strict $num_or_name]} {
set m $num_or_name
} else {
upvar ::punk::ansi::decmode_names decmode_names
if {[dict exists $decmode_names $num_or_name]} {
set m [dict get $decmode_names $num_or_name]
} else {
error "punk::console::set_mode unrecognised mode '$num_or_name'. Known mode names: [dict keys $decmode_names]"
}
}
return "\x1b\[?${m}h"
}
proc unset_mode {num_or_name {inoutchannels {stdin stdout}}} {
if {[string is integer -strict $num_or_name]} {
set m $num_or_name
} else {
upvar ::punk::ansi::decmode_names decmode_names
if {[dict exists $decmode_names $num_or_name]} {
set m [dict get $decmode_names $num_or_name]
} else {
error "punk::console::unset_mode unrecognised mode '$num_or_name'. Known mode names: [dict keys $decmode_names]"
}
}
return "\x1b\[?${m}l"
}
#terminals lie. This should be a reasonable (albeit relatively slow) test of actual width - but some terminals seem to miscalculate. #terminals lie. This should be a reasonable (albeit relatively slow) test of actual width - but some terminals seem to miscalculate.
#todo - a visual interactive test/questionnaire to ask user if things are lining up or if the terminal is telling fibs about cursor position. #todo - a visual interactive test/questionnaire to ask user if things are lining up or if the terminal is telling fibs about cursor position.
#todo - determine if these anomalies are independent of font #todo - determine if these anomalies are independent of font
#punk::ansi should be able to glean widths from unicode data files - but this may be incomplete - todo - compare with what terminal actually does. #punk::ansi should be able to glean widths from unicode data files - but this may be incomplete - todo - compare with what terminal actually does.
#review - vertical movements (e.g /n /v will cause emit 0 to be ineffective - todo - disallow?)
proc test_char_width {char_or_string {emit 0}} { proc test_char_width {char_or_string {emit 0}} {
#return 1 #return 1
#JMN #JMN
@ -1266,6 +1434,57 @@ namespace eval punk::console {
return [expr {$col2 - $col1}] return [expr {$col2 - $col1}]
} }
#get reported cursor position after emitting teststring.
#The row is more likely to be a lie than the column
#With wrapping on we should be able to test if the terminal has an inconsistency between reported width and when it actually wraps.
#(but as line wrapping generally occurs based on width - we probably won't see this - just 'apparently' early wrapping due to printing mismatch with width)
#unfortunately if terminal reports something like \u200B as width 1, but doesn't print it - we can't tell. (vs reporting 1 wide and printing replacement char/space)
#When cursor is already at bottom of screen, scrolling will occur so rowoffset will be zero
#we either need to move cursor up before test - or use alt screen ( or scroll_up then scroll_down?)
#for now we will use alt screen to reduce scrolling effects - REVIEW
proc test_string_cursor {teststring {emit 0}} {
variable ansi_available
if {!$ansi_available} {
puts stderr "No ansi - cannot test char_width of '$teststring' returning [string length $test_string]"
return [string length $teststring]
}
punk::console::enable_alt_screen
punk::console::move 0 0
if {!$emit} {
puts -nonewline stdout \033\[2K\033\[1G ;#2K erase line, 1G cursor at col1
}
set response ""
if {[catch {
set response [punk::console::get_cursor_pos]
} errM]} {
puts stderr "Cannot test_string_cursor for '[punk::ansi::ansistring VIEW $teststring]' - may be no console? Error message from get_cursor_pos: $errM"
return
}
lassign [split $response ";"] row1 col1
if {![string length $response] || ![string is integer -strict $col1] || ![string is integer -strict $row1]} {
puts stderr "test_string_cursor Could not interpret response from get_cursor_pos for initial cursor pos. Response: '[punk::ansi::ansistring VIEW $response]'"
flush stderr
return
}
puts -nonewline stdout $teststring
flush stdout
set response [punk::console::get_cursor_pos]
lassign [split $response ";"] row2 col2
if {![string is integer -strict $col2] || ![string is integer -strict $row2]} {
puts stderr "test_string_cursor could not interpret response from get_cursor_pos for post-emit cursor pos. Response:'[punk::ansi::ansistring VIEW $response]'"
flush stderr
return
}
if {!$emit} {
puts -nonewline stdout \033\[2K\033\[1G
}
flush stdout;#if we don't flush - a subsequent stderr write could move the cursor to a newline and interfere with our 2K1G erasure and cursor repositioning.
punk::console::disable_alt_screen
return [list rowoffset [expr {$col2 - $col1}] columnoffset [expr {$row2 - $row1}]]
}
#todo! - improve ideally we want to use VT sequences to determine - and make a separate utility for testing via systemcalls/os api #todo! - improve ideally we want to use VT sequences to determine - and make a separate utility for testing via systemcalls/os api
proc test_can_ansi {} { proc test_can_ansi {} {
#don't set ansi_avaliable here - we want to be able to change things, retest etc. #don't set ansi_avaliable here - we want to be able to change things, retest etc.
@ -1306,8 +1525,59 @@ namespace eval punk::console {
if {!$ansi_available} { if {!$ansi_available} {
return 0 return 0
} }
set ansi_available [test_can_ansi] #ansi_available defaults to -1 (unknown)
return [expr {$ansi_available}] if {$ansi_available == -1} {
set ansi_available [test_can_ansi]
return $ansi_available
}
return 1
}
variable grapheme_cluster_support [dict create] ;#default empty dict for unknown/untested
#todo - flag to retest? (for consoles where grapheme cluster support can be disabled e.g via decmode 2027)
proc grapheme_cluster_support {} {
variable grapheme_cluster_support
if {[dict size $grapheme_cluster_support]} {
return $grapheme_cluster_support
}
if {[info exists ::env(TERM_PROGRAM)]} {
#terminals known to support grapheme clusters, but unable to respond to decmode request 2027
#wezterm (on windows as at 2024-12 decmode 2027 doesn't work)
#REVIEW - what if terminal is remote wezterm? can/will this env variable
# iterm and apple terminal also set TERM_PROGRAM
if {[string tolower $::env(TERM_PROGRAM)] in [list wezterm]} {
set is_available 1
return [dict create available 1 mode set]
}
}
#where 1 = set, 2 = unset. (0 = mode not recognised, 3 = permanently set, 4 = permanently unset)
set state [get_mode grapheme_clusters] ;#decmode 2027 extension
set is_available 0
switch -- $state {
0 {
set m unsupported ;# the dec query is unsupported - but it's possible the terminal still has grapheme support
}
1 {
set m set
set is_available 1
}
2 {
set m unset
}
3 {
set m permanently_set
set is_available 1
}
4 {
set m permanently_unset
}
default {
set m "BAD_RESPONSE"
}
}
return [dict create available $is_available mode $m]
} }
namespace eval ansi { namespace eval ansi {
@ -1432,7 +1702,7 @@ namespace eval punk::console {
puts -nonewline stdout [punk::ansi::move_column $col] puts -nonewline stdout [punk::ansi::move_column $col]
} }
proc move_row {row} { proc move_row {row} {
puts -nonewline stdout [punk::ansi::move_row $col] puts -nonewline stdout [punk::ansi::move_row $row]
} }
proc move_emit {row col data args} { proc move_emit {row col data args} {
puts -nonewline stdout [punk::ansi::move_emit $row $col $data {*}$args] puts -nonewline stdout [punk::ansi::move_emit $row $col $data {*}$args]
@ -1912,8 +2182,52 @@ namespace eval punk::console {
#[list_end] [comment {--- end definitions namespace punk::console ---}] #[list_end] [comment {--- end definitions namespace punk::console ---}]
} }
namespace eval punk::console::check {
variable has_bug_legacysymbolwidth -1 ;#undetermined
proc has_bug_legacysymbolwidth {} {
#some terminals (on windows as at 2024) miscount width of these single-width blocks internally
#resulting in extra spacing that is sometimes well removed from the character itself (e.g at next ansi reset)
#This was fixed in windows-terminal based systems (2021) but persists in others.
#https://github.com/microsoft/terminal/issues/11694
variable has_bug_legacysymbolwidth
if {!$has_bug_legacysymbolwidth} {
return 0
}
if {$has_bug_legacysymbolwidth == -1} {
#run the test using ansi movement
#we only test a specific character from the known problematic set
set w [punk::console::test_char_width \U1fb7d]
if {$w == 1} {
set has_bug_legacysymbolwidth 0
} else {
#can return 2 on legacy window consoles for example
set has_bug_legacysymbolwidth 1
}
return $has_bug_legacysymbolwidth
}
return 1
}
variable has_bug_zwsp -1 ;#undetermined
proc has_bug_zwsp {} {
#Note that some terminals behave differently regarding a leading zwsp vs one that is inline between other chars.
#we are only testing the inline behaviour here.
variable has_bug_zwsp
if {!$has_bug_zwsp} {
return 0
}
if {$has_bug_zwsp == -1} {
set w [punk::console::test_char_width X\u200bY]
}
if {$w == 2} {
return 0
} else {
#may return 3 - but this gives no indication of whether terminal hides it or not.
return 1
}
return 1
}
}

41
src/bootsupport/modules/punk/lib-0.1.1.tm

@ -63,38 +63,6 @@ package require Tcl 8.6-
#*** !doctools #*** !doctools
#[section API] #[section API]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# oo::class namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#tcl::namespace::eval punk::lib::class {
# #*** !doctools
# #[subsection {Namespace punk::lib::class}]
# #[para] class definitions
# if {[info commands [tcl::namespace::current]::interface_sample1] eq ""} {
# #*** !doctools
# #[list_begin enumerated]
#
# # oo::class create interface_sample1 {
# # #*** !doctools
# # #[enum] CLASS [class interface_sample1]
# # #[list_begin definitions]
#
# # method test {arg1} {
# # #*** !doctools
# # #[call class::interface_sample1 [method test] [arg arg1]]
# # #[para] test method
# # puts "test: $arg1"
# # }
#
# # #*** !doctools
# # #[list_end] [comment {-- end definitions interface_sample1}]
# # }
#
# #*** !doctools
# #[list_end] [comment {--- end class enumeration ---}]
# }
#}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::lib::ensemble { tcl::namespace::eval punk::lib::ensemble {
#wiki.tcl-lang.org/page/ensemble+extend #wiki.tcl-lang.org/page/ensemble+extend
@ -172,7 +140,10 @@ tcl::namespace::eval punk::lib::check {
proc has_tclbug_lsearch_strideallinline {} { proc has_tclbug_lsearch_strideallinline {} {
#bug only occurs with single -index value combined with -stride -all -inline -subindices #bug only occurs with single -index value combined with -stride -all -inline -subindices
#https://core.tcl-lang.org/tcl/tktview/5a1aaa201d #https://core.tcl-lang.org/tcl/tktview/5a1aaa201d
set result [lsearch -stride 3 -all -inline -index 1 -subindices {a1 a2 a3} *] if {[catch {[lsearch -stride 3 -all -inline -index 1 -subindices {a1 a2 a3} *]} result]} {
#we aren't looking for an error result - error most likely indicates tcl too old to support -stride
return 0
}
return [expr {$result ne "a2"}] return [expr {$result ne "a2"}]
} }
@ -2575,12 +2546,12 @@ namespace eval punk::lib {
while {$j <= $max} { while {$j <= $max} {
if {$x % $j == 0} { if {$x % $j == 0} {
set other [expr {$x / $j}] set other [expr {$x / $j}]
if {$other % 2 != 0} { if {$other % 2} {
if {$other ni $factors} { if {$other ni $factors} {
lappend factors $other lappend factors $other
} }
} }
if {$j % 2 != 0} { if {$j % 2} {
if {$j ni $factors} { if {$j ni $factors} {
lappend factors $j lappend factors $j
} }

2
src/bootsupport/modules/punk/mix/base-0.1.tm

@ -869,7 +869,7 @@ namespace eval punk::mix::base {
#todo - write tests #todo - write tests
if {([llength $args] % 2) != 0} { if {[llength $args] % 2} {
error "get_relativecksum_from_base error. args supplied must be in the form of key-value pairs. received '$args' " error "get_relativecksum_from_base error. args supplied must be in the form of key-value pairs. received '$args' "
} }
if {[dict exists $args cksum]} { if {[dict exists $args cksum]} {

2
src/bootsupport/modules/punk/mix/util-0.1.0.tm

@ -51,7 +51,7 @@ namespace eval punk::mix::util {
} else { } else {
if {$ival in $knownopts} { if {$ival in $knownopts} {
#puts ">known at $i : [lindex $args $i]" #puts ">known at $i : [lindex $args $i]"
if {($i % 2) != 0} { if {$i % 2} {
error "unexpected option at index $i. known options: $knownopts must come in -opt val pairs." error "unexpected option at index $i. known options: $knownopts must come in -opt val pairs."
} }
incr i incr i

6
src/bootsupport/modules/punk/nav/fs-0.1.0.tm

@ -1,6 +1,6 @@
# -*- tcl -*- # -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'pmix make' or bin/punkmake to update from <pkg>-buildversion.txt # Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm # module template: punkshell/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm
# #
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. # Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. # Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
@ -18,7 +18,7 @@
# doctools header # doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools #*** !doctools
#[manpage_begin shellspy_module_punk::nav::fs 0 0.1.0] #[manpage_begin punkshell_module_punk::nav::fs 0 0.1.0]
#[copyright "2024"] #[copyright "2024"]
#[titledesc {punk::nav::fs console filesystem navigation}] [comment {-- Name section and table of contents description --}] #[titledesc {punk::nav::fs console filesystem navigation}] [comment {-- Name section and table of contents description --}]
#[moddesc {fs nav}] [comment {-- Description at end of page heading --}] #[moddesc {fs nav}] [comment {-- Description at end of page heading --}]

2
src/bootsupport/modules/punk/packagepreference-0.1.0.tm

@ -1,6 +1,6 @@
# -*- tcl -*- # -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt # Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm # module template: punkshell/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm
# #
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. # Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. # Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.

9
src/bootsupport/modules/punk/repl/codethread-0.1.0.tm

@ -141,8 +141,10 @@ tcl::namespace::eval punk::repl::codethread {
#puts stderr "->runscript" #puts stderr "->runscript"
variable replthread_cond variable replthread_cond
variable output_stdout "" #variable output_stdout
variable output_stderr "" #set output_stdout ""
#variable output_stderr
#set output_stderr ""
#expecting to be called from a thread::send in parent repl - ie in the toplevel interp so that the sub-interp "code" is available #expecting to be called from a thread::send in parent repl - ie in the toplevel interp so that the sub-interp "code" is available
#if a thread::send is done from the commandline in a codethread - Tcl will #if a thread::send is done from the commandline in a codethread - Tcl will
if {"code" ni [interp children] || ![info exists replthread_cond]} { if {"code" ni [interp children] || ![info exists replthread_cond]} {
@ -154,6 +156,9 @@ tcl::namespace::eval punk::repl::codethread {
puts stderr "runscript is meant to be called from the parent repl thread via a thread::send to the codethread" puts stderr "runscript is meant to be called from the parent repl thread via a thread::send to the codethread"
return return
} }
interp eval code [list set ::punk::repl::codethread::output_stdout ""]
interp eval code [list set ::punk::repl::codethread::output_stderr ""]
set outstack [list] set outstack [list]
set errstack [list] set errstack [list]
upvar ::punk::config::running running_config upvar ::punk::config::running running_config

287
src/bootsupport/modules/punk/repl/codethread-0.1.1.tm

@ -0,0 +1,287 @@
# -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: punkshell/src/decktemplates/vendor/punk/modules/template_module-0.0.2.tm
#
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# (C) 2024
#
# @@ Meta Begin
# Application punk::repl::codethread 0.1.1
# Meta platform tcl
# Meta license <unspecified>
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[manpage_begin punkshell_module_punk::repl::codethread 0 0.1.1]
#[copyright "2024"]
#[titledesc {Module repl codethread}] [comment {-- Name section and table of contents description --}]
#[moddesc {codethread for repl - root interpreter}] [comment {-- Description at end of page heading --}]
#[require punk::repl::codethread]
#[keywords module repl]
#[description]
#[para] This is part of the infrastructure required for the punk::repl to operate
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Overview]
#[para] overview of punk::repl::codethread
#[subsection Concepts]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection dependencies]
#[para] packages used by punk::repl::codethread
#[list_begin itemized]
package require Tcl 8.6-
package require punk::config
#*** !doctools
#[item] [package {Tcl 8.6}]
# #package require frobz
# #*** !doctools
# #[item] [package {frobz}]
#*** !doctools
#[list_end]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section API]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# oo::class namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#tcl::namespace::eval punk::repl::codethread::class {
#*** !doctools
#[subsection {Namespace punk::repl::codethread::class}]
#[para] class definitions
#if {[info commands [tcl::namespace::current]::interface_sample1] eq ""} {
#*** !doctools
#[list_begin enumerated]
# oo::class create interface_sample1 {
# #*** !doctools
# #[enum] CLASS [class interface_sample1]
# #[list_begin definitions]
# method test {arg1} {
# #*** !doctools
# #[call class::interface_sample1 [method test] [arg arg1]]
# #[para] test method
# puts "test: $arg1"
# }
# #*** !doctools
# #[list_end] [comment {-- end definitions interface_sample1}]
# }
#*** !doctools
#[list_end] [comment {--- end class enumeration ---}]
#}
#}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Base namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::repl::codethread {
tcl::namespace::export *
variable replthread
variable replthread_cond
variable running 0
variable output_stdout ""
variable output_stderr ""
#variable xyz
#*** !doctools
#[subsection {Namespace punk::repl::codethread}]
#[para] Core API functions for punk::repl::codethread
#[list_begin definitions]
#proc sample1 {p1 n args} {
# #*** !doctools
# #[call [fun sample1] [arg p1] [arg n] [opt {option value...}]]
# #[para]Description of sample1
# #[para] Arguments:
# # [list_begin arguments]
# # [arg_def tring p1] A description of string argument p1.
# # [arg_def integer n] A description of integer argument n.
# # [list_end]
# return "ok"
#}
variable run_command_cache
proc is_running {} {
variable running
return $running
}
proc runscript {script} {
#puts stderr "->runscript"
variable replthread_cond
#variable output_stdout ""
#variable output_stderr ""
#expecting to be called from a thread::send in parent repl - ie in the toplevel interp so that the sub-interp "code" is available
#if a thread::send is done from the commandline in a codethread - Tcl will
if {"code" ni [interp children] || ![info exists replthread_cond]} {
#in case someone tries calling from codethread directly - don't do anything or change any state
#(direct caller could create an interp named code at the level "" -> "code" -"code" and add a replthread_cond value to avoid this check - but it probably won't do anything useful)
#if called directly - the context will be within the first 'code' interp.
#inappropriate caller could add superfluous entries to shellfilter stack if function errors out
#inappropriate caller could affect tsv vars (if their interp allows that anyway)
puts stderr "runscript is meant to be called from the parent repl thread via a thread::send to the codethread"
return
}
interp eval code [list set ::punk::repl::codethread::output_stdout ""]
interp eval code [list set ::punk::repl::codethread::output_stderr ""]
set outstack [list]
set errstack [list]
upvar ::punk::config::running running_config
if {[string length [dict get $running_config color_stdout_repl]] && [interp eval code punk::console::colour]} {
lappend outstack [interp eval code [list shellfilter::stack::add stdout ansiwrap -settings [list -colour [dict get $running_config color_stdout_repl]]]]
}
lappend outstack [interp eval code [list shellfilter::stack::add stdout tee_to_var -settings {-varname ::punk::repl::codethread::output_stdout}]]
if {[string length [dict get $running_config color_stderr_repl]] && [interp eval code punk::console::colour]} {
lappend errstack [interp eval code [list shellfilter::stack::add stderr ansiwrap -settings [list -colour [dict get $running_config color_stderr_repl]]]]
# #lappend errstack [shellfilter::stack::add stderr ansiwrap -settings [list -colour cyan]]
}
lappend errstack [interp eval code [list shellfilter::stack::add stderr tee_to_var -settings {-varname ::punk::repl::codethread::output_stderr}]]
#an experiment
#set errhandle [shellfilter::stack::item_tophandle stderr]
#interp transfer "" $errhandle code
set status [catch {
#shennanigans to keep compiled script around after call.
#otherwise when $script goes out of scope - internal rep of vars set in script changes.
#The shimmering may be no big deal(?) - but debug/analysis using tcl::unsupported::representation becomes impossible.
interp eval code [list ::punk::lib::set_clone ::codeinterp::clonescript $script] ;#like objclone
interp eval code {
lappend ::codeinterp::run_command_cache $::codeinterp::clonescript
if {[llength $::codeinterp::run_command_cache] > 2000} {
set ::codeinterp::run_command_cache [lrange $::codeinterp::run_command_cache 1750 end][unset ::codeinterp::run_command_cache]
}
tcl::namespace::inscope $::punk::ns::ns_current $::codeinterp::clonescript
}
} result]
flush stdout
flush stderr
#interp transfer code $errhandle ""
#flush $errhandle
#set lastoutchar [string index [punk::ansi::ansistrip [interp eval code set ::punk::repl::codethread::output_stdout]] end]
#set lastoutchar [string index [punk::ansi::ansistrip [interp eval code [list set ::punk::repl::codethread::output_stdout]]] end]
set lastoutpart [interp eval code {string range $::punk::repl::codethread::output_stdout end-100 end}]
#note we could be in a *large* ansi segment such as sixel data
#review - why do we need to ansistrip?
set lastoutchar [string index [punk::ansi::ansistrip $lastoutpart] end]
#set lasterrchar [string index [punk::ansi::ansistrip [interp eval code set ::punk::repl::codethread::output_stderr]] end]
set lasterrpart [interp eval code {string range $::punk::repl::codethread::output_stderr end-100 end}]
set lasterrchar [string index [punk::ansi::ansistrip $lasterrpart] end]
#puts stderr "-->[ansistring VIEW -lf 1 $lastoutchar$lasterrchar]"
set tid [thread::id]
tsv::set codethread_$tid info [list lastoutchar $lastoutchar lasterrchar $lasterrchar]
tsv::set codethread_$tid status $status
tsv::set codethread_$tid result $result
tsv::set codethread_$tid errorcode $::errorCode
#only remove from shellfilter::stack the items we added to stack in this function
foreach s [lreverse $outstack] {
interp eval code [list shellfilter::stack::remove stdout $s]
}
foreach s [lreverse $errstack] {
interp eval code [list shellfilter::stack::remove stderr $s]
}
thread::cond notify $replthread_cond
}
#*** !doctools
#[list_end] [comment {--- end definitions namespace punk::repl::codethread ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Secondary API namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::repl::codethread::lib {
tcl::namespace::export *
tcl::namespace::path [tcl::namespace::parent]
#*** !doctools
#[subsection {Namespace punk::repl::codethread::lib}]
#[para] Secondary functions that are part of the API
#[list_begin definitions]
#proc utility1 {p1 args} {
# #*** !doctools
# #[call lib::[fun utility1] [arg p1] [opt {?option value...?}]]
# #[para]Description of utility1
# return 1
#}
#*** !doctools
#[list_end] [comment {--- end definitions namespace punk::repl::codethread::lib ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Internal]
tcl::namespace::eval punk::repl::codethread::system {
#*** !doctools
#[subsection {Namespace punk::repl::codethread::system}]
#[para] Internal functions that are not part of the API
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide punk::repl::codethread [tcl::namespace::eval punk::repl::codethread {
variable pkg punk::repl::codethread
variable version
set version 0.1.1
}]
return
#*** !doctools
#[manpage_end]

2
src/bootsupport/modules/punk/repo-0.1.1.tm

@ -370,7 +370,7 @@ namespace eval punk::repo {
set opt_repotypes [dict get $opts -repotypes] set opt_repotypes [dict get $opts -repotypes]
set opt_repopaths [dict get $opts -repopaths] set opt_repopaths [dict get $opts -repopaths]
if {"$opt_repopaths" ne ""} { if {"$opt_repopaths" ne ""} {
if {([llength $opt_repopaths] % 2 != 0) || ![dict exists $opt_repopaths closest]} { if {([llength $opt_repopaths] % 2) || ![dict exists $opt_repopaths closest]} {
error "workingdir_state error: -repopaths argument invalid. Expected a dict as retrieved using punk::repo::find_repos" error "workingdir_state error: -repopaths argument invalid. Expected a dict as retrieved using punk::repo::find_repos"
} }
set repopaths $opt_repopaths set repopaths $opt_repopaths

11
src/bootsupport/modules/punk/tdl-0.1.0.tm

@ -31,15 +31,19 @@ namespace eval punk::tdl {
server -name trillion -os windows server -name trillion -os windows
server -name vmhost1 -os FreeBSD { server -name vmhost1 -os FreeBSD {
guest -name bsd1 -vmmanager iocage guest -name bsd1 -vmmanager bastille
guest -name p1 -vmmanager bhyve guest -name p1 -vmmanager bhyve
} }
} }
proc prettyparse {script} { proc prettyparse {script {safe 1}} {
set i [interp create -safe] if {$safe} {
set i [interp create -safe]
} else {
set i [interp create]
}
try { try {
# $i eval {unset {*}[info vars]} # $i eval {unset {*}[info vars]}
# foreach command [$i eval {info commands}] {$i hide $command} # foreach command [$i eval {info commands}] {$i hide $command}
@ -65,6 +69,7 @@ namespace eval punk::tdl {
interp delete $i interp delete $i
} }
} }
proc prettyprint {data {level 0}} { proc prettyprint {data {level 0}} {
set ind [string repeat " " $level] set ind [string repeat " " $level]
incr level incr level

4
src/bootsupport/modules/punkcheck-0.1.0.tm

@ -359,7 +359,7 @@ namespace eval punkcheck {
-note \uFFFF\ -note \uFFFF\
] ]
set known_opts [dict keys $defaults] set known_opts [dict keys $defaults]
if {[llength $args] % 2 != 0} { if {[llength $args] % 2} {
error "targetset_end arguments after status must be in the form of -flag value pairs. known flags: $known_opts" error "targetset_end arguments after status must be in the form of -flag value pairs. known flags: $known_opts"
} }
set opts [dict merge $defaults $args] set opts [dict merge $defaults $args]
@ -914,7 +914,7 @@ namespace eval punkcheck {
set changed 0 set changed 0
} }
set installing_record_sources [dict_getwithdefault $installing_record body [list]] set installing_record_sources [dict_getwithdefault $installing_record body [list]]
set ts_now [clock microseconds] ;#gathering metadata - especially checsums on folder can take some time - calc and store elapsed us for time taken to gather metadata set ts_now [clock microseconds] ;#gathering metadata - especially checksums on folder can take some time - calc and store elapsed us for time taken to gather metadata
set metadata_us [expr {$ts_now - $ts_start}] set metadata_us [expr {$ts_now - $ts_start}]
set this_source_record [dict create tag SOURCE -type $ftype -size $fsize -path $source_relpath -cksum $cksum -cksum_all_opts $cksum_all_opts -changed $changed -metadata_us $metadata_us] set this_source_record [dict create tag SOURCE -type $ftype -size $fsize -path $source_relpath -cksum $cksum -cksum_all_opts $cksum_all_opts -changed $changed -metadata_us $metadata_us]
lappend installing_record_sources $this_source_record lappend installing_record_sources $this_source_record

9
src/bootsupport/modules/shellfilter-0.1.9.tm

@ -613,6 +613,10 @@ namespace eval shellfilter::chan {
#It can be useful for test/debugging #It can be useful for test/debugging
#Due to chunking at random breaks - we have to check if an ansi code in the underlying stream has been split - otherwise our wrapping will break the existing ansi #Due to chunking at random breaks - we have to check if an ansi code in the underlying stream has been split - otherwise our wrapping will break the existing ansi
# #
set sixelstart_re {\x1bP([;0-9]*)q} ;#7-bit - todo 8bit
#todo kitty graphics \x1b_G...
#todo iterm graphics
oo::class create ansiwrap { oo::class create ansiwrap {
variable o_trecord variable o_trecord
variable o_enc variable o_enc
@ -646,6 +650,9 @@ namespace eval shellfilter::chan {
set o_is_junction 0 set o_is_junction 0
} }
} }
#todo - track when in sixel,iterm,kitty graphics data - can be very large
method Trackcodes {chunk} { method Trackcodes {chunk} {
#puts stdout "===[ansistring VIEW -lf 1 $o_buffered]" #puts stdout "===[ansistring VIEW -lf 1 $o_buffered]"
set buf $o_buffered$chunk set buf $o_buffered$chunk
@ -2334,7 +2341,7 @@ namespace eval shellfilter {
#set tid [::shellfilter::log::open $runtag [list -syslog "127.0.0.1:514"]] #set tid [::shellfilter::log::open $runtag [list -syslog "127.0.0.1:514"]]
set tid [::shellfilter::log::open $runtag [list -syslog ""]] set tid [::shellfilter::log::open $runtag [list -syslog ""]]
if {([llength $args] % 2) != 0} { if {[llength $args] % 2} {
error "Trailing arguments after any positional arguments must be in pairs of the form -argname argvalue. Valid flags are:'$valid_flags'" error "Trailing arguments after any positional arguments must be in pairs of the form -argname argvalue. Valid flags are:'$valid_flags'"
} }
set invalid_flags [list] set invalid_flags [list]

120
src/bootsupport/modules/textblock-0.1.2.tm

@ -82,11 +82,17 @@ tcl::namespace::eval textblock {
#review - what about ansi off in punk::console? #review - what about ansi off in punk::console?
tcl::namespace::import ::punk::ansi::a ::punk::ansi::a+ tcl::namespace::import ::punk::ansi::a ::punk::ansi::a+
tcl::namespace::export block frame frame_cache framedef frametypes gcross height width widthtopline join join_basic list_as_table pad testblock tcl::namespace::export block frame frame_cache framedef frametypes gcross height width widthtopline join join_basic list_as_table pad testblock
#NOTE sha1, although computationally more intensive, tends to be faster than md5 on modern cpus
#(more likely to be optimised for modern cpu features?)
variable use_md5 ;#framecache variable use_md5 ;#framecache
set use_md5 1 set use_md5 1
if {[catch {package require md5}]} { if {[catch {package require md5}]} {
set use_md5 0 set use_md5 0
} }
#todo - change use_md5 to more generic use_checksum_algorithm function.
# e.g allow md5, sha1, none, etc.
# - perhaps autodetect 32bit vs 64bit (or processortype?) to select a default (also depending on packages available and accelerator presence)
proc use_md5 {{yes_no ""}} { proc use_md5 {{yes_no ""}} {
variable use_md5 variable use_md5
if {$yes_no eq ""} { if {$yes_no eq ""} {
@ -4170,7 +4176,7 @@ tcl::namespace::eval textblock {
} }
set FRAMETYPES [textblock::frametypes] set FRAMETYPES [textblock::frametypes]
punk::args::Get_argspecs [punk::lib::tstr -return string { punk::args::definition [punk::lib::tstr -return string {
*id textblock::list_as_table *id textblock::list_as_table
-return -default table -choices {table tableobject} -return -default table -choices {table tableobject}
@ -4208,7 +4214,7 @@ tcl::namespace::eval textblock {
proc list_as_table {args} { proc list_as_table {args} {
set FRAMETYPES [textblock::frametypes] set FRAMETYPES [textblock::frametypes]
set argd [punk::args::get_dict_by_id textblock::list_as_table $args] set argd [punk::args::get_by_id textblock::list_as_table $args]
set opts [dict get $argd opts] set opts [dict get $argd opts]
set datalist [dict get $argd values datalist] set datalist [dict get $argd values datalist]
@ -5699,7 +5705,7 @@ tcl::namespace::eval textblock {
#custom dict may leave out keys - but cannot have unknown keys #custom dict may leave out keys - but cannot have unknown keys
foreach {k v} $f { foreach {k v} $f {
switch -- $k { switch -- $k {
hl - hlt - hlb - vl - vll - vlr - tlc - trc - blc - brc {} all - hl - hlt - hlb - vl - vll - vlr - tlc - trc - blc - brc {}
hltj - hlbj - vllj - vlrj { hltj - hlbj - vllj - vlrj {
#also allow extra join arguments #also allow extra join arguments
} }
@ -5714,11 +5720,15 @@ tcl::namespace::eval textblock {
set is_custom_dict_ok 0 set is_custom_dict_ok 0
} }
if {!$is_custom_dict_ok} { if {!$is_custom_dict_ok} {
error "frame option -type must be one of known types: $::textblock::frametypes or a dictionary with any of keys hl,hlt,hlb,vl,vll,vlr,tlc,trc,blc,brc" error "frame option -type must be one of known types: $::textblock::frametypes or a dictionary with any of keys all,hl,hlt,hlb,vl,vll,vlr,tlc,trc,blc,brc"
}
if {[dict exists $f all]} {
return [tcl::dict::create category custom type $f]
} else {
set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "]
set custom_frame [tcl::dict::merge $default_custom $f]
return [tcl::dict::create category custom type $custom_frame]
} }
set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "]
set custom_frame [tcl::dict::merge $default_custom $f]
return [tcl::dict::create category custom type $custom_frame]
} }
} }
} }
@ -5769,7 +5779,7 @@ tcl::namespace::eval textblock {
} }
set f [lindex $values 0] set f [lindex $values 0]
set rawglobs [lrange $values 1 end] set rawglobs [lrange $values 1 end]
if {![llength $rawglobs]} { if {![llength $rawglobs] || "all" in $rawglobs || "*" in $rawglobs} {
set globs * set globs *
} else { } else {
set globs [list] set globs [list]
@ -6236,6 +6246,46 @@ tcl::namespace::eval textblock {
#from3 #from3
set trc [punk::char::charshort boxd_ldhz] ;#T shape (ttj) set trc [punk::char::charshort boxd_ldhz] ;#T shape (ttj)
set brc [punk::char::charshort boxd_luhz] ;#upside down T shape (btj) set brc [punk::char::charshort boxd_luhz] ;#upside down T shape (btj)
switch -- $targetleft-$targetright {
heavy-light {
set tlc \u252d ;# Left Heavy and Right Down Light (ttj)
set blc \u2535 ;# Left Heavy and Right Up Light (btj)
set vllj \u2525 ;# left heavy (rtj)
set vlrj \u251c;#right light (ltj)
}
heavy-other {
set tlc \u252d ;# Left Heavy and Right Down Light (ttj)
set blc \u2535 ;# Left Heavy and Right Up Light (btj)
set vllj \u2525 ;# left heavy (rtj)
}
heavy-heavy {
set vllj \u2525 ;# left heavy (rtj)
set vlrj \u251d;#right heavy (ltj)
set tlc \u252d ;# Left Heavy and Right Down Light (ttj)
set blc \u2535 ;# Left Heavy and Right Up Light (btj)
set trc \u252e ;#Right Heavy and Left Down Light (ttj)
set brc \u2536 ;#Right Heavy and Left up Light (btj)
}
light-heavy {
set trc \u252e ;#Right Heavy and Left Down Light (ttj)
set brc \u2536 ;#Right Heavy and Left up Light (btj)
set vlrj \u251d;#right heavy (ltj)
set vllj \u2524 ;# left light (rtj)
}
light-other {
set vllj \u2524 ;# left light (rtj)
}
light-light {
set vllj \u2524 ;# left light (rtj)
set vlrj \u251c;#right light (ltj)
}
}
#set vllj \u2525 ;# left heavy (rtj)
#set vllj \u2524 ;# left light (rtj)
#set vlrj \u251d;#right heavy (ltj)
#set vlrj \u251c;#right light (ltj)
} }
left_up { left_up {
#9 #9
@ -6935,6 +6985,7 @@ tcl::namespace::eval textblock {
self-self { self-self {
#set blc \u27e1 ;# white concave-sided diamond - positioned too far right #set blc \u27e1 ;# white concave-sided diamond - positioned too far right
#set blc \u27e3 ;# concave sided diamond with rightwards tick - positioned too low, too right - big gaps #set blc \u27e3 ;# concave sided diamond with rightwards tick - positioned too low, too right - big gaps
#set blc \u2bce ;# from "Miscellaneous Symbols and Arrows" - positioned too far right
set brc \u2524 ;# *light (rtj) set brc \u2524 ;# *light (rtj)
set tlc \u252c ;# *light (ttj) set tlc \u252c ;# *light (ttj)
} }
@ -6950,6 +7001,15 @@ tcl::namespace::eval textblock {
} }
} }
} }
down_right {
switch -- $targetdown-$targetright {
self-self {
#set brc \u2bce ;# from "Miscellaneous Symbols and Arrows" - positioned too far right
set trc \u252c ;# (ttj)
set blc \u2524 ;# (rtj)
}
}
}
} }
} }
arc_b { arc_b {
@ -7027,6 +7087,15 @@ tcl::namespace::eval textblock {
set blc \U1fb7c ;#legacy block set blc \U1fb7c ;#legacy block
set brc \U1fb7f ;#legacy block set brc \U1fb7f ;#legacy block
if {[punk::console::check::has_bug_legacysymbolwidth]} {
#rather than totally fail on some mixed layout that happens to use block2 - just degrade it - but prevent alignment problems
set sp \u00a0 ;#non breaking space (plain space may act transparent in some use cases)
set tlc $sp
set trc $sp
set blc $sp
set brc $sp
}
#horizontal and vertical bar joins #horizontal and vertical bar joins
set hltj $hlt set hltj $hlt
set hlbj $hlb set hlbj $hlb
@ -7088,15 +7157,20 @@ tcl::namespace::eval textblock {
set vlrj $vlr set vlrj $vlr
} }
default { default {
set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "] ;#only default the general types - these form defaults for more specific types if they're missing #set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "] ;#only default the general types - these form defaults for more specific types if they're missing
if {[llength $f] % 2 != 0} { set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "]
if {"all" in [dict keys $f]} {
set A [dict get $f all]
set default_custom [tcl::dict::create hl $A vl $A tlc $A trc $A blc $A brc $A]
}
if {[llength $f] % 2} {
#todo - retrieve usage from punk::args #todo - retrieve usage from punk::args
error "textblock::frametype frametype '$f' is not one of the predefined frametypes: $::textblock::frametypes and does not appear to be a dictionary for a custom frametype" error "textblock::frametype frametype '$f' is not one of the predefined frametypes: $::textblock::frametypes and does not appear to be a dictionary for a custom frametype"
} }
#unknown order of keys specified by user - validate before creating vars as we need more general elements to be available as defaults #unknown order of keys specified by user - validate before creating vars as we need more general elements to be available as defaults
dict for {k v} $f { dict for {k v} $f {
switch -- $k { switch -- $k {
hl - vl - tlc - trc - blc - brc - hlt - hlb - vll - vlr - hltj - hlbj - vllj - vlrj {} all - hl - vl - tlc - trc - blc - brc - hlt - hlb - vll - vlr - hltj - hlbj - vllj - vlrj {}
default { default {
error "textblock::frametype '$f' has unknown element '$k'" error "textblock::frametype '$f' has unknown element '$k'"
} }
@ -8028,17 +8102,19 @@ tcl::namespace::eval textblock {
return $fs return $fs
} }
} }
punk::args::definition {
*id textblock::gcross
-max_cross_size -default 0 -type integer -help "Largest size cross to use to make up the block
Only cross sizes that divide the size of the overall block will be used.
e.g if the 'size' chosen is 19 (a prime number) - only 1 or the full size of 19 can be used as the crosses to make up the block.
Whereas for a block size of 24, -max_cross_size of 1,2,3,4,6,8,12 or 24 will work. (all divisors)
If a number chosen for -max_cross_size isn't a divisor, the largest divisor below the chosen value will be used.
"
*values -min 1 -max 1
size -default 1 -type integer
}
proc gcross {args} { proc gcross {args} {
set argd [punk::args::get_dict { set argd [punk::args::get_by_id textblock::gcross $args]
-max_cross_size -default 0 -type integer -help "Largest size cross to use to make up the block
Only cross sizes that divide the size of the overall block will be used.
e.g if the 'size' chosen is 19 (a prime number) - only 1 or the full size of 19 can be used as the crosses to make up the block.
Whereas for a block size of 24, -max_cross_size of 1,2,3,4,6,8,12 or 24 will work. (all divisors)
If a number chosen for -max_cross_size isn't a divisor, the largest divisor below the chosen value will be used.
"
*values -min 1
size -default 1 -type integer
} $args]
set size [dict get $argd values size] set size [dict get $argd values size]
set opts [dict get $argd opts] set opts [dict get $argd opts]
@ -8089,7 +8165,7 @@ tcl::namespace::eval textblock {
lappend crossrows [::join $r ""] lappend crossrows [::join $r ""]
} }
if {$max_cross_size % 2 != 0} { if {$max_cross_size % 2} {
#only put centre cross in for odd sized crosses #only put centre cross in for odd sized crosses
set r $row set r $row
lset r $armsize $x lset r $armsize $x

2
src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm vendored

@ -1,5 +1,5 @@
# -*- tcl -*- # -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'pmix make' or bin/punkmake to update from <pkg>-buildversion.txt # Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: %moduletemplate% # module template: %moduletemplate%
# #
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. # Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.

19
src/modules/patternpunk-1.1.tm

@ -13,6 +13,7 @@
package require pattern package require pattern
package require overtype package require overtype
package require punk::args
package require punk::ansi package require punk::ansi
package require punk::lib package require punk::lib
pattern::init pattern::init
@ -77,7 +78,7 @@ set ::punk::bannerTemplate [string trim {
} else { } else {
lassign $cborder_ctext cborder ctext lassign $cborder_ctext cborder ctext
} }
return [ textblock::frame-type arc -ansiborder [a+ Web-black $cborder] [a+ Web-black $ctext][$this . logo2]] return [ textblock::frame -type arc -ansiborder [a+ Web-black $cborder] [a+ Web-black $ctext][$this . logo2]]
} }
>punk .. Property logotk "\[TCL\\\n TK \]" >punk .. Property logotk "\[TCL\\\n TK \]"
proc TCL {args} { proc TCL {args} {
@ -109,12 +110,15 @@ proc TCL {args} {
} }
return $version return $version
} }
punk::args::definition {
*id ">punk . poses"
*proc -name ">punk . poses" -help "Show or list the poses for the Punk mascot"
-censored -default 1 -type boolean -help "Set true to include mild toilet humour poses"
-return -default table -choices {list table}
}
>punk .. Method poses {args} { >punk .. Method poses {args} {
set argd [punk::args::get_dict { set argd [punk::args::get_by_id ">punk . poses" $args]
*proc -name ">punk . poses" -help "Show or list the poses for the Punk mascot"
-censored -default 1 -type boolean -help "Set true to include mild toilet humour poses"
-return -default table -choices {list table}
} $args]
set censored [dict get $argd opts -censored] set censored [dict get $argd opts -censored]
set return [dict get $argd opts -return] set return [dict get $argd opts -return]
@ -359,8 +363,11 @@ v_ /|\/ /
set subtitle [dict get $argd opts -subtitle] set subtitle [dict get $argd opts -subtitle]
set punkdeck [overtype::right [overtype::left [textblock::frame -ansiborder $border_colour -type $frame_type -boxmap $box_map -boxlimits $box_limits -title $hbar_colour$title$RST -subtitle $hbar_colour$subtitle$RST $punk] "$vbar_colour\n\n\P\nU\nN\nK$RST"] "$vbar_colour\n\nD\nE\nC\nK"] set punkdeck [overtype::right [overtype::left [textblock::frame -ansiborder $border_colour -type $frame_type -boxmap $box_map -boxlimits $box_limits -title $hbar_colour$title$RST -subtitle $hbar_colour$subtitle$RST $punk] "$vbar_colour\n\n\P\nU\nN\nK$RST"] "$vbar_colour\n\nD\nE\nC\nK"]
} }
#TODO - reuse textblock::gcross arguments - but reorder for error display
>punk .. Method gcross {{size 1} args} { >punk .. Method gcross {{size 1} args} {
package require textblock package require textblock
set argd [punk::args::get_by_id textblock::gcross [list {*}$args $size]]
textblock::gcross {*}$args $size textblock::gcross {*}$args $size
} }

46
src/modules/punk-0.1.tm

@ -7428,6 +7428,7 @@ namespace eval punk {
} }
if {$topic in [list console terminal]} { if {$topic in [list console terminal]} {
set indent [string repeat " " [string length "WARNING: "]]
lappend cstring_tests [dict create\ lappend cstring_tests [dict create\
type "PM "\ type "PM "\
msg "PRIVACY MESSAGE"\ msg "PRIVACY MESSAGE"\
@ -7472,6 +7473,51 @@ namespace eval punk {
append warningblock \n "WARNING: terminal doesn't hide all [dict get $test type] control strings: $d $d8" append warningblock \n "WARNING: terminal doesn't hide all [dict get $test type] control strings: $d $d8"
} }
} }
if {![catch {punk::console::check::has_bug_legacysymbolwidth} result]} {
if {$result} {
append warningblock \n "WARNING: terminal has legacysymbolwidth bug - screen position for symbol reports 2 wide but displays 1 wide."
append warningblock \n $indent "Layout using 'legacy symbols for computing' affected."
append warningblock \n $indent "(e.g textblock frametype block2 unsupported)"
append warningblock \n $indent "This can cause extreme layout deformation when ANSI is present"
append warningblock \n $indent "In some cases unwanted spacing effects occur at a distance from the characters causing it"
}
} else {
append warningblock \n "WARNING: terminal unable to check for legacysymbolwidth bug. err:$result"
}
if {![catch {punk::console::check::has_bug_zwsp} result]} {
if {$result} {
append warningblock \n "WARNING: terminal has zero width space (\\u200b) bug - cursor position incremented when it shouldn't be."
append warningblock \n $indent "The zwsp may or may not be displayed. zwsp contributes to line length and wrapping point"
}
} else {
append warningblock \n "WARNING: terminal unable to check for zwsp bug. err:$result"
}
set grapheme_support [punk::console::grapheme_cluster_support]
#mode, 1 = set, 2 = unset. (0 = mode not recognised, 3 = permanently set, 4 = permanently unset)
if {![dict size $grapheme_support] || [dict get $grapheme_support mode] eq "unsupported" } {
append warningblock \n "WARNING: terminal either doesn't support grapheme clusters, or doesn't report so via decmode 2027 query."
if {[dict size $grapheme_support] && [dict get $grapheme_support available]} {
append warningblock \n $indent "(but punk::console::grapheme_cluster_support has determined it is probably available)"
}
} else {
if {![dict get $grapheme_support available]} {
switch -- [dict get $grapheme_support mode] {
"unset" {
append warningblock \n "WARNING: terminal reports via decmode 2027 that grapheme cluster support is off."
}
"permanently_unset" {
append warningblock \n "WARNING: terminal reports via decmode 2027 that grapheme cluster support is permanently off."
}
"BAD_RESPONSE" {
append warningblock \n "WARNING: terminal doesn't seem to recognize decmode 2027 query. No grapheme cluster support."
}
}
}
}
} }
lappend chunks [list stderr $warningblock] lappend chunks [list stderr $warningblock]

562
src/modules/punk/ansi-999999.0a1.0.tm

@ -556,21 +556,21 @@ tcl::namespace::eval punk::ansi {
} }
proc example {args} { proc example {args} {
set base [punk::repo::find_project] set base [punk::repo::find_project]
set default_ansibase [file join $base src/testansi] set default_ansifolder [file join $base src/testansi]
set argd [punk::args::get_dict [tstr -return string { set argd [punk::args::get_dict [tstr -return string {
*proc -name punk::ansi::example -help "Display .ans image files in a grid that will fit in console *proc -name punk::ansi::example -help "Display .ans image files in a grid that will fit in console
" "
-colwidth -default 82 -help "Width of each column - default of 82 will fit a standard 80wide ansi image (when framed) -colwidth -default 82 -help "Width of each column - default of 82 will fit a standard 80wide ansi image (when framed)
You can specify a narrower width to truncate images on the right side" You can specify a narrower width to truncate images on the right side"
-folder -default "${$default_ansibase}" -help "Base folder for files if relative paths are used. -folder -default "${$default_ansifolder}" -help "Base folder for files if relative paths are used.
Defaults to <projectbase>/src/testansi - where projectbase is determined from current directory. Defaults to <projectbase>/src/testansi - where projectbase is determined from current directory.
" "
*values -min 0 -max -1 *values -min 0 -max -1
files -default {belinda.ans bot.ans flower.ans fish.ans} -multiple true -help "List of filenames - leave empty to display 4 defaults" files -default {belinda.ans bot.ans flower.ans fish.ans} -multiple true -help "List of filenames - leave empty to display 4 defaults"
}] $args] }] $args]
set colwidth [dict get $argd opts -colwidth] set colwidth [dict get $argd opts -colwidth]
set ansibase [file normalize [dict get $argd opts -folder]] set ansifolder [file normalize [dict get $argd opts -folder]]
set fnames [dict get $argd values files] set fnames [dict get $argd values files]
#assumes fixed column widths e.g 80col images will fit in 82-width frames (common standard for old ansi art) (of arbitrary height) #assumes fixed column widths e.g 80col images will fit in 82-width frames (common standard for old ansi art) (of arbitrary height)
@ -579,8 +579,8 @@ tcl::namespace::eval punk::ansi {
package require punk::repo package require punk::repo
package require punk::console package require punk::console
if {![file exists $ansibase]} { if {![file exists $ansifolder]} {
puts stderr "Missing folder at $ansibase" puts stderr "Missing folder at $ansifolder"
puts stderr "Ensure ansi test files exist: $fnames" puts stderr "Ensure ansi test files exist: $fnames"
#error "punk::ansi::example Cannot find example files" #error "punk::ansi::example Cannot find example files"
} }
@ -588,7 +588,7 @@ tcl::namespace::eval punk::ansi {
set pics [list] set pics [list]
foreach f $fnames { foreach f $fnames {
if {[file pathtype $f] ne "absolute"} { if {[file pathtype $f] ne "absolute"} {
set filepath [file normalize $ansibase/$f] set filepath [file normalize $ansifolder/$f]
} else { } else {
set filepath [file normalize $f] set filepath [file normalize $f]
} }
@ -621,7 +621,7 @@ tcl::namespace::eval punk::ansi {
set subtitle [tcl::dict::get $picinfo status] set subtitle [tcl::dict::get $picinfo status]
} }
set title [tcl::dict::get $picinfo filename] set title [tcl::dict::get $picinfo filename]
set fr [textblock::frame -checkargs 0 -width $colwidth -subtitle $subtitle -title $title [tcl::dict::get $picinfo pic]] set fr [textblock::frame -width $colwidth -subtitle $subtitle -title $title [tcl::dict::get $picinfo pic]]
# -- --- --- --- # -- --- --- ---
#we need the max height of a row element to use join_basic instead of join below #we need the max height of a row element to use join_basic instead of join below
# -- --- --- --- # -- --- --- ---
@ -2096,7 +2096,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
} }
} }
} }
append out "\nNote: The 256 term colours especially 0-15 may be altered by terminal pallette settings or ansi OSC 4 codes, so specific RGB values are unavailable" append out "\nNote: The 256 term colours especially 0-15 may be altered by terminal palette settings or ansi OSC 4 codes, so specific RGB values are unavailable"
return $out return $out
} }
web { web {
@ -2126,8 +2126,25 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
switch -- $f4 { switch -- $f4 {
web- - Web- - WEB- { web- - Web- - WEB- {
set tail [tcl::string::tolower [tcl::string::trim [tcl::string::range $i 4 end] -]] set tail [tcl::string::tolower [tcl::string::trim [tcl::string::range $i 4 end] -]]
if {[tcl::dict::exists $WEB_colour_map $tail]} { set cont [string range $tail end-11 end]
set dec [tcl::dict::get $WEB_colour_map $tail] switch -- $cont {
-contrasting - -contrastive {
set cname [string range $tail 0 end-12]
}
default {
set cname $tail
}
}
if {[tcl::dict::exists $WEB_colour_map $cname]} {
set dec [tcl::dict::get $WEB_colour_map $cname]
switch -- $cont {
-contrasting {
set dec [join [punk::ansi::colour::contrasting {*}[split $dec -]] -]
}
-contrastive {
set dec [join [lindex [punk::ansi::colour::contrast_pair {*}[split $dec -]] 0] -]
}
}
set hex [colour_dec2hex $dec] set hex [colour_dec2hex $dec]
set descr "$hex $dec" set descr "$hex $dec"
} else { } else {
@ -2170,25 +2187,60 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
RGB0 - RGB1 - RGB2 - RGB3 - RGB4 - RGB5 - RGB6 - RGB7 - RGB8 - RGB9 - RGB0 - RGB1 - RGB2 - RGB3 - RGB4 - RGB5 - RGB6 - RGB7 - RGB8 - RGB9 -
rgb# - Rgb# - RGB# - rgb# - Rgb# - RGB# -
und# - und- { und# - und- {
if {[tcl::string::index $i 3] eq "#"} { set cont [string range $i end-11 end]
set tail [tcl::string::range $i 4 end] switch -- $cont {
-contrasting - -contrastive {
set iplain [string range $i 0 end-12]
}
default {
set iplain $i
}
}
if {[tcl::string::index $iplain 3] eq "#"} {
set tail [tcl::string::range $iplain 4 end]
set hex $tail set hex $tail
set dec [colour_hex2dec $hex] set dec [colour_hex2dec $hex]
set info $dec ;#show opposite type as first line of info col
switch -- $cont {
-contrasting {
set decfinal [join [punk::ansi::colour::contrasting {*}[split $dec -]] -]
set hexfinal [colour_dec2hex $decfinal]
}
-contrastive {
set decfinal [join [lindex [punk::ansi::colour::contrast_pair {*}[split $dec -]] 0] -]
set hexfinal [colour_dec2hex $decfinal]
}
default {
set hexfinal $hex
set decfinal $dec
}
}
set info "$hexfinal $decfinal" ;#show opposite type as first line of info col
} else { } else {
set tail [tcl::string::trim [tcl::string::range $i 3 end] -] set tail [tcl::string::trim [tcl::string::range $iplain 3 end] -]
set dec $tail set dec $tail
set hex [colour_dec2hex $dec] switch -- $cont {
set info $hex -contrasting {
set decfinal [join [punk::ansi::colour::contrasting {*}[split $dec -]] -]
}
-contrastive {
set decfinal [join [lindex [punk::ansi::colour::contrast_pair {*}[split $dec -]] 0] -]
}
default {
set decfinal $dec
}
}
set hexfinal [colour_dec2hex $decfinal]
set info "$hexfinal $decfinal"
} }
set webcolours_i [lsearch -all $WEB_colour_map $dec] set webcolours_i [lsearch -all $WEB_colour_map $decfinal]
set webcolours [list] set webcolours [list]
foreach ci $webcolours_i { foreach ci $webcolours_i {
lappend webcolours [lindex $WEB_colour_map $ci-1] lappend webcolours [lindex $WEB_colour_map $ci-1]
} }
set x11colours [list] set x11colours [list]
set x11colours_i [lsearch -all $X11_colour_map $dec] set x11colours_i [lsearch -all $X11_colour_map $decfinal]
foreach ci $x11colours_i { foreach ci $x11colours_i {
set c [lindex $X11_colour_map $ci-1] set c [lindex $X11_colour_map $ci-1]
if {$c ni $webcolours} { if {$c ni $webcolours} {
@ -2205,12 +2257,15 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
} }
unde { unde {
switch -- $i { switch -- $i {
undercurly - underdotted - underdashed - undersingle - underdouble { undercurly - undercurl - underdotted - underdot - underdashed - underdash - undersingle - underdouble {
$t add_row [list $i extended $s [ansistring VIEW $s]] $t add_row [list $i extended $s [ansistring VIEW $s]]
} }
underline { underline {
$t add_row [list $i "SGR 4" $s [ansistring VIEW $s]] $t add_row [list $i "SGR 4" $s [ansistring VIEW $s]]
} }
underlinedefault {
$t add_row [list $i "SGR 59" $s [ansistring VIEW $s]]
}
default { default {
$t add_row [list $i UNKNOWN $s [ansistring VIEW $s]] $t add_row [list $i UNKNOWN $s [ansistring VIEW $s]]
} }
@ -2362,10 +2417,31 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#variable WEB_colour_map #variable WEB_colour_map
#upvar ::punk::ansi::WEB_colour_map WEB_colour_map #upvar ::punk::ansi::WEB_colour_map WEB_colour_map
#foreground web colour #foreground web colour
set cname [tcl::string::tolower [tcl::string::range $i 4 end]] set tail [tcl::string::tolower [tcl::string::range $i 4 end]]
#-contrasting
#-contrastive
set cont [string range $tail end-11 end]
switch -- $cont {
-contrasting - -contrastive {
set cname [string range $tail 0 end-12]
}
default {
set cname $tail
}
}
if {[tcl::dict::exists $WEB_colour_map $cname]} { if {[tcl::dict::exists $WEB_colour_map $cname]} {
set rgbdash [tcl::dict::get $WEB_colour_map $cname] set rgbdash [tcl::dict::get $WEB_colour_map $cname]
set rgb [tcl::string::map { - ;} $rgbdash] switch -- $cont {
-contrasting {
set rgb [join [punk::ansi::colour::contrasting {*}[split $rgbdash -]] {;}]
}
-contrastive {
set rgb [join [lindex [punk::ansi::colour::contrast_pair {*}[split $rgbdash -]] 0] {;}]
}
default {
set rgb [tcl::string::map { - ;} $rgbdash]
}
}
lappend t "38;2;$rgb" lappend t "38;2;$rgb"
} else { } else {
puts stderr "ansi web colour unmatched: '$i' in call 'a+ $args'" puts stderr "ansi web colour unmatched: '$i' in call 'a+ $args'"
@ -2375,9 +2451,30 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#variable WEB_colour_map #variable WEB_colour_map
#upvar ::punk::ansi::WEB_colour_map WEB_colour_map #upvar ::punk::ansi::WEB_colour_map WEB_colour_map
#background web colour #background web colour
set cname [tcl::string::tolower [tcl::string::range $i 4 end]] set tail [tcl::string::tolower [tcl::string::range $i 4 end]]
set cont [string range $tail end-11 end]
switch -- $cont {
-contrasting - -contrastive {
set cname [string range $tail 0 end-12]
}
default {
set cname $tail
}
}
if {[tcl::dict::exists $WEB_colour_map $cname]} { if {[tcl::dict::exists $WEB_colour_map $cname]} {
lappend t "48;2;[tcl::string::map {- ;} [tcl::dict::get $WEB_colour_map $cname]]" set rgbdash [tcl::dict::get $WEB_colour_map $cname]
switch -- $cont {
-contrasting {
set rgb [join [punk::ansi::colour::contrasting {*}[split $rgbdash -]] {;}]
}
-contrastive {
set rgb [join [lindex [punk::ansi::colour::contrast_pair {*}[split $rgbdash -]] 0] {;}]
}
default {
set rgb [tcl::string::map { - ;} $rgbdash]
}
}
lappend t "48;2;$rgb"
} else { } else {
puts stderr "ansi Web colour unmatched: '$i' in call 'a+ $args'" puts stderr "ansi Web colour unmatched: '$i' in call 'a+ $args'"
} }
@ -2407,6 +2504,9 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
underline { underline {
lappend t 4 ;#underline lappend t 4 ;#underline
} }
underlinedefault {
lappend t 59
}
underextendedoff { underextendedoff {
#lremove any existing 4:1 etc #lremove any existing 4:1 etc
#NOTE struct::set result order can differ depending on whether tcl/critcl imp used #NOTE struct::set result order can differ depending on whether tcl/critcl imp used
@ -2420,13 +2520,13 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
underdouble { underdouble {
lappend e 4:2 lappend e 4:2
} }
undercurly { undercurly - undercurl {
lappend e 4:3 lappend e 4:3
} }
underdotted { underdotted - underdot {
lappend e 4:4 lappend e 4:4
} }
underdashed { underdashed - underdash {
lappend e 4:5 lappend e 4:5
} }
default { default {
@ -2542,45 +2642,109 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
} }
} }
} }
rgb- - rgb0 - rgb1 - rgb2 - rgb3 - rgb4 - rgb5 - rgb6 - rgb7 - rgb8 - rgb9 { rgb- - rgb0 - rgb1 - rgb2 - rgb3 - rgb4 - rgb5 - rgb6 - rgb7 - rgb8 - rgb9 -
#decimal rgb foreground
#allow variants rgb-xxx-xxx-xxx and rgbxxx-xxx-xxx
set rgbspec [tcl::string::trim [tcl::string::range $i 3 end] -]
set rgb [tcl::string::map [list - {;} , {;}] $rgbspec]
lappend t "38;2;$rgb"
}
Rgb- - RGB- - Rgb0 - Rgb1 - Rgb2 - Rgb3 - Rgb4 - Rgb5 - Rgb6 - Rgb7 - Rgb8 - Rgb9 - RGB0 - RGB1 - RGB2 - RGB3 - RGB4 - RGB5 - RGB6 - RGB7 - RGB8 - RGB9 { Rgb- - RGB- - Rgb0 - Rgb1 - Rgb2 - Rgb3 - Rgb4 - Rgb5 - Rgb6 - Rgb7 - Rgb8 - Rgb9 - RGB0 - RGB1 - RGB2 - RGB3 - RGB4 - RGB5 - RGB6 - RGB7 - RGB8 - RGB9 {
#decimal rgb background #decimal rgb foreground/background
set rgbspec [tcl::string::trim [tcl::string::range $i 3 end] -] #allow variants rgb-xxx-xxx-xxx and rgbxxx-xxx-xxx
set rgb [tcl::string::map [list - {;} , {;}] $rgbspec]
lappend t "48;2;$rgb" set cont [string range $i end-11 end]
} switch -- $cont {
"rgb#" { -contrasting - -contrastive {
#hex rgb foreground set iplain [string range $i 0 end-12]
set hex6 [tcl::string::trim [tcl::string::range $i 4 end] -] }
set rgb [join [::scan $hex6 %2X%2X%2X] {;}] default {
lappend t "38;2;$rgb" set iplain $i
}
}
set rgbspec [tcl::string::trim [tcl::string::range $iplain 3 end] -]
set RGB [tcl::string::map [list - { } , { } {;} { }] $rgbspec] ;#RGB as list
switch -- $cont {
-contrasting {
set rgbfinal [join [punk::ansi::colour::contrasting {*}$RGB] {;}]
}
-contrastive {
set rgbfinal [join [lindex [punk::ansi::colour::contrast_pair {*}$RGB] 0] {;}]
}
default {
set rgbfinal [join $RGB {;}]
}
}
if {[tcl::string::index $i 0] eq "r"} {
#fg
lappend t "38;2;$rgbfinal"
} else {
#bg
lappend t "48;2;$rgbfinal"
}
} }
"Rgb#" - "RGB#" { "rgb#" - "Rgb#" - "RGB#" {
#hex rgb background
set hex6 [tcl::string::trim [tcl::string::range $i 4 end] -] set hex6 [tcl::string::trim [tcl::string::range $i 4 end] -]
set rgb [join [::scan $hex6 %2X%2X%2X] {;}] #set rgb [join [::scan $hex6 %2X%2X%2X] {;}]
lappend t "48;2;$rgb" set RGB [::scan $hex6 %2X%2X%2X]
set cont [string range $i end-11 end]
switch -- $cont {
-contrasting {
set rgbfinal [join [punk::ansi::colour::contrasting {*}$RGB] {;}]
}
-contrastive {
set rgbfinal [join [lindex [punk::ansi::colour::contrast_pair {*}$RGB] 0] {;}]
}
default {
set rgbfinal [join $RGB {;}]
}
}
if {[tcl::string::index $i 0] eq "r"} {
#hex rgb foreground
lappend t "38;2;$rgbfinal"
} else {
#hex rgb background
lappend t "48;2;$rgbfinal"
}
} }
und- - und0 - und1 - und2 - und3 - und4 - und5 - und6 - und7 - und8 - und9 { und- - und0 - und1 - und2 - und3 - und4 - und5 - und6 - und7 - und8 - und9 {
#decimal rgb underline #decimal rgb underline
#allow variants und-xxx-xxx-xxx and undxxx-xxx-xxx #allow variants und-xxx-xxx-xxx and undxxx-xxx-xxx
#https://wezfurlong.org/wezterm/escape-sequences.html#csi-582-underline-color-rgb
set rgbspec [tcl::string::trim [tcl::string::range $i 3 end] -] set rgbspec [tcl::string::trim [tcl::string::range $i 3 end] -]
set rgb [tcl::string::map [list - {:} , {:}] $rgbspec] set RGB [lrange [tcl::string::map [list - { } , { } {;} { }] $rgbspec] 0 2]
lappend e "58:2::$rgb" #puts "---->'$RGB'<----"
set cont [string range $i end-11 end]
switch -- $cont {
-contrasting {
set rgbfinal [join [punk::ansi::colour::contrasting {*}$RGB] {:}]
}
-contrastive {
set rgbfinal [join [lindex [punk::ansi::colour::contrast_pair {*}$RGB] 0] {:}]
}
default {
set rgbfinal [join $RGB {:}]
}
}
#lappend e "58:2:$rgbfinal" ;# - no colorspace ID - some terminals support? which?
lappend e "58:2::$rgbfinal"
} }
"und#" { "und#" {
#hex rgb underline - (e.g kitty, wezterm) - uses colons as separators #hex rgb underline - (e.g kitty, wezterm) - uses colons as separators
set hex6 [tcl::string::trim [tcl::string::range $i 4 end] -] set hex6 [tcl::string::trim [tcl::string::range $i 4 end] -]
set rgb [join [::scan $hex6 %2X%2X%2X] {:}] #set rgb [join [::scan $hex6 %2X%2X%2X] {:}]
lappend e "58:2::$rgb" set RGB [::scan $hex6 %2X%2X%2X]
set cont [string range $i end-11 end]
switch -- $cont {
-contrasting {
set rgbfinal [join [punk::ansi::colour::contrasting {*}$RGB] {:}]
}
-contrastive {
set rgbfinal [join [lindex [punk::ansi::colour::contrast_pair {*}$RGB] 0] {:}]
}
default {
set rgbfinal [join $RGB {:}]
}
}
lappend e "58:2::$rgbfinal"
} }
undt { undt {
#CSI 58:5 UNDERLINE COLOR PALETTE INDEX
#CSI 58 : 5 : INDEX m
#variable TERM_colour_map #variable TERM_colour_map
#256 colour underline by Xterm name or by integer #256 colour underline by Xterm name or by integer
#name is xterm name or colour index from 0 - 255 #name is xterm name or colour index from 0 - 255
@ -2762,6 +2926,9 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
underline { underline {
lappend t 4 ;#underline lappend t 4 ;#underline
} }
underlinedefault {
lappend t 59
}
underextendedoff { underextendedoff {
#lremove any existing 4:1 etc #lremove any existing 4:1 etc
#use of struct::set with critcl is fast, but will reorder e (with differences depending on tcl vs critcl) #use of struct::set with critcl is fast, but will reorder e (with differences depending on tcl vs critcl)
@ -2775,13 +2942,13 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
underdouble { underdouble {
lappend e 4:2 lappend e 4:2
} }
undercurly { undercurly - undercurl {
lappend e 4:3 lappend e 4:3
} }
underdotted { underdotted - underdot {
lappend e 4:4 lappend e 4:4
} }
underdashed { underdashed - underdash {
lappend e 4:5 lappend e 4:5
} }
default { default {
@ -3262,6 +3429,8 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#tput rmam #tput rmam
return \x1b\[?7l return \x1b\[?7l
} }
proc query_mode_line_wrap {} { proc query_mode_line_wrap {} {
#*** !doctools #*** !doctools
#[call [fun query_mode_line_wrap]] #[call [fun query_mode_line_wrap]]
@ -3274,6 +3443,31 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
# \x1b\[?7\;2\$y # \x1b\[?7\;2\$y
#where 1 = set, 2 = unset. (0 = mode not recognised, 3 = permanently set, 4 = permanently unset) #where 1 = set, 2 = unset. (0 = mode not recognised, 3 = permanently set, 4 = permanently unset)
#names for other alt_screen mechanisms: 1047,1048 vs 1049?
variable decmode_names [dict create\
line_wrap 7\
LNM 20\
alt_screen 1049\
grapheme_clusters 2027\
bracketed_paste 2004\
mouse_sgr_extended 1006\
mouse_urxvt 1015\
mouse_sgr 1016\
]
proc query_mode {num_or_name} {
if {[string is integer -strict $num_or_name]} {
set m $num_or_name
} else {
variable decmode_names
if {[dict exists $decmode_names $num_or_name]} {
set m [dict get $decmode_names $num_or_name]
} else {
error "punk::ansi::query_mode unrecognised mode '$num_or_name'. Known mode names: [dict keys $decmode_names]"
}
}
return "\x1b\[?$m\$p"
}
#Alt screen buffer - smcup/rmcup ti/te #Alt screen buffer - smcup/rmcup ti/te
#Note \x1b\[?47h doesn't work at all in some terminals (e.g alacritty,cmd on windows and reportedly kitty) #Note \x1b\[?47h doesn't work at all in some terminals (e.g alacritty,cmd on windows and reportedly kitty)
@ -3658,7 +3852,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
} }
proc ansistrip2 {text} { proc ansistrip2 {text} {
#*** !doctools #*** !doctools
#[call [fun ansistrip] [arg text] ] #[call [fun ansistrip2] [arg text] ]
#[para]Return a string with ansi codes stripped out #[para]Return a string with ansi codes stripped out
#[para]Alternate graphics chars are replaced with modern unicode equivalents (e.g boxdrawing glyphs) #[para]Alternate graphics chars are replaced with modern unicode equivalents (e.g boxdrawing glyphs)
@ -6016,6 +6210,10 @@ tcl::namespace::eval punk::ansi::ansistring {
SP [list \x20 \u2420]\ SP [list \x20 \u2420]\
DEL [list \x7f \u2421]\ DEL [list \x7f \u2421]\
] ]
set map_c0 [dict create]
dict for {k v} $visuals_c0 {
dict set map_c0 {*}$v
}
#alternate symbols for space #alternate symbols for space
# \u2422 Blank Symbol (b with forwardslash overly) # \u2422 Blank Symbol (b with forwardslash overly)
@ -6051,6 +6249,9 @@ tcl::namespace::eval punk::ansi::ansistring {
#miscellaneous debug code brackets #miscellaneous debug code brackets
set obm \u27e6 ;set cbm \u27e7 ;#square double brackets from Miscellaneous Mathematical Symbols-A set obm \u27e6 ;set cbm \u27e7 ;#square double brackets from Miscellaneous Mathematical Symbols-A
#unicode Tags block brackets
set obt \u2993 ;set cbt \u2994
#this private range so rarely supported in fonts - and visuals are unknown, so we will make up some 2-letter codes for now #this private range so rarely supported in fonts - and visuals are unknown, so we will make up some 2-letter codes for now
#set visuals_c1 [tcl::dict::create\ #set visuals_c1 [tcl::dict::create\
# BPH [list \x82 "${ob8}\ue022 $cb8"]\ # BPH [list \x82 "${ob8}\ue022 $cb8"]\
@ -6120,9 +6321,21 @@ tcl::namespace::eval punk::ansi::ansistring {
APC [list \x9f "${ob8}AP$cb8"]\ APC [list \x9f "${ob8}AP$cb8"]\
] ]
#unicode Tags block - nonprinting mapped to ascii 0-127
set visuals_tags [tcl::dict::create]
for {set i 917504} {$i < 917632} {incr i} {
set asciidec [expr {$i - 917504}]
set vis [format %c $asciidec]
if {[dict exists $map_c0 $vis]} {
set vis [dict get $map_c0 $vis]
}
tcl::dict::set visuals_tags TAG$asciidec [list [format %c $i] "${obt}$vis${cbt}"]
}
set hack [tcl::dict::create] set hack [tcl::dict::create]
tcl::dict::set hack BOM1 [list \uFEFF "${obm}\U1f4a3$cbm"] ;#byte order mark/ ZWNBSP (ZWNBSP usage generally deprecated) - a picture of a bomb(2wide glyph) tcl::dict::set hack BOM1 [list \uFEFF "${obm}\U1f4a3$cbm"] ;#byte order mark/ ZWNBSP (ZWNBSP usage generally deprecated) - a picture of a bomb(2wide glyph)
tcl::dict::set hack ZWSP [list \u200B "${obm}ZWSP$cbm"]
#review - other boms? Encoding dependent? #review - other boms? Encoding dependent?
tcl::dict::set hack DCS [list \x90 "${ob8}\u2328 $cb8"] ;#keyboard from Miscellaneous Technical - 1 wide + pad. tcl::dict::set hack DCS [list \x90 "${ob8}\u2328 $cb8"] ;#keyboard from Miscellaneous Technical - 1 wide + pad.
@ -6133,7 +6346,7 @@ tcl::namespace::eval punk::ansi::ansistring {
tcl::dict::set hack PM [list \x9e "${ob8}PM$cb8"] tcl::dict::set hack PM [list \x9e "${ob8}PM$cb8"]
tcl::dict::set hack APC [list \x9f "${ob8}\U1f534$cb8"] ;#bright red ball from Miscellaneoust Symbols and Pictographs - 2 wide (APC also noted as a potential security risk) tcl::dict::set hack APC [list \x9f "${ob8}\U1f534$cb8"] ;#bright red ball from Miscellaneoust Symbols and Pictographs - 2 wide (APC also noted as a potential security risk)
set debug_visuals [tcl::dict::merge $visuals_c0 $visuals_c1 $hack] set debug_visuals [tcl::dict::merge $visuals_c0 $visuals_c1 $hack $visuals_tags]
#for repeated interaction with the same ANSI string - a mechanism to store state is more efficient #for repeated interaction with the same ANSI string - a mechanism to store state is more efficient
proc NEW {string} { proc NEW {string} {
@ -6165,7 +6378,7 @@ tcl::namespace::eval punk::ansi::ansistring {
-sp 1\ -sp 1\
] ]
set argopts [lrange $args 0 end-1] set argopts [lrange $args 0 end-1]
if {[llength $argopts] % 2 != 0} { if {[llength $argopts] % 2} {
error "ansistring VIEW options must be option-value pairs, received '$argopts'. Known opts [tcl::dict::keys $defaults]" error "ansistring VIEW options must be option-value pairs, received '$argopts'. Known opts [tcl::dict::keys $defaults]"
} }
set opts [tcl::dict::merge $defaults $argopts] set opts [tcl::dict::merge $defaults $argopts]
@ -6760,7 +6973,240 @@ tcl::namespace::eval punk::ansi::ansistring {
#*** !doctools #*** !doctools
#[list_end] [comment {--- end definitions namespace punk::ansi::ta ---}] #[list_end] [comment {--- end definitions namespace punk::ansi::ta ---}]
} }
tcl::namespace::eval punk::ansi::control {
proc APC {args} {
return \x1b_[join $args {;}]\x1b\\
}
proc APC8 {args} {
return \x9f[join $args {;}]\x9c
}
proc CSI {args} {
set finalarg [lindex $args end]
set finalbyte [string index $finalarg end]
if {![regexp {[\x40-\x73]} $finalbyte]} {
error "::punk::ansi::control::CSI final byte must be one in the set @A-Z\[\\\]^_`a-z\{|\}~"
}
if {$finalarg eq $finalbyte} {
return \x1b\[[join [lrange $args 0 end-1] {;}]$finalbyte
} else {
return \x1b\[[join $args {;}]
}
}
proc CSI8 {args} {
set finalarg [lindex $args end]
set finalbyte [string index $finalarg end]
if {![regexp {[\x40-\x73]} $finalbyte]} {
error "::punk::ansi::control::CSI final byte must be one in the set @A-Z\[\\\]^_`a-z\{|\}~"
}
if {$finalarg eq $finalbyte} {
return \x9b[join [lrange $args 0 end-1] {;}]$finalbyte
} else {
return \x9b[join $args {;}]
}
}
proc DCS {args} {
return \x1bP[join $args {;}]\x1b\\
}
proc DCS8 {args} {
return \x90[join $args {;}]\x9c
}
proc OSC {args} {
return \x1b\][join $args {;}]\x1b\\
}
proc OSC8 {args} {
return \x9d[join $args {;}]\x9c
}
}
namespace eval punk::ansi::colour {
package require punk::assertion
if {[catch {namespace import ::punk::assertion::assert} errM]} {
puts stderr "punk error importing punk::assertion::assert\n$errM"
puts stderr "punk::a* commands:[info commands ::punk::a*]"
}
punk::assertion::active on
#see also colors package
#https://sourceforge.net/p/irrational-numbers/code/HEAD/tree/pkgs/Colors/trunk/colors.tcl#l159
# classic formula for luminance (0.0 .. 100.0)
proc luminance {R G B} {
return [expr {(0.3*$R + 0.59*$G + 0.11*$B)/255.0}]
}
#New colour's luminance is dark if orig-colour is bright, and viceversa
#(note not all colours are invertable to return original)
proc contrasting {R G B} {
set lum [luminance $R $G $B]
if {$lum < 0.597} {
set lum 0.9
} else {
set lum 0.2
}
lassign [RGB2hsl $R $G $B] h s l
return [hsl2RGB $h $s $lum]
}
proc contrast_pair {R G B} {
set contra [contrasting $R $G $B]
set back [contrasting {*}$contra]
return [list $back $contra] ;#back may or may not equal original R G B
}
proc hsl2RGB { H S L } {
if { $L < 0.5 } {
set Q [expr {$L*(1.0+$S)}]
} else {
set Q [expr {$L+$S-($L*$S)}]
}
set P [expr {2.0*$L-$Q}]
set Hk [expr {$H/360.0}]
set T(R) [expr {$Hk+1.0/3.0}]
set T(G) $Hk
set T(B) [expr {$Hk-1.0/3.0}]
# normalize
foreach c {R G B} {
if {$T($c) < 0.0} { set T($c) [expr {$T($c)+1.0}] }
if {$T($c) > 1.0} { set T($c) [expr {$T($c)-1.0}] }
}
foreach c {R G B} {
if {$T($c) < [expr {1.0/6.0}]} {
set T($c) [expr {$P+($Q-$P)*6.0*$T($c)}]
} elseif {$T($c) < 0.5} {
set T($c) $Q
} elseif {$T($c) < [expr {2.0/3.0}]} {
set T($c) [expr {$P+($Q-$P)*(2.0/3.0-$T($c))*6.0}]
} else {
set T($c) $P
}
set T($c) [expr {round($T($c)*255)}]
}
return [list $T(R) $T(G) $T(B)]
}
proc RGB2hsl { R G B } {
set r [expr {$R/255.0}]
set g [expr {$G/255.0}]
set b [expr {$B/255.0}]
set max $r
set min $r
if { $g > $max } { set max $g }
if { $g < $min } { set min $g }
if { $b > $max } { set max $b }
if { $b < $min } { set min $b }
if { $max == $min } {
set H 0.0
} elseif { $b == $max } {
set H [expr {60* ($r-$g)/($max-$min)+240}]
} elseif { $g == $max } {
set H [expr {60* ($b-$r)/($max-$min)+120}]
} else {
# $r == $max
if { $g >= $b } {
set H [expr {60* ($g-$b)/($max-$min)}]
} else {
set H [expr {60* ($g-$b)/($max-$min)+360}]
}
}
set L [expr {($max+$min)/2}]
if { $L == 0.0 || $max == $min } {
set S 0.0
} elseif { $L <= 0.5 } {
set S [expr {($max-$min)/($max+$min)}]
} else {
set S [expr {($max-$min)/(2.0-($max+$min))}]
}
return [list $H $S $L]
}
#red green blue to hsl (hue saturation luminance)
#https://www.rapidtables.com/convert/color/rgb-to-hsl.html
proc jexer_rgb_to_hsl {red green blue} {
#algorithm port from Jexer LegacySixelEncode.java - with thanks to Autumn Lamonte (MIT lic)
assert {$red >=0 && $red <= 255}
assert {$green >=0 && $green <= 255}
assert {$blue >=0 && $blue <= 255}
set R [expr {$red / 255.0}]
set G [expr {$green / 255.0}]
set B [expr {$blue / 255.0}]
set Rmax 0
set Gmax 0
set Bmax 0
set min [expr {$R < $G ? $R : $G}]
set min [expr {$min < $B ? $min : $B}]
set max 0
if {($R >= $G) && ($R >= $B)} {
set max $R
set Rmax 1
} elseif {($G >= $R) && ($G >= $B)} {
set max $G
set Gmax 1
} elseif {($B >= $G) && ($B >= $R)} {
set max $B
set Bmax 1
}
set L [expr {($min + $max) / 2.0}]
set H 0.0
set S 0.0
#REVIEW - java allows floating point division by 0.0 - producing positive infinity, negative infinity or NaN
#This makes the original java algorithm a little more obscure
if {$min != $max} {
#no divide by zero issues due to min != max
if {$L < 0.5} {
set S [expr {($max - $min) / ($max + $min)}]
} else {
set S [expr {($max - $min) / (2.0 - $max - $min)}]
}
}
if {$Rmax} {
#puts "G'$G' B'$B' max'$max' min'$min'"
assert {$Gmax == 0}
assert {$Bmax == 0}
if {($max - $min) == 0} {
set H 0.0 ;#review
} else {
set H [expr {($G - $B) / ($max - $min)}]
}
} elseif {$Gmax} {
assert {$Rmax == 0}
assert {$Bmax == 0}
if {($max - $min) == 0} {
set H 2.0
} else {
set H [expr {2.0 + ($B - $R) / ($max - $min)}]
}
} elseif {$Bmax} {
assert {$Rmax == 0}
assert {$Gmax == 0}
if {($max - $min) == 0} {
set H 4.0
} else {
set H [expr {4.0 + ($R - $G) / ($max - $min)}]
}
}
if {$H < 0.0} {
set H [expr {$H + 6.0}]
}
#Tcl mathfunc round vs int (which rounds down)
set hue [expr {round($H * 60)}]
set sat [expr {round($S * 100)}]
set lum [expr {round($L * 100)}]
assert {$hue >= 0 && $hue <= 360}
assert {$sat >= 0 && $sat <= 100}
assert {$lum >= 0 && $lum <= 100}
return [list $hue $sat $lum]
}
}
tcl::namespace::eval punk::ansi::internal { tcl::namespace::eval punk::ansi::internal {
proc splitn {str {len 1}} { proc splitn {str {len 1}} {
#from textutil::split::splitn #from textutil::split::splitn
@ -6837,7 +7283,7 @@ tcl::namespace::eval punk::ansi::internal {
if {$2digithexchars eq ""} { if {$2digithexchars eq ""} {
return "" return ""
} }
if {[tcl::string::length $2digithexchars] % 2 != 0} { if {[tcl::string::length $2digithexchars] % 2} {
error "hex2str requires an even number of hex digits (2 per character)" error "hex2str requires an even number of hex digits (2 per character)"
} }
set 2str "" set 2str ""

143
src/modules/punk/args-999999.0a1.0.tm

@ -1,6 +1,6 @@
# -*- tcl -*- # -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'pmix make' or bin/punkmake to update from <pkg>-buildversion.txt # Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'pmix make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.2.tm # module template: punkshell/src/decktemplates/vendor/punk/modules/template_module-0.0.2.tm
# #
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. # Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. # Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
@ -202,6 +202,7 @@
#[list_begin itemized] #[list_begin itemized]
package require Tcl 8.6- package require Tcl 8.6-
#optional? punk::trie #optional? punk::trie
#optional? punk::textblock
#*** !doctools #*** !doctools
#[item] [package {Tcl 8.6-}] #[item] [package {Tcl 8.6-}]
@ -267,7 +268,10 @@ tcl::namespace::eval punk::args {
#[para] Core API functions for punk::args #[para] Core API functions for punk::args
#[list_begin definitions] #[list_begin definitions]
#todo - some sort of punk::args::cherrypick operation to get spec from an existing set
#todo - doctools output from definition
#dict get value with default wrapper for tcl 8.6
if {[info commands ::tcl::dict::getdef] eq ""} { if {[info commands ::tcl::dict::getdef] eq ""} {
#package require punk::lib #package require punk::lib
#interp alias "" ::punk::args::Dict_getdef "" ::punk::lib::dict_getdef #interp alias "" ::punk::args::Dict_getdef "" ::punk::lib::dict_getdef
@ -301,7 +305,7 @@ tcl::namespace::eval punk::args {
#review - how to make work with trie prefix e.g -corner -aliases {-corners} #review - how to make work with trie prefix e.g -corner -aliases {-corners}
#We mightn't want the prefix to be longer just because of an alias #We mightn't want the prefix to be longer just because of an alias
proc Get_argspecs {optionspecs args} { proc definition {optionspecs args} {
variable argspec_cache variable argspec_cache
#variable argspecs ;#REVIEW!! #variable argspecs ;#REVIEW!!
variable argspec_ids variable argspec_ids
@ -434,6 +438,7 @@ tcl::namespace::eval punk::args {
} }
} }
set proc_info {} set proc_info {}
set id_info {} ;#e.g -children <list> ??
set opt_any 0 set opt_any 0
set val_min 0 set val_min 0
set val_max -1 ;#-1 for no limit set val_max -1 ;#-1 for no limit
@ -444,8 +449,8 @@ tcl::namespace::eval punk::args {
"" - # {continue} "" - # {continue}
} }
set linespecs [lassign $trimln argname] set linespecs [lassign $trimln argname]
if {$argname ne "*id" && [llength $linespecs] %2 != 0} { if {$argname ne "*id" && [llength $linespecs] % 2} {
error "punk::args::get_dict - bad optionspecs line for record '$argname' Remaining items on line must be in paired option-value format - received '$linespecs'" error "punk::args::definition - bad optionspecs line for record '$argname' Remaining items on line must be in paired option-value format - received '$linespecs'"
} }
set firstchar [tcl::string::index $argname 0] set firstchar [tcl::string::index $argname 0]
set secondchar [tcl::string::index $argname 1] set secondchar [tcl::string::index $argname 1]
@ -454,14 +459,18 @@ tcl::namespace::eval punk::args {
switch -- [tcl::string::range $argname 1 end] { switch -- [tcl::string::range $argname 1 end] {
id { id {
#id line must have single entry - a unique id assigned by the user - an id will be allocated if no id line present or the value is "auto" #id line must have single entry - a unique id assigned by the user - an id will be allocated if no id line present or the value is "auto"
if {[llength $starspecs] != 1} { if {[llength $starspecs] == 0} {
error "punk::args::Get_argspecs - *id line must have a single entry following *id." error "punk::args::definition - *id line must have at least a single entry following *id."
} }
if {$spec_id ne ""} { if {$spec_id ne ""} {
#disallow duplicate *id line #disallow duplicate *id line
error "punk::args::Get_argspecs - *id already set. Existing value $spec_id" error "punk::args::definition - *id already set. Existing value $spec_id"
}
set spec_id [lindex $starspecs 0]
set id_info [lrange $starspecs 1 end]
if {[llength $id_info] %2} {
error "punk::args::definition - bad *id line. Remaining items on line after *id <id> must be in paired option-value format - received '$linespecs'"
} }
set spec_id $starspecs
} }
proc { proc {
#allow arbitrary - review #allow arbitrary - review
@ -523,7 +532,7 @@ tcl::namespace::eval punk::args {
-type -optional -allow_ansi -validate_ansistripped -strip_ansi -multiple\ -type -optional -allow_ansi -validate_ansistripped -strip_ansi -multiple\
-regexprepass -regexprefail -validationtransform\ -regexprepass -regexprefail -validationtransform\
} }
error "punk::args::Get_argspecs - unrecognised key '$k' in *opts line. Known keys: $known" error "punk::args::definition - unrecognised key '$k' in *opts line. Known keys: $known"
} }
} }
} }
@ -534,14 +543,14 @@ tcl::namespace::eval punk::args {
-min - -min -
-minvalues { -minvalues {
if {$v < 0} { if {$v < 0} {
error "punk::args::Get_argspecs - minimum acceptable value for key '$k' in *opts line is 0. got $v" error "punk::args::definition - minimum acceptable value for key '$k' in *opts line is 0. got $v"
} }
set val_min $v set val_min $v
} }
-max - -max -
-maxvalues { -maxvalues {
if {$v < -1} { if {$v < -1} {
error "punk::args::Get_argspecs - minimum acceptable value for key '$k' in *opts line is -1 (indicating unlimited). got $v" error "punk::args::definition - minimum acceptable value for key '$k' in *opts line is -1 (indicating unlimited). got $v"
} }
set val_max $v set val_max $v
} }
@ -594,14 +603,14 @@ tcl::namespace::eval punk::args {
-type -optional -allow_ansi -validate_ansistripped -strip_ansi -multiple\ -type -optional -allow_ansi -validate_ansistripped -strip_ansi -multiple\
-regexprepass -regexprefail -validationtransform\ -regexprepass -regexprefail -validationtransform\
} }
error "punk::args::Get_argspecs - unrecognised key '$k' in *values line. Known keys: $known" error "punk::args::definition - unrecognised key '$k' in *values line. Known keys: $known"
} }
} }
} }
} }
default { default {
error "punk::args::Get_argspecs - unrecognised * line in '$ln'. Expected *proc *opts or *values - use **name if paramname needs to be *name" error "punk::args::definition - unrecognised * line in '$ln'. Expected *proc *opts or *values - use **name if paramname needs to be *name"
} }
} }
continue continue
@ -654,7 +663,7 @@ tcl::namespace::eval punk::args {
lappend opt_solos $argname lappend opt_solos $argname
} else { } else {
#-solo only valid for flags #-solo only valid for flags
error "punk::args::get_dict - invalid -type 'none' for positional argument positional argument '$argname'" error "punk::args::definition - invalid -type 'none' for positional argument positional argument '$argname'"
} }
} }
any - anything { any - anything {
@ -681,8 +690,8 @@ tcl::namespace::eval punk::args {
} }
-validationtransform { -validationtransform {
#string is dict only 8.7/9+ #string is dict only 8.7/9+
if {([llength $specval] % 2) != 0} { if {[llength $specval] % 2} {
error "punk::args::get_dict - invalid value for key '$spec' in specifications for argument '$argname' - value must be a dictionary" error "punk::args::definition - invalid value for key '$spec' in specifications for argument '$argname' - value must be a dictionary"
} }
dict for {tk tv} $specval { dict for {tk tv} $specval {
switch -- $tk { switch -- $tk {
@ -690,7 +699,7 @@ tcl::namespace::eval punk::args {
} }
default { default {
set known_transform_keys [list -function -type -minlen -maxlen -range] ;#-choices etc? set known_transform_keys [list -function -type -minlen -maxlen -range] ;#-choices etc?
error "punk::args::get_dict - invalid subkey $tk for key '$spec' in specifications for argument '$argname' must be one of: $known_transform_keys" error "punk::args::definition - invalid subkey $tk for key '$spec' in specifications for argument '$argname' must be one of: $known_transform_keys"
} }
} }
} }
@ -701,7 +710,7 @@ tcl::namespace::eval punk::args {
-nocase -optional -multiple -validate_ansistripped -allow_ansi -strip_ansi -help\ -nocase -optional -multiple -validate_ansistripped -allow_ansi -strip_ansi -help\
-regexprepass -regexprefail -validationtransform\ -regexprepass -regexprefail -validationtransform\
] ]
error "punk::args::get_dict - unrecognised key '$spec' in specifications for argument '$argname' Known option specification keys: $known_argspecs" error "punk::args::definition - unrecognised key '$spec' in specifications for argument '$argname' Known option specification keys: $known_argspecs"
} }
} }
} }
@ -764,6 +773,7 @@ tcl::namespace::eval punk::args {
valspec_defaults $valspec_defaults\ valspec_defaults $valspec_defaults\
val_checks_defaults $val_checks_defaults\ val_checks_defaults $val_checks_defaults\
proc_info $proc_info\ proc_info $proc_info\
id_info $id_info\
] ]
tcl::dict::set argspec_cache $cache_key $result tcl::dict::set argspec_cache $cache_key $result
#tcl::dict::set argspecs $spec_id $optionspecs #tcl::dict::set argspecs $spec_id $optionspecs
@ -817,6 +827,7 @@ tcl::namespace::eval punk::args {
} }
set caller [regexp -inline {\S+} $cmdinfo] set caller [regexp -inline {\S+} $cmdinfo]
if {$caller eq "namespace"} { if {$caller eq "namespace"} {
# review - message?
set cmdinfo "punk::args::get_dict called from namespace" set cmdinfo "punk::args::get_dict called from namespace"
} }
return $cmdinfo return $cmdinfo
@ -825,6 +836,7 @@ tcl::namespace::eval punk::args {
#basic recursion blocker #basic recursion blocker
variable arg_error_isrunning 0 variable arg_error_isrunning 0
proc arg_error {msg spec_dict {badarg ""}} { proc arg_error {msg spec_dict {badarg ""}} {
#limit colours to standard 16 so that themes can apply to help output
variable arg_error_isrunning variable arg_error_isrunning
if {$arg_error_isrunning} { if {$arg_error_isrunning} {
error "arg_error already running - error in arg_error?\n triggering errmsg: $badarg" error "arg_error already running - error in arg_error?\n triggering errmsg: $badarg"
@ -843,20 +855,17 @@ tcl::namespace::eval punk::args {
set procname [::punk::args::Dict_getdef $spec_dict proc_info -name ""] set procname [::punk::args::Dict_getdef $spec_dict proc_info -name ""]
set prochelp [::punk::args::Dict_getdef $spec_dict proc_info -help ""] set prochelp [::punk::args::Dict_getdef $spec_dict proc_info -help ""]
#set t [textblock::class::table new [a+ web-yellow]Usage[a]]
set t [textblock::class::table new [a+ brightyellow]Usage[a]] set t [textblock::class::table new [a+ brightyellow]Usage[a]]
set blank_header_col [list ""] set blank_header_col [list ""]
if {$procname ne ""} { if {$procname ne ""} {
lappend blank_header_col "" lappend blank_header_col ""
#set procname_display [a+ web-white]$procname[a]
set procname_display [a+ brightwhite]$procname[a] set procname_display [a+ brightwhite]$procname[a]
} else { } else {
set procname_display "" set procname_display ""
} }
if {$prochelp ne ""} { if {$prochelp ne ""} {
lappend blank_header_col "" lappend blank_header_col ""
#set prochelp_display [a+ web-white]$prochelp[a]
set prochelp_display [a+ brightwhite]$prochelp[a] set prochelp_display [a+ brightwhite]$prochelp[a]
} else { } else {
set prochelp_display "" set prochelp_display ""
@ -880,12 +889,19 @@ tcl::namespace::eval punk::args {
$t configure_header 2 -values {Arg Type Default Multiple Help} $t configure_header 2 -values {Arg Type Default Multiple Help}
} }
#set c_default [a+ web-white Web-limegreen]
set c_default [a+ brightwhite Brightgreen] set RST [a]
#set c_badarg [a+ web-crimson] #set A_DEFAULT [a+ brightwhite Brightgreen]
set c_badarg [a+ brightred] set A_DEFAULT ""
#set greencheck [a+ web-limegreen]\u2713[a] set A_BADARG [a+ brightred]
set greencheck [a+ brightgreen]\u2713[a] set greencheck [a+ brightgreen]\u2713[a]
set A_PREFIX [a+ green] ;#use a+ so colour off can apply
if {$A_PREFIX eq ""} {
set A_PREFIX [a+ underline]
set A_PREFIXEND [a+ nounderline]\u200B ;#padding will take ANSI from last char - so add a zero width space
} else {
set A_PREFIXEND $RST
}
set opt_names [list] set opt_names [list]
set opt_names_display [list] set opt_names_display [list]
@ -894,8 +910,6 @@ tcl::namespace::eval punk::args {
set trie [punk::trie::trieclass new {*}[dict get $spec_dict opt_names]] set trie [punk::trie::trieclass new {*}[dict get $spec_dict opt_names]]
set idents [dict get [$trie shortest_idents ""] scanned] set idents [dict get [$trie shortest_idents ""] scanned]
$trie destroy $trie destroy
set M "\x1b\[32m" ;#mark in green
set RST "\x1b\[m"
foreach c [dict get $spec_dict opt_names] { foreach c [dict get $spec_dict opt_names] {
set id [dict get $idents $c] set id [dict get $idents $c]
#REVIEW #REVIEW
@ -907,7 +921,7 @@ tcl::namespace::eval punk::args {
set prefix [string range $c 0 $idlen-1] set prefix [string range $c 0 $idlen-1]
set tail [string range $c $idlen end] set tail [string range $c $idlen end]
} }
lappend opt_names_display $M$prefix$RST$tail lappend opt_names_display $A_PREFIX$prefix$A_PREFIXEND$tail
#lappend opt_names_display $M[ansistring VIEW $prefix]$RST[ansistring VIEW $tail] #lappend opt_names_display $M[ansistring VIEW $prefix]$RST[ansistring VIEW $tail]
lappend opt_names $c lappend opt_names $c
} }
@ -916,18 +930,31 @@ tcl::namespace::eval punk::args {
set opt_names_display $opt_names set opt_names_display $opt_names
} }
} }
set val_names [dict get $spec_dict val_names] set trailing_val_names [dict get $spec_dict val_names] ;#temporarily assign all as trailing
set val_names_display $val_names set leading_val_names [list]
dict for {argname info} [tcl::dict::get $spec_dict arg_info] {
if {![string match -* $argname]} {
lappend leading_val_names [lpop trailing_val_names 0]
} else {
break
}
}
if {![llength $leading_val_names] && ![llength $opt_names]} {
#all vals were actually trailing - no opts
set trailing_val_names $leading_val_names
set leading_val_names {}
}
set leading_val_names_display $leading_val_names
set trailing_val_names_display $trailing_val_names
#display options first then values #display options first then values
foreach argumentset [list [list $opt_names_display $opt_names] [list $val_names_display $val_names]] { foreach argumentset [list [list $leading_val_names_display $leading_val_names] [list $opt_names_display $opt_names] [list $trailing_val_names_display $trailing_val_names]] {
lassign $argumentset argnames_display argnames lassign $argumentset argnames_display argnames
foreach argshow $argnames_display arg $argnames { foreach argshow $argnames_display arg $argnames {
set arginfo [dict get $spec_dict arg_info $arg] set arginfo [dict get $spec_dict arg_info $arg]
if {[dict exists $arginfo -default]} { if {[dict exists $arginfo -default]} {
#set default $c_default[dict get $arginfo -default] set default $A_DEFAULT[dict get $arginfo -default]$RST
set default [dict get $arginfo -default]
} else { } else {
set default "" set default ""
} }
@ -954,8 +981,6 @@ tcl::namespace::eval punk::args {
set trie [punk::trie::trieclass new {*}[dict get $arginfo -choices]] set trie [punk::trie::trieclass new {*}[dict get $arginfo -choices]]
set idents [dict get [$trie shortest_idents ""] scanned] set idents [dict get [$trie shortest_idents ""] scanned]
$trie destroy $trie destroy
set M "\x1b\[32m" ;#mark in green
set RST "\x1b\[m"
foreach c [dict get $arginfo -choices] { foreach c [dict get $arginfo -choices] {
set id [dict get $idents $c] set id [dict get $idents $c]
if {$id eq $c} { if {$id eq $c} {
@ -966,7 +991,7 @@ tcl::namespace::eval punk::args {
set prefix [string range $c 0 $idlen-1] set prefix [string range $c 0 $idlen-1]
set tail [string range $c $idlen end] set tail [string range $c $idlen end]
} }
lappend formattedchoices "$M[ansistring VIEW $prefix]$RST[ansistring VIEW $tail]" lappend formattedchoices "$A_PREFIX[ansistring VIEW $prefix]$A_PREFIXEND[ansistring VIEW $tail]"
} }
} errM]} { } errM]} {
puts stderr "prefix marking failed\n$errM" puts stderr "prefix marking failed\n$errM"
@ -999,7 +1024,7 @@ tcl::namespace::eval punk::args {
} }
$t add_row [list $argshow [dict get $arginfo -type] $default $multiple $help] $t add_row [list $argshow [dict get $arginfo -type] $default $multiple $help]
if {$arg eq $badarg} { if {$arg eq $badarg} {
$t configure_row [expr {[$t row_count]-1}] -ansibase $c_badarg $t configure_row [expr {[$t row_count]-1}] -ansibase $A_BADARG
} }
} }
} }
@ -1033,10 +1058,10 @@ tcl::namespace::eval punk::args {
#provide ability to look up and reuse definitions from ids etc #provide ability to look up and reuse definitions from ids etc
# #
proc get_dict_by_id {id {arglist ""}} { proc get_by_id {id {arglist ""}} {
set spec [get_spec $id] set spec [get_spec $id]
if {$spec eq ""} { if {$spec eq ""} {
error "punk::args::get_dict_by_id - no such id: $id" error "punk::args::get_by_id - no such id: $id"
} }
return [get_dict $spec $arglist] return [get_dict $spec $arglist]
} }
@ -1121,7 +1146,7 @@ tcl::namespace::eval punk::args {
} }
set argspecs [Get_argspecs $optionspecs] set argspecs [definition $optionspecs]
tcl::dict::with argspecs {} ;#turn keys into vars tcl::dict::with argspecs {} ;#turn keys into vars
#puts "-arg_info->$arg_info" #puts "-arg_info->$arg_info"
set flagsreceived [list] ;#for checking if required flags satisfied set flagsreceived [list] ;#for checking if required flags satisfied
@ -1132,11 +1157,24 @@ tcl::namespace::eval punk::args {
#todo: -minmultiple -maxmultiple ? #todo: -minmultiple -maxmultiple ?
# -- --- --- ---
# Handle leading positionals
# todo - consider allowing last leading positional to have -multiple 1 but only if there exists an eopts marker later (--) ?
set opts $opt_defaults set opts $opt_defaults
set pre_values {}
dict for {a info} $arg_info {
#todo - flag for possible subhandler - whether leading - or not (shellfilter concept)
if {![string match -* $a]} {
lappend pre_values [lpop rawargs 0]
} else {
break
}
}
#assert - rawargs has been reduced by leading positionals
if {$id ne "jtest"} { if {$id ne "jtest"} {
set arglist {} set arglist {}
set values {} set post_values {}
#val_min, val_max #val_min, val_max
#puts stderr "rawargs: $rawargs" #puts stderr "rawargs: $rawargs"
#puts stderr "arg_info: $arg_info" #puts stderr "arg_info: $arg_info"
@ -1157,7 +1195,7 @@ tcl::namespace::eval punk::args {
if {$remaining_args_including_this <= $val_min} { if {$remaining_args_including_this <= $val_min} {
# if current arg is -- it will pass through as a value here # if current arg is -- it will pass through as a value here
set arglist [lrange $rawargs 0 $i-1] set arglist [lrange $rawargs 0 $i-1]
set values [lrange $rawargs $i end] set post_values [lrange $rawargs $i end]
break break
} }
@ -1169,19 +1207,19 @@ tcl::namespace::eval punk::args {
if {$remaining_args_including_this == $val_max} { if {$remaining_args_including_this == $val_max} {
#assume it's a value. #assume it's a value.
set arglist [lrange $rawargs 0 $i-1] set arglist [lrange $rawargs 0 $i-1]
set values [lrange $rawargs $i end] set post_values [lrange $rawargs $i end]
} else { } else {
#assume it's an end-of-options marker #assume it's an end-of-options marker
lappend flagsreceived -- lappend flagsreceived --
set arglist [lrange $rawargs 0 $i] set arglist [lrange $rawargs 0 $i]
set values [lrange $rawargs $i+1 end] set post_values [lrange $rawargs $i+1 end]
} }
} else { } else {
#unlimited number of values accepted #unlimited number of post_values accepted
#treat this as eopts - we don't care if remainder look like options or not #treat this as eopts - we don't care if remainder look like options or not
lappend flagsreceived -- lappend flagsreceived --
set arglist [lrange $rawargs 0 $i] set arglist [lrange $rawargs 0 $i]
set values [lrange $rawargs $i+1 end] set post_values [lrange $rawargs $i+1 end]
} }
break break
} else { } else {
@ -1194,7 +1232,7 @@ tcl::namespace::eval punk::args {
#if no optvalue following - assume it's a value #if no optvalue following - assume it's a value
#(caller should probably have used -- before it) #(caller should probably have used -- before it)
set arglist [lrange $rawargs 0 $i-1] set arglist [lrange $rawargs 0 $i-1]
set values [lrange $rawargs $i end] set post_values [lrange $rawargs $i end]
break break
} }
@ -1242,7 +1280,7 @@ tcl::namespace::eval punk::args {
#unmatched option in right position to be considered a value - treat like eopts #unmatched option in right position to be considered a value - treat like eopts
#review - document that an unspecified arg within range of possible values will act like eopts -- #review - document that an unspecified arg within range of possible values will act like eopts --
set arglist [lrange $rawargs 0 $i-1] set arglist [lrange $rawargs 0 $i-1]
set values [lrange $rawargs $i end] set post_values [lrange $rawargs $i end]
break break
} }
if {$opt_any} { if {$opt_any} {
@ -1284,12 +1322,13 @@ tcl::namespace::eval punk::args {
} else { } else {
#not flaglike #not flaglike
set arglist [lrange $rawargs 0 $i-1] set arglist [lrange $rawargs 0 $i-1]
set values [lrange $rawargs $i end] set post_values [lrange $rawargs $i end]
break break
} }
} }
set values [list {*}$pre_values {*}$post_values]
} else { } else {
set values $rawargs ;#no -flags detected set values [list {*}$pre_values {*}$rawargs] ;#no -flags detected
set arglist [list] set arglist [list]
} }
#puts stderr "--> arglist: $arglist" #puts stderr "--> arglist: $arglist"

102
src/modules/punk/char-999999.0a1.0.tm

@ -1912,19 +1912,32 @@ tcl::namespace::eval punk::char {
tailcall ansifreestring_width $text tailcall ansifreestring_width $text
} }
#faster than textutil::wcswidth (at least for string up to a few K in length) #todo - consider disallowing/erroring out when \r \n in string?
# - tab/vtab?
# - compare with wcswidth returning -1 for entire string containing such in python,perl
proc wcswidth {string} { proc wcswidth {string} {
#faster than textutil::wcswidth (at least for string up to a few K in length)
#..but - 'scan' is horrible for 400K+
#TODO
set codes [scan $string [tcl::string::repeat %c [tcl::string::length $string]]] set codes [scan $string [tcl::string::repeat %c [tcl::string::length $string]]]
set width 0 set width 0
foreach c $codes { foreach c $codes {
if {$c <= 255} { #unicode Tags block zero width
incr width if {$c < 917504 || $c > 917631} {
} else { if {$c <= 255} {
set w [textutil::wcswidth_char $c] #review - non-printing ascii? why does textutil::wcswidth report 1 ??
if {$w < 0} { #todo - compare with python or other lang wcwidth
return -1 if {!($c < 31 || $c == 127)} {
incr width
}
} else { } else {
#TODO - various other joiners and non-printing chars
set w [textutil::wcswidth_char $c]
if {$w < 0} {
return -1
} else {
incr width $w incr width $w
}
} }
} }
} }
@ -2029,7 +2042,8 @@ tcl::namespace::eval punk::char {
# return [tcl::string::length $text] # return [tcl::string::length $text]
#} #}
if {![regexp "\[\uFF-\U10FFFF\]" $text]} { if {![regexp "\[\uFF-\U10FFFF\]" $text]} {
return [tcl::string::length $text] #return [tcl::string::length $text]
return [punk::char::wcswidth $text] ;#still use our wcswidth to account for non-printable ascii
} }
#split just to get the standalone character widths - and then scan for other combiners (?) - or scan for clusters first? #split just to get the standalone character widths - and then scan for other combiners (?) - or scan for clusters first?
@ -2052,7 +2066,7 @@ tcl::namespace::eval punk::char {
#fall back to textutil::wcswidth (which doesn't for example handle diactricts/combiners so we can't use until these and other things such as \u200b and diacritics are already stripped/accounted for) #fall back to textutil::wcswidth (which doesn't for example handle diactricts/combiners so we can't use until these and other things such as \u200b and diacritics are already stripped/accounted for)
#todo - find something that understands grapheme clusters - needed also for grapheme_split #todo - find something that understands grapheme clusters - needed also for grapheme_split
#use punk::char::wcswidth - faster than the string split in textutil::wcswidth but still uses textutil::wcswidth_char #use punk::char::wcswidth - faster than the string split in textutil::wcswidth but still uses textutil::wcswidth_char
incr len [wcswidth $uc] incr len [punk::char::wcswidth $uc]
} }
#todo - work out what to do with anomalies like grave combiner which print at different lengths on different terminals (fonts?) and for which cursor-query escape sequence lies. #todo - work out what to do with anomalies like grave combiner which print at different lengths on different terminals (fonts?) and for which cursor-query escape sequence lies.
return $len return $len
@ -2229,7 +2243,68 @@ tcl::namespace::eval punk::char {
return [tcl::string::map $map $str] return [tcl::string::map $map $str]
} }
#todo - lookup from unicode tables
variable flags [dict create\
AU \U1F1E6\U1F1FA\
US \U1F1FA\U1F1F8\
ZW \U1F1FF\U1F1FC
]
variable rflags
dict for {k v} $flags {
dict set rflags $v $k
}
proc flag_from_ascii {code} {
variable flags
if {[regexp {^[A-Z]{2}$} $code]} {
if {[dict exists $flags $code]} {
return [dict get $flags $code]
} else {
error "unsupported flags code: $code"
}
} else {
#try as subregion
#e.g gbeng,gbwls,gbsct
return \U1f3f4[tag_from_ascii $code]\Ue007f
}
}
proc flag_to_ascii {charsequence} {
variable rflags
if {[dict exists $rflags $charsequence]} {
return [dict get $rflags $charsequence]
}
if {[string index $charsequence 0] eq "\U1F3F4" && [string index $charsequence end] eq "\UE007F"} {
#subdivision flag
set tag [string range $charsequence 1 end-1]
return [tag_to_ascii $tag]
}
error "unknown flag $charsequence"
}
proc tag_to_ascii {t} {
set fmt [string repeat %c [string length $t]]
set declist [scan $t $fmt]
#unicode Tags block - e0000 to e007f
set declist [lmap dec $declist {
if {$dec < 917504 || $dec > 917631} {
error "char [ansistring VIEW -lf 1 -cr 1 -vt 1 [format %c $dec]] has decimal value $dec. Not in unicode Tags block range 917504-917631 (e0000-e007f)"
}
incr dec -917504
}]
return [format $fmt {*}$declist]
}
proc tag_from_ascii {a} {
set fmt [string repeat %c [string length $a]]
set declist [scan $a $fmt]
set declist [lmap dec $declist {
if {$dec > 127} {
error "char [ansistring VIEW -lf 1 -cr 1 -vt 1 [format %c $dec]] has decimal value $dec. Not in ascii range 0-127"
}
incr dec 917504
}]
return [format $fmt {*}$declist]
}
#split into plaintext and runs of combiners (combining diacritical marks - not ZWJ or ZWJNJ) #split into plaintext and runs of combiners (combining diacritical marks - not ZWJ or ZWJNJ)
proc combiner_split {text} { proc combiner_split {text} {
@ -2250,15 +2325,12 @@ tcl::namespace::eval punk::char {
#puts "->start $start ->match $matchStart $matchEnd" #puts "->start $start ->match $matchStart $matchEnd"
lappend list [tcl::string::range $text $start $matchStart-1] [tcl::string::range $text $matchStart $matchEnd] lappend list [tcl::string::range $text $start $matchStart-1] [tcl::string::range $text $matchStart $matchEnd]
set start [expr {$matchEnd+1}] set start [expr {$matchEnd+1}]
#if {$start >= [tcl::string::length $text]} {
# break
#}
} }
lappend list [tcl::string::range $text $start end] lappend list [tcl::string::range $text $start end]
} }
#ZWJ ZWNJ ? #ZWJ ZWNJ ?
#SWSP ?
#1st shot - basic diacritics #1st shot - basic diacritics
#todo - become aware of unicode grapheme cluster boundaries #todo - become aware of unicode grapheme cluster boundaries
@ -2269,6 +2341,8 @@ tcl::namespace::eval punk::char {
#should also handle the ZWJ (and the variation selectors? eg \uFE0F) character which should account for emoji clusters #should also handle the ZWJ (and the variation selectors? eg \uFE0F) character which should account for emoji clusters
#Note - emoji cluster could be for example 11 code points/41 bytes (family emoji with skin tone modifiers for each member, 3 ZWJs) #Note - emoji cluster could be for example 11 code points/41 bytes (family emoji with skin tone modifiers for each member, 3 ZWJs)
#This still leaves a whole class of clusters.. korean etc unhandled :/ #This still leaves a whole class of clusters.. korean etc unhandled :/
#todo - tk::startOfCluster / tk::endOfCluster - try to get it brought into Tcl
#https://core.tcl-lang.org/tcl/tktview/a4c7eeaf63
proc grapheme_split {text} { proc grapheme_split {text} {
set graphemes [list] set graphemes [list]
set csplits [combiner_split $text] set csplits [combiner_split $text]
@ -2287,7 +2361,7 @@ tcl::namespace::eval punk::char {
set graphemes [list] set graphemes [list]
set csplits [combiner_split $text] set csplits [combiner_split $text]
foreach {pt combiners} [lrange $csplits 0 end-1] { foreach {pt combiners} [lrange $csplits 0 end-1] {
set pt_decs [scan $pt [tcl::string::repeat %c [tcl::string::length $pt]]] set pt_decs [scan $pt [tcl::string::repeat %c [tcl::string::length $pt]]] ;#warning scan %c... slow for v large strings (e.g 400k+)
set combiner_decs [scan $combiners [tcl::string::repeat %c [tcl::string::length $combiners]]] set combiner_decs [scan $combiners [tcl::string::repeat %c [tcl::string::length $combiners]]]
lset pt_decs end [concat [lindex $pt_decs end] $combiner_decs] lset pt_decs end [concat [lindex $pt_decs end] $combiner_decs]
lappend graphemes {*}$pt_decs lappend graphemes {*}$pt_decs

326
src/modules/punk/console-999999.0a1.0.tm

@ -46,9 +46,12 @@
package require Tcl 8.6- package require Tcl 8.6-
package require Thread ;#tsv required to sync is_raw package require Thread ;#tsv required to sync is_raw
package require punk::ansi package require punk::ansi
package require punk::args
#*** !doctools #*** !doctools
#[item] [package {Tcl 8.6-}] #[item] [package {Tcl 8.6-}]
#[item] [package {Thread}]
#[item] [package {punk::ansi}] #[item] [package {punk::ansi}]
#[item] [package {punk::args}]
#*** !doctools #*** !doctools
@ -109,6 +112,8 @@ namespace eval punk::console {
variable ansi_wanted 2 ;#2 for default assumed yes, will be set to -1 for automatically unwanted when ansi unavailable values of 0 or 1 won't be autoset variable ansi_wanted 2 ;#2 for default assumed yes, will be set to -1 for automatically unwanted when ansi unavailable values of 0 or 1 won't be autoset
#punk::console namespace - contains *directly* acting functions - some based on ansi escapes from the 'ansi' sub namespace, some on local system calls or executable calls wrapped in the 'local' sub namespace #punk::console namespace - contains *directly* acting functions - some based on ansi escapes from the 'ansi' sub namespace, some on local system calls or executable calls wrapped in the 'local' sub namespace
#directly acting means they write to stdout to cause the console to peform the action, or they perform the action immediately via other means. #directly acting means they write to stdout to cause the console to peform the action, or they perform the action immediately via other means.
#punk::console::ansi contains a subset of punk::ansi, but with emission to stdout as opposed to simply returning the ansi sequence. #punk::console::ansi contains a subset of punk::ansi, but with emission to stdout as opposed to simply returning the ansi sequence.
@ -255,6 +260,8 @@ namespace eval punk::console {
enable_bracketed_paste enable_bracketed_paste
} }
#todo stop_application_mode {} {}
proc mode {{raw_or_line query}} { proc mode {{raw_or_line query}} {
#variable is_raw #variable is_raw
variable ansi_available variable ansi_available
@ -634,7 +641,7 @@ namespace eval punk::console {
#todo - make timeout configurable? #todo - make timeout configurable?
set waitvarname "::punk::console::ansi_response_wait($callid)" set waitvarname "::punk::console::ansi_response_wait($callid)"
#500ms is generally plenty for a terminal to respond.. but not in some cases. e.g event loop busy with stdin keypress?? review #500ms is generally plenty for a terminal to respond.. but not in some cases. e.g event loop busy with stdin keypress?? review
set timeoutid($callid) [after 2000 [list set $waitvarname timedout]] set timeoutid($callid) [after 1000 [list set $waitvarname timedout]]
#JMN #JMN
# - stderr vs stdout # - stderr vs stdout
@ -1040,6 +1047,64 @@ namespace eval punk::console {
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels] set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload return $payload
} }
variable last_da1_result ""
#TODO - 22? 28? 32?
#1 132 columns
#2 Printer port extension
#4 Sixel extension
#6 Selective erase
#7 DRCS
#8 UDK
#9 NRCS
#12 SCS extension
#15 Technical character set
#18 Windowing capability
#21 Horizontal scrolling
#23 Greek extension
#24 Turkish extension
#42 ISO Latin 2 character set
#44 PCTerm
#45 Soft key map
#46 ASCII emulation
#https://vt100.net/docs/vt510-rm/DA1.html
#
proc get_device_attributes {{inoutchannels {stdin stdout}}} {
#DA1
variable last_da1_result
#first element in result is the terminal's architectural class 61,62,63,64.. ?
#for vt100 we get things like: "ESC\[?1;0c"
#for vt102 "ESC\[?6c"
#set capturingregex {(.*)(\x1b\[\?6[0-9][;]*([;0-9]+)c)$} ;#must capture prefix,entire-response,response-payload
set capturingregex {(.*)(\x1b\[\?([0-9]*[;0-9]+)c)$} ;#must capture prefix,entire-response,response-payload
set request "\x1b\[c"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
set last_da1_result $payload
return $payload
}
#https://vt100.net/docs/vt510-rm/DA2.html
proc get_device_attributes_secondary {{inoutchannels {stdin stdout}}} {
#DA2
set capturingregex {(.*)(\x1b\[\>([0-9]*[;][0-9]*[;][0-1]c))$} ;#must capture prefix,entire-response,response-payload
#expect CSI > X;10|20;0|1c - docs suggest X should be something like 61. In practice we see 0 or 1 (windows) REVIEW
set request "\x1b\[>c"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload
}
proc get_device_attributes_tertiary {{inoutchannels {stdin stdout}}} {
#DA3
set capturingregex {(.*)(\x1bP!\|([0-9]{8})\x1b\\)$} ;#must capture prefix,entire-response,response-payload
set request "\x1b\[=c"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload
}
proc get_terminal_id {{inoutchannels {stdin stdout}}} {
#DA3 - alias
get_device_attributes_tertiary $inoutchannels
}
proc get_tabstops {{inoutchannels {stdin stdout}}} { proc get_tabstops {{inoutchannels {stdin stdout}}} {
#DECTABSR \x1b\[2\$w #DECTABSR \x1b\[2\$w
#response example " ^[P2$u9/17/25/33/41/49/57/65/73/81^[\ " (where ^[ is \x1b) #response example " ^[P2$u9/17/25/33/41/49/57/65/73/81^[\ " (where ^[ is \x1b)
@ -1110,6 +1175,55 @@ namespace eval punk::console {
return [split [get_cursor_pos $inoutchannels] ";"] return [split [get_cursor_pos $inoutchannels] ";"]
} }
#todo - work out how to query terminal and set cell size in pixels
#for now use the windows default
variable cell_size
set cell_size ""
set cell_size_fallback 10x20
#todo - change -inoutchannels to -terminalobject with prebuilt default
punk::args::definition {
*id punk::console::cell_size
-inoutchannels -default {stdin stdout} -type list
*values -min 0 -max 1
newsize -default ""
}
proc cell_size {args} {
set argd [punk::args::get_by_id punk::console::cell_size $args]
set inoutchannels [dict get $argd opts -inoutchannels]
set newsize [dict get $argd values newsize]
variable cell_size
if {$newsize eq ""} {
#query existing setting
if {$cell_size eq ""} {
#not set - try to query terminal's overall dimensions
set pixeldict [punk::console::get_xterm_pixels $inoutchannels]
lassign $pixeldict _w sw _h sh
if {[string is integer -strict $sw] && [string is integer -strict $sh]} {
lassign [punk::console::get_size] _cols columns _rows rows
#review - is returned size in pixels always a multiple of rows and cols?
set w [expr {$sw / $columns}]
set h [expr {$sh / $rows}]
set cell_size ${w}x${h}
return $cell_size
} else {
set cell_size $::punk::console::cell_size_fallback
puts stderr "punk::console::cell_size unable to query terminal for pixel data - using default $cell_size"
return $cell_size
}
}
return $cell_size
}
#newsize supplied - try to set
lassign [split [string tolower $newsize] x] w h
if {![string is integer -strict $w] || ![string is integer -strict $h] || $w < 1 || $h < 1} {
error "punk::sixel::cell_size error - expected format WxH where W and H are positive integers - got '$newsize'"
}
set cell_size ${w}x${h}
}
#todo - determine cursor on/off state before the call to restore properly. #todo - determine cursor on/off state before the call to restore properly.
proc get_size {{inoutchannels {stdin stdout}}} { proc get_size {{inoutchannels {stdin stdout}}} {
lassign $inoutchannels in out lassign $inoutchannels in out
@ -1202,13 +1316,19 @@ namespace eval punk::console {
lassign [split $payload {;}] rows cols lassign [split $payload {;}] rows cols
return [list columns $cols rows $rows] return [list columns $cols rows $rows]
} }
proc get_xterm_pixels {{inoutchannels {stdin stdout}}} {
set capturingregex {(.*)(\x1b\[4;([0-9]+;[0-9]+)t)$} ;#must capture prefix,entire-response,response-payload
set request "\x1b\[14t"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
lassign [split $payload {;}] height width
return [list width $width height $height]
}
proc get_mode_line_wrap {{inoutchannels {stdin stdout}}} { proc get_mode_line_wrap {{inoutchannels {stdin stdout}}} {
set capturingregex {(.*)(\x1b\[\?7;([0-9]+)\$y)$} ;#must capture prefix,entire-response,response-payload set capturingregex {(.*)(\x1b\[\?7;([0-9]+)\$y)$} ;#must capture prefix,entire-response,response-payload
set request "\x1b\[?7\$p" set request "\x1b\[?7\$p"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels] set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload return $payload
} }
#Terminals generally default to LNM being reset (off) ie enter key sends a lone <cr> #Terminals generally default to LNM being reset (off) ie enter key sends a lone <cr>
#Terminals tested on windows either don't respond to this query, or respond with 0 (meaning mode not understood) #Terminals tested on windows either don't respond to this query, or respond with 0 (meaning mode not understood)
#I presume from this that almost nobody is using LNM 1 (which sends both <cr> and <lf>) #I presume from this that almost nobody is using LNM 1 (which sends both <cr> and <lf>)
@ -1218,11 +1338,59 @@ namespace eval punk::console {
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels] set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload return $payload
} }
#DECRPM responses e.g:
# \x1b\[?7\;1\$y
# \x1b\[?7\;2\$y
#where 1 = set, 2 = unset. (0 = mode not recognised, 3 = permanently set, 4 = permanently unset)
proc get_mode {num_or_name {inoutchannels {stdin stdout}}} {
if {[string is integer -strict $num_or_name]} {
set m $num_or_name
} else {
upvar ::punk::ansi::decmode_names decmode_names
if {[dict exists $decmode_names $num_or_name]} {
set m [dict get $decmode_names $num_or_name]
} else {
error "punk::console::get_mode unrecognised mode '$num_or_name'. Known mode names: [dict keys $decmode_names]"
}
}
set capturingregex [string map [list %MODE% $m] {(.*)(\x1b\[\?%MODE%;([0-9]+)\$y)$}] ;#must capture prefix,entire-response,response-payload
set request "\x1b\[?$m\$p"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload
}
proc set_mode {num_or_name {inoutchannels {stdin stdout}}} {
if {[string is integer -strict $num_or_name]} {
set m $num_or_name
} else {
upvar ::punk::ansi::decmode_names decmode_names
if {[dict exists $decmode_names $num_or_name]} {
set m [dict get $decmode_names $num_or_name]
} else {
error "punk::console::set_mode unrecognised mode '$num_or_name'. Known mode names: [dict keys $decmode_names]"
}
}
return "\x1b\[?${m}h"
}
proc unset_mode {num_or_name {inoutchannels {stdin stdout}}} {
if {[string is integer -strict $num_or_name]} {
set m $num_or_name
} else {
upvar ::punk::ansi::decmode_names decmode_names
if {[dict exists $decmode_names $num_or_name]} {
set m [dict get $decmode_names $num_or_name]
} else {
error "punk::console::unset_mode unrecognised mode '$num_or_name'. Known mode names: [dict keys $decmode_names]"
}
}
return "\x1b\[?${m}l"
}
#terminals lie. This should be a reasonable (albeit relatively slow) test of actual width - but some terminals seem to miscalculate. #terminals lie. This should be a reasonable (albeit relatively slow) test of actual width - but some terminals seem to miscalculate.
#todo - a visual interactive test/questionnaire to ask user if things are lining up or if the terminal is telling fibs about cursor position. #todo - a visual interactive test/questionnaire to ask user if things are lining up or if the terminal is telling fibs about cursor position.
#todo - determine if these anomalies are independent of font #todo - determine if these anomalies are independent of font
#punk::ansi should be able to glean widths from unicode data files - but this may be incomplete - todo - compare with what terminal actually does. #punk::ansi should be able to glean widths from unicode data files - but this may be incomplete - todo - compare with what terminal actually does.
#review - vertical movements (e.g /n /v will cause emit 0 to be ineffective - todo - disallow?)
proc test_char_width {char_or_string {emit 0}} { proc test_char_width {char_or_string {emit 0}} {
#return 1 #return 1
#JMN #JMN
@ -1266,6 +1434,57 @@ namespace eval punk::console {
return [expr {$col2 - $col1}] return [expr {$col2 - $col1}]
} }
#get reported cursor position after emitting teststring.
#The row is more likely to be a lie than the column
#With wrapping on we should be able to test if the terminal has an inconsistency between reported width and when it actually wraps.
#(but as line wrapping generally occurs based on width - we probably won't see this - just 'apparently' early wrapping due to printing mismatch with width)
#unfortunately if terminal reports something like \u200B as width 1, but doesn't print it - we can't tell. (vs reporting 1 wide and printing replacement char/space)
#When cursor is already at bottom of screen, scrolling will occur so rowoffset will be zero
#we either need to move cursor up before test - or use alt screen ( or scroll_up then scroll_down?)
#for now we will use alt screen to reduce scrolling effects - REVIEW
proc test_string_cursor {teststring {emit 0}} {
variable ansi_available
if {!$ansi_available} {
puts stderr "No ansi - cannot test char_width of '$teststring' returning [string length $test_string]"
return [string length $teststring]
}
punk::console::enable_alt_screen
punk::console::move 0 0
if {!$emit} {
puts -nonewline stdout \033\[2K\033\[1G ;#2K erase line, 1G cursor at col1
}
set response ""
if {[catch {
set response [punk::console::get_cursor_pos]
} errM]} {
puts stderr "Cannot test_string_cursor for '[punk::ansi::ansistring VIEW $teststring]' - may be no console? Error message from get_cursor_pos: $errM"
return
}
lassign [split $response ";"] row1 col1
if {![string length $response] || ![string is integer -strict $col1] || ![string is integer -strict $row1]} {
puts stderr "test_string_cursor Could not interpret response from get_cursor_pos for initial cursor pos. Response: '[punk::ansi::ansistring VIEW $response]'"
flush stderr
return
}
puts -nonewline stdout $teststring
flush stdout
set response [punk::console::get_cursor_pos]
lassign [split $response ";"] row2 col2
if {![string is integer -strict $col2] || ![string is integer -strict $row2]} {
puts stderr "test_string_cursor could not interpret response from get_cursor_pos for post-emit cursor pos. Response:'[punk::ansi::ansistring VIEW $response]'"
flush stderr
return
}
if {!$emit} {
puts -nonewline stdout \033\[2K\033\[1G
}
flush stdout;#if we don't flush - a subsequent stderr write could move the cursor to a newline and interfere with our 2K1G erasure and cursor repositioning.
punk::console::disable_alt_screen
return [list rowoffset [expr {$col2 - $col1}] columnoffset [expr {$row2 - $row1}]]
}
#todo! - improve ideally we want to use VT sequences to determine - and make a separate utility for testing via systemcalls/os api #todo! - improve ideally we want to use VT sequences to determine - and make a separate utility for testing via systemcalls/os api
proc test_can_ansi {} { proc test_can_ansi {} {
#don't set ansi_avaliable here - we want to be able to change things, retest etc. #don't set ansi_avaliable here - we want to be able to change things, retest etc.
@ -1306,8 +1525,59 @@ namespace eval punk::console {
if {!$ansi_available} { if {!$ansi_available} {
return 0 return 0
} }
set ansi_available [test_can_ansi] #ansi_available defaults to -1 (unknown)
return [expr {$ansi_available}] if {$ansi_available == -1} {
set ansi_available [test_can_ansi]
return $ansi_available
}
return 1
}
variable grapheme_cluster_support [dict create] ;#default empty dict for unknown/untested
#todo - flag to retest? (for consoles where grapheme cluster support can be disabled e.g via decmode 2027)
proc grapheme_cluster_support {} {
variable grapheme_cluster_support
if {[dict size $grapheme_cluster_support]} {
return $grapheme_cluster_support
}
if {[info exists ::env(TERM_PROGRAM)]} {
#terminals known to support grapheme clusters, but unable to respond to decmode request 2027
#wezterm (on windows as at 2024-12 decmode 2027 doesn't work)
#REVIEW - what if terminal is remote wezterm? can/will this env variable
# iterm and apple terminal also set TERM_PROGRAM
if {[string tolower $::env(TERM_PROGRAM)] in [list wezterm]} {
set is_available 1
return [dict create available 1 mode set]
}
}
#where 1 = set, 2 = unset. (0 = mode not recognised, 3 = permanently set, 4 = permanently unset)
set state [get_mode grapheme_clusters] ;#decmode 2027 extension
set is_available 0
switch -- $state {
0 {
set m unsupported ;# the dec query is unsupported - but it's possible the terminal still has grapheme support
}
1 {
set m set
set is_available 1
}
2 {
set m unset
}
3 {
set m permanently_set
set is_available 1
}
4 {
set m permanently_unset
}
default {
set m "BAD_RESPONSE"
}
}
return [dict create available $is_available mode $m]
} }
namespace eval ansi { namespace eval ansi {
@ -1432,7 +1702,7 @@ namespace eval punk::console {
puts -nonewline stdout [punk::ansi::move_column $col] puts -nonewline stdout [punk::ansi::move_column $col]
} }
proc move_row {row} { proc move_row {row} {
puts -nonewline stdout [punk::ansi::move_row $col] puts -nonewline stdout [punk::ansi::move_row $row]
} }
proc move_emit {row col data args} { proc move_emit {row col data args} {
puts -nonewline stdout [punk::ansi::move_emit $row $col $data {*}$args] puts -nonewline stdout [punk::ansi::move_emit $row $col $data {*}$args]
@ -1912,8 +2182,52 @@ namespace eval punk::console {
#[list_end] [comment {--- end definitions namespace punk::console ---}] #[list_end] [comment {--- end definitions namespace punk::console ---}]
} }
namespace eval punk::console::check {
variable has_bug_legacysymbolwidth -1 ;#undetermined
proc has_bug_legacysymbolwidth {} {
#some terminals (on windows as at 2024) miscount width of these single-width blocks internally
#resulting in extra spacing that is sometimes well removed from the character itself (e.g at next ansi reset)
#This was fixed in windows-terminal based systems (2021) but persists in others.
#https://github.com/microsoft/terminal/issues/11694
variable has_bug_legacysymbolwidth
if {!$has_bug_legacysymbolwidth} {
return 0
}
if {$has_bug_legacysymbolwidth == -1} {
#run the test using ansi movement
#we only test a specific character from the known problematic set
set w [punk::console::test_char_width \U1fb7d]
if {$w == 1} {
set has_bug_legacysymbolwidth 0
} else {
#can return 2 on legacy window consoles for example
set has_bug_legacysymbolwidth 1
}
return $has_bug_legacysymbolwidth
}
return 1
}
variable has_bug_zwsp -1 ;#undetermined
proc has_bug_zwsp {} {
#Note that some terminals behave differently regarding a leading zwsp vs one that is inline between other chars.
#we are only testing the inline behaviour here.
variable has_bug_zwsp
if {!$has_bug_zwsp} {
return 0
}
if {$has_bug_zwsp == -1} {
set w [punk::console::test_char_width X\u200bY]
}
if {$w == 2} {
return 0
} else {
#may return 3 - but this gives no indication of whether terminal hides it or not.
return 1
}
return 1
}
}

41
src/modules/punk/lib-999999.0a1.0.tm

@ -63,38 +63,6 @@ package require Tcl 8.6-
#*** !doctools #*** !doctools
#[section API] #[section API]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# oo::class namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#tcl::namespace::eval punk::lib::class {
# #*** !doctools
# #[subsection {Namespace punk::lib::class}]
# #[para] class definitions
# if {[info commands [tcl::namespace::current]::interface_sample1] eq ""} {
# #*** !doctools
# #[list_begin enumerated]
#
# # oo::class create interface_sample1 {
# # #*** !doctools
# # #[enum] CLASS [class interface_sample1]
# # #[list_begin definitions]
#
# # method test {arg1} {
# # #*** !doctools
# # #[call class::interface_sample1 [method test] [arg arg1]]
# # #[para] test method
# # puts "test: $arg1"
# # }
#
# # #*** !doctools
# # #[list_end] [comment {-- end definitions interface_sample1}]
# # }
#
# #*** !doctools
# #[list_end] [comment {--- end class enumeration ---}]
# }
#}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::lib::ensemble { tcl::namespace::eval punk::lib::ensemble {
#wiki.tcl-lang.org/page/ensemble+extend #wiki.tcl-lang.org/page/ensemble+extend
@ -172,7 +140,10 @@ tcl::namespace::eval punk::lib::check {
proc has_tclbug_lsearch_strideallinline {} { proc has_tclbug_lsearch_strideallinline {} {
#bug only occurs with single -index value combined with -stride -all -inline -subindices #bug only occurs with single -index value combined with -stride -all -inline -subindices
#https://core.tcl-lang.org/tcl/tktview/5a1aaa201d #https://core.tcl-lang.org/tcl/tktview/5a1aaa201d
set result [lsearch -stride 3 -all -inline -index 1 -subindices {a1 a2 a3} *] if {[catch {[lsearch -stride 3 -all -inline -index 1 -subindices {a1 a2 a3} *]} result]} {
#we aren't looking for an error result - error most likely indicates tcl too old to support -stride
return 0
}
return [expr {$result ne "a2"}] return [expr {$result ne "a2"}]
} }
@ -2575,12 +2546,12 @@ namespace eval punk::lib {
while {$j <= $max} { while {$j <= $max} {
if {$x % $j == 0} { if {$x % $j == 0} {
set other [expr {$x / $j}] set other [expr {$x / $j}]
if {$other % 2 != 0} { if {$other % 2} {
if {$other ni $factors} { if {$other ni $factors} {
lappend factors $other lappend factors $other
} }
} }
if {$j % 2 != 0} { if {$j % 2} {
if {$j ni $factors} { if {$j ni $factors} {
lappend factors $j lappend factors $j
} }

2
src/modules/punk/mix/base-0.1.tm

@ -869,7 +869,7 @@ namespace eval punk::mix::base {
#todo - write tests #todo - write tests
if {([llength $args] % 2) != 0} { if {[llength $args] % 2} {
error "get_relativecksum_from_base error. args supplied must be in the form of key-value pairs. received '$args' " error "get_relativecksum_from_base error. args supplied must be in the form of key-value pairs. received '$args' "
} }
if {[dict exists $args cksum]} { if {[dict exists $args cksum]} {

2
src/modules/punk/mix/templates/layouts/project/src/make.tcl

@ -424,7 +424,7 @@ if {$::punkmake::command eq "bootsupport"} {
} }
} }
if {[llength $bootsupport_module_folders] % 2 != 0} { if {[llength $bootsupport_module_folders] % 2} {
#todo - change include_modules.config structure to be line based? we have no way of verifying paired entries because we accept a flat list #todo - change include_modules.config structure to be line based? we have no way of verifying paired entries because we accept a flat list
puts stderr "WARNING - Skipping bootsupport_module_folders - list should be a list of base subpath pairs" puts stderr "WARNING - Skipping bootsupport_module_folders - list should be a list of base subpath pairs"
} else { } else {

2
src/modules/punk/mix/util-999999.0a1.0.tm

@ -51,7 +51,7 @@ namespace eval punk::mix::util {
} else { } else {
if {$ival in $knownopts} { if {$ival in $knownopts} {
#puts ">known at $i : [lindex $args $i]" #puts ">known at $i : [lindex $args $i]"
if {($i % 2) != 0} { if {$i % 2} {
error "unexpected option at index $i. known options: $knownopts must come in -opt val pairs." error "unexpected option at index $i. known options: $knownopts must come in -opt val pairs."
} }
incr i incr i

6
src/modules/punk/nav/fs-999999.0a1.0.tm

@ -1,6 +1,6 @@
# -*- tcl -*- # -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'pmix make' or bin/punkmake to update from <pkg>-buildversion.txt # Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm # module template: punkshell/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm
# #
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. # Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. # Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
@ -18,7 +18,7 @@
# doctools header # doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools #*** !doctools
#[manpage_begin shellspy_module_punk::nav::fs 0 999999.0a1.0] #[manpage_begin punkshell_module_punk::nav::fs 0 999999.0a1.0]
#[copyright "2024"] #[copyright "2024"]
#[titledesc {punk::nav::fs console filesystem navigation}] [comment {-- Name section and table of contents description --}] #[titledesc {punk::nav::fs console filesystem navigation}] [comment {-- Name section and table of contents description --}]
#[moddesc {fs nav}] [comment {-- Description at end of page heading --}] #[moddesc {fs nav}] [comment {-- Description at end of page heading --}]

2
src/modules/punk/packagepreference-999999.0a1.0.tm

@ -1,6 +1,6 @@
# -*- tcl -*- # -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt # Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm # module template: punkshell/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm
# #
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. # Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. # Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.

30
src/modules/punk/repl-0.1.tm

@ -2675,9 +2675,11 @@ namespace eval repl {
set new_state [thread::send %replthread% [list punk::console::colour {*}$args]] set new_state [thread::send %replthread% [list punk::console::colour {*}$args]]
if {[expr {$new_state}] ne [expr {$colour_state}]} { if {[expr {$new_state}] ne [expr {$colour_state}]} {
interp eval code [list punk::console::colour {*}$args] ;#align code interp's view of colour state with repl thread interp eval code [list punk::console::colour {*}$args] ;#align code interp's view of colour state with repl thread
interp eval code [string map [list <cstate> $new_state] {
#we don't want to run a raw script directly in our code interp if we're using variables
#because we will potentially collide with user vars in that context (or create vars there) - so use apply
interp eval code [list apply {docolour {
#adjust channel transform stack #adjust channel transform stack
set docolour [expr {<cstate>}]
if {!$docolour} { if {!$docolour} {
set s [lindex $::codeinterp::outstack end] set s [lindex $::codeinterp::outstack end]
if {$s ne ""} { if {$s ne ""} {
@ -2697,7 +2699,7 @@ namespace eval repl {
} }
} }
}] }} $new_state]
} }
return $new_state return $new_state
} else { } else {
@ -2948,13 +2950,21 @@ namespace eval repl {
package require punk package require punk
package require shellrun package require shellrun
package require shellfilter package require shellfilter
set running_config $::punk::config::running #set running_config $::punk::config::running
if {[string length [dict get $running_config color_stderr]] && [punk::console::colour]} { #if {[string length [dict get $running_config color_stderr]] && [punk::console::colour]} {
lappend ::codeinterp::errstack [shellfilter::stack::add stderr ansiwrap -settings [list -colour [dict get $running_config color_stderr]]] # lappend ::codeinterp::errstack [shellfilter::stack::add stderr ansiwrap -settings [list -colour [dict get $running_config color_stderr]]]
} #}
if {[string length [dict get $running_config color_stdout]] && [punk::console::colour]} { #if {[string length [dict get $running_config color_stdout]] && [punk::console::colour]} {
lappend ::codeinterp::outstack [shellfilter::stack::add stdout ansiwrap -settings [list -colour [dict get $running_config color_stdout]]] # lappend ::codeinterp::outstack [shellfilter::stack::add stdout ansiwrap -settings [list -colour [dict get $running_config color_stdout]]]
} #}
apply {running_config {
if {[string length [dict get $running_config color_stderr]] && [punk::console::colour]} {
lappend ::codeinterp::errstack [shellfilter::stack::add stderr ansiwrap -settings [list -colour [dict get $running_config color_stderr]]]
}
if {[string length [dict get $running_config color_stdout]] && [punk::console::colour]} {
lappend ::codeinterp::outstack [shellfilter::stack::add stdout ansiwrap -settings [list -colour [dict get $running_config color_stdout]]]
}
}} $::punk::config::running
package require textblock package require textblock
} errM]} { } errM]} {

30
src/modules/punk/repl/codethread-999999.0a1.0.tm

@ -1,6 +1,6 @@
# -*- tcl -*- # -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'pmix make' or bin/punkmake to update from <pkg>-buildversion.txt # Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.2.tm # module template: punkshell/src/decktemplates/vendor/punk/modules/template_module-0.0.2.tm
# #
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. # Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. # Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
@ -18,7 +18,7 @@
# doctools header # doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools #*** !doctools
#[manpage_begin shellspy_module_punk::repl::codethread 0 999999.0a1.0] #[manpage_begin punkshell_module_punk::repl::codethread 0 999999.0a1.0]
#[copyright "2024"] #[copyright "2024"]
#[titledesc {Module repl codethread}] [comment {-- Name section and table of contents description --}] #[titledesc {Module repl codethread}] [comment {-- Name section and table of contents description --}]
#[moddesc {codethread for repl - root interpreter}] [comment {-- Description at end of page heading --}] #[moddesc {codethread for repl - root interpreter}] [comment {-- Description at end of page heading --}]
@ -66,10 +66,13 @@ package require punk::config
# oo::class namespace # oo::class namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#tcl::namespace::eval punk::repl::codethread::class { #tcl::namespace::eval punk::repl::codethread::class {
#*** !doctools #*** !doctools
#[subsection {Namespace punk::repl::codethread::class}] #[subsection {Namespace punk::repl::codethread::class}]
#[para] class definitions #[para] class definitions
#if {[info commands [tcl::namespace::current]::interface_sample1] eq ""} { #if {[info commands [tcl::namespace::current]::interface_sample1] eq ""} {
#*** !doctools #*** !doctools
#[list_begin enumerated] #[list_begin enumerated]
@ -91,6 +94,7 @@ package require punk::config
#*** !doctools #*** !doctools
#[list_end] [comment {--- end class enumeration ---}] #[list_end] [comment {--- end class enumeration ---}]
#} #}
#} #}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
@ -141,8 +145,9 @@ tcl::namespace::eval punk::repl::codethread {
#puts stderr "->runscript" #puts stderr "->runscript"
variable replthread_cond variable replthread_cond
variable output_stdout "" #variable output_stdout ""
variable output_stderr "" #variable output_stderr ""
#expecting to be called from a thread::send in parent repl - ie in the toplevel interp so that the sub-interp "code" is available #expecting to be called from a thread::send in parent repl - ie in the toplevel interp so that the sub-interp "code" is available
#if a thread::send is done from the commandline in a codethread - Tcl will #if a thread::send is done from the commandline in a codethread - Tcl will
if {"code" ni [interp children] || ![info exists replthread_cond]} { if {"code" ni [interp children] || ![info exists replthread_cond]} {
@ -154,6 +159,9 @@ tcl::namespace::eval punk::repl::codethread {
puts stderr "runscript is meant to be called from the parent repl thread via a thread::send to the codethread" puts stderr "runscript is meant to be called from the parent repl thread via a thread::send to the codethread"
return return
} }
interp eval code [list set ::punk::repl::codethread::output_stdout ""]
interp eval code [list set ::punk::repl::codethread::output_stderr ""]
set outstack [list] set outstack [list]
set errstack [list] set errstack [list]
upvar ::punk::config::running running_config upvar ::punk::config::running running_config
@ -192,8 +200,16 @@ tcl::namespace::eval punk::repl::codethread {
#interp transfer code $errhandle "" #interp transfer code $errhandle ""
#flush $errhandle #flush $errhandle
set lastoutchar [string index [punk::ansi::ansistrip [interp eval code set ::punk::repl::codethread::output_stdout]] end] #set lastoutchar [string index [punk::ansi::ansistrip [interp eval code set ::punk::repl::codethread::output_stdout]] end]
set lasterrchar [string index [punk::ansi::ansistrip [interp eval code set ::punk::repl::codethread::output_stderr]] end] #set lastoutchar [string index [punk::ansi::ansistrip [interp eval code [list set ::punk::repl::codethread::output_stdout]]] end]
set lastoutpart [interp eval code {string range $::punk::repl::codethread::output_stdout end-100 end}]
#note we could be in a *large* ansi segment such as sixel data
#review - why do we need to ansistrip?
set lastoutchar [string index [punk::ansi::ansistrip $lastoutpart] end]
#set lasterrchar [string index [punk::ansi::ansistrip [interp eval code set ::punk::repl::codethread::output_stderr]] end]
set lasterrpart [interp eval code {string range $::punk::repl::codethread::output_stderr end-100 end}]
set lasterrchar [string index [punk::ansi::ansistrip $lasterrpart] end]
#puts stderr "-->[ansistring VIEW -lf 1 $lastoutchar$lasterrchar]" #puts stderr "-->[ansistring VIEW -lf 1 $lastoutchar$lasterrchar]"
set tid [thread::id] set tid [thread::id]

2
src/modules/punk/repl/codethread-buildversion.txt

@ -1,3 +1,3 @@
0.1.0 0.1.1
#First line must be a semantic version number #First line must be a semantic version number
#all other lines are ignored. #all other lines are ignored.

2
src/modules/punk/repo-999999.0a1.0.tm

@ -370,7 +370,7 @@ namespace eval punk::repo {
set opt_repotypes [dict get $opts -repotypes] set opt_repotypes [dict get $opts -repotypes]
set opt_repopaths [dict get $opts -repopaths] set opt_repopaths [dict get $opts -repopaths]
if {"$opt_repopaths" ne ""} { if {"$opt_repopaths" ne ""} {
if {([llength $opt_repopaths] % 2 != 0) || ![dict exists $opt_repopaths closest]} { if {([llength $opt_repopaths] % 2) || ![dict exists $opt_repopaths closest]} {
error "workingdir_state error: -repopaths argument invalid. Expected a dict as retrieved using punk::repo::find_repos" error "workingdir_state error: -repopaths argument invalid. Expected a dict as retrieved using punk::repo::find_repos"
} }
set repopaths $opt_repopaths set repopaths $opt_repopaths

322
src/modules/punk/sixel-999999.0a1.0.tm

@ -0,0 +1,322 @@
# -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm
#
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# (C) 2024
#
# @@ Meta Begin
# Application punk::sixel 999999.0a1.0
# Meta platform tcl
# Meta license <unspecified>
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[manpage_begin punkshell_module_punk::sixel 0 999999.0a1.0]
#[copyright "2024"]
#[titledesc {punk::sixel API}] [comment {-- Name section and table of contents description --}]
#[moddesc {experimental sixel functions}] [comment {-- Description at end of page heading --}]
#[require punk::sixel]
#[keywords module experimental]
#[description]
#[para] Experimental support functions for working with sixel data
#[para] For real sixel work a version written in a systems language such as c or zig may be required.
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Overview]
#[para] overview of punk::sixel
#[subsection Concepts]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection dependencies]
#[para] packages used by punk::sixel
#[list_begin itemized]
package require Tcl 8.6-
package require punk::args
package require punk::console
package require punk::ansi
#*** !doctools
#[item] [package {Tcl 8.6}]
#[item] [package {punk::args}]
#[item] [package {punk::console}]
#[item] [package {punk::ansi}]
# #package require frobz
# #*** !doctools
# #[item] [package {frobz}]
#*** !doctools
#[list_end]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section API]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# oo::class namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#tcl::namespace::eval punk::sixel::class {
#*** !doctools
#[subsection {Namespace punk::sixel::class}]
#[para] class definitions
#if {[tcl::info::commands [tcl::namespace::current]::interface_sample1] eq ""} {
#*** !doctools
#[list_begin enumerated]
# oo::class create interface_sample1 {
# #*** !doctools
# #[enum] CLASS [class interface_sample1]
# #[list_begin definitions]
# method test {arg1} {
# #*** !doctools
# #[call class::interface_sample1 [method test] [arg arg1]]
# #[para] test method
# puts "test: $arg1"
# }
# #*** !doctools
# #[list_end] [comment {-- end definitions interface_sample1}]
# }
#*** !doctools
#[list_end] [comment {--- end class enumeration ---}]
#}
#}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#reading
#https://www.reddit.com/r/linux/comments/t3m7zm/quick_roundup_of_bitmap_graphics_availability_in/
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Base namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::sixel {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
#variable xyz
set sixelstart_re {\x1bP([;0-9]*)q} ;#7-bit - todo 8bit
#*** !doctools
#[subsection {Namespace punk::sixel}]
#[para] Core API functions for punk::sixel
#[list_begin definitions]
#proc sample1 {p1 n args} {
# #*** !doctools
# #[call [fun sample1] [arg p1] [arg n] [opt {option value...}]]
# #[para]Description of sample1
# #[para] Arguments:
# # [list_begin arguments]
# # [arg_def tring p1] A description of string argument p1.
# # [arg_def integer n] A description of integer argument n.
# # [list_end]
# return "ok"
#}
#terminated by ST
#some older terminals may terminate at first other esc encountered.
#non-sixel characters ignored (? review)
#we will for now consume all to final ST
#TODO - sixel row/col info is dependent on terminal - pass in -terminalobject or -inoutchannels (for use with punk::console::cell_size)
punk::args::definition {
*id punk::sixel::get_info
-cache -default 1 -type boolean -help\
"Cached result based on sha1 hash."
-cell_size -default "" -help\
"override terminal cell_size.
If left empty, attempt to use value from querying terminal."
*values -min 1 -max 1
sixelstring -type string -help "A single sixel image - currently only 7-bit supported"
}
variable sixelinfo_cache
set sixelinfo_cache [dict create]
proc get_info {args} {
set argd [punk::args::get_by_id punk::sixel::get_info $args]
set sixelstring [dict get $argd values sixelstring]
set do_cache [dict get $argd opts -cache]
set cell_size_override [dict get $argd opts -cell_size]
if {$do_cache} {
if {[catch {package require sha1}]} {
set do_cache 0
}
}
if {$do_cache} {
variable sixelinfo_cache
set cacheid ${cell_size_override}_[sha1::sha1 $sixelstring]
if {[dict exists $sixelinfo_cache $cacheid]} {
return [dict get $sixelinfo_cache $cacheid]
}
}
#relatively slow because a) we parse each sixel line in case it is ragged width b) should probably be written in a systems language or be a library call
set raster_lines [split $sixelstring -]
set height_pixels [expr {[llength $raster_lines] * 6}]
if {$cell_size_override ne ""} {
lassign [split [string tolower $cell_size_override] x] cwidth cheight
if {![string is integer -strict $cwidth] || ![string is integer -strict $cheight]} {
error "punk::sixel::get_info -cell_sixe must be of the form WxH where W and H are positive integers"
}
set cell_size $cell_size_override
} else {
set cell_size [punk::console::cell_size]
}
lassign [split $cell_size x] cwidth cheight
set height_cells [expr {int(ceil($height_pixels /double($cheight)))}]
set sixelparams ""
set sixel_extents [list] ;#number of sixes in each line taking into account retraces due to $
set line0 [lindex $raster_lines 0]
if {[regexp -indices {^\x1bP([;0-9]*)q} $line0 i_match]} {
#todo - 8bit
#set params [string range $line0 {*}$i_params] ;#may be empty
set linedata [string range $line0 [lindex $i_match 1]+1 end]
if {[string index $linedata 0] eq {"}} {
if {[regexp -indices {\"([;0-9]*)} $linedata i_match]} {
#i_params is raster info (todo?)
set linedata [string range $linedata [lindex $i_match 1]+1 end]
} else {
#lone quote?
set linedata [string range $linedata 1 end]
}
}
lset raster_lines 0 $linedata
} else {
error "punk::sixel::get_info failed to recognise first line as a sixel string"
}
foreach linedata $raster_lines {
set line_sixelrun 0
set line_sixelrun_max 0 ;#max encountered for this line
for {set s 0} {$s < [string length $linedata]} {incr s} {
#set cdec [scan [string index $linedata $s] %c] ;#scanning each char and switching on cdec is slower
set c [string index $linedata $s]
switch -- $c {
{#} {
#colour palette select or set
if {[regexp -start $s -indices {#([;0-9]*)} $linedata i_match]} {
#set colour_info [string range $linedata {*}$i_colour]
set s [lindex $i_match 1]
}
#if no number following.. ignore
}
{$} {
#retrace
set line_sixelrun_max [expr {max($line_sixelrun_max,$line_sixelrun)}]
set line_sixelrun 0
}
{!} {
#repeat #<num><char>
set repeat [regexp -inline -start $s+1 {[0-9]*} $linedata]
if {[string is integer -strict $repeat] && $repeat >= 0} {
incr s [expr {[string length $repeat]+1}] ;#add one for the repeated sixel char
incr line_sixelrun $repeat
}
}
default {
#don't use escape in switch selector - ensures jump table is used.
if {$c eq "\x1b"} {
if {[string index $linedata $s+1] eq "\\"} {
#7bit ST
break
}
} else {
incr line_sixelrun
}
}
}
}
lappend sixel_extents [expr {max($line_sixelrun_max,$line_sixelrun)}]
}
set width_pixels [tcl::mathfunc::max 0 {*}$sixel_extents]
set width_cells [expr {int(ceil($width_pixels/double($cwidth)))}]
set result [dict create rasterlines [llength $raster_lines] columns $width_cells rows $height_cells cell_size $cell_size width_pixels $width_pixels height_pixels $height_pixels sixel_extents $sixel_extents]
#return [dict create rasterlines [llength $raster_lines] columns $width_cells rows $height_cells width_pixels $width_pixels height_pixels $height_pixels]
if {$do_cache} {
dict set sixelinfo_cache $cacheid $result
}
return $result
}
#*** !doctools
#[list_end] [comment {--- end definitions namespace punk::sixel ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Secondary API namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::sixel::lib {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
tcl::namespace::path [tcl::namespace::parent]
#*** !doctools
#[subsection {Namespace punk::sixel::lib}]
#[para] Secondary functions that are part of the API
#[list_begin definitions]
#proc utility1 {p1 args} {
# #*** !doctools
# #[call lib::[fun utility1] [arg p1] [opt {?option value...?}]]
# #[para]Description of utility1
# return 1
#}
proc ascii_to_sixelvalue {a} {
set dec [scan $a %c]
if {$dec < 63 || $dec > 127} {error "ascii character to convert to sixel value must be from 63 to 126 (chars '?' through to '~')"}
incr dec -63
}
proc ascii_from_sixelvalue {sv} {
if {$sv < 0 || $sv > 63} {error "sixel value must be from 0 to 63 inclusive"}
format %c [expr {$sv + 63}]
}
#*** !doctools
#[list_end] [comment {--- end definitions namespace punk::sixel::lib ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide punk::sixel [tcl::namespace::eval punk::sixel {
variable pkg punk::sixel
variable version
set version 999999.0a1.0
}]
return
#*** !doctools
#[manpage_end]

3
src/modules/punk/sixel-buildversion.txt

@ -0,0 +1,3 @@
0.1.0
#First line must be a semantic version number
#all other lines are ignored.

11
src/modules/punk/tdl-999999.0a1.0.tm

@ -31,15 +31,19 @@ namespace eval punk::tdl {
server -name trillion -os windows server -name trillion -os windows
server -name vmhost1 -os FreeBSD { server -name vmhost1 -os FreeBSD {
guest -name bsd1 -vmmanager iocage guest -name bsd1 -vmmanager bastille
guest -name p1 -vmmanager bhyve guest -name p1 -vmmanager bhyve
} }
} }
proc prettyparse {script} { proc prettyparse {script {safe 1}} {
set i [interp create -safe] if {$safe} {
set i [interp create -safe]
} else {
set i [interp create]
}
try { try {
# $i eval {unset {*}[info vars]} # $i eval {unset {*}[info vars]}
# foreach command [$i eval {info commands}] {$i hide $command} # foreach command [$i eval {info commands}] {$i hide $command}
@ -65,6 +69,7 @@ namespace eval punk::tdl {
interp delete $i interp delete $i
} }
} }
proc prettyprint {data {level 0}} { proc prettyprint {data {level 0}} {
set ind [string repeat " " $level] set ind [string repeat " " $level]
incr level incr level

16
src/modules/punk/uc-999999.0a1.0.tm

@ -133,7 +133,7 @@ namespace eval punk::uc {
# Author: Andreas Kupries <andreas.kupries@gmail.com> # Author: Andreas Kupries <andreas.kupries@gmail.com>
### ###
proc wcswidth_type char { proc wcswidth_type char_as_decimal {
return [lindex { return [lindex {
N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N
N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N N
@ -17542,10 +17542,10 @@ proc wcswidth_type char {
A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A
A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A
A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A A
} $char] } $char_as_decimal]
} }
proc wcswidth_char char { proc wcswidth_char char_as_decimal {
return [lindex { return [lindex {
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
@ -34954,15 +34954,15 @@ proc wcswidth_char char {
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
} $char] } $char_as_decimal]
} }
proc wcswidth {string} { proc wcswidth {string} {
set width 0 set width 0
set len [string length $string] set len [string length $string]
foreach c [split $string {}] { foreach c [split $string {}] {
scan $c %c char scan $c %c char_as_decimal
set n [::textutil::wcswidth_char $char] set n [::textutil::wcswidth_char $char_as_decimal]
if {$n < 0} { if {$n < 0} {
return -1 return -1
} }
@ -34971,8 +34971,10 @@ proc wcswidth_char char {
return $width return $width
} }
proc wcswidth2 {string} { proc wcswidth2 {string} {
#NOTE - scan gets *very* slow for first call with long length string e.g > 500K?
#can appear to hang around 1M (? just extremely slow?)
set codes [scan $string [string repeat %c [string length $string]]] set codes [scan $string [string repeat %c [string length $string]]]
set widths [lmap c $codes {wcswidth_char $c}] set widths [lmap decimal $codes {wcswidth_char $decimal}]
if {-1 in $widths} { if {-1 in $widths} {
return -1 return -1
} }

4
src/modules/punkcheck-0.1.0.tm

@ -359,7 +359,7 @@ namespace eval punkcheck {
-note \uFFFF\ -note \uFFFF\
] ]
set known_opts [dict keys $defaults] set known_opts [dict keys $defaults]
if {[llength $args] % 2 != 0} { if {[llength $args] % 2} {
error "targetset_end arguments after status must be in the form of -flag value pairs. known flags: $known_opts" error "targetset_end arguments after status must be in the form of -flag value pairs. known flags: $known_opts"
} }
set opts [dict merge $defaults $args] set opts [dict merge $defaults $args]
@ -914,7 +914,7 @@ namespace eval punkcheck {
set changed 0 set changed 0
} }
set installing_record_sources [dict_getwithdefault $installing_record body [list]] set installing_record_sources [dict_getwithdefault $installing_record body [list]]
set ts_now [clock microseconds] ;#gathering metadata - especially checsums on folder can take some time - calc and store elapsed us for time taken to gather metadata set ts_now [clock microseconds] ;#gathering metadata - especially checksums on folder can take some time - calc and store elapsed us for time taken to gather metadata
set metadata_us [expr {$ts_now - $ts_start}] set metadata_us [expr {$ts_now - $ts_start}]
set this_source_record [dict create tag SOURCE -type $ftype -size $fsize -path $source_relpath -cksum $cksum -cksum_all_opts $cksum_all_opts -changed $changed -metadata_us $metadata_us] set this_source_record [dict create tag SOURCE -type $ftype -size $fsize -path $source_relpath -cksum $cksum -cksum_all_opts $cksum_all_opts -changed $changed -metadata_us $metadata_us]
lappend installing_record_sources $this_source_record lappend installing_record_sources $this_source_record

9
src/modules/shellfilter-0.1.9.tm

@ -613,6 +613,10 @@ namespace eval shellfilter::chan {
#It can be useful for test/debugging #It can be useful for test/debugging
#Due to chunking at random breaks - we have to check if an ansi code in the underlying stream has been split - otherwise our wrapping will break the existing ansi #Due to chunking at random breaks - we have to check if an ansi code in the underlying stream has been split - otherwise our wrapping will break the existing ansi
# #
set sixelstart_re {\x1bP([;0-9]*)q} ;#7-bit - todo 8bit
#todo kitty graphics \x1b_G...
#todo iterm graphics
oo::class create ansiwrap { oo::class create ansiwrap {
variable o_trecord variable o_trecord
variable o_enc variable o_enc
@ -646,6 +650,9 @@ namespace eval shellfilter::chan {
set o_is_junction 0 set o_is_junction 0
} }
} }
#todo - track when in sixel,iterm,kitty graphics data - can be very large
method Trackcodes {chunk} { method Trackcodes {chunk} {
#puts stdout "===[ansistring VIEW -lf 1 $o_buffered]" #puts stdout "===[ansistring VIEW -lf 1 $o_buffered]"
set buf $o_buffered$chunk set buf $o_buffered$chunk
@ -2334,7 +2341,7 @@ namespace eval shellfilter {
#set tid [::shellfilter::log::open $runtag [list -syslog "127.0.0.1:514"]] #set tid [::shellfilter::log::open $runtag [list -syslog "127.0.0.1:514"]]
set tid [::shellfilter::log::open $runtag [list -syslog ""]] set tid [::shellfilter::log::open $runtag [list -syslog ""]]
if {([llength $args] % 2) != 0} { if {[llength $args] % 2} {
error "Trailing arguments after any positional arguments must be in pairs of the form -argname argvalue. Valid flags are:'$valid_flags'" error "Trailing arguments after any positional arguments must be in pairs of the form -argname argvalue. Valid flags are:'$valid_flags'"
} }
set invalid_flags [list] set invalid_flags [list]

120
src/modules/textblock-999999.0a1.0.tm

@ -82,11 +82,17 @@ tcl::namespace::eval textblock {
#review - what about ansi off in punk::console? #review - what about ansi off in punk::console?
tcl::namespace::import ::punk::ansi::a ::punk::ansi::a+ tcl::namespace::import ::punk::ansi::a ::punk::ansi::a+
tcl::namespace::export block frame frame_cache framedef frametypes gcross height width widthtopline join join_basic list_as_table pad testblock tcl::namespace::export block frame frame_cache framedef frametypes gcross height width widthtopline join join_basic list_as_table pad testblock
#NOTE sha1, although computationally more intensive, tends to be faster than md5 on modern cpus
#(more likely to be optimised for modern cpu features?)
variable use_md5 ;#framecache variable use_md5 ;#framecache
set use_md5 1 set use_md5 1
if {[catch {package require md5}]} { if {[catch {package require md5}]} {
set use_md5 0 set use_md5 0
} }
#todo - change use_md5 to more generic use_checksum_algorithm function.
# e.g allow md5, sha1, none, etc.
# - perhaps autodetect 32bit vs 64bit (or processortype?) to select a default (also depending on packages available and accelerator presence)
proc use_md5 {{yes_no ""}} { proc use_md5 {{yes_no ""}} {
variable use_md5 variable use_md5
if {$yes_no eq ""} { if {$yes_no eq ""} {
@ -4170,7 +4176,7 @@ tcl::namespace::eval textblock {
} }
set FRAMETYPES [textblock::frametypes] set FRAMETYPES [textblock::frametypes]
punk::args::Get_argspecs [punk::lib::tstr -return string { punk::args::definition [punk::lib::tstr -return string {
*id textblock::list_as_table *id textblock::list_as_table
-return -default table -choices {table tableobject} -return -default table -choices {table tableobject}
@ -4208,7 +4214,7 @@ tcl::namespace::eval textblock {
proc list_as_table {args} { proc list_as_table {args} {
set FRAMETYPES [textblock::frametypes] set FRAMETYPES [textblock::frametypes]
set argd [punk::args::get_dict_by_id textblock::list_as_table $args] set argd [punk::args::get_by_id textblock::list_as_table $args]
set opts [dict get $argd opts] set opts [dict get $argd opts]
set datalist [dict get $argd values datalist] set datalist [dict get $argd values datalist]
@ -5699,7 +5705,7 @@ tcl::namespace::eval textblock {
#custom dict may leave out keys - but cannot have unknown keys #custom dict may leave out keys - but cannot have unknown keys
foreach {k v} $f { foreach {k v} $f {
switch -- $k { switch -- $k {
hl - hlt - hlb - vl - vll - vlr - tlc - trc - blc - brc {} all - hl - hlt - hlb - vl - vll - vlr - tlc - trc - blc - brc {}
hltj - hlbj - vllj - vlrj { hltj - hlbj - vllj - vlrj {
#also allow extra join arguments #also allow extra join arguments
} }
@ -5714,11 +5720,15 @@ tcl::namespace::eval textblock {
set is_custom_dict_ok 0 set is_custom_dict_ok 0
} }
if {!$is_custom_dict_ok} { if {!$is_custom_dict_ok} {
error "frame option -type must be one of known types: $::textblock::frametypes or a dictionary with any of keys hl,hlt,hlb,vl,vll,vlr,tlc,trc,blc,brc" error "frame option -type must be one of known types: $::textblock::frametypes or a dictionary with any of keys all,hl,hlt,hlb,vl,vll,vlr,tlc,trc,blc,brc"
}
if {[dict exists $f all]} {
return [tcl::dict::create category custom type $f]
} else {
set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "]
set custom_frame [tcl::dict::merge $default_custom $f]
return [tcl::dict::create category custom type $custom_frame]
} }
set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "]
set custom_frame [tcl::dict::merge $default_custom $f]
return [tcl::dict::create category custom type $custom_frame]
} }
} }
} }
@ -5769,7 +5779,7 @@ tcl::namespace::eval textblock {
} }
set f [lindex $values 0] set f [lindex $values 0]
set rawglobs [lrange $values 1 end] set rawglobs [lrange $values 1 end]
if {![llength $rawglobs]} { if {![llength $rawglobs] || "all" in $rawglobs || "*" in $rawglobs} {
set globs * set globs *
} else { } else {
set globs [list] set globs [list]
@ -6236,6 +6246,46 @@ tcl::namespace::eval textblock {
#from3 #from3
set trc [punk::char::charshort boxd_ldhz] ;#T shape (ttj) set trc [punk::char::charshort boxd_ldhz] ;#T shape (ttj)
set brc [punk::char::charshort boxd_luhz] ;#upside down T shape (btj) set brc [punk::char::charshort boxd_luhz] ;#upside down T shape (btj)
switch -- $targetleft-$targetright {
heavy-light {
set tlc \u252d ;# Left Heavy and Right Down Light (ttj)
set blc \u2535 ;# Left Heavy and Right Up Light (btj)
set vllj \u2525 ;# left heavy (rtj)
set vlrj \u251c;#right light (ltj)
}
heavy-other {
set tlc \u252d ;# Left Heavy and Right Down Light (ttj)
set blc \u2535 ;# Left Heavy and Right Up Light (btj)
set vllj \u2525 ;# left heavy (rtj)
}
heavy-heavy {
set vllj \u2525 ;# left heavy (rtj)
set vlrj \u251d;#right heavy (ltj)
set tlc \u252d ;# Left Heavy and Right Down Light (ttj)
set blc \u2535 ;# Left Heavy and Right Up Light (btj)
set trc \u252e ;#Right Heavy and Left Down Light (ttj)
set brc \u2536 ;#Right Heavy and Left up Light (btj)
}
light-heavy {
set trc \u252e ;#Right Heavy and Left Down Light (ttj)
set brc \u2536 ;#Right Heavy and Left up Light (btj)
set vlrj \u251d;#right heavy (ltj)
set vllj \u2524 ;# left light (rtj)
}
light-other {
set vllj \u2524 ;# left light (rtj)
}
light-light {
set vllj \u2524 ;# left light (rtj)
set vlrj \u251c;#right light (ltj)
}
}
#set vllj \u2525 ;# left heavy (rtj)
#set vllj \u2524 ;# left light (rtj)
#set vlrj \u251d;#right heavy (ltj)
#set vlrj \u251c;#right light (ltj)
} }
left_up { left_up {
#9 #9
@ -6935,6 +6985,7 @@ tcl::namespace::eval textblock {
self-self { self-self {
#set blc \u27e1 ;# white concave-sided diamond - positioned too far right #set blc \u27e1 ;# white concave-sided diamond - positioned too far right
#set blc \u27e3 ;# concave sided diamond with rightwards tick - positioned too low, too right - big gaps #set blc \u27e3 ;# concave sided diamond with rightwards tick - positioned too low, too right - big gaps
#set blc \u2bce ;# from "Miscellaneous Symbols and Arrows" - positioned too far right
set brc \u2524 ;# *light (rtj) set brc \u2524 ;# *light (rtj)
set tlc \u252c ;# *light (ttj) set tlc \u252c ;# *light (ttj)
} }
@ -6950,6 +7001,15 @@ tcl::namespace::eval textblock {
} }
} }
} }
down_right {
switch -- $targetdown-$targetright {
self-self {
#set brc \u2bce ;# from "Miscellaneous Symbols and Arrows" - positioned too far right
set trc \u252c ;# (ttj)
set blc \u2524 ;# (rtj)
}
}
}
} }
} }
arc_b { arc_b {
@ -7027,6 +7087,15 @@ tcl::namespace::eval textblock {
set blc \U1fb7c ;#legacy block set blc \U1fb7c ;#legacy block
set brc \U1fb7f ;#legacy block set brc \U1fb7f ;#legacy block
if {[punk::console::check::has_bug_legacysymbolwidth]} {
#rather than totally fail on some mixed layout that happens to use block2 - just degrade it - but prevent alignment problems
set sp \u00a0 ;#non breaking space (plain space may act transparent in some use cases)
set tlc $sp
set trc $sp
set blc $sp
set brc $sp
}
#horizontal and vertical bar joins #horizontal and vertical bar joins
set hltj $hlt set hltj $hlt
set hlbj $hlb set hlbj $hlb
@ -7088,15 +7157,20 @@ tcl::namespace::eval textblock {
set vlrj $vlr set vlrj $vlr
} }
default { default {
set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "] ;#only default the general types - these form defaults for more specific types if they're missing #set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "] ;#only default the general types - these form defaults for more specific types if they're missing
if {[llength $f] % 2 != 0} { set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "]
if {"all" in [dict keys $f]} {
set A [dict get $f all]
set default_custom [tcl::dict::create hl $A vl $A tlc $A trc $A blc $A brc $A]
}
if {[llength $f] % 2} {
#todo - retrieve usage from punk::args #todo - retrieve usage from punk::args
error "textblock::frametype frametype '$f' is not one of the predefined frametypes: $::textblock::frametypes and does not appear to be a dictionary for a custom frametype" error "textblock::frametype frametype '$f' is not one of the predefined frametypes: $::textblock::frametypes and does not appear to be a dictionary for a custom frametype"
} }
#unknown order of keys specified by user - validate before creating vars as we need more general elements to be available as defaults #unknown order of keys specified by user - validate before creating vars as we need more general elements to be available as defaults
dict for {k v} $f { dict for {k v} $f {
switch -- $k { switch -- $k {
hl - vl - tlc - trc - blc - brc - hlt - hlb - vll - vlr - hltj - hlbj - vllj - vlrj {} all - hl - vl - tlc - trc - blc - brc - hlt - hlb - vll - vlr - hltj - hlbj - vllj - vlrj {}
default { default {
error "textblock::frametype '$f' has unknown element '$k'" error "textblock::frametype '$f' has unknown element '$k'"
} }
@ -8028,17 +8102,19 @@ tcl::namespace::eval textblock {
return $fs return $fs
} }
} }
punk::args::definition {
*id textblock::gcross
-max_cross_size -default 0 -type integer -help "Largest size cross to use to make up the block
Only cross sizes that divide the size of the overall block will be used.
e.g if the 'size' chosen is 19 (a prime number) - only 1 or the full size of 19 can be used as the crosses to make up the block.
Whereas for a block size of 24, -max_cross_size of 1,2,3,4,6,8,12 or 24 will work. (all divisors)
If a number chosen for -max_cross_size isn't a divisor, the largest divisor below the chosen value will be used.
"
*values -min 1 -max 1
size -default 1 -type integer
}
proc gcross {args} { proc gcross {args} {
set argd [punk::args::get_dict { set argd [punk::args::get_by_id textblock::gcross $args]
-max_cross_size -default 0 -type integer -help "Largest size cross to use to make up the block
Only cross sizes that divide the size of the overall block will be used.
e.g if the 'size' chosen is 19 (a prime number) - only 1 or the full size of 19 can be used as the crosses to make up the block.
Whereas for a block size of 24, -max_cross_size of 1,2,3,4,6,8,12 or 24 will work. (all divisors)
If a number chosen for -max_cross_size isn't a divisor, the largest divisor below the chosen value will be used.
"
*values -min 1
size -default 1 -type integer
} $args]
set size [dict get $argd values size] set size [dict get $argd values size]
set opts [dict get $argd opts] set opts [dict get $argd opts]
@ -8089,7 +8165,7 @@ tcl::namespace::eval textblock {
lappend crossrows [::join $r ""] lappend crossrows [::join $r ""]
} }
if {$max_cross_size % 2 != 0} { if {$max_cross_size % 2} {
#only put centre cross in for odd sized crosses #only put centre cross in for odd sized crosses
set r $row set r $row
lset r $armsize $x lset r $armsize $x

2
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/overtype-1.6.5.tm

@ -1771,7 +1771,7 @@ tcl::namespace::eval overtype {
#-returnextra enables returning of overflow and length #-returnextra enables returning of overflow and length
#review - use punk::ansi::ta::detect to short-circuit processing and do simpler string calcs as an optimisation? #review - use punk::ansi::ta::detect to short-circuit processing and do simpler string calcs as an optimisation?
#review - DECSWL/DECDWL double width line codes - very difficult/impossible to align and compose with other elements #review - DECSWL/DECDWL double width line codes - very difficult/impossible to align and compose with other elements
#(could render it by faking it with sixels and a lot of work - find/make a sixel font and ensure it's exactly 2 cols per char? #(could render it by faking it with sixels and a lot of work - find/make a sixel font (converter?) and ensure it's exactly 2 cols per char?
# This would probably be impractical to support for different fonts) # This would probably be impractical to support for different fonts)
#todo - review transparency issues with single/double width characters #todo - review transparency issues with single/double width characters
#bidi - need a base direction and concept of directional runs for RTL vs LTR - may be best handled at another layer? #bidi - need a base direction and concept of directional runs for RTL vs LTR - may be best handled at another layer?

46
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk-0.1.tm

@ -7428,6 +7428,7 @@ namespace eval punk {
} }
if {$topic in [list console terminal]} { if {$topic in [list console terminal]} {
set indent [string repeat " " [string length "WARNING: "]]
lappend cstring_tests [dict create\ lappend cstring_tests [dict create\
type "PM "\ type "PM "\
msg "PRIVACY MESSAGE"\ msg "PRIVACY MESSAGE"\
@ -7472,6 +7473,51 @@ namespace eval punk {
append warningblock \n "WARNING: terminal doesn't hide all [dict get $test type] control strings: $d $d8" append warningblock \n "WARNING: terminal doesn't hide all [dict get $test type] control strings: $d $d8"
} }
} }
if {![catch {punk::console::check::has_bug_legacysymbolwidth} result]} {
if {$result} {
append warningblock \n "WARNING: terminal has legacysymbolwidth bug - screen position for symbol reports 2 wide but displays 1 wide."
append warningblock \n $indent "Layout using 'legacy symbols for computing' affected."
append warningblock \n $indent "(e.g textblock frametype block2 unsupported)"
append warningblock \n $indent "This can cause extreme layout deformation when ANSI is present"
append warningblock \n $indent "In some cases unwanted spacing effects occur at a distance from the characters causing it"
}
} else {
append warningblock \n "WARNING: terminal unable to check for legacysymbolwidth bug. err:$result"
}
if {![catch {punk::console::check::has_bug_zwsp} result]} {
if {$result} {
append warningblock \n "WARNING: terminal has zero width space (\\u200b) bug - cursor position incremented when it shouldn't be."
append warningblock \n $indent "The zwsp may or may not be displayed. zwsp contributes to line length and wrapping point"
}
} else {
append warningblock \n "WARNING: terminal unable to check for zwsp bug. err:$result"
}
set grapheme_support [punk::console::grapheme_cluster_support]
#mode, 1 = set, 2 = unset. (0 = mode not recognised, 3 = permanently set, 4 = permanently unset)
if {![dict size $grapheme_support] || [dict get $grapheme_support mode] eq "unsupported" } {
append warningblock \n "WARNING: terminal either doesn't support grapheme clusters, or doesn't report so via decmode 2027 query."
if {[dict size $grapheme_support] && [dict get $grapheme_support available]} {
append warningblock \n $indent "(but punk::console::grapheme_cluster_support has determined it is probably available)"
}
} else {
if {![dict get $grapheme_support available]} {
switch -- [dict get $grapheme_support mode] {
"unset" {
append warningblock \n "WARNING: terminal reports via decmode 2027 that grapheme cluster support is off."
}
"permanently_unset" {
append warningblock \n "WARNING: terminal reports via decmode 2027 that grapheme cluster support is permanently off."
}
"BAD_RESPONSE" {
append warningblock \n "WARNING: terminal doesn't seem to recognize decmode 2027 query. No grapheme cluster support."
}
}
}
}
} }
lappend chunks [list stderr $warningblock] lappend chunks [list stderr $warningblock]

562
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/ansi-0.1.1.tm

@ -556,21 +556,21 @@ tcl::namespace::eval punk::ansi {
} }
proc example {args} { proc example {args} {
set base [punk::repo::find_project] set base [punk::repo::find_project]
set default_ansibase [file join $base src/testansi] set default_ansifolder [file join $base src/testansi]
set argd [punk::args::get_dict [tstr -return string { set argd [punk::args::get_dict [tstr -return string {
*proc -name punk::ansi::example -help "Display .ans image files in a grid that will fit in console *proc -name punk::ansi::example -help "Display .ans image files in a grid that will fit in console
" "
-colwidth -default 82 -help "Width of each column - default of 82 will fit a standard 80wide ansi image (when framed) -colwidth -default 82 -help "Width of each column - default of 82 will fit a standard 80wide ansi image (when framed)
You can specify a narrower width to truncate images on the right side" You can specify a narrower width to truncate images on the right side"
-folder -default "${$default_ansibase}" -help "Base folder for files if relative paths are used. -folder -default "${$default_ansifolder}" -help "Base folder for files if relative paths are used.
Defaults to <projectbase>/src/testansi - where projectbase is determined from current directory. Defaults to <projectbase>/src/testansi - where projectbase is determined from current directory.
" "
*values -min 0 -max -1 *values -min 0 -max -1
files -default {belinda.ans bot.ans flower.ans fish.ans} -multiple true -help "List of filenames - leave empty to display 4 defaults" files -default {belinda.ans bot.ans flower.ans fish.ans} -multiple true -help "List of filenames - leave empty to display 4 defaults"
}] $args] }] $args]
set colwidth [dict get $argd opts -colwidth] set colwidth [dict get $argd opts -colwidth]
set ansibase [file normalize [dict get $argd opts -folder]] set ansifolder [file normalize [dict get $argd opts -folder]]
set fnames [dict get $argd values files] set fnames [dict get $argd values files]
#assumes fixed column widths e.g 80col images will fit in 82-width frames (common standard for old ansi art) (of arbitrary height) #assumes fixed column widths e.g 80col images will fit in 82-width frames (common standard for old ansi art) (of arbitrary height)
@ -579,8 +579,8 @@ tcl::namespace::eval punk::ansi {
package require punk::repo package require punk::repo
package require punk::console package require punk::console
if {![file exists $ansibase]} { if {![file exists $ansifolder]} {
puts stderr "Missing folder at $ansibase" puts stderr "Missing folder at $ansifolder"
puts stderr "Ensure ansi test files exist: $fnames" puts stderr "Ensure ansi test files exist: $fnames"
#error "punk::ansi::example Cannot find example files" #error "punk::ansi::example Cannot find example files"
} }
@ -588,7 +588,7 @@ tcl::namespace::eval punk::ansi {
set pics [list] set pics [list]
foreach f $fnames { foreach f $fnames {
if {[file pathtype $f] ne "absolute"} { if {[file pathtype $f] ne "absolute"} {
set filepath [file normalize $ansibase/$f] set filepath [file normalize $ansifolder/$f]
} else { } else {
set filepath [file normalize $f] set filepath [file normalize $f]
} }
@ -621,7 +621,7 @@ tcl::namespace::eval punk::ansi {
set subtitle [tcl::dict::get $picinfo status] set subtitle [tcl::dict::get $picinfo status]
} }
set title [tcl::dict::get $picinfo filename] set title [tcl::dict::get $picinfo filename]
set fr [textblock::frame -checkargs 0 -width $colwidth -subtitle $subtitle -title $title [tcl::dict::get $picinfo pic]] set fr [textblock::frame -width $colwidth -subtitle $subtitle -title $title [tcl::dict::get $picinfo pic]]
# -- --- --- --- # -- --- --- ---
#we need the max height of a row element to use join_basic instead of join below #we need the max height of a row element to use join_basic instead of join below
# -- --- --- --- # -- --- --- ---
@ -2096,7 +2096,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
} }
} }
} }
append out "\nNote: The 256 term colours especially 0-15 may be altered by terminal pallette settings or ansi OSC 4 codes, so specific RGB values are unavailable" append out "\nNote: The 256 term colours especially 0-15 may be altered by terminal palette settings or ansi OSC 4 codes, so specific RGB values are unavailable"
return $out return $out
} }
web { web {
@ -2126,8 +2126,25 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
switch -- $f4 { switch -- $f4 {
web- - Web- - WEB- { web- - Web- - WEB- {
set tail [tcl::string::tolower [tcl::string::trim [tcl::string::range $i 4 end] -]] set tail [tcl::string::tolower [tcl::string::trim [tcl::string::range $i 4 end] -]]
if {[tcl::dict::exists $WEB_colour_map $tail]} { set cont [string range $tail end-11 end]
set dec [tcl::dict::get $WEB_colour_map $tail] switch -- $cont {
-contrasting - -contrastive {
set cname [string range $tail 0 end-12]
}
default {
set cname $tail
}
}
if {[tcl::dict::exists $WEB_colour_map $cname]} {
set dec [tcl::dict::get $WEB_colour_map $cname]
switch -- $cont {
-contrasting {
set dec [join [punk::ansi::colour::contrasting {*}[split $dec -]] -]
}
-contrastive {
set dec [join [lindex [punk::ansi::colour::contrast_pair {*}[split $dec -]] 0] -]
}
}
set hex [colour_dec2hex $dec] set hex [colour_dec2hex $dec]
set descr "$hex $dec" set descr "$hex $dec"
} else { } else {
@ -2170,25 +2187,60 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
RGB0 - RGB1 - RGB2 - RGB3 - RGB4 - RGB5 - RGB6 - RGB7 - RGB8 - RGB9 - RGB0 - RGB1 - RGB2 - RGB3 - RGB4 - RGB5 - RGB6 - RGB7 - RGB8 - RGB9 -
rgb# - Rgb# - RGB# - rgb# - Rgb# - RGB# -
und# - und- { und# - und- {
if {[tcl::string::index $i 3] eq "#"} { set cont [string range $i end-11 end]
set tail [tcl::string::range $i 4 end] switch -- $cont {
-contrasting - -contrastive {
set iplain [string range $i 0 end-12]
}
default {
set iplain $i
}
}
if {[tcl::string::index $iplain 3] eq "#"} {
set tail [tcl::string::range $iplain 4 end]
set hex $tail set hex $tail
set dec [colour_hex2dec $hex] set dec [colour_hex2dec $hex]
set info $dec ;#show opposite type as first line of info col
switch -- $cont {
-contrasting {
set decfinal [join [punk::ansi::colour::contrasting {*}[split $dec -]] -]
set hexfinal [colour_dec2hex $decfinal]
}
-contrastive {
set decfinal [join [lindex [punk::ansi::colour::contrast_pair {*}[split $dec -]] 0] -]
set hexfinal [colour_dec2hex $decfinal]
}
default {
set hexfinal $hex
set decfinal $dec
}
}
set info "$hexfinal $decfinal" ;#show opposite type as first line of info col
} else { } else {
set tail [tcl::string::trim [tcl::string::range $i 3 end] -] set tail [tcl::string::trim [tcl::string::range $iplain 3 end] -]
set dec $tail set dec $tail
set hex [colour_dec2hex $dec] switch -- $cont {
set info $hex -contrasting {
set decfinal [join [punk::ansi::colour::contrasting {*}[split $dec -]] -]
}
-contrastive {
set decfinal [join [lindex [punk::ansi::colour::contrast_pair {*}[split $dec -]] 0] -]
}
default {
set decfinal $dec
}
}
set hexfinal [colour_dec2hex $decfinal]
set info "$hexfinal $decfinal"
} }
set webcolours_i [lsearch -all $WEB_colour_map $dec] set webcolours_i [lsearch -all $WEB_colour_map $decfinal]
set webcolours [list] set webcolours [list]
foreach ci $webcolours_i { foreach ci $webcolours_i {
lappend webcolours [lindex $WEB_colour_map $ci-1] lappend webcolours [lindex $WEB_colour_map $ci-1]
} }
set x11colours [list] set x11colours [list]
set x11colours_i [lsearch -all $X11_colour_map $dec] set x11colours_i [lsearch -all $X11_colour_map $decfinal]
foreach ci $x11colours_i { foreach ci $x11colours_i {
set c [lindex $X11_colour_map $ci-1] set c [lindex $X11_colour_map $ci-1]
if {$c ni $webcolours} { if {$c ni $webcolours} {
@ -2205,12 +2257,15 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
} }
unde { unde {
switch -- $i { switch -- $i {
undercurly - underdotted - underdashed - undersingle - underdouble { undercurly - undercurl - underdotted - underdot - underdashed - underdash - undersingle - underdouble {
$t add_row [list $i extended $s [ansistring VIEW $s]] $t add_row [list $i extended $s [ansistring VIEW $s]]
} }
underline { underline {
$t add_row [list $i "SGR 4" $s [ansistring VIEW $s]] $t add_row [list $i "SGR 4" $s [ansistring VIEW $s]]
} }
underlinedefault {
$t add_row [list $i "SGR 59" $s [ansistring VIEW $s]]
}
default { default {
$t add_row [list $i UNKNOWN $s [ansistring VIEW $s]] $t add_row [list $i UNKNOWN $s [ansistring VIEW $s]]
} }
@ -2362,10 +2417,31 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#variable WEB_colour_map #variable WEB_colour_map
#upvar ::punk::ansi::WEB_colour_map WEB_colour_map #upvar ::punk::ansi::WEB_colour_map WEB_colour_map
#foreground web colour #foreground web colour
set cname [tcl::string::tolower [tcl::string::range $i 4 end]] set tail [tcl::string::tolower [tcl::string::range $i 4 end]]
#-contrasting
#-contrastive
set cont [string range $tail end-11 end]
switch -- $cont {
-contrasting - -contrastive {
set cname [string range $tail 0 end-12]
}
default {
set cname $tail
}
}
if {[tcl::dict::exists $WEB_colour_map $cname]} { if {[tcl::dict::exists $WEB_colour_map $cname]} {
set rgbdash [tcl::dict::get $WEB_colour_map $cname] set rgbdash [tcl::dict::get $WEB_colour_map $cname]
set rgb [tcl::string::map { - ;} $rgbdash] switch -- $cont {
-contrasting {
set rgb [join [punk::ansi::colour::contrasting {*}[split $rgbdash -]] {;}]
}
-contrastive {
set rgb [join [lindex [punk::ansi::colour::contrast_pair {*}[split $rgbdash -]] 0] {;}]
}
default {
set rgb [tcl::string::map { - ;} $rgbdash]
}
}
lappend t "38;2;$rgb" lappend t "38;2;$rgb"
} else { } else {
puts stderr "ansi web colour unmatched: '$i' in call 'a+ $args'" puts stderr "ansi web colour unmatched: '$i' in call 'a+ $args'"
@ -2375,9 +2451,30 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#variable WEB_colour_map #variable WEB_colour_map
#upvar ::punk::ansi::WEB_colour_map WEB_colour_map #upvar ::punk::ansi::WEB_colour_map WEB_colour_map
#background web colour #background web colour
set cname [tcl::string::tolower [tcl::string::range $i 4 end]] set tail [tcl::string::tolower [tcl::string::range $i 4 end]]
set cont [string range $tail end-11 end]
switch -- $cont {
-contrasting - -contrastive {
set cname [string range $tail 0 end-12]
}
default {
set cname $tail
}
}
if {[tcl::dict::exists $WEB_colour_map $cname]} { if {[tcl::dict::exists $WEB_colour_map $cname]} {
lappend t "48;2;[tcl::string::map {- ;} [tcl::dict::get $WEB_colour_map $cname]]" set rgbdash [tcl::dict::get $WEB_colour_map $cname]
switch -- $cont {
-contrasting {
set rgb [join [punk::ansi::colour::contrasting {*}[split $rgbdash -]] {;}]
}
-contrastive {
set rgb [join [lindex [punk::ansi::colour::contrast_pair {*}[split $rgbdash -]] 0] {;}]
}
default {
set rgb [tcl::string::map { - ;} $rgbdash]
}
}
lappend t "48;2;$rgb"
} else { } else {
puts stderr "ansi Web colour unmatched: '$i' in call 'a+ $args'" puts stderr "ansi Web colour unmatched: '$i' in call 'a+ $args'"
} }
@ -2407,6 +2504,9 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
underline { underline {
lappend t 4 ;#underline lappend t 4 ;#underline
} }
underlinedefault {
lappend t 59
}
underextendedoff { underextendedoff {
#lremove any existing 4:1 etc #lremove any existing 4:1 etc
#NOTE struct::set result order can differ depending on whether tcl/critcl imp used #NOTE struct::set result order can differ depending on whether tcl/critcl imp used
@ -2420,13 +2520,13 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
underdouble { underdouble {
lappend e 4:2 lappend e 4:2
} }
undercurly { undercurly - undercurl {
lappend e 4:3 lappend e 4:3
} }
underdotted { underdotted - underdot {
lappend e 4:4 lappend e 4:4
} }
underdashed { underdashed - underdash {
lappend e 4:5 lappend e 4:5
} }
default { default {
@ -2542,45 +2642,109 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
} }
} }
} }
rgb- - rgb0 - rgb1 - rgb2 - rgb3 - rgb4 - rgb5 - rgb6 - rgb7 - rgb8 - rgb9 { rgb- - rgb0 - rgb1 - rgb2 - rgb3 - rgb4 - rgb5 - rgb6 - rgb7 - rgb8 - rgb9 -
#decimal rgb foreground
#allow variants rgb-xxx-xxx-xxx and rgbxxx-xxx-xxx
set rgbspec [tcl::string::trim [tcl::string::range $i 3 end] -]
set rgb [tcl::string::map [list - {;} , {;}] $rgbspec]
lappend t "38;2;$rgb"
}
Rgb- - RGB- - Rgb0 - Rgb1 - Rgb2 - Rgb3 - Rgb4 - Rgb5 - Rgb6 - Rgb7 - Rgb8 - Rgb9 - RGB0 - RGB1 - RGB2 - RGB3 - RGB4 - RGB5 - RGB6 - RGB7 - RGB8 - RGB9 { Rgb- - RGB- - Rgb0 - Rgb1 - Rgb2 - Rgb3 - Rgb4 - Rgb5 - Rgb6 - Rgb7 - Rgb8 - Rgb9 - RGB0 - RGB1 - RGB2 - RGB3 - RGB4 - RGB5 - RGB6 - RGB7 - RGB8 - RGB9 {
#decimal rgb background #decimal rgb foreground/background
set rgbspec [tcl::string::trim [tcl::string::range $i 3 end] -] #allow variants rgb-xxx-xxx-xxx and rgbxxx-xxx-xxx
set rgb [tcl::string::map [list - {;} , {;}] $rgbspec]
lappend t "48;2;$rgb" set cont [string range $i end-11 end]
} switch -- $cont {
"rgb#" { -contrasting - -contrastive {
#hex rgb foreground set iplain [string range $i 0 end-12]
set hex6 [tcl::string::trim [tcl::string::range $i 4 end] -] }
set rgb [join [::scan $hex6 %2X%2X%2X] {;}] default {
lappend t "38;2;$rgb" set iplain $i
}
}
set rgbspec [tcl::string::trim [tcl::string::range $iplain 3 end] -]
set RGB [tcl::string::map [list - { } , { } {;} { }] $rgbspec] ;#RGB as list
switch -- $cont {
-contrasting {
set rgbfinal [join [punk::ansi::colour::contrasting {*}$RGB] {;}]
}
-contrastive {
set rgbfinal [join [lindex [punk::ansi::colour::contrast_pair {*}$RGB] 0] {;}]
}
default {
set rgbfinal [join $RGB {;}]
}
}
if {[tcl::string::index $i 0] eq "r"} {
#fg
lappend t "38;2;$rgbfinal"
} else {
#bg
lappend t "48;2;$rgbfinal"
}
} }
"Rgb#" - "RGB#" { "rgb#" - "Rgb#" - "RGB#" {
#hex rgb background
set hex6 [tcl::string::trim [tcl::string::range $i 4 end] -] set hex6 [tcl::string::trim [tcl::string::range $i 4 end] -]
set rgb [join [::scan $hex6 %2X%2X%2X] {;}] #set rgb [join [::scan $hex6 %2X%2X%2X] {;}]
lappend t "48;2;$rgb" set RGB [::scan $hex6 %2X%2X%2X]
set cont [string range $i end-11 end]
switch -- $cont {
-contrasting {
set rgbfinal [join [punk::ansi::colour::contrasting {*}$RGB] {;}]
}
-contrastive {
set rgbfinal [join [lindex [punk::ansi::colour::contrast_pair {*}$RGB] 0] {;}]
}
default {
set rgbfinal [join $RGB {;}]
}
}
if {[tcl::string::index $i 0] eq "r"} {
#hex rgb foreground
lappend t "38;2;$rgbfinal"
} else {
#hex rgb background
lappend t "48;2;$rgbfinal"
}
} }
und- - und0 - und1 - und2 - und3 - und4 - und5 - und6 - und7 - und8 - und9 { und- - und0 - und1 - und2 - und3 - und4 - und5 - und6 - und7 - und8 - und9 {
#decimal rgb underline #decimal rgb underline
#allow variants und-xxx-xxx-xxx and undxxx-xxx-xxx #allow variants und-xxx-xxx-xxx and undxxx-xxx-xxx
#https://wezfurlong.org/wezterm/escape-sequences.html#csi-582-underline-color-rgb
set rgbspec [tcl::string::trim [tcl::string::range $i 3 end] -] set rgbspec [tcl::string::trim [tcl::string::range $i 3 end] -]
set rgb [tcl::string::map [list - {:} , {:}] $rgbspec] set RGB [lrange [tcl::string::map [list - { } , { } {;} { }] $rgbspec] 0 2]
lappend e "58:2::$rgb" #puts "---->'$RGB'<----"
set cont [string range $i end-11 end]
switch -- $cont {
-contrasting {
set rgbfinal [join [punk::ansi::colour::contrasting {*}$RGB] {:}]
}
-contrastive {
set rgbfinal [join [lindex [punk::ansi::colour::contrast_pair {*}$RGB] 0] {:}]
}
default {
set rgbfinal [join $RGB {:}]
}
}
#lappend e "58:2:$rgbfinal" ;# - no colorspace ID - some terminals support? which?
lappend e "58:2::$rgbfinal"
} }
"und#" { "und#" {
#hex rgb underline - (e.g kitty, wezterm) - uses colons as separators #hex rgb underline - (e.g kitty, wezterm) - uses colons as separators
set hex6 [tcl::string::trim [tcl::string::range $i 4 end] -] set hex6 [tcl::string::trim [tcl::string::range $i 4 end] -]
set rgb [join [::scan $hex6 %2X%2X%2X] {:}] #set rgb [join [::scan $hex6 %2X%2X%2X] {:}]
lappend e "58:2::$rgb" set RGB [::scan $hex6 %2X%2X%2X]
set cont [string range $i end-11 end]
switch -- $cont {
-contrasting {
set rgbfinal [join [punk::ansi::colour::contrasting {*}$RGB] {:}]
}
-contrastive {
set rgbfinal [join [lindex [punk::ansi::colour::contrast_pair {*}$RGB] 0] {:}]
}
default {
set rgbfinal [join $RGB {:}]
}
}
lappend e "58:2::$rgbfinal"
} }
undt { undt {
#CSI 58:5 UNDERLINE COLOR PALETTE INDEX
#CSI 58 : 5 : INDEX m
#variable TERM_colour_map #variable TERM_colour_map
#256 colour underline by Xterm name or by integer #256 colour underline by Xterm name or by integer
#name is xterm name or colour index from 0 - 255 #name is xterm name or colour index from 0 - 255
@ -2762,6 +2926,9 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
underline { underline {
lappend t 4 ;#underline lappend t 4 ;#underline
} }
underlinedefault {
lappend t 59
}
underextendedoff { underextendedoff {
#lremove any existing 4:1 etc #lremove any existing 4:1 etc
#use of struct::set with critcl is fast, but will reorder e (with differences depending on tcl vs critcl) #use of struct::set with critcl is fast, but will reorder e (with differences depending on tcl vs critcl)
@ -2775,13 +2942,13 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
underdouble { underdouble {
lappend e 4:2 lappend e 4:2
} }
undercurly { undercurly - undercurl {
lappend e 4:3 lappend e 4:3
} }
underdotted { underdotted - underdot {
lappend e 4:4 lappend e 4:4
} }
underdashed { underdashed - underdash {
lappend e 4:5 lappend e 4:5
} }
default { default {
@ -3262,6 +3429,8 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#tput rmam #tput rmam
return \x1b\[?7l return \x1b\[?7l
} }
proc query_mode_line_wrap {} { proc query_mode_line_wrap {} {
#*** !doctools #*** !doctools
#[call [fun query_mode_line_wrap]] #[call [fun query_mode_line_wrap]]
@ -3274,6 +3443,31 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
# \x1b\[?7\;2\$y # \x1b\[?7\;2\$y
#where 1 = set, 2 = unset. (0 = mode not recognised, 3 = permanently set, 4 = permanently unset) #where 1 = set, 2 = unset. (0 = mode not recognised, 3 = permanently set, 4 = permanently unset)
#names for other alt_screen mechanisms: 1047,1048 vs 1049?
variable decmode_names [dict create\
line_wrap 7\
LNM 20\
alt_screen 1049\
grapheme_clusters 2027\
bracketed_paste 2004\
mouse_sgr_extended 1006\
mouse_urxvt 1015\
mouse_sgr 1016\
]
proc query_mode {num_or_name} {
if {[string is integer -strict $num_or_name]} {
set m $num_or_name
} else {
variable decmode_names
if {[dict exists $decmode_names $num_or_name]} {
set m [dict get $decmode_names $num_or_name]
} else {
error "punk::ansi::query_mode unrecognised mode '$num_or_name'. Known mode names: [dict keys $decmode_names]"
}
}
return "\x1b\[?$m\$p"
}
#Alt screen buffer - smcup/rmcup ti/te #Alt screen buffer - smcup/rmcup ti/te
#Note \x1b\[?47h doesn't work at all in some terminals (e.g alacritty,cmd on windows and reportedly kitty) #Note \x1b\[?47h doesn't work at all in some terminals (e.g alacritty,cmd on windows and reportedly kitty)
@ -3658,7 +3852,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
} }
proc ansistrip2 {text} { proc ansistrip2 {text} {
#*** !doctools #*** !doctools
#[call [fun ansistrip] [arg text] ] #[call [fun ansistrip2] [arg text] ]
#[para]Return a string with ansi codes stripped out #[para]Return a string with ansi codes stripped out
#[para]Alternate graphics chars are replaced with modern unicode equivalents (e.g boxdrawing glyphs) #[para]Alternate graphics chars are replaced with modern unicode equivalents (e.g boxdrawing glyphs)
@ -6016,6 +6210,10 @@ tcl::namespace::eval punk::ansi::ansistring {
SP [list \x20 \u2420]\ SP [list \x20 \u2420]\
DEL [list \x7f \u2421]\ DEL [list \x7f \u2421]\
] ]
set map_c0 [dict create]
dict for {k v} $visuals_c0 {
dict set map_c0 {*}$v
}
#alternate symbols for space #alternate symbols for space
# \u2422 Blank Symbol (b with forwardslash overly) # \u2422 Blank Symbol (b with forwardslash overly)
@ -6051,6 +6249,9 @@ tcl::namespace::eval punk::ansi::ansistring {
#miscellaneous debug code brackets #miscellaneous debug code brackets
set obm \u27e6 ;set cbm \u27e7 ;#square double brackets from Miscellaneous Mathematical Symbols-A set obm \u27e6 ;set cbm \u27e7 ;#square double brackets from Miscellaneous Mathematical Symbols-A
#unicode Tags block brackets
set obt \u2993 ;set cbt \u2994
#this private range so rarely supported in fonts - and visuals are unknown, so we will make up some 2-letter codes for now #this private range so rarely supported in fonts - and visuals are unknown, so we will make up some 2-letter codes for now
#set visuals_c1 [tcl::dict::create\ #set visuals_c1 [tcl::dict::create\
# BPH [list \x82 "${ob8}\ue022 $cb8"]\ # BPH [list \x82 "${ob8}\ue022 $cb8"]\
@ -6120,9 +6321,21 @@ tcl::namespace::eval punk::ansi::ansistring {
APC [list \x9f "${ob8}AP$cb8"]\ APC [list \x9f "${ob8}AP$cb8"]\
] ]
#unicode Tags block - nonprinting mapped to ascii 0-127
set visuals_tags [tcl::dict::create]
for {set i 917504} {$i < 917632} {incr i} {
set asciidec [expr {$i - 917504}]
set vis [format %c $asciidec]
if {[dict exists $map_c0 $vis]} {
set vis [dict get $map_c0 $vis]
}
tcl::dict::set visuals_tags TAG$asciidec [list [format %c $i] "${obt}$vis${cbt}"]
}
set hack [tcl::dict::create] set hack [tcl::dict::create]
tcl::dict::set hack BOM1 [list \uFEFF "${obm}\U1f4a3$cbm"] ;#byte order mark/ ZWNBSP (ZWNBSP usage generally deprecated) - a picture of a bomb(2wide glyph) tcl::dict::set hack BOM1 [list \uFEFF "${obm}\U1f4a3$cbm"] ;#byte order mark/ ZWNBSP (ZWNBSP usage generally deprecated) - a picture of a bomb(2wide glyph)
tcl::dict::set hack ZWSP [list \u200B "${obm}ZWSP$cbm"]
#review - other boms? Encoding dependent? #review - other boms? Encoding dependent?
tcl::dict::set hack DCS [list \x90 "${ob8}\u2328 $cb8"] ;#keyboard from Miscellaneous Technical - 1 wide + pad. tcl::dict::set hack DCS [list \x90 "${ob8}\u2328 $cb8"] ;#keyboard from Miscellaneous Technical - 1 wide + pad.
@ -6133,7 +6346,7 @@ tcl::namespace::eval punk::ansi::ansistring {
tcl::dict::set hack PM [list \x9e "${ob8}PM$cb8"] tcl::dict::set hack PM [list \x9e "${ob8}PM$cb8"]
tcl::dict::set hack APC [list \x9f "${ob8}\U1f534$cb8"] ;#bright red ball from Miscellaneoust Symbols and Pictographs - 2 wide (APC also noted as a potential security risk) tcl::dict::set hack APC [list \x9f "${ob8}\U1f534$cb8"] ;#bright red ball from Miscellaneoust Symbols and Pictographs - 2 wide (APC also noted as a potential security risk)
set debug_visuals [tcl::dict::merge $visuals_c0 $visuals_c1 $hack] set debug_visuals [tcl::dict::merge $visuals_c0 $visuals_c1 $hack $visuals_tags]
#for repeated interaction with the same ANSI string - a mechanism to store state is more efficient #for repeated interaction with the same ANSI string - a mechanism to store state is more efficient
proc NEW {string} { proc NEW {string} {
@ -6165,7 +6378,7 @@ tcl::namespace::eval punk::ansi::ansistring {
-sp 1\ -sp 1\
] ]
set argopts [lrange $args 0 end-1] set argopts [lrange $args 0 end-1]
if {[llength $argopts] % 2 != 0} { if {[llength $argopts] % 2} {
error "ansistring VIEW options must be option-value pairs, received '$argopts'. Known opts [tcl::dict::keys $defaults]" error "ansistring VIEW options must be option-value pairs, received '$argopts'. Known opts [tcl::dict::keys $defaults]"
} }
set opts [tcl::dict::merge $defaults $argopts] set opts [tcl::dict::merge $defaults $argopts]
@ -6760,7 +6973,240 @@ tcl::namespace::eval punk::ansi::ansistring {
#*** !doctools #*** !doctools
#[list_end] [comment {--- end definitions namespace punk::ansi::ta ---}] #[list_end] [comment {--- end definitions namespace punk::ansi::ta ---}]
} }
tcl::namespace::eval punk::ansi::control {
proc APC {args} {
return \x1b_[join $args {;}]\x1b\\
}
proc APC8 {args} {
return \x9f[join $args {;}]\x9c
}
proc CSI {args} {
set finalarg [lindex $args end]
set finalbyte [string index $finalarg end]
if {![regexp {[\x40-\x73]} $finalbyte]} {
error "::punk::ansi::control::CSI final byte must be one in the set @A-Z\[\\\]^_`a-z\{|\}~"
}
if {$finalarg eq $finalbyte} {
return \x1b\[[join [lrange $args 0 end-1] {;}]$finalbyte
} else {
return \x1b\[[join $args {;}]
}
}
proc CSI8 {args} {
set finalarg [lindex $args end]
set finalbyte [string index $finalarg end]
if {![regexp {[\x40-\x73]} $finalbyte]} {
error "::punk::ansi::control::CSI final byte must be one in the set @A-Z\[\\\]^_`a-z\{|\}~"
}
if {$finalarg eq $finalbyte} {
return \x9b[join [lrange $args 0 end-1] {;}]$finalbyte
} else {
return \x9b[join $args {;}]
}
}
proc DCS {args} {
return \x1bP[join $args {;}]\x1b\\
}
proc DCS8 {args} {
return \x90[join $args {;}]\x9c
}
proc OSC {args} {
return \x1b\][join $args {;}]\x1b\\
}
proc OSC8 {args} {
return \x9d[join $args {;}]\x9c
}
}
namespace eval punk::ansi::colour {
package require punk::assertion
if {[catch {namespace import ::punk::assertion::assert} errM]} {
puts stderr "punk error importing punk::assertion::assert\n$errM"
puts stderr "punk::a* commands:[info commands ::punk::a*]"
}
punk::assertion::active on
#see also colors package
#https://sourceforge.net/p/irrational-numbers/code/HEAD/tree/pkgs/Colors/trunk/colors.tcl#l159
# classic formula for luminance (0.0 .. 100.0)
proc luminance {R G B} {
return [expr {(0.3*$R + 0.59*$G + 0.11*$B)/255.0}]
}
#New colour's luminance is dark if orig-colour is bright, and viceversa
#(note not all colours are invertable to return original)
proc contrasting {R G B} {
set lum [luminance $R $G $B]
if {$lum < 0.597} {
set lum 0.9
} else {
set lum 0.2
}
lassign [RGB2hsl $R $G $B] h s l
return [hsl2RGB $h $s $lum]
}
proc contrast_pair {R G B} {
set contra [contrasting $R $G $B]
set back [contrasting {*}$contra]
return [list $back $contra] ;#back may or may not equal original R G B
}
proc hsl2RGB { H S L } {
if { $L < 0.5 } {
set Q [expr {$L*(1.0+$S)}]
} else {
set Q [expr {$L+$S-($L*$S)}]
}
set P [expr {2.0*$L-$Q}]
set Hk [expr {$H/360.0}]
set T(R) [expr {$Hk+1.0/3.0}]
set T(G) $Hk
set T(B) [expr {$Hk-1.0/3.0}]
# normalize
foreach c {R G B} {
if {$T($c) < 0.0} { set T($c) [expr {$T($c)+1.0}] }
if {$T($c) > 1.0} { set T($c) [expr {$T($c)-1.0}] }
}
foreach c {R G B} {
if {$T($c) < [expr {1.0/6.0}]} {
set T($c) [expr {$P+($Q-$P)*6.0*$T($c)}]
} elseif {$T($c) < 0.5} {
set T($c) $Q
} elseif {$T($c) < [expr {2.0/3.0}]} {
set T($c) [expr {$P+($Q-$P)*(2.0/3.0-$T($c))*6.0}]
} else {
set T($c) $P
}
set T($c) [expr {round($T($c)*255)}]
}
return [list $T(R) $T(G) $T(B)]
}
proc RGB2hsl { R G B } {
set r [expr {$R/255.0}]
set g [expr {$G/255.0}]
set b [expr {$B/255.0}]
set max $r
set min $r
if { $g > $max } { set max $g }
if { $g < $min } { set min $g }
if { $b > $max } { set max $b }
if { $b < $min } { set min $b }
if { $max == $min } {
set H 0.0
} elseif { $b == $max } {
set H [expr {60* ($r-$g)/($max-$min)+240}]
} elseif { $g == $max } {
set H [expr {60* ($b-$r)/($max-$min)+120}]
} else {
# $r == $max
if { $g >= $b } {
set H [expr {60* ($g-$b)/($max-$min)}]
} else {
set H [expr {60* ($g-$b)/($max-$min)+360}]
}
}
set L [expr {($max+$min)/2}]
if { $L == 0.0 || $max == $min } {
set S 0.0
} elseif { $L <= 0.5 } {
set S [expr {($max-$min)/($max+$min)}]
} else {
set S [expr {($max-$min)/(2.0-($max+$min))}]
}
return [list $H $S $L]
}
#red green blue to hsl (hue saturation luminance)
#https://www.rapidtables.com/convert/color/rgb-to-hsl.html
proc jexer_rgb_to_hsl {red green blue} {
#algorithm port from Jexer LegacySixelEncode.java - with thanks to Autumn Lamonte (MIT lic)
assert {$red >=0 && $red <= 255}
assert {$green >=0 && $green <= 255}
assert {$blue >=0 && $blue <= 255}
set R [expr {$red / 255.0}]
set G [expr {$green / 255.0}]
set B [expr {$blue / 255.0}]
set Rmax 0
set Gmax 0
set Bmax 0
set min [expr {$R < $G ? $R : $G}]
set min [expr {$min < $B ? $min : $B}]
set max 0
if {($R >= $G) && ($R >= $B)} {
set max $R
set Rmax 1
} elseif {($G >= $R) && ($G >= $B)} {
set max $G
set Gmax 1
} elseif {($B >= $G) && ($B >= $R)} {
set max $B
set Bmax 1
}
set L [expr {($min + $max) / 2.0}]
set H 0.0
set S 0.0
#REVIEW - java allows floating point division by 0.0 - producing positive infinity, negative infinity or NaN
#This makes the original java algorithm a little more obscure
if {$min != $max} {
#no divide by zero issues due to min != max
if {$L < 0.5} {
set S [expr {($max - $min) / ($max + $min)}]
} else {
set S [expr {($max - $min) / (2.0 - $max - $min)}]
}
}
if {$Rmax} {
#puts "G'$G' B'$B' max'$max' min'$min'"
assert {$Gmax == 0}
assert {$Bmax == 0}
if {($max - $min) == 0} {
set H 0.0 ;#review
} else {
set H [expr {($G - $B) / ($max - $min)}]
}
} elseif {$Gmax} {
assert {$Rmax == 0}
assert {$Bmax == 0}
if {($max - $min) == 0} {
set H 2.0
} else {
set H [expr {2.0 + ($B - $R) / ($max - $min)}]
}
} elseif {$Bmax} {
assert {$Rmax == 0}
assert {$Gmax == 0}
if {($max - $min) == 0} {
set H 4.0
} else {
set H [expr {4.0 + ($R - $G) / ($max - $min)}]
}
}
if {$H < 0.0} {
set H [expr {$H + 6.0}]
}
#Tcl mathfunc round vs int (which rounds down)
set hue [expr {round($H * 60)}]
set sat [expr {round($S * 100)}]
set lum [expr {round($L * 100)}]
assert {$hue >= 0 && $hue <= 360}
assert {$sat >= 0 && $sat <= 100}
assert {$lum >= 0 && $lum <= 100}
return [list $hue $sat $lum]
}
}
tcl::namespace::eval punk::ansi::internal { tcl::namespace::eval punk::ansi::internal {
proc splitn {str {len 1}} { proc splitn {str {len 1}} {
#from textutil::split::splitn #from textutil::split::splitn
@ -6837,7 +7283,7 @@ tcl::namespace::eval punk::ansi::internal {
if {$2digithexchars eq ""} { if {$2digithexchars eq ""} {
return "" return ""
} }
if {[tcl::string::length $2digithexchars] % 2 != 0} { if {[tcl::string::length $2digithexchars] % 2} {
error "hex2str requires an even number of hex digits (2 per character)" error "hex2str requires an even number of hex digits (2 per character)"
} }
set 2str "" set 2str ""

143
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/args-0.1.0.tm

@ -1,6 +1,6 @@
# -*- tcl -*- # -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'pmix make' or bin/punkmake to update from <pkg>-buildversion.txt # Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'pmix make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.2.tm # module template: punkshell/src/decktemplates/vendor/punk/modules/template_module-0.0.2.tm
# #
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. # Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. # Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
@ -202,6 +202,7 @@
#[list_begin itemized] #[list_begin itemized]
package require Tcl 8.6- package require Tcl 8.6-
#optional? punk::trie #optional? punk::trie
#optional? punk::textblock
#*** !doctools #*** !doctools
#[item] [package {Tcl 8.6-}] #[item] [package {Tcl 8.6-}]
@ -267,7 +268,10 @@ tcl::namespace::eval punk::args {
#[para] Core API functions for punk::args #[para] Core API functions for punk::args
#[list_begin definitions] #[list_begin definitions]
#todo - some sort of punk::args::cherrypick operation to get spec from an existing set
#todo - doctools output from definition
#dict get value with default wrapper for tcl 8.6
if {[info commands ::tcl::dict::getdef] eq ""} { if {[info commands ::tcl::dict::getdef] eq ""} {
#package require punk::lib #package require punk::lib
#interp alias "" ::punk::args::Dict_getdef "" ::punk::lib::dict_getdef #interp alias "" ::punk::args::Dict_getdef "" ::punk::lib::dict_getdef
@ -301,7 +305,7 @@ tcl::namespace::eval punk::args {
#review - how to make work with trie prefix e.g -corner -aliases {-corners} #review - how to make work with trie prefix e.g -corner -aliases {-corners}
#We mightn't want the prefix to be longer just because of an alias #We mightn't want the prefix to be longer just because of an alias
proc Get_argspecs {optionspecs args} { proc definition {optionspecs args} {
variable argspec_cache variable argspec_cache
#variable argspecs ;#REVIEW!! #variable argspecs ;#REVIEW!!
variable argspec_ids variable argspec_ids
@ -434,6 +438,7 @@ tcl::namespace::eval punk::args {
} }
} }
set proc_info {} set proc_info {}
set id_info {} ;#e.g -children <list> ??
set opt_any 0 set opt_any 0
set val_min 0 set val_min 0
set val_max -1 ;#-1 for no limit set val_max -1 ;#-1 for no limit
@ -444,8 +449,8 @@ tcl::namespace::eval punk::args {
"" - # {continue} "" - # {continue}
} }
set linespecs [lassign $trimln argname] set linespecs [lassign $trimln argname]
if {$argname ne "*id" && [llength $linespecs] %2 != 0} { if {$argname ne "*id" && [llength $linespecs] % 2} {
error "punk::args::get_dict - bad optionspecs line for record '$argname' Remaining items on line must be in paired option-value format - received '$linespecs'" error "punk::args::definition - bad optionspecs line for record '$argname' Remaining items on line must be in paired option-value format - received '$linespecs'"
} }
set firstchar [tcl::string::index $argname 0] set firstchar [tcl::string::index $argname 0]
set secondchar [tcl::string::index $argname 1] set secondchar [tcl::string::index $argname 1]
@ -454,14 +459,18 @@ tcl::namespace::eval punk::args {
switch -- [tcl::string::range $argname 1 end] { switch -- [tcl::string::range $argname 1 end] {
id { id {
#id line must have single entry - a unique id assigned by the user - an id will be allocated if no id line present or the value is "auto" #id line must have single entry - a unique id assigned by the user - an id will be allocated if no id line present or the value is "auto"
if {[llength $starspecs] != 1} { if {[llength $starspecs] == 0} {
error "punk::args::Get_argspecs - *id line must have a single entry following *id." error "punk::args::definition - *id line must have at least a single entry following *id."
} }
if {$spec_id ne ""} { if {$spec_id ne ""} {
#disallow duplicate *id line #disallow duplicate *id line
error "punk::args::Get_argspecs - *id already set. Existing value $spec_id" error "punk::args::definition - *id already set. Existing value $spec_id"
}
set spec_id [lindex $starspecs 0]
set id_info [lrange $starspecs 1 end]
if {[llength $id_info] %2} {
error "punk::args::definition - bad *id line. Remaining items on line after *id <id> must be in paired option-value format - received '$linespecs'"
} }
set spec_id $starspecs
} }
proc { proc {
#allow arbitrary - review #allow arbitrary - review
@ -523,7 +532,7 @@ tcl::namespace::eval punk::args {
-type -optional -allow_ansi -validate_ansistripped -strip_ansi -multiple\ -type -optional -allow_ansi -validate_ansistripped -strip_ansi -multiple\
-regexprepass -regexprefail -validationtransform\ -regexprepass -regexprefail -validationtransform\
} }
error "punk::args::Get_argspecs - unrecognised key '$k' in *opts line. Known keys: $known" error "punk::args::definition - unrecognised key '$k' in *opts line. Known keys: $known"
} }
} }
} }
@ -534,14 +543,14 @@ tcl::namespace::eval punk::args {
-min - -min -
-minvalues { -minvalues {
if {$v < 0} { if {$v < 0} {
error "punk::args::Get_argspecs - minimum acceptable value for key '$k' in *opts line is 0. got $v" error "punk::args::definition - minimum acceptable value for key '$k' in *opts line is 0. got $v"
} }
set val_min $v set val_min $v
} }
-max - -max -
-maxvalues { -maxvalues {
if {$v < -1} { if {$v < -1} {
error "punk::args::Get_argspecs - minimum acceptable value for key '$k' in *opts line is -1 (indicating unlimited). got $v" error "punk::args::definition - minimum acceptable value for key '$k' in *opts line is -1 (indicating unlimited). got $v"
} }
set val_max $v set val_max $v
} }
@ -594,14 +603,14 @@ tcl::namespace::eval punk::args {
-type -optional -allow_ansi -validate_ansistripped -strip_ansi -multiple\ -type -optional -allow_ansi -validate_ansistripped -strip_ansi -multiple\
-regexprepass -regexprefail -validationtransform\ -regexprepass -regexprefail -validationtransform\
} }
error "punk::args::Get_argspecs - unrecognised key '$k' in *values line. Known keys: $known" error "punk::args::definition - unrecognised key '$k' in *values line. Known keys: $known"
} }
} }
} }
} }
default { default {
error "punk::args::Get_argspecs - unrecognised * line in '$ln'. Expected *proc *opts or *values - use **name if paramname needs to be *name" error "punk::args::definition - unrecognised * line in '$ln'. Expected *proc *opts or *values - use **name if paramname needs to be *name"
} }
} }
continue continue
@ -654,7 +663,7 @@ tcl::namespace::eval punk::args {
lappend opt_solos $argname lappend opt_solos $argname
} else { } else {
#-solo only valid for flags #-solo only valid for flags
error "punk::args::get_dict - invalid -type 'none' for positional argument positional argument '$argname'" error "punk::args::definition - invalid -type 'none' for positional argument positional argument '$argname'"
} }
} }
any - anything { any - anything {
@ -681,8 +690,8 @@ tcl::namespace::eval punk::args {
} }
-validationtransform { -validationtransform {
#string is dict only 8.7/9+ #string is dict only 8.7/9+
if {([llength $specval] % 2) != 0} { if {[llength $specval] % 2} {
error "punk::args::get_dict - invalid value for key '$spec' in specifications for argument '$argname' - value must be a dictionary" error "punk::args::definition - invalid value for key '$spec' in specifications for argument '$argname' - value must be a dictionary"
} }
dict for {tk tv} $specval { dict for {tk tv} $specval {
switch -- $tk { switch -- $tk {
@ -690,7 +699,7 @@ tcl::namespace::eval punk::args {
} }
default { default {
set known_transform_keys [list -function -type -minlen -maxlen -range] ;#-choices etc? set known_transform_keys [list -function -type -minlen -maxlen -range] ;#-choices etc?
error "punk::args::get_dict - invalid subkey $tk for key '$spec' in specifications for argument '$argname' must be one of: $known_transform_keys" error "punk::args::definition - invalid subkey $tk for key '$spec' in specifications for argument '$argname' must be one of: $known_transform_keys"
} }
} }
} }
@ -701,7 +710,7 @@ tcl::namespace::eval punk::args {
-nocase -optional -multiple -validate_ansistripped -allow_ansi -strip_ansi -help\ -nocase -optional -multiple -validate_ansistripped -allow_ansi -strip_ansi -help\
-regexprepass -regexprefail -validationtransform\ -regexprepass -regexprefail -validationtransform\
] ]
error "punk::args::get_dict - unrecognised key '$spec' in specifications for argument '$argname' Known option specification keys: $known_argspecs" error "punk::args::definition - unrecognised key '$spec' in specifications for argument '$argname' Known option specification keys: $known_argspecs"
} }
} }
} }
@ -764,6 +773,7 @@ tcl::namespace::eval punk::args {
valspec_defaults $valspec_defaults\ valspec_defaults $valspec_defaults\
val_checks_defaults $val_checks_defaults\ val_checks_defaults $val_checks_defaults\
proc_info $proc_info\ proc_info $proc_info\
id_info $id_info\
] ]
tcl::dict::set argspec_cache $cache_key $result tcl::dict::set argspec_cache $cache_key $result
#tcl::dict::set argspecs $spec_id $optionspecs #tcl::dict::set argspecs $spec_id $optionspecs
@ -817,6 +827,7 @@ tcl::namespace::eval punk::args {
} }
set caller [regexp -inline {\S+} $cmdinfo] set caller [regexp -inline {\S+} $cmdinfo]
if {$caller eq "namespace"} { if {$caller eq "namespace"} {
# review - message?
set cmdinfo "punk::args::get_dict called from namespace" set cmdinfo "punk::args::get_dict called from namespace"
} }
return $cmdinfo return $cmdinfo
@ -825,6 +836,7 @@ tcl::namespace::eval punk::args {
#basic recursion blocker #basic recursion blocker
variable arg_error_isrunning 0 variable arg_error_isrunning 0
proc arg_error {msg spec_dict {badarg ""}} { proc arg_error {msg spec_dict {badarg ""}} {
#limit colours to standard 16 so that themes can apply to help output
variable arg_error_isrunning variable arg_error_isrunning
if {$arg_error_isrunning} { if {$arg_error_isrunning} {
error "arg_error already running - error in arg_error?\n triggering errmsg: $badarg" error "arg_error already running - error in arg_error?\n triggering errmsg: $badarg"
@ -843,20 +855,17 @@ tcl::namespace::eval punk::args {
set procname [::punk::args::Dict_getdef $spec_dict proc_info -name ""] set procname [::punk::args::Dict_getdef $spec_dict proc_info -name ""]
set prochelp [::punk::args::Dict_getdef $spec_dict proc_info -help ""] set prochelp [::punk::args::Dict_getdef $spec_dict proc_info -help ""]
#set t [textblock::class::table new [a+ web-yellow]Usage[a]]
set t [textblock::class::table new [a+ brightyellow]Usage[a]] set t [textblock::class::table new [a+ brightyellow]Usage[a]]
set blank_header_col [list ""] set blank_header_col [list ""]
if {$procname ne ""} { if {$procname ne ""} {
lappend blank_header_col "" lappend blank_header_col ""
#set procname_display [a+ web-white]$procname[a]
set procname_display [a+ brightwhite]$procname[a] set procname_display [a+ brightwhite]$procname[a]
} else { } else {
set procname_display "" set procname_display ""
} }
if {$prochelp ne ""} { if {$prochelp ne ""} {
lappend blank_header_col "" lappend blank_header_col ""
#set prochelp_display [a+ web-white]$prochelp[a]
set prochelp_display [a+ brightwhite]$prochelp[a] set prochelp_display [a+ brightwhite]$prochelp[a]
} else { } else {
set prochelp_display "" set prochelp_display ""
@ -880,12 +889,19 @@ tcl::namespace::eval punk::args {
$t configure_header 2 -values {Arg Type Default Multiple Help} $t configure_header 2 -values {Arg Type Default Multiple Help}
} }
#set c_default [a+ web-white Web-limegreen]
set c_default [a+ brightwhite Brightgreen] set RST [a]
#set c_badarg [a+ web-crimson] #set A_DEFAULT [a+ brightwhite Brightgreen]
set c_badarg [a+ brightred] set A_DEFAULT ""
#set greencheck [a+ web-limegreen]\u2713[a] set A_BADARG [a+ brightred]
set greencheck [a+ brightgreen]\u2713[a] set greencheck [a+ brightgreen]\u2713[a]
set A_PREFIX [a+ green] ;#use a+ so colour off can apply
if {$A_PREFIX eq ""} {
set A_PREFIX [a+ underline]
set A_PREFIXEND [a+ nounderline]\u200B ;#padding will take ANSI from last char - so add a zero width space
} else {
set A_PREFIXEND $RST
}
set opt_names [list] set opt_names [list]
set opt_names_display [list] set opt_names_display [list]
@ -894,8 +910,6 @@ tcl::namespace::eval punk::args {
set trie [punk::trie::trieclass new {*}[dict get $spec_dict opt_names]] set trie [punk::trie::trieclass new {*}[dict get $spec_dict opt_names]]
set idents [dict get [$trie shortest_idents ""] scanned] set idents [dict get [$trie shortest_idents ""] scanned]
$trie destroy $trie destroy
set M "\x1b\[32m" ;#mark in green
set RST "\x1b\[m"
foreach c [dict get $spec_dict opt_names] { foreach c [dict get $spec_dict opt_names] {
set id [dict get $idents $c] set id [dict get $idents $c]
#REVIEW #REVIEW
@ -907,7 +921,7 @@ tcl::namespace::eval punk::args {
set prefix [string range $c 0 $idlen-1] set prefix [string range $c 0 $idlen-1]
set tail [string range $c $idlen end] set tail [string range $c $idlen end]
} }
lappend opt_names_display $M$prefix$RST$tail lappend opt_names_display $A_PREFIX$prefix$A_PREFIXEND$tail
#lappend opt_names_display $M[ansistring VIEW $prefix]$RST[ansistring VIEW $tail] #lappend opt_names_display $M[ansistring VIEW $prefix]$RST[ansistring VIEW $tail]
lappend opt_names $c lappend opt_names $c
} }
@ -916,18 +930,31 @@ tcl::namespace::eval punk::args {
set opt_names_display $opt_names set opt_names_display $opt_names
} }
} }
set val_names [dict get $spec_dict val_names] set trailing_val_names [dict get $spec_dict val_names] ;#temporarily assign all as trailing
set val_names_display $val_names set leading_val_names [list]
dict for {argname info} [tcl::dict::get $spec_dict arg_info] {
if {![string match -* $argname]} {
lappend leading_val_names [lpop trailing_val_names 0]
} else {
break
}
}
if {![llength $leading_val_names] && ![llength $opt_names]} {
#all vals were actually trailing - no opts
set trailing_val_names $leading_val_names
set leading_val_names {}
}
set leading_val_names_display $leading_val_names
set trailing_val_names_display $trailing_val_names
#display options first then values #display options first then values
foreach argumentset [list [list $opt_names_display $opt_names] [list $val_names_display $val_names]] { foreach argumentset [list [list $leading_val_names_display $leading_val_names] [list $opt_names_display $opt_names] [list $trailing_val_names_display $trailing_val_names]] {
lassign $argumentset argnames_display argnames lassign $argumentset argnames_display argnames
foreach argshow $argnames_display arg $argnames { foreach argshow $argnames_display arg $argnames {
set arginfo [dict get $spec_dict arg_info $arg] set arginfo [dict get $spec_dict arg_info $arg]
if {[dict exists $arginfo -default]} { if {[dict exists $arginfo -default]} {
#set default $c_default[dict get $arginfo -default] set default $A_DEFAULT[dict get $arginfo -default]$RST
set default [dict get $arginfo -default]
} else { } else {
set default "" set default ""
} }
@ -954,8 +981,6 @@ tcl::namespace::eval punk::args {
set trie [punk::trie::trieclass new {*}[dict get $arginfo -choices]] set trie [punk::trie::trieclass new {*}[dict get $arginfo -choices]]
set idents [dict get [$trie shortest_idents ""] scanned] set idents [dict get [$trie shortest_idents ""] scanned]
$trie destroy $trie destroy
set M "\x1b\[32m" ;#mark in green
set RST "\x1b\[m"
foreach c [dict get $arginfo -choices] { foreach c [dict get $arginfo -choices] {
set id [dict get $idents $c] set id [dict get $idents $c]
if {$id eq $c} { if {$id eq $c} {
@ -966,7 +991,7 @@ tcl::namespace::eval punk::args {
set prefix [string range $c 0 $idlen-1] set prefix [string range $c 0 $idlen-1]
set tail [string range $c $idlen end] set tail [string range $c $idlen end]
} }
lappend formattedchoices "$M[ansistring VIEW $prefix]$RST[ansistring VIEW $tail]" lappend formattedchoices "$A_PREFIX[ansistring VIEW $prefix]$A_PREFIXEND[ansistring VIEW $tail]"
} }
} errM]} { } errM]} {
puts stderr "prefix marking failed\n$errM" puts stderr "prefix marking failed\n$errM"
@ -999,7 +1024,7 @@ tcl::namespace::eval punk::args {
} }
$t add_row [list $argshow [dict get $arginfo -type] $default $multiple $help] $t add_row [list $argshow [dict get $arginfo -type] $default $multiple $help]
if {$arg eq $badarg} { if {$arg eq $badarg} {
$t configure_row [expr {[$t row_count]-1}] -ansibase $c_badarg $t configure_row [expr {[$t row_count]-1}] -ansibase $A_BADARG
} }
} }
} }
@ -1033,10 +1058,10 @@ tcl::namespace::eval punk::args {
#provide ability to look up and reuse definitions from ids etc #provide ability to look up and reuse definitions from ids etc
# #
proc get_dict_by_id {id {arglist ""}} { proc get_by_id {id {arglist ""}} {
set spec [get_spec $id] set spec [get_spec $id]
if {$spec eq ""} { if {$spec eq ""} {
error "punk::args::get_dict_by_id - no such id: $id" error "punk::args::get_by_id - no such id: $id"
} }
return [get_dict $spec $arglist] return [get_dict $spec $arglist]
} }
@ -1121,7 +1146,7 @@ tcl::namespace::eval punk::args {
} }
set argspecs [Get_argspecs $optionspecs] set argspecs [definition $optionspecs]
tcl::dict::with argspecs {} ;#turn keys into vars tcl::dict::with argspecs {} ;#turn keys into vars
#puts "-arg_info->$arg_info" #puts "-arg_info->$arg_info"
set flagsreceived [list] ;#for checking if required flags satisfied set flagsreceived [list] ;#for checking if required flags satisfied
@ -1132,11 +1157,24 @@ tcl::namespace::eval punk::args {
#todo: -minmultiple -maxmultiple ? #todo: -minmultiple -maxmultiple ?
# -- --- --- ---
# Handle leading positionals
# todo - consider allowing last leading positional to have -multiple 1 but only if there exists an eopts marker later (--) ?
set opts $opt_defaults set opts $opt_defaults
set pre_values {}
dict for {a info} $arg_info {
#todo - flag for possible subhandler - whether leading - or not (shellfilter concept)
if {![string match -* $a]} {
lappend pre_values [lpop rawargs 0]
} else {
break
}
}
#assert - rawargs has been reduced by leading positionals
if {$id ne "jtest"} { if {$id ne "jtest"} {
set arglist {} set arglist {}
set values {} set post_values {}
#val_min, val_max #val_min, val_max
#puts stderr "rawargs: $rawargs" #puts stderr "rawargs: $rawargs"
#puts stderr "arg_info: $arg_info" #puts stderr "arg_info: $arg_info"
@ -1157,7 +1195,7 @@ tcl::namespace::eval punk::args {
if {$remaining_args_including_this <= $val_min} { if {$remaining_args_including_this <= $val_min} {
# if current arg is -- it will pass through as a value here # if current arg is -- it will pass through as a value here
set arglist [lrange $rawargs 0 $i-1] set arglist [lrange $rawargs 0 $i-1]
set values [lrange $rawargs $i end] set post_values [lrange $rawargs $i end]
break break
} }
@ -1169,19 +1207,19 @@ tcl::namespace::eval punk::args {
if {$remaining_args_including_this == $val_max} { if {$remaining_args_including_this == $val_max} {
#assume it's a value. #assume it's a value.
set arglist [lrange $rawargs 0 $i-1] set arglist [lrange $rawargs 0 $i-1]
set values [lrange $rawargs $i end] set post_values [lrange $rawargs $i end]
} else { } else {
#assume it's an end-of-options marker #assume it's an end-of-options marker
lappend flagsreceived -- lappend flagsreceived --
set arglist [lrange $rawargs 0 $i] set arglist [lrange $rawargs 0 $i]
set values [lrange $rawargs $i+1 end] set post_values [lrange $rawargs $i+1 end]
} }
} else { } else {
#unlimited number of values accepted #unlimited number of post_values accepted
#treat this as eopts - we don't care if remainder look like options or not #treat this as eopts - we don't care if remainder look like options or not
lappend flagsreceived -- lappend flagsreceived --
set arglist [lrange $rawargs 0 $i] set arglist [lrange $rawargs 0 $i]
set values [lrange $rawargs $i+1 end] set post_values [lrange $rawargs $i+1 end]
} }
break break
} else { } else {
@ -1194,7 +1232,7 @@ tcl::namespace::eval punk::args {
#if no optvalue following - assume it's a value #if no optvalue following - assume it's a value
#(caller should probably have used -- before it) #(caller should probably have used -- before it)
set arglist [lrange $rawargs 0 $i-1] set arglist [lrange $rawargs 0 $i-1]
set values [lrange $rawargs $i end] set post_values [lrange $rawargs $i end]
break break
} }
@ -1242,7 +1280,7 @@ tcl::namespace::eval punk::args {
#unmatched option in right position to be considered a value - treat like eopts #unmatched option in right position to be considered a value - treat like eopts
#review - document that an unspecified arg within range of possible values will act like eopts -- #review - document that an unspecified arg within range of possible values will act like eopts --
set arglist [lrange $rawargs 0 $i-1] set arglist [lrange $rawargs 0 $i-1]
set values [lrange $rawargs $i end] set post_values [lrange $rawargs $i end]
break break
} }
if {$opt_any} { if {$opt_any} {
@ -1284,12 +1322,13 @@ tcl::namespace::eval punk::args {
} else { } else {
#not flaglike #not flaglike
set arglist [lrange $rawargs 0 $i-1] set arglist [lrange $rawargs 0 $i-1]
set values [lrange $rawargs $i end] set post_values [lrange $rawargs $i end]
break break
} }
} }
set values [list {*}$pre_values {*}$post_values]
} else { } else {
set values $rawargs ;#no -flags detected set values [list {*}$pre_values {*}$rawargs] ;#no -flags detected
set arglist [list] set arglist [list]
} }
#puts stderr "--> arglist: $arglist" #puts stderr "--> arglist: $arglist"

102
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/char-0.1.0.tm

@ -1912,19 +1912,32 @@ tcl::namespace::eval punk::char {
tailcall ansifreestring_width $text tailcall ansifreestring_width $text
} }
#faster than textutil::wcswidth (at least for string up to a few K in length) #todo - consider disallowing/erroring out when \r \n in string?
# - tab/vtab?
# - compare with wcswidth returning -1 for entire string containing such in python,perl
proc wcswidth {string} { proc wcswidth {string} {
#faster than textutil::wcswidth (at least for string up to a few K in length)
#..but - 'scan' is horrible for 400K+
#TODO
set codes [scan $string [tcl::string::repeat %c [tcl::string::length $string]]] set codes [scan $string [tcl::string::repeat %c [tcl::string::length $string]]]
set width 0 set width 0
foreach c $codes { foreach c $codes {
if {$c <= 255} { #unicode Tags block zero width
incr width if {$c < 917504 || $c > 917631} {
} else { if {$c <= 255} {
set w [textutil::wcswidth_char $c] #review - non-printing ascii? why does textutil::wcswidth report 1 ??
if {$w < 0} { #todo - compare with python or other lang wcwidth
return -1 if {!($c < 31 || $c == 127)} {
incr width
}
} else { } else {
#TODO - various other joiners and non-printing chars
set w [textutil::wcswidth_char $c]
if {$w < 0} {
return -1
} else {
incr width $w incr width $w
}
} }
} }
} }
@ -2029,7 +2042,8 @@ tcl::namespace::eval punk::char {
# return [tcl::string::length $text] # return [tcl::string::length $text]
#} #}
if {![regexp "\[\uFF-\U10FFFF\]" $text]} { if {![regexp "\[\uFF-\U10FFFF\]" $text]} {
return [tcl::string::length $text] #return [tcl::string::length $text]
return [punk::char::wcswidth $text] ;#still use our wcswidth to account for non-printable ascii
} }
#split just to get the standalone character widths - and then scan for other combiners (?) - or scan for clusters first? #split just to get the standalone character widths - and then scan for other combiners (?) - or scan for clusters first?
@ -2052,7 +2066,7 @@ tcl::namespace::eval punk::char {
#fall back to textutil::wcswidth (which doesn't for example handle diactricts/combiners so we can't use until these and other things such as \u200b and diacritics are already stripped/accounted for) #fall back to textutil::wcswidth (which doesn't for example handle diactricts/combiners so we can't use until these and other things such as \u200b and diacritics are already stripped/accounted for)
#todo - find something that understands grapheme clusters - needed also for grapheme_split #todo - find something that understands grapheme clusters - needed also for grapheme_split
#use punk::char::wcswidth - faster than the string split in textutil::wcswidth but still uses textutil::wcswidth_char #use punk::char::wcswidth - faster than the string split in textutil::wcswidth but still uses textutil::wcswidth_char
incr len [wcswidth $uc] incr len [punk::char::wcswidth $uc]
} }
#todo - work out what to do with anomalies like grave combiner which print at different lengths on different terminals (fonts?) and for which cursor-query escape sequence lies. #todo - work out what to do with anomalies like grave combiner which print at different lengths on different terminals (fonts?) and for which cursor-query escape sequence lies.
return $len return $len
@ -2229,7 +2243,68 @@ tcl::namespace::eval punk::char {
return [tcl::string::map $map $str] return [tcl::string::map $map $str]
} }
#todo - lookup from unicode tables
variable flags [dict create\
AU \U1F1E6\U1F1FA\
US \U1F1FA\U1F1F8\
ZW \U1F1FF\U1F1FC
]
variable rflags
dict for {k v} $flags {
dict set rflags $v $k
}
proc flag_from_ascii {code} {
variable flags
if {[regexp {^[A-Z]{2}$} $code]} {
if {[dict exists $flags $code]} {
return [dict get $flags $code]
} else {
error "unsupported flags code: $code"
}
} else {
#try as subregion
#e.g gbeng,gbwls,gbsct
return \U1f3f4[tag_from_ascii $code]\Ue007f
}
}
proc flag_to_ascii {charsequence} {
variable rflags
if {[dict exists $rflags $charsequence]} {
return [dict get $rflags $charsequence]
}
if {[string index $charsequence 0] eq "\U1F3F4" && [string index $charsequence end] eq "\UE007F"} {
#subdivision flag
set tag [string range $charsequence 1 end-1]
return [tag_to_ascii $tag]
}
error "unknown flag $charsequence"
}
proc tag_to_ascii {t} {
set fmt [string repeat %c [string length $t]]
set declist [scan $t $fmt]
#unicode Tags block - e0000 to e007f
set declist [lmap dec $declist {
if {$dec < 917504 || $dec > 917631} {
error "char [ansistring VIEW -lf 1 -cr 1 -vt 1 [format %c $dec]] has decimal value $dec. Not in unicode Tags block range 917504-917631 (e0000-e007f)"
}
incr dec -917504
}]
return [format $fmt {*}$declist]
}
proc tag_from_ascii {a} {
set fmt [string repeat %c [string length $a]]
set declist [scan $a $fmt]
set declist [lmap dec $declist {
if {$dec > 127} {
error "char [ansistring VIEW -lf 1 -cr 1 -vt 1 [format %c $dec]] has decimal value $dec. Not in ascii range 0-127"
}
incr dec 917504
}]
return [format $fmt {*}$declist]
}
#split into plaintext and runs of combiners (combining diacritical marks - not ZWJ or ZWJNJ) #split into plaintext and runs of combiners (combining diacritical marks - not ZWJ or ZWJNJ)
proc combiner_split {text} { proc combiner_split {text} {
@ -2250,15 +2325,12 @@ tcl::namespace::eval punk::char {
#puts "->start $start ->match $matchStart $matchEnd" #puts "->start $start ->match $matchStart $matchEnd"
lappend list [tcl::string::range $text $start $matchStart-1] [tcl::string::range $text $matchStart $matchEnd] lappend list [tcl::string::range $text $start $matchStart-1] [tcl::string::range $text $matchStart $matchEnd]
set start [expr {$matchEnd+1}] set start [expr {$matchEnd+1}]
#if {$start >= [tcl::string::length $text]} {
# break
#}
} }
lappend list [tcl::string::range $text $start end] lappend list [tcl::string::range $text $start end]
} }
#ZWJ ZWNJ ? #ZWJ ZWNJ ?
#SWSP ?
#1st shot - basic diacritics #1st shot - basic diacritics
#todo - become aware of unicode grapheme cluster boundaries #todo - become aware of unicode grapheme cluster boundaries
@ -2269,6 +2341,8 @@ tcl::namespace::eval punk::char {
#should also handle the ZWJ (and the variation selectors? eg \uFE0F) character which should account for emoji clusters #should also handle the ZWJ (and the variation selectors? eg \uFE0F) character which should account for emoji clusters
#Note - emoji cluster could be for example 11 code points/41 bytes (family emoji with skin tone modifiers for each member, 3 ZWJs) #Note - emoji cluster could be for example 11 code points/41 bytes (family emoji with skin tone modifiers for each member, 3 ZWJs)
#This still leaves a whole class of clusters.. korean etc unhandled :/ #This still leaves a whole class of clusters.. korean etc unhandled :/
#todo - tk::startOfCluster / tk::endOfCluster - try to get it brought into Tcl
#https://core.tcl-lang.org/tcl/tktview/a4c7eeaf63
proc grapheme_split {text} { proc grapheme_split {text} {
set graphemes [list] set graphemes [list]
set csplits [combiner_split $text] set csplits [combiner_split $text]
@ -2287,7 +2361,7 @@ tcl::namespace::eval punk::char {
set graphemes [list] set graphemes [list]
set csplits [combiner_split $text] set csplits [combiner_split $text]
foreach {pt combiners} [lrange $csplits 0 end-1] { foreach {pt combiners} [lrange $csplits 0 end-1] {
set pt_decs [scan $pt [tcl::string::repeat %c [tcl::string::length $pt]]] set pt_decs [scan $pt [tcl::string::repeat %c [tcl::string::length $pt]]] ;#warning scan %c... slow for v large strings (e.g 400k+)
set combiner_decs [scan $combiners [tcl::string::repeat %c [tcl::string::length $combiners]]] set combiner_decs [scan $combiners [tcl::string::repeat %c [tcl::string::length $combiners]]]
lset pt_decs end [concat [lindex $pt_decs end] $combiner_decs] lset pt_decs end [concat [lindex $pt_decs end] $combiner_decs]
lappend graphemes {*}$pt_decs lappend graphemes {*}$pt_decs

326
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/console-0.1.1.tm

@ -46,9 +46,12 @@
package require Tcl 8.6- package require Tcl 8.6-
package require Thread ;#tsv required to sync is_raw package require Thread ;#tsv required to sync is_raw
package require punk::ansi package require punk::ansi
package require punk::args
#*** !doctools #*** !doctools
#[item] [package {Tcl 8.6-}] #[item] [package {Tcl 8.6-}]
#[item] [package {Thread}]
#[item] [package {punk::ansi}] #[item] [package {punk::ansi}]
#[item] [package {punk::args}]
#*** !doctools #*** !doctools
@ -109,6 +112,8 @@ namespace eval punk::console {
variable ansi_wanted 2 ;#2 for default assumed yes, will be set to -1 for automatically unwanted when ansi unavailable values of 0 or 1 won't be autoset variable ansi_wanted 2 ;#2 for default assumed yes, will be set to -1 for automatically unwanted when ansi unavailable values of 0 or 1 won't be autoset
#punk::console namespace - contains *directly* acting functions - some based on ansi escapes from the 'ansi' sub namespace, some on local system calls or executable calls wrapped in the 'local' sub namespace #punk::console namespace - contains *directly* acting functions - some based on ansi escapes from the 'ansi' sub namespace, some on local system calls or executable calls wrapped in the 'local' sub namespace
#directly acting means they write to stdout to cause the console to peform the action, or they perform the action immediately via other means. #directly acting means they write to stdout to cause the console to peform the action, or they perform the action immediately via other means.
#punk::console::ansi contains a subset of punk::ansi, but with emission to stdout as opposed to simply returning the ansi sequence. #punk::console::ansi contains a subset of punk::ansi, but with emission to stdout as opposed to simply returning the ansi sequence.
@ -255,6 +260,8 @@ namespace eval punk::console {
enable_bracketed_paste enable_bracketed_paste
} }
#todo stop_application_mode {} {}
proc mode {{raw_or_line query}} { proc mode {{raw_or_line query}} {
#variable is_raw #variable is_raw
variable ansi_available variable ansi_available
@ -634,7 +641,7 @@ namespace eval punk::console {
#todo - make timeout configurable? #todo - make timeout configurable?
set waitvarname "::punk::console::ansi_response_wait($callid)" set waitvarname "::punk::console::ansi_response_wait($callid)"
#500ms is generally plenty for a terminal to respond.. but not in some cases. e.g event loop busy with stdin keypress?? review #500ms is generally plenty for a terminal to respond.. but not in some cases. e.g event loop busy with stdin keypress?? review
set timeoutid($callid) [after 2000 [list set $waitvarname timedout]] set timeoutid($callid) [after 1000 [list set $waitvarname timedout]]
#JMN #JMN
# - stderr vs stdout # - stderr vs stdout
@ -1040,6 +1047,64 @@ namespace eval punk::console {
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels] set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload return $payload
} }
variable last_da1_result ""
#TODO - 22? 28? 32?
#1 132 columns
#2 Printer port extension
#4 Sixel extension
#6 Selective erase
#7 DRCS
#8 UDK
#9 NRCS
#12 SCS extension
#15 Technical character set
#18 Windowing capability
#21 Horizontal scrolling
#23 Greek extension
#24 Turkish extension
#42 ISO Latin 2 character set
#44 PCTerm
#45 Soft key map
#46 ASCII emulation
#https://vt100.net/docs/vt510-rm/DA1.html
#
proc get_device_attributes {{inoutchannels {stdin stdout}}} {
#DA1
variable last_da1_result
#first element in result is the terminal's architectural class 61,62,63,64.. ?
#for vt100 we get things like: "ESC\[?1;0c"
#for vt102 "ESC\[?6c"
#set capturingregex {(.*)(\x1b\[\?6[0-9][;]*([;0-9]+)c)$} ;#must capture prefix,entire-response,response-payload
set capturingregex {(.*)(\x1b\[\?([0-9]*[;0-9]+)c)$} ;#must capture prefix,entire-response,response-payload
set request "\x1b\[c"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
set last_da1_result $payload
return $payload
}
#https://vt100.net/docs/vt510-rm/DA2.html
proc get_device_attributes_secondary {{inoutchannels {stdin stdout}}} {
#DA2
set capturingregex {(.*)(\x1b\[\>([0-9]*[;][0-9]*[;][0-1]c))$} ;#must capture prefix,entire-response,response-payload
#expect CSI > X;10|20;0|1c - docs suggest X should be something like 61. In practice we see 0 or 1 (windows) REVIEW
set request "\x1b\[>c"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload
}
proc get_device_attributes_tertiary {{inoutchannels {stdin stdout}}} {
#DA3
set capturingregex {(.*)(\x1bP!\|([0-9]{8})\x1b\\)$} ;#must capture prefix,entire-response,response-payload
set request "\x1b\[=c"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload
}
proc get_terminal_id {{inoutchannels {stdin stdout}}} {
#DA3 - alias
get_device_attributes_tertiary $inoutchannels
}
proc get_tabstops {{inoutchannels {stdin stdout}}} { proc get_tabstops {{inoutchannels {stdin stdout}}} {
#DECTABSR \x1b\[2\$w #DECTABSR \x1b\[2\$w
#response example " ^[P2$u9/17/25/33/41/49/57/65/73/81^[\ " (where ^[ is \x1b) #response example " ^[P2$u9/17/25/33/41/49/57/65/73/81^[\ " (where ^[ is \x1b)
@ -1110,6 +1175,55 @@ namespace eval punk::console {
return [split [get_cursor_pos $inoutchannels] ";"] return [split [get_cursor_pos $inoutchannels] ";"]
} }
#todo - work out how to query terminal and set cell size in pixels
#for now use the windows default
variable cell_size
set cell_size ""
set cell_size_fallback 10x20
#todo - change -inoutchannels to -terminalobject with prebuilt default
punk::args::definition {
*id punk::console::cell_size
-inoutchannels -default {stdin stdout} -type list
*values -min 0 -max 1
newsize -default ""
}
proc cell_size {args} {
set argd [punk::args::get_by_id punk::console::cell_size $args]
set inoutchannels [dict get $argd opts -inoutchannels]
set newsize [dict get $argd values newsize]
variable cell_size
if {$newsize eq ""} {
#query existing setting
if {$cell_size eq ""} {
#not set - try to query terminal's overall dimensions
set pixeldict [punk::console::get_xterm_pixels $inoutchannels]
lassign $pixeldict _w sw _h sh
if {[string is integer -strict $sw] && [string is integer -strict $sh]} {
lassign [punk::console::get_size] _cols columns _rows rows
#review - is returned size in pixels always a multiple of rows and cols?
set w [expr {$sw / $columns}]
set h [expr {$sh / $rows}]
set cell_size ${w}x${h}
return $cell_size
} else {
set cell_size $::punk::console::cell_size_fallback
puts stderr "punk::console::cell_size unable to query terminal for pixel data - using default $cell_size"
return $cell_size
}
}
return $cell_size
}
#newsize supplied - try to set
lassign [split [string tolower $newsize] x] w h
if {![string is integer -strict $w] || ![string is integer -strict $h] || $w < 1 || $h < 1} {
error "punk::sixel::cell_size error - expected format WxH where W and H are positive integers - got '$newsize'"
}
set cell_size ${w}x${h}
}
#todo - determine cursor on/off state before the call to restore properly. #todo - determine cursor on/off state before the call to restore properly.
proc get_size {{inoutchannels {stdin stdout}}} { proc get_size {{inoutchannels {stdin stdout}}} {
lassign $inoutchannels in out lassign $inoutchannels in out
@ -1202,13 +1316,19 @@ namespace eval punk::console {
lassign [split $payload {;}] rows cols lassign [split $payload {;}] rows cols
return [list columns $cols rows $rows] return [list columns $cols rows $rows]
} }
proc get_xterm_pixels {{inoutchannels {stdin stdout}}} {
set capturingregex {(.*)(\x1b\[4;([0-9]+;[0-9]+)t)$} ;#must capture prefix,entire-response,response-payload
set request "\x1b\[14t"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
lassign [split $payload {;}] height width
return [list width $width height $height]
}
proc get_mode_line_wrap {{inoutchannels {stdin stdout}}} { proc get_mode_line_wrap {{inoutchannels {stdin stdout}}} {
set capturingregex {(.*)(\x1b\[\?7;([0-9]+)\$y)$} ;#must capture prefix,entire-response,response-payload set capturingregex {(.*)(\x1b\[\?7;([0-9]+)\$y)$} ;#must capture prefix,entire-response,response-payload
set request "\x1b\[?7\$p" set request "\x1b\[?7\$p"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels] set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload return $payload
} }
#Terminals generally default to LNM being reset (off) ie enter key sends a lone <cr> #Terminals generally default to LNM being reset (off) ie enter key sends a lone <cr>
#Terminals tested on windows either don't respond to this query, or respond with 0 (meaning mode not understood) #Terminals tested on windows either don't respond to this query, or respond with 0 (meaning mode not understood)
#I presume from this that almost nobody is using LNM 1 (which sends both <cr> and <lf>) #I presume from this that almost nobody is using LNM 1 (which sends both <cr> and <lf>)
@ -1218,11 +1338,59 @@ namespace eval punk::console {
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels] set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload return $payload
} }
#DECRPM responses e.g:
# \x1b\[?7\;1\$y
# \x1b\[?7\;2\$y
#where 1 = set, 2 = unset. (0 = mode not recognised, 3 = permanently set, 4 = permanently unset)
proc get_mode {num_or_name {inoutchannels {stdin stdout}}} {
if {[string is integer -strict $num_or_name]} {
set m $num_or_name
} else {
upvar ::punk::ansi::decmode_names decmode_names
if {[dict exists $decmode_names $num_or_name]} {
set m [dict get $decmode_names $num_or_name]
} else {
error "punk::console::get_mode unrecognised mode '$num_or_name'. Known mode names: [dict keys $decmode_names]"
}
}
set capturingregex [string map [list %MODE% $m] {(.*)(\x1b\[\?%MODE%;([0-9]+)\$y)$}] ;#must capture prefix,entire-response,response-payload
set request "\x1b\[?$m\$p"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload
}
proc set_mode {num_or_name {inoutchannels {stdin stdout}}} {
if {[string is integer -strict $num_or_name]} {
set m $num_or_name
} else {
upvar ::punk::ansi::decmode_names decmode_names
if {[dict exists $decmode_names $num_or_name]} {
set m [dict get $decmode_names $num_or_name]
} else {
error "punk::console::set_mode unrecognised mode '$num_or_name'. Known mode names: [dict keys $decmode_names]"
}
}
return "\x1b\[?${m}h"
}
proc unset_mode {num_or_name {inoutchannels {stdin stdout}}} {
if {[string is integer -strict $num_or_name]} {
set m $num_or_name
} else {
upvar ::punk::ansi::decmode_names decmode_names
if {[dict exists $decmode_names $num_or_name]} {
set m [dict get $decmode_names $num_or_name]
} else {
error "punk::console::unset_mode unrecognised mode '$num_or_name'. Known mode names: [dict keys $decmode_names]"
}
}
return "\x1b\[?${m}l"
}
#terminals lie. This should be a reasonable (albeit relatively slow) test of actual width - but some terminals seem to miscalculate. #terminals lie. This should be a reasonable (albeit relatively slow) test of actual width - but some terminals seem to miscalculate.
#todo - a visual interactive test/questionnaire to ask user if things are lining up or if the terminal is telling fibs about cursor position. #todo - a visual interactive test/questionnaire to ask user if things are lining up or if the terminal is telling fibs about cursor position.
#todo - determine if these anomalies are independent of font #todo - determine if these anomalies are independent of font
#punk::ansi should be able to glean widths from unicode data files - but this may be incomplete - todo - compare with what terminal actually does. #punk::ansi should be able to glean widths from unicode data files - but this may be incomplete - todo - compare with what terminal actually does.
#review - vertical movements (e.g /n /v will cause emit 0 to be ineffective - todo - disallow?)
proc test_char_width {char_or_string {emit 0}} { proc test_char_width {char_or_string {emit 0}} {
#return 1 #return 1
#JMN #JMN
@ -1266,6 +1434,57 @@ namespace eval punk::console {
return [expr {$col2 - $col1}] return [expr {$col2 - $col1}]
} }
#get reported cursor position after emitting teststring.
#The row is more likely to be a lie than the column
#With wrapping on we should be able to test if the terminal has an inconsistency between reported width and when it actually wraps.
#(but as line wrapping generally occurs based on width - we probably won't see this - just 'apparently' early wrapping due to printing mismatch with width)
#unfortunately if terminal reports something like \u200B as width 1, but doesn't print it - we can't tell. (vs reporting 1 wide and printing replacement char/space)
#When cursor is already at bottom of screen, scrolling will occur so rowoffset will be zero
#we either need to move cursor up before test - or use alt screen ( or scroll_up then scroll_down?)
#for now we will use alt screen to reduce scrolling effects - REVIEW
proc test_string_cursor {teststring {emit 0}} {
variable ansi_available
if {!$ansi_available} {
puts stderr "No ansi - cannot test char_width of '$teststring' returning [string length $test_string]"
return [string length $teststring]
}
punk::console::enable_alt_screen
punk::console::move 0 0
if {!$emit} {
puts -nonewline stdout \033\[2K\033\[1G ;#2K erase line, 1G cursor at col1
}
set response ""
if {[catch {
set response [punk::console::get_cursor_pos]
} errM]} {
puts stderr "Cannot test_string_cursor for '[punk::ansi::ansistring VIEW $teststring]' - may be no console? Error message from get_cursor_pos: $errM"
return
}
lassign [split $response ";"] row1 col1
if {![string length $response] || ![string is integer -strict $col1] || ![string is integer -strict $row1]} {
puts stderr "test_string_cursor Could not interpret response from get_cursor_pos for initial cursor pos. Response: '[punk::ansi::ansistring VIEW $response]'"
flush stderr
return
}
puts -nonewline stdout $teststring
flush stdout
set response [punk::console::get_cursor_pos]
lassign [split $response ";"] row2 col2
if {![string is integer -strict $col2] || ![string is integer -strict $row2]} {
puts stderr "test_string_cursor could not interpret response from get_cursor_pos for post-emit cursor pos. Response:'[punk::ansi::ansistring VIEW $response]'"
flush stderr
return
}
if {!$emit} {
puts -nonewline stdout \033\[2K\033\[1G
}
flush stdout;#if we don't flush - a subsequent stderr write could move the cursor to a newline and interfere with our 2K1G erasure and cursor repositioning.
punk::console::disable_alt_screen
return [list rowoffset [expr {$col2 - $col1}] columnoffset [expr {$row2 - $row1}]]
}
#todo! - improve ideally we want to use VT sequences to determine - and make a separate utility for testing via systemcalls/os api #todo! - improve ideally we want to use VT sequences to determine - and make a separate utility for testing via systemcalls/os api
proc test_can_ansi {} { proc test_can_ansi {} {
#don't set ansi_avaliable here - we want to be able to change things, retest etc. #don't set ansi_avaliable here - we want to be able to change things, retest etc.
@ -1306,8 +1525,59 @@ namespace eval punk::console {
if {!$ansi_available} { if {!$ansi_available} {
return 0 return 0
} }
set ansi_available [test_can_ansi] #ansi_available defaults to -1 (unknown)
return [expr {$ansi_available}] if {$ansi_available == -1} {
set ansi_available [test_can_ansi]
return $ansi_available
}
return 1
}
variable grapheme_cluster_support [dict create] ;#default empty dict for unknown/untested
#todo - flag to retest? (for consoles where grapheme cluster support can be disabled e.g via decmode 2027)
proc grapheme_cluster_support {} {
variable grapheme_cluster_support
if {[dict size $grapheme_cluster_support]} {
return $grapheme_cluster_support
}
if {[info exists ::env(TERM_PROGRAM)]} {
#terminals known to support grapheme clusters, but unable to respond to decmode request 2027
#wezterm (on windows as at 2024-12 decmode 2027 doesn't work)
#REVIEW - what if terminal is remote wezterm? can/will this env variable
# iterm and apple terminal also set TERM_PROGRAM
if {[string tolower $::env(TERM_PROGRAM)] in [list wezterm]} {
set is_available 1
return [dict create available 1 mode set]
}
}
#where 1 = set, 2 = unset. (0 = mode not recognised, 3 = permanently set, 4 = permanently unset)
set state [get_mode grapheme_clusters] ;#decmode 2027 extension
set is_available 0
switch -- $state {
0 {
set m unsupported ;# the dec query is unsupported - but it's possible the terminal still has grapheme support
}
1 {
set m set
set is_available 1
}
2 {
set m unset
}
3 {
set m permanently_set
set is_available 1
}
4 {
set m permanently_unset
}
default {
set m "BAD_RESPONSE"
}
}
return [dict create available $is_available mode $m]
} }
namespace eval ansi { namespace eval ansi {
@ -1432,7 +1702,7 @@ namespace eval punk::console {
puts -nonewline stdout [punk::ansi::move_column $col] puts -nonewline stdout [punk::ansi::move_column $col]
} }
proc move_row {row} { proc move_row {row} {
puts -nonewline stdout [punk::ansi::move_row $col] puts -nonewline stdout [punk::ansi::move_row $row]
} }
proc move_emit {row col data args} { proc move_emit {row col data args} {
puts -nonewline stdout [punk::ansi::move_emit $row $col $data {*}$args] puts -nonewline stdout [punk::ansi::move_emit $row $col $data {*}$args]
@ -1912,8 +2182,52 @@ namespace eval punk::console {
#[list_end] [comment {--- end definitions namespace punk::console ---}] #[list_end] [comment {--- end definitions namespace punk::console ---}]
} }
namespace eval punk::console::check {
variable has_bug_legacysymbolwidth -1 ;#undetermined
proc has_bug_legacysymbolwidth {} {
#some terminals (on windows as at 2024) miscount width of these single-width blocks internally
#resulting in extra spacing that is sometimes well removed from the character itself (e.g at next ansi reset)
#This was fixed in windows-terminal based systems (2021) but persists in others.
#https://github.com/microsoft/terminal/issues/11694
variable has_bug_legacysymbolwidth
if {!$has_bug_legacysymbolwidth} {
return 0
}
if {$has_bug_legacysymbolwidth == -1} {
#run the test using ansi movement
#we only test a specific character from the known problematic set
set w [punk::console::test_char_width \U1fb7d]
if {$w == 1} {
set has_bug_legacysymbolwidth 0
} else {
#can return 2 on legacy window consoles for example
set has_bug_legacysymbolwidth 1
}
return $has_bug_legacysymbolwidth
}
return 1
}
variable has_bug_zwsp -1 ;#undetermined
proc has_bug_zwsp {} {
#Note that some terminals behave differently regarding a leading zwsp vs one that is inline between other chars.
#we are only testing the inline behaviour here.
variable has_bug_zwsp
if {!$has_bug_zwsp} {
return 0
}
if {$has_bug_zwsp == -1} {
set w [punk::console::test_char_width X\u200bY]
}
if {$w == 2} {
return 0
} else {
#may return 3 - but this gives no indication of whether terminal hides it or not.
return 1
}
return 1
}
}

41
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/lib-0.1.1.tm

@ -63,38 +63,6 @@ package require Tcl 8.6-
#*** !doctools #*** !doctools
#[section API] #[section API]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# oo::class namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#tcl::namespace::eval punk::lib::class {
# #*** !doctools
# #[subsection {Namespace punk::lib::class}]
# #[para] class definitions
# if {[info commands [tcl::namespace::current]::interface_sample1] eq ""} {
# #*** !doctools
# #[list_begin enumerated]
#
# # oo::class create interface_sample1 {
# # #*** !doctools
# # #[enum] CLASS [class interface_sample1]
# # #[list_begin definitions]
#
# # method test {arg1} {
# # #*** !doctools
# # #[call class::interface_sample1 [method test] [arg arg1]]
# # #[para] test method
# # puts "test: $arg1"
# # }
#
# # #*** !doctools
# # #[list_end] [comment {-- end definitions interface_sample1}]
# # }
#
# #*** !doctools
# #[list_end] [comment {--- end class enumeration ---}]
# }
#}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::lib::ensemble { tcl::namespace::eval punk::lib::ensemble {
#wiki.tcl-lang.org/page/ensemble+extend #wiki.tcl-lang.org/page/ensemble+extend
@ -172,7 +140,10 @@ tcl::namespace::eval punk::lib::check {
proc has_tclbug_lsearch_strideallinline {} { proc has_tclbug_lsearch_strideallinline {} {
#bug only occurs with single -index value combined with -stride -all -inline -subindices #bug only occurs with single -index value combined with -stride -all -inline -subindices
#https://core.tcl-lang.org/tcl/tktview/5a1aaa201d #https://core.tcl-lang.org/tcl/tktview/5a1aaa201d
set result [lsearch -stride 3 -all -inline -index 1 -subindices {a1 a2 a3} *] if {[catch {[lsearch -stride 3 -all -inline -index 1 -subindices {a1 a2 a3} *]} result]} {
#we aren't looking for an error result - error most likely indicates tcl too old to support -stride
return 0
}
return [expr {$result ne "a2"}] return [expr {$result ne "a2"}]
} }
@ -2575,12 +2546,12 @@ namespace eval punk::lib {
while {$j <= $max} { while {$j <= $max} {
if {$x % $j == 0} { if {$x % $j == 0} {
set other [expr {$x / $j}] set other [expr {$x / $j}]
if {$other % 2 != 0} { if {$other % 2} {
if {$other ni $factors} { if {$other ni $factors} {
lappend factors $other lappend factors $other
} }
} }
if {$j % 2 != 0} { if {$j % 2} {
if {$j ni $factors} { if {$j ni $factors} {
lappend factors $j lappend factors $j
} }

2
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/base-0.1.tm

@ -869,7 +869,7 @@ namespace eval punk::mix::base {
#todo - write tests #todo - write tests
if {([llength $args] % 2) != 0} { if {[llength $args] % 2} {
error "get_relativecksum_from_base error. args supplied must be in the form of key-value pairs. received '$args' " error "get_relativecksum_from_base error. args supplied must be in the form of key-value pairs. received '$args' "
} }
if {[dict exists $args cksum]} { if {[dict exists $args cksum]} {

2
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/util-0.1.0.tm

@ -51,7 +51,7 @@ namespace eval punk::mix::util {
} else { } else {
if {$ival in $knownopts} { if {$ival in $knownopts} {
#puts ">known at $i : [lindex $args $i]" #puts ">known at $i : [lindex $args $i]"
if {($i % 2) != 0} { if {$i % 2} {
error "unexpected option at index $i. known options: $knownopts must come in -opt val pairs." error "unexpected option at index $i. known options: $knownopts must come in -opt val pairs."
} }
incr i incr i

6
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/nav/fs-0.1.0.tm

@ -1,6 +1,6 @@
# -*- tcl -*- # -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'pmix make' or bin/punkmake to update from <pkg>-buildversion.txt # Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm # module template: punkshell/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm
# #
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. # Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. # Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
@ -18,7 +18,7 @@
# doctools header # doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools #*** !doctools
#[manpage_begin shellspy_module_punk::nav::fs 0 0.1.0] #[manpage_begin punkshell_module_punk::nav::fs 0 0.1.0]
#[copyright "2024"] #[copyright "2024"]
#[titledesc {punk::nav::fs console filesystem navigation}] [comment {-- Name section and table of contents description --}] #[titledesc {punk::nav::fs console filesystem navigation}] [comment {-- Name section and table of contents description --}]
#[moddesc {fs nav}] [comment {-- Description at end of page heading --}] #[moddesc {fs nav}] [comment {-- Description at end of page heading --}]

2
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/packagepreference-0.1.0.tm

@ -1,6 +1,6 @@
# -*- tcl -*- # -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt # Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm # module template: punkshell/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm
# #
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. # Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. # Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.

9
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repl/codethread-0.1.0.tm

@ -141,8 +141,10 @@ tcl::namespace::eval punk::repl::codethread {
#puts stderr "->runscript" #puts stderr "->runscript"
variable replthread_cond variable replthread_cond
variable output_stdout "" #variable output_stdout
variable output_stderr "" #set output_stdout ""
#variable output_stderr
#set output_stderr ""
#expecting to be called from a thread::send in parent repl - ie in the toplevel interp so that the sub-interp "code" is available #expecting to be called from a thread::send in parent repl - ie in the toplevel interp so that the sub-interp "code" is available
#if a thread::send is done from the commandline in a codethread - Tcl will #if a thread::send is done from the commandline in a codethread - Tcl will
if {"code" ni [interp children] || ![info exists replthread_cond]} { if {"code" ni [interp children] || ![info exists replthread_cond]} {
@ -154,6 +156,9 @@ tcl::namespace::eval punk::repl::codethread {
puts stderr "runscript is meant to be called from the parent repl thread via a thread::send to the codethread" puts stderr "runscript is meant to be called from the parent repl thread via a thread::send to the codethread"
return return
} }
interp eval code [list set ::punk::repl::codethread::output_stdout ""]
interp eval code [list set ::punk::repl::codethread::output_stderr ""]
set outstack [list] set outstack [list]
set errstack [list] set errstack [list]
upvar ::punk::config::running running_config upvar ::punk::config::running running_config

287
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repl/codethread-0.1.1.tm

@ -0,0 +1,287 @@
# -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: punkshell/src/decktemplates/vendor/punk/modules/template_module-0.0.2.tm
#
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# (C) 2024
#
# @@ Meta Begin
# Application punk::repl::codethread 0.1.1
# Meta platform tcl
# Meta license <unspecified>
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[manpage_begin punkshell_module_punk::repl::codethread 0 0.1.1]
#[copyright "2024"]
#[titledesc {Module repl codethread}] [comment {-- Name section and table of contents description --}]
#[moddesc {codethread for repl - root interpreter}] [comment {-- Description at end of page heading --}]
#[require punk::repl::codethread]
#[keywords module repl]
#[description]
#[para] This is part of the infrastructure required for the punk::repl to operate
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Overview]
#[para] overview of punk::repl::codethread
#[subsection Concepts]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection dependencies]
#[para] packages used by punk::repl::codethread
#[list_begin itemized]
package require Tcl 8.6-
package require punk::config
#*** !doctools
#[item] [package {Tcl 8.6}]
# #package require frobz
# #*** !doctools
# #[item] [package {frobz}]
#*** !doctools
#[list_end]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section API]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# oo::class namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#tcl::namespace::eval punk::repl::codethread::class {
#*** !doctools
#[subsection {Namespace punk::repl::codethread::class}]
#[para] class definitions
#if {[info commands [tcl::namespace::current]::interface_sample1] eq ""} {
#*** !doctools
#[list_begin enumerated]
# oo::class create interface_sample1 {
# #*** !doctools
# #[enum] CLASS [class interface_sample1]
# #[list_begin definitions]
# method test {arg1} {
# #*** !doctools
# #[call class::interface_sample1 [method test] [arg arg1]]
# #[para] test method
# puts "test: $arg1"
# }
# #*** !doctools
# #[list_end] [comment {-- end definitions interface_sample1}]
# }
#*** !doctools
#[list_end] [comment {--- end class enumeration ---}]
#}
#}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Base namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::repl::codethread {
tcl::namespace::export *
variable replthread
variable replthread_cond
variable running 0
variable output_stdout ""
variable output_stderr ""
#variable xyz
#*** !doctools
#[subsection {Namespace punk::repl::codethread}]
#[para] Core API functions for punk::repl::codethread
#[list_begin definitions]
#proc sample1 {p1 n args} {
# #*** !doctools
# #[call [fun sample1] [arg p1] [arg n] [opt {option value...}]]
# #[para]Description of sample1
# #[para] Arguments:
# # [list_begin arguments]
# # [arg_def tring p1] A description of string argument p1.
# # [arg_def integer n] A description of integer argument n.
# # [list_end]
# return "ok"
#}
variable run_command_cache
proc is_running {} {
variable running
return $running
}
proc runscript {script} {
#puts stderr "->runscript"
variable replthread_cond
#variable output_stdout ""
#variable output_stderr ""
#expecting to be called from a thread::send in parent repl - ie in the toplevel interp so that the sub-interp "code" is available
#if a thread::send is done from the commandline in a codethread - Tcl will
if {"code" ni [interp children] || ![info exists replthread_cond]} {
#in case someone tries calling from codethread directly - don't do anything or change any state
#(direct caller could create an interp named code at the level "" -> "code" -"code" and add a replthread_cond value to avoid this check - but it probably won't do anything useful)
#if called directly - the context will be within the first 'code' interp.
#inappropriate caller could add superfluous entries to shellfilter stack if function errors out
#inappropriate caller could affect tsv vars (if their interp allows that anyway)
puts stderr "runscript is meant to be called from the parent repl thread via a thread::send to the codethread"
return
}
interp eval code [list set ::punk::repl::codethread::output_stdout ""]
interp eval code [list set ::punk::repl::codethread::output_stderr ""]
set outstack [list]
set errstack [list]
upvar ::punk::config::running running_config
if {[string length [dict get $running_config color_stdout_repl]] && [interp eval code punk::console::colour]} {
lappend outstack [interp eval code [list shellfilter::stack::add stdout ansiwrap -settings [list -colour [dict get $running_config color_stdout_repl]]]]
}
lappend outstack [interp eval code [list shellfilter::stack::add stdout tee_to_var -settings {-varname ::punk::repl::codethread::output_stdout}]]
if {[string length [dict get $running_config color_stderr_repl]] && [interp eval code punk::console::colour]} {
lappend errstack [interp eval code [list shellfilter::stack::add stderr ansiwrap -settings [list -colour [dict get $running_config color_stderr_repl]]]]
# #lappend errstack [shellfilter::stack::add stderr ansiwrap -settings [list -colour cyan]]
}
lappend errstack [interp eval code [list shellfilter::stack::add stderr tee_to_var -settings {-varname ::punk::repl::codethread::output_stderr}]]
#an experiment
#set errhandle [shellfilter::stack::item_tophandle stderr]
#interp transfer "" $errhandle code
set status [catch {
#shennanigans to keep compiled script around after call.
#otherwise when $script goes out of scope - internal rep of vars set in script changes.
#The shimmering may be no big deal(?) - but debug/analysis using tcl::unsupported::representation becomes impossible.
interp eval code [list ::punk::lib::set_clone ::codeinterp::clonescript $script] ;#like objclone
interp eval code {
lappend ::codeinterp::run_command_cache $::codeinterp::clonescript
if {[llength $::codeinterp::run_command_cache] > 2000} {
set ::codeinterp::run_command_cache [lrange $::codeinterp::run_command_cache 1750 end][unset ::codeinterp::run_command_cache]
}
tcl::namespace::inscope $::punk::ns::ns_current $::codeinterp::clonescript
}
} result]
flush stdout
flush stderr
#interp transfer code $errhandle ""
#flush $errhandle
#set lastoutchar [string index [punk::ansi::ansistrip [interp eval code set ::punk::repl::codethread::output_stdout]] end]
#set lastoutchar [string index [punk::ansi::ansistrip [interp eval code [list set ::punk::repl::codethread::output_stdout]]] end]
set lastoutpart [interp eval code {string range $::punk::repl::codethread::output_stdout end-100 end}]
#note we could be in a *large* ansi segment such as sixel data
#review - why do we need to ansistrip?
set lastoutchar [string index [punk::ansi::ansistrip $lastoutpart] end]
#set lasterrchar [string index [punk::ansi::ansistrip [interp eval code set ::punk::repl::codethread::output_stderr]] end]
set lasterrpart [interp eval code {string range $::punk::repl::codethread::output_stderr end-100 end}]
set lasterrchar [string index [punk::ansi::ansistrip $lasterrpart] end]
#puts stderr "-->[ansistring VIEW -lf 1 $lastoutchar$lasterrchar]"
set tid [thread::id]
tsv::set codethread_$tid info [list lastoutchar $lastoutchar lasterrchar $lasterrchar]
tsv::set codethread_$tid status $status
tsv::set codethread_$tid result $result
tsv::set codethread_$tid errorcode $::errorCode
#only remove from shellfilter::stack the items we added to stack in this function
foreach s [lreverse $outstack] {
interp eval code [list shellfilter::stack::remove stdout $s]
}
foreach s [lreverse $errstack] {
interp eval code [list shellfilter::stack::remove stderr $s]
}
thread::cond notify $replthread_cond
}
#*** !doctools
#[list_end] [comment {--- end definitions namespace punk::repl::codethread ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Secondary API namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::repl::codethread::lib {
tcl::namespace::export *
tcl::namespace::path [tcl::namespace::parent]
#*** !doctools
#[subsection {Namespace punk::repl::codethread::lib}]
#[para] Secondary functions that are part of the API
#[list_begin definitions]
#proc utility1 {p1 args} {
# #*** !doctools
# #[call lib::[fun utility1] [arg p1] [opt {?option value...?}]]
# #[para]Description of utility1
# return 1
#}
#*** !doctools
#[list_end] [comment {--- end definitions namespace punk::repl::codethread::lib ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Internal]
tcl::namespace::eval punk::repl::codethread::system {
#*** !doctools
#[subsection {Namespace punk::repl::codethread::system}]
#[para] Internal functions that are not part of the API
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide punk::repl::codethread [tcl::namespace::eval punk::repl::codethread {
variable pkg punk::repl::codethread
variable version
set version 0.1.1
}]
return
#*** !doctools
#[manpage_end]

2
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repo-0.1.1.tm

@ -370,7 +370,7 @@ namespace eval punk::repo {
set opt_repotypes [dict get $opts -repotypes] set opt_repotypes [dict get $opts -repotypes]
set opt_repopaths [dict get $opts -repopaths] set opt_repopaths [dict get $opts -repopaths]
if {"$opt_repopaths" ne ""} { if {"$opt_repopaths" ne ""} {
if {([llength $opt_repopaths] % 2 != 0) || ![dict exists $opt_repopaths closest]} { if {([llength $opt_repopaths] % 2) || ![dict exists $opt_repopaths closest]} {
error "workingdir_state error: -repopaths argument invalid. Expected a dict as retrieved using punk::repo::find_repos" error "workingdir_state error: -repopaths argument invalid. Expected a dict as retrieved using punk::repo::find_repos"
} }
set repopaths $opt_repopaths set repopaths $opt_repopaths

11
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/tdl-0.1.0.tm

@ -31,15 +31,19 @@ namespace eval punk::tdl {
server -name trillion -os windows server -name trillion -os windows
server -name vmhost1 -os FreeBSD { server -name vmhost1 -os FreeBSD {
guest -name bsd1 -vmmanager iocage guest -name bsd1 -vmmanager bastille
guest -name p1 -vmmanager bhyve guest -name p1 -vmmanager bhyve
} }
} }
proc prettyparse {script} { proc prettyparse {script {safe 1}} {
set i [interp create -safe] if {$safe} {
set i [interp create -safe]
} else {
set i [interp create]
}
try { try {
# $i eval {unset {*}[info vars]} # $i eval {unset {*}[info vars]}
# foreach command [$i eval {info commands}] {$i hide $command} # foreach command [$i eval {info commands}] {$i hide $command}
@ -65,6 +69,7 @@ namespace eval punk::tdl {
interp delete $i interp delete $i
} }
} }
proc prettyprint {data {level 0}} { proc prettyprint {data {level 0}} {
set ind [string repeat " " $level] set ind [string repeat " " $level]
incr level incr level

4
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punkcheck-0.1.0.tm

@ -359,7 +359,7 @@ namespace eval punkcheck {
-note \uFFFF\ -note \uFFFF\
] ]
set known_opts [dict keys $defaults] set known_opts [dict keys $defaults]
if {[llength $args] % 2 != 0} { if {[llength $args] % 2} {
error "targetset_end arguments after status must be in the form of -flag value pairs. known flags: $known_opts" error "targetset_end arguments after status must be in the form of -flag value pairs. known flags: $known_opts"
} }
set opts [dict merge $defaults $args] set opts [dict merge $defaults $args]
@ -914,7 +914,7 @@ namespace eval punkcheck {
set changed 0 set changed 0
} }
set installing_record_sources [dict_getwithdefault $installing_record body [list]] set installing_record_sources [dict_getwithdefault $installing_record body [list]]
set ts_now [clock microseconds] ;#gathering metadata - especially checsums on folder can take some time - calc and store elapsed us for time taken to gather metadata set ts_now [clock microseconds] ;#gathering metadata - especially checksums on folder can take some time - calc and store elapsed us for time taken to gather metadata
set metadata_us [expr {$ts_now - $ts_start}] set metadata_us [expr {$ts_now - $ts_start}]
set this_source_record [dict create tag SOURCE -type $ftype -size $fsize -path $source_relpath -cksum $cksum -cksum_all_opts $cksum_all_opts -changed $changed -metadata_us $metadata_us] set this_source_record [dict create tag SOURCE -type $ftype -size $fsize -path $source_relpath -cksum $cksum -cksum_all_opts $cksum_all_opts -changed $changed -metadata_us $metadata_us]
lappend installing_record_sources $this_source_record lappend installing_record_sources $this_source_record

9
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/shellfilter-0.1.9.tm

@ -613,6 +613,10 @@ namespace eval shellfilter::chan {
#It can be useful for test/debugging #It can be useful for test/debugging
#Due to chunking at random breaks - we have to check if an ansi code in the underlying stream has been split - otherwise our wrapping will break the existing ansi #Due to chunking at random breaks - we have to check if an ansi code in the underlying stream has been split - otherwise our wrapping will break the existing ansi
# #
set sixelstart_re {\x1bP([;0-9]*)q} ;#7-bit - todo 8bit
#todo kitty graphics \x1b_G...
#todo iterm graphics
oo::class create ansiwrap { oo::class create ansiwrap {
variable o_trecord variable o_trecord
variable o_enc variable o_enc
@ -646,6 +650,9 @@ namespace eval shellfilter::chan {
set o_is_junction 0 set o_is_junction 0
} }
} }
#todo - track when in sixel,iterm,kitty graphics data - can be very large
method Trackcodes {chunk} { method Trackcodes {chunk} {
#puts stdout "===[ansistring VIEW -lf 1 $o_buffered]" #puts stdout "===[ansistring VIEW -lf 1 $o_buffered]"
set buf $o_buffered$chunk set buf $o_buffered$chunk
@ -2334,7 +2341,7 @@ namespace eval shellfilter {
#set tid [::shellfilter::log::open $runtag [list -syslog "127.0.0.1:514"]] #set tid [::shellfilter::log::open $runtag [list -syslog "127.0.0.1:514"]]
set tid [::shellfilter::log::open $runtag [list -syslog ""]] set tid [::shellfilter::log::open $runtag [list -syslog ""]]
if {([llength $args] % 2) != 0} { if {[llength $args] % 2} {
error "Trailing arguments after any positional arguments must be in pairs of the form -argname argvalue. Valid flags are:'$valid_flags'" error "Trailing arguments after any positional arguments must be in pairs of the form -argname argvalue. Valid flags are:'$valid_flags'"
} }
set invalid_flags [list] set invalid_flags [list]

120
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/textblock-0.1.2.tm

@ -82,11 +82,17 @@ tcl::namespace::eval textblock {
#review - what about ansi off in punk::console? #review - what about ansi off in punk::console?
tcl::namespace::import ::punk::ansi::a ::punk::ansi::a+ tcl::namespace::import ::punk::ansi::a ::punk::ansi::a+
tcl::namespace::export block frame frame_cache framedef frametypes gcross height width widthtopline join join_basic list_as_table pad testblock tcl::namespace::export block frame frame_cache framedef frametypes gcross height width widthtopline join join_basic list_as_table pad testblock
#NOTE sha1, although computationally more intensive, tends to be faster than md5 on modern cpus
#(more likely to be optimised for modern cpu features?)
variable use_md5 ;#framecache variable use_md5 ;#framecache
set use_md5 1 set use_md5 1
if {[catch {package require md5}]} { if {[catch {package require md5}]} {
set use_md5 0 set use_md5 0
} }
#todo - change use_md5 to more generic use_checksum_algorithm function.
# e.g allow md5, sha1, none, etc.
# - perhaps autodetect 32bit vs 64bit (or processortype?) to select a default (also depending on packages available and accelerator presence)
proc use_md5 {{yes_no ""}} { proc use_md5 {{yes_no ""}} {
variable use_md5 variable use_md5
if {$yes_no eq ""} { if {$yes_no eq ""} {
@ -4170,7 +4176,7 @@ tcl::namespace::eval textblock {
} }
set FRAMETYPES [textblock::frametypes] set FRAMETYPES [textblock::frametypes]
punk::args::Get_argspecs [punk::lib::tstr -return string { punk::args::definition [punk::lib::tstr -return string {
*id textblock::list_as_table *id textblock::list_as_table
-return -default table -choices {table tableobject} -return -default table -choices {table tableobject}
@ -4208,7 +4214,7 @@ tcl::namespace::eval textblock {
proc list_as_table {args} { proc list_as_table {args} {
set FRAMETYPES [textblock::frametypes] set FRAMETYPES [textblock::frametypes]
set argd [punk::args::get_dict_by_id textblock::list_as_table $args] set argd [punk::args::get_by_id textblock::list_as_table $args]
set opts [dict get $argd opts] set opts [dict get $argd opts]
set datalist [dict get $argd values datalist] set datalist [dict get $argd values datalist]
@ -5699,7 +5705,7 @@ tcl::namespace::eval textblock {
#custom dict may leave out keys - but cannot have unknown keys #custom dict may leave out keys - but cannot have unknown keys
foreach {k v} $f { foreach {k v} $f {
switch -- $k { switch -- $k {
hl - hlt - hlb - vl - vll - vlr - tlc - trc - blc - brc {} all - hl - hlt - hlb - vl - vll - vlr - tlc - trc - blc - brc {}
hltj - hlbj - vllj - vlrj { hltj - hlbj - vllj - vlrj {
#also allow extra join arguments #also allow extra join arguments
} }
@ -5714,11 +5720,15 @@ tcl::namespace::eval textblock {
set is_custom_dict_ok 0 set is_custom_dict_ok 0
} }
if {!$is_custom_dict_ok} { if {!$is_custom_dict_ok} {
error "frame option -type must be one of known types: $::textblock::frametypes or a dictionary with any of keys hl,hlt,hlb,vl,vll,vlr,tlc,trc,blc,brc" error "frame option -type must be one of known types: $::textblock::frametypes or a dictionary with any of keys all,hl,hlt,hlb,vl,vll,vlr,tlc,trc,blc,brc"
}
if {[dict exists $f all]} {
return [tcl::dict::create category custom type $f]
} else {
set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "]
set custom_frame [tcl::dict::merge $default_custom $f]
return [tcl::dict::create category custom type $custom_frame]
} }
set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "]
set custom_frame [tcl::dict::merge $default_custom $f]
return [tcl::dict::create category custom type $custom_frame]
} }
} }
} }
@ -5769,7 +5779,7 @@ tcl::namespace::eval textblock {
} }
set f [lindex $values 0] set f [lindex $values 0]
set rawglobs [lrange $values 1 end] set rawglobs [lrange $values 1 end]
if {![llength $rawglobs]} { if {![llength $rawglobs] || "all" in $rawglobs || "*" in $rawglobs} {
set globs * set globs *
} else { } else {
set globs [list] set globs [list]
@ -6236,6 +6246,46 @@ tcl::namespace::eval textblock {
#from3 #from3
set trc [punk::char::charshort boxd_ldhz] ;#T shape (ttj) set trc [punk::char::charshort boxd_ldhz] ;#T shape (ttj)
set brc [punk::char::charshort boxd_luhz] ;#upside down T shape (btj) set brc [punk::char::charshort boxd_luhz] ;#upside down T shape (btj)
switch -- $targetleft-$targetright {
heavy-light {
set tlc \u252d ;# Left Heavy and Right Down Light (ttj)
set blc \u2535 ;# Left Heavy and Right Up Light (btj)
set vllj \u2525 ;# left heavy (rtj)
set vlrj \u251c;#right light (ltj)
}
heavy-other {
set tlc \u252d ;# Left Heavy and Right Down Light (ttj)
set blc \u2535 ;# Left Heavy and Right Up Light (btj)
set vllj \u2525 ;# left heavy (rtj)
}
heavy-heavy {
set vllj \u2525 ;# left heavy (rtj)
set vlrj \u251d;#right heavy (ltj)
set tlc \u252d ;# Left Heavy and Right Down Light (ttj)
set blc \u2535 ;# Left Heavy and Right Up Light (btj)
set trc \u252e ;#Right Heavy and Left Down Light (ttj)
set brc \u2536 ;#Right Heavy and Left up Light (btj)
}
light-heavy {
set trc \u252e ;#Right Heavy and Left Down Light (ttj)
set brc \u2536 ;#Right Heavy and Left up Light (btj)
set vlrj \u251d;#right heavy (ltj)
set vllj \u2524 ;# left light (rtj)
}
light-other {
set vllj \u2524 ;# left light (rtj)
}
light-light {
set vllj \u2524 ;# left light (rtj)
set vlrj \u251c;#right light (ltj)
}
}
#set vllj \u2525 ;# left heavy (rtj)
#set vllj \u2524 ;# left light (rtj)
#set vlrj \u251d;#right heavy (ltj)
#set vlrj \u251c;#right light (ltj)
} }
left_up { left_up {
#9 #9
@ -6935,6 +6985,7 @@ tcl::namespace::eval textblock {
self-self { self-self {
#set blc \u27e1 ;# white concave-sided diamond - positioned too far right #set blc \u27e1 ;# white concave-sided diamond - positioned too far right
#set blc \u27e3 ;# concave sided diamond with rightwards tick - positioned too low, too right - big gaps #set blc \u27e3 ;# concave sided diamond with rightwards tick - positioned too low, too right - big gaps
#set blc \u2bce ;# from "Miscellaneous Symbols and Arrows" - positioned too far right
set brc \u2524 ;# *light (rtj) set brc \u2524 ;# *light (rtj)
set tlc \u252c ;# *light (ttj) set tlc \u252c ;# *light (ttj)
} }
@ -6950,6 +7001,15 @@ tcl::namespace::eval textblock {
} }
} }
} }
down_right {
switch -- $targetdown-$targetright {
self-self {
#set brc \u2bce ;# from "Miscellaneous Symbols and Arrows" - positioned too far right
set trc \u252c ;# (ttj)
set blc \u2524 ;# (rtj)
}
}
}
} }
} }
arc_b { arc_b {
@ -7027,6 +7087,15 @@ tcl::namespace::eval textblock {
set blc \U1fb7c ;#legacy block set blc \U1fb7c ;#legacy block
set brc \U1fb7f ;#legacy block set brc \U1fb7f ;#legacy block
if {[punk::console::check::has_bug_legacysymbolwidth]} {
#rather than totally fail on some mixed layout that happens to use block2 - just degrade it - but prevent alignment problems
set sp \u00a0 ;#non breaking space (plain space may act transparent in some use cases)
set tlc $sp
set trc $sp
set blc $sp
set brc $sp
}
#horizontal and vertical bar joins #horizontal and vertical bar joins
set hltj $hlt set hltj $hlt
set hlbj $hlb set hlbj $hlb
@ -7088,15 +7157,20 @@ tcl::namespace::eval textblock {
set vlrj $vlr set vlrj $vlr
} }
default { default {
set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "] ;#only default the general types - these form defaults for more specific types if they're missing #set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "] ;#only default the general types - these form defaults for more specific types if they're missing
if {[llength $f] % 2 != 0} { set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "]
if {"all" in [dict keys $f]} {
set A [dict get $f all]
set default_custom [tcl::dict::create hl $A vl $A tlc $A trc $A blc $A brc $A]
}
if {[llength $f] % 2} {
#todo - retrieve usage from punk::args #todo - retrieve usage from punk::args
error "textblock::frametype frametype '$f' is not one of the predefined frametypes: $::textblock::frametypes and does not appear to be a dictionary for a custom frametype" error "textblock::frametype frametype '$f' is not one of the predefined frametypes: $::textblock::frametypes and does not appear to be a dictionary for a custom frametype"
} }
#unknown order of keys specified by user - validate before creating vars as we need more general elements to be available as defaults #unknown order of keys specified by user - validate before creating vars as we need more general elements to be available as defaults
dict for {k v} $f { dict for {k v} $f {
switch -- $k { switch -- $k {
hl - vl - tlc - trc - blc - brc - hlt - hlb - vll - vlr - hltj - hlbj - vllj - vlrj {} all - hl - vl - tlc - trc - blc - brc - hlt - hlb - vll - vlr - hltj - hlbj - vllj - vlrj {}
default { default {
error "textblock::frametype '$f' has unknown element '$k'" error "textblock::frametype '$f' has unknown element '$k'"
} }
@ -8028,17 +8102,19 @@ tcl::namespace::eval textblock {
return $fs return $fs
} }
} }
punk::args::definition {
*id textblock::gcross
-max_cross_size -default 0 -type integer -help "Largest size cross to use to make up the block
Only cross sizes that divide the size of the overall block will be used.
e.g if the 'size' chosen is 19 (a prime number) - only 1 or the full size of 19 can be used as the crosses to make up the block.
Whereas for a block size of 24, -max_cross_size of 1,2,3,4,6,8,12 or 24 will work. (all divisors)
If a number chosen for -max_cross_size isn't a divisor, the largest divisor below the chosen value will be used.
"
*values -min 1 -max 1
size -default 1 -type integer
}
proc gcross {args} { proc gcross {args} {
set argd [punk::args::get_dict { set argd [punk::args::get_by_id textblock::gcross $args]
-max_cross_size -default 0 -type integer -help "Largest size cross to use to make up the block
Only cross sizes that divide the size of the overall block will be used.
e.g if the 'size' chosen is 19 (a prime number) - only 1 or the full size of 19 can be used as the crosses to make up the block.
Whereas for a block size of 24, -max_cross_size of 1,2,3,4,6,8,12 or 24 will work. (all divisors)
If a number chosen for -max_cross_size isn't a divisor, the largest divisor below the chosen value will be used.
"
*values -min 1
size -default 1 -type integer
} $args]
set size [dict get $argd values size] set size [dict get $argd values size]
set opts [dict get $argd opts] set opts [dict get $argd opts]
@ -8089,7 +8165,7 @@ tcl::namespace::eval textblock {
lappend crossrows [::join $r ""] lappend crossrows [::join $r ""]
} }
if {$max_cross_size % 2 != 0} { if {$max_cross_size % 2} {
#only put centre cross in for odd sized crosses #only put centre cross in for odd sized crosses
set r $row set r $row
lset r $armsize $x lset r $armsize $x

2
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/overtype-1.6.5.tm

@ -1771,7 +1771,7 @@ tcl::namespace::eval overtype {
#-returnextra enables returning of overflow and length #-returnextra enables returning of overflow and length
#review - use punk::ansi::ta::detect to short-circuit processing and do simpler string calcs as an optimisation? #review - use punk::ansi::ta::detect to short-circuit processing and do simpler string calcs as an optimisation?
#review - DECSWL/DECDWL double width line codes - very difficult/impossible to align and compose with other elements #review - DECSWL/DECDWL double width line codes - very difficult/impossible to align and compose with other elements
#(could render it by faking it with sixels and a lot of work - find/make a sixel font and ensure it's exactly 2 cols per char? #(could render it by faking it with sixels and a lot of work - find/make a sixel font (converter?) and ensure it's exactly 2 cols per char?
# This would probably be impractical to support for different fonts) # This would probably be impractical to support for different fonts)
#todo - review transparency issues with single/double width characters #todo - review transparency issues with single/double width characters
#bidi - need a base direction and concept of directional runs for RTL vs LTR - may be best handled at another layer? #bidi - need a base direction and concept of directional runs for RTL vs LTR - may be best handled at another layer?

46
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk-0.1.tm

@ -7428,6 +7428,7 @@ namespace eval punk {
} }
if {$topic in [list console terminal]} { if {$topic in [list console terminal]} {
set indent [string repeat " " [string length "WARNING: "]]
lappend cstring_tests [dict create\ lappend cstring_tests [dict create\
type "PM "\ type "PM "\
msg "PRIVACY MESSAGE"\ msg "PRIVACY MESSAGE"\
@ -7472,6 +7473,51 @@ namespace eval punk {
append warningblock \n "WARNING: terminal doesn't hide all [dict get $test type] control strings: $d $d8" append warningblock \n "WARNING: terminal doesn't hide all [dict get $test type] control strings: $d $d8"
} }
} }
if {![catch {punk::console::check::has_bug_legacysymbolwidth} result]} {
if {$result} {
append warningblock \n "WARNING: terminal has legacysymbolwidth bug - screen position for symbol reports 2 wide but displays 1 wide."
append warningblock \n $indent "Layout using 'legacy symbols for computing' affected."
append warningblock \n $indent "(e.g textblock frametype block2 unsupported)"
append warningblock \n $indent "This can cause extreme layout deformation when ANSI is present"
append warningblock \n $indent "In some cases unwanted spacing effects occur at a distance from the characters causing it"
}
} else {
append warningblock \n "WARNING: terminal unable to check for legacysymbolwidth bug. err:$result"
}
if {![catch {punk::console::check::has_bug_zwsp} result]} {
if {$result} {
append warningblock \n "WARNING: terminal has zero width space (\\u200b) bug - cursor position incremented when it shouldn't be."
append warningblock \n $indent "The zwsp may or may not be displayed. zwsp contributes to line length and wrapping point"
}
} else {
append warningblock \n "WARNING: terminal unable to check for zwsp bug. err:$result"
}
set grapheme_support [punk::console::grapheme_cluster_support]
#mode, 1 = set, 2 = unset. (0 = mode not recognised, 3 = permanently set, 4 = permanently unset)
if {![dict size $grapheme_support] || [dict get $grapheme_support mode] eq "unsupported" } {
append warningblock \n "WARNING: terminal either doesn't support grapheme clusters, or doesn't report so via decmode 2027 query."
if {[dict size $grapheme_support] && [dict get $grapheme_support available]} {
append warningblock \n $indent "(but punk::console::grapheme_cluster_support has determined it is probably available)"
}
} else {
if {![dict get $grapheme_support available]} {
switch -- [dict get $grapheme_support mode] {
"unset" {
append warningblock \n "WARNING: terminal reports via decmode 2027 that grapheme cluster support is off."
}
"permanently_unset" {
append warningblock \n "WARNING: terminal reports via decmode 2027 that grapheme cluster support is permanently off."
}
"BAD_RESPONSE" {
append warningblock \n "WARNING: terminal doesn't seem to recognize decmode 2027 query. No grapheme cluster support."
}
}
}
}
} }
lappend chunks [list stderr $warningblock] lappend chunks [list stderr $warningblock]

562
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/ansi-0.1.1.tm

@ -556,21 +556,21 @@ tcl::namespace::eval punk::ansi {
} }
proc example {args} { proc example {args} {
set base [punk::repo::find_project] set base [punk::repo::find_project]
set default_ansibase [file join $base src/testansi] set default_ansifolder [file join $base src/testansi]
set argd [punk::args::get_dict [tstr -return string { set argd [punk::args::get_dict [tstr -return string {
*proc -name punk::ansi::example -help "Display .ans image files in a grid that will fit in console *proc -name punk::ansi::example -help "Display .ans image files in a grid that will fit in console
" "
-colwidth -default 82 -help "Width of each column - default of 82 will fit a standard 80wide ansi image (when framed) -colwidth -default 82 -help "Width of each column - default of 82 will fit a standard 80wide ansi image (when framed)
You can specify a narrower width to truncate images on the right side" You can specify a narrower width to truncate images on the right side"
-folder -default "${$default_ansibase}" -help "Base folder for files if relative paths are used. -folder -default "${$default_ansifolder}" -help "Base folder for files if relative paths are used.
Defaults to <projectbase>/src/testansi - where projectbase is determined from current directory. Defaults to <projectbase>/src/testansi - where projectbase is determined from current directory.
" "
*values -min 0 -max -1 *values -min 0 -max -1
files -default {belinda.ans bot.ans flower.ans fish.ans} -multiple true -help "List of filenames - leave empty to display 4 defaults" files -default {belinda.ans bot.ans flower.ans fish.ans} -multiple true -help "List of filenames - leave empty to display 4 defaults"
}] $args] }] $args]
set colwidth [dict get $argd opts -colwidth] set colwidth [dict get $argd opts -colwidth]
set ansibase [file normalize [dict get $argd opts -folder]] set ansifolder [file normalize [dict get $argd opts -folder]]
set fnames [dict get $argd values files] set fnames [dict get $argd values files]
#assumes fixed column widths e.g 80col images will fit in 82-width frames (common standard for old ansi art) (of arbitrary height) #assumes fixed column widths e.g 80col images will fit in 82-width frames (common standard for old ansi art) (of arbitrary height)
@ -579,8 +579,8 @@ tcl::namespace::eval punk::ansi {
package require punk::repo package require punk::repo
package require punk::console package require punk::console
if {![file exists $ansibase]} { if {![file exists $ansifolder]} {
puts stderr "Missing folder at $ansibase" puts stderr "Missing folder at $ansifolder"
puts stderr "Ensure ansi test files exist: $fnames" puts stderr "Ensure ansi test files exist: $fnames"
#error "punk::ansi::example Cannot find example files" #error "punk::ansi::example Cannot find example files"
} }
@ -588,7 +588,7 @@ tcl::namespace::eval punk::ansi {
set pics [list] set pics [list]
foreach f $fnames { foreach f $fnames {
if {[file pathtype $f] ne "absolute"} { if {[file pathtype $f] ne "absolute"} {
set filepath [file normalize $ansibase/$f] set filepath [file normalize $ansifolder/$f]
} else { } else {
set filepath [file normalize $f] set filepath [file normalize $f]
} }
@ -621,7 +621,7 @@ tcl::namespace::eval punk::ansi {
set subtitle [tcl::dict::get $picinfo status] set subtitle [tcl::dict::get $picinfo status]
} }
set title [tcl::dict::get $picinfo filename] set title [tcl::dict::get $picinfo filename]
set fr [textblock::frame -checkargs 0 -width $colwidth -subtitle $subtitle -title $title [tcl::dict::get $picinfo pic]] set fr [textblock::frame -width $colwidth -subtitle $subtitle -title $title [tcl::dict::get $picinfo pic]]
# -- --- --- --- # -- --- --- ---
#we need the max height of a row element to use join_basic instead of join below #we need the max height of a row element to use join_basic instead of join below
# -- --- --- --- # -- --- --- ---
@ -2096,7 +2096,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
} }
} }
} }
append out "\nNote: The 256 term colours especially 0-15 may be altered by terminal pallette settings or ansi OSC 4 codes, so specific RGB values are unavailable" append out "\nNote: The 256 term colours especially 0-15 may be altered by terminal palette settings or ansi OSC 4 codes, so specific RGB values are unavailable"
return $out return $out
} }
web { web {
@ -2126,8 +2126,25 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
switch -- $f4 { switch -- $f4 {
web- - Web- - WEB- { web- - Web- - WEB- {
set tail [tcl::string::tolower [tcl::string::trim [tcl::string::range $i 4 end] -]] set tail [tcl::string::tolower [tcl::string::trim [tcl::string::range $i 4 end] -]]
if {[tcl::dict::exists $WEB_colour_map $tail]} { set cont [string range $tail end-11 end]
set dec [tcl::dict::get $WEB_colour_map $tail] switch -- $cont {
-contrasting - -contrastive {
set cname [string range $tail 0 end-12]
}
default {
set cname $tail
}
}
if {[tcl::dict::exists $WEB_colour_map $cname]} {
set dec [tcl::dict::get $WEB_colour_map $cname]
switch -- $cont {
-contrasting {
set dec [join [punk::ansi::colour::contrasting {*}[split $dec -]] -]
}
-contrastive {
set dec [join [lindex [punk::ansi::colour::contrast_pair {*}[split $dec -]] 0] -]
}
}
set hex [colour_dec2hex $dec] set hex [colour_dec2hex $dec]
set descr "$hex $dec" set descr "$hex $dec"
} else { } else {
@ -2170,25 +2187,60 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
RGB0 - RGB1 - RGB2 - RGB3 - RGB4 - RGB5 - RGB6 - RGB7 - RGB8 - RGB9 - RGB0 - RGB1 - RGB2 - RGB3 - RGB4 - RGB5 - RGB6 - RGB7 - RGB8 - RGB9 -
rgb# - Rgb# - RGB# - rgb# - Rgb# - RGB# -
und# - und- { und# - und- {
if {[tcl::string::index $i 3] eq "#"} { set cont [string range $i end-11 end]
set tail [tcl::string::range $i 4 end] switch -- $cont {
-contrasting - -contrastive {
set iplain [string range $i 0 end-12]
}
default {
set iplain $i
}
}
if {[tcl::string::index $iplain 3] eq "#"} {
set tail [tcl::string::range $iplain 4 end]
set hex $tail set hex $tail
set dec [colour_hex2dec $hex] set dec [colour_hex2dec $hex]
set info $dec ;#show opposite type as first line of info col
switch -- $cont {
-contrasting {
set decfinal [join [punk::ansi::colour::contrasting {*}[split $dec -]] -]
set hexfinal [colour_dec2hex $decfinal]
}
-contrastive {
set decfinal [join [lindex [punk::ansi::colour::contrast_pair {*}[split $dec -]] 0] -]
set hexfinal [colour_dec2hex $decfinal]
}
default {
set hexfinal $hex
set decfinal $dec
}
}
set info "$hexfinal $decfinal" ;#show opposite type as first line of info col
} else { } else {
set tail [tcl::string::trim [tcl::string::range $i 3 end] -] set tail [tcl::string::trim [tcl::string::range $iplain 3 end] -]
set dec $tail set dec $tail
set hex [colour_dec2hex $dec] switch -- $cont {
set info $hex -contrasting {
set decfinal [join [punk::ansi::colour::contrasting {*}[split $dec -]] -]
}
-contrastive {
set decfinal [join [lindex [punk::ansi::colour::contrast_pair {*}[split $dec -]] 0] -]
}
default {
set decfinal $dec
}
}
set hexfinal [colour_dec2hex $decfinal]
set info "$hexfinal $decfinal"
} }
set webcolours_i [lsearch -all $WEB_colour_map $dec] set webcolours_i [lsearch -all $WEB_colour_map $decfinal]
set webcolours [list] set webcolours [list]
foreach ci $webcolours_i { foreach ci $webcolours_i {
lappend webcolours [lindex $WEB_colour_map $ci-1] lappend webcolours [lindex $WEB_colour_map $ci-1]
} }
set x11colours [list] set x11colours [list]
set x11colours_i [lsearch -all $X11_colour_map $dec] set x11colours_i [lsearch -all $X11_colour_map $decfinal]
foreach ci $x11colours_i { foreach ci $x11colours_i {
set c [lindex $X11_colour_map $ci-1] set c [lindex $X11_colour_map $ci-1]
if {$c ni $webcolours} { if {$c ni $webcolours} {
@ -2205,12 +2257,15 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
} }
unde { unde {
switch -- $i { switch -- $i {
undercurly - underdotted - underdashed - undersingle - underdouble { undercurly - undercurl - underdotted - underdot - underdashed - underdash - undersingle - underdouble {
$t add_row [list $i extended $s [ansistring VIEW $s]] $t add_row [list $i extended $s [ansistring VIEW $s]]
} }
underline { underline {
$t add_row [list $i "SGR 4" $s [ansistring VIEW $s]] $t add_row [list $i "SGR 4" $s [ansistring VIEW $s]]
} }
underlinedefault {
$t add_row [list $i "SGR 59" $s [ansistring VIEW $s]]
}
default { default {
$t add_row [list $i UNKNOWN $s [ansistring VIEW $s]] $t add_row [list $i UNKNOWN $s [ansistring VIEW $s]]
} }
@ -2362,10 +2417,31 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#variable WEB_colour_map #variable WEB_colour_map
#upvar ::punk::ansi::WEB_colour_map WEB_colour_map #upvar ::punk::ansi::WEB_colour_map WEB_colour_map
#foreground web colour #foreground web colour
set cname [tcl::string::tolower [tcl::string::range $i 4 end]] set tail [tcl::string::tolower [tcl::string::range $i 4 end]]
#-contrasting
#-contrastive
set cont [string range $tail end-11 end]
switch -- $cont {
-contrasting - -contrastive {
set cname [string range $tail 0 end-12]
}
default {
set cname $tail
}
}
if {[tcl::dict::exists $WEB_colour_map $cname]} { if {[tcl::dict::exists $WEB_colour_map $cname]} {
set rgbdash [tcl::dict::get $WEB_colour_map $cname] set rgbdash [tcl::dict::get $WEB_colour_map $cname]
set rgb [tcl::string::map { - ;} $rgbdash] switch -- $cont {
-contrasting {
set rgb [join [punk::ansi::colour::contrasting {*}[split $rgbdash -]] {;}]
}
-contrastive {
set rgb [join [lindex [punk::ansi::colour::contrast_pair {*}[split $rgbdash -]] 0] {;}]
}
default {
set rgb [tcl::string::map { - ;} $rgbdash]
}
}
lappend t "38;2;$rgb" lappend t "38;2;$rgb"
} else { } else {
puts stderr "ansi web colour unmatched: '$i' in call 'a+ $args'" puts stderr "ansi web colour unmatched: '$i' in call 'a+ $args'"
@ -2375,9 +2451,30 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#variable WEB_colour_map #variable WEB_colour_map
#upvar ::punk::ansi::WEB_colour_map WEB_colour_map #upvar ::punk::ansi::WEB_colour_map WEB_colour_map
#background web colour #background web colour
set cname [tcl::string::tolower [tcl::string::range $i 4 end]] set tail [tcl::string::tolower [tcl::string::range $i 4 end]]
set cont [string range $tail end-11 end]
switch -- $cont {
-contrasting - -contrastive {
set cname [string range $tail 0 end-12]
}
default {
set cname $tail
}
}
if {[tcl::dict::exists $WEB_colour_map $cname]} { if {[tcl::dict::exists $WEB_colour_map $cname]} {
lappend t "48;2;[tcl::string::map {- ;} [tcl::dict::get $WEB_colour_map $cname]]" set rgbdash [tcl::dict::get $WEB_colour_map $cname]
switch -- $cont {
-contrasting {
set rgb [join [punk::ansi::colour::contrasting {*}[split $rgbdash -]] {;}]
}
-contrastive {
set rgb [join [lindex [punk::ansi::colour::contrast_pair {*}[split $rgbdash -]] 0] {;}]
}
default {
set rgb [tcl::string::map { - ;} $rgbdash]
}
}
lappend t "48;2;$rgb"
} else { } else {
puts stderr "ansi Web colour unmatched: '$i' in call 'a+ $args'" puts stderr "ansi Web colour unmatched: '$i' in call 'a+ $args'"
} }
@ -2407,6 +2504,9 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
underline { underline {
lappend t 4 ;#underline lappend t 4 ;#underline
} }
underlinedefault {
lappend t 59
}
underextendedoff { underextendedoff {
#lremove any existing 4:1 etc #lremove any existing 4:1 etc
#NOTE struct::set result order can differ depending on whether tcl/critcl imp used #NOTE struct::set result order can differ depending on whether tcl/critcl imp used
@ -2420,13 +2520,13 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
underdouble { underdouble {
lappend e 4:2 lappend e 4:2
} }
undercurly { undercurly - undercurl {
lappend e 4:3 lappend e 4:3
} }
underdotted { underdotted - underdot {
lappend e 4:4 lappend e 4:4
} }
underdashed { underdashed - underdash {
lappend e 4:5 lappend e 4:5
} }
default { default {
@ -2542,45 +2642,109 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
} }
} }
} }
rgb- - rgb0 - rgb1 - rgb2 - rgb3 - rgb4 - rgb5 - rgb6 - rgb7 - rgb8 - rgb9 { rgb- - rgb0 - rgb1 - rgb2 - rgb3 - rgb4 - rgb5 - rgb6 - rgb7 - rgb8 - rgb9 -
#decimal rgb foreground
#allow variants rgb-xxx-xxx-xxx and rgbxxx-xxx-xxx
set rgbspec [tcl::string::trim [tcl::string::range $i 3 end] -]
set rgb [tcl::string::map [list - {;} , {;}] $rgbspec]
lappend t "38;2;$rgb"
}
Rgb- - RGB- - Rgb0 - Rgb1 - Rgb2 - Rgb3 - Rgb4 - Rgb5 - Rgb6 - Rgb7 - Rgb8 - Rgb9 - RGB0 - RGB1 - RGB2 - RGB3 - RGB4 - RGB5 - RGB6 - RGB7 - RGB8 - RGB9 { Rgb- - RGB- - Rgb0 - Rgb1 - Rgb2 - Rgb3 - Rgb4 - Rgb5 - Rgb6 - Rgb7 - Rgb8 - Rgb9 - RGB0 - RGB1 - RGB2 - RGB3 - RGB4 - RGB5 - RGB6 - RGB7 - RGB8 - RGB9 {
#decimal rgb background #decimal rgb foreground/background
set rgbspec [tcl::string::trim [tcl::string::range $i 3 end] -] #allow variants rgb-xxx-xxx-xxx and rgbxxx-xxx-xxx
set rgb [tcl::string::map [list - {;} , {;}] $rgbspec]
lappend t "48;2;$rgb" set cont [string range $i end-11 end]
} switch -- $cont {
"rgb#" { -contrasting - -contrastive {
#hex rgb foreground set iplain [string range $i 0 end-12]
set hex6 [tcl::string::trim [tcl::string::range $i 4 end] -] }
set rgb [join [::scan $hex6 %2X%2X%2X] {;}] default {
lappend t "38;2;$rgb" set iplain $i
}
}
set rgbspec [tcl::string::trim [tcl::string::range $iplain 3 end] -]
set RGB [tcl::string::map [list - { } , { } {;} { }] $rgbspec] ;#RGB as list
switch -- $cont {
-contrasting {
set rgbfinal [join [punk::ansi::colour::contrasting {*}$RGB] {;}]
}
-contrastive {
set rgbfinal [join [lindex [punk::ansi::colour::contrast_pair {*}$RGB] 0] {;}]
}
default {
set rgbfinal [join $RGB {;}]
}
}
if {[tcl::string::index $i 0] eq "r"} {
#fg
lappend t "38;2;$rgbfinal"
} else {
#bg
lappend t "48;2;$rgbfinal"
}
} }
"Rgb#" - "RGB#" { "rgb#" - "Rgb#" - "RGB#" {
#hex rgb background
set hex6 [tcl::string::trim [tcl::string::range $i 4 end] -] set hex6 [tcl::string::trim [tcl::string::range $i 4 end] -]
set rgb [join [::scan $hex6 %2X%2X%2X] {;}] #set rgb [join [::scan $hex6 %2X%2X%2X] {;}]
lappend t "48;2;$rgb" set RGB [::scan $hex6 %2X%2X%2X]
set cont [string range $i end-11 end]
switch -- $cont {
-contrasting {
set rgbfinal [join [punk::ansi::colour::contrasting {*}$RGB] {;}]
}
-contrastive {
set rgbfinal [join [lindex [punk::ansi::colour::contrast_pair {*}$RGB] 0] {;}]
}
default {
set rgbfinal [join $RGB {;}]
}
}
if {[tcl::string::index $i 0] eq "r"} {
#hex rgb foreground
lappend t "38;2;$rgbfinal"
} else {
#hex rgb background
lappend t "48;2;$rgbfinal"
}
} }
und- - und0 - und1 - und2 - und3 - und4 - und5 - und6 - und7 - und8 - und9 { und- - und0 - und1 - und2 - und3 - und4 - und5 - und6 - und7 - und8 - und9 {
#decimal rgb underline #decimal rgb underline
#allow variants und-xxx-xxx-xxx and undxxx-xxx-xxx #allow variants und-xxx-xxx-xxx and undxxx-xxx-xxx
#https://wezfurlong.org/wezterm/escape-sequences.html#csi-582-underline-color-rgb
set rgbspec [tcl::string::trim [tcl::string::range $i 3 end] -] set rgbspec [tcl::string::trim [tcl::string::range $i 3 end] -]
set rgb [tcl::string::map [list - {:} , {:}] $rgbspec] set RGB [lrange [tcl::string::map [list - { } , { } {;} { }] $rgbspec] 0 2]
lappend e "58:2::$rgb" #puts "---->'$RGB'<----"
set cont [string range $i end-11 end]
switch -- $cont {
-contrasting {
set rgbfinal [join [punk::ansi::colour::contrasting {*}$RGB] {:}]
}
-contrastive {
set rgbfinal [join [lindex [punk::ansi::colour::contrast_pair {*}$RGB] 0] {:}]
}
default {
set rgbfinal [join $RGB {:}]
}
}
#lappend e "58:2:$rgbfinal" ;# - no colorspace ID - some terminals support? which?
lappend e "58:2::$rgbfinal"
} }
"und#" { "und#" {
#hex rgb underline - (e.g kitty, wezterm) - uses colons as separators #hex rgb underline - (e.g kitty, wezterm) - uses colons as separators
set hex6 [tcl::string::trim [tcl::string::range $i 4 end] -] set hex6 [tcl::string::trim [tcl::string::range $i 4 end] -]
set rgb [join [::scan $hex6 %2X%2X%2X] {:}] #set rgb [join [::scan $hex6 %2X%2X%2X] {:}]
lappend e "58:2::$rgb" set RGB [::scan $hex6 %2X%2X%2X]
set cont [string range $i end-11 end]
switch -- $cont {
-contrasting {
set rgbfinal [join [punk::ansi::colour::contrasting {*}$RGB] {:}]
}
-contrastive {
set rgbfinal [join [lindex [punk::ansi::colour::contrast_pair {*}$RGB] 0] {:}]
}
default {
set rgbfinal [join $RGB {:}]
}
}
lappend e "58:2::$rgbfinal"
} }
undt { undt {
#CSI 58:5 UNDERLINE COLOR PALETTE INDEX
#CSI 58 : 5 : INDEX m
#variable TERM_colour_map #variable TERM_colour_map
#256 colour underline by Xterm name or by integer #256 colour underline by Xterm name or by integer
#name is xterm name or colour index from 0 - 255 #name is xterm name or colour index from 0 - 255
@ -2762,6 +2926,9 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
underline { underline {
lappend t 4 ;#underline lappend t 4 ;#underline
} }
underlinedefault {
lappend t 59
}
underextendedoff { underextendedoff {
#lremove any existing 4:1 etc #lremove any existing 4:1 etc
#use of struct::set with critcl is fast, but will reorder e (with differences depending on tcl vs critcl) #use of struct::set with critcl is fast, but will reorder e (with differences depending on tcl vs critcl)
@ -2775,13 +2942,13 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
underdouble { underdouble {
lappend e 4:2 lappend e 4:2
} }
undercurly { undercurly - undercurl {
lappend e 4:3 lappend e 4:3
} }
underdotted { underdotted - underdot {
lappend e 4:4 lappend e 4:4
} }
underdashed { underdashed - underdash {
lappend e 4:5 lappend e 4:5
} }
default { default {
@ -3262,6 +3429,8 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#tput rmam #tput rmam
return \x1b\[?7l return \x1b\[?7l
} }
proc query_mode_line_wrap {} { proc query_mode_line_wrap {} {
#*** !doctools #*** !doctools
#[call [fun query_mode_line_wrap]] #[call [fun query_mode_line_wrap]]
@ -3274,6 +3443,31 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
# \x1b\[?7\;2\$y # \x1b\[?7\;2\$y
#where 1 = set, 2 = unset. (0 = mode not recognised, 3 = permanently set, 4 = permanently unset) #where 1 = set, 2 = unset. (0 = mode not recognised, 3 = permanently set, 4 = permanently unset)
#names for other alt_screen mechanisms: 1047,1048 vs 1049?
variable decmode_names [dict create\
line_wrap 7\
LNM 20\
alt_screen 1049\
grapheme_clusters 2027\
bracketed_paste 2004\
mouse_sgr_extended 1006\
mouse_urxvt 1015\
mouse_sgr 1016\
]
proc query_mode {num_or_name} {
if {[string is integer -strict $num_or_name]} {
set m $num_or_name
} else {
variable decmode_names
if {[dict exists $decmode_names $num_or_name]} {
set m [dict get $decmode_names $num_or_name]
} else {
error "punk::ansi::query_mode unrecognised mode '$num_or_name'. Known mode names: [dict keys $decmode_names]"
}
}
return "\x1b\[?$m\$p"
}
#Alt screen buffer - smcup/rmcup ti/te #Alt screen buffer - smcup/rmcup ti/te
#Note \x1b\[?47h doesn't work at all in some terminals (e.g alacritty,cmd on windows and reportedly kitty) #Note \x1b\[?47h doesn't work at all in some terminals (e.g alacritty,cmd on windows and reportedly kitty)
@ -3658,7 +3852,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
} }
proc ansistrip2 {text} { proc ansistrip2 {text} {
#*** !doctools #*** !doctools
#[call [fun ansistrip] [arg text] ] #[call [fun ansistrip2] [arg text] ]
#[para]Return a string with ansi codes stripped out #[para]Return a string with ansi codes stripped out
#[para]Alternate graphics chars are replaced with modern unicode equivalents (e.g boxdrawing glyphs) #[para]Alternate graphics chars are replaced with modern unicode equivalents (e.g boxdrawing glyphs)
@ -6016,6 +6210,10 @@ tcl::namespace::eval punk::ansi::ansistring {
SP [list \x20 \u2420]\ SP [list \x20 \u2420]\
DEL [list \x7f \u2421]\ DEL [list \x7f \u2421]\
] ]
set map_c0 [dict create]
dict for {k v} $visuals_c0 {
dict set map_c0 {*}$v
}
#alternate symbols for space #alternate symbols for space
# \u2422 Blank Symbol (b with forwardslash overly) # \u2422 Blank Symbol (b with forwardslash overly)
@ -6051,6 +6249,9 @@ tcl::namespace::eval punk::ansi::ansistring {
#miscellaneous debug code brackets #miscellaneous debug code brackets
set obm \u27e6 ;set cbm \u27e7 ;#square double brackets from Miscellaneous Mathematical Symbols-A set obm \u27e6 ;set cbm \u27e7 ;#square double brackets from Miscellaneous Mathematical Symbols-A
#unicode Tags block brackets
set obt \u2993 ;set cbt \u2994
#this private range so rarely supported in fonts - and visuals are unknown, so we will make up some 2-letter codes for now #this private range so rarely supported in fonts - and visuals are unknown, so we will make up some 2-letter codes for now
#set visuals_c1 [tcl::dict::create\ #set visuals_c1 [tcl::dict::create\
# BPH [list \x82 "${ob8}\ue022 $cb8"]\ # BPH [list \x82 "${ob8}\ue022 $cb8"]\
@ -6120,9 +6321,21 @@ tcl::namespace::eval punk::ansi::ansistring {
APC [list \x9f "${ob8}AP$cb8"]\ APC [list \x9f "${ob8}AP$cb8"]\
] ]
#unicode Tags block - nonprinting mapped to ascii 0-127
set visuals_tags [tcl::dict::create]
for {set i 917504} {$i < 917632} {incr i} {
set asciidec [expr {$i - 917504}]
set vis [format %c $asciidec]
if {[dict exists $map_c0 $vis]} {
set vis [dict get $map_c0 $vis]
}
tcl::dict::set visuals_tags TAG$asciidec [list [format %c $i] "${obt}$vis${cbt}"]
}
set hack [tcl::dict::create] set hack [tcl::dict::create]
tcl::dict::set hack BOM1 [list \uFEFF "${obm}\U1f4a3$cbm"] ;#byte order mark/ ZWNBSP (ZWNBSP usage generally deprecated) - a picture of a bomb(2wide glyph) tcl::dict::set hack BOM1 [list \uFEFF "${obm}\U1f4a3$cbm"] ;#byte order mark/ ZWNBSP (ZWNBSP usage generally deprecated) - a picture of a bomb(2wide glyph)
tcl::dict::set hack ZWSP [list \u200B "${obm}ZWSP$cbm"]
#review - other boms? Encoding dependent? #review - other boms? Encoding dependent?
tcl::dict::set hack DCS [list \x90 "${ob8}\u2328 $cb8"] ;#keyboard from Miscellaneous Technical - 1 wide + pad. tcl::dict::set hack DCS [list \x90 "${ob8}\u2328 $cb8"] ;#keyboard from Miscellaneous Technical - 1 wide + pad.
@ -6133,7 +6346,7 @@ tcl::namespace::eval punk::ansi::ansistring {
tcl::dict::set hack PM [list \x9e "${ob8}PM$cb8"] tcl::dict::set hack PM [list \x9e "${ob8}PM$cb8"]
tcl::dict::set hack APC [list \x9f "${ob8}\U1f534$cb8"] ;#bright red ball from Miscellaneoust Symbols and Pictographs - 2 wide (APC also noted as a potential security risk) tcl::dict::set hack APC [list \x9f "${ob8}\U1f534$cb8"] ;#bright red ball from Miscellaneoust Symbols and Pictographs - 2 wide (APC also noted as a potential security risk)
set debug_visuals [tcl::dict::merge $visuals_c0 $visuals_c1 $hack] set debug_visuals [tcl::dict::merge $visuals_c0 $visuals_c1 $hack $visuals_tags]
#for repeated interaction with the same ANSI string - a mechanism to store state is more efficient #for repeated interaction with the same ANSI string - a mechanism to store state is more efficient
proc NEW {string} { proc NEW {string} {
@ -6165,7 +6378,7 @@ tcl::namespace::eval punk::ansi::ansistring {
-sp 1\ -sp 1\
] ]
set argopts [lrange $args 0 end-1] set argopts [lrange $args 0 end-1]
if {[llength $argopts] % 2 != 0} { if {[llength $argopts] % 2} {
error "ansistring VIEW options must be option-value pairs, received '$argopts'. Known opts [tcl::dict::keys $defaults]" error "ansistring VIEW options must be option-value pairs, received '$argopts'. Known opts [tcl::dict::keys $defaults]"
} }
set opts [tcl::dict::merge $defaults $argopts] set opts [tcl::dict::merge $defaults $argopts]
@ -6760,7 +6973,240 @@ tcl::namespace::eval punk::ansi::ansistring {
#*** !doctools #*** !doctools
#[list_end] [comment {--- end definitions namespace punk::ansi::ta ---}] #[list_end] [comment {--- end definitions namespace punk::ansi::ta ---}]
} }
tcl::namespace::eval punk::ansi::control {
proc APC {args} {
return \x1b_[join $args {;}]\x1b\\
}
proc APC8 {args} {
return \x9f[join $args {;}]\x9c
}
proc CSI {args} {
set finalarg [lindex $args end]
set finalbyte [string index $finalarg end]
if {![regexp {[\x40-\x73]} $finalbyte]} {
error "::punk::ansi::control::CSI final byte must be one in the set @A-Z\[\\\]^_`a-z\{|\}~"
}
if {$finalarg eq $finalbyte} {
return \x1b\[[join [lrange $args 0 end-1] {;}]$finalbyte
} else {
return \x1b\[[join $args {;}]
}
}
proc CSI8 {args} {
set finalarg [lindex $args end]
set finalbyte [string index $finalarg end]
if {![regexp {[\x40-\x73]} $finalbyte]} {
error "::punk::ansi::control::CSI final byte must be one in the set @A-Z\[\\\]^_`a-z\{|\}~"
}
if {$finalarg eq $finalbyte} {
return \x9b[join [lrange $args 0 end-1] {;}]$finalbyte
} else {
return \x9b[join $args {;}]
}
}
proc DCS {args} {
return \x1bP[join $args {;}]\x1b\\
}
proc DCS8 {args} {
return \x90[join $args {;}]\x9c
}
proc OSC {args} {
return \x1b\][join $args {;}]\x1b\\
}
proc OSC8 {args} {
return \x9d[join $args {;}]\x9c
}
}
namespace eval punk::ansi::colour {
package require punk::assertion
if {[catch {namespace import ::punk::assertion::assert} errM]} {
puts stderr "punk error importing punk::assertion::assert\n$errM"
puts stderr "punk::a* commands:[info commands ::punk::a*]"
}
punk::assertion::active on
#see also colors package
#https://sourceforge.net/p/irrational-numbers/code/HEAD/tree/pkgs/Colors/trunk/colors.tcl#l159
# classic formula for luminance (0.0 .. 100.0)
proc luminance {R G B} {
return [expr {(0.3*$R + 0.59*$G + 0.11*$B)/255.0}]
}
#New colour's luminance is dark if orig-colour is bright, and viceversa
#(note not all colours are invertable to return original)
proc contrasting {R G B} {
set lum [luminance $R $G $B]
if {$lum < 0.597} {
set lum 0.9
} else {
set lum 0.2
}
lassign [RGB2hsl $R $G $B] h s l
return [hsl2RGB $h $s $lum]
}
proc contrast_pair {R G B} {
set contra [contrasting $R $G $B]
set back [contrasting {*}$contra]
return [list $back $contra] ;#back may or may not equal original R G B
}
proc hsl2RGB { H S L } {
if { $L < 0.5 } {
set Q [expr {$L*(1.0+$S)}]
} else {
set Q [expr {$L+$S-($L*$S)}]
}
set P [expr {2.0*$L-$Q}]
set Hk [expr {$H/360.0}]
set T(R) [expr {$Hk+1.0/3.0}]
set T(G) $Hk
set T(B) [expr {$Hk-1.0/3.0}]
# normalize
foreach c {R G B} {
if {$T($c) < 0.0} { set T($c) [expr {$T($c)+1.0}] }
if {$T($c) > 1.0} { set T($c) [expr {$T($c)-1.0}] }
}
foreach c {R G B} {
if {$T($c) < [expr {1.0/6.0}]} {
set T($c) [expr {$P+($Q-$P)*6.0*$T($c)}]
} elseif {$T($c) < 0.5} {
set T($c) $Q
} elseif {$T($c) < [expr {2.0/3.0}]} {
set T($c) [expr {$P+($Q-$P)*(2.0/3.0-$T($c))*6.0}]
} else {
set T($c) $P
}
set T($c) [expr {round($T($c)*255)}]
}
return [list $T(R) $T(G) $T(B)]
}
proc RGB2hsl { R G B } {
set r [expr {$R/255.0}]
set g [expr {$G/255.0}]
set b [expr {$B/255.0}]
set max $r
set min $r
if { $g > $max } { set max $g }
if { $g < $min } { set min $g }
if { $b > $max } { set max $b }
if { $b < $min } { set min $b }
if { $max == $min } {
set H 0.0
} elseif { $b == $max } {
set H [expr {60* ($r-$g)/($max-$min)+240}]
} elseif { $g == $max } {
set H [expr {60* ($b-$r)/($max-$min)+120}]
} else {
# $r == $max
if { $g >= $b } {
set H [expr {60* ($g-$b)/($max-$min)}]
} else {
set H [expr {60* ($g-$b)/($max-$min)+360}]
}
}
set L [expr {($max+$min)/2}]
if { $L == 0.0 || $max == $min } {
set S 0.0
} elseif { $L <= 0.5 } {
set S [expr {($max-$min)/($max+$min)}]
} else {
set S [expr {($max-$min)/(2.0-($max+$min))}]
}
return [list $H $S $L]
}
#red green blue to hsl (hue saturation luminance)
#https://www.rapidtables.com/convert/color/rgb-to-hsl.html
proc jexer_rgb_to_hsl {red green blue} {
#algorithm port from Jexer LegacySixelEncode.java - with thanks to Autumn Lamonte (MIT lic)
assert {$red >=0 && $red <= 255}
assert {$green >=0 && $green <= 255}
assert {$blue >=0 && $blue <= 255}
set R [expr {$red / 255.0}]
set G [expr {$green / 255.0}]
set B [expr {$blue / 255.0}]
set Rmax 0
set Gmax 0
set Bmax 0
set min [expr {$R < $G ? $R : $G}]
set min [expr {$min < $B ? $min : $B}]
set max 0
if {($R >= $G) && ($R >= $B)} {
set max $R
set Rmax 1
} elseif {($G >= $R) && ($G >= $B)} {
set max $G
set Gmax 1
} elseif {($B >= $G) && ($B >= $R)} {
set max $B
set Bmax 1
}
set L [expr {($min + $max) / 2.0}]
set H 0.0
set S 0.0
#REVIEW - java allows floating point division by 0.0 - producing positive infinity, negative infinity or NaN
#This makes the original java algorithm a little more obscure
if {$min != $max} {
#no divide by zero issues due to min != max
if {$L < 0.5} {
set S [expr {($max - $min) / ($max + $min)}]
} else {
set S [expr {($max - $min) / (2.0 - $max - $min)}]
}
}
if {$Rmax} {
#puts "G'$G' B'$B' max'$max' min'$min'"
assert {$Gmax == 0}
assert {$Bmax == 0}
if {($max - $min) == 0} {
set H 0.0 ;#review
} else {
set H [expr {($G - $B) / ($max - $min)}]
}
} elseif {$Gmax} {
assert {$Rmax == 0}
assert {$Bmax == 0}
if {($max - $min) == 0} {
set H 2.0
} else {
set H [expr {2.0 + ($B - $R) / ($max - $min)}]
}
} elseif {$Bmax} {
assert {$Rmax == 0}
assert {$Gmax == 0}
if {($max - $min) == 0} {
set H 4.0
} else {
set H [expr {4.0 + ($R - $G) / ($max - $min)}]
}
}
if {$H < 0.0} {
set H [expr {$H + 6.0}]
}
#Tcl mathfunc round vs int (which rounds down)
set hue [expr {round($H * 60)}]
set sat [expr {round($S * 100)}]
set lum [expr {round($L * 100)}]
assert {$hue >= 0 && $hue <= 360}
assert {$sat >= 0 && $sat <= 100}
assert {$lum >= 0 && $lum <= 100}
return [list $hue $sat $lum]
}
}
tcl::namespace::eval punk::ansi::internal { tcl::namespace::eval punk::ansi::internal {
proc splitn {str {len 1}} { proc splitn {str {len 1}} {
#from textutil::split::splitn #from textutil::split::splitn
@ -6837,7 +7283,7 @@ tcl::namespace::eval punk::ansi::internal {
if {$2digithexchars eq ""} { if {$2digithexchars eq ""} {
return "" return ""
} }
if {[tcl::string::length $2digithexchars] % 2 != 0} { if {[tcl::string::length $2digithexchars] % 2} {
error "hex2str requires an even number of hex digits (2 per character)" error "hex2str requires an even number of hex digits (2 per character)"
} }
set 2str "" set 2str ""

143
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/args-0.1.0.tm

@ -1,6 +1,6 @@
# -*- tcl -*- # -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'pmix make' or bin/punkmake to update from <pkg>-buildversion.txt # Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'pmix make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.2.tm # module template: punkshell/src/decktemplates/vendor/punk/modules/template_module-0.0.2.tm
# #
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. # Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. # Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
@ -202,6 +202,7 @@
#[list_begin itemized] #[list_begin itemized]
package require Tcl 8.6- package require Tcl 8.6-
#optional? punk::trie #optional? punk::trie
#optional? punk::textblock
#*** !doctools #*** !doctools
#[item] [package {Tcl 8.6-}] #[item] [package {Tcl 8.6-}]
@ -267,7 +268,10 @@ tcl::namespace::eval punk::args {
#[para] Core API functions for punk::args #[para] Core API functions for punk::args
#[list_begin definitions] #[list_begin definitions]
#todo - some sort of punk::args::cherrypick operation to get spec from an existing set
#todo - doctools output from definition
#dict get value with default wrapper for tcl 8.6
if {[info commands ::tcl::dict::getdef] eq ""} { if {[info commands ::tcl::dict::getdef] eq ""} {
#package require punk::lib #package require punk::lib
#interp alias "" ::punk::args::Dict_getdef "" ::punk::lib::dict_getdef #interp alias "" ::punk::args::Dict_getdef "" ::punk::lib::dict_getdef
@ -301,7 +305,7 @@ tcl::namespace::eval punk::args {
#review - how to make work with trie prefix e.g -corner -aliases {-corners} #review - how to make work with trie prefix e.g -corner -aliases {-corners}
#We mightn't want the prefix to be longer just because of an alias #We mightn't want the prefix to be longer just because of an alias
proc Get_argspecs {optionspecs args} { proc definition {optionspecs args} {
variable argspec_cache variable argspec_cache
#variable argspecs ;#REVIEW!! #variable argspecs ;#REVIEW!!
variable argspec_ids variable argspec_ids
@ -434,6 +438,7 @@ tcl::namespace::eval punk::args {
} }
} }
set proc_info {} set proc_info {}
set id_info {} ;#e.g -children <list> ??
set opt_any 0 set opt_any 0
set val_min 0 set val_min 0
set val_max -1 ;#-1 for no limit set val_max -1 ;#-1 for no limit
@ -444,8 +449,8 @@ tcl::namespace::eval punk::args {
"" - # {continue} "" - # {continue}
} }
set linespecs [lassign $trimln argname] set linespecs [lassign $trimln argname]
if {$argname ne "*id" && [llength $linespecs] %2 != 0} { if {$argname ne "*id" && [llength $linespecs] % 2} {
error "punk::args::get_dict - bad optionspecs line for record '$argname' Remaining items on line must be in paired option-value format - received '$linespecs'" error "punk::args::definition - bad optionspecs line for record '$argname' Remaining items on line must be in paired option-value format - received '$linespecs'"
} }
set firstchar [tcl::string::index $argname 0] set firstchar [tcl::string::index $argname 0]
set secondchar [tcl::string::index $argname 1] set secondchar [tcl::string::index $argname 1]
@ -454,14 +459,18 @@ tcl::namespace::eval punk::args {
switch -- [tcl::string::range $argname 1 end] { switch -- [tcl::string::range $argname 1 end] {
id { id {
#id line must have single entry - a unique id assigned by the user - an id will be allocated if no id line present or the value is "auto" #id line must have single entry - a unique id assigned by the user - an id will be allocated if no id line present or the value is "auto"
if {[llength $starspecs] != 1} { if {[llength $starspecs] == 0} {
error "punk::args::Get_argspecs - *id line must have a single entry following *id." error "punk::args::definition - *id line must have at least a single entry following *id."
} }
if {$spec_id ne ""} { if {$spec_id ne ""} {
#disallow duplicate *id line #disallow duplicate *id line
error "punk::args::Get_argspecs - *id already set. Existing value $spec_id" error "punk::args::definition - *id already set. Existing value $spec_id"
}
set spec_id [lindex $starspecs 0]
set id_info [lrange $starspecs 1 end]
if {[llength $id_info] %2} {
error "punk::args::definition - bad *id line. Remaining items on line after *id <id> must be in paired option-value format - received '$linespecs'"
} }
set spec_id $starspecs
} }
proc { proc {
#allow arbitrary - review #allow arbitrary - review
@ -523,7 +532,7 @@ tcl::namespace::eval punk::args {
-type -optional -allow_ansi -validate_ansistripped -strip_ansi -multiple\ -type -optional -allow_ansi -validate_ansistripped -strip_ansi -multiple\
-regexprepass -regexprefail -validationtransform\ -regexprepass -regexprefail -validationtransform\
} }
error "punk::args::Get_argspecs - unrecognised key '$k' in *opts line. Known keys: $known" error "punk::args::definition - unrecognised key '$k' in *opts line. Known keys: $known"
} }
} }
} }
@ -534,14 +543,14 @@ tcl::namespace::eval punk::args {
-min - -min -
-minvalues { -minvalues {
if {$v < 0} { if {$v < 0} {
error "punk::args::Get_argspecs - minimum acceptable value for key '$k' in *opts line is 0. got $v" error "punk::args::definition - minimum acceptable value for key '$k' in *opts line is 0. got $v"
} }
set val_min $v set val_min $v
} }
-max - -max -
-maxvalues { -maxvalues {
if {$v < -1} { if {$v < -1} {
error "punk::args::Get_argspecs - minimum acceptable value for key '$k' in *opts line is -1 (indicating unlimited). got $v" error "punk::args::definition - minimum acceptable value for key '$k' in *opts line is -1 (indicating unlimited). got $v"
} }
set val_max $v set val_max $v
} }
@ -594,14 +603,14 @@ tcl::namespace::eval punk::args {
-type -optional -allow_ansi -validate_ansistripped -strip_ansi -multiple\ -type -optional -allow_ansi -validate_ansistripped -strip_ansi -multiple\
-regexprepass -regexprefail -validationtransform\ -regexprepass -regexprefail -validationtransform\
} }
error "punk::args::Get_argspecs - unrecognised key '$k' in *values line. Known keys: $known" error "punk::args::definition - unrecognised key '$k' in *values line. Known keys: $known"
} }
} }
} }
} }
default { default {
error "punk::args::Get_argspecs - unrecognised * line in '$ln'. Expected *proc *opts or *values - use **name if paramname needs to be *name" error "punk::args::definition - unrecognised * line in '$ln'. Expected *proc *opts or *values - use **name if paramname needs to be *name"
} }
} }
continue continue
@ -654,7 +663,7 @@ tcl::namespace::eval punk::args {
lappend opt_solos $argname lappend opt_solos $argname
} else { } else {
#-solo only valid for flags #-solo only valid for flags
error "punk::args::get_dict - invalid -type 'none' for positional argument positional argument '$argname'" error "punk::args::definition - invalid -type 'none' for positional argument positional argument '$argname'"
} }
} }
any - anything { any - anything {
@ -681,8 +690,8 @@ tcl::namespace::eval punk::args {
} }
-validationtransform { -validationtransform {
#string is dict only 8.7/9+ #string is dict only 8.7/9+
if {([llength $specval] % 2) != 0} { if {[llength $specval] % 2} {
error "punk::args::get_dict - invalid value for key '$spec' in specifications for argument '$argname' - value must be a dictionary" error "punk::args::definition - invalid value for key '$spec' in specifications for argument '$argname' - value must be a dictionary"
} }
dict for {tk tv} $specval { dict for {tk tv} $specval {
switch -- $tk { switch -- $tk {
@ -690,7 +699,7 @@ tcl::namespace::eval punk::args {
} }
default { default {
set known_transform_keys [list -function -type -minlen -maxlen -range] ;#-choices etc? set known_transform_keys [list -function -type -minlen -maxlen -range] ;#-choices etc?
error "punk::args::get_dict - invalid subkey $tk for key '$spec' in specifications for argument '$argname' must be one of: $known_transform_keys" error "punk::args::definition - invalid subkey $tk for key '$spec' in specifications for argument '$argname' must be one of: $known_transform_keys"
} }
} }
} }
@ -701,7 +710,7 @@ tcl::namespace::eval punk::args {
-nocase -optional -multiple -validate_ansistripped -allow_ansi -strip_ansi -help\ -nocase -optional -multiple -validate_ansistripped -allow_ansi -strip_ansi -help\
-regexprepass -regexprefail -validationtransform\ -regexprepass -regexprefail -validationtransform\
] ]
error "punk::args::get_dict - unrecognised key '$spec' in specifications for argument '$argname' Known option specification keys: $known_argspecs" error "punk::args::definition - unrecognised key '$spec' in specifications for argument '$argname' Known option specification keys: $known_argspecs"
} }
} }
} }
@ -764,6 +773,7 @@ tcl::namespace::eval punk::args {
valspec_defaults $valspec_defaults\ valspec_defaults $valspec_defaults\
val_checks_defaults $val_checks_defaults\ val_checks_defaults $val_checks_defaults\
proc_info $proc_info\ proc_info $proc_info\
id_info $id_info\
] ]
tcl::dict::set argspec_cache $cache_key $result tcl::dict::set argspec_cache $cache_key $result
#tcl::dict::set argspecs $spec_id $optionspecs #tcl::dict::set argspecs $spec_id $optionspecs
@ -817,6 +827,7 @@ tcl::namespace::eval punk::args {
} }
set caller [regexp -inline {\S+} $cmdinfo] set caller [regexp -inline {\S+} $cmdinfo]
if {$caller eq "namespace"} { if {$caller eq "namespace"} {
# review - message?
set cmdinfo "punk::args::get_dict called from namespace" set cmdinfo "punk::args::get_dict called from namespace"
} }
return $cmdinfo return $cmdinfo
@ -825,6 +836,7 @@ tcl::namespace::eval punk::args {
#basic recursion blocker #basic recursion blocker
variable arg_error_isrunning 0 variable arg_error_isrunning 0
proc arg_error {msg spec_dict {badarg ""}} { proc arg_error {msg spec_dict {badarg ""}} {
#limit colours to standard 16 so that themes can apply to help output
variable arg_error_isrunning variable arg_error_isrunning
if {$arg_error_isrunning} { if {$arg_error_isrunning} {
error "arg_error already running - error in arg_error?\n triggering errmsg: $badarg" error "arg_error already running - error in arg_error?\n triggering errmsg: $badarg"
@ -843,20 +855,17 @@ tcl::namespace::eval punk::args {
set procname [::punk::args::Dict_getdef $spec_dict proc_info -name ""] set procname [::punk::args::Dict_getdef $spec_dict proc_info -name ""]
set prochelp [::punk::args::Dict_getdef $spec_dict proc_info -help ""] set prochelp [::punk::args::Dict_getdef $spec_dict proc_info -help ""]
#set t [textblock::class::table new [a+ web-yellow]Usage[a]]
set t [textblock::class::table new [a+ brightyellow]Usage[a]] set t [textblock::class::table new [a+ brightyellow]Usage[a]]
set blank_header_col [list ""] set blank_header_col [list ""]
if {$procname ne ""} { if {$procname ne ""} {
lappend blank_header_col "" lappend blank_header_col ""
#set procname_display [a+ web-white]$procname[a]
set procname_display [a+ brightwhite]$procname[a] set procname_display [a+ brightwhite]$procname[a]
} else { } else {
set procname_display "" set procname_display ""
} }
if {$prochelp ne ""} { if {$prochelp ne ""} {
lappend blank_header_col "" lappend blank_header_col ""
#set prochelp_display [a+ web-white]$prochelp[a]
set prochelp_display [a+ brightwhite]$prochelp[a] set prochelp_display [a+ brightwhite]$prochelp[a]
} else { } else {
set prochelp_display "" set prochelp_display ""
@ -880,12 +889,19 @@ tcl::namespace::eval punk::args {
$t configure_header 2 -values {Arg Type Default Multiple Help} $t configure_header 2 -values {Arg Type Default Multiple Help}
} }
#set c_default [a+ web-white Web-limegreen]
set c_default [a+ brightwhite Brightgreen] set RST [a]
#set c_badarg [a+ web-crimson] #set A_DEFAULT [a+ brightwhite Brightgreen]
set c_badarg [a+ brightred] set A_DEFAULT ""
#set greencheck [a+ web-limegreen]\u2713[a] set A_BADARG [a+ brightred]
set greencheck [a+ brightgreen]\u2713[a] set greencheck [a+ brightgreen]\u2713[a]
set A_PREFIX [a+ green] ;#use a+ so colour off can apply
if {$A_PREFIX eq ""} {
set A_PREFIX [a+ underline]
set A_PREFIXEND [a+ nounderline]\u200B ;#padding will take ANSI from last char - so add a zero width space
} else {
set A_PREFIXEND $RST
}
set opt_names [list] set opt_names [list]
set opt_names_display [list] set opt_names_display [list]
@ -894,8 +910,6 @@ tcl::namespace::eval punk::args {
set trie [punk::trie::trieclass new {*}[dict get $spec_dict opt_names]] set trie [punk::trie::trieclass new {*}[dict get $spec_dict opt_names]]
set idents [dict get [$trie shortest_idents ""] scanned] set idents [dict get [$trie shortest_idents ""] scanned]
$trie destroy $trie destroy
set M "\x1b\[32m" ;#mark in green
set RST "\x1b\[m"
foreach c [dict get $spec_dict opt_names] { foreach c [dict get $spec_dict opt_names] {
set id [dict get $idents $c] set id [dict get $idents $c]
#REVIEW #REVIEW
@ -907,7 +921,7 @@ tcl::namespace::eval punk::args {
set prefix [string range $c 0 $idlen-1] set prefix [string range $c 0 $idlen-1]
set tail [string range $c $idlen end] set tail [string range $c $idlen end]
} }
lappend opt_names_display $M$prefix$RST$tail lappend opt_names_display $A_PREFIX$prefix$A_PREFIXEND$tail
#lappend opt_names_display $M[ansistring VIEW $prefix]$RST[ansistring VIEW $tail] #lappend opt_names_display $M[ansistring VIEW $prefix]$RST[ansistring VIEW $tail]
lappend opt_names $c lappend opt_names $c
} }
@ -916,18 +930,31 @@ tcl::namespace::eval punk::args {
set opt_names_display $opt_names set opt_names_display $opt_names
} }
} }
set val_names [dict get $spec_dict val_names] set trailing_val_names [dict get $spec_dict val_names] ;#temporarily assign all as trailing
set val_names_display $val_names set leading_val_names [list]
dict for {argname info} [tcl::dict::get $spec_dict arg_info] {
if {![string match -* $argname]} {
lappend leading_val_names [lpop trailing_val_names 0]
} else {
break
}
}
if {![llength $leading_val_names] && ![llength $opt_names]} {
#all vals were actually trailing - no opts
set trailing_val_names $leading_val_names
set leading_val_names {}
}
set leading_val_names_display $leading_val_names
set trailing_val_names_display $trailing_val_names
#display options first then values #display options first then values
foreach argumentset [list [list $opt_names_display $opt_names] [list $val_names_display $val_names]] { foreach argumentset [list [list $leading_val_names_display $leading_val_names] [list $opt_names_display $opt_names] [list $trailing_val_names_display $trailing_val_names]] {
lassign $argumentset argnames_display argnames lassign $argumentset argnames_display argnames
foreach argshow $argnames_display arg $argnames { foreach argshow $argnames_display arg $argnames {
set arginfo [dict get $spec_dict arg_info $arg] set arginfo [dict get $spec_dict arg_info $arg]
if {[dict exists $arginfo -default]} { if {[dict exists $arginfo -default]} {
#set default $c_default[dict get $arginfo -default] set default $A_DEFAULT[dict get $arginfo -default]$RST
set default [dict get $arginfo -default]
} else { } else {
set default "" set default ""
} }
@ -954,8 +981,6 @@ tcl::namespace::eval punk::args {
set trie [punk::trie::trieclass new {*}[dict get $arginfo -choices]] set trie [punk::trie::trieclass new {*}[dict get $arginfo -choices]]
set idents [dict get [$trie shortest_idents ""] scanned] set idents [dict get [$trie shortest_idents ""] scanned]
$trie destroy $trie destroy
set M "\x1b\[32m" ;#mark in green
set RST "\x1b\[m"
foreach c [dict get $arginfo -choices] { foreach c [dict get $arginfo -choices] {
set id [dict get $idents $c] set id [dict get $idents $c]
if {$id eq $c} { if {$id eq $c} {
@ -966,7 +991,7 @@ tcl::namespace::eval punk::args {
set prefix [string range $c 0 $idlen-1] set prefix [string range $c 0 $idlen-1]
set tail [string range $c $idlen end] set tail [string range $c $idlen end]
} }
lappend formattedchoices "$M[ansistring VIEW $prefix]$RST[ansistring VIEW $tail]" lappend formattedchoices "$A_PREFIX[ansistring VIEW $prefix]$A_PREFIXEND[ansistring VIEW $tail]"
} }
} errM]} { } errM]} {
puts stderr "prefix marking failed\n$errM" puts stderr "prefix marking failed\n$errM"
@ -999,7 +1024,7 @@ tcl::namespace::eval punk::args {
} }
$t add_row [list $argshow [dict get $arginfo -type] $default $multiple $help] $t add_row [list $argshow [dict get $arginfo -type] $default $multiple $help]
if {$arg eq $badarg} { if {$arg eq $badarg} {
$t configure_row [expr {[$t row_count]-1}] -ansibase $c_badarg $t configure_row [expr {[$t row_count]-1}] -ansibase $A_BADARG
} }
} }
} }
@ -1033,10 +1058,10 @@ tcl::namespace::eval punk::args {
#provide ability to look up and reuse definitions from ids etc #provide ability to look up and reuse definitions from ids etc
# #
proc get_dict_by_id {id {arglist ""}} { proc get_by_id {id {arglist ""}} {
set spec [get_spec $id] set spec [get_spec $id]
if {$spec eq ""} { if {$spec eq ""} {
error "punk::args::get_dict_by_id - no such id: $id" error "punk::args::get_by_id - no such id: $id"
} }
return [get_dict $spec $arglist] return [get_dict $spec $arglist]
} }
@ -1121,7 +1146,7 @@ tcl::namespace::eval punk::args {
} }
set argspecs [Get_argspecs $optionspecs] set argspecs [definition $optionspecs]
tcl::dict::with argspecs {} ;#turn keys into vars tcl::dict::with argspecs {} ;#turn keys into vars
#puts "-arg_info->$arg_info" #puts "-arg_info->$arg_info"
set flagsreceived [list] ;#for checking if required flags satisfied set flagsreceived [list] ;#for checking if required flags satisfied
@ -1132,11 +1157,24 @@ tcl::namespace::eval punk::args {
#todo: -minmultiple -maxmultiple ? #todo: -minmultiple -maxmultiple ?
# -- --- --- ---
# Handle leading positionals
# todo - consider allowing last leading positional to have -multiple 1 but only if there exists an eopts marker later (--) ?
set opts $opt_defaults set opts $opt_defaults
set pre_values {}
dict for {a info} $arg_info {
#todo - flag for possible subhandler - whether leading - or not (shellfilter concept)
if {![string match -* $a]} {
lappend pre_values [lpop rawargs 0]
} else {
break
}
}
#assert - rawargs has been reduced by leading positionals
if {$id ne "jtest"} { if {$id ne "jtest"} {
set arglist {} set arglist {}
set values {} set post_values {}
#val_min, val_max #val_min, val_max
#puts stderr "rawargs: $rawargs" #puts stderr "rawargs: $rawargs"
#puts stderr "arg_info: $arg_info" #puts stderr "arg_info: $arg_info"
@ -1157,7 +1195,7 @@ tcl::namespace::eval punk::args {
if {$remaining_args_including_this <= $val_min} { if {$remaining_args_including_this <= $val_min} {
# if current arg is -- it will pass through as a value here # if current arg is -- it will pass through as a value here
set arglist [lrange $rawargs 0 $i-1] set arglist [lrange $rawargs 0 $i-1]
set values [lrange $rawargs $i end] set post_values [lrange $rawargs $i end]
break break
} }
@ -1169,19 +1207,19 @@ tcl::namespace::eval punk::args {
if {$remaining_args_including_this == $val_max} { if {$remaining_args_including_this == $val_max} {
#assume it's a value. #assume it's a value.
set arglist [lrange $rawargs 0 $i-1] set arglist [lrange $rawargs 0 $i-1]
set values [lrange $rawargs $i end] set post_values [lrange $rawargs $i end]
} else { } else {
#assume it's an end-of-options marker #assume it's an end-of-options marker
lappend flagsreceived -- lappend flagsreceived --
set arglist [lrange $rawargs 0 $i] set arglist [lrange $rawargs 0 $i]
set values [lrange $rawargs $i+1 end] set post_values [lrange $rawargs $i+1 end]
} }
} else { } else {
#unlimited number of values accepted #unlimited number of post_values accepted
#treat this as eopts - we don't care if remainder look like options or not #treat this as eopts - we don't care if remainder look like options or not
lappend flagsreceived -- lappend flagsreceived --
set arglist [lrange $rawargs 0 $i] set arglist [lrange $rawargs 0 $i]
set values [lrange $rawargs $i+1 end] set post_values [lrange $rawargs $i+1 end]
} }
break break
} else { } else {
@ -1194,7 +1232,7 @@ tcl::namespace::eval punk::args {
#if no optvalue following - assume it's a value #if no optvalue following - assume it's a value
#(caller should probably have used -- before it) #(caller should probably have used -- before it)
set arglist [lrange $rawargs 0 $i-1] set arglist [lrange $rawargs 0 $i-1]
set values [lrange $rawargs $i end] set post_values [lrange $rawargs $i end]
break break
} }
@ -1242,7 +1280,7 @@ tcl::namespace::eval punk::args {
#unmatched option in right position to be considered a value - treat like eopts #unmatched option in right position to be considered a value - treat like eopts
#review - document that an unspecified arg within range of possible values will act like eopts -- #review - document that an unspecified arg within range of possible values will act like eopts --
set arglist [lrange $rawargs 0 $i-1] set arglist [lrange $rawargs 0 $i-1]
set values [lrange $rawargs $i end] set post_values [lrange $rawargs $i end]
break break
} }
if {$opt_any} { if {$opt_any} {
@ -1284,12 +1322,13 @@ tcl::namespace::eval punk::args {
} else { } else {
#not flaglike #not flaglike
set arglist [lrange $rawargs 0 $i-1] set arglist [lrange $rawargs 0 $i-1]
set values [lrange $rawargs $i end] set post_values [lrange $rawargs $i end]
break break
} }
} }
set values [list {*}$pre_values {*}$post_values]
} else { } else {
set values $rawargs ;#no -flags detected set values [list {*}$pre_values {*}$rawargs] ;#no -flags detected
set arglist [list] set arglist [list]
} }
#puts stderr "--> arglist: $arglist" #puts stderr "--> arglist: $arglist"

102
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/char-0.1.0.tm

@ -1912,19 +1912,32 @@ tcl::namespace::eval punk::char {
tailcall ansifreestring_width $text tailcall ansifreestring_width $text
} }
#faster than textutil::wcswidth (at least for string up to a few K in length) #todo - consider disallowing/erroring out when \r \n in string?
# - tab/vtab?
# - compare with wcswidth returning -1 for entire string containing such in python,perl
proc wcswidth {string} { proc wcswidth {string} {
#faster than textutil::wcswidth (at least for string up to a few K in length)
#..but - 'scan' is horrible for 400K+
#TODO
set codes [scan $string [tcl::string::repeat %c [tcl::string::length $string]]] set codes [scan $string [tcl::string::repeat %c [tcl::string::length $string]]]
set width 0 set width 0
foreach c $codes { foreach c $codes {
if {$c <= 255} { #unicode Tags block zero width
incr width if {$c < 917504 || $c > 917631} {
} else { if {$c <= 255} {
set w [textutil::wcswidth_char $c] #review - non-printing ascii? why does textutil::wcswidth report 1 ??
if {$w < 0} { #todo - compare with python or other lang wcwidth
return -1 if {!($c < 31 || $c == 127)} {
incr width
}
} else { } else {
#TODO - various other joiners and non-printing chars
set w [textutil::wcswidth_char $c]
if {$w < 0} {
return -1
} else {
incr width $w incr width $w
}
} }
} }
} }
@ -2029,7 +2042,8 @@ tcl::namespace::eval punk::char {
# return [tcl::string::length $text] # return [tcl::string::length $text]
#} #}
if {![regexp "\[\uFF-\U10FFFF\]" $text]} { if {![regexp "\[\uFF-\U10FFFF\]" $text]} {
return [tcl::string::length $text] #return [tcl::string::length $text]
return [punk::char::wcswidth $text] ;#still use our wcswidth to account for non-printable ascii
} }
#split just to get the standalone character widths - and then scan for other combiners (?) - or scan for clusters first? #split just to get the standalone character widths - and then scan for other combiners (?) - or scan for clusters first?
@ -2052,7 +2066,7 @@ tcl::namespace::eval punk::char {
#fall back to textutil::wcswidth (which doesn't for example handle diactricts/combiners so we can't use until these and other things such as \u200b and diacritics are already stripped/accounted for) #fall back to textutil::wcswidth (which doesn't for example handle diactricts/combiners so we can't use until these and other things such as \u200b and diacritics are already stripped/accounted for)
#todo - find something that understands grapheme clusters - needed also for grapheme_split #todo - find something that understands grapheme clusters - needed also for grapheme_split
#use punk::char::wcswidth - faster than the string split in textutil::wcswidth but still uses textutil::wcswidth_char #use punk::char::wcswidth - faster than the string split in textutil::wcswidth but still uses textutil::wcswidth_char
incr len [wcswidth $uc] incr len [punk::char::wcswidth $uc]
} }
#todo - work out what to do with anomalies like grave combiner which print at different lengths on different terminals (fonts?) and for which cursor-query escape sequence lies. #todo - work out what to do with anomalies like grave combiner which print at different lengths on different terminals (fonts?) and for which cursor-query escape sequence lies.
return $len return $len
@ -2229,7 +2243,68 @@ tcl::namespace::eval punk::char {
return [tcl::string::map $map $str] return [tcl::string::map $map $str]
} }
#todo - lookup from unicode tables
variable flags [dict create\
AU \U1F1E6\U1F1FA\
US \U1F1FA\U1F1F8\
ZW \U1F1FF\U1F1FC
]
variable rflags
dict for {k v} $flags {
dict set rflags $v $k
}
proc flag_from_ascii {code} {
variable flags
if {[regexp {^[A-Z]{2}$} $code]} {
if {[dict exists $flags $code]} {
return [dict get $flags $code]
} else {
error "unsupported flags code: $code"
}
} else {
#try as subregion
#e.g gbeng,gbwls,gbsct
return \U1f3f4[tag_from_ascii $code]\Ue007f
}
}
proc flag_to_ascii {charsequence} {
variable rflags
if {[dict exists $rflags $charsequence]} {
return [dict get $rflags $charsequence]
}
if {[string index $charsequence 0] eq "\U1F3F4" && [string index $charsequence end] eq "\UE007F"} {
#subdivision flag
set tag [string range $charsequence 1 end-1]
return [tag_to_ascii $tag]
}
error "unknown flag $charsequence"
}
proc tag_to_ascii {t} {
set fmt [string repeat %c [string length $t]]
set declist [scan $t $fmt]
#unicode Tags block - e0000 to e007f
set declist [lmap dec $declist {
if {$dec < 917504 || $dec > 917631} {
error "char [ansistring VIEW -lf 1 -cr 1 -vt 1 [format %c $dec]] has decimal value $dec. Not in unicode Tags block range 917504-917631 (e0000-e007f)"
}
incr dec -917504
}]
return [format $fmt {*}$declist]
}
proc tag_from_ascii {a} {
set fmt [string repeat %c [string length $a]]
set declist [scan $a $fmt]
set declist [lmap dec $declist {
if {$dec > 127} {
error "char [ansistring VIEW -lf 1 -cr 1 -vt 1 [format %c $dec]] has decimal value $dec. Not in ascii range 0-127"
}
incr dec 917504
}]
return [format $fmt {*}$declist]
}
#split into plaintext and runs of combiners (combining diacritical marks - not ZWJ or ZWJNJ) #split into plaintext and runs of combiners (combining diacritical marks - not ZWJ or ZWJNJ)
proc combiner_split {text} { proc combiner_split {text} {
@ -2250,15 +2325,12 @@ tcl::namespace::eval punk::char {
#puts "->start $start ->match $matchStart $matchEnd" #puts "->start $start ->match $matchStart $matchEnd"
lappend list [tcl::string::range $text $start $matchStart-1] [tcl::string::range $text $matchStart $matchEnd] lappend list [tcl::string::range $text $start $matchStart-1] [tcl::string::range $text $matchStart $matchEnd]
set start [expr {$matchEnd+1}] set start [expr {$matchEnd+1}]
#if {$start >= [tcl::string::length $text]} {
# break
#}
} }
lappend list [tcl::string::range $text $start end] lappend list [tcl::string::range $text $start end]
} }
#ZWJ ZWNJ ? #ZWJ ZWNJ ?
#SWSP ?
#1st shot - basic diacritics #1st shot - basic diacritics
#todo - become aware of unicode grapheme cluster boundaries #todo - become aware of unicode grapheme cluster boundaries
@ -2269,6 +2341,8 @@ tcl::namespace::eval punk::char {
#should also handle the ZWJ (and the variation selectors? eg \uFE0F) character which should account for emoji clusters #should also handle the ZWJ (and the variation selectors? eg \uFE0F) character which should account for emoji clusters
#Note - emoji cluster could be for example 11 code points/41 bytes (family emoji with skin tone modifiers for each member, 3 ZWJs) #Note - emoji cluster could be for example 11 code points/41 bytes (family emoji with skin tone modifiers for each member, 3 ZWJs)
#This still leaves a whole class of clusters.. korean etc unhandled :/ #This still leaves a whole class of clusters.. korean etc unhandled :/
#todo - tk::startOfCluster / tk::endOfCluster - try to get it brought into Tcl
#https://core.tcl-lang.org/tcl/tktview/a4c7eeaf63
proc grapheme_split {text} { proc grapheme_split {text} {
set graphemes [list] set graphemes [list]
set csplits [combiner_split $text] set csplits [combiner_split $text]
@ -2287,7 +2361,7 @@ tcl::namespace::eval punk::char {
set graphemes [list] set graphemes [list]
set csplits [combiner_split $text] set csplits [combiner_split $text]
foreach {pt combiners} [lrange $csplits 0 end-1] { foreach {pt combiners} [lrange $csplits 0 end-1] {
set pt_decs [scan $pt [tcl::string::repeat %c [tcl::string::length $pt]]] set pt_decs [scan $pt [tcl::string::repeat %c [tcl::string::length $pt]]] ;#warning scan %c... slow for v large strings (e.g 400k+)
set combiner_decs [scan $combiners [tcl::string::repeat %c [tcl::string::length $combiners]]] set combiner_decs [scan $combiners [tcl::string::repeat %c [tcl::string::length $combiners]]]
lset pt_decs end [concat [lindex $pt_decs end] $combiner_decs] lset pt_decs end [concat [lindex $pt_decs end] $combiner_decs]
lappend graphemes {*}$pt_decs lappend graphemes {*}$pt_decs

326
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/console-0.1.1.tm

@ -46,9 +46,12 @@
package require Tcl 8.6- package require Tcl 8.6-
package require Thread ;#tsv required to sync is_raw package require Thread ;#tsv required to sync is_raw
package require punk::ansi package require punk::ansi
package require punk::args
#*** !doctools #*** !doctools
#[item] [package {Tcl 8.6-}] #[item] [package {Tcl 8.6-}]
#[item] [package {Thread}]
#[item] [package {punk::ansi}] #[item] [package {punk::ansi}]
#[item] [package {punk::args}]
#*** !doctools #*** !doctools
@ -109,6 +112,8 @@ namespace eval punk::console {
variable ansi_wanted 2 ;#2 for default assumed yes, will be set to -1 for automatically unwanted when ansi unavailable values of 0 or 1 won't be autoset variable ansi_wanted 2 ;#2 for default assumed yes, will be set to -1 for automatically unwanted when ansi unavailable values of 0 or 1 won't be autoset
#punk::console namespace - contains *directly* acting functions - some based on ansi escapes from the 'ansi' sub namespace, some on local system calls or executable calls wrapped in the 'local' sub namespace #punk::console namespace - contains *directly* acting functions - some based on ansi escapes from the 'ansi' sub namespace, some on local system calls or executable calls wrapped in the 'local' sub namespace
#directly acting means they write to stdout to cause the console to peform the action, or they perform the action immediately via other means. #directly acting means they write to stdout to cause the console to peform the action, or they perform the action immediately via other means.
#punk::console::ansi contains a subset of punk::ansi, but with emission to stdout as opposed to simply returning the ansi sequence. #punk::console::ansi contains a subset of punk::ansi, but with emission to stdout as opposed to simply returning the ansi sequence.
@ -255,6 +260,8 @@ namespace eval punk::console {
enable_bracketed_paste enable_bracketed_paste
} }
#todo stop_application_mode {} {}
proc mode {{raw_or_line query}} { proc mode {{raw_or_line query}} {
#variable is_raw #variable is_raw
variable ansi_available variable ansi_available
@ -634,7 +641,7 @@ namespace eval punk::console {
#todo - make timeout configurable? #todo - make timeout configurable?
set waitvarname "::punk::console::ansi_response_wait($callid)" set waitvarname "::punk::console::ansi_response_wait($callid)"
#500ms is generally plenty for a terminal to respond.. but not in some cases. e.g event loop busy with stdin keypress?? review #500ms is generally plenty for a terminal to respond.. but not in some cases. e.g event loop busy with stdin keypress?? review
set timeoutid($callid) [after 2000 [list set $waitvarname timedout]] set timeoutid($callid) [after 1000 [list set $waitvarname timedout]]
#JMN #JMN
# - stderr vs stdout # - stderr vs stdout
@ -1040,6 +1047,64 @@ namespace eval punk::console {
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels] set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload return $payload
} }
variable last_da1_result ""
#TODO - 22? 28? 32?
#1 132 columns
#2 Printer port extension
#4 Sixel extension
#6 Selective erase
#7 DRCS
#8 UDK
#9 NRCS
#12 SCS extension
#15 Technical character set
#18 Windowing capability
#21 Horizontal scrolling
#23 Greek extension
#24 Turkish extension
#42 ISO Latin 2 character set
#44 PCTerm
#45 Soft key map
#46 ASCII emulation
#https://vt100.net/docs/vt510-rm/DA1.html
#
proc get_device_attributes {{inoutchannels {stdin stdout}}} {
#DA1
variable last_da1_result
#first element in result is the terminal's architectural class 61,62,63,64.. ?
#for vt100 we get things like: "ESC\[?1;0c"
#for vt102 "ESC\[?6c"
#set capturingregex {(.*)(\x1b\[\?6[0-9][;]*([;0-9]+)c)$} ;#must capture prefix,entire-response,response-payload
set capturingregex {(.*)(\x1b\[\?([0-9]*[;0-9]+)c)$} ;#must capture prefix,entire-response,response-payload
set request "\x1b\[c"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
set last_da1_result $payload
return $payload
}
#https://vt100.net/docs/vt510-rm/DA2.html
proc get_device_attributes_secondary {{inoutchannels {stdin stdout}}} {
#DA2
set capturingregex {(.*)(\x1b\[\>([0-9]*[;][0-9]*[;][0-1]c))$} ;#must capture prefix,entire-response,response-payload
#expect CSI > X;10|20;0|1c - docs suggest X should be something like 61. In practice we see 0 or 1 (windows) REVIEW
set request "\x1b\[>c"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload
}
proc get_device_attributes_tertiary {{inoutchannels {stdin stdout}}} {
#DA3
set capturingregex {(.*)(\x1bP!\|([0-9]{8})\x1b\\)$} ;#must capture prefix,entire-response,response-payload
set request "\x1b\[=c"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload
}
proc get_terminal_id {{inoutchannels {stdin stdout}}} {
#DA3 - alias
get_device_attributes_tertiary $inoutchannels
}
proc get_tabstops {{inoutchannels {stdin stdout}}} { proc get_tabstops {{inoutchannels {stdin stdout}}} {
#DECTABSR \x1b\[2\$w #DECTABSR \x1b\[2\$w
#response example " ^[P2$u9/17/25/33/41/49/57/65/73/81^[\ " (where ^[ is \x1b) #response example " ^[P2$u9/17/25/33/41/49/57/65/73/81^[\ " (where ^[ is \x1b)
@ -1110,6 +1175,55 @@ namespace eval punk::console {
return [split [get_cursor_pos $inoutchannels] ";"] return [split [get_cursor_pos $inoutchannels] ";"]
} }
#todo - work out how to query terminal and set cell size in pixels
#for now use the windows default
variable cell_size
set cell_size ""
set cell_size_fallback 10x20
#todo - change -inoutchannels to -terminalobject with prebuilt default
punk::args::definition {
*id punk::console::cell_size
-inoutchannels -default {stdin stdout} -type list
*values -min 0 -max 1
newsize -default ""
}
proc cell_size {args} {
set argd [punk::args::get_by_id punk::console::cell_size $args]
set inoutchannels [dict get $argd opts -inoutchannels]
set newsize [dict get $argd values newsize]
variable cell_size
if {$newsize eq ""} {
#query existing setting
if {$cell_size eq ""} {
#not set - try to query terminal's overall dimensions
set pixeldict [punk::console::get_xterm_pixels $inoutchannels]
lassign $pixeldict _w sw _h sh
if {[string is integer -strict $sw] && [string is integer -strict $sh]} {
lassign [punk::console::get_size] _cols columns _rows rows
#review - is returned size in pixels always a multiple of rows and cols?
set w [expr {$sw / $columns}]
set h [expr {$sh / $rows}]
set cell_size ${w}x${h}
return $cell_size
} else {
set cell_size $::punk::console::cell_size_fallback
puts stderr "punk::console::cell_size unable to query terminal for pixel data - using default $cell_size"
return $cell_size
}
}
return $cell_size
}
#newsize supplied - try to set
lassign [split [string tolower $newsize] x] w h
if {![string is integer -strict $w] || ![string is integer -strict $h] || $w < 1 || $h < 1} {
error "punk::sixel::cell_size error - expected format WxH where W and H are positive integers - got '$newsize'"
}
set cell_size ${w}x${h}
}
#todo - determine cursor on/off state before the call to restore properly. #todo - determine cursor on/off state before the call to restore properly.
proc get_size {{inoutchannels {stdin stdout}}} { proc get_size {{inoutchannels {stdin stdout}}} {
lassign $inoutchannels in out lassign $inoutchannels in out
@ -1202,13 +1316,19 @@ namespace eval punk::console {
lassign [split $payload {;}] rows cols lassign [split $payload {;}] rows cols
return [list columns $cols rows $rows] return [list columns $cols rows $rows]
} }
proc get_xterm_pixels {{inoutchannels {stdin stdout}}} {
set capturingregex {(.*)(\x1b\[4;([0-9]+;[0-9]+)t)$} ;#must capture prefix,entire-response,response-payload
set request "\x1b\[14t"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
lassign [split $payload {;}] height width
return [list width $width height $height]
}
proc get_mode_line_wrap {{inoutchannels {stdin stdout}}} { proc get_mode_line_wrap {{inoutchannels {stdin stdout}}} {
set capturingregex {(.*)(\x1b\[\?7;([0-9]+)\$y)$} ;#must capture prefix,entire-response,response-payload set capturingregex {(.*)(\x1b\[\?7;([0-9]+)\$y)$} ;#must capture prefix,entire-response,response-payload
set request "\x1b\[?7\$p" set request "\x1b\[?7\$p"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels] set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload return $payload
} }
#Terminals generally default to LNM being reset (off) ie enter key sends a lone <cr> #Terminals generally default to LNM being reset (off) ie enter key sends a lone <cr>
#Terminals tested on windows either don't respond to this query, or respond with 0 (meaning mode not understood) #Terminals tested on windows either don't respond to this query, or respond with 0 (meaning mode not understood)
#I presume from this that almost nobody is using LNM 1 (which sends both <cr> and <lf>) #I presume from this that almost nobody is using LNM 1 (which sends both <cr> and <lf>)
@ -1218,11 +1338,59 @@ namespace eval punk::console {
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels] set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload return $payload
} }
#DECRPM responses e.g:
# \x1b\[?7\;1\$y
# \x1b\[?7\;2\$y
#where 1 = set, 2 = unset. (0 = mode not recognised, 3 = permanently set, 4 = permanently unset)
proc get_mode {num_or_name {inoutchannels {stdin stdout}}} {
if {[string is integer -strict $num_or_name]} {
set m $num_or_name
} else {
upvar ::punk::ansi::decmode_names decmode_names
if {[dict exists $decmode_names $num_or_name]} {
set m [dict get $decmode_names $num_or_name]
} else {
error "punk::console::get_mode unrecognised mode '$num_or_name'. Known mode names: [dict keys $decmode_names]"
}
}
set capturingregex [string map [list %MODE% $m] {(.*)(\x1b\[\?%MODE%;([0-9]+)\$y)$}] ;#must capture prefix,entire-response,response-payload
set request "\x1b\[?$m\$p"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload
}
proc set_mode {num_or_name {inoutchannels {stdin stdout}}} {
if {[string is integer -strict $num_or_name]} {
set m $num_or_name
} else {
upvar ::punk::ansi::decmode_names decmode_names
if {[dict exists $decmode_names $num_or_name]} {
set m [dict get $decmode_names $num_or_name]
} else {
error "punk::console::set_mode unrecognised mode '$num_or_name'. Known mode names: [dict keys $decmode_names]"
}
}
return "\x1b\[?${m}h"
}
proc unset_mode {num_or_name {inoutchannels {stdin stdout}}} {
if {[string is integer -strict $num_or_name]} {
set m $num_or_name
} else {
upvar ::punk::ansi::decmode_names decmode_names
if {[dict exists $decmode_names $num_or_name]} {
set m [dict get $decmode_names $num_or_name]
} else {
error "punk::console::unset_mode unrecognised mode '$num_or_name'. Known mode names: [dict keys $decmode_names]"
}
}
return "\x1b\[?${m}l"
}
#terminals lie. This should be a reasonable (albeit relatively slow) test of actual width - but some terminals seem to miscalculate. #terminals lie. This should be a reasonable (albeit relatively slow) test of actual width - but some terminals seem to miscalculate.
#todo - a visual interactive test/questionnaire to ask user if things are lining up or if the terminal is telling fibs about cursor position. #todo - a visual interactive test/questionnaire to ask user if things are lining up or if the terminal is telling fibs about cursor position.
#todo - determine if these anomalies are independent of font #todo - determine if these anomalies are independent of font
#punk::ansi should be able to glean widths from unicode data files - but this may be incomplete - todo - compare with what terminal actually does. #punk::ansi should be able to glean widths from unicode data files - but this may be incomplete - todo - compare with what terminal actually does.
#review - vertical movements (e.g /n /v will cause emit 0 to be ineffective - todo - disallow?)
proc test_char_width {char_or_string {emit 0}} { proc test_char_width {char_or_string {emit 0}} {
#return 1 #return 1
#JMN #JMN
@ -1266,6 +1434,57 @@ namespace eval punk::console {
return [expr {$col2 - $col1}] return [expr {$col2 - $col1}]
} }
#get reported cursor position after emitting teststring.
#The row is more likely to be a lie than the column
#With wrapping on we should be able to test if the terminal has an inconsistency between reported width and when it actually wraps.
#(but as line wrapping generally occurs based on width - we probably won't see this - just 'apparently' early wrapping due to printing mismatch with width)
#unfortunately if terminal reports something like \u200B as width 1, but doesn't print it - we can't tell. (vs reporting 1 wide and printing replacement char/space)
#When cursor is already at bottom of screen, scrolling will occur so rowoffset will be zero
#we either need to move cursor up before test - or use alt screen ( or scroll_up then scroll_down?)
#for now we will use alt screen to reduce scrolling effects - REVIEW
proc test_string_cursor {teststring {emit 0}} {
variable ansi_available
if {!$ansi_available} {
puts stderr "No ansi - cannot test char_width of '$teststring' returning [string length $test_string]"
return [string length $teststring]
}
punk::console::enable_alt_screen
punk::console::move 0 0
if {!$emit} {
puts -nonewline stdout \033\[2K\033\[1G ;#2K erase line, 1G cursor at col1
}
set response ""
if {[catch {
set response [punk::console::get_cursor_pos]
} errM]} {
puts stderr "Cannot test_string_cursor for '[punk::ansi::ansistring VIEW $teststring]' - may be no console? Error message from get_cursor_pos: $errM"
return
}
lassign [split $response ";"] row1 col1
if {![string length $response] || ![string is integer -strict $col1] || ![string is integer -strict $row1]} {
puts stderr "test_string_cursor Could not interpret response from get_cursor_pos for initial cursor pos. Response: '[punk::ansi::ansistring VIEW $response]'"
flush stderr
return
}
puts -nonewline stdout $teststring
flush stdout
set response [punk::console::get_cursor_pos]
lassign [split $response ";"] row2 col2
if {![string is integer -strict $col2] || ![string is integer -strict $row2]} {
puts stderr "test_string_cursor could not interpret response from get_cursor_pos for post-emit cursor pos. Response:'[punk::ansi::ansistring VIEW $response]'"
flush stderr
return
}
if {!$emit} {
puts -nonewline stdout \033\[2K\033\[1G
}
flush stdout;#if we don't flush - a subsequent stderr write could move the cursor to a newline and interfere with our 2K1G erasure and cursor repositioning.
punk::console::disable_alt_screen
return [list rowoffset [expr {$col2 - $col1}] columnoffset [expr {$row2 - $row1}]]
}
#todo! - improve ideally we want to use VT sequences to determine - and make a separate utility for testing via systemcalls/os api #todo! - improve ideally we want to use VT sequences to determine - and make a separate utility for testing via systemcalls/os api
proc test_can_ansi {} { proc test_can_ansi {} {
#don't set ansi_avaliable here - we want to be able to change things, retest etc. #don't set ansi_avaliable here - we want to be able to change things, retest etc.
@ -1306,8 +1525,59 @@ namespace eval punk::console {
if {!$ansi_available} { if {!$ansi_available} {
return 0 return 0
} }
set ansi_available [test_can_ansi] #ansi_available defaults to -1 (unknown)
return [expr {$ansi_available}] if {$ansi_available == -1} {
set ansi_available [test_can_ansi]
return $ansi_available
}
return 1
}
variable grapheme_cluster_support [dict create] ;#default empty dict for unknown/untested
#todo - flag to retest? (for consoles where grapheme cluster support can be disabled e.g via decmode 2027)
proc grapheme_cluster_support {} {
variable grapheme_cluster_support
if {[dict size $grapheme_cluster_support]} {
return $grapheme_cluster_support
}
if {[info exists ::env(TERM_PROGRAM)]} {
#terminals known to support grapheme clusters, but unable to respond to decmode request 2027
#wezterm (on windows as at 2024-12 decmode 2027 doesn't work)
#REVIEW - what if terminal is remote wezterm? can/will this env variable
# iterm and apple terminal also set TERM_PROGRAM
if {[string tolower $::env(TERM_PROGRAM)] in [list wezterm]} {
set is_available 1
return [dict create available 1 mode set]
}
}
#where 1 = set, 2 = unset. (0 = mode not recognised, 3 = permanently set, 4 = permanently unset)
set state [get_mode grapheme_clusters] ;#decmode 2027 extension
set is_available 0
switch -- $state {
0 {
set m unsupported ;# the dec query is unsupported - but it's possible the terminal still has grapheme support
}
1 {
set m set
set is_available 1
}
2 {
set m unset
}
3 {
set m permanently_set
set is_available 1
}
4 {
set m permanently_unset
}
default {
set m "BAD_RESPONSE"
}
}
return [dict create available $is_available mode $m]
} }
namespace eval ansi { namespace eval ansi {
@ -1432,7 +1702,7 @@ namespace eval punk::console {
puts -nonewline stdout [punk::ansi::move_column $col] puts -nonewline stdout [punk::ansi::move_column $col]
} }
proc move_row {row} { proc move_row {row} {
puts -nonewline stdout [punk::ansi::move_row $col] puts -nonewline stdout [punk::ansi::move_row $row]
} }
proc move_emit {row col data args} { proc move_emit {row col data args} {
puts -nonewline stdout [punk::ansi::move_emit $row $col $data {*}$args] puts -nonewline stdout [punk::ansi::move_emit $row $col $data {*}$args]
@ -1912,8 +2182,52 @@ namespace eval punk::console {
#[list_end] [comment {--- end definitions namespace punk::console ---}] #[list_end] [comment {--- end definitions namespace punk::console ---}]
} }
namespace eval punk::console::check {
variable has_bug_legacysymbolwidth -1 ;#undetermined
proc has_bug_legacysymbolwidth {} {
#some terminals (on windows as at 2024) miscount width of these single-width blocks internally
#resulting in extra spacing that is sometimes well removed from the character itself (e.g at next ansi reset)
#This was fixed in windows-terminal based systems (2021) but persists in others.
#https://github.com/microsoft/terminal/issues/11694
variable has_bug_legacysymbolwidth
if {!$has_bug_legacysymbolwidth} {
return 0
}
if {$has_bug_legacysymbolwidth == -1} {
#run the test using ansi movement
#we only test a specific character from the known problematic set
set w [punk::console::test_char_width \U1fb7d]
if {$w == 1} {
set has_bug_legacysymbolwidth 0
} else {
#can return 2 on legacy window consoles for example
set has_bug_legacysymbolwidth 1
}
return $has_bug_legacysymbolwidth
}
return 1
}
variable has_bug_zwsp -1 ;#undetermined
proc has_bug_zwsp {} {
#Note that some terminals behave differently regarding a leading zwsp vs one that is inline between other chars.
#we are only testing the inline behaviour here.
variable has_bug_zwsp
if {!$has_bug_zwsp} {
return 0
}
if {$has_bug_zwsp == -1} {
set w [punk::console::test_char_width X\u200bY]
}
if {$w == 2} {
return 0
} else {
#may return 3 - but this gives no indication of whether terminal hides it or not.
return 1
}
return 1
}
}

41
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/lib-0.1.1.tm

@ -63,38 +63,6 @@ package require Tcl 8.6-
#*** !doctools #*** !doctools
#[section API] #[section API]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# oo::class namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#tcl::namespace::eval punk::lib::class {
# #*** !doctools
# #[subsection {Namespace punk::lib::class}]
# #[para] class definitions
# if {[info commands [tcl::namespace::current]::interface_sample1] eq ""} {
# #*** !doctools
# #[list_begin enumerated]
#
# # oo::class create interface_sample1 {
# # #*** !doctools
# # #[enum] CLASS [class interface_sample1]
# # #[list_begin definitions]
#
# # method test {arg1} {
# # #*** !doctools
# # #[call class::interface_sample1 [method test] [arg arg1]]
# # #[para] test method
# # puts "test: $arg1"
# # }
#
# # #*** !doctools
# # #[list_end] [comment {-- end definitions interface_sample1}]
# # }
#
# #*** !doctools
# #[list_end] [comment {--- end class enumeration ---}]
# }
#}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::lib::ensemble { tcl::namespace::eval punk::lib::ensemble {
#wiki.tcl-lang.org/page/ensemble+extend #wiki.tcl-lang.org/page/ensemble+extend
@ -172,7 +140,10 @@ tcl::namespace::eval punk::lib::check {
proc has_tclbug_lsearch_strideallinline {} { proc has_tclbug_lsearch_strideallinline {} {
#bug only occurs with single -index value combined with -stride -all -inline -subindices #bug only occurs with single -index value combined with -stride -all -inline -subindices
#https://core.tcl-lang.org/tcl/tktview/5a1aaa201d #https://core.tcl-lang.org/tcl/tktview/5a1aaa201d
set result [lsearch -stride 3 -all -inline -index 1 -subindices {a1 a2 a3} *] if {[catch {[lsearch -stride 3 -all -inline -index 1 -subindices {a1 a2 a3} *]} result]} {
#we aren't looking for an error result - error most likely indicates tcl too old to support -stride
return 0
}
return [expr {$result ne "a2"}] return [expr {$result ne "a2"}]
} }
@ -2575,12 +2546,12 @@ namespace eval punk::lib {
while {$j <= $max} { while {$j <= $max} {
if {$x % $j == 0} { if {$x % $j == 0} {
set other [expr {$x / $j}] set other [expr {$x / $j}]
if {$other % 2 != 0} { if {$other % 2} {
if {$other ni $factors} { if {$other ni $factors} {
lappend factors $other lappend factors $other
} }
} }
if {$j % 2 != 0} { if {$j % 2} {
if {$j ni $factors} { if {$j ni $factors} {
lappend factors $j lappend factors $j
} }

2
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/base-0.1.tm

@ -869,7 +869,7 @@ namespace eval punk::mix::base {
#todo - write tests #todo - write tests
if {([llength $args] % 2) != 0} { if {[llength $args] % 2} {
error "get_relativecksum_from_base error. args supplied must be in the form of key-value pairs. received '$args' " error "get_relativecksum_from_base error. args supplied must be in the form of key-value pairs. received '$args' "
} }
if {[dict exists $args cksum]} { if {[dict exists $args cksum]} {

2
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/util-0.1.0.tm

@ -51,7 +51,7 @@ namespace eval punk::mix::util {
} else { } else {
if {$ival in $knownopts} { if {$ival in $knownopts} {
#puts ">known at $i : [lindex $args $i]" #puts ">known at $i : [lindex $args $i]"
if {($i % 2) != 0} { if {$i % 2} {
error "unexpected option at index $i. known options: $knownopts must come in -opt val pairs." error "unexpected option at index $i. known options: $knownopts must come in -opt val pairs."
} }
incr i incr i

6
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/nav/fs-0.1.0.tm

@ -1,6 +1,6 @@
# -*- tcl -*- # -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'pmix make' or bin/punkmake to update from <pkg>-buildversion.txt # Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm # module template: punkshell/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm
# #
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. # Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. # Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
@ -18,7 +18,7 @@
# doctools header # doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools #*** !doctools
#[manpage_begin shellspy_module_punk::nav::fs 0 0.1.0] #[manpage_begin punkshell_module_punk::nav::fs 0 0.1.0]
#[copyright "2024"] #[copyright "2024"]
#[titledesc {punk::nav::fs console filesystem navigation}] [comment {-- Name section and table of contents description --}] #[titledesc {punk::nav::fs console filesystem navigation}] [comment {-- Name section and table of contents description --}]
#[moddesc {fs nav}] [comment {-- Description at end of page heading --}] #[moddesc {fs nav}] [comment {-- Description at end of page heading --}]

2
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/packagepreference-0.1.0.tm

@ -1,6 +1,6 @@
# -*- tcl -*- # -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt # Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm # module template: punkshell/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm
# #
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. # Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. # Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.

9
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repl/codethread-0.1.0.tm

@ -141,8 +141,10 @@ tcl::namespace::eval punk::repl::codethread {
#puts stderr "->runscript" #puts stderr "->runscript"
variable replthread_cond variable replthread_cond
variable output_stdout "" #variable output_stdout
variable output_stderr "" #set output_stdout ""
#variable output_stderr
#set output_stderr ""
#expecting to be called from a thread::send in parent repl - ie in the toplevel interp so that the sub-interp "code" is available #expecting to be called from a thread::send in parent repl - ie in the toplevel interp so that the sub-interp "code" is available
#if a thread::send is done from the commandline in a codethread - Tcl will #if a thread::send is done from the commandline in a codethread - Tcl will
if {"code" ni [interp children] || ![info exists replthread_cond]} { if {"code" ni [interp children] || ![info exists replthread_cond]} {
@ -154,6 +156,9 @@ tcl::namespace::eval punk::repl::codethread {
puts stderr "runscript is meant to be called from the parent repl thread via a thread::send to the codethread" puts stderr "runscript is meant to be called from the parent repl thread via a thread::send to the codethread"
return return
} }
interp eval code [list set ::punk::repl::codethread::output_stdout ""]
interp eval code [list set ::punk::repl::codethread::output_stderr ""]
set outstack [list] set outstack [list]
set errstack [list] set errstack [list]
upvar ::punk::config::running running_config upvar ::punk::config::running running_config

287
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repl/codethread-0.1.1.tm

@ -0,0 +1,287 @@
# -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: punkshell/src/decktemplates/vendor/punk/modules/template_module-0.0.2.tm
#
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# (C) 2024
#
# @@ Meta Begin
# Application punk::repl::codethread 0.1.1
# Meta platform tcl
# Meta license <unspecified>
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[manpage_begin punkshell_module_punk::repl::codethread 0 0.1.1]
#[copyright "2024"]
#[titledesc {Module repl codethread}] [comment {-- Name section and table of contents description --}]
#[moddesc {codethread for repl - root interpreter}] [comment {-- Description at end of page heading --}]
#[require punk::repl::codethread]
#[keywords module repl]
#[description]
#[para] This is part of the infrastructure required for the punk::repl to operate
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Overview]
#[para] overview of punk::repl::codethread
#[subsection Concepts]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection dependencies]
#[para] packages used by punk::repl::codethread
#[list_begin itemized]
package require Tcl 8.6-
package require punk::config
#*** !doctools
#[item] [package {Tcl 8.6}]
# #package require frobz
# #*** !doctools
# #[item] [package {frobz}]
#*** !doctools
#[list_end]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section API]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# oo::class namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#tcl::namespace::eval punk::repl::codethread::class {
#*** !doctools
#[subsection {Namespace punk::repl::codethread::class}]
#[para] class definitions
#if {[info commands [tcl::namespace::current]::interface_sample1] eq ""} {
#*** !doctools
#[list_begin enumerated]
# oo::class create interface_sample1 {
# #*** !doctools
# #[enum] CLASS [class interface_sample1]
# #[list_begin definitions]
# method test {arg1} {
# #*** !doctools
# #[call class::interface_sample1 [method test] [arg arg1]]
# #[para] test method
# puts "test: $arg1"
# }
# #*** !doctools
# #[list_end] [comment {-- end definitions interface_sample1}]
# }
#*** !doctools
#[list_end] [comment {--- end class enumeration ---}]
#}
#}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Base namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::repl::codethread {
tcl::namespace::export *
variable replthread
variable replthread_cond
variable running 0
variable output_stdout ""
variable output_stderr ""
#variable xyz
#*** !doctools
#[subsection {Namespace punk::repl::codethread}]
#[para] Core API functions for punk::repl::codethread
#[list_begin definitions]
#proc sample1 {p1 n args} {
# #*** !doctools
# #[call [fun sample1] [arg p1] [arg n] [opt {option value...}]]
# #[para]Description of sample1
# #[para] Arguments:
# # [list_begin arguments]
# # [arg_def tring p1] A description of string argument p1.
# # [arg_def integer n] A description of integer argument n.
# # [list_end]
# return "ok"
#}
variable run_command_cache
proc is_running {} {
variable running
return $running
}
proc runscript {script} {
#puts stderr "->runscript"
variable replthread_cond
#variable output_stdout ""
#variable output_stderr ""
#expecting to be called from a thread::send in parent repl - ie in the toplevel interp so that the sub-interp "code" is available
#if a thread::send is done from the commandline in a codethread - Tcl will
if {"code" ni [interp children] || ![info exists replthread_cond]} {
#in case someone tries calling from codethread directly - don't do anything or change any state
#(direct caller could create an interp named code at the level "" -> "code" -"code" and add a replthread_cond value to avoid this check - but it probably won't do anything useful)
#if called directly - the context will be within the first 'code' interp.
#inappropriate caller could add superfluous entries to shellfilter stack if function errors out
#inappropriate caller could affect tsv vars (if their interp allows that anyway)
puts stderr "runscript is meant to be called from the parent repl thread via a thread::send to the codethread"
return
}
interp eval code [list set ::punk::repl::codethread::output_stdout ""]
interp eval code [list set ::punk::repl::codethread::output_stderr ""]
set outstack [list]
set errstack [list]
upvar ::punk::config::running running_config
if {[string length [dict get $running_config color_stdout_repl]] && [interp eval code punk::console::colour]} {
lappend outstack [interp eval code [list shellfilter::stack::add stdout ansiwrap -settings [list -colour [dict get $running_config color_stdout_repl]]]]
}
lappend outstack [interp eval code [list shellfilter::stack::add stdout tee_to_var -settings {-varname ::punk::repl::codethread::output_stdout}]]
if {[string length [dict get $running_config color_stderr_repl]] && [interp eval code punk::console::colour]} {
lappend errstack [interp eval code [list shellfilter::stack::add stderr ansiwrap -settings [list -colour [dict get $running_config color_stderr_repl]]]]
# #lappend errstack [shellfilter::stack::add stderr ansiwrap -settings [list -colour cyan]]
}
lappend errstack [interp eval code [list shellfilter::stack::add stderr tee_to_var -settings {-varname ::punk::repl::codethread::output_stderr}]]
#an experiment
#set errhandle [shellfilter::stack::item_tophandle stderr]
#interp transfer "" $errhandle code
set status [catch {
#shennanigans to keep compiled script around after call.
#otherwise when $script goes out of scope - internal rep of vars set in script changes.
#The shimmering may be no big deal(?) - but debug/analysis using tcl::unsupported::representation becomes impossible.
interp eval code [list ::punk::lib::set_clone ::codeinterp::clonescript $script] ;#like objclone
interp eval code {
lappend ::codeinterp::run_command_cache $::codeinterp::clonescript
if {[llength $::codeinterp::run_command_cache] > 2000} {
set ::codeinterp::run_command_cache [lrange $::codeinterp::run_command_cache 1750 end][unset ::codeinterp::run_command_cache]
}
tcl::namespace::inscope $::punk::ns::ns_current $::codeinterp::clonescript
}
} result]
flush stdout
flush stderr
#interp transfer code $errhandle ""
#flush $errhandle
#set lastoutchar [string index [punk::ansi::ansistrip [interp eval code set ::punk::repl::codethread::output_stdout]] end]
#set lastoutchar [string index [punk::ansi::ansistrip [interp eval code [list set ::punk::repl::codethread::output_stdout]]] end]
set lastoutpart [interp eval code {string range $::punk::repl::codethread::output_stdout end-100 end}]
#note we could be in a *large* ansi segment such as sixel data
#review - why do we need to ansistrip?
set lastoutchar [string index [punk::ansi::ansistrip $lastoutpart] end]
#set lasterrchar [string index [punk::ansi::ansistrip [interp eval code set ::punk::repl::codethread::output_stderr]] end]
set lasterrpart [interp eval code {string range $::punk::repl::codethread::output_stderr end-100 end}]
set lasterrchar [string index [punk::ansi::ansistrip $lasterrpart] end]
#puts stderr "-->[ansistring VIEW -lf 1 $lastoutchar$lasterrchar]"
set tid [thread::id]
tsv::set codethread_$tid info [list lastoutchar $lastoutchar lasterrchar $lasterrchar]
tsv::set codethread_$tid status $status
tsv::set codethread_$tid result $result
tsv::set codethread_$tid errorcode $::errorCode
#only remove from shellfilter::stack the items we added to stack in this function
foreach s [lreverse $outstack] {
interp eval code [list shellfilter::stack::remove stdout $s]
}
foreach s [lreverse $errstack] {
interp eval code [list shellfilter::stack::remove stderr $s]
}
thread::cond notify $replthread_cond
}
#*** !doctools
#[list_end] [comment {--- end definitions namespace punk::repl::codethread ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Secondary API namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::repl::codethread::lib {
tcl::namespace::export *
tcl::namespace::path [tcl::namespace::parent]
#*** !doctools
#[subsection {Namespace punk::repl::codethread::lib}]
#[para] Secondary functions that are part of the API
#[list_begin definitions]
#proc utility1 {p1 args} {
# #*** !doctools
# #[call lib::[fun utility1] [arg p1] [opt {?option value...?}]]
# #[para]Description of utility1
# return 1
#}
#*** !doctools
#[list_end] [comment {--- end definitions namespace punk::repl::codethread::lib ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Internal]
tcl::namespace::eval punk::repl::codethread::system {
#*** !doctools
#[subsection {Namespace punk::repl::codethread::system}]
#[para] Internal functions that are not part of the API
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide punk::repl::codethread [tcl::namespace::eval punk::repl::codethread {
variable pkg punk::repl::codethread
variable version
set version 0.1.1
}]
return
#*** !doctools
#[manpage_end]

2
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repo-0.1.1.tm

@ -370,7 +370,7 @@ namespace eval punk::repo {
set opt_repotypes [dict get $opts -repotypes] set opt_repotypes [dict get $opts -repotypes]
set opt_repopaths [dict get $opts -repopaths] set opt_repopaths [dict get $opts -repopaths]
if {"$opt_repopaths" ne ""} { if {"$opt_repopaths" ne ""} {
if {([llength $opt_repopaths] % 2 != 0) || ![dict exists $opt_repopaths closest]} { if {([llength $opt_repopaths] % 2) || ![dict exists $opt_repopaths closest]} {
error "workingdir_state error: -repopaths argument invalid. Expected a dict as retrieved using punk::repo::find_repos" error "workingdir_state error: -repopaths argument invalid. Expected a dict as retrieved using punk::repo::find_repos"
} }
set repopaths $opt_repopaths set repopaths $opt_repopaths

11
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/tdl-0.1.0.tm

@ -31,15 +31,19 @@ namespace eval punk::tdl {
server -name trillion -os windows server -name trillion -os windows
server -name vmhost1 -os FreeBSD { server -name vmhost1 -os FreeBSD {
guest -name bsd1 -vmmanager iocage guest -name bsd1 -vmmanager bastille
guest -name p1 -vmmanager bhyve guest -name p1 -vmmanager bhyve
} }
} }
proc prettyparse {script} { proc prettyparse {script {safe 1}} {
set i [interp create -safe] if {$safe} {
set i [interp create -safe]
} else {
set i [interp create]
}
try { try {
# $i eval {unset {*}[info vars]} # $i eval {unset {*}[info vars]}
# foreach command [$i eval {info commands}] {$i hide $command} # foreach command [$i eval {info commands}] {$i hide $command}
@ -65,6 +69,7 @@ namespace eval punk::tdl {
interp delete $i interp delete $i
} }
} }
proc prettyprint {data {level 0}} { proc prettyprint {data {level 0}} {
set ind [string repeat " " $level] set ind [string repeat " " $level]
incr level incr level

4
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punkcheck-0.1.0.tm

@ -359,7 +359,7 @@ namespace eval punkcheck {
-note \uFFFF\ -note \uFFFF\
] ]
set known_opts [dict keys $defaults] set known_opts [dict keys $defaults]
if {[llength $args] % 2 != 0} { if {[llength $args] % 2} {
error "targetset_end arguments after status must be in the form of -flag value pairs. known flags: $known_opts" error "targetset_end arguments after status must be in the form of -flag value pairs. known flags: $known_opts"
} }
set opts [dict merge $defaults $args] set opts [dict merge $defaults $args]
@ -914,7 +914,7 @@ namespace eval punkcheck {
set changed 0 set changed 0
} }
set installing_record_sources [dict_getwithdefault $installing_record body [list]] set installing_record_sources [dict_getwithdefault $installing_record body [list]]
set ts_now [clock microseconds] ;#gathering metadata - especially checsums on folder can take some time - calc and store elapsed us for time taken to gather metadata set ts_now [clock microseconds] ;#gathering metadata - especially checksums on folder can take some time - calc and store elapsed us for time taken to gather metadata
set metadata_us [expr {$ts_now - $ts_start}] set metadata_us [expr {$ts_now - $ts_start}]
set this_source_record [dict create tag SOURCE -type $ftype -size $fsize -path $source_relpath -cksum $cksum -cksum_all_opts $cksum_all_opts -changed $changed -metadata_us $metadata_us] set this_source_record [dict create tag SOURCE -type $ftype -size $fsize -path $source_relpath -cksum $cksum -cksum_all_opts $cksum_all_opts -changed $changed -metadata_us $metadata_us]
lappend installing_record_sources $this_source_record lappend installing_record_sources $this_source_record

9
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/shellfilter-0.1.9.tm

@ -613,6 +613,10 @@ namespace eval shellfilter::chan {
#It can be useful for test/debugging #It can be useful for test/debugging
#Due to chunking at random breaks - we have to check if an ansi code in the underlying stream has been split - otherwise our wrapping will break the existing ansi #Due to chunking at random breaks - we have to check if an ansi code in the underlying stream has been split - otherwise our wrapping will break the existing ansi
# #
set sixelstart_re {\x1bP([;0-9]*)q} ;#7-bit - todo 8bit
#todo kitty graphics \x1b_G...
#todo iterm graphics
oo::class create ansiwrap { oo::class create ansiwrap {
variable o_trecord variable o_trecord
variable o_enc variable o_enc
@ -646,6 +650,9 @@ namespace eval shellfilter::chan {
set o_is_junction 0 set o_is_junction 0
} }
} }
#todo - track when in sixel,iterm,kitty graphics data - can be very large
method Trackcodes {chunk} { method Trackcodes {chunk} {
#puts stdout "===[ansistring VIEW -lf 1 $o_buffered]" #puts stdout "===[ansistring VIEW -lf 1 $o_buffered]"
set buf $o_buffered$chunk set buf $o_buffered$chunk
@ -2334,7 +2341,7 @@ namespace eval shellfilter {
#set tid [::shellfilter::log::open $runtag [list -syslog "127.0.0.1:514"]] #set tid [::shellfilter::log::open $runtag [list -syslog "127.0.0.1:514"]]
set tid [::shellfilter::log::open $runtag [list -syslog ""]] set tid [::shellfilter::log::open $runtag [list -syslog ""]]
if {([llength $args] % 2) != 0} { if {[llength $args] % 2} {
error "Trailing arguments after any positional arguments must be in pairs of the form -argname argvalue. Valid flags are:'$valid_flags'" error "Trailing arguments after any positional arguments must be in pairs of the form -argname argvalue. Valid flags are:'$valid_flags'"
} }
set invalid_flags [list] set invalid_flags [list]

120
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/textblock-0.1.2.tm

@ -82,11 +82,17 @@ tcl::namespace::eval textblock {
#review - what about ansi off in punk::console? #review - what about ansi off in punk::console?
tcl::namespace::import ::punk::ansi::a ::punk::ansi::a+ tcl::namespace::import ::punk::ansi::a ::punk::ansi::a+
tcl::namespace::export block frame frame_cache framedef frametypes gcross height width widthtopline join join_basic list_as_table pad testblock tcl::namespace::export block frame frame_cache framedef frametypes gcross height width widthtopline join join_basic list_as_table pad testblock
#NOTE sha1, although computationally more intensive, tends to be faster than md5 on modern cpus
#(more likely to be optimised for modern cpu features?)
variable use_md5 ;#framecache variable use_md5 ;#framecache
set use_md5 1 set use_md5 1
if {[catch {package require md5}]} { if {[catch {package require md5}]} {
set use_md5 0 set use_md5 0
} }
#todo - change use_md5 to more generic use_checksum_algorithm function.
# e.g allow md5, sha1, none, etc.
# - perhaps autodetect 32bit vs 64bit (or processortype?) to select a default (also depending on packages available and accelerator presence)
proc use_md5 {{yes_no ""}} { proc use_md5 {{yes_no ""}} {
variable use_md5 variable use_md5
if {$yes_no eq ""} { if {$yes_no eq ""} {
@ -4170,7 +4176,7 @@ tcl::namespace::eval textblock {
} }
set FRAMETYPES [textblock::frametypes] set FRAMETYPES [textblock::frametypes]
punk::args::Get_argspecs [punk::lib::tstr -return string { punk::args::definition [punk::lib::tstr -return string {
*id textblock::list_as_table *id textblock::list_as_table
-return -default table -choices {table tableobject} -return -default table -choices {table tableobject}
@ -4208,7 +4214,7 @@ tcl::namespace::eval textblock {
proc list_as_table {args} { proc list_as_table {args} {
set FRAMETYPES [textblock::frametypes] set FRAMETYPES [textblock::frametypes]
set argd [punk::args::get_dict_by_id textblock::list_as_table $args] set argd [punk::args::get_by_id textblock::list_as_table $args]
set opts [dict get $argd opts] set opts [dict get $argd opts]
set datalist [dict get $argd values datalist] set datalist [dict get $argd values datalist]
@ -5699,7 +5705,7 @@ tcl::namespace::eval textblock {
#custom dict may leave out keys - but cannot have unknown keys #custom dict may leave out keys - but cannot have unknown keys
foreach {k v} $f { foreach {k v} $f {
switch -- $k { switch -- $k {
hl - hlt - hlb - vl - vll - vlr - tlc - trc - blc - brc {} all - hl - hlt - hlb - vl - vll - vlr - tlc - trc - blc - brc {}
hltj - hlbj - vllj - vlrj { hltj - hlbj - vllj - vlrj {
#also allow extra join arguments #also allow extra join arguments
} }
@ -5714,11 +5720,15 @@ tcl::namespace::eval textblock {
set is_custom_dict_ok 0 set is_custom_dict_ok 0
} }
if {!$is_custom_dict_ok} { if {!$is_custom_dict_ok} {
error "frame option -type must be one of known types: $::textblock::frametypes or a dictionary with any of keys hl,hlt,hlb,vl,vll,vlr,tlc,trc,blc,brc" error "frame option -type must be one of known types: $::textblock::frametypes or a dictionary with any of keys all,hl,hlt,hlb,vl,vll,vlr,tlc,trc,blc,brc"
}
if {[dict exists $f all]} {
return [tcl::dict::create category custom type $f]
} else {
set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "]
set custom_frame [tcl::dict::merge $default_custom $f]
return [tcl::dict::create category custom type $custom_frame]
} }
set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "]
set custom_frame [tcl::dict::merge $default_custom $f]
return [tcl::dict::create category custom type $custom_frame]
} }
} }
} }
@ -5769,7 +5779,7 @@ tcl::namespace::eval textblock {
} }
set f [lindex $values 0] set f [lindex $values 0]
set rawglobs [lrange $values 1 end] set rawglobs [lrange $values 1 end]
if {![llength $rawglobs]} { if {![llength $rawglobs] || "all" in $rawglobs || "*" in $rawglobs} {
set globs * set globs *
} else { } else {
set globs [list] set globs [list]
@ -6236,6 +6246,46 @@ tcl::namespace::eval textblock {
#from3 #from3
set trc [punk::char::charshort boxd_ldhz] ;#T shape (ttj) set trc [punk::char::charshort boxd_ldhz] ;#T shape (ttj)
set brc [punk::char::charshort boxd_luhz] ;#upside down T shape (btj) set brc [punk::char::charshort boxd_luhz] ;#upside down T shape (btj)
switch -- $targetleft-$targetright {
heavy-light {
set tlc \u252d ;# Left Heavy and Right Down Light (ttj)
set blc \u2535 ;# Left Heavy and Right Up Light (btj)
set vllj \u2525 ;# left heavy (rtj)
set vlrj \u251c;#right light (ltj)
}
heavy-other {
set tlc \u252d ;# Left Heavy and Right Down Light (ttj)
set blc \u2535 ;# Left Heavy and Right Up Light (btj)
set vllj \u2525 ;# left heavy (rtj)
}
heavy-heavy {
set vllj \u2525 ;# left heavy (rtj)
set vlrj \u251d;#right heavy (ltj)
set tlc \u252d ;# Left Heavy and Right Down Light (ttj)
set blc \u2535 ;# Left Heavy and Right Up Light (btj)
set trc \u252e ;#Right Heavy and Left Down Light (ttj)
set brc \u2536 ;#Right Heavy and Left up Light (btj)
}
light-heavy {
set trc \u252e ;#Right Heavy and Left Down Light (ttj)
set brc \u2536 ;#Right Heavy and Left up Light (btj)
set vlrj \u251d;#right heavy (ltj)
set vllj \u2524 ;# left light (rtj)
}
light-other {
set vllj \u2524 ;# left light (rtj)
}
light-light {
set vllj \u2524 ;# left light (rtj)
set vlrj \u251c;#right light (ltj)
}
}
#set vllj \u2525 ;# left heavy (rtj)
#set vllj \u2524 ;# left light (rtj)
#set vlrj \u251d;#right heavy (ltj)
#set vlrj \u251c;#right light (ltj)
} }
left_up { left_up {
#9 #9
@ -6935,6 +6985,7 @@ tcl::namespace::eval textblock {
self-self { self-self {
#set blc \u27e1 ;# white concave-sided diamond - positioned too far right #set blc \u27e1 ;# white concave-sided diamond - positioned too far right
#set blc \u27e3 ;# concave sided diamond with rightwards tick - positioned too low, too right - big gaps #set blc \u27e3 ;# concave sided diamond with rightwards tick - positioned too low, too right - big gaps
#set blc \u2bce ;# from "Miscellaneous Symbols and Arrows" - positioned too far right
set brc \u2524 ;# *light (rtj) set brc \u2524 ;# *light (rtj)
set tlc \u252c ;# *light (ttj) set tlc \u252c ;# *light (ttj)
} }
@ -6950,6 +7001,15 @@ tcl::namespace::eval textblock {
} }
} }
} }
down_right {
switch -- $targetdown-$targetright {
self-self {
#set brc \u2bce ;# from "Miscellaneous Symbols and Arrows" - positioned too far right
set trc \u252c ;# (ttj)
set blc \u2524 ;# (rtj)
}
}
}
} }
} }
arc_b { arc_b {
@ -7027,6 +7087,15 @@ tcl::namespace::eval textblock {
set blc \U1fb7c ;#legacy block set blc \U1fb7c ;#legacy block
set brc \U1fb7f ;#legacy block set brc \U1fb7f ;#legacy block
if {[punk::console::check::has_bug_legacysymbolwidth]} {
#rather than totally fail on some mixed layout that happens to use block2 - just degrade it - but prevent alignment problems
set sp \u00a0 ;#non breaking space (plain space may act transparent in some use cases)
set tlc $sp
set trc $sp
set blc $sp
set brc $sp
}
#horizontal and vertical bar joins #horizontal and vertical bar joins
set hltj $hlt set hltj $hlt
set hlbj $hlb set hlbj $hlb
@ -7088,15 +7157,20 @@ tcl::namespace::eval textblock {
set vlrj $vlr set vlrj $vlr
} }
default { default {
set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "] ;#only default the general types - these form defaults for more specific types if they're missing #set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "] ;#only default the general types - these form defaults for more specific types if they're missing
if {[llength $f] % 2 != 0} { set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "]
if {"all" in [dict keys $f]} {
set A [dict get $f all]
set default_custom [tcl::dict::create hl $A vl $A tlc $A trc $A blc $A brc $A]
}
if {[llength $f] % 2} {
#todo - retrieve usage from punk::args #todo - retrieve usage from punk::args
error "textblock::frametype frametype '$f' is not one of the predefined frametypes: $::textblock::frametypes and does not appear to be a dictionary for a custom frametype" error "textblock::frametype frametype '$f' is not one of the predefined frametypes: $::textblock::frametypes and does not appear to be a dictionary for a custom frametype"
} }
#unknown order of keys specified by user - validate before creating vars as we need more general elements to be available as defaults #unknown order of keys specified by user - validate before creating vars as we need more general elements to be available as defaults
dict for {k v} $f { dict for {k v} $f {
switch -- $k { switch -- $k {
hl - vl - tlc - trc - blc - brc - hlt - hlb - vll - vlr - hltj - hlbj - vllj - vlrj {} all - hl - vl - tlc - trc - blc - brc - hlt - hlb - vll - vlr - hltj - hlbj - vllj - vlrj {}
default { default {
error "textblock::frametype '$f' has unknown element '$k'" error "textblock::frametype '$f' has unknown element '$k'"
} }
@ -8028,17 +8102,19 @@ tcl::namespace::eval textblock {
return $fs return $fs
} }
} }
punk::args::definition {
*id textblock::gcross
-max_cross_size -default 0 -type integer -help "Largest size cross to use to make up the block
Only cross sizes that divide the size of the overall block will be used.
e.g if the 'size' chosen is 19 (a prime number) - only 1 or the full size of 19 can be used as the crosses to make up the block.
Whereas for a block size of 24, -max_cross_size of 1,2,3,4,6,8,12 or 24 will work. (all divisors)
If a number chosen for -max_cross_size isn't a divisor, the largest divisor below the chosen value will be used.
"
*values -min 1 -max 1
size -default 1 -type integer
}
proc gcross {args} { proc gcross {args} {
set argd [punk::args::get_dict { set argd [punk::args::get_by_id textblock::gcross $args]
-max_cross_size -default 0 -type integer -help "Largest size cross to use to make up the block
Only cross sizes that divide the size of the overall block will be used.
e.g if the 'size' chosen is 19 (a prime number) - only 1 or the full size of 19 can be used as the crosses to make up the block.
Whereas for a block size of 24, -max_cross_size of 1,2,3,4,6,8,12 or 24 will work. (all divisors)
If a number chosen for -max_cross_size isn't a divisor, the largest divisor below the chosen value will be used.
"
*values -min 1
size -default 1 -type integer
} $args]
set size [dict get $argd values size] set size [dict get $argd values size]
set opts [dict get $argd opts] set opts [dict get $argd opts]
@ -8089,7 +8165,7 @@ tcl::namespace::eval textblock {
lappend crossrows [::join $r ""] lappend crossrows [::join $r ""]
} }
if {$max_cross_size % 2 != 0} { if {$max_cross_size % 2} {
#only put centre cross in for odd sized crosses #only put centre cross in for odd sized crosses
set r $row set r $row
lset r $armsize $x lset r $armsize $x

41
src/runtime/mapvfs.config

@ -12,48 +12,59 @@
#e.g #e.g
#- myproject.vfs #- myproject.vfs
#- punk86.vfs #- punk86.vfs
##tclkit86bi.exe {punk8win.vfs punkbi kit} #tclkit86bi.exe {punk8win.vfs punkbi kit}
#c:\tcl.bawt tcl 8.6.13 bawt #c:\tcl.bawt tcl 8.6.13 bawt
##tclkit-win64-dyn.exe {punk86bawt.vfs punkbawt kit} ##tclkit-win64-dyn.exe {punk86bawt.vfs punkbawt kit}
tclkit-win64-dyn.exe {punk86bawt.vfs punksys kit}
#magicsplat tclkit - no Tk #magicsplat tclkit - no Tk
##tclkit8613.exe punk86.vfs #tclkit8613.exe punk86.vfs
#magicsplat modified tclkit - added tk, changed icon #magicsplat modified tclkit - added tk, changed icon
##tclkit8613punk.exe punk86.vfs {punk8win.vfs punk86} ##tclkit8613punk.exe punk86.vfs {punk8win.vfs punk86}
#tclkit8613punk.head.exe {punk8_statictwapi.vfs punk86head}
#tclkit87a5.exe punk86.vfs punk87} {punk.vfs punkmain} #tclkit87a5.exe {punk86.vfs punk87} {punk.vfs punkmain}
##tclkit87a5.exe {punk8win.vfs punk87} #tclkit87a5.exe {punk8win.vfs punk87}
################################## ##################################
#TCL9 #TCL9
tclsh90b2 {punk9win.vfs punk90b2 zip} #tclsh90b2 {punk9win.vfs punk90b2 zip}
#tclsh90b4_piperepl.exe {punk9win.vfs punk90b4 zip} {critcl.vfs critcl9 zip} #tclsh90b4_piperepl.exe {punk9win.vfs punk90b4 zip}
#tclsh901.exe {punk9win.vfs punk901 zip}
tclsh901t.exe {punk9win.vfs punk901t zipcat}
#critcl doesn't seem? to work as a zip
#tclsh90b4_piperepl.exe {critcl.vfs critcl9 zip}
#tclsh90b4_piperepl.exe {critcl-3.3.1.vfs critcl kit} #tclsh901k.exe {mkzipfix.vfs punktest zip}
tclkit86bi.exe {critcl-3.3.1.vfs critcl kit}
#kit won't work with TCL9 til we get something like vfs::mkcl running - which in turn requires vlerq - (single c file to compile)
#Mk4tcl? c++ based - not as attractive.
##tclsh90b4_piperepl.exe {punk9win.vfs punk90b4kit kit}
#tclsh90b4_piperepl.exe {punk9win.vfs punk90b4 zip}
#we would require compiled cookfs extension to extract existing vfs from a cookit, or if we wanted to re-write as cookfs #we would require compiled cookfs extension to extract existing vfs from a cookit, or if we wanted to re-write as cookfs
#(possibly upx binary too if compressed - upx is easily attainable on most platforms) #(possibly upx binary too if compressed - upx is easily attainable on most platforms)
cookitU.exe {punk9cook.vfs punk9cook cookfs} #cookitU.exe {punk9cook.vfs punk9cook cookfs}
#cookitU.exe {punk9cook.vfs punk9cz zip}
################################## ##################################
#critcl doesn't seem to work as a zip or metakit in 2024 - gcc paths point to vfs
#critcl needs updating to copy files out to real fs for critcl to work
#Use unwrapped critcl for now
#tclsh90b4_piperepl.exe {critcl-3.3.1.vfs critcl kit}
#tclkit86bi.exe {critcl-3.3.1.vfs critcl kit}
##tclkit87a5bawt.exe punk86.vfs
##tclkit86bi.exe vfs_windows/punk86win.vfs
#temp hack - todo fix .exe for x-platform #temp hack - todo fix .exe for x-platform

88
src/scriptapps/punk.tcl

@ -2,12 +2,96 @@
set dirname [file dirname [file normalize [info script]]] set dirname [file dirname [file normalize [info script]]]
if {[file tail $dirname] eq "bin"} { if {[file tail $dirname] eq "bin"} {
lassign [split [info tclversion] .] tclmajorv tclminorv lassign [split [info tclversion] .] tclmajorv tclminorv
set launchpath "../src/vfs/punk${tclmajorv}win.vfs/main.tcl" #we shouldn't try to call into the ../src/vfs/* folders - as they are 'partials'
#instead we call into one of the ../src/_build/exename.vfs folders
if {"windows" eq $::tcl_platform(platform)} {
set ext ".exe"
} else {
set ext ""
}
switch -- $tclmajorv {
8 {
set vfsfolder [file normalize [file join $dirname "../src/_build/punk86${ext}.vfs"]]
}
9 {
set vfsfolder [file normalize [file join $dirname "../src/_build/punk9cook${ext}.vfs"]]
}
default {
error "no vfs available for tcl major version $tclmajorv"
}
}
set launchpath [file join $vfsfolder main.tcl]
if {[file exists [file join $dirname $launchpath]]} { if {[file exists [file join $dirname $launchpath]]} {
#tclsh [file join $dirname $launchpath] {*}$::argv #tclsh [file join $dirname $launchpath] {*}$::argv
namespace eval ::punkboot {
#this drop-box style interaction is not pretty..
#variable vfs_entry_point
#set vfs_entry_point $launchpath ;#tell a cooperating main.tcl what's going on
variable internal_paths
#set internal_paths [list [info library] $vfscommonfolder $vfsfolder]
set internal_paths [list [file dirname [file dirname [info nameofexecutable]]]]
}
source [file join $dirname $launchpath] source [file join $dirname $launchpath]
} else { } else {
puts stderr "Unable to locate punk${tclmajorv} entry-point main.tcl tried:$launchpath" #todo - tidy up - we don't use fauxlinks in src/vfs/xxx.vfs anymore
# - but we may want to fauxlink the active exe vfs above? REVIEW
set failed 1
set tried_list [list $launchpath]
set fauxlinks [glob -nocomplain -dir $vfsfolder -type f main.tcl#*.fxlnk main.tcl#*.fauxlink]
#puts stderr "globbed in $vfsfolder result: $fauxlinks"
if {[llength $fauxlinks] >= 1} {
set vfscommonfolder [file join [file dirname $vfsfolder] _vfscommon.vfs]
#review - we don't know what other vfs folder might make up the unbuild vfs
#_vfscommon.vfs is standard - REVIEW
foreach vfs [list $vfscommonfolder $vfsfolder] {
if {[file exists $vfs/modules]} {
tcl::tm::add $vfs/modules
}
if {[file exists $vfs/modules_tcl${tclmajorv}]} {
tcl::tm::add $vfs/modules_tcl${tclmajorv}
}
if {[file exists $vfs/lib]} {
lappend ::auto_path $vfs/lib
}
}
if {![catch {package require fauxlink} errM]} {
set maintcl_fauxlink [lindex $fauxlinks 0]
set fdict [fauxlink::resolve $maintcl_fauxlink]
set tags [dict get $fdict tags]
#puts stdout "trying fauxlink: $fdict"
if {[dict get $fdict name] eq "main.tcl" && "punk::boot,merge_over" in "$tags"} {
set target [dict get $fdict targetpath]
if {[file pathtype $target] eq "relative"} {
set targetfile [file join $vfsfolder $target]
} else {
set targetfile $target
}
set targetfile [file normalize $targetfile]
puts stdout "trying fauxlinked file: $targetfile"
if {[file exists $targetfile]} {
namespace eval ::punkboot {
#this drop-box style interaction is not pretty..
variable vfs_entry_point
set vfs_entry_point $launchpath ;#tell a cooperating main.tcl what's going on
variable internal_paths
set internal_paths [list $vfscommonfolder $vfsfolder]
}
set failed 0
source $targetfile
} else {
puts "fauxlink target: $targetfile doesn't seem to exist"
}
}
} else {
puts stderr "Couldn't load fauxlink package. Tried tm paths [list $vfscommonfolder/modules $vfsfolder/modules]"
}
} else {
puts stderr "Couldn't find a fauxlink file as a fallback"
}
if {$failed} {
puts stderr "Unable to locate punk${tclmajorv} entry-point main.tcl tried:[join $tried_list \n]"
}
} }
} else { } else {
puts stderr "punk launch script must be located in the punk bin folder" puts stderr "punk launch script must be located in the punk bin folder"

1
src/testansi/3-sixels.six

@ -0,0 +1 @@
Pq"1;1;93;14#0;2;60;0;0#1;2;0;66;0#2;2;56;60;0#3;2;47;38;97#4;2;72;0;69#5;2;0;66;72#6;2;72;72;72#7;2;0;0;0#0!11~#1!12~#2!12~#3!12~#4!12~#5!12~#6!12~#7!10~-#0!11~#1!12~#2!12~#3!12~#4!12~#5!12~#6!12~#7!10~-#0!11B#1!12B#2!12B#3!12B#4!12B#5!12B#6!12B#7!10B\

0
src/testansi/War Collider - Harley_Quinn[4D2 APPROVED].png → src/testansi/harley_quinn_large.png

Before

Width:  |  Height:  |  Size: 11 MiB

After

Width:  |  Height:  |  Size: 11 MiB

1
src/testansi/image.six

File diff suppressed because one or more lines are too long

1
src/testansi/jw_carina_nircam.six

File diff suppressed because one or more lines are too long

1
src/testansi/lady-of-shalott.six

File diff suppressed because one or more lines are too long

25
src/testansi/text-test.six

@ -0,0 +1,25 @@
Alignment depends on font size, so this may not display correctly. The
(future) solution is to detect or ask for the size of the font used.
aaaaaaaaaaaaaa
aaa aaa
aaa aaa
aaa aaa
aaa aaa
aaa aaa
aaaaaaaaaaaaaa
 Pq"1;1;64;64#0;2;97;97;97#1;2;97;94;94#2;2;97;88;82#3;2;97;85;75#4;2;97;97;94#5;2;97;75;66#6;2;91;53;41#7;2;97;66;56#8;2;97;75;63#9;2;88;47;31#10;2;97;82;75#11;2;97;78;69#12;2;88;50;35#13;2;85;41;28#14;2;94;69;60#15;2;97;91;85#16;2;91;56;41#17;2;82;38;25#18;2;88;53;44#19;2;97;91;88#20;2;85;44;35#21;2;85;44;28#22;2;85;47;35#23;2;97;85;78#24;2;94;66;53#25;2;97;94;88#26;2;94;60;47#27;2;94;75;66#28;2;91;56;44#29;2;97;94;91#30;2;88;50;38#31;2;91;50;38#32;2;82;38;22#33;2;82;35;22#34;2;75;31;19#35;2;78;35;25#36;2;94;69;56#37;2;78;35;22#38;2;75;35;22#39;2;94;72;63#40;2;82;44;31#41;2;82;41;28#42;2;94;78;72#43;2;75;28;19#44;2;72;31;22#45;2;85;50;38#46;2;91;63;50#47;2;94;75;69#48;2;91;60;47#49;2;82;35;19#50;2;75;28;16#51;2;72;28;16#52;2;72;28;19#53;2;69;28;16#54;2;69;25;19#55;2;66;25;16#56;2;88;50;31#57;2;91;56;47#58;2;69;31;22#59;2;69;25;16#60;2;69;28;19#61;2;72;31;25#62;2;78;28;16#63;2;63;22;16#64;2;66;22;16#65;2;78;41;25#66;2;60;19;16#67;2;69;28;22#68;2;63;22;13#69;2;66;25;13#70;2;66;22;13#71;2;94;72;60#72;2;60;22;13#73;2;56;22;16
#0!29~^NFBBBr!28~$ #4!29?_!4?_C$#1!30?OGC??G$#11!30?_#8O#9OO#3C$#12!31?_#5G#2C#7G$#13!32?_#6G#10O$#14!33?_-
#0!21~^NFN!4~!4?}!30~$#1!21?_??_#15!4?@??G#19@$#23!22?O!6?O#13C_#2O$#7!22?_#11O!5?K#21GO#3C$#4!23?GO#10!4?A#9Q#17B#14A$#24!23?_#25!5?_#16@#20C#18@$#26!30?_#22G#27_-
#0!21~o???F^~~~G???@B!28~$#1!21?G!4?_???C!4?C$#15!21?E???O!4?A#24C??C#10G$#19!21?@#2O?C!5?_??@#19A#14O$#31!22?AOO!6?A#13W?G#39_$#4!22?_??G!4?O#8W#30@#26A#35O$#6!22?CA#20_#36_#5!4?@#16_#32E#33C#38_$#28!22?@@#25A#37!7?_#34w$#7!22?G#21G!7?@$#14!23?_G$#22!23?C#29@-
#0!21~^F!4?@???_oow{!28~$#4!21?_#14_??O!7?CA#3@$!22?OB#22@@#1oA@!4?G?A$#25!22?G#26G!6?@#23O#25??C$#13!23?_S#41E???_??C#40?@$#8!23?C#31_G#42E???_?G$#9!23?O#20A#15_HC#17O!4?A$#32!24?G#11??GA#44OG$#27!27?O#37_C?@$#36!27?_#21G#10@#34EEB@$#28!28?C#12A#45OG$#43!29?G-
#0!16~!5^K???FNG???F!10N^^^!19~$#4!16?_!4?O#11C??O!8?OO#10O!6?_$#25!17?_!4?G#9C!5?C!9?_#16_#1O#14_$#19!18?_#3_!4?O#32_!8?___#3O#21_#29?O#24_$#27!20?_??G???PO???OO$#1!21?BO??G?C#37_???_$#24!21?_A#13B!6?O!6?_$#46!22?@!8?O$#26!22?_#15O!6?A#34_$#31!23?_#5G#49?_??_#19@G!7?O!4?_$#14!24?D#8?O#2AG!9?O$#21!24?_#33??_!5?_$#47!24?A#7???C#16W#42C$#12!28?A#22A#43_$#17!28?@#30@#48G-
#0!13~B@#34w?AGGAA?EC_?__???@?@???O?@@@!8?o#15C#1o$!13?S#36_?@#38K!4?@???_#17!4?_!7?O#26_O!6?C#51GO#29A#27G$#4!13?_#3A#35C??A??@!7?__#41?_#40_#22__#53G?CAAA#12G!6?_#2A#38G#46O$#25!13?G#18G#2@#52Oooo{KA?GQ]^ZNV^!4]UUA@G?E??GWO??G$#46!14?C#31A#44K?CE?_[WO#54G???OG#6!5?_#55GGKCC#31O#32OK!5?_$#57!14?O#17?A???@#58O#61___#46!12?_#36__!6?A#0!5@BN!10~$#60!16?_#28@#22@#13@#43???@#51AD@?C!4?@?!4@#30?O#9_!7?CC$#50!24?@#37!16?G?D???G?O$#62!42?_#59__o__#33_#3A$#54!43?A#43AC??W$#56!43?G#18@#13AC#24C???C_$#50!44?O#11@#25@#19AA-
#0!14~w_#52BGBr?O__o!4G!8?C!6?ODB@#3G??C#27@$#10!14?@G!9?A!4?@#3@???A#27A#15@!9?G!4?A$#4!14?C#36C!7?A!4?@?C#0A#34G???C!7?_#18O#21C??@#25G#31A$#19!14?A#13@G!4?@?C#11A#53_O#19A!5?@B@@!9?O#0!4ow{!11~$#31!15?A!6?@#18@#20C#9C#55_oOO??O???G??CMWP`J#36_#4_#12C#50@AA#37@$#25!15?O#57O!8?@@#26C!5?C#30C#21C#66?OO#16@#32@#53!4?A#41??C#13C#49@$#34!16?C!4?C#16A#28?@#6?C#46@C#51GGG!7?A!7?AA#23G#57C$#47!16?_#60F??o!4?O#15A#64?__oo?O?G??C?O_m]C#65G$#44!17?O{CEGWWG#25???A#1A??AA#48???A#11@!7?G$#41!17?_#58?GG_#37C#24!6?C??C#59GG!6?@$#38!20?@#21A#5!7?@#4A#14C#63__oooggw_E$#2!32?@#13!5?A-
#0!11~^!4NMG#44@ACG@#53OoB#63!5?AKKmm~~Nn[#34_?C#10G@!7?_$#2!11?_!4?O??O#58BF?A#54G#55KHBE_`pa!8?RN@#14COO!5?_$#1!12?O!6?_#46?_#60G?@#64Ou{x^]KPoO!5?BK#50O??_#0F!6N^^!9~$#36!12?_#19O??@#11A#15G?_#67?ELE#59_#66!7?B@@??oO#59_??A#17@#1K!6?O$#22!13?_#17_!4?CGO_#68!12?O#13!6?_#31O#36O#19A!4?O#25O??_$#3!14?O???_#52@??O_#32!19?_#45A#4?G#8O#3O#9_#30_#46_$#10!15?O#41_?A#48GO#40!22?G#49_#62?_#33_#21_#15O$#37!15?_#25?S#4O$#28!17?@#24C$#57!17?_-
#0!9~xo_#53AHG??_!8?@#8_#9?C!8?GC?@#43?@#70???A#52__?@?A#4_O$!9?E#23G!8?@A#19@??C???G!4?O#31C?O???A#66oww{[W#53?@??O#38OG#14@#0_!8~$#2!10?@?_#24_!8?@???c#57C#21??C???_???A?@#32A@#34!7?_?@#2_#23O#3A$#14!10?E#18@O#37O!8?O#15G?O???G???GG#63@?@_?OG?CAae[??EM#24_#21@#40A#10C$#3!11?O#34H??_!8?@#11C??___!5?G#24GC#50G??A#13!8?_??C#15G$#6!11?G#60C#69E?@@#45?@A!8?C#2?G?G??G#59_?O??C!5?@??O??C#46G#25@$#38!11?E#64??EMM]W!9?@???@@@?@??O??C??@?_]EG?E#28O$#52!14?O?_@A?Oo_??@?AA!6?A#17?A#72?__#69!11?G$#59!14?@O??_w_#10C#14G!9?_???C#37O#12?A#69G$#13!14?_#55?O?C!8?@?BBBAAA??_@!4?A@?@??X@$#51!19?CG??_#33_#0G?!4O#13CC#20C#10O#49_!4?C$#4!20?@A??G!6?O#26?_#7?O#71?C#34?@$#22!20?C#20G#1CC??W!5?O#46??C$#25!22?A!4?G#5???_$#41!23?@?A#38A!9?A$#18!23?OA#12_#23!5?G$#27!23?AO-
#0!13~}}{{!5w!7o!7_!8o!4w{{{}}!11~$#25!13?@?A!7?G#15G#23G#8?G!9?G#47G#55A?A!4?@#3?A#18@#36@#10@$#11!14?@!5?C!5?G#33@???@#64??AB??A?A#19G!7?A$#48!15?@#10A#1C!4?G!8?!4O!8?G???C??A$#21!16?@#14A???C!6?G#4OO!4?O#38CCC!8?@#20@$#37!17?@???A!5?C#53A!4?@!8?AA#14C??A$#51!18?@!5?@@!5?!4C#63?BB!4@?@@@$#18!18?A#52!5@??AA#32@@@#27!5?G#37??C!5?A$#19!18?C#9A!5?C#7???GG!4?G#10???G#2G!5?C$!19?C#13A!5?C#59??!5A@#13!5?C#22C!4?A$#34!22?!4A@?CCC?@??C#25!6?G!9?@$#24!22?C!8?!4G#6!7?C#46C#43A#11C$#16!23?C#30C#73!17?@-
#0!64N
\

19
src/vfs/_vfscommon.vfs/modules/patternpunk-1.1.tm

@ -13,6 +13,7 @@
package require pattern package require pattern
package require overtype package require overtype
package require punk::args
package require punk::ansi package require punk::ansi
package require punk::lib package require punk::lib
pattern::init pattern::init
@ -77,7 +78,7 @@ set ::punk::bannerTemplate [string trim {
} else { } else {
lassign $cborder_ctext cborder ctext lassign $cborder_ctext cborder ctext
} }
return [ textblock::frame-type arc -ansiborder [a+ Web-black $cborder] [a+ Web-black $ctext][$this . logo2]] return [ textblock::frame -type arc -ansiborder [a+ Web-black $cborder] [a+ Web-black $ctext][$this . logo2]]
} }
>punk .. Property logotk "\[TCL\\\n TK \]" >punk .. Property logotk "\[TCL\\\n TK \]"
proc TCL {args} { proc TCL {args} {
@ -109,12 +110,15 @@ proc TCL {args} {
} }
return $version return $version
} }
punk::args::definition {
*id ">punk . poses"
*proc -name ">punk . poses" -help "Show or list the poses for the Punk mascot"
-censored -default 1 -type boolean -help "Set true to include mild toilet humour poses"
-return -default table -choices {list table}
}
>punk .. Method poses {args} { >punk .. Method poses {args} {
set argd [punk::args::get_dict { set argd [punk::args::get_by_id ">punk . poses" $args]
*proc -name ">punk . poses" -help "Show or list the poses for the Punk mascot"
-censored -default 1 -type boolean -help "Set true to include mild toilet humour poses"
-return -default table -choices {list table}
} $args]
set censored [dict get $argd opts -censored] set censored [dict get $argd opts -censored]
set return [dict get $argd opts -return] set return [dict get $argd opts -return]
@ -359,8 +363,11 @@ v_ /|\/ /
set subtitle [dict get $argd opts -subtitle] set subtitle [dict get $argd opts -subtitle]
set punkdeck [overtype::right [overtype::left [textblock::frame -ansiborder $border_colour -type $frame_type -boxmap $box_map -boxlimits $box_limits -title $hbar_colour$title$RST -subtitle $hbar_colour$subtitle$RST $punk] "$vbar_colour\n\n\P\nU\nN\nK$RST"] "$vbar_colour\n\nD\nE\nC\nK"] set punkdeck [overtype::right [overtype::left [textblock::frame -ansiborder $border_colour -type $frame_type -boxmap $box_map -boxlimits $box_limits -title $hbar_colour$title$RST -subtitle $hbar_colour$subtitle$RST $punk] "$vbar_colour\n\n\P\nU\nN\nK$RST"] "$vbar_colour\n\nD\nE\nC\nK"]
} }
#TODO - reuse textblock::gcross arguments - but reorder for error display
>punk .. Method gcross {{size 1} args} { >punk .. Method gcross {{size 1} args} {
package require textblock package require textblock
set argd [punk::args::get_by_id textblock::gcross [list {*}$args $size]]
textblock::gcross {*}$args $size textblock::gcross {*}$args $size
} }

46
src/vfs/_vfscommon.vfs/modules/punk-0.1.tm

@ -7428,6 +7428,7 @@ namespace eval punk {
} }
if {$topic in [list console terminal]} { if {$topic in [list console terminal]} {
set indent [string repeat " " [string length "WARNING: "]]
lappend cstring_tests [dict create\ lappend cstring_tests [dict create\
type "PM "\ type "PM "\
msg "PRIVACY MESSAGE"\ msg "PRIVACY MESSAGE"\
@ -7472,6 +7473,51 @@ namespace eval punk {
append warningblock \n "WARNING: terminal doesn't hide all [dict get $test type] control strings: $d $d8" append warningblock \n "WARNING: terminal doesn't hide all [dict get $test type] control strings: $d $d8"
} }
} }
if {![catch {punk::console::check::has_bug_legacysymbolwidth} result]} {
if {$result} {
append warningblock \n "WARNING: terminal has legacysymbolwidth bug - screen position for symbol reports 2 wide but displays 1 wide."
append warningblock \n $indent "Layout using 'legacy symbols for computing' affected."
append warningblock \n $indent "(e.g textblock frametype block2 unsupported)"
append warningblock \n $indent "This can cause extreme layout deformation when ANSI is present"
append warningblock \n $indent "In some cases unwanted spacing effects occur at a distance from the characters causing it"
}
} else {
append warningblock \n "WARNING: terminal unable to check for legacysymbolwidth bug. err:$result"
}
if {![catch {punk::console::check::has_bug_zwsp} result]} {
if {$result} {
append warningblock \n "WARNING: terminal has zero width space (\\u200b) bug - cursor position incremented when it shouldn't be."
append warningblock \n $indent "The zwsp may or may not be displayed. zwsp contributes to line length and wrapping point"
}
} else {
append warningblock \n "WARNING: terminal unable to check for zwsp bug. err:$result"
}
set grapheme_support [punk::console::grapheme_cluster_support]
#mode, 1 = set, 2 = unset. (0 = mode not recognised, 3 = permanently set, 4 = permanently unset)
if {![dict size $grapheme_support] || [dict get $grapheme_support mode] eq "unsupported" } {
append warningblock \n "WARNING: terminal either doesn't support grapheme clusters, or doesn't report so via decmode 2027 query."
if {[dict size $grapheme_support] && [dict get $grapheme_support available]} {
append warningblock \n $indent "(but punk::console::grapheme_cluster_support has determined it is probably available)"
}
} else {
if {![dict get $grapheme_support available]} {
switch -- [dict get $grapheme_support mode] {
"unset" {
append warningblock \n "WARNING: terminal reports via decmode 2027 that grapheme cluster support is off."
}
"permanently_unset" {
append warningblock \n "WARNING: terminal reports via decmode 2027 that grapheme cluster support is permanently off."
}
"BAD_RESPONSE" {
append warningblock \n "WARNING: terminal doesn't seem to recognize decmode 2027 query. No grapheme cluster support."
}
}
}
}
} }
lappend chunks [list stderr $warningblock] lappend chunks [list stderr $warningblock]

562
src/vfs/_vfscommon.vfs/modules/punk/ansi-0.1.1.tm

@ -556,21 +556,21 @@ tcl::namespace::eval punk::ansi {
} }
proc example {args} { proc example {args} {
set base [punk::repo::find_project] set base [punk::repo::find_project]
set default_ansibase [file join $base src/testansi] set default_ansifolder [file join $base src/testansi]
set argd [punk::args::get_dict [tstr -return string { set argd [punk::args::get_dict [tstr -return string {
*proc -name punk::ansi::example -help "Display .ans image files in a grid that will fit in console *proc -name punk::ansi::example -help "Display .ans image files in a grid that will fit in console
" "
-colwidth -default 82 -help "Width of each column - default of 82 will fit a standard 80wide ansi image (when framed) -colwidth -default 82 -help "Width of each column - default of 82 will fit a standard 80wide ansi image (when framed)
You can specify a narrower width to truncate images on the right side" You can specify a narrower width to truncate images on the right side"
-folder -default "${$default_ansibase}" -help "Base folder for files if relative paths are used. -folder -default "${$default_ansifolder}" -help "Base folder for files if relative paths are used.
Defaults to <projectbase>/src/testansi - where projectbase is determined from current directory. Defaults to <projectbase>/src/testansi - where projectbase is determined from current directory.
" "
*values -min 0 -max -1 *values -min 0 -max -1
files -default {belinda.ans bot.ans flower.ans fish.ans} -multiple true -help "List of filenames - leave empty to display 4 defaults" files -default {belinda.ans bot.ans flower.ans fish.ans} -multiple true -help "List of filenames - leave empty to display 4 defaults"
}] $args] }] $args]
set colwidth [dict get $argd opts -colwidth] set colwidth [dict get $argd opts -colwidth]
set ansibase [file normalize [dict get $argd opts -folder]] set ansifolder [file normalize [dict get $argd opts -folder]]
set fnames [dict get $argd values files] set fnames [dict get $argd values files]
#assumes fixed column widths e.g 80col images will fit in 82-width frames (common standard for old ansi art) (of arbitrary height) #assumes fixed column widths e.g 80col images will fit in 82-width frames (common standard for old ansi art) (of arbitrary height)
@ -579,8 +579,8 @@ tcl::namespace::eval punk::ansi {
package require punk::repo package require punk::repo
package require punk::console package require punk::console
if {![file exists $ansibase]} { if {![file exists $ansifolder]} {
puts stderr "Missing folder at $ansibase" puts stderr "Missing folder at $ansifolder"
puts stderr "Ensure ansi test files exist: $fnames" puts stderr "Ensure ansi test files exist: $fnames"
#error "punk::ansi::example Cannot find example files" #error "punk::ansi::example Cannot find example files"
} }
@ -588,7 +588,7 @@ tcl::namespace::eval punk::ansi {
set pics [list] set pics [list]
foreach f $fnames { foreach f $fnames {
if {[file pathtype $f] ne "absolute"} { if {[file pathtype $f] ne "absolute"} {
set filepath [file normalize $ansibase/$f] set filepath [file normalize $ansifolder/$f]
} else { } else {
set filepath [file normalize $f] set filepath [file normalize $f]
} }
@ -621,7 +621,7 @@ tcl::namespace::eval punk::ansi {
set subtitle [tcl::dict::get $picinfo status] set subtitle [tcl::dict::get $picinfo status]
} }
set title [tcl::dict::get $picinfo filename] set title [tcl::dict::get $picinfo filename]
set fr [textblock::frame -checkargs 0 -width $colwidth -subtitle $subtitle -title $title [tcl::dict::get $picinfo pic]] set fr [textblock::frame -width $colwidth -subtitle $subtitle -title $title [tcl::dict::get $picinfo pic]]
# -- --- --- --- # -- --- --- ---
#we need the max height of a row element to use join_basic instead of join below #we need the max height of a row element to use join_basic instead of join below
# -- --- --- --- # -- --- --- ---
@ -2096,7 +2096,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
} }
} }
} }
append out "\nNote: The 256 term colours especially 0-15 may be altered by terminal pallette settings or ansi OSC 4 codes, so specific RGB values are unavailable" append out "\nNote: The 256 term colours especially 0-15 may be altered by terminal palette settings or ansi OSC 4 codes, so specific RGB values are unavailable"
return $out return $out
} }
web { web {
@ -2126,8 +2126,25 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
switch -- $f4 { switch -- $f4 {
web- - Web- - WEB- { web- - Web- - WEB- {
set tail [tcl::string::tolower [tcl::string::trim [tcl::string::range $i 4 end] -]] set tail [tcl::string::tolower [tcl::string::trim [tcl::string::range $i 4 end] -]]
if {[tcl::dict::exists $WEB_colour_map $tail]} { set cont [string range $tail end-11 end]
set dec [tcl::dict::get $WEB_colour_map $tail] switch -- $cont {
-contrasting - -contrastive {
set cname [string range $tail 0 end-12]
}
default {
set cname $tail
}
}
if {[tcl::dict::exists $WEB_colour_map $cname]} {
set dec [tcl::dict::get $WEB_colour_map $cname]
switch -- $cont {
-contrasting {
set dec [join [punk::ansi::colour::contrasting {*}[split $dec -]] -]
}
-contrastive {
set dec [join [lindex [punk::ansi::colour::contrast_pair {*}[split $dec -]] 0] -]
}
}
set hex [colour_dec2hex $dec] set hex [colour_dec2hex $dec]
set descr "$hex $dec" set descr "$hex $dec"
} else { } else {
@ -2170,25 +2187,60 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
RGB0 - RGB1 - RGB2 - RGB3 - RGB4 - RGB5 - RGB6 - RGB7 - RGB8 - RGB9 - RGB0 - RGB1 - RGB2 - RGB3 - RGB4 - RGB5 - RGB6 - RGB7 - RGB8 - RGB9 -
rgb# - Rgb# - RGB# - rgb# - Rgb# - RGB# -
und# - und- { und# - und- {
if {[tcl::string::index $i 3] eq "#"} { set cont [string range $i end-11 end]
set tail [tcl::string::range $i 4 end] switch -- $cont {
-contrasting - -contrastive {
set iplain [string range $i 0 end-12]
}
default {
set iplain $i
}
}
if {[tcl::string::index $iplain 3] eq "#"} {
set tail [tcl::string::range $iplain 4 end]
set hex $tail set hex $tail
set dec [colour_hex2dec $hex] set dec [colour_hex2dec $hex]
set info $dec ;#show opposite type as first line of info col
switch -- $cont {
-contrasting {
set decfinal [join [punk::ansi::colour::contrasting {*}[split $dec -]] -]
set hexfinal [colour_dec2hex $decfinal]
}
-contrastive {
set decfinal [join [lindex [punk::ansi::colour::contrast_pair {*}[split $dec -]] 0] -]
set hexfinal [colour_dec2hex $decfinal]
}
default {
set hexfinal $hex
set decfinal $dec
}
}
set info "$hexfinal $decfinal" ;#show opposite type as first line of info col
} else { } else {
set tail [tcl::string::trim [tcl::string::range $i 3 end] -] set tail [tcl::string::trim [tcl::string::range $iplain 3 end] -]
set dec $tail set dec $tail
set hex [colour_dec2hex $dec] switch -- $cont {
set info $hex -contrasting {
set decfinal [join [punk::ansi::colour::contrasting {*}[split $dec -]] -]
}
-contrastive {
set decfinal [join [lindex [punk::ansi::colour::contrast_pair {*}[split $dec -]] 0] -]
}
default {
set decfinal $dec
}
}
set hexfinal [colour_dec2hex $decfinal]
set info "$hexfinal $decfinal"
} }
set webcolours_i [lsearch -all $WEB_colour_map $dec] set webcolours_i [lsearch -all $WEB_colour_map $decfinal]
set webcolours [list] set webcolours [list]
foreach ci $webcolours_i { foreach ci $webcolours_i {
lappend webcolours [lindex $WEB_colour_map $ci-1] lappend webcolours [lindex $WEB_colour_map $ci-1]
} }
set x11colours [list] set x11colours [list]
set x11colours_i [lsearch -all $X11_colour_map $dec] set x11colours_i [lsearch -all $X11_colour_map $decfinal]
foreach ci $x11colours_i { foreach ci $x11colours_i {
set c [lindex $X11_colour_map $ci-1] set c [lindex $X11_colour_map $ci-1]
if {$c ni $webcolours} { if {$c ni $webcolours} {
@ -2205,12 +2257,15 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
} }
unde { unde {
switch -- $i { switch -- $i {
undercurly - underdotted - underdashed - undersingle - underdouble { undercurly - undercurl - underdotted - underdot - underdashed - underdash - undersingle - underdouble {
$t add_row [list $i extended $s [ansistring VIEW $s]] $t add_row [list $i extended $s [ansistring VIEW $s]]
} }
underline { underline {
$t add_row [list $i "SGR 4" $s [ansistring VIEW $s]] $t add_row [list $i "SGR 4" $s [ansistring VIEW $s]]
} }
underlinedefault {
$t add_row [list $i "SGR 59" $s [ansistring VIEW $s]]
}
default { default {
$t add_row [list $i UNKNOWN $s [ansistring VIEW $s]] $t add_row [list $i UNKNOWN $s [ansistring VIEW $s]]
} }
@ -2362,10 +2417,31 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#variable WEB_colour_map #variable WEB_colour_map
#upvar ::punk::ansi::WEB_colour_map WEB_colour_map #upvar ::punk::ansi::WEB_colour_map WEB_colour_map
#foreground web colour #foreground web colour
set cname [tcl::string::tolower [tcl::string::range $i 4 end]] set tail [tcl::string::tolower [tcl::string::range $i 4 end]]
#-contrasting
#-contrastive
set cont [string range $tail end-11 end]
switch -- $cont {
-contrasting - -contrastive {
set cname [string range $tail 0 end-12]
}
default {
set cname $tail
}
}
if {[tcl::dict::exists $WEB_colour_map $cname]} { if {[tcl::dict::exists $WEB_colour_map $cname]} {
set rgbdash [tcl::dict::get $WEB_colour_map $cname] set rgbdash [tcl::dict::get $WEB_colour_map $cname]
set rgb [tcl::string::map { - ;} $rgbdash] switch -- $cont {
-contrasting {
set rgb [join [punk::ansi::colour::contrasting {*}[split $rgbdash -]] {;}]
}
-contrastive {
set rgb [join [lindex [punk::ansi::colour::contrast_pair {*}[split $rgbdash -]] 0] {;}]
}
default {
set rgb [tcl::string::map { - ;} $rgbdash]
}
}
lappend t "38;2;$rgb" lappend t "38;2;$rgb"
} else { } else {
puts stderr "ansi web colour unmatched: '$i' in call 'a+ $args'" puts stderr "ansi web colour unmatched: '$i' in call 'a+ $args'"
@ -2375,9 +2451,30 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#variable WEB_colour_map #variable WEB_colour_map
#upvar ::punk::ansi::WEB_colour_map WEB_colour_map #upvar ::punk::ansi::WEB_colour_map WEB_colour_map
#background web colour #background web colour
set cname [tcl::string::tolower [tcl::string::range $i 4 end]] set tail [tcl::string::tolower [tcl::string::range $i 4 end]]
set cont [string range $tail end-11 end]
switch -- $cont {
-contrasting - -contrastive {
set cname [string range $tail 0 end-12]
}
default {
set cname $tail
}
}
if {[tcl::dict::exists $WEB_colour_map $cname]} { if {[tcl::dict::exists $WEB_colour_map $cname]} {
lappend t "48;2;[tcl::string::map {- ;} [tcl::dict::get $WEB_colour_map $cname]]" set rgbdash [tcl::dict::get $WEB_colour_map $cname]
switch -- $cont {
-contrasting {
set rgb [join [punk::ansi::colour::contrasting {*}[split $rgbdash -]] {;}]
}
-contrastive {
set rgb [join [lindex [punk::ansi::colour::contrast_pair {*}[split $rgbdash -]] 0] {;}]
}
default {
set rgb [tcl::string::map { - ;} $rgbdash]
}
}
lappend t "48;2;$rgb"
} else { } else {
puts stderr "ansi Web colour unmatched: '$i' in call 'a+ $args'" puts stderr "ansi Web colour unmatched: '$i' in call 'a+ $args'"
} }
@ -2407,6 +2504,9 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
underline { underline {
lappend t 4 ;#underline lappend t 4 ;#underline
} }
underlinedefault {
lappend t 59
}
underextendedoff { underextendedoff {
#lremove any existing 4:1 etc #lremove any existing 4:1 etc
#NOTE struct::set result order can differ depending on whether tcl/critcl imp used #NOTE struct::set result order can differ depending on whether tcl/critcl imp used
@ -2420,13 +2520,13 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
underdouble { underdouble {
lappend e 4:2 lappend e 4:2
} }
undercurly { undercurly - undercurl {
lappend e 4:3 lappend e 4:3
} }
underdotted { underdotted - underdot {
lappend e 4:4 lappend e 4:4
} }
underdashed { underdashed - underdash {
lappend e 4:5 lappend e 4:5
} }
default { default {
@ -2542,45 +2642,109 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
} }
} }
} }
rgb- - rgb0 - rgb1 - rgb2 - rgb3 - rgb4 - rgb5 - rgb6 - rgb7 - rgb8 - rgb9 { rgb- - rgb0 - rgb1 - rgb2 - rgb3 - rgb4 - rgb5 - rgb6 - rgb7 - rgb8 - rgb9 -
#decimal rgb foreground
#allow variants rgb-xxx-xxx-xxx and rgbxxx-xxx-xxx
set rgbspec [tcl::string::trim [tcl::string::range $i 3 end] -]
set rgb [tcl::string::map [list - {;} , {;}] $rgbspec]
lappend t "38;2;$rgb"
}
Rgb- - RGB- - Rgb0 - Rgb1 - Rgb2 - Rgb3 - Rgb4 - Rgb5 - Rgb6 - Rgb7 - Rgb8 - Rgb9 - RGB0 - RGB1 - RGB2 - RGB3 - RGB4 - RGB5 - RGB6 - RGB7 - RGB8 - RGB9 { Rgb- - RGB- - Rgb0 - Rgb1 - Rgb2 - Rgb3 - Rgb4 - Rgb5 - Rgb6 - Rgb7 - Rgb8 - Rgb9 - RGB0 - RGB1 - RGB2 - RGB3 - RGB4 - RGB5 - RGB6 - RGB7 - RGB8 - RGB9 {
#decimal rgb background #decimal rgb foreground/background
set rgbspec [tcl::string::trim [tcl::string::range $i 3 end] -] #allow variants rgb-xxx-xxx-xxx and rgbxxx-xxx-xxx
set rgb [tcl::string::map [list - {;} , {;}] $rgbspec]
lappend t "48;2;$rgb" set cont [string range $i end-11 end]
} switch -- $cont {
"rgb#" { -contrasting - -contrastive {
#hex rgb foreground set iplain [string range $i 0 end-12]
set hex6 [tcl::string::trim [tcl::string::range $i 4 end] -] }
set rgb [join [::scan $hex6 %2X%2X%2X] {;}] default {
lappend t "38;2;$rgb" set iplain $i
}
}
set rgbspec [tcl::string::trim [tcl::string::range $iplain 3 end] -]
set RGB [tcl::string::map [list - { } , { } {;} { }] $rgbspec] ;#RGB as list
switch -- $cont {
-contrasting {
set rgbfinal [join [punk::ansi::colour::contrasting {*}$RGB] {;}]
}
-contrastive {
set rgbfinal [join [lindex [punk::ansi::colour::contrast_pair {*}$RGB] 0] {;}]
}
default {
set rgbfinal [join $RGB {;}]
}
}
if {[tcl::string::index $i 0] eq "r"} {
#fg
lappend t "38;2;$rgbfinal"
} else {
#bg
lappend t "48;2;$rgbfinal"
}
} }
"Rgb#" - "RGB#" { "rgb#" - "Rgb#" - "RGB#" {
#hex rgb background
set hex6 [tcl::string::trim [tcl::string::range $i 4 end] -] set hex6 [tcl::string::trim [tcl::string::range $i 4 end] -]
set rgb [join [::scan $hex6 %2X%2X%2X] {;}] #set rgb [join [::scan $hex6 %2X%2X%2X] {;}]
lappend t "48;2;$rgb" set RGB [::scan $hex6 %2X%2X%2X]
set cont [string range $i end-11 end]
switch -- $cont {
-contrasting {
set rgbfinal [join [punk::ansi::colour::contrasting {*}$RGB] {;}]
}
-contrastive {
set rgbfinal [join [lindex [punk::ansi::colour::contrast_pair {*}$RGB] 0] {;}]
}
default {
set rgbfinal [join $RGB {;}]
}
}
if {[tcl::string::index $i 0] eq "r"} {
#hex rgb foreground
lappend t "38;2;$rgbfinal"
} else {
#hex rgb background
lappend t "48;2;$rgbfinal"
}
} }
und- - und0 - und1 - und2 - und3 - und4 - und5 - und6 - und7 - und8 - und9 { und- - und0 - und1 - und2 - und3 - und4 - und5 - und6 - und7 - und8 - und9 {
#decimal rgb underline #decimal rgb underline
#allow variants und-xxx-xxx-xxx and undxxx-xxx-xxx #allow variants und-xxx-xxx-xxx and undxxx-xxx-xxx
#https://wezfurlong.org/wezterm/escape-sequences.html#csi-582-underline-color-rgb
set rgbspec [tcl::string::trim [tcl::string::range $i 3 end] -] set rgbspec [tcl::string::trim [tcl::string::range $i 3 end] -]
set rgb [tcl::string::map [list - {:} , {:}] $rgbspec] set RGB [lrange [tcl::string::map [list - { } , { } {;} { }] $rgbspec] 0 2]
lappend e "58:2::$rgb" #puts "---->'$RGB'<----"
set cont [string range $i end-11 end]
switch -- $cont {
-contrasting {
set rgbfinal [join [punk::ansi::colour::contrasting {*}$RGB] {:}]
}
-contrastive {
set rgbfinal [join [lindex [punk::ansi::colour::contrast_pair {*}$RGB] 0] {:}]
}
default {
set rgbfinal [join $RGB {:}]
}
}
#lappend e "58:2:$rgbfinal" ;# - no colorspace ID - some terminals support? which?
lappend e "58:2::$rgbfinal"
} }
"und#" { "und#" {
#hex rgb underline - (e.g kitty, wezterm) - uses colons as separators #hex rgb underline - (e.g kitty, wezterm) - uses colons as separators
set hex6 [tcl::string::trim [tcl::string::range $i 4 end] -] set hex6 [tcl::string::trim [tcl::string::range $i 4 end] -]
set rgb [join [::scan $hex6 %2X%2X%2X] {:}] #set rgb [join [::scan $hex6 %2X%2X%2X] {:}]
lappend e "58:2::$rgb" set RGB [::scan $hex6 %2X%2X%2X]
set cont [string range $i end-11 end]
switch -- $cont {
-contrasting {
set rgbfinal [join [punk::ansi::colour::contrasting {*}$RGB] {:}]
}
-contrastive {
set rgbfinal [join [lindex [punk::ansi::colour::contrast_pair {*}$RGB] 0] {:}]
}
default {
set rgbfinal [join $RGB {:}]
}
}
lappend e "58:2::$rgbfinal"
} }
undt { undt {
#CSI 58:5 UNDERLINE COLOR PALETTE INDEX
#CSI 58 : 5 : INDEX m
#variable TERM_colour_map #variable TERM_colour_map
#256 colour underline by Xterm name or by integer #256 colour underline by Xterm name or by integer
#name is xterm name or colour index from 0 - 255 #name is xterm name or colour index from 0 - 255
@ -2762,6 +2926,9 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
underline { underline {
lappend t 4 ;#underline lappend t 4 ;#underline
} }
underlinedefault {
lappend t 59
}
underextendedoff { underextendedoff {
#lremove any existing 4:1 etc #lremove any existing 4:1 etc
#use of struct::set with critcl is fast, but will reorder e (with differences depending on tcl vs critcl) #use of struct::set with critcl is fast, but will reorder e (with differences depending on tcl vs critcl)
@ -2775,13 +2942,13 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
underdouble { underdouble {
lappend e 4:2 lappend e 4:2
} }
undercurly { undercurly - undercurl {
lappend e 4:3 lappend e 4:3
} }
underdotted { underdotted - underdot {
lappend e 4:4 lappend e 4:4
} }
underdashed { underdashed - underdash {
lappend e 4:5 lappend e 4:5
} }
default { default {
@ -3262,6 +3429,8 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#tput rmam #tput rmam
return \x1b\[?7l return \x1b\[?7l
} }
proc query_mode_line_wrap {} { proc query_mode_line_wrap {} {
#*** !doctools #*** !doctools
#[call [fun query_mode_line_wrap]] #[call [fun query_mode_line_wrap]]
@ -3274,6 +3443,31 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
# \x1b\[?7\;2\$y # \x1b\[?7\;2\$y
#where 1 = set, 2 = unset. (0 = mode not recognised, 3 = permanently set, 4 = permanently unset) #where 1 = set, 2 = unset. (0 = mode not recognised, 3 = permanently set, 4 = permanently unset)
#names for other alt_screen mechanisms: 1047,1048 vs 1049?
variable decmode_names [dict create\
line_wrap 7\
LNM 20\
alt_screen 1049\
grapheme_clusters 2027\
bracketed_paste 2004\
mouse_sgr_extended 1006\
mouse_urxvt 1015\
mouse_sgr 1016\
]
proc query_mode {num_or_name} {
if {[string is integer -strict $num_or_name]} {
set m $num_or_name
} else {
variable decmode_names
if {[dict exists $decmode_names $num_or_name]} {
set m [dict get $decmode_names $num_or_name]
} else {
error "punk::ansi::query_mode unrecognised mode '$num_or_name'. Known mode names: [dict keys $decmode_names]"
}
}
return "\x1b\[?$m\$p"
}
#Alt screen buffer - smcup/rmcup ti/te #Alt screen buffer - smcup/rmcup ti/te
#Note \x1b\[?47h doesn't work at all in some terminals (e.g alacritty,cmd on windows and reportedly kitty) #Note \x1b\[?47h doesn't work at all in some terminals (e.g alacritty,cmd on windows and reportedly kitty)
@ -3658,7 +3852,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
} }
proc ansistrip2 {text} { proc ansistrip2 {text} {
#*** !doctools #*** !doctools
#[call [fun ansistrip] [arg text] ] #[call [fun ansistrip2] [arg text] ]
#[para]Return a string with ansi codes stripped out #[para]Return a string with ansi codes stripped out
#[para]Alternate graphics chars are replaced with modern unicode equivalents (e.g boxdrawing glyphs) #[para]Alternate graphics chars are replaced with modern unicode equivalents (e.g boxdrawing glyphs)
@ -6016,6 +6210,10 @@ tcl::namespace::eval punk::ansi::ansistring {
SP [list \x20 \u2420]\ SP [list \x20 \u2420]\
DEL [list \x7f \u2421]\ DEL [list \x7f \u2421]\
] ]
set map_c0 [dict create]
dict for {k v} $visuals_c0 {
dict set map_c0 {*}$v
}
#alternate symbols for space #alternate symbols for space
# \u2422 Blank Symbol (b with forwardslash overly) # \u2422 Blank Symbol (b with forwardslash overly)
@ -6051,6 +6249,9 @@ tcl::namespace::eval punk::ansi::ansistring {
#miscellaneous debug code brackets #miscellaneous debug code brackets
set obm \u27e6 ;set cbm \u27e7 ;#square double brackets from Miscellaneous Mathematical Symbols-A set obm \u27e6 ;set cbm \u27e7 ;#square double brackets from Miscellaneous Mathematical Symbols-A
#unicode Tags block brackets
set obt \u2993 ;set cbt \u2994
#this private range so rarely supported in fonts - and visuals are unknown, so we will make up some 2-letter codes for now #this private range so rarely supported in fonts - and visuals are unknown, so we will make up some 2-letter codes for now
#set visuals_c1 [tcl::dict::create\ #set visuals_c1 [tcl::dict::create\
# BPH [list \x82 "${ob8}\ue022 $cb8"]\ # BPH [list \x82 "${ob8}\ue022 $cb8"]\
@ -6120,9 +6321,21 @@ tcl::namespace::eval punk::ansi::ansistring {
APC [list \x9f "${ob8}AP$cb8"]\ APC [list \x9f "${ob8}AP$cb8"]\
] ]
#unicode Tags block - nonprinting mapped to ascii 0-127
set visuals_tags [tcl::dict::create]
for {set i 917504} {$i < 917632} {incr i} {
set asciidec [expr {$i - 917504}]
set vis [format %c $asciidec]
if {[dict exists $map_c0 $vis]} {
set vis [dict get $map_c0 $vis]
}
tcl::dict::set visuals_tags TAG$asciidec [list [format %c $i] "${obt}$vis${cbt}"]
}
set hack [tcl::dict::create] set hack [tcl::dict::create]
tcl::dict::set hack BOM1 [list \uFEFF "${obm}\U1f4a3$cbm"] ;#byte order mark/ ZWNBSP (ZWNBSP usage generally deprecated) - a picture of a bomb(2wide glyph) tcl::dict::set hack BOM1 [list \uFEFF "${obm}\U1f4a3$cbm"] ;#byte order mark/ ZWNBSP (ZWNBSP usage generally deprecated) - a picture of a bomb(2wide glyph)
tcl::dict::set hack ZWSP [list \u200B "${obm}ZWSP$cbm"]
#review - other boms? Encoding dependent? #review - other boms? Encoding dependent?
tcl::dict::set hack DCS [list \x90 "${ob8}\u2328 $cb8"] ;#keyboard from Miscellaneous Technical - 1 wide + pad. tcl::dict::set hack DCS [list \x90 "${ob8}\u2328 $cb8"] ;#keyboard from Miscellaneous Technical - 1 wide + pad.
@ -6133,7 +6346,7 @@ tcl::namespace::eval punk::ansi::ansistring {
tcl::dict::set hack PM [list \x9e "${ob8}PM$cb8"] tcl::dict::set hack PM [list \x9e "${ob8}PM$cb8"]
tcl::dict::set hack APC [list \x9f "${ob8}\U1f534$cb8"] ;#bright red ball from Miscellaneoust Symbols and Pictographs - 2 wide (APC also noted as a potential security risk) tcl::dict::set hack APC [list \x9f "${ob8}\U1f534$cb8"] ;#bright red ball from Miscellaneoust Symbols and Pictographs - 2 wide (APC also noted as a potential security risk)
set debug_visuals [tcl::dict::merge $visuals_c0 $visuals_c1 $hack] set debug_visuals [tcl::dict::merge $visuals_c0 $visuals_c1 $hack $visuals_tags]
#for repeated interaction with the same ANSI string - a mechanism to store state is more efficient #for repeated interaction with the same ANSI string - a mechanism to store state is more efficient
proc NEW {string} { proc NEW {string} {
@ -6165,7 +6378,7 @@ tcl::namespace::eval punk::ansi::ansistring {
-sp 1\ -sp 1\
] ]
set argopts [lrange $args 0 end-1] set argopts [lrange $args 0 end-1]
if {[llength $argopts] % 2 != 0} { if {[llength $argopts] % 2} {
error "ansistring VIEW options must be option-value pairs, received '$argopts'. Known opts [tcl::dict::keys $defaults]" error "ansistring VIEW options must be option-value pairs, received '$argopts'. Known opts [tcl::dict::keys $defaults]"
} }
set opts [tcl::dict::merge $defaults $argopts] set opts [tcl::dict::merge $defaults $argopts]
@ -6760,7 +6973,240 @@ tcl::namespace::eval punk::ansi::ansistring {
#*** !doctools #*** !doctools
#[list_end] [comment {--- end definitions namespace punk::ansi::ta ---}] #[list_end] [comment {--- end definitions namespace punk::ansi::ta ---}]
} }
tcl::namespace::eval punk::ansi::control {
proc APC {args} {
return \x1b_[join $args {;}]\x1b\\
}
proc APC8 {args} {
return \x9f[join $args {;}]\x9c
}
proc CSI {args} {
set finalarg [lindex $args end]
set finalbyte [string index $finalarg end]
if {![regexp {[\x40-\x73]} $finalbyte]} {
error "::punk::ansi::control::CSI final byte must be one in the set @A-Z\[\\\]^_`a-z\{|\}~"
}
if {$finalarg eq $finalbyte} {
return \x1b\[[join [lrange $args 0 end-1] {;}]$finalbyte
} else {
return \x1b\[[join $args {;}]
}
}
proc CSI8 {args} {
set finalarg [lindex $args end]
set finalbyte [string index $finalarg end]
if {![regexp {[\x40-\x73]} $finalbyte]} {
error "::punk::ansi::control::CSI final byte must be one in the set @A-Z\[\\\]^_`a-z\{|\}~"
}
if {$finalarg eq $finalbyte} {
return \x9b[join [lrange $args 0 end-1] {;}]$finalbyte
} else {
return \x9b[join $args {;}]
}
}
proc DCS {args} {
return \x1bP[join $args {;}]\x1b\\
}
proc DCS8 {args} {
return \x90[join $args {;}]\x9c
}
proc OSC {args} {
return \x1b\][join $args {;}]\x1b\\
}
proc OSC8 {args} {
return \x9d[join $args {;}]\x9c
}
}
namespace eval punk::ansi::colour {
package require punk::assertion
if {[catch {namespace import ::punk::assertion::assert} errM]} {
puts stderr "punk error importing punk::assertion::assert\n$errM"
puts stderr "punk::a* commands:[info commands ::punk::a*]"
}
punk::assertion::active on
#see also colors package
#https://sourceforge.net/p/irrational-numbers/code/HEAD/tree/pkgs/Colors/trunk/colors.tcl#l159
# classic formula for luminance (0.0 .. 100.0)
proc luminance {R G B} {
return [expr {(0.3*$R + 0.59*$G + 0.11*$B)/255.0}]
}
#New colour's luminance is dark if orig-colour is bright, and viceversa
#(note not all colours are invertable to return original)
proc contrasting {R G B} {
set lum [luminance $R $G $B]
if {$lum < 0.597} {
set lum 0.9
} else {
set lum 0.2
}
lassign [RGB2hsl $R $G $B] h s l
return [hsl2RGB $h $s $lum]
}
proc contrast_pair {R G B} {
set contra [contrasting $R $G $B]
set back [contrasting {*}$contra]
return [list $back $contra] ;#back may or may not equal original R G B
}
proc hsl2RGB { H S L } {
if { $L < 0.5 } {
set Q [expr {$L*(1.0+$S)}]
} else {
set Q [expr {$L+$S-($L*$S)}]
}
set P [expr {2.0*$L-$Q}]
set Hk [expr {$H/360.0}]
set T(R) [expr {$Hk+1.0/3.0}]
set T(G) $Hk
set T(B) [expr {$Hk-1.0/3.0}]
# normalize
foreach c {R G B} {
if {$T($c) < 0.0} { set T($c) [expr {$T($c)+1.0}] }
if {$T($c) > 1.0} { set T($c) [expr {$T($c)-1.0}] }
}
foreach c {R G B} {
if {$T($c) < [expr {1.0/6.0}]} {
set T($c) [expr {$P+($Q-$P)*6.0*$T($c)}]
} elseif {$T($c) < 0.5} {
set T($c) $Q
} elseif {$T($c) < [expr {2.0/3.0}]} {
set T($c) [expr {$P+($Q-$P)*(2.0/3.0-$T($c))*6.0}]
} else {
set T($c) $P
}
set T($c) [expr {round($T($c)*255)}]
}
return [list $T(R) $T(G) $T(B)]
}
proc RGB2hsl { R G B } {
set r [expr {$R/255.0}]
set g [expr {$G/255.0}]
set b [expr {$B/255.0}]
set max $r
set min $r
if { $g > $max } { set max $g }
if { $g < $min } { set min $g }
if { $b > $max } { set max $b }
if { $b < $min } { set min $b }
if { $max == $min } {
set H 0.0
} elseif { $b == $max } {
set H [expr {60* ($r-$g)/($max-$min)+240}]
} elseif { $g == $max } {
set H [expr {60* ($b-$r)/($max-$min)+120}]
} else {
# $r == $max
if { $g >= $b } {
set H [expr {60* ($g-$b)/($max-$min)}]
} else {
set H [expr {60* ($g-$b)/($max-$min)+360}]
}
}
set L [expr {($max+$min)/2}]
if { $L == 0.0 || $max == $min } {
set S 0.0
} elseif { $L <= 0.5 } {
set S [expr {($max-$min)/($max+$min)}]
} else {
set S [expr {($max-$min)/(2.0-($max+$min))}]
}
return [list $H $S $L]
}
#red green blue to hsl (hue saturation luminance)
#https://www.rapidtables.com/convert/color/rgb-to-hsl.html
proc jexer_rgb_to_hsl {red green blue} {
#algorithm port from Jexer LegacySixelEncode.java - with thanks to Autumn Lamonte (MIT lic)
assert {$red >=0 && $red <= 255}
assert {$green >=0 && $green <= 255}
assert {$blue >=0 && $blue <= 255}
set R [expr {$red / 255.0}]
set G [expr {$green / 255.0}]
set B [expr {$blue / 255.0}]
set Rmax 0
set Gmax 0
set Bmax 0
set min [expr {$R < $G ? $R : $G}]
set min [expr {$min < $B ? $min : $B}]
set max 0
if {($R >= $G) && ($R >= $B)} {
set max $R
set Rmax 1
} elseif {($G >= $R) && ($G >= $B)} {
set max $G
set Gmax 1
} elseif {($B >= $G) && ($B >= $R)} {
set max $B
set Bmax 1
}
set L [expr {($min + $max) / 2.0}]
set H 0.0
set S 0.0
#REVIEW - java allows floating point division by 0.0 - producing positive infinity, negative infinity or NaN
#This makes the original java algorithm a little more obscure
if {$min != $max} {
#no divide by zero issues due to min != max
if {$L < 0.5} {
set S [expr {($max - $min) / ($max + $min)}]
} else {
set S [expr {($max - $min) / (2.0 - $max - $min)}]
}
}
if {$Rmax} {
#puts "G'$G' B'$B' max'$max' min'$min'"
assert {$Gmax == 0}
assert {$Bmax == 0}
if {($max - $min) == 0} {
set H 0.0 ;#review
} else {
set H [expr {($G - $B) / ($max - $min)}]
}
} elseif {$Gmax} {
assert {$Rmax == 0}
assert {$Bmax == 0}
if {($max - $min) == 0} {
set H 2.0
} else {
set H [expr {2.0 + ($B - $R) / ($max - $min)}]
}
} elseif {$Bmax} {
assert {$Rmax == 0}
assert {$Gmax == 0}
if {($max - $min) == 0} {
set H 4.0
} else {
set H [expr {4.0 + ($R - $G) / ($max - $min)}]
}
}
if {$H < 0.0} {
set H [expr {$H + 6.0}]
}
#Tcl mathfunc round vs int (which rounds down)
set hue [expr {round($H * 60)}]
set sat [expr {round($S * 100)}]
set lum [expr {round($L * 100)}]
assert {$hue >= 0 && $hue <= 360}
assert {$sat >= 0 && $sat <= 100}
assert {$lum >= 0 && $lum <= 100}
return [list $hue $sat $lum]
}
}
tcl::namespace::eval punk::ansi::internal { tcl::namespace::eval punk::ansi::internal {
proc splitn {str {len 1}} { proc splitn {str {len 1}} {
#from textutil::split::splitn #from textutil::split::splitn
@ -6837,7 +7283,7 @@ tcl::namespace::eval punk::ansi::internal {
if {$2digithexchars eq ""} { if {$2digithexchars eq ""} {
return "" return ""
} }
if {[tcl::string::length $2digithexchars] % 2 != 0} { if {[tcl::string::length $2digithexchars] % 2} {
error "hex2str requires an even number of hex digits (2 per character)" error "hex2str requires an even number of hex digits (2 per character)"
} }
set 2str "" set 2str ""

143
src/vfs/_vfscommon.vfs/modules/punk/args-0.1.0.tm

@ -1,6 +1,6 @@
# -*- tcl -*- # -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'pmix make' or bin/punkmake to update from <pkg>-buildversion.txt # Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'pmix make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.2.tm # module template: punkshell/src/decktemplates/vendor/punk/modules/template_module-0.0.2.tm
# #
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. # Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. # Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
@ -202,6 +202,7 @@
#[list_begin itemized] #[list_begin itemized]
package require Tcl 8.6- package require Tcl 8.6-
#optional? punk::trie #optional? punk::trie
#optional? punk::textblock
#*** !doctools #*** !doctools
#[item] [package {Tcl 8.6-}] #[item] [package {Tcl 8.6-}]
@ -267,7 +268,10 @@ tcl::namespace::eval punk::args {
#[para] Core API functions for punk::args #[para] Core API functions for punk::args
#[list_begin definitions] #[list_begin definitions]
#todo - some sort of punk::args::cherrypick operation to get spec from an existing set
#todo - doctools output from definition
#dict get value with default wrapper for tcl 8.6
if {[info commands ::tcl::dict::getdef] eq ""} { if {[info commands ::tcl::dict::getdef] eq ""} {
#package require punk::lib #package require punk::lib
#interp alias "" ::punk::args::Dict_getdef "" ::punk::lib::dict_getdef #interp alias "" ::punk::args::Dict_getdef "" ::punk::lib::dict_getdef
@ -301,7 +305,7 @@ tcl::namespace::eval punk::args {
#review - how to make work with trie prefix e.g -corner -aliases {-corners} #review - how to make work with trie prefix e.g -corner -aliases {-corners}
#We mightn't want the prefix to be longer just because of an alias #We mightn't want the prefix to be longer just because of an alias
proc Get_argspecs {optionspecs args} { proc definition {optionspecs args} {
variable argspec_cache variable argspec_cache
#variable argspecs ;#REVIEW!! #variable argspecs ;#REVIEW!!
variable argspec_ids variable argspec_ids
@ -434,6 +438,7 @@ tcl::namespace::eval punk::args {
} }
} }
set proc_info {} set proc_info {}
set id_info {} ;#e.g -children <list> ??
set opt_any 0 set opt_any 0
set val_min 0 set val_min 0
set val_max -1 ;#-1 for no limit set val_max -1 ;#-1 for no limit
@ -444,8 +449,8 @@ tcl::namespace::eval punk::args {
"" - # {continue} "" - # {continue}
} }
set linespecs [lassign $trimln argname] set linespecs [lassign $trimln argname]
if {$argname ne "*id" && [llength $linespecs] %2 != 0} { if {$argname ne "*id" && [llength $linespecs] % 2} {
error "punk::args::get_dict - bad optionspecs line for record '$argname' Remaining items on line must be in paired option-value format - received '$linespecs'" error "punk::args::definition - bad optionspecs line for record '$argname' Remaining items on line must be in paired option-value format - received '$linespecs'"
} }
set firstchar [tcl::string::index $argname 0] set firstchar [tcl::string::index $argname 0]
set secondchar [tcl::string::index $argname 1] set secondchar [tcl::string::index $argname 1]
@ -454,14 +459,18 @@ tcl::namespace::eval punk::args {
switch -- [tcl::string::range $argname 1 end] { switch -- [tcl::string::range $argname 1 end] {
id { id {
#id line must have single entry - a unique id assigned by the user - an id will be allocated if no id line present or the value is "auto" #id line must have single entry - a unique id assigned by the user - an id will be allocated if no id line present or the value is "auto"
if {[llength $starspecs] != 1} { if {[llength $starspecs] == 0} {
error "punk::args::Get_argspecs - *id line must have a single entry following *id." error "punk::args::definition - *id line must have at least a single entry following *id."
} }
if {$spec_id ne ""} { if {$spec_id ne ""} {
#disallow duplicate *id line #disallow duplicate *id line
error "punk::args::Get_argspecs - *id already set. Existing value $spec_id" error "punk::args::definition - *id already set. Existing value $spec_id"
}
set spec_id [lindex $starspecs 0]
set id_info [lrange $starspecs 1 end]
if {[llength $id_info] %2} {
error "punk::args::definition - bad *id line. Remaining items on line after *id <id> must be in paired option-value format - received '$linespecs'"
} }
set spec_id $starspecs
} }
proc { proc {
#allow arbitrary - review #allow arbitrary - review
@ -523,7 +532,7 @@ tcl::namespace::eval punk::args {
-type -optional -allow_ansi -validate_ansistripped -strip_ansi -multiple\ -type -optional -allow_ansi -validate_ansistripped -strip_ansi -multiple\
-regexprepass -regexprefail -validationtransform\ -regexprepass -regexprefail -validationtransform\
} }
error "punk::args::Get_argspecs - unrecognised key '$k' in *opts line. Known keys: $known" error "punk::args::definition - unrecognised key '$k' in *opts line. Known keys: $known"
} }
} }
} }
@ -534,14 +543,14 @@ tcl::namespace::eval punk::args {
-min - -min -
-minvalues { -minvalues {
if {$v < 0} { if {$v < 0} {
error "punk::args::Get_argspecs - minimum acceptable value for key '$k' in *opts line is 0. got $v" error "punk::args::definition - minimum acceptable value for key '$k' in *opts line is 0. got $v"
} }
set val_min $v set val_min $v
} }
-max - -max -
-maxvalues { -maxvalues {
if {$v < -1} { if {$v < -1} {
error "punk::args::Get_argspecs - minimum acceptable value for key '$k' in *opts line is -1 (indicating unlimited). got $v" error "punk::args::definition - minimum acceptable value for key '$k' in *opts line is -1 (indicating unlimited). got $v"
} }
set val_max $v set val_max $v
} }
@ -594,14 +603,14 @@ tcl::namespace::eval punk::args {
-type -optional -allow_ansi -validate_ansistripped -strip_ansi -multiple\ -type -optional -allow_ansi -validate_ansistripped -strip_ansi -multiple\
-regexprepass -regexprefail -validationtransform\ -regexprepass -regexprefail -validationtransform\
} }
error "punk::args::Get_argspecs - unrecognised key '$k' in *values line. Known keys: $known" error "punk::args::definition - unrecognised key '$k' in *values line. Known keys: $known"
} }
} }
} }
} }
default { default {
error "punk::args::Get_argspecs - unrecognised * line in '$ln'. Expected *proc *opts or *values - use **name if paramname needs to be *name" error "punk::args::definition - unrecognised * line in '$ln'. Expected *proc *opts or *values - use **name if paramname needs to be *name"
} }
} }
continue continue
@ -654,7 +663,7 @@ tcl::namespace::eval punk::args {
lappend opt_solos $argname lappend opt_solos $argname
} else { } else {
#-solo only valid for flags #-solo only valid for flags
error "punk::args::get_dict - invalid -type 'none' for positional argument positional argument '$argname'" error "punk::args::definition - invalid -type 'none' for positional argument positional argument '$argname'"
} }
} }
any - anything { any - anything {
@ -681,8 +690,8 @@ tcl::namespace::eval punk::args {
} }
-validationtransform { -validationtransform {
#string is dict only 8.7/9+ #string is dict only 8.7/9+
if {([llength $specval] % 2) != 0} { if {[llength $specval] % 2} {
error "punk::args::get_dict - invalid value for key '$spec' in specifications for argument '$argname' - value must be a dictionary" error "punk::args::definition - invalid value for key '$spec' in specifications for argument '$argname' - value must be a dictionary"
} }
dict for {tk tv} $specval { dict for {tk tv} $specval {
switch -- $tk { switch -- $tk {
@ -690,7 +699,7 @@ tcl::namespace::eval punk::args {
} }
default { default {
set known_transform_keys [list -function -type -minlen -maxlen -range] ;#-choices etc? set known_transform_keys [list -function -type -minlen -maxlen -range] ;#-choices etc?
error "punk::args::get_dict - invalid subkey $tk for key '$spec' in specifications for argument '$argname' must be one of: $known_transform_keys" error "punk::args::definition - invalid subkey $tk for key '$spec' in specifications for argument '$argname' must be one of: $known_transform_keys"
} }
} }
} }
@ -701,7 +710,7 @@ tcl::namespace::eval punk::args {
-nocase -optional -multiple -validate_ansistripped -allow_ansi -strip_ansi -help\ -nocase -optional -multiple -validate_ansistripped -allow_ansi -strip_ansi -help\
-regexprepass -regexprefail -validationtransform\ -regexprepass -regexprefail -validationtransform\
] ]
error "punk::args::get_dict - unrecognised key '$spec' in specifications for argument '$argname' Known option specification keys: $known_argspecs" error "punk::args::definition - unrecognised key '$spec' in specifications for argument '$argname' Known option specification keys: $known_argspecs"
} }
} }
} }
@ -764,6 +773,7 @@ tcl::namespace::eval punk::args {
valspec_defaults $valspec_defaults\ valspec_defaults $valspec_defaults\
val_checks_defaults $val_checks_defaults\ val_checks_defaults $val_checks_defaults\
proc_info $proc_info\ proc_info $proc_info\
id_info $id_info\
] ]
tcl::dict::set argspec_cache $cache_key $result tcl::dict::set argspec_cache $cache_key $result
#tcl::dict::set argspecs $spec_id $optionspecs #tcl::dict::set argspecs $spec_id $optionspecs
@ -817,6 +827,7 @@ tcl::namespace::eval punk::args {
} }
set caller [regexp -inline {\S+} $cmdinfo] set caller [regexp -inline {\S+} $cmdinfo]
if {$caller eq "namespace"} { if {$caller eq "namespace"} {
# review - message?
set cmdinfo "punk::args::get_dict called from namespace" set cmdinfo "punk::args::get_dict called from namespace"
} }
return $cmdinfo return $cmdinfo
@ -825,6 +836,7 @@ tcl::namespace::eval punk::args {
#basic recursion blocker #basic recursion blocker
variable arg_error_isrunning 0 variable arg_error_isrunning 0
proc arg_error {msg spec_dict {badarg ""}} { proc arg_error {msg spec_dict {badarg ""}} {
#limit colours to standard 16 so that themes can apply to help output
variable arg_error_isrunning variable arg_error_isrunning
if {$arg_error_isrunning} { if {$arg_error_isrunning} {
error "arg_error already running - error in arg_error?\n triggering errmsg: $badarg" error "arg_error already running - error in arg_error?\n triggering errmsg: $badarg"
@ -843,20 +855,17 @@ tcl::namespace::eval punk::args {
set procname [::punk::args::Dict_getdef $spec_dict proc_info -name ""] set procname [::punk::args::Dict_getdef $spec_dict proc_info -name ""]
set prochelp [::punk::args::Dict_getdef $spec_dict proc_info -help ""] set prochelp [::punk::args::Dict_getdef $spec_dict proc_info -help ""]
#set t [textblock::class::table new [a+ web-yellow]Usage[a]]
set t [textblock::class::table new [a+ brightyellow]Usage[a]] set t [textblock::class::table new [a+ brightyellow]Usage[a]]
set blank_header_col [list ""] set blank_header_col [list ""]
if {$procname ne ""} { if {$procname ne ""} {
lappend blank_header_col "" lappend blank_header_col ""
#set procname_display [a+ web-white]$procname[a]
set procname_display [a+ brightwhite]$procname[a] set procname_display [a+ brightwhite]$procname[a]
} else { } else {
set procname_display "" set procname_display ""
} }
if {$prochelp ne ""} { if {$prochelp ne ""} {
lappend blank_header_col "" lappend blank_header_col ""
#set prochelp_display [a+ web-white]$prochelp[a]
set prochelp_display [a+ brightwhite]$prochelp[a] set prochelp_display [a+ brightwhite]$prochelp[a]
} else { } else {
set prochelp_display "" set prochelp_display ""
@ -880,12 +889,19 @@ tcl::namespace::eval punk::args {
$t configure_header 2 -values {Arg Type Default Multiple Help} $t configure_header 2 -values {Arg Type Default Multiple Help}
} }
#set c_default [a+ web-white Web-limegreen]
set c_default [a+ brightwhite Brightgreen] set RST [a]
#set c_badarg [a+ web-crimson] #set A_DEFAULT [a+ brightwhite Brightgreen]
set c_badarg [a+ brightred] set A_DEFAULT ""
#set greencheck [a+ web-limegreen]\u2713[a] set A_BADARG [a+ brightred]
set greencheck [a+ brightgreen]\u2713[a] set greencheck [a+ brightgreen]\u2713[a]
set A_PREFIX [a+ green] ;#use a+ so colour off can apply
if {$A_PREFIX eq ""} {
set A_PREFIX [a+ underline]
set A_PREFIXEND [a+ nounderline]\u200B ;#padding will take ANSI from last char - so add a zero width space
} else {
set A_PREFIXEND $RST
}
set opt_names [list] set opt_names [list]
set opt_names_display [list] set opt_names_display [list]
@ -894,8 +910,6 @@ tcl::namespace::eval punk::args {
set trie [punk::trie::trieclass new {*}[dict get $spec_dict opt_names]] set trie [punk::trie::trieclass new {*}[dict get $spec_dict opt_names]]
set idents [dict get [$trie shortest_idents ""] scanned] set idents [dict get [$trie shortest_idents ""] scanned]
$trie destroy $trie destroy
set M "\x1b\[32m" ;#mark in green
set RST "\x1b\[m"
foreach c [dict get $spec_dict opt_names] { foreach c [dict get $spec_dict opt_names] {
set id [dict get $idents $c] set id [dict get $idents $c]
#REVIEW #REVIEW
@ -907,7 +921,7 @@ tcl::namespace::eval punk::args {
set prefix [string range $c 0 $idlen-1] set prefix [string range $c 0 $idlen-1]
set tail [string range $c $idlen end] set tail [string range $c $idlen end]
} }
lappend opt_names_display $M$prefix$RST$tail lappend opt_names_display $A_PREFIX$prefix$A_PREFIXEND$tail
#lappend opt_names_display $M[ansistring VIEW $prefix]$RST[ansistring VIEW $tail] #lappend opt_names_display $M[ansistring VIEW $prefix]$RST[ansistring VIEW $tail]
lappend opt_names $c lappend opt_names $c
} }
@ -916,18 +930,31 @@ tcl::namespace::eval punk::args {
set opt_names_display $opt_names set opt_names_display $opt_names
} }
} }
set val_names [dict get $spec_dict val_names] set trailing_val_names [dict get $spec_dict val_names] ;#temporarily assign all as trailing
set val_names_display $val_names set leading_val_names [list]
dict for {argname info} [tcl::dict::get $spec_dict arg_info] {
if {![string match -* $argname]} {
lappend leading_val_names [lpop trailing_val_names 0]
} else {
break
}
}
if {![llength $leading_val_names] && ![llength $opt_names]} {
#all vals were actually trailing - no opts
set trailing_val_names $leading_val_names
set leading_val_names {}
}
set leading_val_names_display $leading_val_names
set trailing_val_names_display $trailing_val_names
#display options first then values #display options first then values
foreach argumentset [list [list $opt_names_display $opt_names] [list $val_names_display $val_names]] { foreach argumentset [list [list $leading_val_names_display $leading_val_names] [list $opt_names_display $opt_names] [list $trailing_val_names_display $trailing_val_names]] {
lassign $argumentset argnames_display argnames lassign $argumentset argnames_display argnames
foreach argshow $argnames_display arg $argnames { foreach argshow $argnames_display arg $argnames {
set arginfo [dict get $spec_dict arg_info $arg] set arginfo [dict get $spec_dict arg_info $arg]
if {[dict exists $arginfo -default]} { if {[dict exists $arginfo -default]} {
#set default $c_default[dict get $arginfo -default] set default $A_DEFAULT[dict get $arginfo -default]$RST
set default [dict get $arginfo -default]
} else { } else {
set default "" set default ""
} }
@ -954,8 +981,6 @@ tcl::namespace::eval punk::args {
set trie [punk::trie::trieclass new {*}[dict get $arginfo -choices]] set trie [punk::trie::trieclass new {*}[dict get $arginfo -choices]]
set idents [dict get [$trie shortest_idents ""] scanned] set idents [dict get [$trie shortest_idents ""] scanned]
$trie destroy $trie destroy
set M "\x1b\[32m" ;#mark in green
set RST "\x1b\[m"
foreach c [dict get $arginfo -choices] { foreach c [dict get $arginfo -choices] {
set id [dict get $idents $c] set id [dict get $idents $c]
if {$id eq $c} { if {$id eq $c} {
@ -966,7 +991,7 @@ tcl::namespace::eval punk::args {
set prefix [string range $c 0 $idlen-1] set prefix [string range $c 0 $idlen-1]
set tail [string range $c $idlen end] set tail [string range $c $idlen end]
} }
lappend formattedchoices "$M[ansistring VIEW $prefix]$RST[ansistring VIEW $tail]" lappend formattedchoices "$A_PREFIX[ansistring VIEW $prefix]$A_PREFIXEND[ansistring VIEW $tail]"
} }
} errM]} { } errM]} {
puts stderr "prefix marking failed\n$errM" puts stderr "prefix marking failed\n$errM"
@ -999,7 +1024,7 @@ tcl::namespace::eval punk::args {
} }
$t add_row [list $argshow [dict get $arginfo -type] $default $multiple $help] $t add_row [list $argshow [dict get $arginfo -type] $default $multiple $help]
if {$arg eq $badarg} { if {$arg eq $badarg} {
$t configure_row [expr {[$t row_count]-1}] -ansibase $c_badarg $t configure_row [expr {[$t row_count]-1}] -ansibase $A_BADARG
} }
} }
} }
@ -1033,10 +1058,10 @@ tcl::namespace::eval punk::args {
#provide ability to look up and reuse definitions from ids etc #provide ability to look up and reuse definitions from ids etc
# #
proc get_dict_by_id {id {arglist ""}} { proc get_by_id {id {arglist ""}} {
set spec [get_spec $id] set spec [get_spec $id]
if {$spec eq ""} { if {$spec eq ""} {
error "punk::args::get_dict_by_id - no such id: $id" error "punk::args::get_by_id - no such id: $id"
} }
return [get_dict $spec $arglist] return [get_dict $spec $arglist]
} }
@ -1121,7 +1146,7 @@ tcl::namespace::eval punk::args {
} }
set argspecs [Get_argspecs $optionspecs] set argspecs [definition $optionspecs]
tcl::dict::with argspecs {} ;#turn keys into vars tcl::dict::with argspecs {} ;#turn keys into vars
#puts "-arg_info->$arg_info" #puts "-arg_info->$arg_info"
set flagsreceived [list] ;#for checking if required flags satisfied set flagsreceived [list] ;#for checking if required flags satisfied
@ -1132,11 +1157,24 @@ tcl::namespace::eval punk::args {
#todo: -minmultiple -maxmultiple ? #todo: -minmultiple -maxmultiple ?
# -- --- --- ---
# Handle leading positionals
# todo - consider allowing last leading positional to have -multiple 1 but only if there exists an eopts marker later (--) ?
set opts $opt_defaults set opts $opt_defaults
set pre_values {}
dict for {a info} $arg_info {
#todo - flag for possible subhandler - whether leading - or not (shellfilter concept)
if {![string match -* $a]} {
lappend pre_values [lpop rawargs 0]
} else {
break
}
}
#assert - rawargs has been reduced by leading positionals
if {$id ne "jtest"} { if {$id ne "jtest"} {
set arglist {} set arglist {}
set values {} set post_values {}
#val_min, val_max #val_min, val_max
#puts stderr "rawargs: $rawargs" #puts stderr "rawargs: $rawargs"
#puts stderr "arg_info: $arg_info" #puts stderr "arg_info: $arg_info"
@ -1157,7 +1195,7 @@ tcl::namespace::eval punk::args {
if {$remaining_args_including_this <= $val_min} { if {$remaining_args_including_this <= $val_min} {
# if current arg is -- it will pass through as a value here # if current arg is -- it will pass through as a value here
set arglist [lrange $rawargs 0 $i-1] set arglist [lrange $rawargs 0 $i-1]
set values [lrange $rawargs $i end] set post_values [lrange $rawargs $i end]
break break
} }
@ -1169,19 +1207,19 @@ tcl::namespace::eval punk::args {
if {$remaining_args_including_this == $val_max} { if {$remaining_args_including_this == $val_max} {
#assume it's a value. #assume it's a value.
set arglist [lrange $rawargs 0 $i-1] set arglist [lrange $rawargs 0 $i-1]
set values [lrange $rawargs $i end] set post_values [lrange $rawargs $i end]
} else { } else {
#assume it's an end-of-options marker #assume it's an end-of-options marker
lappend flagsreceived -- lappend flagsreceived --
set arglist [lrange $rawargs 0 $i] set arglist [lrange $rawargs 0 $i]
set values [lrange $rawargs $i+1 end] set post_values [lrange $rawargs $i+1 end]
} }
} else { } else {
#unlimited number of values accepted #unlimited number of post_values accepted
#treat this as eopts - we don't care if remainder look like options or not #treat this as eopts - we don't care if remainder look like options or not
lappend flagsreceived -- lappend flagsreceived --
set arglist [lrange $rawargs 0 $i] set arglist [lrange $rawargs 0 $i]
set values [lrange $rawargs $i+1 end] set post_values [lrange $rawargs $i+1 end]
} }
break break
} else { } else {
@ -1194,7 +1232,7 @@ tcl::namespace::eval punk::args {
#if no optvalue following - assume it's a value #if no optvalue following - assume it's a value
#(caller should probably have used -- before it) #(caller should probably have used -- before it)
set arglist [lrange $rawargs 0 $i-1] set arglist [lrange $rawargs 0 $i-1]
set values [lrange $rawargs $i end] set post_values [lrange $rawargs $i end]
break break
} }
@ -1242,7 +1280,7 @@ tcl::namespace::eval punk::args {
#unmatched option in right position to be considered a value - treat like eopts #unmatched option in right position to be considered a value - treat like eopts
#review - document that an unspecified arg within range of possible values will act like eopts -- #review - document that an unspecified arg within range of possible values will act like eopts --
set arglist [lrange $rawargs 0 $i-1] set arglist [lrange $rawargs 0 $i-1]
set values [lrange $rawargs $i end] set post_values [lrange $rawargs $i end]
break break
} }
if {$opt_any} { if {$opt_any} {
@ -1284,12 +1322,13 @@ tcl::namespace::eval punk::args {
} else { } else {
#not flaglike #not flaglike
set arglist [lrange $rawargs 0 $i-1] set arglist [lrange $rawargs 0 $i-1]
set values [lrange $rawargs $i end] set post_values [lrange $rawargs $i end]
break break
} }
} }
set values [list {*}$pre_values {*}$post_values]
} else { } else {
set values $rawargs ;#no -flags detected set values [list {*}$pre_values {*}$rawargs] ;#no -flags detected
set arglist [list] set arglist [list]
} }
#puts stderr "--> arglist: $arglist" #puts stderr "--> arglist: $arglist"

102
src/vfs/_vfscommon.vfs/modules/punk/char-0.1.0.tm

@ -1912,19 +1912,32 @@ tcl::namespace::eval punk::char {
tailcall ansifreestring_width $text tailcall ansifreestring_width $text
} }
#faster than textutil::wcswidth (at least for string up to a few K in length) #todo - consider disallowing/erroring out when \r \n in string?
# - tab/vtab?
# - compare with wcswidth returning -1 for entire string containing such in python,perl
proc wcswidth {string} { proc wcswidth {string} {
#faster than textutil::wcswidth (at least for string up to a few K in length)
#..but - 'scan' is horrible for 400K+
#TODO
set codes [scan $string [tcl::string::repeat %c [tcl::string::length $string]]] set codes [scan $string [tcl::string::repeat %c [tcl::string::length $string]]]
set width 0 set width 0
foreach c $codes { foreach c $codes {
if {$c <= 255} { #unicode Tags block zero width
incr width if {$c < 917504 || $c > 917631} {
} else { if {$c <= 255} {
set w [textutil::wcswidth_char $c] #review - non-printing ascii? why does textutil::wcswidth report 1 ??
if {$w < 0} { #todo - compare with python or other lang wcwidth
return -1 if {!($c < 31 || $c == 127)} {
incr width
}
} else { } else {
#TODO - various other joiners and non-printing chars
set w [textutil::wcswidth_char $c]
if {$w < 0} {
return -1
} else {
incr width $w incr width $w
}
} }
} }
} }
@ -2029,7 +2042,8 @@ tcl::namespace::eval punk::char {
# return [tcl::string::length $text] # return [tcl::string::length $text]
#} #}
if {![regexp "\[\uFF-\U10FFFF\]" $text]} { if {![regexp "\[\uFF-\U10FFFF\]" $text]} {
return [tcl::string::length $text] #return [tcl::string::length $text]
return [punk::char::wcswidth $text] ;#still use our wcswidth to account for non-printable ascii
} }
#split just to get the standalone character widths - and then scan for other combiners (?) - or scan for clusters first? #split just to get the standalone character widths - and then scan for other combiners (?) - or scan for clusters first?
@ -2052,7 +2066,7 @@ tcl::namespace::eval punk::char {
#fall back to textutil::wcswidth (which doesn't for example handle diactricts/combiners so we can't use until these and other things such as \u200b and diacritics are already stripped/accounted for) #fall back to textutil::wcswidth (which doesn't for example handle diactricts/combiners so we can't use until these and other things such as \u200b and diacritics are already stripped/accounted for)
#todo - find something that understands grapheme clusters - needed also for grapheme_split #todo - find something that understands grapheme clusters - needed also for grapheme_split
#use punk::char::wcswidth - faster than the string split in textutil::wcswidth but still uses textutil::wcswidth_char #use punk::char::wcswidth - faster than the string split in textutil::wcswidth but still uses textutil::wcswidth_char
incr len [wcswidth $uc] incr len [punk::char::wcswidth $uc]
} }
#todo - work out what to do with anomalies like grave combiner which print at different lengths on different terminals (fonts?) and for which cursor-query escape sequence lies. #todo - work out what to do with anomalies like grave combiner which print at different lengths on different terminals (fonts?) and for which cursor-query escape sequence lies.
return $len return $len
@ -2229,7 +2243,68 @@ tcl::namespace::eval punk::char {
return [tcl::string::map $map $str] return [tcl::string::map $map $str]
} }
#todo - lookup from unicode tables
variable flags [dict create\
AU \U1F1E6\U1F1FA\
US \U1F1FA\U1F1F8\
ZW \U1F1FF\U1F1FC
]
variable rflags
dict for {k v} $flags {
dict set rflags $v $k
}
proc flag_from_ascii {code} {
variable flags
if {[regexp {^[A-Z]{2}$} $code]} {
if {[dict exists $flags $code]} {
return [dict get $flags $code]
} else {
error "unsupported flags code: $code"
}
} else {
#try as subregion
#e.g gbeng,gbwls,gbsct
return \U1f3f4[tag_from_ascii $code]\Ue007f
}
}
proc flag_to_ascii {charsequence} {
variable rflags
if {[dict exists $rflags $charsequence]} {
return [dict get $rflags $charsequence]
}
if {[string index $charsequence 0] eq "\U1F3F4" && [string index $charsequence end] eq "\UE007F"} {
#subdivision flag
set tag [string range $charsequence 1 end-1]
return [tag_to_ascii $tag]
}
error "unknown flag $charsequence"
}
proc tag_to_ascii {t} {
set fmt [string repeat %c [string length $t]]
set declist [scan $t $fmt]
#unicode Tags block - e0000 to e007f
set declist [lmap dec $declist {
if {$dec < 917504 || $dec > 917631} {
error "char [ansistring VIEW -lf 1 -cr 1 -vt 1 [format %c $dec]] has decimal value $dec. Not in unicode Tags block range 917504-917631 (e0000-e007f)"
}
incr dec -917504
}]
return [format $fmt {*}$declist]
}
proc tag_from_ascii {a} {
set fmt [string repeat %c [string length $a]]
set declist [scan $a $fmt]
set declist [lmap dec $declist {
if {$dec > 127} {
error "char [ansistring VIEW -lf 1 -cr 1 -vt 1 [format %c $dec]] has decimal value $dec. Not in ascii range 0-127"
}
incr dec 917504
}]
return [format $fmt {*}$declist]
}
#split into plaintext and runs of combiners (combining diacritical marks - not ZWJ or ZWJNJ) #split into plaintext and runs of combiners (combining diacritical marks - not ZWJ or ZWJNJ)
proc combiner_split {text} { proc combiner_split {text} {
@ -2250,15 +2325,12 @@ tcl::namespace::eval punk::char {
#puts "->start $start ->match $matchStart $matchEnd" #puts "->start $start ->match $matchStart $matchEnd"
lappend list [tcl::string::range $text $start $matchStart-1] [tcl::string::range $text $matchStart $matchEnd] lappend list [tcl::string::range $text $start $matchStart-1] [tcl::string::range $text $matchStart $matchEnd]
set start [expr {$matchEnd+1}] set start [expr {$matchEnd+1}]
#if {$start >= [tcl::string::length $text]} {
# break
#}
} }
lappend list [tcl::string::range $text $start end] lappend list [tcl::string::range $text $start end]
} }
#ZWJ ZWNJ ? #ZWJ ZWNJ ?
#SWSP ?
#1st shot - basic diacritics #1st shot - basic diacritics
#todo - become aware of unicode grapheme cluster boundaries #todo - become aware of unicode grapheme cluster boundaries
@ -2269,6 +2341,8 @@ tcl::namespace::eval punk::char {
#should also handle the ZWJ (and the variation selectors? eg \uFE0F) character which should account for emoji clusters #should also handle the ZWJ (and the variation selectors? eg \uFE0F) character which should account for emoji clusters
#Note - emoji cluster could be for example 11 code points/41 bytes (family emoji with skin tone modifiers for each member, 3 ZWJs) #Note - emoji cluster could be for example 11 code points/41 bytes (family emoji with skin tone modifiers for each member, 3 ZWJs)
#This still leaves a whole class of clusters.. korean etc unhandled :/ #This still leaves a whole class of clusters.. korean etc unhandled :/
#todo - tk::startOfCluster / tk::endOfCluster - try to get it brought into Tcl
#https://core.tcl-lang.org/tcl/tktview/a4c7eeaf63
proc grapheme_split {text} { proc grapheme_split {text} {
set graphemes [list] set graphemes [list]
set csplits [combiner_split $text] set csplits [combiner_split $text]
@ -2287,7 +2361,7 @@ tcl::namespace::eval punk::char {
set graphemes [list] set graphemes [list]
set csplits [combiner_split $text] set csplits [combiner_split $text]
foreach {pt combiners} [lrange $csplits 0 end-1] { foreach {pt combiners} [lrange $csplits 0 end-1] {
set pt_decs [scan $pt [tcl::string::repeat %c [tcl::string::length $pt]]] set pt_decs [scan $pt [tcl::string::repeat %c [tcl::string::length $pt]]] ;#warning scan %c... slow for v large strings (e.g 400k+)
set combiner_decs [scan $combiners [tcl::string::repeat %c [tcl::string::length $combiners]]] set combiner_decs [scan $combiners [tcl::string::repeat %c [tcl::string::length $combiners]]]
lset pt_decs end [concat [lindex $pt_decs end] $combiner_decs] lset pt_decs end [concat [lindex $pt_decs end] $combiner_decs]
lappend graphemes {*}$pt_decs lappend graphemes {*}$pt_decs

326
src/vfs/_vfscommon.vfs/modules/punk/console-0.1.1.tm

@ -46,9 +46,12 @@
package require Tcl 8.6- package require Tcl 8.6-
package require Thread ;#tsv required to sync is_raw package require Thread ;#tsv required to sync is_raw
package require punk::ansi package require punk::ansi
package require punk::args
#*** !doctools #*** !doctools
#[item] [package {Tcl 8.6-}] #[item] [package {Tcl 8.6-}]
#[item] [package {Thread}]
#[item] [package {punk::ansi}] #[item] [package {punk::ansi}]
#[item] [package {punk::args}]
#*** !doctools #*** !doctools
@ -109,6 +112,8 @@ namespace eval punk::console {
variable ansi_wanted 2 ;#2 for default assumed yes, will be set to -1 for automatically unwanted when ansi unavailable values of 0 or 1 won't be autoset variable ansi_wanted 2 ;#2 for default assumed yes, will be set to -1 for automatically unwanted when ansi unavailable values of 0 or 1 won't be autoset
#punk::console namespace - contains *directly* acting functions - some based on ansi escapes from the 'ansi' sub namespace, some on local system calls or executable calls wrapped in the 'local' sub namespace #punk::console namespace - contains *directly* acting functions - some based on ansi escapes from the 'ansi' sub namespace, some on local system calls or executable calls wrapped in the 'local' sub namespace
#directly acting means they write to stdout to cause the console to peform the action, or they perform the action immediately via other means. #directly acting means they write to stdout to cause the console to peform the action, or they perform the action immediately via other means.
#punk::console::ansi contains a subset of punk::ansi, but with emission to stdout as opposed to simply returning the ansi sequence. #punk::console::ansi contains a subset of punk::ansi, but with emission to stdout as opposed to simply returning the ansi sequence.
@ -255,6 +260,8 @@ namespace eval punk::console {
enable_bracketed_paste enable_bracketed_paste
} }
#todo stop_application_mode {} {}
proc mode {{raw_or_line query}} { proc mode {{raw_or_line query}} {
#variable is_raw #variable is_raw
variable ansi_available variable ansi_available
@ -634,7 +641,7 @@ namespace eval punk::console {
#todo - make timeout configurable? #todo - make timeout configurable?
set waitvarname "::punk::console::ansi_response_wait($callid)" set waitvarname "::punk::console::ansi_response_wait($callid)"
#500ms is generally plenty for a terminal to respond.. but not in some cases. e.g event loop busy with stdin keypress?? review #500ms is generally plenty for a terminal to respond.. but not in some cases. e.g event loop busy with stdin keypress?? review
set timeoutid($callid) [after 2000 [list set $waitvarname timedout]] set timeoutid($callid) [after 1000 [list set $waitvarname timedout]]
#JMN #JMN
# - stderr vs stdout # - stderr vs stdout
@ -1040,6 +1047,64 @@ namespace eval punk::console {
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels] set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload return $payload
} }
variable last_da1_result ""
#TODO - 22? 28? 32?
#1 132 columns
#2 Printer port extension
#4 Sixel extension
#6 Selective erase
#7 DRCS
#8 UDK
#9 NRCS
#12 SCS extension
#15 Technical character set
#18 Windowing capability
#21 Horizontal scrolling
#23 Greek extension
#24 Turkish extension
#42 ISO Latin 2 character set
#44 PCTerm
#45 Soft key map
#46 ASCII emulation
#https://vt100.net/docs/vt510-rm/DA1.html
#
proc get_device_attributes {{inoutchannels {stdin stdout}}} {
#DA1
variable last_da1_result
#first element in result is the terminal's architectural class 61,62,63,64.. ?
#for vt100 we get things like: "ESC\[?1;0c"
#for vt102 "ESC\[?6c"
#set capturingregex {(.*)(\x1b\[\?6[0-9][;]*([;0-9]+)c)$} ;#must capture prefix,entire-response,response-payload
set capturingregex {(.*)(\x1b\[\?([0-9]*[;0-9]+)c)$} ;#must capture prefix,entire-response,response-payload
set request "\x1b\[c"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
set last_da1_result $payload
return $payload
}
#https://vt100.net/docs/vt510-rm/DA2.html
proc get_device_attributes_secondary {{inoutchannels {stdin stdout}}} {
#DA2
set capturingregex {(.*)(\x1b\[\>([0-9]*[;][0-9]*[;][0-1]c))$} ;#must capture prefix,entire-response,response-payload
#expect CSI > X;10|20;0|1c - docs suggest X should be something like 61. In practice we see 0 or 1 (windows) REVIEW
set request "\x1b\[>c"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload
}
proc get_device_attributes_tertiary {{inoutchannels {stdin stdout}}} {
#DA3
set capturingregex {(.*)(\x1bP!\|([0-9]{8})\x1b\\)$} ;#must capture prefix,entire-response,response-payload
set request "\x1b\[=c"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload
}
proc get_terminal_id {{inoutchannels {stdin stdout}}} {
#DA3 - alias
get_device_attributes_tertiary $inoutchannels
}
proc get_tabstops {{inoutchannels {stdin stdout}}} { proc get_tabstops {{inoutchannels {stdin stdout}}} {
#DECTABSR \x1b\[2\$w #DECTABSR \x1b\[2\$w
#response example " ^[P2$u9/17/25/33/41/49/57/65/73/81^[\ " (where ^[ is \x1b) #response example " ^[P2$u9/17/25/33/41/49/57/65/73/81^[\ " (where ^[ is \x1b)
@ -1110,6 +1175,55 @@ namespace eval punk::console {
return [split [get_cursor_pos $inoutchannels] ";"] return [split [get_cursor_pos $inoutchannels] ";"]
} }
#todo - work out how to query terminal and set cell size in pixels
#for now use the windows default
variable cell_size
set cell_size ""
set cell_size_fallback 10x20
#todo - change -inoutchannels to -terminalobject with prebuilt default
punk::args::definition {
*id punk::console::cell_size
-inoutchannels -default {stdin stdout} -type list
*values -min 0 -max 1
newsize -default ""
}
proc cell_size {args} {
set argd [punk::args::get_by_id punk::console::cell_size $args]
set inoutchannels [dict get $argd opts -inoutchannels]
set newsize [dict get $argd values newsize]
variable cell_size
if {$newsize eq ""} {
#query existing setting
if {$cell_size eq ""} {
#not set - try to query terminal's overall dimensions
set pixeldict [punk::console::get_xterm_pixels $inoutchannels]
lassign $pixeldict _w sw _h sh
if {[string is integer -strict $sw] && [string is integer -strict $sh]} {
lassign [punk::console::get_size] _cols columns _rows rows
#review - is returned size in pixels always a multiple of rows and cols?
set w [expr {$sw / $columns}]
set h [expr {$sh / $rows}]
set cell_size ${w}x${h}
return $cell_size
} else {
set cell_size $::punk::console::cell_size_fallback
puts stderr "punk::console::cell_size unable to query terminal for pixel data - using default $cell_size"
return $cell_size
}
}
return $cell_size
}
#newsize supplied - try to set
lassign [split [string tolower $newsize] x] w h
if {![string is integer -strict $w] || ![string is integer -strict $h] || $w < 1 || $h < 1} {
error "punk::sixel::cell_size error - expected format WxH where W and H are positive integers - got '$newsize'"
}
set cell_size ${w}x${h}
}
#todo - determine cursor on/off state before the call to restore properly. #todo - determine cursor on/off state before the call to restore properly.
proc get_size {{inoutchannels {stdin stdout}}} { proc get_size {{inoutchannels {stdin stdout}}} {
lassign $inoutchannels in out lassign $inoutchannels in out
@ -1202,13 +1316,19 @@ namespace eval punk::console {
lassign [split $payload {;}] rows cols lassign [split $payload {;}] rows cols
return [list columns $cols rows $rows] return [list columns $cols rows $rows]
} }
proc get_xterm_pixels {{inoutchannels {stdin stdout}}} {
set capturingregex {(.*)(\x1b\[4;([0-9]+;[0-9]+)t)$} ;#must capture prefix,entire-response,response-payload
set request "\x1b\[14t"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
lassign [split $payload {;}] height width
return [list width $width height $height]
}
proc get_mode_line_wrap {{inoutchannels {stdin stdout}}} { proc get_mode_line_wrap {{inoutchannels {stdin stdout}}} {
set capturingregex {(.*)(\x1b\[\?7;([0-9]+)\$y)$} ;#must capture prefix,entire-response,response-payload set capturingregex {(.*)(\x1b\[\?7;([0-9]+)\$y)$} ;#must capture prefix,entire-response,response-payload
set request "\x1b\[?7\$p" set request "\x1b\[?7\$p"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels] set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload return $payload
} }
#Terminals generally default to LNM being reset (off) ie enter key sends a lone <cr> #Terminals generally default to LNM being reset (off) ie enter key sends a lone <cr>
#Terminals tested on windows either don't respond to this query, or respond with 0 (meaning mode not understood) #Terminals tested on windows either don't respond to this query, or respond with 0 (meaning mode not understood)
#I presume from this that almost nobody is using LNM 1 (which sends both <cr> and <lf>) #I presume from this that almost nobody is using LNM 1 (which sends both <cr> and <lf>)
@ -1218,11 +1338,59 @@ namespace eval punk::console {
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels] set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload return $payload
} }
#DECRPM responses e.g:
# \x1b\[?7\;1\$y
# \x1b\[?7\;2\$y
#where 1 = set, 2 = unset. (0 = mode not recognised, 3 = permanently set, 4 = permanently unset)
proc get_mode {num_or_name {inoutchannels {stdin stdout}}} {
if {[string is integer -strict $num_or_name]} {
set m $num_or_name
} else {
upvar ::punk::ansi::decmode_names decmode_names
if {[dict exists $decmode_names $num_or_name]} {
set m [dict get $decmode_names $num_or_name]
} else {
error "punk::console::get_mode unrecognised mode '$num_or_name'. Known mode names: [dict keys $decmode_names]"
}
}
set capturingregex [string map [list %MODE% $m] {(.*)(\x1b\[\?%MODE%;([0-9]+)\$y)$}] ;#must capture prefix,entire-response,response-payload
set request "\x1b\[?$m\$p"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload
}
proc set_mode {num_or_name {inoutchannels {stdin stdout}}} {
if {[string is integer -strict $num_or_name]} {
set m $num_or_name
} else {
upvar ::punk::ansi::decmode_names decmode_names
if {[dict exists $decmode_names $num_or_name]} {
set m [dict get $decmode_names $num_or_name]
} else {
error "punk::console::set_mode unrecognised mode '$num_or_name'. Known mode names: [dict keys $decmode_names]"
}
}
return "\x1b\[?${m}h"
}
proc unset_mode {num_or_name {inoutchannels {stdin stdout}}} {
if {[string is integer -strict $num_or_name]} {
set m $num_or_name
} else {
upvar ::punk::ansi::decmode_names decmode_names
if {[dict exists $decmode_names $num_or_name]} {
set m [dict get $decmode_names $num_or_name]
} else {
error "punk::console::unset_mode unrecognised mode '$num_or_name'. Known mode names: [dict keys $decmode_names]"
}
}
return "\x1b\[?${m}l"
}
#terminals lie. This should be a reasonable (albeit relatively slow) test of actual width - but some terminals seem to miscalculate. #terminals lie. This should be a reasonable (albeit relatively slow) test of actual width - but some terminals seem to miscalculate.
#todo - a visual interactive test/questionnaire to ask user if things are lining up or if the terminal is telling fibs about cursor position. #todo - a visual interactive test/questionnaire to ask user if things are lining up or if the terminal is telling fibs about cursor position.
#todo - determine if these anomalies are independent of font #todo - determine if these anomalies are independent of font
#punk::ansi should be able to glean widths from unicode data files - but this may be incomplete - todo - compare with what terminal actually does. #punk::ansi should be able to glean widths from unicode data files - but this may be incomplete - todo - compare with what terminal actually does.
#review - vertical movements (e.g /n /v will cause emit 0 to be ineffective - todo - disallow?)
proc test_char_width {char_or_string {emit 0}} { proc test_char_width {char_or_string {emit 0}} {
#return 1 #return 1
#JMN #JMN
@ -1266,6 +1434,57 @@ namespace eval punk::console {
return [expr {$col2 - $col1}] return [expr {$col2 - $col1}]
} }
#get reported cursor position after emitting teststring.
#The row is more likely to be a lie than the column
#With wrapping on we should be able to test if the terminal has an inconsistency between reported width and when it actually wraps.
#(but as line wrapping generally occurs based on width - we probably won't see this - just 'apparently' early wrapping due to printing mismatch with width)
#unfortunately if terminal reports something like \u200B as width 1, but doesn't print it - we can't tell. (vs reporting 1 wide and printing replacement char/space)
#When cursor is already at bottom of screen, scrolling will occur so rowoffset will be zero
#we either need to move cursor up before test - or use alt screen ( or scroll_up then scroll_down?)
#for now we will use alt screen to reduce scrolling effects - REVIEW
proc test_string_cursor {teststring {emit 0}} {
variable ansi_available
if {!$ansi_available} {
puts stderr "No ansi - cannot test char_width of '$teststring' returning [string length $test_string]"
return [string length $teststring]
}
punk::console::enable_alt_screen
punk::console::move 0 0
if {!$emit} {
puts -nonewline stdout \033\[2K\033\[1G ;#2K erase line, 1G cursor at col1
}
set response ""
if {[catch {
set response [punk::console::get_cursor_pos]
} errM]} {
puts stderr "Cannot test_string_cursor for '[punk::ansi::ansistring VIEW $teststring]' - may be no console? Error message from get_cursor_pos: $errM"
return
}
lassign [split $response ";"] row1 col1
if {![string length $response] || ![string is integer -strict $col1] || ![string is integer -strict $row1]} {
puts stderr "test_string_cursor Could not interpret response from get_cursor_pos for initial cursor pos. Response: '[punk::ansi::ansistring VIEW $response]'"
flush stderr
return
}
puts -nonewline stdout $teststring
flush stdout
set response [punk::console::get_cursor_pos]
lassign [split $response ";"] row2 col2
if {![string is integer -strict $col2] || ![string is integer -strict $row2]} {
puts stderr "test_string_cursor could not interpret response from get_cursor_pos for post-emit cursor pos. Response:'[punk::ansi::ansistring VIEW $response]'"
flush stderr
return
}
if {!$emit} {
puts -nonewline stdout \033\[2K\033\[1G
}
flush stdout;#if we don't flush - a subsequent stderr write could move the cursor to a newline and interfere with our 2K1G erasure and cursor repositioning.
punk::console::disable_alt_screen
return [list rowoffset [expr {$col2 - $col1}] columnoffset [expr {$row2 - $row1}]]
}
#todo! - improve ideally we want to use VT sequences to determine - and make a separate utility for testing via systemcalls/os api #todo! - improve ideally we want to use VT sequences to determine - and make a separate utility for testing via systemcalls/os api
proc test_can_ansi {} { proc test_can_ansi {} {
#don't set ansi_avaliable here - we want to be able to change things, retest etc. #don't set ansi_avaliable here - we want to be able to change things, retest etc.
@ -1306,8 +1525,59 @@ namespace eval punk::console {
if {!$ansi_available} { if {!$ansi_available} {
return 0 return 0
} }
set ansi_available [test_can_ansi] #ansi_available defaults to -1 (unknown)
return [expr {$ansi_available}] if {$ansi_available == -1} {
set ansi_available [test_can_ansi]
return $ansi_available
}
return 1
}
variable grapheme_cluster_support [dict create] ;#default empty dict for unknown/untested
#todo - flag to retest? (for consoles where grapheme cluster support can be disabled e.g via decmode 2027)
proc grapheme_cluster_support {} {
variable grapheme_cluster_support
if {[dict size $grapheme_cluster_support]} {
return $grapheme_cluster_support
}
if {[info exists ::env(TERM_PROGRAM)]} {
#terminals known to support grapheme clusters, but unable to respond to decmode request 2027
#wezterm (on windows as at 2024-12 decmode 2027 doesn't work)
#REVIEW - what if terminal is remote wezterm? can/will this env variable
# iterm and apple terminal also set TERM_PROGRAM
if {[string tolower $::env(TERM_PROGRAM)] in [list wezterm]} {
set is_available 1
return [dict create available 1 mode set]
}
}
#where 1 = set, 2 = unset. (0 = mode not recognised, 3 = permanently set, 4 = permanently unset)
set state [get_mode grapheme_clusters] ;#decmode 2027 extension
set is_available 0
switch -- $state {
0 {
set m unsupported ;# the dec query is unsupported - but it's possible the terminal still has grapheme support
}
1 {
set m set
set is_available 1
}
2 {
set m unset
}
3 {
set m permanently_set
set is_available 1
}
4 {
set m permanently_unset
}
default {
set m "BAD_RESPONSE"
}
}
return [dict create available $is_available mode $m]
} }
namespace eval ansi { namespace eval ansi {
@ -1432,7 +1702,7 @@ namespace eval punk::console {
puts -nonewline stdout [punk::ansi::move_column $col] puts -nonewline stdout [punk::ansi::move_column $col]
} }
proc move_row {row} { proc move_row {row} {
puts -nonewline stdout [punk::ansi::move_row $col] puts -nonewline stdout [punk::ansi::move_row $row]
} }
proc move_emit {row col data args} { proc move_emit {row col data args} {
puts -nonewline stdout [punk::ansi::move_emit $row $col $data {*}$args] puts -nonewline stdout [punk::ansi::move_emit $row $col $data {*}$args]
@ -1912,8 +2182,52 @@ namespace eval punk::console {
#[list_end] [comment {--- end definitions namespace punk::console ---}] #[list_end] [comment {--- end definitions namespace punk::console ---}]
} }
namespace eval punk::console::check {
variable has_bug_legacysymbolwidth -1 ;#undetermined
proc has_bug_legacysymbolwidth {} {
#some terminals (on windows as at 2024) miscount width of these single-width blocks internally
#resulting in extra spacing that is sometimes well removed from the character itself (e.g at next ansi reset)
#This was fixed in windows-terminal based systems (2021) but persists in others.
#https://github.com/microsoft/terminal/issues/11694
variable has_bug_legacysymbolwidth
if {!$has_bug_legacysymbolwidth} {
return 0
}
if {$has_bug_legacysymbolwidth == -1} {
#run the test using ansi movement
#we only test a specific character from the known problematic set
set w [punk::console::test_char_width \U1fb7d]
if {$w == 1} {
set has_bug_legacysymbolwidth 0
} else {
#can return 2 on legacy window consoles for example
set has_bug_legacysymbolwidth 1
}
return $has_bug_legacysymbolwidth
}
return 1
}
variable has_bug_zwsp -1 ;#undetermined
proc has_bug_zwsp {} {
#Note that some terminals behave differently regarding a leading zwsp vs one that is inline between other chars.
#we are only testing the inline behaviour here.
variable has_bug_zwsp
if {!$has_bug_zwsp} {
return 0
}
if {$has_bug_zwsp == -1} {
set w [punk::console::test_char_width X\u200bY]
}
if {$w == 2} {
return 0
} else {
#may return 3 - but this gives no indication of whether terminal hides it or not.
return 1
}
return 1
}
}

41
src/vfs/_vfscommon.vfs/modules/punk/lib-0.1.1.tm

@ -63,38 +63,6 @@ package require Tcl 8.6-
#*** !doctools #*** !doctools
#[section API] #[section API]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# oo::class namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#tcl::namespace::eval punk::lib::class {
# #*** !doctools
# #[subsection {Namespace punk::lib::class}]
# #[para] class definitions
# if {[info commands [tcl::namespace::current]::interface_sample1] eq ""} {
# #*** !doctools
# #[list_begin enumerated]
#
# # oo::class create interface_sample1 {
# # #*** !doctools
# # #[enum] CLASS [class interface_sample1]
# # #[list_begin definitions]
#
# # method test {arg1} {
# # #*** !doctools
# # #[call class::interface_sample1 [method test] [arg arg1]]
# # #[para] test method
# # puts "test: $arg1"
# # }
#
# # #*** !doctools
# # #[list_end] [comment {-- end definitions interface_sample1}]
# # }
#
# #*** !doctools
# #[list_end] [comment {--- end class enumeration ---}]
# }
#}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::lib::ensemble { tcl::namespace::eval punk::lib::ensemble {
#wiki.tcl-lang.org/page/ensemble+extend #wiki.tcl-lang.org/page/ensemble+extend
@ -172,7 +140,10 @@ tcl::namespace::eval punk::lib::check {
proc has_tclbug_lsearch_strideallinline {} { proc has_tclbug_lsearch_strideallinline {} {
#bug only occurs with single -index value combined with -stride -all -inline -subindices #bug only occurs with single -index value combined with -stride -all -inline -subindices
#https://core.tcl-lang.org/tcl/tktview/5a1aaa201d #https://core.tcl-lang.org/tcl/tktview/5a1aaa201d
set result [lsearch -stride 3 -all -inline -index 1 -subindices {a1 a2 a3} *] if {[catch {[lsearch -stride 3 -all -inline -index 1 -subindices {a1 a2 a3} *]} result]} {
#we aren't looking for an error result - error most likely indicates tcl too old to support -stride
return 0
}
return [expr {$result ne "a2"}] return [expr {$result ne "a2"}]
} }
@ -2575,12 +2546,12 @@ namespace eval punk::lib {
while {$j <= $max} { while {$j <= $max} {
if {$x % $j == 0} { if {$x % $j == 0} {
set other [expr {$x / $j}] set other [expr {$x / $j}]
if {$other % 2 != 0} { if {$other % 2} {
if {$other ni $factors} { if {$other ni $factors} {
lappend factors $other lappend factors $other
} }
} }
if {$j % 2 != 0} { if {$j % 2} {
if {$j ni $factors} { if {$j ni $factors} {
lappend factors $j lappend factors $j
} }

2
src/vfs/_vfscommon.vfs/modules/punk/mix/base-0.1.tm

@ -869,7 +869,7 @@ namespace eval punk::mix::base {
#todo - write tests #todo - write tests
if {([llength $args] % 2) != 0} { if {[llength $args] % 2} {
error "get_relativecksum_from_base error. args supplied must be in the form of key-value pairs. received '$args' " error "get_relativecksum_from_base error. args supplied must be in the form of key-value pairs. received '$args' "
} }
if {[dict exists $args cksum]} { if {[dict exists $args cksum]} {

2
src/vfs/_vfscommon.vfs/modules/punk/mix/templates/layouts/project/src/make.tcl

@ -424,7 +424,7 @@ if {$::punkmake::command eq "bootsupport"} {
} }
} }
if {[llength $bootsupport_module_folders] % 2 != 0} { if {[llength $bootsupport_module_folders] % 2} {
#todo - change include_modules.config structure to be line based? we have no way of verifying paired entries because we accept a flat list #todo - change include_modules.config structure to be line based? we have no way of verifying paired entries because we accept a flat list
puts stderr "WARNING - Skipping bootsupport_module_folders - list should be a list of base subpath pairs" puts stderr "WARNING - Skipping bootsupport_module_folders - list should be a list of base subpath pairs"
} else { } else {

2
src/vfs/_vfscommon.vfs/modules/punk/mix/util-0.1.0.tm

@ -51,7 +51,7 @@ namespace eval punk::mix::util {
} else { } else {
if {$ival in $knownopts} { if {$ival in $knownopts} {
#puts ">known at $i : [lindex $args $i]" #puts ">known at $i : [lindex $args $i]"
if {($i % 2) != 0} { if {$i % 2} {
error "unexpected option at index $i. known options: $knownopts must come in -opt val pairs." error "unexpected option at index $i. known options: $knownopts must come in -opt val pairs."
} }
incr i incr i

6
src/vfs/_vfscommon.vfs/modules/punk/nav/fs-0.1.0.tm

@ -1,6 +1,6 @@
# -*- tcl -*- # -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'pmix make' or bin/punkmake to update from <pkg>-buildversion.txt # Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm # module template: punkshell/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm
# #
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. # Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. # Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
@ -18,7 +18,7 @@
# doctools header # doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools #*** !doctools
#[manpage_begin shellspy_module_punk::nav::fs 0 0.1.0] #[manpage_begin punkshell_module_punk::nav::fs 0 0.1.0]
#[copyright "2024"] #[copyright "2024"]
#[titledesc {punk::nav::fs console filesystem navigation}] [comment {-- Name section and table of contents description --}] #[titledesc {punk::nav::fs console filesystem navigation}] [comment {-- Name section and table of contents description --}]
#[moddesc {fs nav}] [comment {-- Description at end of page heading --}] #[moddesc {fs nav}] [comment {-- Description at end of page heading --}]

2
src/vfs/_vfscommon.vfs/modules/punk/packagepreference-0.1.0.tm

@ -1,6 +1,6 @@
# -*- tcl -*- # -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt # Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm # module template: punkshell/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm
# #
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. # Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. # Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.

30
src/vfs/_vfscommon.vfs/modules/punk/repl-0.1.tm

@ -2675,9 +2675,11 @@ namespace eval repl {
set new_state [thread::send %replthread% [list punk::console::colour {*}$args]] set new_state [thread::send %replthread% [list punk::console::colour {*}$args]]
if {[expr {$new_state}] ne [expr {$colour_state}]} { if {[expr {$new_state}] ne [expr {$colour_state}]} {
interp eval code [list punk::console::colour {*}$args] ;#align code interp's view of colour state with repl thread interp eval code [list punk::console::colour {*}$args] ;#align code interp's view of colour state with repl thread
interp eval code [string map [list <cstate> $new_state] {
#we don't want to run a raw script directly in our code interp if we're using variables
#because we will potentially collide with user vars in that context (or create vars there) - so use apply
interp eval code [list apply {docolour {
#adjust channel transform stack #adjust channel transform stack
set docolour [expr {<cstate>}]
if {!$docolour} { if {!$docolour} {
set s [lindex $::codeinterp::outstack end] set s [lindex $::codeinterp::outstack end]
if {$s ne ""} { if {$s ne ""} {
@ -2697,7 +2699,7 @@ namespace eval repl {
} }
} }
}] }} $new_state]
} }
return $new_state return $new_state
} else { } else {
@ -2948,13 +2950,21 @@ namespace eval repl {
package require punk package require punk
package require shellrun package require shellrun
package require shellfilter package require shellfilter
set running_config $::punk::config::running #set running_config $::punk::config::running
if {[string length [dict get $running_config color_stderr]] && [punk::console::colour]} { #if {[string length [dict get $running_config color_stderr]] && [punk::console::colour]} {
lappend ::codeinterp::errstack [shellfilter::stack::add stderr ansiwrap -settings [list -colour [dict get $running_config color_stderr]]] # lappend ::codeinterp::errstack [shellfilter::stack::add stderr ansiwrap -settings [list -colour [dict get $running_config color_stderr]]]
} #}
if {[string length [dict get $running_config color_stdout]] && [punk::console::colour]} { #if {[string length [dict get $running_config color_stdout]] && [punk::console::colour]} {
lappend ::codeinterp::outstack [shellfilter::stack::add stdout ansiwrap -settings [list -colour [dict get $running_config color_stdout]]] # lappend ::codeinterp::outstack [shellfilter::stack::add stdout ansiwrap -settings [list -colour [dict get $running_config color_stdout]]]
} #}
apply {running_config {
if {[string length [dict get $running_config color_stderr]] && [punk::console::colour]} {
lappend ::codeinterp::errstack [shellfilter::stack::add stderr ansiwrap -settings [list -colour [dict get $running_config color_stderr]]]
}
if {[string length [dict get $running_config color_stdout]] && [punk::console::colour]} {
lappend ::codeinterp::outstack [shellfilter::stack::add stdout ansiwrap -settings [list -colour [dict get $running_config color_stdout]]]
}
}} $::punk::config::running
package require textblock package require textblock
} errM]} { } errM]} {

10
src/vfs/_vfscommon.vfs/modules/punk/repl/codethread-0.1.0.tm

@ -1,6 +1,6 @@
# -*- tcl -*- # -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'pmix make' or bin/punkmake to update from <pkg>-buildversion.txt # Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.2.tm # module template: punkshell/src/decktemplates/vendor/punk/modules/template_module-0.0.2.tm
# #
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem. # Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository. # Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
@ -18,7 +18,7 @@
# doctools header # doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools #*** !doctools
#[manpage_begin shellspy_module_punk::repl::codethread 0 0.1.0] #[manpage_begin punkshell_module_punk::repl::codethread 0 0.1.0]
#[copyright "2024"] #[copyright "2024"]
#[titledesc {Module repl codethread}] [comment {-- Name section and table of contents description --}] #[titledesc {Module repl codethread}] [comment {-- Name section and table of contents description --}]
#[moddesc {codethread for repl - root interpreter}] [comment {-- Description at end of page heading --}] #[moddesc {codethread for repl - root interpreter}] [comment {-- Description at end of page heading --}]
@ -66,10 +66,13 @@ package require punk::config
# oo::class namespace # oo::class namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#tcl::namespace::eval punk::repl::codethread::class { #tcl::namespace::eval punk::repl::codethread::class {
#*** !doctools #*** !doctools
#[subsection {Namespace punk::repl::codethread::class}] #[subsection {Namespace punk::repl::codethread::class}]
#[para] class definitions #[para] class definitions
#if {[info commands [tcl::namespace::current]::interface_sample1] eq ""} { #if {[info commands [tcl::namespace::current]::interface_sample1] eq ""} {
#*** !doctools #*** !doctools
#[list_begin enumerated] #[list_begin enumerated]
@ -91,6 +94,7 @@ package require punk::config
#*** !doctools #*** !doctools
#[list_end] [comment {--- end class enumeration ---}] #[list_end] [comment {--- end class enumeration ---}]
#} #}
#} #}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save