Browse Source

ansi and raw repl modes - ansi testfiles

master
Julian Noble 9 months ago
parent
commit
cab6dc2e0c
  1. 1
      src/bootsupport/include_modules.config
  2. 1756
      src/bootsupport/modules/overtype-1.5.9.tm
  3. 941
      src/bootsupport/modules/punk/ansi-0.1.1.tm
  4. 54
      src/bootsupport/modules/punk/args-0.1.0.tm
  5. 26
      src/bootsupport/modules/punk/cap/handlers/templates-0.1.0.tm
  6. 428
      src/bootsupport/modules/punk/char-0.1.0.tm
  7. 648
      src/bootsupport/modules/punk/console-0.1.1.tm
  8. 4
      src/bootsupport/modules/punk/encmime-0.1.0.tm
  9. 24
      src/bootsupport/modules/punk/fileline-0.1.0.tm
  10. 1847
      src/bootsupport/modules/punk/lib-0.1.1.tm
  11. 56
      src/bootsupport/modules/punk/mix/base-0.1.tm
  12. 8
      src/bootsupport/modules/punk/mix/commandset/layout-0.1.0.tm
  13. 4
      src/bootsupport/modules/punk/mix/commandset/project-0.1.0.tm
  14. 21
      src/bootsupport/modules/punk/mix/commandset/scriptwrap-0.1.0.tm
  15. 18
      src/bootsupport/modules/punk/ns-0.1.0.tm
  16. 14
      src/bootsupport/modules/punk/path-0.1.0.tm
  17. 22
      src/bootsupport/modules/punk/repo-0.1.1.tm
  18. 8
      src/bootsupport/modules/punkcheck-0.1.0.tm
  19. 1
      src/bootsupport/modules/textutil/wcswidth-35.1.tm
  20. 47
      src/modules/flagfilter-0.3.tm
  21. 264
      src/modules/punk-0.1.tm
  22. 1149
      src/modules/punk/ansi-999999.0a1.0.tm
  23. 50
      src/modules/punk/args-999999.0a1.0.tm
  24. 26
      src/modules/punk/cap/handlers/templates-999999.0a1.0.tm
  25. 424
      src/modules/punk/char-999999.0a1.0.tm
  26. 3
      src/modules/punk/config-0.1.tm
  27. 166
      src/modules/punk/console-999999.0a1.0.tm
  28. 15
      src/modules/punk/fileline-999999.0a1.0.tm
  29. 318
      src/modules/punk/lib-999999.0a1.0.tm
  30. 2
      src/modules/punk/lib-buildversion.txt
  31. 56
      src/modules/punk/mix/base-0.1.tm
  32. 73
      src/modules/punk/mix/commandset/scriptwrap-999999.0a1.0.tm
  33. 41
      src/modules/punk/ns-999999.0a1.0.tm
  34. 10
      src/modules/punk/path-999999.0a1.0.tm
  35. 318
      src/modules/punk/repl-0.1.tm
  36. 22
      src/modules/punk/repo-999999.0a1.0.tm
  37. 18
      src/modules/punk/timeinterval-999999.0a1.0.tm
  38. 35044
      src/modules/punk/uc-999999.0a1.0.tm
  39. 3
      src/modules/punk/uc-buildversion.txt
  40. 42
      src/modules/punkcheck-0.1.0.tm
  41. 14
      src/modules/punkcheck/cli-999999.0a1.0.tm
  42. 155
      src/modules/shellfilter-0.1.9.tm
  43. 8
      src/modules/shellrun-0.1.1.tm
  44. 159
      src/modules/textblock-999999.0a1.0.tm
  45. 76
      src/testansi/beastie.ans
  46. 72
      src/testansi/fish.ans
  47. 95
      src/testansi/flower.ans
  48. 201
      src/testansi/fruit.ans
  49. 20
      src/testansi/punk.ansi
  50. 1022
      src/vendormodules/overtype-1.5.8.tm
  51. 2194
      src/vendormodules/overtype-1.5.9.tm
  52. 1
      src/vendormodules/textutil/wcswidth-35.1.tm

1
src/bootsupport/include_modules.config

@ -10,6 +10,7 @@ set bootsupport_modules [list\
src/vendormodules fileutil\ src/vendormodules fileutil\
src/vendormodules textutil::tabify\ src/vendormodules textutil::tabify\
src/vendormodules textutil::split\ src/vendormodules textutil::split\
src/vendormodules textutil::wcswidth\
modules punkcheck\ modules punkcheck\
modules punk::ansi\ modules punk::ansi\
modules punk::args\ modules punk::args\

1756
src/bootsupport/modules/overtype-1.5.9.tm

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

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

@ -81,9 +81,9 @@
#[para] packages used by punk::args #[para] packages used by punk::args
#[list_begin itemized] #[list_begin itemized]
package require Tcl 8.6 package require Tcl 8.6-
#*** !doctools #*** !doctools
#[item] [package {Tcl 8.6}] #[item] [package {Tcl 8.6-}]
# #package require frobz # #package require frobz
# #*** !doctools # #*** !doctools
@ -174,7 +174,6 @@ namespace eval punk::args {
#} $args #} $args
set optionspecs [string map [list \r\n \n] $optionspecs] set optionspecs [string map [list \r\n \n] $optionspecs]
set known_argspecs [list -default -type -range -choices -nocase -optional -multiple -validate_without_ansi -allow_ansi -strip_ansi]
set optspec_defaults [dict create\ set optspec_defaults [dict create\
-optional 1\ -optional 1\
-allow_ansi 1\ -allow_ansi 1\
@ -204,11 +203,8 @@ namespace eval punk::args {
foreach ln $records { foreach ln $records {
set trimln [string trim $ln] set trimln [string trim $ln]
if {$trimln eq ""} { switch -- [string index $trimln 0] {
continue "" - # {continue}
}
if {[string index $trimln 0] eq "#"} {
continue
} }
set argname [lindex $trimln 0] set argname [lindex $trimln 0]
set argspecs [lrange $trimln 1 end] set argspecs [lrange $trimln 1 end]
@ -224,10 +220,15 @@ namespace eval punk::args {
error "punk::args::opts_values - bad optionspecs line for argument '$argname' Remaining items on line must be in paired option-value format - received '$argspecs'" error "punk::args::opts_values - bad optionspecs line for argument '$argname' Remaining items on line must be in paired option-value format - received '$argspecs'"
} }
dict for {spec specval} $argspecs { dict for {spec specval} $argspecs {
if {$spec ni [concat $known_argspecs -ARGTYPE]} { #literal-key switch - bytecompiled to jumpTable
switch -- $spec {
-default - -type - -range - -choices - -nocase - -optional - -multiple - -validate_without_ansi - -allow_ansi - -strip_ansi - -ARGTYPE {}
default {
set known_argspecs [list -default -type -range -choices -nocase -optional -multiple -validate_without_ansi -allow_ansi -strip_ansi]
error "punk::args::opts_values - unrecognised key '$spec' in specifications for argument '$argname' Known option specification keys: $known_argspecs" error "punk::args::opts_values - unrecognised key '$spec' in specifications for argument '$argname' Known option specification keys: $known_argspecs"
} }
} }
}
set argspecs [dict merge $optspec_defaults $argspecs] set argspecs [dict merge $optspec_defaults $argspecs]
dict set arg_info $argname $argspecs dict set arg_info $argname $argspecs
if {![dict get $argspecs -optional]} { if {![dict get $argspecs -optional]} {
@ -450,7 +451,10 @@ namespace eval punk::args {
if {!$is_default} { if {!$is_default} {
if {[dict exists $arg_info $o -type]} { if {[dict exists $arg_info $o -type]} {
set type [dict get $arg_info $o -type] set type [dict get $arg_info $o -type]
if {[string tolower $type] in {int integer double}} { switch -- [string tolower $type] {
int -
integer -
double {
if {[string tolower $type] in {int integer}} { if {[string tolower $type] in {int integer}} {
foreach e $vlist e_check $vlist_check { foreach e $vlist e_check $vlist_check {
if {![string is integer -strict $e_check]} { if {![string is integer -strict $e_check]} {
@ -474,19 +478,38 @@ namespace eval punk::args {
} }
} }
} }
} elseif {[string tolower $type] in {bool boolean}} { }
bool -
boolean {
foreach e $vlist e_check $vlist_check { foreach e $vlist e_check $vlist_check {
if {![string is boolean -strict $e_check]} { if {![string is boolean -strict $e_check]} {
error "Option $o for $caller requires type 'boolean'. Received: '$e'" error "Option $o for $caller requires type 'boolean'. Received: '$e'"
} }
} }
} elseif {[string tolower $type] in {alnum alpha ascii control digit graph lower print punct space upper wordchar xdigit}} { }
alnum -
alpha -
ascii -
control -
digit -
graph -
lower -
print -
punct -
space -
upper -
wordchar -
xdigit {
foreach e $vlist e_check $vlist_check { foreach e $vlist e_check $vlist_check {
if {![string is [string tolower $type] $e_check]} { if {![string is [string tolower $type] $e_check]} {
error "Option $o for $caller requires type '[string tolower $type]'. Received: '$e'" error "Option $o for $caller requires type '[string tolower $type]'. Received: '$e'"
} }
} }
} elseif {[string tolower $type] in {file directory existingfile existingdirectory}} { }
file -
directory -
existingfile -
existingdirectory {
foreach e $vlist e_check $vlist_check { foreach e $vlist e_check $vlist_check {
if {!([string length $e_check]>0 && ![regexp {[\"*?<>\;]} $e_check])} { if {!([string length $e_check]>0 && ![regexp {[\"*?<>\;]} $e_check])} {
error "Option $o for $caller requires type '[string tolower $type]'. Received: '$e' which doesn't look like it could be a file or directory" error "Option $o for $caller requires type '[string tolower $type]'. Received: '$e' which doesn't look like it could be a file or directory"
@ -505,7 +528,9 @@ namespace eval punk::args {
} }
} }
} }
} elseif {[string tolower $type] in {char character}} { }
char -
character {
foreach e $vlist e_check $vlist_check { foreach e $vlist e_check $vlist_check {
if {[string length != 1]} { if {[string length != 1]} {
error "Option $o for $caller requires type '[string tolower $type]'. Received: '$e' which is not a single character" error "Option $o for $caller requires type '[string tolower $type]'. Received: '$e' which is not a single character"
@ -513,6 +538,7 @@ namespace eval punk::args {
} }
} }
} }
}
if {[dict exists $arg_info $o -choices]} { if {[dict exists $arg_info $o -choices]} {
set choices [dict get $arg_info $o -choices] set choices [dict get $arg_info $o -choices]
set nocase [dict get $arg_info $o -nocase] set nocase [dict get $arg_info $o -nocase]

26
src/bootsupport/modules/punk/cap/handlers/templates-0.1.0.tm

@ -73,15 +73,16 @@ namespace eval punk::cap::handlers::templates {
#adhoc and currentproject* paths are relative to cwd - so no projectbase information can be stored at registration time. #adhoc and currentproject* paths are relative to cwd - so no projectbase information can be stored at registration time.
#not all template item types will need projectbase information - as the item data may be self-contained within the template structure - #not all template item types will need projectbase information - as the item data may be self-contained within the template structure -
#but project_layout will need it - or at least need to know if there is no project - because project_layout data is never stored in the template folder structure directly. #but project_layout will need it - or at least need to know if there is no project - because project_layout data is never stored in the template folder structure directly.
switch -- $pathtype {
if {$pathtype eq "adhoc"} { adhoc {
if {[file pathtype $path] ne "relative"} { if {[file pathtype $path] ne "relative"} {
puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but provided a path '$path' of type $pathtype which doesn't seem to be a relative path" puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but provided a path '$path' of type $pathtype which doesn't seem to be a relative path"
return 0 return 0
} }
set extended_capdict $capdict set extended_capdict $capdict
dict set extended_capdict vendor $vendor dict set extended_capdict vendor $vendor
} elseif {$pathtype eq "module"} { }
module {
set provide_statement [package ifneeded $pkg [package require $pkg]] set provide_statement [package ifneeded $pkg [package require $pkg]]
set tmfile [lindex $provide_statement end] set tmfile [lindex $provide_statement end]
if {![file exists $tmfile]} { if {![file exists $tmfile]} {
@ -103,7 +104,8 @@ namespace eval punk::cap::handlers::templates {
set resolved_path [file join $tmfolder $path] set resolved_path [file join $tmfolder $path]
dict set extended_capdict resolved_path $resolved_path dict set extended_capdict resolved_path $resolved_path
dict set extended_capdict projectbase $projectbase dict set extended_capdict projectbase $projectbase
} elseif {$pathtype eq "currentproject_multivendor"} { }
currentproject_multivendor {
#currently only intended for punk::mix::templates - review if 3rd party _multivendor trees even make sense #currently only intended for punk::mix::templates - review if 3rd party _multivendor trees even make sense
if {$pkg ni $multivendor_package_whitelist} { if {$pkg ni $multivendor_package_whitelist} {
puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but package is not in whitelist $multivendor_package_whitelist - 3rd party _multivendor tree not supported" puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but package is not in whitelist $multivendor_package_whitelist - 3rd party _multivendor tree not supported"
@ -116,7 +118,8 @@ namespace eval punk::cap::handlers::templates {
set extended_capdict $capdict set extended_capdict $capdict
dict set extended_capdict vendor $vendor ;#vendor key still required.. controlling vendor? dict set extended_capdict vendor $vendor ;#vendor key still required.. controlling vendor?
} elseif {$pathtype eq "currentproject"} { }
currentproject {
if {[file pathtype $path] ne "relative"} { if {[file pathtype $path] ne "relative"} {
puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but provided a path '$path' of type $pathtype which doesn't seem to be a relative path" puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but provided a path '$path' of type $pathtype which doesn't seem to be a relative path"
return 0 return 0
@ -127,7 +130,8 @@ namespace eval punk::cap::handlers::templates {
set extended_capdict $capdict set extended_capdict $capdict
dict set extended_capdict vendor $vendor dict set extended_capdict vendor $vendor
} elseif {$pathtype eq "shellproject"} { }
shellproject {
if {[file pathtype $path] ne "relative"} { if {[file pathtype $path] ne "relative"} {
puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but provided a path '$path' of type $pathtype which doesn't seem to be a relative path" puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but provided a path '$path' of type $pathtype which doesn't seem to be a relative path"
return 0 return 0
@ -139,7 +143,8 @@ namespace eval punk::cap::handlers::templates {
set extended_capdict $capdict set extended_capdict $capdict
dict set extended_capdict vendor $vendor dict set extended_capdict vendor $vendor
dict set extended_capdict projectbase $projectbase dict set extended_capdict projectbase $projectbase
} elseif {$pathtype eq "shellproject_multivendor"} { }
shellproject_multivendor {
#currently only intended for punk::templates - review if 3rd party _multivendor trees even make sense #currently only intended for punk::templates - review if 3rd party _multivendor trees even make sense
if {$pkg ni $multivendor_package_whitelist} { if {$pkg ni $multivendor_package_whitelist} {
puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but package is not in whitelist $multivendor_package_whitelist - 3rd party _multivendor tree not supported" puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but package is not in whitelist $multivendor_package_whitelist - 3rd party _multivendor tree not supported"
@ -156,7 +161,8 @@ namespace eval punk::cap::handlers::templates {
set extended_capdict $capdict set extended_capdict $capdict
dict set extended_capdict vendor $vendor dict set extended_capdict vendor $vendor
dict set extended_capdict projectbase $projectbase dict set extended_capdict projectbase $projectbase
} elseif {$pathtype eq "absolute"} { }
absolute {
if {[file pathtype $path] ne "absolute"} { if {[file pathtype $path] ne "absolute"} {
puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but provided a path '$path' of type $pathtype which doesn't seem to be absolute" puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but provided a path '$path' of type $pathtype which doesn't seem to be absolute"
return 0 return 0
@ -174,10 +180,12 @@ namespace eval punk::cap::handlers::templates {
dict set extended_capdict resolved_path $normpath dict set extended_capdict resolved_path $normpath
dict set extended_capdict vendor $vendor dict set extended_capdict vendor $vendor
dict set extended_capdict projectbase $projectbase dict set extended_capdict projectbase $projectbase
} else { }
default {
puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but provided a path '$path' with unrecognised type $pathtype" puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but provided a path '$path' with unrecognised type $pathtype"
return 0 return 0
} }
}
# -- --- --- --- --- --- --- ---- --- # -- --- --- --- --- --- --- ---- ---
# update package internal data # update package internal data

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

@ -55,7 +55,9 @@
#[item] [package console] #[item] [package console]
#[para] - #[para] -
package require Tcl 8.6 package require Tcl 8.6-
#dependency on tcllib not ideal for bootstrapping as punk::char is core to many features.. (textutil::wcswidth is therefore included in bootsupport/include_modules.config) review
package require textutil::wcswidth
#*** !doctools #*** !doctools
#[list_end] #[list_end]
@ -71,6 +73,7 @@ package require Tcl 8.6
namespace eval punk::char { namespace eval punk::char {
namespace export * namespace export *
variable grapheme_widths [dict create]
# -- -------------------------------------------------------------------------- # -- --------------------------------------------------------------------------
variable encmimens ;#namespace of mime package providing reversemapencoding and mapencoding functions variable encmimens ;#namespace of mime package providing reversemapencoding and mapencoding functions
#tcllib mime requires tcl::chan::memchan,events,core and/or Trf #tcllib mime requires tcl::chan::memchan,events,core and/or Trf
@ -525,7 +528,7 @@ namespace eval punk::char {
# e.g encoding convertto dingbats <somethingpretty> will output something that doesn't look dingbatty on screen. # e.g encoding convertto dingbats <somethingpretty> will output something that doesn't look dingbatty on screen.
#-- --- --- --- --- --- --- --- #-- --- --- --- --- --- --- ---
#must use Tcl instead of tcl (at least for 8.6) #must use Tcl instead of tcl (at least for 8.6)
if {![package vsatisfies [package present Tcl] 8.7]} { if {![package vsatisfies [package present Tcl] 8.7-]} {
proc encodable "s {enc [encoding system]}" { proc encodable "s {enc [encoding system]}" {
set encname [encname $enc] set encname [encname $enc]
if {($encname eq "ascii")} { if {($encname eq "ascii")} {
@ -1259,31 +1262,31 @@ namespace eval punk::char {
variable charsets variable charsets
set hex_char [format %04x $dec_char] set hex_char [format %04x $dec_char]
set returninfo [dict create] set returninfo [dict create]
if {"dec" in $fields} { foreach f $fields {
switch -- $f {
dec {
dict set returninfo dec $dec_char dict set returninfo dec $dec_char
} }
if {"hex" in $fields} { hex {
dict set returninfo hex $hex_char dict set returninfo hex $hex_char
} }
if {"desc" in $fields} { desc {
if {[dict exists $charinfo $dec_char desc]} { if {[dict exists $charinfo $dec_char desc]} {
dict set returninfo desc [dict get $charinfo $dec_char desc] dict set returninfo desc [dict get $charinfo $dec_char desc]
} else { } else {
dict set returninfo desc "" dict set returninfo desc ""
} }
} }
if {"short" in $fields} { short {
if {[dict exists $charinfo $dec_char short]} { if {[dict exists $charinfo $dec_char short]} {
dict set returninfo desc [dict get $charinfo $dec_char short] dict set returninfo desc [dict get $charinfo $dec_char short]
} else { } else {
dict set returninfo short "" dict set returninfo short ""
} }
} }
testwidth {
#todo - expectedwidth - lookup the printing width it is *supposed* to have from unicode tables #todo - expectedwidth - lookup the printing width it is *supposed* to have from unicode tables
#testwidth is one of the main ones likely to be worthwhile excluding as querying the console via ansi takes time #testwidth is one of the main ones likely to be worthwhile excluding as querying the console via ansi takes time
if {"testwidth" in $fields} {
set existing_testwidth "" set existing_testwidth ""
if {[dict exists $charinfo $dec_char testwidth]} { if {[dict exists $charinfo $dec_char testwidth]} {
set existing_testwidth [dict get $charinfo $dec_char testwidth] set existing_testwidth [dict get $charinfo $dec_char testwidth]
@ -1300,17 +1303,16 @@ namespace eval punk::char {
dict set returninfo testwidth $existing_testwidth dict set returninfo testwidth $existing_testwidth
} }
} }
if {"char" in $fields} { char {
set char [format %c $dec_char] set char [format %c $dec_char]
dict set returninfo char $char dict set returninfo char $char
} }
memberof {
#memberof takes in the order of a few hundred microseconds if a simple scan of all ranges is taken - possibly worthwhile caching/optimising #memberof takes in the order of a few hundred microseconds if a simple scan of all ranges is taken - possibly worthwhile caching/optimising
#note that memberof is not just unicode blocks - but scripts and other non-contiguous sets consisting of multiple ranges - some of which may include ranges of a single character. (e.g WGL4) #note that memberof is not just unicode blocks - but scripts and other non-contiguous sets consisting of multiple ranges - some of which may include ranges of a single character. (e.g WGL4)
#This means there probably isn't a really efficient means of calculating membership other than scanning all the defined ranges. #This means there probably isn't a really efficient means of calculating membership other than scanning all the defined ranges.
#We could potentially populate it using a side-thread - but it seems reasonable to just cache result after first use here. #We could potentially populate it using a side-thread - but it seems reasonable to just cache result after first use here.
#some efficiency could also be gained by pre-calculating the extents for each charset which isn't a simple unicode block. (and perhaps sorting by max char) #some efficiency could also be gained by pre-calculating the extents for each charset which isn't a simple unicode block. (and perhaps sorting by max char)
if {"memberof" in $fields} {
set memberof [list] set memberof [list]
dict for {setname setinfo} $charsets { dict for {setname setinfo} $charsets {
foreach r [dict get $setinfo ranges] { foreach r [dict get $setinfo ranges] {
@ -1324,6 +1326,8 @@ namespace eval punk::char {
} }
dict set returninfo memberof $memberof dict set returninfo memberof $memberof
} }
}
}
return $returninfo return $returninfo
} }
@ -1512,32 +1516,76 @@ namespace eval punk::char {
#non-overlapping unicode blocks #non-overlapping unicode blocks
proc char_blocks {name_or_glob} { proc char_blocks {{name_or_glob *}} {
error "unicode block searching unimplemented" variable charsets
#todo - search only charsets that have settype = block #todo - more efficient datastructures?
if {![regexp {[?*]} $name_or_glob]} {
#no glob - just retrieve it
if {[dict exists $charsets $name_or_glob]} {
if {[dict get $charsets $name_or_glob settype] eq "block"} {
return [dict create $name_or_glob [dict get $charsets $name_or_glob]]
}
}
#no exact match - try case insensitive..
set name [lsearch -inline -nocase [dict keys $charsets] $name_or_glob]
if {$name ne ""} {
if {[dict get $charsets $name settype] eq "block"} {
return [dict create $name [dict get $charsets $name]]
}
}
} else {
#build a subset
set charsets_block [dict create]
dict for {k v} $charsets {
if {[string match -nocase $name_or_glob $k]} {
if {[dict get $v settype] eq "block"} {
dict set charsets_block $k $v
}
}
}
return $charsets_block
}
}
proc charset_names {{name_or_glob *}} {
variable charsets
if {![regexp {[?*]} $name_or_glob]} {
#no glob - just retrieve it
if {[dict exists $charsets $name_or_glob]} {
return [list $name_or_glob]
}
#no exact match - try case insensitive..
set name [lsearch -inline -nocase [dict keys $charsets] $name_or_glob]
if {$name ne ""} {
return [list $name]
}
} else {
if {$name_or_glob eq "*"} {
return [lsort [dict keys $charsets]]
}
return [lsort [lsearch -all -inline -nocase [dict keys $charsets] $name_or_glob]]
}
} }
#deprecated
#major named sets such as unicode blocks, scripts, and other sets such as microsoft WGL4 #major named sets such as unicode blocks, scripts, and other sets such as microsoft WGL4
#case insensitive search - possibly with globs #case insensitive search - possibly with *basic* globs (? and * only - not lb rb)
proc charset_names {{namesearch *}} { proc charset_names2 {{namesearch *}} {
variable charsets variable charsets
set sortedkeys [lsort -increasing -dictionary [dict keys $charsets]] ;#NOTE must use -dictionary to use -sorted flag below #dictionary sorting of the keys is slow! - we should obviously store it in sorted order instead of sorting entire list on retrieval - or just sort results
#set sortedkeys [lsort -increasing -dictionary [dict keys $charsets]] ;#NOTE must use -dictionary to use -sorted flag below
set sortedkeys [lsort -increasing [dict keys $charsets]]
if {$namesearch eq "*"} { if {$namesearch eq "*"} {
return $sortedkeys return $sortedkeys
} }
if {[regexp {[?*]} $namesearch]} { if {[regexp {[?*]} $namesearch]} {
#name glob search #name glob search
set matched_names [lsearch -all -inline -nocase $sortedkeys $namesearch] ;#no point using -sorted flag when -all is used return [lsearch -all -inline -nocase $sortedkeys $namesearch] ;#no point using -sorted flag when -all is used
} else {
set matched [lsearch -sorted -inline -nocase $sortedkeys $namesearch] ;#no globs - stop on first match
if {[llength $matched]} {
return [list $matched]
} else { } else {
return [list] #return [lsearch -sorted -inline -nocase $sortedkeys $namesearch] ;#no globs - bails out earlier if -sorted?
return [lsearch -inline -nocase $sortedkeys $namesearch] ;#no globs
} }
} }
return $matched_names
}
proc charsets {{namesearch *}} { proc charsets {{namesearch *}} {
package require textblock package require textblock
variable charsets variable charsets
@ -1585,7 +1633,7 @@ namespace eval punk::char {
} }
set dict_list [list] set dict_list [list]
foreach m $matches { foreach m $matches {
lappend dict_list [dict create $m [charset_dictget $name]] lappend dict_list [dict create $m [charset_dictget $m]]
} }
#return $dict_list #return $dict_list
return [join $dict_list \n] return [join $dict_list \n]
@ -1651,7 +1699,8 @@ namespace eval punk::char {
set twidth [dict get $charinfo $dec testwidth] set twidth [dict get $charinfo $dec testwidth]
} }
if {$twidth eq ""} { if {$twidth eq ""} {
set width [string_width $ch] ;#based on unicode props #set width [ansifreestring_width $ch] ;#based on unicode props
set width [grapheme_width_cached $ch]
} else { } else {
set width $twidth set width $twidth
} }
@ -1780,7 +1829,7 @@ namespace eval punk::char {
} }
if {$twidth eq ""} { if {$twidth eq ""} {
#puts -nonewline stdout "." ;#this #puts -nonewline stdout "." ;#this
set width [char_info_testwidth $ch] ;#based on unicode props set width [char_info_testwidth $ch] ;#based on console test rather than unicode props
dict set charinfo $dec testwidth $width dict set charinfo $dec testwidth $width
} else { } else {
set width $twidth set width $twidth
@ -1792,32 +1841,164 @@ namespace eval punk::char {
puts stdout "\ncalibration done - results cached in charinfo dictionary" puts stdout "\ncalibration done - results cached in charinfo dictionary"
return [dict create charcount $charcount widths $width_results] return [dict create charcount $charcount widths $width_results]
} }
#prerequisites - no ansi escapes - no newlines
#review - what about \r \t \b ?
proc string_width {text} {
#review is detecting \033 enough? what about 8-bit escapes?
#maint warning - also in overtype!
#intended for single grapheme - but will work for multiple
#cannot contain ansi or newlines
#(a cache of ansifreestring_width calls - as these are quite regex heavy)
proc grapheme_width_cached {ch} {
variable grapheme_widths
if {[dict exists $grapheme_widths $ch]} {
return [dict get $grapheme_widths $ch]
}
set width [punk::char::ansifreestring_width $ch] ;#review - can we provide faster version if we know it's a single grapheme rather than a string? (grapheme is still a string as it may have combiners/diacritics)
dict set grapheme_widths $ch $width
return $width
}
#no char_width - use grapheme_width terminology to be clearer
proc grapheme_width {char} {
error "grapheme_width unimplemented - use ansifreestring_width"
}
#return N Na W etc from unicode data
#review
proc char_uc_width_prop {char} {
error "char_uc_width unimplemented try textutil::wcswidth_type"
}
#todo - provide a grapheme_width function that is optimised for speed
proc string_width {text} {
#burn approx 2uS (2024) checking for ansi codes - not just SGR
if {[punk::ansi::ta::detect $text]} {
puts stderr "string_width detected ANSI!"
}
if {[string first \n $text] >= 0} { if {[string first \n $text] >= 0} {
error "string_width accepts only a single line" error "string_width accepts only a single line"
} }
tailcall ansifreestring_width $text
}
#faster than textutil::wcswidth (at least for string up to a few K in length)
proc wcswidth {string} {
set codes [scan $string [string repeat %c [string length $string]]]
set width 0
foreach c $codes {
set w [textutil::wcswidth_char $c]
if {$w < 0} {
return -1
} else {
incr width $w
}
}
return $width
}
proc wcswidth2 {string} {
set codes [scan $string [string repeat %c [string length $string]]]
set widths [lmap c $codes {textutil::wcswidth_char $c}]
if {-1 in $widths} {
return -1
}
return [tcl::mathop::+ {*}$widths]
}
#prerequisites - no ansi escapes - no newlines
#review - what about \r \t \b ?
#NO processing of \b - already handled in ansi::printing_length which then calls this
#this version breaks string into sequences of ascii vs unicode
proc ansifreestring_width {text} {
#caller responsible for calling ansistrip first if text may have ansi codes - and for ensuring no newlines
#we can c0 control characters after or while processing ansi escapes. #we can c0 control characters after or while processing ansi escapes.
#we need to map remaining control characters to zero-width (under assumption we're not using a font/codepage that displays them - review!) #we need to map remaining control characters to zero-width (under assumption we're not using a font/codepage that displays them - review!)
#anyway - we must allow things like raw ESC,DEL, NUL etc to pass through without error #anyway - we must allow things like raw ESC,DEL, NUL etc to pass through without error
#if {[string first \033 $text] >= 0} { #if {[string first \033 $text] >= 0} {
# error "string_width doesn't accept ansi escape sequences. Use punk::ansi::stripansi first" # error "string_width doesn't accept ansi escape sequences. Use punk::ansi::stripansi first"
#} #}
#todo - various combining diacritical marks.. from grave - to various complicated unicode joiners and composing chars etc
#as at 2023 - terminals generally seem to use the simplistic approach of tallying up individual character-widths, which means combinations that print short are reported too long by the terminal esc 6 n sequence.
#- {Combining Diacritical Marks} {ranges {{start 768 end 879 note {unicode block 0300..036F}}} description {} settype block}
#- {Combining Diacritical Marks Extended} {ranges {{start 6832 end 6911 note {unicode block 1AB0..1AFF}}} description {} settype block}
#- {Combining Diacritical Marks Supplement} {ranges {{start 7616 end 7679 note {unicode block 1DC0..1DFF}}} description {} settype block}
#- {Combining Diacritical Marks for Symbols} {ranges {{start 8400 end 8447 note {unicode block 20D0..20FF}}} description {} settype block}
#- {Combining Half Marks} {ranges {{start 65056 end 65071 note {unicode block FE20..FE2F}}} description {} settype block}
#
# initial simplistic approach is just to strip these ... todo REVIEW
#experiment to detect leading diacritics - but this isn't necessary - it's still zero-width - and if the user is splitting properly we shouldn't be getting a string with leading diacritics anyway
#(leading combiners may display in terminal as mark on rightmost prompt char which is usually a space - but won't add width even then)
#set re_leading_diacritic {^(?:[\u0300-\u036f]+|[\u1ab0-\u1aff]+|[\u1dc0-\u1dff]+|[\u20d0-\u20ff]+|[\ufe20-\ufe2f]+)}
#if {[regexp $re_leading_diacritic $text]} {
# set text " $text"
#}
set re_diacritics {[\u0300-\u036f]+|[\u1ab0-\u1aff]+|[\u1dc0-\u1dff]+|[\u20d0-\u20ff]+|[\ufe20-\ufe2f]+}
set text [regsub -all $re_diacritics $text ""]
#review
#if we strip out ZWJ \u0200d (zero width combiner) - then we don't give the chance for a grapheme combining test to merge properly e.g combining emojis
#as at 2024 - textutil::wcswidth doesn't seem to be aware of these - and counts the joiner as one wide
#ZWNJ \u0200c should also be zero length - but as a 'non' joiner - it should add zero to the length
#ZWSP \u0200b zero width space
#we should only map control sequences to nothing after processing ones with length effects, such as \b (\x07f) or DEL \x1f
#todo - document that these shouldn't be present in input rather than explicitly checking here
set re_ascii_c0 {[\U0000-\U001F]} set re_ascii_c0 {[\U0000-\U001F]}
set text [regsub -all $re_ascii_c0 $text ""] set text [regsub -all $re_ascii_c0 $text ""]
#todo - check double-width chars in unicode blocks.. try to do reasonably quicky
#short-circuit basic cases #short-circuit basic cases
if {![regexp {[\uFF-\U10FFFF]} $text]} { #support tcl pre 2023-11 - see regexp bug below
#control chars? #if {![regexp {[\uFF-\U10FFFF]} $text]} {
# return [string length $text]
#}
if {![regexp "\[\uFF-\U10FFFF\]" $text]} {
return [string length $text] return [string length $text]
} }
#split just to get the standalone character widths - and then scan for other combiners (?) - or scan for clusters first?
#review
#set can_regex_high_unicode [string match [regexp -all -inline {[\U80-\U0010FFFF]} \U0001F525] \U0001F525]
#tcl pre 2023-11 - braced high unicode regexes don't work
#fixed in bug-4ed788c618 2023-11
#set uc_chars [regexp -all -inline "\[\u0100-\U10FFFF\]" $text] ;#e.g return list of chars in range only
#maintain unicode as sequences - todo - scan for grapheme clusters
#set uc_sequences [punk::ansi::ta::_perlish_split "(?:\[\u0100-\U10FFFF\])+" $text]
set uc_sequences [punk::ansi::ta::_perlish_split "(?:\[\u0000-\u00FF\])+" $text]
set len 0
foreach {uc ascii} $uc_sequences {
#puts "-ascii $ascii"
#puts "-uc $uc"
incr len [string length $ascii]
#textutil::wcswidth uses unicode data
#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
#use punk::char::wcswidth - faster than the string split in textutil::wcswidth but still uses textutil::wcswidth_char
incr len [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.
return $len
}
#kept as a fallback for review/test if textutil::wcswidth doesn't do what we require on all terminals.
#this version looks at console testwidths if they've been cached.
#It is relatively fast - but tests unicode widths char by char - so won't be useful going forward for grapheme clusters.
proc ansifreestring_width2 {text} {
#caller responsible for calling ansistrip first if text may have ansi codes - and for ensuring no newlines
#we can c0 control characters after or while processing ansi escapes.
#we need to map remaining control characters to zero-width (under assumption we're not using a font/codepage that displays them - review!)
#anyway - we must allow things like raw ESC,DEL, NUL etc to pass through without error
#if {[string first \033 $text] >= 0} {
# error "string_width doesn't accept ansi escape sequences. Use punk::ansi::stripansi first"
#}
#todo - various combining diacritical marks.. from grave - to various complicated unicode joiners and composing chars etc #todo - various combining diacritical marks.. from grave - to various complicated unicode joiners and composing chars etc
#as at 2023 - terminals generally seem to use the simplistic approach of tallying up individual character-widths, which means combinations that print short are reported too long by the terminal esc 6 n sequence. #as at 2023 - terminals generally seem to use the simplistic approach of tallying up individual character-widths, which means combinations that print short are reported too long by the terminal esc 6 n sequence.
@ -1831,7 +2012,7 @@ namespace eval punk::char {
# #
# initial simplistic approach is just to strip these ... todo REVIEW # initial simplistic approach is just to strip these ... todo REVIEW
#experiment to detect leading diacritics - but this isn't necessary - it's still zero-width #experiment to detect leading diacritics - but this isn't necessary - it's still zero-width - and if the user is splitting properly we shouldn't be getting a string with leading diacritics anyway
#(leading combiners may display in terminal as mark on rightmost prompt char which is usually a space - but won't add width even then) #(leading combiners may display in terminal as mark on rightmost prompt char which is usually a space - but won't add width even then)
#set re_leading_diacritic {^(?:[\u0300-\u036f]+|[\u1ab0-\u1aff]+|[\u1dc0-\u1dff]+|[\u20d0-\u20ff]+|[\ufe20-\ufe2f]+)} #set re_leading_diacritic {^(?:[\u0300-\u036f]+|[\u1ab0-\u1aff]+|[\u1dc0-\u1dff]+|[\u20d0-\u20ff]+|[\ufe20-\ufe2f]+)}
#if {[regexp $re_leading_diacritic $text]} { #if {[regexp $re_leading_diacritic $text]} {
@ -1840,7 +2021,30 @@ namespace eval punk::char {
set re_diacritics {[\u0300-\u036f]+|[\u1ab0-\u1aff]+|[\u1dc0-\u1dff]+|[\u20d0-\u20ff]+|[\ufe20-\ufe2f]+} set re_diacritics {[\u0300-\u036f]+|[\u1ab0-\u1aff]+|[\u1dc0-\u1dff]+|[\u20d0-\u20ff]+|[\ufe20-\ufe2f]+}
set text [regsub -all $re_diacritics $text ""] set text [regsub -all $re_diacritics $text ""]
#review
#if we strip out ZWJ \u0200d (zero width combiner) - then we don't give the chance for a grapheme combining test to merge properly e.g combining emojis
#as at 2024 - textutil::wcswidth doesn't seem to be aware of these - and counts the joiner as one wide
#ZWNJ \u0200c should also be zero length - but as a 'non' joiner - it should add zero to the length
#ZWSP \u0200b zero width space
#we should only map control sequences to nothing after processing ones with length effects, such as \b (\x07f) or DEL \x1f
#todo - document that these shouldn't be present in input rather than explicitly checking here
set re_ascii_c0 {[\U0000-\U001F]}
set text [regsub -all $re_ascii_c0 $text ""]
#short-circuit basic cases
#support tcl pre 2023-11 - see regexp bug below
#if {![regexp {[\uFF-\U10FFFF]} $text]} {
# return [string length $text]
#}
if {![regexp "\[\uFF-\U10FFFF\]" $text]} {
return [string length $text]
}
#review - wcswidth should detect these
set re_ascii_fullwidth {[\uFF01-\uFF5e]} set re_ascii_fullwidth {[\uFF01-\uFF5e]}
set doublewidth_char_count 0 set doublewidth_char_count 0
@ -1851,15 +2055,30 @@ namespace eval punk::char {
#tcl pre 2023-11 - braced high unicode regexes don't work #tcl pre 2023-11 - braced high unicode regexes don't work
#fixed in bug-4ed788c618 2023-11 #fixed in bug-4ed788c618 2023-11
#set uc_sequences [regexp -all -inline -indices {[\u0100-\U10FFFF]} $text] #set uc_sequences [regexp -all -inline -indices {[\u0100-\U10FFFF]} $text]
set uc_sequences [regexp -all -inline -indices "\[\u0100-\U10FFFF\]" $text] #set uc_sequences [regexp -all -inline -indices "\[\u0100-\U10FFFF\]" $text] ;#e.g for string: \U0001f9d1abc\U001f525ab returns {0 0} {4 4}
foreach uc_range $uc_sequences { set uc_chars [regexp -all -inline "\[\u0100-\U10FFFF\]" $text] ;#e.g return list of chars in range only
set chars [string range $text {*}$uc_range] foreach c $uc_chars {
foreach c $chars {
if {[regexp $re_ascii_fullwidth $c]} { if {[regexp $re_ascii_fullwidth $c]} {
incr doublewidth_char_count incr doublewidth_char_count
} else { } else {
#todo - replace with function that doesn't use console - just unicode data #review
# a)- terminals lie - so we could have a bad cached testwidth
# b)- textutil::wcswidth_char seems to be east-asian-width based - and not a reliable indicator of 'character cells taken by the character when printed to the terminal' despite this statement in tclllib docs.
#(character width is a complex context-dependent topic)
# c) by checking for a cached console testwidth first - we make this function less deterministic/repeatable depending on whether console tests have been run.
# d) Upstream caching of grapheme_width may also lock in a result from whatever method was first employed here
#Despite all this - the mechanism is hoped to give best effort consistency for terminals
#further work needed for combining emojis etc - which can't be done in a per character loop
#todo - see if wcswidth does any of this. It is very slow for strings that include mixed ascii/unicode - so perhaps we can use a perlish_split
# to process sequences of unicode.
#- and the user has the option to test character sets first if terminal position reporting gives better results
if {[char_info_is_testwidth_cached $c]} {
set width [char_info_testwidth_cached $c] set width [char_info_testwidth_cached $c]
} else {
#textutil::wcswidth uses unicode data
#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)
set width [textutil::wcswidth_char [scan $c %c]]
}
if {$width == 0} { if {$width == 0} {
incr zerowidth_char_count incr zerowidth_char_count
} elseif {$width == 2} { } elseif {$width == 2} {
@ -1867,11 +2086,62 @@ namespace eval punk::char {
} }
} }
} }
}
#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 [expr {[string length $text] + $doublewidth_char_count - $zerowidth_char_count}] return [expr {[string length $text] + $doublewidth_char_count - $zerowidth_char_count}]
} }
#slow - textutil::wcswidth is slow with mixed ascii uc
proc ansifreestring_width3 {text} {
#caller responsible for calling ansistrip first if text may have ansi codes - and for ensuring no newlines
#we can c0 control characters after or while processing ansi escapes.
#we need to map remaining control characters to zero-width (under assumption we're not using a font/codepage that displays them - review!)
#anyway - we must allow things like raw ESC,DEL, NUL etc to pass through without error
#if {[string first \033 $text] >= 0} {
# error "string_width doesn't accept ansi escape sequences. Use punk::ansi::stripansi first"
#}
#review - 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.
# - various combining diacritical marks.. from grave - to various complicated unicode joiners and composing chars etc
#as at 2023 - terminals generally seem to use the simplistic approach of tallying up individual character-widths, which means combinations that print short are reported too long by the terminal esc 6 n sequence.
#- {Combining Diacritical Marks} {ranges {{start 768 end 879 note {unicode block 0300..036F}}} description {} settype block}
#- {Combining Diacritical Marks Extended} {ranges {{start 6832 end 6911 note {unicode block 1AB0..1AFF}}} description {} settype block}
#- {Combining Diacritical Marks Supplement} {ranges {{start 7616 end 7679 note {unicode block 1DC0..1DFF}}} description {} settype block}
#- {Combining Diacritical Marks for Symbols} {ranges {{start 8400 end 8447 note {unicode block 20D0..20FF}}} description {} settype block}
#- {Combining Half Marks} {ranges {{start 65056 end 65071 note {unicode block FE20..FE2F}}} description {} settype block}
#
# initial simplistic approach is just to strip these ... todo REVIEW
#experiment to detect leading diacritics - but this isn't necessary - it's still zero-width - and if the user is splitting properly we shouldn't be getting a string with leading diacritics anyway
#(leading combiners may display in terminal as mark on rightmost prompt char which is usually a space - but won't add width even then)
#set re_leading_diacritic {^(?:[\u0300-\u036f]+|[\u1ab0-\u1aff]+|[\u1dc0-\u1dff]+|[\u20d0-\u20ff]+|[\ufe20-\ufe2f]+)}
#if {[regexp $re_leading_diacritic $text]} {
# set text " $text"
#}
set re_diacritics {[\u0300-\u036f]+|[\u1ab0-\u1aff]+|[\u1dc0-\u1dff]+|[\u20d0-\u20ff]+|[\ufe20-\ufe2f]+}
set text [regsub -all $re_diacritics $text ""]
#we should only map control sequences to nothing after processing ones with length effects, such as \b (\x07f) or DEL \x1f
#todo - document that these shouldn't be present in input rather than explicitly checking here
set re_ascii_c0 {[\U0000-\U001F]}
set text [regsub -all $re_ascii_c0 $text ""]
#short-circuit basic cases
#support tcl pre 2023-11 - see regexp bug below
#if {![regexp {[\uFF-\U10FFFF]} $text]} {
# return [string length $text]
#}
if {![regexp "\[\uFF-\U10FFFF\]" $text]} {
return [string length $text]
}
#slow when ascii mixed with unicode (but why?)
return [punk::wcswidth $text]
}
#This shouldn't be called on text containing ansi codes! #This shouldn't be called on text containing ansi codes!
proc strip_nonprinting_ascii {str} { proc strip_nonprinting_ascii {str} {
#review - some single-byte 'control' chars have visual representations e.g ETX as heart depending on font/codepage #review - some single-byte 'control' chars have visual representations e.g ETX as heart depending on font/codepage
@ -1885,22 +2155,14 @@ namespace eval punk::char {
return [string map $map $str] return [string map $map $str]
} }
proc char_width {char} {
error "char_width unimplemented"
}
#return N Na W etc from unicode data
proc char_uc_width_prop {char} {
error "char_uc_width unimplemented"
}
#split into plaintext and runs of combiners #split into plaintext and runs of combiners (combining diacritical marks - not ZWJ or ZWJNJ)
proc combiner_split {text} { proc combiner_split {text} {
#split into odd numbered list (or zero) in a similar way to punk::ansi::ta::_perlish_split #split into odd numbered list (or zero) in a similar way to punk::ansi::ta::_perlish_split
# #
set re_diacritics {[\u0300-\u036f]+|[\u1ab0-\u1aff]+|[\u1dc0-\u1dff]+|[\u20d0-\u20ff]+|[\ufe20-\ufe2f]+} #set re_diacritics {[\u0300-\u036f]+|[\u1ab0-\u1aff]+|[\u1dc0-\u1dff]+|[\u20d0-\u20ff]+|[\ufe20-\ufe2f]+}
set graphemes [list] set graphemes [list]
set g ""
if {[string length $text] == 0} { if {[string length $text] == 0} {
return {} return {}
} }
@ -1909,7 +2171,7 @@ namespace eval punk::char {
set strlen [string length $text] set strlen [string length $text]
#make sure our regexes aren't non-greedy - or we may not have exit condition for loop #make sure our regexes aren't non-greedy - or we may not have exit condition for loop
#review #review
while {$start < $strlen && [regexp -start $start -indices -- $re_diacritics $text match]} { while {$start < $strlen && [regexp -start $start -indices -- {[\u0300-\u036f]+|[\u1ab0-\u1aff]+|[\u1dc0-\u1dff]+|[\u20d0-\u20ff]+|[\ufe20-\ufe2f]+} $text match]} {
lassign $match matchStart matchEnd lassign $match matchStart matchEnd
#puts "->start $start ->match $matchStart $matchEnd" #puts "->start $start ->match $matchStart $matchEnd"
lappend list [string range $text $start $matchStart-1] [string range $text $matchStart $matchEnd] lappend list [string range $text $start $matchStart-1] [string range $text $matchStart $matchEnd]
@ -1920,13 +2182,19 @@ namespace eval punk::char {
#} #}
} }
lappend list [string range $text $start end] lappend list [string range $text $start end]
return $list
} }
#ZWJ ZWNJ ?
#1st shot - basic diacritics #1st shot - basic diacritics
#todo - become aware of unicode grapheme cluster boundaries #todo - become aware of unicode grapheme cluster boundaries
# #This is difficult in Tcl without unicode property based Character Classes in the regex engine
#review - this needs to be performant - it is used a lot by punk terminal/ansi features
#todo - trie data structures for unicode?
#for now we can at least combine diacritics
#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)
#This still leaves a whole class of clusters.. korean etc unhandled :/
proc grapheme_split {text} { proc grapheme_split {text} {
set graphemes [list] set graphemes [list]
set csplits [combiner_split $text] set csplits [combiner_split $text]
@ -1941,9 +2209,51 @@ namespace eval punk::char {
} }
return $graphemes return $graphemes
} }
proc grapheme_split_dec {text} {
set graphemes [list]
set csplits [combiner_split $text]
foreach {pt combiners} [lrange $csplits 0 end-1] {
set pt_decs [scan $pt [string repeat %c [string length $pt]]]
set combiner_decs [scan $combiners [string repeat %c [string length $combiners]]]
lset pt_decs end [concat [lindex $pt_decs end] $combiner_decs]
lappend graphemes {*}$pt_decs
}
#last csplit never has a combiner (_perlish_split style) - and may be empty - in which case we don't append it as a grapheme
if {[lindex $csplits end] ne ""} {
lappend graphemes {*}[scan [lindex $csplits end] [string repeat %c [string length [lindex $csplits end]]]]
}
return $graphemes
}
proc grapheme_split_dec2 {text} {
set graphemes [list]
set csplits [combiner_split $text]
foreach {pt combiners} $csplits {
set pt_decs [scan $pt [string repeat %c [string length $pt]]]
if {$combiners ne ""} {
set combiner_decs [scan $combiners [string repeat %c [string length $combiners]]]
lset pt_decs end [concat [lindex $pt_decs end] $combiner_decs]
}
lappend graphemes {*}$pt_decs
}
return $graphemes
}
proc grapheme_split2 {text} {
set graphemes [list]
set csplits [combiner_split $text]
foreach {pt combiners} [lrange $csplits 0 end-1] {
set clist [split $pt ""]
lappend graphemes {*}[lrange $clist 0 end-1] [string cat [lindex $clist end] $combiners]
}
#last csplit never has a combiner (_perlish_split style) - and may be empty - in which case we don't append it as a grapheme
if {[lindex $csplits end] ne ""} {
lappend graphemes {*}[split [lindex $csplits end] ""]
}
return $graphemes
}
# -- --- --- --- --- # -- --- --- --- ---
#will accept a single char or a string - test using console cursor position reporting #will accept a single char or a string - test using console cursor position reporting
#unreliable
proc char_info_testwidth {ch {emit 0}} { proc char_info_testwidth {ch {emit 0}} {
package require punk::console package require punk::console
#uses cursor movement and relies on console to report position.. which it may do properly for single chars - but may misreport for combinations that combine into a single glyph #uses cursor movement and relies on console to report position.. which it may do properly for single chars - but may misreport for combinations that combine into a single glyph
@ -1964,6 +2274,10 @@ namespace eval punk::char {
return $twidth return $twidth
} }
} }
proc char_info_is_testwidth_cached {char} {
variable charinfo
return [dict exists $charinfo [scan $char %c] testwidth]
}
# -- --- --- --- --- # -- --- --- --- ---

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

@ -34,8 +34,20 @@ namespace eval punk::console {
variable previous_stty_state_stdin "" variable previous_stty_state_stdin ""
variable previous_stty_state_stdout "" variable previous_stty_state_stdout ""
variable previous_stty_state_stderr "" variable previous_stty_state_stderr ""
variable is_raw 0 variable is_raw 0
variable input_chunks_waiting
if {![info exists input_chunks_waiting(stdin)]} {
set input_chunks_waiting(stdin) [list]
}
# --
variable ansi_available -1 ;#default -1 for unknown. Leave it this way so test for ansi support is run.
#-1 still evaluates to true - as the modern assumption for ansi availability is true
#only false if ansi_available has been set 0 by test_can_ansi
#support stripansi for legacy windows terminals
# --
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.
@ -55,41 +67,61 @@ namespace eval punk::console {
} }
if {"windows" eq $::tcl_platform(platform)} { if {"windows" eq $::tcl_platform(platform)} {
proc enableAnsi {} { #accept args for all dummy/load functions so we don't have to match/update argument signatures here
proc enableAnsi {args} {
#loopavoidancetoken (don't remove) #loopavoidancetoken (don't remove)
internal::define_windows_procs internal::define_windows_procs
internal::abort_if_loop internal::abort_if_loop
tailcall enableAnsi tailcall enableAnsi {*}$args
} }
#review what raw mode means with regard to a specific channel vs terminal as a whole #review what raw mode means with regard to a specific channel vs terminal as a whole
proc enableRaw {{channel stdin}} { proc enableRaw {args} {
#loopavoidancetoken (don't remove) #loopavoidancetoken (don't remove)
internal::define_windows_procs internal::define_windows_procs
internal::abort_if_loop internal::abort_if_loop
tailcall enableRaw $channel tailcall enableRaw {*}$args
} }
proc disableRaw {{channel stdin}} { proc disableRaw {args} {
#loopavoidancetoken (don't remove) #loopavoidancetoken (don't remove)
internal::define_windows_procs internal::define_windows_procs
internal::abort_if_loop internal::abort_if_loop
tailcall disableRaw $channel tailcall disableRaw {*}$args
} }
proc enableVirtualTerminal {} { proc enableVirtualTerminal {args} {
#loopavoidancetoken (don't remove) #loopavoidancetoken (don't remove)
internal::define_windows_procs internal::define_windows_procs
internal::abort_if_loop internal::abort_if_loop
tailcall enableVirtualTerminal tailcall enableVirtualTerminal {*}$args
} }
proc disableVirtualTerminal {} { proc disableVirtualTerminal {args} {
#loopavoidancetoken (don't remove) #loopavoidancetoken (don't remove)
internal::define_windows_procs internal::define_windows_procs
internal::abort_if_loop internal::abort_if_loop
tailcall disableVirtualTerminal tailcall disableVirtualTerminal {*}$args
} }
set funcs [list disableAnsi enableProcessedInput disableProcessedInput]
foreach f $funcs {
proc $f {args} [string map [list %f% $f] {
set mybody [info body %f%]
internal::define_windows_procs
set newbody [info body %f%]
if {$newbody ne $mybody} {
tailcall %f% {*}$args
} else {
#error vs noop?
puts stderr "Unable to set implementation for %f% - check twapi?"
}
}]
}
} else { } else {
proc enableAnsi {} { proc enableAnsi {} {
#todo? #todo?
} }
proc disableAnsi {} {
}
#todo - something better - the 'channel' concept may not really apply on unix, as raw mode is for input and output modes #todo - something better - the 'channel' concept may not really apply on unix, as raw mode is for input and output modes
proc enableRaw {{channel stdin}} { proc enableRaw {{channel stdin}} {
@ -118,14 +150,15 @@ namespace eval punk::console {
set is_raw 0 set is_raw 0
return done return done
} }
proc enableVirtualTerminal {} { proc enableVirtualTerminal {{channels {input output}}} {
} }
proc disableVirtualTerminal {} { proc disableVirtualTerminal {args} {
} }
} }
#review - document and decide granularity required. should we enable/disable more than one at once?
proc enable_mouse {} { proc enable_mouse {} {
puts -nonewline stdout \x1b\[?1000h puts -nonewline stdout \x1b\[?1000h
puts -nonewline stdout \x1b\[?1003h puts -nonewline stdout \x1b\[?1003h
@ -157,6 +190,7 @@ namespace eval punk::console {
} }
proc mode {{raw_or_line query}} { proc mode {{raw_or_line query}} {
variable is_raw variable is_raw
variable ansi_available
set raw_or_line [string tolower $raw_or_line] set raw_or_line [string tolower $raw_or_line]
if {$raw_or_line eq "query"} { if {$raw_or_line eq "query"} {
if {$is_raw} { if {$is_raw} {
@ -166,14 +200,18 @@ namespace eval punk::console {
} }
} elseif {$raw_or_line eq "raw"} { } elseif {$raw_or_line eq "raw"} {
punk::console::enableRaw punk::console::enableRaw
if {[can_ansi]} {
punk::console::enableVirtualTerminal both punk::console::enableVirtualTerminal both
}
} elseif {$raw_or_line eq "line"} { } elseif {$raw_or_line eq "line"} {
#review -order. disableRaw has memory from enableRaw.. but but for line mode we want vt disabled - so call it after disableRaw (?) #review -order. disableRaw has memory from enableRaw.. but but for line mode we want vt disabled - so call it after disableRaw (?)
punk::console::disableRaw punk::console::disableRaw
if {[can_ansi]} {
punk::console::disableVirtualTerminal input ;#default readline arrow behaviour etc punk::console::disableVirtualTerminal input ;#default readline arrow behaviour etc
punk::console::enableVirtualTerminal output ;#display/use ansi codes punk::console::enableVirtualTerminal output ;#display/use ansi codes
}
} else { } else {
error "punk::console::mode expected 'raw' or 'line' or default value 'query' error "punk::console::mode expected 'raw' or 'line' or default value 'query'"
} }
} }
@ -211,6 +249,7 @@ namespace eval punk::console {
#enableAnsi seems like it should be directly under punk::console .. but then it seems inconsistent if other local console-mode setting functions aren't. #enableAnsi seems like it should be directly under punk::console .. but then it seems inconsistent if other local console-mode setting functions aren't.
#Find a compromise to organise things somewhat sensibly.. #Find a compromise to organise things somewhat sensibly..
#this is really enableAnsi *processing*
proc [namespace parent]::enableAnsi {} { proc [namespace parent]::enableAnsi {} {
#output handle modes #output handle modes
#Enable virtual terminal processing (sometimes off in older windows terminals) #Enable virtual terminal processing (sometimes off in older windows terminals)
@ -220,12 +259,13 @@ namespace eval punk::console {
#DISABLE_NEWLINE_AUTO_RETURN = 0x0008 #DISABLE_NEWLINE_AUTO_RETURN = 0x0008
set h_out [twapi::get_console_handle stdout] set h_out [twapi::get_console_handle stdout]
set oldmode_out [twapi::GetConsoleMode $h_out] set oldmode_out [twapi::GetConsoleMode $h_out]
set newmode_out [expr {$oldmode_out | 5}] ;#5? set newmode_out [expr {$oldmode_out | 4}] ;#don't enable processed output too, even though it's required. keep symmetrical with disableAnsi?
twapi::SetConsoleMode $h_out $newmode_out twapi::SetConsoleMode $h_out $newmode_out
#what does window_input have to do with it??
#input handle modes #input handle modes
#ENABLE_PROCESSED_INPUT 0x0001 #ENABLE_PROCESSED_INPUT 0x0001 ;#set to zero will allow ctrl-c to be reported as keyboard input rather than as a signal
#ENABLE_LINE_INPUT 0x0002 #ENABLE_LINE_INPUT 0x0002
#ENABLE_ECHO_INPUT 0x0004 #ENABLE_ECHO_INPUT 0x0004
#ENABLE_WINDOW_INPUT 0x0008 (default off when a terminal created) #ENABLE_WINDOW_INPUT 0x0008 (default off when a terminal created)
@ -245,10 +285,10 @@ namespace eval punk::console {
proc [namespace parent]::disableAnsi {} { proc [namespace parent]::disableAnsi {} {
set h_out [twapi::get_console_handle stdout] set h_out [twapi::get_console_handle stdout]
set oldmode_out [twapi::GetConsoleMode $h_out] set oldmode_out [twapi::GetConsoleMode $h_out]
set newmode_out [expr {$oldmode_out & ~5}] set newmode_out [expr {$oldmode_out & ~4}]
twapi::SetConsoleMode $h_out $newmode_out twapi::SetConsoleMode $h_out $newmode_out
#??? review
set h_in [twapi::get_console_handle stdin] set h_in [twapi::get_console_handle stdin]
set oldmode_in [twapi::GetConsoleMode $h_in] set oldmode_in [twapi::GetConsoleMode $h_in]
set newmode_in [expr {$oldmode_in & ~8}] set newmode_in [expr {$oldmode_in & ~8}]
@ -372,6 +412,12 @@ namespace eval punk::console {
} }
proc [namespace parent]::disableVirtualTerminal {{channels {input output}}} { proc [namespace parent]::disableVirtualTerminal {{channels {input output}}} {
} }
proc [namespace parent]::enableProcessedInput {args} {
}
proc [namespace parent]::disableProcessedInput {args} {
}
} }
@ -442,39 +488,200 @@ namespace eval punk::console {
} }
#review - 1 byte at a time seems inefficient... but we don't have a way to peek or put back chars (?) #capturingendregex should capture ANY prefix, whole escape match - and a subcapture of the data we're interested in and match at end of string.
#todo - timeout - what if terminal doesn't put data on stdin? #ie {(.*)(ESC(info)end)$}
#review - what if we slurp in data meant for main loop? Main loop probably needs to detect these responses and store them for lookup *instead* of this handler #e.g {(.*)(\x1bP44!~([:alnum:])\x1b\\)$}
#we expect 4 results from regexp -indices -inline (overallmatch, prefix, wholeescape,info)
#todo - check capturingendregex value supplied has appropriate captures and tail-anchor
proc get_ansi_response_payload {query capturingendregex {inoutchannels {stdin stdout}}} {
lassign $inoutchannels input output
#chunks from input that need to be handled by readers
upvar ::punk::console::input_chunks_waiting input_chunks_waiting
#we need to cooperate with other stdin/$input readers and put data here if we overconsume.
#Main repl reader may be currently active - or may be inactive.
#This call could come from within code called by the main reader - or from user code running while main read-loop is temporarily disabled
#In other contexts there may not even be another input reader
#REVIEW - what if there is existing data in input_chunks_waiting - is it for us?
#temp - let's keep alert to it until we decide if it's legit/required..
if {[info exists input_chunks_waiting($input)] && [llength $input_chunks_waiting($input)]} {
#puts stderr "[punk::ansi::a+ cyan bold]get_ansi_response_payload called while input_chunks_waiting($input) contained data: $input_chunks_waiting($input)[punk::ansi::a]"
}
if {!$::punk::console::ansi_available} {
return ""
}
set callid [info cmdcount] ;#info cmdcount is almost as fast as clock clicks - and whilst not unique in a long-running app(will wrap?) - fine for this context
#
upvar ::punk::console::ansi_response_chunk accumulator
upvar ::punk::console::ansi_response_wait waitvar
set accumulator($callid) ""
set waitvar($callid) ""
#todo - use a linked array and an accumulatorid and waitvar id? When can there be more than one terminal query in-flight?
set existing_handler [fileevent $input readable] ;#review!
fileevent $input readable {}
set input_state [fconfigure $input]
#todo - test and save rawstate so we don't disableRaw if console was already raw
if {!$::punk::console::is_raw} {
set was_raw 0
punk::console::enableRaw
} else {
set was_raw 1
}
fconfigure $input -blocking 0
#
set this_handler ::punk::console::internal::ansi_response_handler_regex
if {[lindex $existing_handler 0] eq $this_handler} {
puts stderr "[punk::ansi::a+ red]Warning get_ansi_response_payload called while existing ansi response handler in place[a]: $this_handler"
}
#in handler - its used for a boolean match (capturing aspect not used)
fileevent $input readable [list $this_handler $input $callid $capturingendregex]
# - stderr vs stdout
#It has to be same channel as used by functions such as test_char_width or erroneous results returned for those functions
#(presumably race conditions as to when data hits console?)
#review - experiment changing this and calling functions to stderr and see if it works
#review - Are there disadvantages to using stdout vs stderr?
#puts stdout "sending console request [ansistring VIEW $query]"
puts -nonewline $output $query;flush $output
#response from terminal
#e.g for cursor position \033\[46;1R
#todo - make timeout configurable?
set waitvarname "::punk::console::ansi_response_wait($callid)"
set cancel_timeout_id [after 500 [list set $waitvarname timedout]]
if {[set waitvar($callid)] eq ""} {
vwait ::punk::console::ansi_response_wait($callid)
}
#response handler automatically removes it's own fileevent
fileevent $input readable {} ;#explicit remove anyway - review
if {$waitvar($callid) ne "timedout"} {
after cancel $cancel_timeout_id
} else {
puts stderr "timeout in get_ansi_response_payload"
}
if {$was_raw == 0} {
punk::console::disableRaw
}
#restore $input state
fconfigure $input -blocking [dict get $input_state -blocking]
set response [set accumulator($callid)]
if {$response ne ""} {
set got_match [regexp -indices $capturingendregex $response _match_indices prefix_indices response_indices payload_indices]
if {$got_match} {
set responsedata [string range $response {*}$response_indices]
set payload [string range $response {*}$payload_indices]
set prefixdata [string range $response {*}$prefix_indices]
if {$prefixdata ne ""} {
#puts stderr "Warning - get_ansi_response_payload read extra data at start - '[ansistring VIEW -lf 1 $prefixdata]' (response=[ansistring VIEW -lf 1 $responsedata])"
lappend input_chunks_waiting($input) $prefixdata
}
} else {
#timedout - or eof?
puts stderr "get_ansi_response_payload regex match '$capturingendregex' to data '[ansistring VIEW $response]' not found"
lappend input_chunks_waiting($input) $response
set payload ""
}
} else {
#timedout or eof? and nothing read
set payload ""
}
#is there a way to know if existing_handler is input_chunks_waiting aware?
if {[string length $existing_handler] && [lindex $existing_handler 0] ne $this_handler} {
#puts "get_ansi_response_paylaod reinstalling ------>$existing_handler<------"
fileevent $input readable $existing_handler
#we may have consumed all pending input on $input - so there may be no trigger for the readable fileevent
if {[llength $input_chunks_waiting($input)]} {
#This is experimental If a handler is aware of input_chunks_waiting - there should be no need to schedule a trigger
#If it isn't, but the handler can accept an existing chunk of data as an argument - we could trigger and pass it the waiting chunks - but there's no way to know its API.
#we could look at info args - but that's not likely to tell us much in a robust way.
#we could create a reflected channel for stdin? That is potentially an overreach..?
#triggering it manually... as it was already listening - this should generally do no harm as it was the active reader anyway, but won't help with the missing data if it's input_chunks_waiting-unaware.
puts stderr "[punk::ansi::a+ yellow bold]-->punk::console::get_ansi_response_payload triggering existing handler while over-read data is in punk::console::input_chunks_waiting($input) instead of channel [ansistring VIEW $input_chunks_waiting($input)][punk::ansi::a]"
after idle [list after 0 $existing_handler]
}
#Note - we still may be in_repl_handler here (which disables its own reader while executing commandlines)
#The input_chunks_waiting may really belong to the existing_handler we found - but if it doesn't consume them they will end up being read by the repl_handler when it eventually re-enables.
#todo - some better structure than just a list of waiting chunks indexed by input channel, so repl/other handlers can determine the context in which these waiting chunks were generated?
} elseif {[llength $::repl::in_repl_handler]} {
if {[llength $input_chunks_waiting($input)]} {
#don't trigger the repl handler manually - we will inevitably get things out of order - as it knows when to enable/disable itself based on whether chunks are waiting.
#triggering it by putting it on the eventloop will potentially result in re-entrancy
#The cooperating reader must be designed to consume waiting chunks and only reschedule it's channel read handler once all waiting chunks have been consumed.
#puts stderr "[punk::ansi::a+ green bold]--> repl_handler has chunks to consume [ansistring VIEW $input_chunks_waiting($input)][punk::ansi::a]"
}
}
catch {
unset accumulator($callid)
unset waitvar($callid)
}
#set punk::console::chunk ""
return $payload
}
#review - reading 1 byte at a time and repeatedly running the small capturing/completion regex seems a little inefficient... but we don't have a way to peek or put back chars (?)
#review (we do have the punk::console::input_chunks_waiting($chan) array to cooperatively put back data - but this won't work for user scripts not aware of this)
#review - timeout - what if terminal doesn't put data on stdin? error vs stderr report vs empty results
#review - Main loop may need to detect some terminal responses and store them for lookup instead-of or as-well-as this handler?
#e.g what happens to mouse-events while user code is executing?
#we may still need this handler if such a loop doesn't exist. #we may still need this handler if such a loop doesn't exist.
proc ansi_response_handler {chan accumulatorvar waitvar} { proc ansi_response_handler_regex {chan callid endregex} {
upvar ::punk::console::ansi_response_chunk chunks
upvar ::punk::console::ansi_response_wait waits
#endregex should explicitly have a trailing $
set status [catch {read $chan 1} bytes] set status [catch {read $chan 1} bytes]
if { $status != 0 } { if { $status != 0 } {
# Error on the channel # Error on the channel
fileevent stdin readable {} fileevent $chan readable {}
puts "error reading $chan: $bytes" puts "ansi_response_handler_regex error reading $chan: $bytes"
set $waitvar [list error_read status $status bytes $bytes] set waits($callid) [list error_read status $status bytes $bytes]
} elseif {$bytes ne ""} { } elseif {$bytes ne ""} {
# Successfully read the channel # Successfully read the channel
#puts "got: [string length $bytes]" #puts "got: [string length $bytes]bytes"
upvar $accumulatorvar chunk append chunks($callid) $bytes
append chunk $bytes #puts stderr [ansistring VIEW $chunks($callid)]
if {$bytes eq "R"} { if {[regexp $endregex $chunks($callid)]} {
fileevent stdin readable {} fileevent $chan readable {}
set $waitvar ok #puts stderr "matched - setting ansi_response_wait($callid) ok"
set waits($callid) ok
} }
} elseif {[eof $chan]} { } elseif {[eof $chan]} {
fileevent stdin readable {} fileevent $chan readable {}
# End of file on the channel # End of file on the channel
#review #review
puts "ansi_response_handler end of file" puts stderr "ansi_response_handler_regex end of file on channel $chan"
set $waitvar eof set waits($callid) eof
} elseif {[fblocked $chan]} { } elseif {[fblocked $chan]} {
# Read blocked. Just return # Read blocked. Just return
# Caller should be using timeout on the wait variable
} else { } else {
fileevent stdin readable {} fileevent $chan readable {}
# Something else # Something else
puts "ansi_response_handler can't happen" puts stderr "ansi_response_handler_regex Situation shouldn't be possible. No error and no bytes read on channel $chan but chan is not fblocked or EOF"
set $waitvar error_unknown set waits($callid) error_unknown_zerobytes_while_not_blocked_or_eof
} }
} }
} ;#end namespace eval internal } ;#end namespace eval internal
@ -487,67 +694,99 @@ namespace eval punk::console {
} }
} }
namespace eval ansi { #a and a+ functions are not very useful when emitting directly to console
proc a+ {args} { #e.g puts [punk::console::a red]test[punk::console::a cyan] would produce a cyan coloured test as the commands are evaluated first
puts -nonewline [::punk::ansi::a+ {*}$args]
} #proc a {args} {
} # variable colour_disabled
proc ansi+ {args} { # variable ansi_wanted
variable colour_disabled # if {$colour_disabled || $ansi_wanted <= 0} {
if {$colour_disabled == 1} { # return
return # }
} # #stdout
# tailcall ansi::a {*}$args
#}
#proc a+ {args} {
# variable colour_disabled
# variable ansi_wanted
# if {$colour_disabled || $ansi_wanted <= 0} {
# return
# }
# #stdout
# tailcall ansi::a+ {*}$args
#}
proc a? {args} {
#stdout #stdout
tailcall ansi::a+ {*}$args
}
proc get_ansi+ {args} {
variable colour_disabled variable colour_disabled
if {$colour_disabled == 1} { variable ansi_wanted
return if {$colour_disabled || $ansi_wanted <= 0} {
puts -nonewline [punk::ansi::stripansi [::punk::ansi::a?]]
} else {
tailcall ansi::a? {*}$args
} }
tailcall punk::ansi::a+ {*}$args
} }
namespace eval ansi { proc code_a {args} {
proc a {args} { variable colour_disabled
puts -nonewline [::punk::ansi::a {*}$args] variable ansi_wanted
if {$colour_disabled || $ansi_wanted <= 0} {
return
} }
tailcall punk::ansi::a {*}$args
} }
proc ansi {args} { proc code_a? {args} {
variable colour_disabled variable colour_disabled
if {$colour_disabled == 1} { variable ansi_wanted
return if {$colour_disabled || $ansi_wanted <= 0} {
return [punk::ansi::stripansi [::punk::ansi::a? {*}$args]]
} else {
tailcall ::punk::ansi::a? {*}$args
} }
#stdout
tailcall ansi::a {*}$args
} }
proc get_ansi {args} { proc code_a+ {args} {
variable colour_disabled variable colour_disabled
if {$colour_disabled == 1} { variable ansi_wanted
if {$colour_disabled || $ansi_wanted <= 0} {
return return
} }
tailcall punk::ansi::a {*}$args tailcall punk::ansi::a+ {*}$args
} }
namespace eval ansi { proc ansi {{onoff {}}} {
proc a? {args} { variable ansi_wanted
puts -nonewline stdout [::punk::ansi::a? {*}$args] if {[string length $onoff]} {
set onoff [string tolower $onoff]
switch -- $onoff {
1 -
on -
true -
yes {
set ansi_wanted 1
} }
0 -
off -
false -
no {
set ansi_wanted 0
} }
proc ansi? {args} { default {
#stdout set ansi_wanted 2
tailcall ansi::a? {*}$args
} }
proc get_ansi? {args} { default {
tailcall ::punk::ansi::a? {*}$args error "punk::console::ansi expected 0|1|on|off|true|false|yes|no|default"
}
}
}
catch {repl::reset_prompt}
return [expr {$ansi_wanted}]
} }
proc colour {{onoff {}}} { proc colour {{onoff {}}} {
variable colour_disabled variable colour_disabled
if {[string length $onoff]} { if {[string length $onoff]} {
set onoff [string tolower $onoff] set onoff [string tolower $onoff]
#an experiment with complete disabling vs test of state for each call
if {$onoff in [list 1 on true yes]} { if {$onoff in [list 1 on true yes]} {
interp alias "" a+ "" punk::console::ansi+ interp alias "" a+ "" punk::console::code_a+
set colour_disabled 0 set colour_disabled 0
} elseif {$onoff in [list 0 off false no]} { } elseif {$onoff in [list 0 off false no]} {
interp alias "" a+ "" control::no-op interp alias "" a+ "" control::no-op
@ -560,14 +799,17 @@ namespace eval punk::console {
return [expr {!$colour_disabled}] return [expr {!$colour_disabled}]
} }
namespace eval ansi { namespace eval ansi {
proc reset {} { proc a {args} {
puts -nonewline stdout [punk::ansi::reset] puts -nonewline [::punk::ansi::a {*}$args]
} }
proc a? {args} {
puts -nonewline stdout [::punk::ansi::a? {*}$args]
}
proc a+ {args} {
puts -nonewline [::punk::ansi::a+ {*}$args]
} }
namespace import ansi::reset
namespace eval ansi {
proc clear {} { proc clear {} {
puts -nonewline stdout [punk::ansi::clear] puts -nonewline stdout [punk::ansi::clear]
} }
@ -580,11 +822,15 @@ namespace eval punk::console {
proc clear_all {} { proc clear_all {} {
puts -nonewline stdout [punk::ansi::clear_all] puts -nonewline stdout [punk::ansi::clear_all]
} }
proc reset {} {
puts -nonewline stdout [punk::ansi::reset]
}
} }
namespace import ansi::clear namespace import ansi::clear
namespace import ansi::clear_above namespace import ansi::clear_above
namespace import ansi::clear_below namespace import ansi::clear_below
namespace import ansi::clear_all namespace import ansi::clear_all
namespace import ansi::reset
namespace eval local { namespace eval local {
proc set_codepage_output {cpname} { proc set_codepage_output {cpname} {
@ -607,91 +853,80 @@ namespace eval punk::console {
namespace import local::set_codepage_output namespace import local::set_codepage_output
namespace import local::set_codepage_input namespace import local::set_codepage_input
# -- --- --- --- --- --- ---
proc get_cursor_pos {} { #get_ansi_response functions
set ::punk::console::chunk "" #review - can these functions sensibly be used on channels not attached to the local console?
#ie can we default to {stdin stdout} but allow other channel pairs?
set accumulator ::punk::console::chunk # -- --- --- --- --- --- ---
set waitvar ::punk::console::chunkdone proc get_cursor_pos {{inoutchannels {stdin stdout}}} {
set existing_handler [fileevent stdin readable] ;#review!
set $waitvar ""
set stdin_state [fconfigure stdin]
#todo - only use own handler if an existing stdin handler not present.. (or console is in line mode)
#todo - test and save rawstate so we don't disableRaw if console was already raw
if {!$::punk::console::is_raw} {
set was_raw 0
enableRaw
} else {
set was_raw 1
}
fconfigure stdin -blocking 0
#
fileevent stdin readable [list ::punk::console::internal::ansi_response_handler stdin $accumulator $waitvar]
# - stderr vs stdout
#It has to be same channel as used by functions such as test_char_width or erroneous results returned for those functions
#(presumably race conditions as to when data hits console?)
#review - experiment changing this and calling functions to stderr and see if it works
#review - Are there disadvantages to using stdout vs stderr?
puts -nonewline stdout \033\[6n ;flush stdout
#response from terminal #response from terminal
#e.g \033\[46;1R #e.g \033\[46;1R
set capturingregex {(.*)(\x1b\[([0-9]+;[0-9]+)R)$} ;#must capture prefix,entire-response,response-payload
#todo - make timeout configurable? set request "\033\[6n"
set cancel_timeout_id [after 2000 [list set $waitvar timedout]] set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
after 0 {update idletasks} return $payload
set info ""
if {[set $waitvar] eq ""} {
vwait $waitvar
} }
if {$waitvar ne "timedout"} { proc get_checksum_rect {id page t l b r {inoutchannels {stdin stdout}}} {
after cancel $cancel_timeout_id #e.g \x1b\[P44!~E797\x1b\\
} else { #re e.g {(.*)(\x1b\[P44!~([[:alnum:]])\x1b\[\\)$}
return "" set capturingregex [string map [list %id% $id] {(.*)(\x1bP%id%!~([[:alnum:]]+)\x1b\\)$}]
} set request "\x1b\[${id}\;${page}\;$t\;$l\;$b\;$r*y"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
if {$was_raw == 0} { return $payload
disableRaw
} }
#restore stdin state proc get_device_status {{inoutchannels {stdin stdout}}} {
fconfigure stdin -blocking [dict get $stdin_state -blocking] set capturingregex {(.*)(\x1b\[([0-9]+)n)$} ;#must capture prefix,entire-response,response-payload
if {[string length $existing_handler]} { set request "\x1b\[5n"
fileevent stdin readable $existing_handler set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
return $payload
} }
#response handler automatically removes it's own fileevent
set info [set $accumulator]
set start [string first \x1b $info]
if {$start > 0} {
set other [string range $info 0 $start-1]
#!!!!! TODO
# Log this somewhere? Work out how to stop it happening?
#puts stderr "Warning - get_cursor_pos read extra data at start - '$other'"
set info [string range $info $start end]
}
#set punk::console::chunk ""
set data [string range $info 2 end-1]
return $data
}
proc get_cursor_pos_list {} { proc get_cursor_pos_list {} {
return [split [get_cursor_pos] ";"] return [split [get_cursor_pos] ";"]
} }
proc get_size {} {
if {[catch {
puts -nonewline [punk::ansi::cursor_off][punk::ansi::cursor_save][punk::ansi::move 2000 2000]
lassign [get_cursor_pos_list] lines cols
puts -nonewline [punk::ansi::cursor_restore][punk::console::cursor_on];flush stdout
set result [list columns $cols rows $lines]
} errM]} {
puts -nonewline [punk::ansi::cursor_restore]
puts -nonewline [punk::ansi::cursor_on]
error "$errM"
} else {
return $result
}
}
proc get_dimensions {} {
lassign [get_size] _c cols _l lines
return "${cols}x${lines}"
}
#the (xterm?) CSI 18t query is supported by *some* terminals
proc get_xterm_size {{inoutchannels {stdin stdout}}} {
set capturingregex {(.*)(\x1b\[8;([0-9]+;[0-9]+)t)$} ;#must capture prefix,entire-response,response-payload
set request "\x1b\[18t"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
lassign [split $payload {;}] rows cols
return [list columns $cols rows $rows]
}
#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.
proc test_char_width {char_or_string {emit 0}} { proc test_char_width {char_or_string {emit 0}} {
#return 1
#JMN
#puts stderr "cwtest"
variable ansi_available
if {!$ansi_available} {
puts stderr "No ansi - cannot test char_width of '$char_or_string' returning [string length $char_or_string]"
return [string length $char_or_string]
}
if {!$emit} { if {!$emit} {
puts -nonewline stdout \033\[2K\033\[1G ;#2K erase line 1G cursor at col1 puts -nonewline stdout \033\[2K\033\[1G ;#2K erase line 1G cursor at col1
} }
@ -725,6 +960,50 @@ namespace eval punk::console {
return [expr {$col2 - $col1}] return [expr {$col2 - $col1}]
} }
#todo! - improve
proc test_can_ansi {} {
#don't set ansi_avaliable here - we want to be able to change things, retest etc.
if {"windows" eq "$::tcl_platform(platform)"} {
if {[package provide twapi] ne ""} {
set h_out [twapi::get_console_handle stdout]
set existing_mode [twapi::GetConsoleMode $h_out]
if {[expr {$existing_mode & 4}]} {
#virtual terminal processing happens to be enabled - so it's supported
return 1
}
#output mode
#ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
#try temporarily setting it - if we get an error - ansi not supported
if {[catch {
twapi::SetConsoleMode $h_out [expr {$existing_mode | 4}]
} errM]} {
return 0
}
#restore
twapi::SetConsoleMode $h_out [expr {$existing_mode & ~4}]
return 1
} else {
#todo - try a cursorpos query and read stdin to see if we got a response?
puts stderr "Unable to verify terminal ansi support - assuming modern default of true"
puts stderr "to force disable, use command: ansi off"
return 1
}
} else {
return 1
}
}
#review
proc can_ansi {} {
variable ansi_available
if {!$ansi_available} {
return 0
}
set ansi_available [test_can_ansi]
return [expr {$ansi_available}]
}
namespace eval ansi { namespace eval ansi {
proc cursor_on {} { proc cursor_on {} {
puts -nonewline stdout [punk::ansi::cursor_on] puts -nonewline stdout [punk::ansi::cursor_on]
@ -768,7 +1047,15 @@ namespace eval punk::console {
puts -nonewline stdout [punk::ansi::titleset $windowtitle] puts -nonewline stdout [punk::ansi::titleset $windowtitle]
} }
} }
namespace import ansi::titleset #namespace import ansi::titleset
proc titleset {windowtitle} {
variable ansi_wanted
if { $ansi_wanted <= 0} {
punk::console::local::titleset $windowtitle
} else {
tailcall ansi::titleset $windowtitle
}
}
#no known pure-ansi solution #no known pure-ansi solution
proc titleget {} { proc titleget {} {
return [local::titleget] return [local::titleget]
@ -852,14 +1139,14 @@ namespace eval punk::console {
#review - worth the extra microseconds to inline? might be if used in for example prompt on every keypress. #review - worth the extra microseconds to inline? might be if used in for example prompt on every keypress.
#caller should build as much as possible using the punk::ansi versions to avoid extra puts calls #caller should build as much as possible using the punk::ansi versions to avoid extra puts calls
proc save_cursor {} { proc cursor_save {} {
#*** !doctools #*** !doctools
#[call [fun save_cursor]] #[call [fun cursor_save]]
puts -nonewline \x1b\[s puts -nonewline \x1b\[s
} }
proc restore_cursor {} { proc cursor_restore {} {
#*** !doctools #*** !doctools
#[call [fun restore_cursor]] #[call [fun cursor_restore]]
puts -nonewline \x1b\[u puts -nonewline \x1b\[u
} }
proc insert_spaces {count} { proc insert_spaces {count} {
@ -886,8 +1173,8 @@ namespace eval punk::console {
namespace import ansi::move_down namespace import ansi::move_down
namespace import ansi::move_column namespace import ansi::move_column
namespace import ansi::move_row namespace import ansi::move_row
namespace import ansi::save_cursor namespace import ansi::cursor_save
namespace import ansi::restore_cursor namespace import ansi::cursor_restore
namespace import ansi::scroll_down namespace import ansi::scroll_down
namespace import ansi::scroll_up namespace import ansi::scroll_up
namespace import ansi::insert_spaces namespace import ansi::insert_spaces
@ -906,27 +1193,64 @@ namespace eval punk::console {
#set blanks [string repeat " " [expr {$col + $tw}]] #set blanks [string repeat " " [expr {$col + $tw}]]
#puts -nonewline [punk::ansi::erase_eol]$blanks;move_emit_return this $col $text #puts -nonewline [punk::ansi::erase_eol]$blanks;move_emit_return this $col $text
#puts -nonewline [move_emit_return this $col [punk::ansi::insert_spaces 150]$text] #puts -nonewline [move_emit_return this $col [punk::ansi::insert_spaces 150]$text]
save_cursor cursor_save
#move_emit_return this $col [punk::ansi::move_forward 50][punk::ansi::insert_spaces 150][punk::ansi::move_back 50][punk::ansi::move_forward $col]$text #move_emit_return this $col [punk::ansi::move_forward 50][punk::ansi::insert_spaces 150][punk::ansi::move_back 50][punk::ansi::move_forward $col]$text
puts -nonewline [punk::ansi::insert_spaces 150][punk::ansi::move_column $col]$text #puts -nonewline [punk::ansi::insert_spaces 150][punk::ansi::move_column $col]$text
restore_cursor puts -nonewline [punk::ansi::erase_eol][punk::ansi::move_column $col]$text
cursor_restore
} }
proc move_emit_return {row col data args} { proc move_emit_return {row col data args} {
#todo detect if in raw mode or not? #todo detect if in raw mode or not?
set is_in_raw 0 set is_in_raw 0
lassign [punk::console::get_cursor_pos_list] orig_row orig_col lassign [punk::console::get_cursor_pos_list] orig_row orig_col
move_emit $row $col $data set commands ""
append commands [punk::ansi::move_emit $row $col $data]
foreach {row col data} $args { foreach {row col data} $args {
move_emit $row $col $data append commands [punk::ansi::move_emit $row $col $data]
} }
if {!$is_in_raw} { if {!$is_in_raw} {
incr orig_row -1 incr orig_row -1
} }
move $orig_row $orig_col append commands [punk::ansi::move $orig_row $orig_col]
puts -nonewline stdout $commands
return "" return ""
} }
#we can be faster and more efficient if we use the consoles cursor_save command - but each savecursor overrides any previous one.
#leave cursor_off/cursor_on to caller who can wrap more efficiently..
proc cursorsave_move_emit_return {row col data args} {
set commands ""
append commands [punk::ansi::cursor_save]
append commands [punk::ansi::move_emit $row $col $data]
foreach {row col data} $args {
append commands [punk::ansi::move_emit $row $col $data]
}
append commands [punk::ansi::cursor_restore]
puts -nonewline stdout $commands; flush stdout
}
proc move_emitblock_return {row col textblock} {
lassign [punk::console::get_cursor_pos_list] orig_row orig_col
set commands ""
foreach ln [split $textblock \n] {
append commands [punk::ansi::move_emit $row $col $ln]
incr row
}
append commands [punk::ansi::move $orig_row $orig_col]
puts -nonewline $commands
return
}
proc cursorsave_move_emitblock_return {row col textblock} {
set commands ""
append commands [punk::ansi::cursor_save]
foreach ln [split $textblock \n] {
append commands [punk::ansi::move_emit $row $col $ln]
incr row
}
append commands [punk::ansi::cursor_restore]
puts -nonewline stdout $commands;flush stdout
return
}
proc move_call_return {row col script} { proc move_call_return {row col script} {
lassign [punk::console::get_cursor_pos_list] orig_row orig_col lassign [punk::console::get_cursor_pos_list] orig_row orig_col
move $row $col move $row $col
@ -934,7 +1258,7 @@ namespace eval punk::console {
move $orig_row $orig_col move $orig_row $orig_col
} }
#this doesn't work - we would need an internal virtual screen structure to pick up cursor attributes from arbitrary locations #this doesn't work - we would need an internal virtual screen structure to pick up cursor attributes from arbitrary locations?
# ncurses and its ilk may have something like that - but we specifically want to avoid curses libraries # ncurses and its ilk may have something like that - but we specifically want to avoid curses libraries
proc pick {row col} { proc pick {row col} {
lassign [punk::console::get_cursor_pos_list] orig_row orig_col lassign [punk::console::get_cursor_pos_list] orig_row orig_col

4
src/bootsupport/modules/punk/encmime-0.1.0.tm

@ -50,9 +50,9 @@
#[para] packages used by punk::encmime #[para] packages used by punk::encmime
#[list_begin itemized] #[list_begin itemized]
package require Tcl 8.6 package require Tcl 8.6-
#*** !doctools #*** !doctools
#[item] [package {Tcl 8.6}] #[item] [package {Tcl 8.6-}]
# #package require frobz # #package require frobz
# #*** !doctools # #*** !doctools

24
src/bootsupport/modules/punk/fileline-0.1.0.tm

@ -60,10 +60,10 @@
#[para] packages needed by punk::fileline #[para] packages needed by punk::fileline
#[list_begin itemized] #[list_begin itemized]
package require Tcl 8.6 package require Tcl 8.6-
package require punk::args package require punk::args
#*** !doctools #*** !doctools
#[item] [package {Tcl 8.6}] #[item] [package {Tcl 8.6-}]
#[item] [package {punk::args}] #[item] [package {punk::args}]
@ -368,6 +368,7 @@ namespace eval punk::fileline::class {
} else { } else {
set tail [string trimleft $opt_linebase +];#ignore + set tail [string trimleft $opt_linebase +];#ignore +
} }
#todo - switch -glob -- $tail
if {[string match eof* $tail]} { if {[string match eof* $tail]} {
set endmath [string range $tail 3 end] set endmath [string range $tail 3 end]
#todo endmath? #todo endmath?
@ -1066,9 +1067,11 @@ namespace eval punk::fileline::class {
foreach whichvar [list start end] { foreach whichvar [list start end] {
upvar 0 ${whichvar}idx index upvar 0 ${whichvar}idx index
if {![string is digit -strict $index]} { if {![string is digit -strict $index]} {
if {"end" eq $index} { switch -glob -- $index {
end {
set index $max set index $max
} elseif {[string match "*-*" $index]} { }
"*-*" {
#end-int or int-int - like lrange etc we don't accept arbitrarily complex expressions #end-int or int-int - like lrange etc we don't accept arbitrarily complex expressions
lassign [split $index -] A B lassign [split $index -] A B
if {$A eq "end"} { if {$A eq "end"} {
@ -1076,7 +1079,8 @@ namespace eval punk::fileline::class {
} else { } else {
set index [expr {$A - $B}] set index [expr {$A - $B}]
} }
} elseif {[string match "*+*" $index]} { }
"*+*" {
lassign [split $index +] A B lassign [split $index +] A B
if {$A eq "end"} { if {$A eq "end"} {
#review - this will just result in out of bounds error in final test - as desired #review - this will just result in out of bounds error in final test - as desired
@ -1085,7 +1089,8 @@ namespace eval punk::fileline::class {
} else { } else {
set index [expr {$A + $B}] set index [expr {$A + $B}]
} }
} else { }
default {
#May be something like +2 or -0 which braced expr can hanle #May be something like +2 or -0 which braced expr can hanle
#we would like to avoid unbraced expr here - as we're potentially dealing with ranges that may come from external sources. #we would like to avoid unbraced expr here - as we're potentially dealing with ranges that may come from external sources.
if {[catch {expr {$index}} index]} { if {[catch {expr {$index}} index]} {
@ -1097,6 +1102,7 @@ namespace eval punk::fileline::class {
} }
} }
} }
}
#Unlike Tcl lrange,lindex etc - we don't want to support out of bound indices. #Unlike Tcl lrange,lindex etc - we don't want to support out of bound indices.
#show the supplied index and what it was mapped to in the error message. #show the supplied index and what it was mapped to in the error message.
if {$startidx < 0 || $startidx > $max} { if {$startidx < 0 || $startidx > $max} {
@ -1308,6 +1314,7 @@ namespace eval punk::fileline {
set bomenc "" set bomenc ""
set is_reliabletxt 0 ;#see http://reliabletxt.com - only utf-8 with bom, utf-16be, utf-16le, utf-32be supported as at 2024 set is_reliabletxt 0 ;#see http://reliabletxt.com - only utf-8 with bom, utf-16be, utf-16le, utf-32be supported as at 2024
set startdata 0 set startdata 0
#todo switch -glob
if {[string match "efbbbf*" $maybe_bom]} { if {[string match "efbbbf*" $maybe_bom]} {
set bomid utf-8 set bomid utf-8
set bomenc utf-8 set bomenc utf-8
@ -1424,6 +1431,7 @@ namespace eval punk::fileline {
set encoding_selected $bomenc set encoding_selected $bomenc
} }
} else { } else {
#!?
if {$bomenc eq "binary"} { if {$bomenc eq "binary"} {
set datachunk [string range $rawchunk $startdata end] set datachunk [string range $rawchunk $startdata end]
set encoding_selected binary set encoding_selected binary
@ -1523,7 +1531,7 @@ namespace eval punk::fileline::lib {
# is_span 1 boundaries {514 1026 1538} # is_span 1 boundaries {514 1026 1538}
#[example_end] #[example_end]
#[para] This function automatically uses lseq (if Tcl >= 8.7) when number of boundaries spanned is approximately greater than 75 #[para] This function automatically uses lseq (if Tcl >= 8.7) when number of boundaries spanned is approximately greater than 75
if {[catch {package require Tcl 8.7}]} { if {[catch {package require Tcl 8.7-}]} {
#only one implementation available for older Tcl #only one implementation available for older Tcl
tailcall punk::fileline::system::_range_spans_chunk_boundaries_tcl $start $end $chunksize {*}$args tailcall punk::fileline::system::_range_spans_chunk_boundaries_tcl $start $end $chunksize {*}$args
} }
@ -1675,7 +1683,7 @@ namespace eval punk::fileline::system {
proc _range_spans_chunk_boundaries_TIMEIT {start end chunksize {repeat 1}} { proc _range_spans_chunk_boundaries_TIMEIT {start end chunksize {repeat 1}} {
puts "main : [time {punk::fileline::lib::range_spans_chunk_boundaries $start $end $chunksize} $repeat]" puts "main : [time {punk::fileline::lib::range_spans_chunk_boundaries $start $end $chunksize} $repeat]"
puts "tcl : [time {punk::fileline::system::_range_spans_chunk_boundaries_tcl $start $end $chunksize} $repeat]" puts "tcl : [time {punk::fileline::system::_range_spans_chunk_boundaries_tcl $start $end $chunksize} $repeat]"
if {![catch {package require Tcl 8.7}]} { if {![catch {package require Tcl 8.7-}]} {
puts "lseq : [time {punk::fileline::system::_range_spans_chunk_boundaries_lseq $start $end $chunksize} $repeat]" puts "lseq : [time {punk::fileline::system::_range_spans_chunk_boundaries_lseq $start $end $chunksize} $repeat]"
} }
} }

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

File diff suppressed because it is too large Load Diff

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

@ -494,10 +494,14 @@ namespace eval punk::mix::base {
if {[catch {file type $path} ftype]} { if {[catch {file type $path} ftype]} {
return [list cksum "<PATHNOTFOUND>" opts $opts] return [list cksum "<PATHNOTFOUND>" opts $opts]
} }
if {$ftype ni [list file directory]} {
#review - links? #review - links?
switch -- $ftype {
file - directory {}
default {
error "cksum_path error file type '$ftype' not supported" error "cksum_path error file type '$ftype' not supported"
} }
}
set opt_cksum_algorithm [dict get $opts -cksum_algorithm] set opt_cksum_algorithm [dict get $opts -cksum_algorithm]
@ -512,8 +516,10 @@ namespace eval punk::mix::base {
set opt_cksum_meta [dict get $opts -cksum_meta] set opt_cksum_meta [dict get $opts -cksum_meta]
set opt_use_tar [dict get $opts -cksum_usetar] set opt_use_tar [dict get $opts -cksum_usetar]
if {$ftype eq "file"} { switch -- $ftype {
if {$opt_use_tar eq "auto"} { file {
switch -- $opt_use_tar {
auto {
if {$opt_cksum_meta eq "1"} { if {$opt_cksum_meta eq "1"} {
set opt_use_tar 1 set opt_use_tar 1
} else { } else {
@ -522,7 +528,8 @@ namespace eval punk::mix::base {
set opt_cksum_meta 0 set opt_cksum_meta 0
set opt_use_tar 0 set opt_use_tar 0
} }
} elseif {$opt_use_tar eq "0"} { }
0 {
if {$opt_cksum_meta eq "1"} { if {$opt_cksum_meta eq "1"} {
puts stderr "cksum_path doesn't yet support a non-tar cksum with metadata for a file" puts stderr "cksum_path doesn't yet support a non-tar cksum with metadata for a file"
return [list error unsupported_meta_without_tar cksum "<ERR>" opts $opts] return [list error unsupported_meta_without_tar cksum "<ERR>" opts $opts]
@ -530,7 +537,8 @@ namespace eval punk::mix::base {
#meta == auto or 0 #meta == auto or 0
set opt_cksum_meta 0 set opt_cksum_meta 0
} }
} else { }
default {
#tar == 1 #tar == 1
if {$opt_cksum_meta eq "0"} { if {$opt_cksum_meta eq "0"} {
puts stderr "cksum_path doesn't yet support a tar cksum without metadata for a file" puts stderr "cksum_path doesn't yet support a tar cksum without metadata for a file"
@ -540,8 +548,11 @@ namespace eval punk::mix::base {
set opt_cksum_meta 1 set opt_cksum_meta 1
} }
} }
} elseif {$ftype eq "directory"} { }
if {$opt_use_tar eq "auto"} { }
directory {
switch -- $opt_use_tar {
auto {
if {$opt_cksum_meta in [list "auto" "1"]} { if {$opt_cksum_meta in [list "auto" "1"]} {
set opt_use_tar 1 set opt_use_tar 1
set opt_cksum_meta 1 set opt_cksum_meta 1
@ -549,10 +560,12 @@ namespace eval punk::mix::base {
puts stderr "cksum_path doesn't yet support a content-only cksum of a folder structure. Currently only files supported without metadata. For folders use cksum_path -cksum_meta 1 or auto" puts stderr "cksum_path doesn't yet support a content-only cksum of a folder structure. Currently only files supported without metadata. For folders use cksum_path -cksum_meta 1 or auto"
return [list error unsupported_directory_cksum_without_meta cksum "<ERR>" opts $opts] return [list error unsupported_directory_cksum_without_meta cksum "<ERR>" opts $opts]
} }
} elseif {$opt_use_tar eq "0"} { }
0 {
puts stderr "cksum_path doesn't yet support a cksum of a folder structure without tar. Currently only files supported without metadata. For folders use cksum_path -cksum_meta 1 or auto with -cksum_usetar 1 or auto" puts stderr "cksum_path doesn't yet support a cksum of a folder structure without tar. Currently only files supported without metadata. For folders use cksum_path -cksum_meta 1 or auto with -cksum_usetar 1 or auto"
return [list error unsupported_directory_cksum_without_tar cksum "<ERR>" opts $opts] return [list error unsupported_directory_cksum_without_tar cksum "<ERR>" opts $opts]
} else { }
default {
#tar 1 #tar 1
if {$opt_cksum_meta eq "0"} { if {$opt_cksum_meta eq "0"} {
puts stderr "cksum_path doesn't yet support a tar checksum of a folder structure without metadat. Currently only files supported without metadata. For folders use cksum_path -cksum_meta 1 or auto with -cksum_usetar 1 or auto" puts stderr "cksum_path doesn't yet support a tar checksum of a folder structure without metadat. Currently only files supported without metadata. For folders use cksum_path -cksum_meta 1 or auto with -cksum_usetar 1 or auto"
@ -563,6 +576,8 @@ namespace eval punk::mix::base {
} }
} }
} }
}
}
dict set opts_actual -cksum_meta $opt_cksum_meta dict set opts_actual -cksum_meta $opt_cksum_meta
dict set opts_actual -cksum_usetar $opt_use_tar dict set opts_actual -cksum_usetar $opt_use_tar
@ -578,30 +593,37 @@ namespace eval punk::mix::base {
puts stderr "cksum_path doesn't yet support cksum of entire volume. (todo)" puts stderr "cksum_path doesn't yet support cksum of entire volume. (todo)"
return [list error unsupported_path opts $opts] return [list error unsupported_path opts $opts]
} }
switch -- $opt_cksum_algorithm {
if {$opt_cksum_algorithm eq "sha1"} { sha1 {
package require sha1 package require sha1
set cksum_command [list sha1::sha1 -hex -file] set cksum_command [list sha1::sha1 -hex -file]
} elseif {$opt_cksum_algorithm in [list "sha2" "sha256"]} { }
sha2 - sha256 {
package require sha256 package require sha256
set cksum_command [list sha2::sha256 -hex -file] set cksum_command [list sha2::sha256 -hex -file]
} elseif {$opt_cksum_algorithm eq "md5"} { }
md5 {
package require md5 package require md5
set cksum_command [list md5::md5 -hex -file] set cksum_command [list md5::md5 -hex -file]
} elseif {$opt_cksum_algorithm eq "cksum"} { }
cksum {
package require cksum ;#tcllib package require cksum ;#tcllib
set cksum_command [list crc::cksum -format 0x%X -file] set cksum_command [list crc::cksum -format 0x%X -file]
} elseif {$opt_cksum_algorithm eq "adler32"} { }
adler32 {
set cksum_command [list cksum_adler32_file] set cksum_command [list cksum_adler32_file]
} elseif {$opt_cksum_algorithm in [list "sha3" "sha3-256"]} { }
sha3 - sha3-256 {
#todo - replace with something that doesn't call another process #todo - replace with something that doesn't call another process
#set cksum_command [list apply {{file} {lindex [exec fossil sha3sum -256 $file] 0}}] #set cksum_command [list apply {{file} {lindex [exec fossil sha3sum -256 $file] 0}}]
set cksum_command [list $sha3_implementation 256] set cksum_command [list $sha3_implementation 256]
} elseif {$opt_cksum_algorithm in [list "sha3-224" "sha3-384" "sha3-512"]} { }
sha3-224 - sha3-384 - sah3-512 {
set bits [lindex [split $opt_cksum_algorithm -] 1] set bits [lindex [split $opt_cksum_algorithm -] 1]
#set cksum_command [list apply {{bits file} {lindex [exec fossil sha3sum -$bits $file] 0}} $bits] #set cksum_command [list apply {{bits file} {lindex [exec fossil sha3sum -$bits $file] 0}} $bits]
set cksum_command [list $sha3_implementation $bits] set cksum_command [list $sha3_implementation $bits]
} }
}
set cksum "" set cksum ""
if {$opt_use_tar != 0} { if {$opt_use_tar != 0} {

8
src/bootsupport/modules/punk/mix/commandset/layout-0.1.0.tm

@ -114,11 +114,11 @@ namespace eval punk::mix::commandset::layout {
set tablewidth [expr {$widest(name) + $vsep_w + $widest(pathtype) + $vsep_w + $widest(path)}] set tablewidth [expr {$widest(name) + $vsep_w + $widest(pathtype) + $vsep_w + $widest(path)}]
set table "" set table ""
append table [string repeat - $tablewidth] \n append table [string repeat - $tablewidth] \n
append table "[textblock::join [overtype::left $col(name) $title(name)] $vsep [overtype::left $col(pathtype) $title(pathtype)] $vsep [overtype::left $col(path) $title(path)]]" \n append table "[textblock::join -- [overtype::left $col(name) $title(name)] $vsep [overtype::left $col(pathtype) $title(pathtype)] $vsep [overtype::left $col(path) $title(path)]]" \n
append table [string repeat - $tablewidth] \n append table [string repeat - $tablewidth] \n
foreach n $names pt $pathtypes p $paths { foreach n $names pt $pathtypes p $paths {
append table "[textblock::join [overtype::left $col(name) $n] $vsep [overtype::left $col(pathtype) $pt] $vsep [overtype::left $col(path) $p]]" \n append table "[textblock::join -- [overtype::left $col(name) $n] $vsep [overtype::left $col(pathtype) $pt] $vsep [overtype::left $col(path) $p]]" \n
} }
return $table return $table
@ -161,11 +161,11 @@ namespace eval punk::mix::commandset::layout {
set tablewidth [expr {$widest(name) + $vsep_w + $widest(pathtype) + $vsep_w + $widest(path)}] set tablewidth [expr {$widest(name) + $vsep_w + $widest(pathtype) + $vsep_w + $widest(path)}]
set table "" set table ""
append table [string repeat - $tablewidth] \n append table [string repeat - $tablewidth] \n
append table "[textblock::join [overtype::left $col(name) $title(name)] $vsep [overtype::left $col(pathtype) $title(pathtype)] $vsep [overtype::left $col(path) $title(path)]]" \n append table "[textblock::join -- [overtype::left $col(name) $title(name)] $vsep [overtype::left $col(pathtype) $title(pathtype)] $vsep [overtype::left $col(path) $title(path)]]" \n
append table [string repeat - $tablewidth] \n append table [string repeat - $tablewidth] \n
foreach n $names pt $pathtypes p $paths { foreach n $names pt $pathtypes p $paths {
append table "[textblock::join [overtype::left $col(name) $n] $vsep [overtype::left $col(pathtype) $pt] $vsep [overtype::left $col(path) $p]]" \n append table "[textblock::join -- [overtype::left $col(name) $n] $vsep [overtype::left $col(pathtype) $pt] $vsep [overtype::left $col(path) $p]]" \n
} }
return $table return $table

4
src/bootsupport/modules/punk/mix/commandset/project-0.1.0.tm

@ -59,9 +59,9 @@
#[para] packages used by punk::mix::commandset::project #[para] packages used by punk::mix::commandset::project
#[list_begin itemized] #[list_begin itemized]
package require Tcl 8.6 package require Tcl 8.6-
#*** !doctools #*** !doctools
#[item] [package {Tcl 8.6}] #[item] [package {Tcl 8.6-}]
#[item] [package punk::ns] #[item] [package punk::ns]
#[item] [package sqlite3] (binary) #[item] [package sqlite3] (binary)
#[item] [package overtype] #[item] [package overtype]

21
src/bootsupport/modules/punk/mix/commandset/scriptwrap-0.1.0.tm

@ -209,9 +209,13 @@ namespace eval punk::mix::commandset::scriptwrap {
set callposn -1 set callposn -1
set trimln [string trim $callingline_payload] set trimln [string trim $callingline_payload]
if {[string match "rem *" $trimln] || [string match "@rem *" $trimln] || [string match "REM *" $trimln] || [string match "@REM *" $trimln]} {
#if {[string match "rem *" $trimln] || [string match "@rem *" $trimln] || [string match "REM *" $trimln] || [string match "@REM *" $trimln]} {}
#ignore things that look like a call that are beind a REM #ignore things that look like a call that are beind a REM
} else { switch -glob -nocase -- $trimln {
"rem *" -
"@rem *" {}
default {
#todo - better callsite analysis. There can be data between @GOTO or @CALL and : other than just whitespace! #todo - better callsite analysis. There can be data between @GOTO or @CALL and : other than just whitespace!
@ -695,7 +699,8 @@ namespace eval punk::mix::commandset::scriptwrap {
} }
} }
} }
} } ;# end default switch case on trimln
} ;# end switch
incr file_offset $callingline_len ;#including per-line stored line-ending incr file_offset $callingline_len ;#including per-line stored line-ending
} }
if {[dict size $possible_target_labels_found] > 0} { if {[dict size $possible_target_labels_found] > 0} {
@ -1381,7 +1386,8 @@ namespace eval punk::mix::commandset::scriptwrap {
set inputconsumed 0 set inputconsumed 0
foreach c $inputchars { foreach c $inputchars {
if {!$invar} { if {!$invar} {
if {$c eq "%"} { switch -- $c {
"%" {
set caretseq 0 set caretseq 0
set lookahead [lrange $inputchars $inputconsumed+1 end] set lookahead [lrange $inputchars $inputconsumed+1 end]
if {"%" in $lookahead} { if {"%" in $lookahead} {
@ -1390,14 +1396,16 @@ namespace eval punk::mix::commandset::scriptwrap {
} else { } else {
incr percentrun incr percentrun
} }
} elseif {$c eq "^"} { }
"^" {
if {$caretseq} { if {$caretseq} {
set caretseq 0 set caretseq 0
append labelout "^" ;#for every pair encountered in direct sequence - second gets included in label append labelout "^" ;#for every pair encountered in direct sequence - second gets included in label
} else { } else {
set caretseq 1 set caretseq 1
} }
} else { }
default {
set caretseq 0 set caretseq 0
if {$percentrun && ($c in [list 0 1 2 3 4 5 6 7 8 9])} { if {$percentrun && ($c in [list 0 1 2 3 4 5 6 7 8 9])} {
#subst %i with value - here we have no way of getting that - so use blank - user will get warning that target not found #subst %i with value - here we have no way of getting that - so use blank - user will get warning that target not found
@ -1411,6 +1419,7 @@ namespace eval punk::mix::commandset::scriptwrap {
append labelout $c append labelout $c
} }
} }
}
} else { } else {
#in var - don't do anything with carets(?) #in var - don't do anything with carets(?)
if {$c eq "%" && $percentrun == 1} { if {$c eq "%" && $percentrun == 1} {

18
src/bootsupport/modules/punk/ns-0.1.0.tm

@ -766,8 +766,9 @@ namespace eval punk::ns {
set e [a+ yellow bold] set e [a+ yellow bold]
set o [a+ cyan bold] set o [a+ cyan bold]
set p [a+ white bold] set p [a+ white bold]
set a1 [a][a+ cyan]
foreach ch1 $children1 ch2 $children2 cmd1 $elements1 cmd2 $elements2 cmd3 $elements3 cmd4 $elements4 { foreach ch1 $children1 ch2 $children2 cmd1 $elements1 cmd2 $elements2 cmd3 $elements3 cmd4 $elements4 {
set a1 [a+ cyan]
set c1 [a+ white] set c1 [a+ white]
set c2 [a+ white] set c2 [a+ white]
set c3 [a+ white] set c3 [a+ white]
@ -1355,8 +1356,9 @@ namespace eval punk::ns {
proc corp {path} { proc corp {path} {
#thanks to Richard Suchenwirth for the original - wiki.tcl-lang.org/page/corp #thanks to Richard Suchenwirth for the original - wiki.tcl-lang.org/page/corp
#Note: modified here to support aliases and relative/absolute name (with respect to namespace .ie ::name vs name) #Note: modified here to support aliases and relative/absolute name (with respect to namespace .ie ::name vs name)
set indent " " ;#review
if {[info exists ::auto_index($path)]} { if {[info exists ::auto_index($path)]} {
set body "# $::auto_index($path)\n" set body "\n${indent}#corp# auto_index $::auto_index($path)"
} else { } else {
set body "" set body ""
} }
@ -1404,10 +1406,20 @@ namespace eval punk::ns {
} }
} }
if {[nsprefix $origin] ne [nsprefix [nsjoin ${targetns} $name]]} { if {[nsprefix $origin] ne [nsprefix [nsjoin ${targetns} $name]]} {
append body "# namespace origin $origin" \n append body \n "${indent}#corp# namespace origin $origin"
} }
if {$body ne "" && [string index $body end] ne "\n"} {
append body \n
}
if {![catch {package require textutil::tabify} errpkg]} {
set bodytext [info body $origin]
#punk::lib::indent preserves trailing empty lines - unlike textutil version
set bodytext [punk::lib::undent [textutil::untabify2 $bodytext]]
append body [punk::lib::indent $bodytext $indent]
} else {
append body [info body $origin] append body [info body $origin]
}
set argl {} set argl {}
foreach a [info args $origin] { foreach a [info args $origin] {
if {[info default $origin $a def]} { if {[info default $origin $a def]} {

14
src/bootsupport/modules/punk/path-0.1.0.tm

@ -44,9 +44,9 @@
#[para] packages used by punk::path #[para] packages used by punk::path
#[list_begin itemized] #[list_begin itemized]
package require Tcl 8.6 package require Tcl 8.6-
#*** !doctools #*** !doctools
#[item] [package {Tcl 8.6}] #[item] [package {Tcl 8.6-}]
# #package require frobz # #package require frobz
# #*** !doctools # #*** !doctools
@ -126,11 +126,10 @@ namespace eval punk::path {
if {[string range $seg end end] eq "/"} { if {[string range $seg end end] eq "/"} {
set seg [string range $seg 0 end-1] ;# e.g c:/ -> c: / -> "" so that join at end doesn't double up set seg [string range $seg 0 end-1] ;# e.g c:/ -> c: / -> "" so that join at end doesn't double up
} }
if {$seg eq "*"} { switch -- $seg {
lappend pats {[^/]*} * {lappend pats {[^/]*}}
} elseif {$seg eq "**"} { ** {lappend pats {.*}}
lappend pats {.*} default {
} else {
set seg [string map [list {^ {\^} $ {\$} [} {\[} ( {\(} \{ \\\{ \\ {\\}] $seg] ;#treat regex characters in the input as literals set seg [string map [list {^ {\^} $ {\$} [} {\[} ( {\(} \{ \\\{ \\ {\\}] $seg] ;#treat regex characters in the input as literals
set seg [string map [list . {[.]}] $seg] set seg [string map [list . {[.]}] $seg]
if {[regexp {[*?]} $seg]} { if {[regexp {[*?]} $seg]} {
@ -141,6 +140,7 @@ namespace eval punk::path {
} }
} }
} }
}
return "^[join $pats /]\$" return "^[join $pats /]\$"
} }
proc globmatchpath {pathglob path args} { proc globmatchpath {pathglob path args} {

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

@ -418,29 +418,37 @@ namespace eval punk::repo {
continue continue
} }
} }
if {[string match "EDITED *" $ln]} { switch -glob -- $ln {
"EDITED *" {
set path [string trim [string range $ln [string length "EDITED "] end]] ;#should handle spaced paths set path [string trim [string range $ln [string length "EDITED "] end]] ;#should handle spaced paths
dict set pathdict $path "changed" dict set pathdict $path "changed"
} elseif {[string match "ADDED *" $ln]} { }
"ADDED *" {
set path [string trim [string range $ln [string length "ADDED "] end]] set path [string trim [string range $ln [string length "ADDED "] end]]
dict set pathdict $path "new" dict set pathdict $path "new"
} elseif {[string match "DELETED *" $ln]} { }
"DELETED *" {
set path [string trim [string range $ln [string length "DELETED "] end]] set path [string trim [string range $ln [string length "DELETED "] end]]
dict set pathdict $path "missing" dict set pathdict $path "missing"
} elseif {[string match "MISSING *" $ln]} { }
"MISSING *" {
set path [string trim [string range $ln [string length "MISSING "] end]] set path [string trim [string range $ln [string length "MISSING "] end]]
dict set pathdict $path "missing" dict set pathdict $path "missing"
} elseif {[string match "EXTRA *" $ln]} { }
"EXTRA * " {
#fossil will explicitly list files in a new folder - as opposed to git which shows just the folder #fossil will explicitly list files in a new folder - as opposed to git which shows just the folder
set path [string trim [string range $ln [string length "EXTRA "] end]] set path [string trim [string range $ln [string length "EXTRA "] end]]
dict set pathdict $path "extra" dict set pathdict $path "extra"
} elseif {[string match "UNCHANGED *" $ln]} { }
"UNCHANGED *" {
set path [string trim [string range $ln [string length "UNCHANGED "] end]] set path [string trim [string range $ln [string length "UNCHANGED "] end]]
dict set pathdict $path "unchanged" dict set pathdict $path "unchanged"
} else { }
default {
#emit for now #emit for now
puts stderr "unprocessed fossilstate line: $ln" puts stderr "unprocessed fossilstate line: $ln"
} }
}
#other entries?? #other entries??
} }
break break

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

@ -73,7 +73,11 @@ namespace eval punkcheck {
set record_list [list] set record_list [list]
if {[file exists $punkcheck_file]} { if {[file exists $punkcheck_file]} {
set tdlscript [punk::mix::util::fcat $punkcheck_file] set tdlscript [punk::mix::util::fcat $punkcheck_file]
if {[catch {
set record_list [punk::tdl::prettyparse $tdlscript] set record_list [punk::tdl::prettyparse $tdlscript]
} errparse]} {
error "punkcheck::load_records_from_file failed to parse '$punkcheck_file'\n error:$errparse"
}
} }
return $record_list return $record_list
} }
@ -131,10 +135,12 @@ namespace eval punkcheck {
#get last previous that is tagged INSTALL-RECORD,MODIFY-RECORD,DELETE-RECORD #get last previous that is tagged INSTALL-RECORD,MODIFY-RECORD,DELETE-RECORD
set revlist [lreverse $previous_records] set revlist [lreverse $previous_records]
foreach rec $revlist { foreach rec $revlist {
if {[dict get $rec tag] in [list "INSTALL-RECORD" "MODIFY-RECORD" "DELETE-RECORD" "VIRTUAL-RECORD"]} { switch -- [dict get $rec tag] {
INSTALL-RECORD - MODIFY-RECORD - DELETE-RECORD - VIRTUAL-RECORD {
return $rec return $rec
} }
} }
}
return [list] return [list]
} }
} }

1
src/bootsupport/modules/textutil/wcswidth-35.1.tm

@ -8,6 +8,7 @@
# Author: Sean Woods <yoda@etoyoc.com> # Author: Sean Woods <yoda@etoyoc.com>
### ###
package provide textutil::wcswidth 35.1 package provide textutil::wcswidth 35.1
namespace eval ::textutil {}
proc ::textutil::wcswidth_type char { proc ::textutil::wcswidth_type char {
if {$char == 161} { return A } if {$char == 161} { return A }
if {$char == 164} { return A } if {$char == 164} { return A }

47
src/modules/flagfilter-0.3.tm

@ -731,7 +731,8 @@ namespace eval flagfilter {
lassign $vinfo class type val lassign $vinfo class type val
if {[string match $classmatch $class]} { if {[string match $classmatch $class]} {
set a [llength $all_flagged] ;#index into all_flagged list we are building set a [llength $all_flagged] ;#index into all_flagged list we are building
if {$type eq "soloflag"} { switch -- $type {
soloflag {
if {[dict exists $seenflag $val]} { if {[dict exists $seenflag $val]} {
set seenindex [dict get $seenflag $val] set seenindex [dict get $seenflag $val]
set seenindexplus [expr {$seenindex+1}] set seenindexplus [expr {$seenindex+1}]
@ -742,13 +743,15 @@ namespace eval flagfilter {
dict set seenflag $val $a dict set seenflag $val $a
lappend all_flagged $val 1 lappend all_flagged $val 1
} }
} elseif {$type eq "flag"} { }
flag {
if {![dict exists $seenflag $val]} { if {![dict exists $seenflag $val]} {
dict set seenflag $val $a dict set seenflag $val $a
lappend all_flagged $val lappend all_flagged $val
} }
#no need to do anything if already seen - flagvalue must be next, and it will work out where to go. #no need to do anything if already seen - flagvalue must be next, and it will work out where to go.
} elseif {$type eq "flagvalue"} { }
flagvalue {
set idxflagfor [expr {$k -1}] set idxflagfor [expr {$k -1}]
set flagforinfo [dict get $o_map $idxflagfor] set flagforinfo [dict get $o_map $idxflagfor]
lassign $flagforinfo ffclass fftype ffval lassign $flagforinfo ffclass fftype ffval
@ -767,6 +770,7 @@ namespace eval flagfilter {
} }
} }
} }
}
return $all_flagged return $all_flagged
} }
method typedrange_class_type_from_arg {argclass argtype} { method typedrange_class_type_from_arg {argclass argtype} {
@ -2230,24 +2234,29 @@ namespace eval flagfilter {
#jn concat allows $command to itself be a list #jn concat allows $command to itself be a list
##tcl dispatchtype ##tcl dispatchtype
dict set dispatchrecord dispatchtype $dispatchtype dict set dispatchrecord dispatchtype $dispatchtype
if {$dispatchtype eq "tcl"} { switch -- $dispatchtype {
tcl {
do_debug 1 $debugc "DISPATCHING with tcl arg order: $command $matched_operands $matched_opts $extraflags" do_debug 1 $debugc "DISPATCHING with tcl arg order: $command $matched_operands $matched_opts $extraflags"
#set commandline [list $command {*}$matched_operands {*}$matched_opts {*}$extraflags] #set commandline [list $command {*}$matched_operands {*}$matched_opts {*}$extraflags]
set commandline [concat $command $matched_operands $matched_opts $extraflags] set commandline [concat $command $matched_operands $matched_opts $extraflags]
} elseif {$dispatchtype eq "raw"} { }
raw {
do_debug 1 $debugc "DISPATCHING with raw args : $command [dict get $dispatchrecord raw]" do_debug 1 $debugc "DISPATCHING with raw args : $command [dict get $dispatchrecord raw]"
#set commandline [list $command {*}[dict get $dispatchrecord raw] {*}$extraflags] #set commandline [list $command {*}[dict get $dispatchrecord raw] {*}$extraflags]
set commandline [concat $command [dict get $dispatchrecord raw] $extraflags] set commandline [concat $command [dict get $dispatchrecord raw] $extraflags]
} elseif {$dispatchtype eq "shell"} { }
shell {
do_debug 1 $debugc "DISPATCHING with shell args : $command [dict get $dispatchrecord raw]" do_debug 1 $debugc "DISPATCHING with shell args : $command [dict get $dispatchrecord raw]"
#assume the shell arguments are in one quoted string? #assume the shell arguments are in one quoted string?
set commandline [concat $command [list [dict get $dispatchrecord raw]] $extraflags] set commandline [concat $command [list [dict get $dispatchrecord raw]] $extraflags]
} else { }
default {
#non quoted shell? raw + defaults? #non quoted shell? raw + defaults?
do_debug 1 $debugc "DISPATCHING with given arg order: $command $matched_in_order $extraflags" do_debug 1 $debugc "DISPATCHING with given arg order: $command $matched_in_order $extraflags"
#set commandline [list $command {*}$matched_in_order {*}$extraflags] #set commandline [list $command {*}$matched_in_order {*}$extraflags]
set commandline [concat $command $matched_in_order $extraflags] set commandline [concat $command $matched_in_order $extraflags]
} }
}
dict set dispatchrecord asdispatched $commandline dict set dispatchrecord asdispatched $commandline
set dispatchresult "" set dispatchresult ""
@ -2378,7 +2387,8 @@ namespace eval flagfilter {
do_debug 1 $debugc "[string repeat = 40]" do_debug 1 $debugc "[string repeat = 40]"
foreach {k v} $combined { foreach {k v} $combined {
set dlev [dict get $debugdict $k] set dlev [dict get $debugdict $k]
if {$k eq "dispatch"} { switch -- $k {
dispatch {
set col1 [string repeat " " 12] set col1 [string repeat " " 12]
#process as paired list rather than dict (support repeated commands) #process as paired list rather than dict (support repeated commands)
set i 0 set i 0
@ -2413,7 +2423,8 @@ namespace eval flagfilter {
#foreach {nm rem} [lrange $v 2 end] { #foreach {nm rem} [lrange $v 2 end] {
# do_debug 1 $debugc "[overtype::left $col1 { ... }] $nm [list $rem]" # do_debug 1 $debugc "[overtype::left $col1 { ... }] $nm [list $rem]"
#} #}
} elseif {$k eq "dispatchresultlist"} { }
dispatchresultlist {
set col1 [string repeat " " 25] set col1 [string repeat " " 25]
set i 0 set i 0
foreach dresult $v { foreach dresult $v {
@ -2425,7 +2436,8 @@ namespace eval flagfilter {
do_debug $dlev $debugc "$c1 $dresult" do_debug $dlev $debugc "$c1 $dresult"
incr i incr i
} }
} elseif {$k eq "classifications"} { }
classifications {
set col1 [string repeat " " 25] set col1 [string repeat " " 25]
set len [dict size $v] set len [dict size $v]
if {$len == 0} { if {$len == 0} {
@ -2453,7 +2465,8 @@ namespace eval flagfilter {
} }
do_debug $dlev $debugc "$c1 [string trim $line]" do_debug $dlev $debugc "$c1 [string trim $line]"
} }
} elseif {$k eq "gridstring"} { }
gridstring {
set col1 [string repeat " " 25] set col1 [string repeat " " 25]
set i 0 set i 0
foreach ln [split $v \n] { foreach ln [split $v \n] {
@ -2465,12 +2478,13 @@ namespace eval flagfilter {
do_debug $dlev $debugc "$c1 $ln" do_debug $dlev $debugc "$c1 $ln"
incr i incr i
} }
}
} else { default {
set col1 [string repeat " " 25] set col1 [string repeat " " 25]
do_debug $dlev $debugc "[overtype::left $col1 $k] $v" do_debug $dlev $debugc "[overtype::left $col1 $k] $v"
} }
} }
}
do_debug 1 $debugc "[string repeat = 40]" do_debug 1 $debugc "[string repeat = 40]"
do_debug 1 $debugc "DEBUG-END $caller" do_debug 1 $debugc "DEBUG-END $caller"
if {[string length $raise_dispatch_error_instead_of_return]} { if {[string length $raise_dispatch_error_instead_of_return]} {
@ -2495,10 +2509,11 @@ namespace eval flagfilter {
for {set i $a} {$i <=$b} {incr i} { for {set i $a} {$i <=$b} {incr i} {
set arginfo [dict get $classifications $i] set arginfo [dict get $classifications $i]
lassign $arginfo class ftype v lassign $arginfo class ftype v
if {$ftype eq "flag"} { switch -- $ftype {
flag - flagvalue {
lappend extraflags $v lappend extraflags $v
} }
if {$ftype eq "soloflag"} { soloflag {
lappend extraflags $v lappend extraflags $v
if {[dict exists $defaults $v]} { if {[dict exists $defaults $v]} {
lappend extraflags [dict get $defaults $v] lappend extraflags [dict get $defaults $v]
@ -2506,8 +2521,6 @@ namespace eval flagfilter {
lappend extraflags 1 lappend extraflags 1
} }
} }
if {$ftype eq "flagvalue"} {
lappend extraflags $v
} }
} }
foreach {k v} [dict get $defaults] { foreach {k v} [dict get $defaults] {

264
src/modules/punk-0.1.tm

@ -729,8 +729,8 @@ namespace eval punk {
set already_assigned 0 set already_assigned 0
set do_bounds_check 0 ;#modified by leading single @ for list operations - doesn't apply to certain items like 'head','tail' which have specifically defined bounds-checks implicit in their normal meaning. set do_bounds_check 0 ;#modified by leading single @ for list operations - doesn't apply to certain items like 'head','tail' which have specifically defined bounds-checks implicit in their normal meaning.
#thse have anyhead and anytail for explicit allowance to be used on lists with insufficient items to produce values. #thse have anyhead and anytail for explicit allowance to be used on lists with insufficient items to produce values.
switch -- $index {
if {$index eq "#"} { # {
set active_key_type "list" set active_key_type "list"
if {![catch {llength $leveldata} assigned]} { if {![catch {llength $leveldata} assigned]} {
set already_assigned 1 set already_assigned 1
@ -738,7 +738,8 @@ namespace eval punk {
set action ?mismatch-not-a-list set action ?mismatch-not-a-list
break break
} }
} elseif {$index eq "##"} { }
## {
set active_key_type "dict" set active_key_type "dict"
if {![catch {dict size $leveldata} assigned]} { if {![catch {dict size $leveldata} assigned]} {
set already_assigned 1 set already_assigned 1
@ -746,10 +747,12 @@ namespace eval punk {
set action ?mismatch-not-a-dict set action ?mismatch-not-a-dict
break break
} }
} elseif {$index eq "#?"} { }
#? {
set assigned [string length $leveldata] set assigned [string length $leveldata]
set already_assigned 1 set already_assigned 1
} elseif {$index eq "@"} { }
@ {
upvar v_list_idx v_list_idx ;#positional tracker for /@ - list position upvar v_list_idx v_list_idx ;#positional tracker for /@ - list position
set active_key_type "list" set active_key_type "list"
#e.g @1/1/@/1 the lone @ is a positional spec for this specific subkey #e.g @1/1/@/1 the lone @ is a positional spec for this specific subkey
@ -771,9 +774,10 @@ namespace eval punk {
} }
set assigned [lindex $leveldata $index] set assigned [lindex $leveldata $index]
set already_assigned 1 set already_assigned 1
}
} else { default {
if {$index in [list "@@" "@?@" "@??@"]} { switch -exact -- $index {
@@ - @?@ - @??@ {
set active_key_type "dict" set active_key_type "dict"
#NOTE: it may at first seem pointless to use @@/key, since we have to know the key - but this can be used to match 'key' only at the first position in .= list key {x y} key2 etc #NOTE: it may at first seem pointless to use @@/key, since we have to know the key - but this can be used to match 'key' only at the first position in .= list key {x y} key2 etc
@ -810,7 +814,10 @@ namespace eval punk {
} }
} }
set already_assigned 1 set already_assigned 1
} elseif {[string match @@* $index]} { }
default {
switch -glob -- $index {
@@* {
set active_key_type "dict" set active_key_type "dict"
set key [string range $index 2 end] set key [string range $index 2 end]
#dict exists test is safe - no need for catch #dict exists test is safe - no need for catch
@ -821,7 +828,8 @@ namespace eval punk {
break break
} }
set already_assigned 1 set already_assigned 1
} elseif {[string match {@\?@*} $index]} { }
{@\?@*} {
set active_key_type "dict" set active_key_type "dict"
set key [string range $index 3 end] set key [string range $index 3 end]
#dict exists test is safe - no need for catch #dict exists test is safe - no need for catch
@ -831,7 +839,8 @@ namespace eval punk {
set assigned [list] set assigned [list]
} }
set already_assigned 1 set already_assigned 1
} elseif {[string match {@\?\?@*} $index]} { }
{@\?\?@*} {
set active_key_type "dict" set active_key_type "dict"
set key [string range $index 4 end] set key [string range $index 4 end]
#dict exists test is safe - no need for catch #dict exists test is safe - no need for catch
@ -841,38 +850,49 @@ namespace eval punk {
set assigned [list] set assigned [list]
} }
set already_assigned 1 set already_assigned 1
} elseif {[string match @* $index]} { }
@* {
set active_key_type "list" set active_key_type "list"
set do_bounds_check 1 set do_bounds_check 1
set index [string trimleft $index @] set index [string trimleft $index @]
} else { }
default {
# #
} }
}
}
}
if {!$already_assigned} { if {!$already_assigned} {
if {[string match "not-*" $index] && $active_key_type in [list "" "list"]} { if {[string match "not-*" $index] && $active_key_type in [list "" "list"]} {
#e.g not-0-end-1 not-end-4-end-2 #e.g not-0-end-1 not-end-4-end-2
set get_not 1 set get_not 1
#cherry-pick some easy cases, and either assign, or re-map to corresponding index #cherry-pick some easy cases, and either assign, or re-map to corresponding index
if {$index eq "not-tail"} { switch -- $index {
not-tail {
set active_key_type "list" set active_key_type "list"
set assigned [lindex $leveldata 0]; set already_assigned 1 set assigned [lindex $leveldata 0]; set already_assigned 1
} elseif {$index in [list "not-head" "not-0"]} { }
not-head {
set active_key_type "list" set active_key_type "list"
#set selector "tail"; set get_not 0 #set selector "tail"; set get_not 0
set assigned [lrange $leveldata 1 end]; set already_assigned 1 set assigned [lrange $leveldata 1 end]; set already_assigned 1
} elseif {$index eq "not-end"} { }
not-end {
set active_key_type "list" set active_key_type "list"
set assigned [lrange $leveldata 0 end-1]; set already_assigned 1 set assigned [lrange $leveldata 0 end-1]; set already_assigned 1
} else { }
default {
#trim off the not- and let the remaining index handle based on get_not being 1 #trim off the not- and let the remaining index handle based on get_not being 1
set index [string range $index 4 end] set index [string range $index 4 end]
} }
} }
} }
}
}
} }
if {!$already_assigned} { if {!$already_assigned} {
@ -1496,24 +1516,29 @@ namespace eval punk {
append script \n {#e.g not-0-end-1 not-end-4-end-2} append script \n {#e.g not-0-end-1 not-end-4-end-2}
set get_not 1 set get_not 1
#cherry-pick some easy cases, and either assign, or re-map to corresponding index #cherry-pick some easy cases, and either assign, or re-map to corresponding index
if {$index eq "not-tail"} { switch -- $index {
not-tail {
append script \n {# set active_key_type "list"} append script \n {# set active_key_type "list"}
append script \n {set assigned [lindex $leveldata 0]} append script \n {set assigned [lindex $leveldata 0]}
set level_script_complete 1 set level_script_complete 1
} elseif {$index in [list "not-head" "not-0"]} { }
not-head - not-0 {
append script \n {# set active_key_type "list"} append script \n {# set active_key_type "list"}
append script \n {set assigned [lrange $leveldata 1 end]} append script \n {set assigned [lrange $leveldata 1 end]}
set level_script_complete 1 set level_script_complete 1
} elseif {$index eq "not-end"} { }
not-end {
append script \n {# set active_key_type "list"} append script \n {# set active_key_type "list"}
append script \n {set assigned [lrange $leveldata 0 end-1]} append script \n {set assigned [lrange $leveldata 0 end-1]}
set level_script_complete 1 set level_script_complete 1
} else { }
default {
#trim off the not- and let the remaining index handle based on get_not being 1 #trim off the not- and let the remaining index handle based on get_not being 1
set index [string range $index 4 end] set index [string range $index 4 end]
append script \n "set index $index" append script \n "set index $index"
} }
} }
}
} }
@ -1969,39 +1994,46 @@ namespace eval punk {
lappend var_class [list $v_key 0] lappend var_class [list $v_key 0]
lappend varspecs_trimmed $v_key lappend varspecs_trimmed $v_key
} else { } else {
set firstchar [string index $v 0]
set lastchar [string index $v end] set lastchar [string index $v end]
if {$lastchar eq "+"} { switch -- $lastchar {
+ {
lappend classes 9 lappend classes 9
set vname [string range $v 0 end-1] set vname [string range $v 0 end-1]
} }
if {$lastchar eq "-"} { - {
lappend classes 10 lappend classes 10
set vname [string range $v 0 end-1] set vname [string range $v 0 end-1]
} }
if {$firstchar in $leading_classifiers} { }
if {$firstchar eq "'"} { set firstchar [string index $v 0]
switch -- $firstchar {
' {
lappend var_class [list $v_key 1] lappend var_class [list $v_key 1]
#set vname [string range $v 1 end] #set vname [string range $v 1 end]
lappend varspecs_trimmed [list $vname $key] lappend varspecs_trimmed [list $vname $key]
} elseif {$firstchar eq "^"} { }
^ {
lappend classes [list 2] lappend classes [list 2]
#use vname - may already have trailing +/- stripped #use vname - may already have trailing +/- stripped
set vname [string range $vname 1 end] set vname [string range $vname 1 end]
set secondclassifier [string index $v 1] set secondclassifier [string index $v 1]
if {$secondclassifier eq "&"} { switch -- $secondclassifier {
"&" {
#pinned boolean #pinned boolean
lappend classes 3 lappend classes 3
set vname [string range $v 2 end] set vname [string range $v 2 end]
} elseif {$secondclassifier eq "#"} { }
"#" {
#pinned numeric comparison instead of string comparison #pinned numeric comparison instead of string comparison
lappend classes 8 lappend classes 8
set vname [string range $vname 1 end] set vname [string range $vname 1 end]
} elseif {$secondclassifier eq "*"} { }
"*" {
#pinned glob #pinned glob
lappend classes 7 lappend classes 7
set vname [string range $v 2 end] set vname [string range $v 2 end]
} }
}
#todo - check for second tag - & for pinned boolean? #todo - check for second tag - & for pinned boolean?
#consider requiring ^# for numeric comparisons. currently no way to do a strictly string comparison on pinned variables.... default ^var really shouldn't be doing any magic. #consider requiring ^# for numeric comparisons. currently no way to do a strictly string comparison on pinned variables.... default ^var really shouldn't be doing any magic.
#while we're at it.. pinned glob would be nice. ^* #while we're at it.. pinned glob would be nice. ^*
@ -2009,7 +2041,8 @@ namespace eval punk {
#These all limit the range of varnames permissible - which is no big deal. #These all limit the range of varnames permissible - which is no big deal.
lappend var_class [list $v_key $classes] lappend var_class [list $v_key $classes]
lappend varspecs_trimmed [list $vname $key] lappend varspecs_trimmed [list $vname $key]
} elseif {$firstchar eq "&"} { }
& {
#we require boolean literals to be single-quoted so we can use cross-binding on boolean vars. #we require boolean literals to be single-quoted so we can use cross-binding on boolean vars.
#ie &true is the variable true whereas &'true' or &'1' &'t' etc are literal booleans #ie &true is the variable true whereas &'true' or &'1' &'t' etc are literal booleans
#allow exception of &1 &0 to be literal booleans - because we disallow 0 & 1 as varnames in other contexts anyway - so it would be more consistent not to treat as varnames here. #allow exception of &1 &0 to be literal booleans - because we disallow 0 & 1 as varnames in other contexts anyway - so it would be more consistent not to treat as varnames here.
@ -2017,8 +2050,7 @@ namespace eval punk {
set vname [string range $v 1 end] set vname [string range $v 1 end]
lappend varspecs_trimmed [list $vname $key] lappend varspecs_trimmed [list $vname $key]
} }
default {
} else {
if {([string first ? $v]) >=0 || ([string first * $v] >=0)} { if {([string first ? $v]) >=0 || ([string first * $v] >=0)} {
lappend var_class [list $v_key 7] ;#glob lappend var_class [list $v_key 7] ;#glob
#leave vname as the full glob #leave vname as the full glob
@ -2049,7 +2081,7 @@ namespace eval punk {
} }
} }
} }
}
} }
lappend var_names $vname lappend var_names $vname
} }
@ -2662,18 +2694,20 @@ namespace eval punk {
#unpinned non-atoms #unpinned non-atoms
#cross-binding. Within this multivar pattern group only (use pin ^ for binding to result from a previous pattern) #cross-binding. Within this multivar pattern group only (use pin ^ for binding to result from a previous pattern)
# #
if {$varname eq ""} { switch -- $varname {
"" {
#don't attempt cross-bind on empty-varname #don't attempt cross-bind on empty-varname
lset match_state $i 1 lset match_state $i 1
#don't change var_action $i 1 to set #don't change var_action $i 1 to set
lset expected_values $i [list var $varname spec $lhsspec info "match-no-lhs-var" lhs - rhs $val] lset expected_values $i [list var $varname spec $lhsspec info "match-no-lhs-var" lhs - rhs $val]
} elseif {$varname eq "_"} { }
"_" {
#don't cross-bind on the special 'don't-care' varname #don't cross-bind on the special 'don't-care' varname
lset match_state $i 1 lset match_state $i 1
lset var_actions $i 1 [string range $act 1 end] ;# ?set -> set lset var_actions $i 1 [string range $act 1 end] ;# ?set -> set
lset expected_values $i [list var $varname spec $lhsspec info "match-any-lhs-dontcare-var" lhs - rhs $val] lset expected_values $i [list var $varname spec $lhsspec info "match-any-lhs-dontcare-var" lhs - rhs $val]
} else { }
default {
set first_bound [lsearch -index 0 $var_actions $varname] set first_bound [lsearch -index 0 $var_actions $varname]
#assert first_bound >=0, we will always find something - usually self #assert first_bound >=0, we will always find something - usually self
if {$first_bound == $i} { if {$first_bound == $i} {
@ -2696,7 +2730,7 @@ namespace eval punk {
} }
} }
} }
}
} }
incr i incr i
@ -3401,11 +3435,7 @@ namespace eval punk {
return 1 return 1
} elseif {[string first " " $arg] >= 0 || [string first \t $arg] >= 0} { } elseif {[string first " " $arg] >= 0 || [string first \t $arg] >= 0} {
lassign [_rhs_tail_split $arg] _ part2 ;#will have part2 if unquoted whitespace found lassign [_rhs_tail_split $arg] _ part2 ;#will have part2 if unquoted whitespace found
if {$part2 eq ""} { return [expr {$part2 ne ""}]
return 0
} else {
return 1
}
} else { } else {
return 0 return 0
} }
@ -3426,14 +3456,27 @@ namespace eval punk {
set indq 0 set indq 0
} }
} else { } else {
if {$ch eq {'}} { switch -- $ch {
{'} {
set inq 1 set inq 1
} elseif {$ch eq {"}} { }
{"} {
set indq 1 set indq 1
} elseif {$ch in [list " " \t]} { }
" " {
#whitespace outside of quoting #whitespace outside of quoting
break break
} }
0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - a - b - c - d - e - f - g - h - i - j - k - l - m - n - o - p - q - r - s - t - u - v - w - x - y - z {}
default {
#\t not a literal for switch jumpTable bytecompile - review - can we do it without for example string mapping to <t> (and without a literal binary tab in source file)?
#we can't (reliably?) put \t as one of our switch keys
#
if {$ch eq "\t"} {
break
}
}
}
append equalsrhs $ch append equalsrhs $ch
} }
incr i incr i
@ -3455,13 +3498,15 @@ namespace eval punk {
#nextail is tail for possible recursion based on first argument in the segment #nextail is tail for possible recursion based on first argument in the segment
set nexttail [lassign $fulltail next1] ;#tail head set nexttail [lassign $fulltail next1] ;#tail head
if {$next1 eq "pipematch"} { switch -- $next1 {
pipematch {
set results [uplevel 1 [list pipematch {*}$nexttail]] set results [uplevel 1 [list pipematch {*}$nexttail]]
debug.punk.pipe {>>> pipematch results: $results} 1 debug.punk.pipe {>>> pipematch results: $results} 1
set d [_multi_bind_result $initial_returnvarspec $results] set d [_multi_bind_result $initial_returnvarspec $results]
return [_handle_bind_result $d] return [_handle_bind_result $d]
} elseif {$next1 eq "pipecase"} { }
pipecase {
set msg "pipesyntax\n" set msg "pipesyntax\n"
append msg "pipecase does not return a value directly in the normal way\n" append msg "pipecase does not return a value directly in the normal way\n"
append msg "It will return a casemismatch dict on mismatch\n" append msg "It will return a casemismatch dict on mismatch\n"
@ -3470,6 +3515,7 @@ namespace eval punk {
append msg "Call pipecase from within a pipeline script block or wrapper such as pipeswitch or apply." append msg "Call pipecase from within a pipeline script block or wrapper such as pipeswitch or apply."
error $msg error $msg
} }
}
#temp - this is related to a script for the entire pipeline (functional composition) - not the script for the segment-based x=y or x.=y proc. #temp - this is related to a script for the entire pipeline (functional composition) - not the script for the segment-based x=y or x.=y proc.
set ::_pipescript "" set ::_pipescript ""
@ -3690,8 +3736,6 @@ namespace eval punk {
#whether the segment has insertion_patterns or not - apply any modification from the piper argspecs (script will use modified args/data) #whether the segment has insertion_patterns or not - apply any modification from the piper argspecs (script will use modified args/data)
#pipedvars comes from either previous segment |>, or <| args #pipedvars comes from either previous segment |>, or <| args
if {[dict exists $pipedvars "data"]} { if {[dict exists $pipedvars "data"]} {
@ -4229,6 +4273,10 @@ namespace eval punk {
#it is significantly faster to call a proc like this than to inline it in the unknown proc #it is significantly faster to call a proc like this than to inline it in the unknown proc
proc ::punk::range {from to args} { proc ::punk::range {from to args} {
if {[info commands lseq] ne ""} {
#tcl 8.7+ lseq significantly faster for larger ranges
return [lseq $from $to]
}
set count [expr {($to -$from) + 1}] set count [expr {($to -$from) + 1}]
incr from -1 incr from -1
return [lmap v [lrepeat $count 0] {incr from}] return [lmap v [lrepeat $count 0] {incr from}]
@ -4485,19 +4533,23 @@ namespace eval punk {
#puts stderr "pipematch erroptions:$erroptions" #puts stderr "pipematch erroptions:$erroptions"
#debug.punk.pipe {pipematch error $result} 4 #debug.punk.pipe {pipematch error $result} 4
set ecode [dict get $erroptions -errorcode] set ecode [dict get $erroptions -errorcode]
if {[lrange $ecode 0 1] eq "binding mismatch"} { switch -- [lindex $ecode 0] {
binding {
if {[lindex $ecode 1] eq "mismatch"} {
#error {reason xxx} should only be returned for underlying tcl errors. error {someotherkey xxx} for structured errors such as a binding mismatch #error {reason xxx} should only be returned for underlying tcl errors. error {someotherkey xxx} for structured errors such as a binding mismatch
#return [dict create error [dict create mismatch $result]] #return [dict create error [dict create mismatch $result]]
#puts stderr "pipematch converting error to {error {mismatch <result>}}" #puts stderr "pipematch converting error to {error {mismatch <result>}}"
return [list error [list mismatch $result]] return [list error [list mismatch $result]]
} }
if {[lindex $ecode 0] eq "pipesyntax"} { }
pipesyntax {
#error $result #error $result
return -options $erroptions $result return -options $erroptions $result
} }
if {[lindex $ecode 0] eq "casematch"} { casematch {
return $result return $result
} }
}
#return [dict create error [dict create reason $result]] #return [dict create error [dict create reason $result]]
return [list error [list reason $result]] return [list error [list reason $result]]
} else { } else {
@ -4596,42 +4648,49 @@ namespace eval punk {
if {[catch {uplevel 1 [list ::if 1 $cmdlist]} result erroptions]} { if {[catch {uplevel 1 [list ::if 1 $cmdlist]} result erroptions]} {
#puts stderr "====>>> result: $result erroptions" #puts stderr "====>>> result: $result erroptions"
set ecode [dict get $erroptions -errorcode] set ecode [dict get $erroptions -errorcode]
if {[lindex $ecode 0] eq "pipesyntax"} { switch -- [lindex $ecode 0] {
pipesyntax {
#error $result #error $result
return -options $erroptions $result return -options $erroptions $result
} }
if {[lindex $ecode 0] eq "casenomatch"} { casenomatch {
return -options $erroptions $result return -options $erroptions $result
} }
if {[lrange $ecode 0 1] eq "binding mismatch"} { binding {
if {[lindex $ecode 1] eq "mismatch"} {
#error {reason xxx} should only be returned for underlying tcl errors. error {someotherkey xxx} for structured errors such as a binding mismatch #error {reason xxx} should only be returned for underlying tcl errors. error {someotherkey xxx} for structured errors such as a binding mismatch
#return [dict create error [dict create mismatch $result]] #return [dict create error [dict create mismatch $result]]
# #
#NOTE: casemismatch is part of the api for pipecase. It is a casemismatch rather than an error - because for a pipecase - a casemismatch is an expected event (many casemismatches - one match) #NOTE: casemismatch is part of the api for pipecase. It is a casemismatch rather than an error - because for a pipecase - a casemismatch is an expected event (many casemismatches - one match)
return [dict create casemismatch $result] return [dict create casemismatch $result]
} }
}
}
#we can't always treat $result as a list - may be an error string which can't be represented as a list, and there may be no useful errorCode #we can't always treat $result as a list - may be an error string which can't be represented as a list, and there may be no useful errorCode
#todo - use errorCode instead #todo - use errorCode instead
if {[catch {lindex $result 0} word1]} { if {[catch {lindex $result 0} word1]} {
#tailcall error $result #tailcall error $result
return -options $erroptions $result return -options $erroptions $result
} else { } else {
if {$word1 in [list "switcherror" "funerror"]} { switch -- $word1 {
switcherror - funerror {
error $result "pipecase [lsearch -all -inline $args "*="]" error $result "pipecase [lsearch -all -inline $args "*="]"
} }
if {$word1 in [list "resultswitcherror" "resultfunerror"]} { resultswitcherror - resultfunerror {
#recast the error as a result without @@ok wrapping #recast the error as a result without @@ok wrapping
#use the tailcall return to stop processing other cases in the switch! #use the tailcall return to stop processing other cases in the switch!
tailcall return [dict create error $result] tailcall return [dict create error $result]
} }
if {$word1 eq "ignore"} { ignore {
#suppress error, but use normal return #suppress error, but use normal return
return [dict create error [dict create suppressed $result]] return [dict create error [dict create suppressed $result]]
} else { }
default {
#normal tcl error #normal tcl error
#return [dict create error [dict create reason $result]] #return [dict create error [dict create reason $result]]
tailcall error $result "pipecase $args" [list caseerror] tailcall error $result "pipecase $args" [list caseerror]
}
} }
} }
} else { } else {
@ -4688,11 +4747,13 @@ namespace eval punk {
set r [apply [list {data} $e] $r] set r [apply [list {data} $e] $r]
} else { } else {
if {[llength $e] == 1} { if {[llength $e] == 1} {
if {$e eq {>}} { switch -- $e {
> {
#output to calling context. only pipedata return value and '> varname' should affect caller. #output to calling context. only pipedata return value and '> varname' should affect caller.
incr i incr i
uplevel 1 [list set [lindex $args $i] $r] uplevel 1 [list set [lindex $args $i] $r]
} elseif {$e in {% pipematch ispipematch}} { }
% - pipematch - ispipematch {
incr i incr i
set e2 [lindex $args $i] set e2 [lindex $args $i]
#set body [list $e {*}$e2] #set body [list $e {*}$e2]
@ -4705,7 +4766,8 @@ namespace eval punk {
set applylist [list {data} $body] set applylist [list {data} $body]
#puts stderr $applylist #puts stderr $applylist
set r [apply $applylist $r] set r [apply $applylist $r]
} elseif {$e in [list pipeswitch pipeswitchc]} { }
pipeswitch - pipeswitchc {
#pipeswitch takes a script not a list. #pipeswitch takes a script not a list.
incr i incr i
set e2 [lindex $args $i] set e2 [lindex $args $i]
@ -4718,11 +4780,13 @@ namespace eval punk {
set applylist [list {data} $body] set applylist [list {data} $body]
#puts stderr $applylist #puts stderr $applylist
set r [apply $applylist $r] set r [apply $applylist $r]
} else { }
default {
#puts "other single arg: [list $e $r]" #puts "other single arg: [list $e $r]"
append e { $data} append e { $data}
set r [apply [list {data} $e] $r] set r [apply [list {data} $e] $r]
} }
}
} elseif {[llength $e] == 0} { } elseif {[llength $e] == 0} {
#do nothing - pass data through #do nothing - pass data through
#leave r as is. #leave r as is.
@ -4758,16 +4822,20 @@ namespace eval punk {
return [list stdout [dict get $running_config color_stdout] stderr [dict get $running_config color_stderr]] return [list stdout [dict get $running_config color_stdout] stderr [dict get $running_config color_stderr]]
} else { } else {
set lower_onoff [string tolower $onoff] set lower_onoff [string tolower $onoff]
if {$lower_onoff in [list true on 1]} { switch -- $lower_onoff {
true - on - 1 {
dict set running_config color_stdout [dict get $startup_config color_stdout] dict set running_config color_stdout [dict get $startup_config color_stdout]
dict set running_config color_stderr [dict get $startup_config color_stderr] dict set running_config color_stderr [dict get $startup_config color_stderr]
} elseif {$lower_onoff in [list false off 0]} { }
false - off - 0 {
dict set running_config color_stdout "" dict set running_config color_stdout ""
dict set running_config color_stderr "" dict set running_config color_stderr ""
} else { }
default {
error "channelcolors: invalid value $onoff - expected true|false|on|off|1|0" error "channelcolors: invalid value $onoff - expected true|false|on|off|1|0"
} }
} }
}
return [list stdout [dict get $running_config color_stdout] stderr [dict get $running_config color_stderr]] return [list stdout [dict get $running_config color_stdout] stderr [dict get $running_config color_stderr]]
} }
@ -5354,7 +5422,8 @@ namespace eval punk {
set relativepath [expr {[file pathtype $searchspec] eq "relative"}] set relativepath [expr {[file pathtype $searchspec] eq "relative"}]
set searchbase $opt_searchbase set searchbase $opt_searchbase
if {$opt_glob eq ""} { switch -- $opt_glob {
"" {
if {$relativepath} { if {$relativepath} {
set location [file dirname [file join $searchbase $searchspec]] set location [file dirname [file join $searchbase $searchspec]]
} else { } else {
@ -5362,11 +5431,9 @@ namespace eval punk {
} }
#here tail is treated as a search-pattern within location whether or not it contains glob chars "?" or "*" #here tail is treated as a search-pattern within location whether or not it contains glob chars "?" or "*"
set glob [file tail $searchspec] set glob [file tail $searchspec]
} else { }
set tail [file tail $searchspec] "\uFFFF" {
set tail_has_globs [regexp {[*?]} $tail] set tail_has_globs [regexp {[*?]} [file tail $searchspec]]
if {$opt_glob eq "\uFFFF"} {
if {$tail_has_globs} { if {$tail_has_globs} {
if {$relativepath} { if {$relativepath} {
set location [file dirname [file join $searchbase $searchspec]] set location [file dirname [file join $searchbase $searchspec]]
@ -5383,7 +5450,8 @@ namespace eval punk {
} }
set glob * set glob *
} }
} else { }
default {
#-tailglob supplied separately - ignore any globiness in tail segment of searchspec and treat literally #-tailglob supplied separately - ignore any globiness in tail segment of searchspec and treat literally
if {$relativepath} { if {$relativepath} {
set location [file join $searchbase $searchspec] set location [file join $searchbase $searchspec]
@ -5800,7 +5868,7 @@ namespace eval punk {
set PostDecimalP 0; set PostDecimalP 0;
} }
# Now extract any leading spaces. # Now extract any leading spaces. review - regex for whitespace instead of just ascii space?
set ind 0; set ind 0;
while {[string equal [string index $number $ind] \u0020]} { while {[string equal [string index $number $ind] \u0020]} {
incr ind; incr ind;
@ -6374,13 +6442,15 @@ namespace eval punk {
} }
method unknown {args} { method unknown {args} {
if {[llength $args]} { if {[llength $args]} {
set w1 [lindex $args 0] switch -- [lindex $args 0] {
if {$w1 in [list add delete insert transpose sort set swap]} { add - delete - insert - transpose - sort - set - swap {
$mcmd {*}$args $mcmd {*}$args
return [self] ;#result is the wrapper object for further chaining in pipelines return [self] ;#result is the wrapper object for further chaining in pipelines
} else { }
default {
tailcall $mcmd {*}$args tailcall $mcmd {*}$args
} }
}
} else { } else {
#will error.. but we should pass that on #will error.. but we should pass that on
tailcall $mcmd tailcall $mcmd
@ -6683,17 +6753,22 @@ namespace eval punk {
if {![info object isa $type $obj]} continue if {![info object isa $type $obj]} continue
set type set type
}] }]
if {"class" in $isa} { foreach tp $isa {
switch -- $tp {
class {
lappend info {class superclasses} {class mixins} {class filters} lappend info {class superclasses} {class mixins} {class filters}
lappend info {class methods} {class methods} lappend info {class methods} {class methods}
lappend info {class variables} {class variables} lappend info {class variables} {class variables}
} }
if {"object" in $isa} { object {
lappend info {object class} {object mixins} {object filters} lappend info {object class} {object mixins} {object filters}
lappend info {object methods} {object methods} lappend info {object methods} {object methods}
lappend info {object variables} {object variables} lappend info {object variables} {object variables}
lappend info {object namespace} {object vars} ;#{object commands} lappend info {object namespace} {object vars} ;#{object commands}
} }
}
}
set result [dict create isa $isa] set result [dict create isa $isa]
foreach args $info { foreach args $info {
dict set result $args [info {*}$args $obj] dict set result $args [info {*}$args $obj]
@ -6747,11 +6822,12 @@ namespace eval punk {
} }
set limit [dict get $opts -limit] set limit [dict get $opts -limit]
set opt_ansi [dict get $opts -ansi] set opt_ansi [dict get $opts -ansi]
if {[string tolower $opt_ansi] ni [list 0 1 2 view]} { switch -- [string tolower $opt_ansi] {
0 - 1 - 2 {}
view {set opt_ansi 2}
default {
error "inspect -ansi 0|1|2|view - received -ansi $opt_ansi" error "inspect -ansi 0|1|2|view - received -ansi $opt_ansi"
} }
if {[string tolower $opt_ansi] eq "view"} {
set opt_ansi 2
} }
# -- --- --- --- --- # -- --- --- --- ---
@ -6765,9 +6841,11 @@ namespace eval punk {
set val $pipeargs set val $pipeargs
set count [llength $pipeargs] set count [llength $pipeargs]
} }
if {[string tolower $channel] in {nul null /dev/null}} { switch -- [string tolower $channel] {
nul - null - /dev/null {
return $val return $val
} }
}
set displayval $val ;#default - may be overridden based on -limit set displayval $val ;#default - may be overridden based on -limit
if {$count > 1} { if {$count > 1} {
@ -7271,19 +7349,23 @@ namespace eval punk {
} }
proc repl {startstop} { proc repl {startstop} {
if {$startstop eq "stop"} { switch -- $startstop {
stop {
if {$::repl::running} { if {$::repl::running} {
puts stdout "Attempting repl stop. Try ctrl-c or exit command to leave interpreter" puts stdout "Attempting repl stop. Try ctrl-c or exit command to leave interpreter"
set ::repl::done 1 set ::repl::done 1
} }
} elseif {$startstop eq "start"} { }
start {
if {!$::repl::running} { if {!$::repl::running} {
repl::start stdin repl::start stdin
} }
} else { }
default {
error "repl unknown action '$startstop' - must be start or stop" error "repl unknown action '$startstop' - must be start or stop"
} }
} }
}
} }
package require punk::mod package require punk::mod

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

File diff suppressed because it is too large Load Diff

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

@ -174,7 +174,6 @@ namespace eval punk::args {
#} $args #} $args
set optionspecs [string map [list \r\n \n] $optionspecs] set optionspecs [string map [list \r\n \n] $optionspecs]
set known_argspecs [list -default -type -range -choices -nocase -optional -multiple -validate_without_ansi -allow_ansi -strip_ansi]
set optspec_defaults [dict create\ set optspec_defaults [dict create\
-optional 1\ -optional 1\
-allow_ansi 1\ -allow_ansi 1\
@ -204,11 +203,8 @@ namespace eval punk::args {
foreach ln $records { foreach ln $records {
set trimln [string trim $ln] set trimln [string trim $ln]
if {$trimln eq ""} { switch -- [string index $trimln 0] {
continue "" - # {continue}
}
if {[string index $trimln 0] eq "#"} {
continue
} }
set argname [lindex $trimln 0] set argname [lindex $trimln 0]
set argspecs [lrange $trimln 1 end] set argspecs [lrange $trimln 1 end]
@ -224,10 +220,15 @@ namespace eval punk::args {
error "punk::args::opts_values - bad optionspecs line for argument '$argname' Remaining items on line must be in paired option-value format - received '$argspecs'" error "punk::args::opts_values - bad optionspecs line for argument '$argname' Remaining items on line must be in paired option-value format - received '$argspecs'"
} }
dict for {spec specval} $argspecs { dict for {spec specval} $argspecs {
if {$spec ni [concat $known_argspecs -ARGTYPE]} { #literal-key switch - bytecompiled to jumpTable
switch -- $spec {
-default - -type - -range - -choices - -nocase - -optional - -multiple - -validate_without_ansi - -allow_ansi - -strip_ansi - -ARGTYPE {}
default {
set known_argspecs [list -default -type -range -choices -nocase -optional -multiple -validate_without_ansi -allow_ansi -strip_ansi]
error "punk::args::opts_values - unrecognised key '$spec' in specifications for argument '$argname' Known option specification keys: $known_argspecs" error "punk::args::opts_values - unrecognised key '$spec' in specifications for argument '$argname' Known option specification keys: $known_argspecs"
} }
} }
}
set argspecs [dict merge $optspec_defaults $argspecs] set argspecs [dict merge $optspec_defaults $argspecs]
dict set arg_info $argname $argspecs dict set arg_info $argname $argspecs
if {![dict get $argspecs -optional]} { if {![dict get $argspecs -optional]} {
@ -450,7 +451,10 @@ namespace eval punk::args {
if {!$is_default} { if {!$is_default} {
if {[dict exists $arg_info $o -type]} { if {[dict exists $arg_info $o -type]} {
set type [dict get $arg_info $o -type] set type [dict get $arg_info $o -type]
if {[string tolower $type] in {int integer double}} { switch -- [string tolower $type] {
int -
integer -
double {
if {[string tolower $type] in {int integer}} { if {[string tolower $type] in {int integer}} {
foreach e $vlist e_check $vlist_check { foreach e $vlist e_check $vlist_check {
if {![string is integer -strict $e_check]} { if {![string is integer -strict $e_check]} {
@ -474,19 +478,38 @@ namespace eval punk::args {
} }
} }
} }
} elseif {[string tolower $type] in {bool boolean}} { }
bool -
boolean {
foreach e $vlist e_check $vlist_check { foreach e $vlist e_check $vlist_check {
if {![string is boolean -strict $e_check]} { if {![string is boolean -strict $e_check]} {
error "Option $o for $caller requires type 'boolean'. Received: '$e'" error "Option $o for $caller requires type 'boolean'. Received: '$e'"
} }
} }
} elseif {[string tolower $type] in {alnum alpha ascii control digit graph lower print punct space upper wordchar xdigit}} { }
alnum -
alpha -
ascii -
control -
digit -
graph -
lower -
print -
punct -
space -
upper -
wordchar -
xdigit {
foreach e $vlist e_check $vlist_check { foreach e $vlist e_check $vlist_check {
if {![string is [string tolower $type] $e_check]} { if {![string is [string tolower $type] $e_check]} {
error "Option $o for $caller requires type '[string tolower $type]'. Received: '$e'" error "Option $o for $caller requires type '[string tolower $type]'. Received: '$e'"
} }
} }
} elseif {[string tolower $type] in {file directory existingfile existingdirectory}} { }
file -
directory -
existingfile -
existingdirectory {
foreach e $vlist e_check $vlist_check { foreach e $vlist e_check $vlist_check {
if {!([string length $e_check]>0 && ![regexp {[\"*?<>\;]} $e_check])} { if {!([string length $e_check]>0 && ![regexp {[\"*?<>\;]} $e_check])} {
error "Option $o for $caller requires type '[string tolower $type]'. Received: '$e' which doesn't look like it could be a file or directory" error "Option $o for $caller requires type '[string tolower $type]'. Received: '$e' which doesn't look like it could be a file or directory"
@ -505,7 +528,9 @@ namespace eval punk::args {
} }
} }
} }
} elseif {[string tolower $type] in {char character}} { }
char -
character {
foreach e $vlist e_check $vlist_check { foreach e $vlist e_check $vlist_check {
if {[string length != 1]} { if {[string length != 1]} {
error "Option $o for $caller requires type '[string tolower $type]'. Received: '$e' which is not a single character" error "Option $o for $caller requires type '[string tolower $type]'. Received: '$e' which is not a single character"
@ -513,6 +538,7 @@ namespace eval punk::args {
} }
} }
} }
}
if {[dict exists $arg_info $o -choices]} { if {[dict exists $arg_info $o -choices]} {
set choices [dict get $arg_info $o -choices] set choices [dict get $arg_info $o -choices]
set nocase [dict get $arg_info $o -nocase] set nocase [dict get $arg_info $o -nocase]

26
src/modules/punk/cap/handlers/templates-999999.0a1.0.tm

@ -73,15 +73,16 @@ namespace eval punk::cap::handlers::templates {
#adhoc and currentproject* paths are relative to cwd - so no projectbase information can be stored at registration time. #adhoc and currentproject* paths are relative to cwd - so no projectbase information can be stored at registration time.
#not all template item types will need projectbase information - as the item data may be self-contained within the template structure - #not all template item types will need projectbase information - as the item data may be self-contained within the template structure -
#but project_layout will need it - or at least need to know if there is no project - because project_layout data is never stored in the template folder structure directly. #but project_layout will need it - or at least need to know if there is no project - because project_layout data is never stored in the template folder structure directly.
switch -- $pathtype {
if {$pathtype eq "adhoc"} { adhoc {
if {[file pathtype $path] ne "relative"} { if {[file pathtype $path] ne "relative"} {
puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but provided a path '$path' of type $pathtype which doesn't seem to be a relative path" puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but provided a path '$path' of type $pathtype which doesn't seem to be a relative path"
return 0 return 0
} }
set extended_capdict $capdict set extended_capdict $capdict
dict set extended_capdict vendor $vendor dict set extended_capdict vendor $vendor
} elseif {$pathtype eq "module"} { }
module {
set provide_statement [package ifneeded $pkg [package require $pkg]] set provide_statement [package ifneeded $pkg [package require $pkg]]
set tmfile [lindex $provide_statement end] set tmfile [lindex $provide_statement end]
if {![file exists $tmfile]} { if {![file exists $tmfile]} {
@ -103,7 +104,8 @@ namespace eval punk::cap::handlers::templates {
set resolved_path [file join $tmfolder $path] set resolved_path [file join $tmfolder $path]
dict set extended_capdict resolved_path $resolved_path dict set extended_capdict resolved_path $resolved_path
dict set extended_capdict projectbase $projectbase dict set extended_capdict projectbase $projectbase
} elseif {$pathtype eq "currentproject_multivendor"} { }
currentproject_multivendor {
#currently only intended for punk::mix::templates - review if 3rd party _multivendor trees even make sense #currently only intended for punk::mix::templates - review if 3rd party _multivendor trees even make sense
if {$pkg ni $multivendor_package_whitelist} { if {$pkg ni $multivendor_package_whitelist} {
puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but package is not in whitelist $multivendor_package_whitelist - 3rd party _multivendor tree not supported" puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but package is not in whitelist $multivendor_package_whitelist - 3rd party _multivendor tree not supported"
@ -116,7 +118,8 @@ namespace eval punk::cap::handlers::templates {
set extended_capdict $capdict set extended_capdict $capdict
dict set extended_capdict vendor $vendor ;#vendor key still required.. controlling vendor? dict set extended_capdict vendor $vendor ;#vendor key still required.. controlling vendor?
} elseif {$pathtype eq "currentproject"} { }
currentproject {
if {[file pathtype $path] ne "relative"} { if {[file pathtype $path] ne "relative"} {
puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but provided a path '$path' of type $pathtype which doesn't seem to be a relative path" puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but provided a path '$path' of type $pathtype which doesn't seem to be a relative path"
return 0 return 0
@ -127,7 +130,8 @@ namespace eval punk::cap::handlers::templates {
set extended_capdict $capdict set extended_capdict $capdict
dict set extended_capdict vendor $vendor dict set extended_capdict vendor $vendor
} elseif {$pathtype eq "shellproject"} { }
shellproject {
if {[file pathtype $path] ne "relative"} { if {[file pathtype $path] ne "relative"} {
puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but provided a path '$path' of type $pathtype which doesn't seem to be a relative path" puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but provided a path '$path' of type $pathtype which doesn't seem to be a relative path"
return 0 return 0
@ -139,7 +143,8 @@ namespace eval punk::cap::handlers::templates {
set extended_capdict $capdict set extended_capdict $capdict
dict set extended_capdict vendor $vendor dict set extended_capdict vendor $vendor
dict set extended_capdict projectbase $projectbase dict set extended_capdict projectbase $projectbase
} elseif {$pathtype eq "shellproject_multivendor"} { }
shellproject_multivendor {
#currently only intended for punk::templates - review if 3rd party _multivendor trees even make sense #currently only intended for punk::templates - review if 3rd party _multivendor trees even make sense
if {$pkg ni $multivendor_package_whitelist} { if {$pkg ni $multivendor_package_whitelist} {
puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but package is not in whitelist $multivendor_package_whitelist - 3rd party _multivendor tree not supported" puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but package is not in whitelist $multivendor_package_whitelist - 3rd party _multivendor tree not supported"
@ -156,7 +161,8 @@ namespace eval punk::cap::handlers::templates {
set extended_capdict $capdict set extended_capdict $capdict
dict set extended_capdict vendor $vendor dict set extended_capdict vendor $vendor
dict set extended_capdict projectbase $projectbase dict set extended_capdict projectbase $projectbase
} elseif {$pathtype eq "absolute"} { }
absolute {
if {[file pathtype $path] ne "absolute"} { if {[file pathtype $path] ne "absolute"} {
puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but provided a path '$path' of type $pathtype which doesn't seem to be absolute" puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but provided a path '$path' of type $pathtype which doesn't seem to be absolute"
return 0 return 0
@ -174,10 +180,12 @@ namespace eval punk::cap::handlers::templates {
dict set extended_capdict resolved_path $normpath dict set extended_capdict resolved_path $normpath
dict set extended_capdict vendor $vendor dict set extended_capdict vendor $vendor
dict set extended_capdict projectbase $projectbase dict set extended_capdict projectbase $projectbase
} else { }
default {
puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but provided a path '$path' with unrecognised type $pathtype" puts stderr "punk::cap::handlers::templates::capsystem::pkg_register WARNING - package '$pkg' is attempting to register with punk::cap as a provider of '$capname' capability but provided a path '$path' with unrecognised type $pathtype"
return 0 return 0
} }
}
# -- --- --- --- --- --- --- ---- --- # -- --- --- --- --- --- --- ---- ---
# update package internal data # update package internal data

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

@ -56,6 +56,8 @@
#[para] - #[para] -
package require Tcl 8.6- package require Tcl 8.6-
#dependency on tcllib not ideal for bootstrapping as punk::char is core to many features.. (textutil::wcswidth is therefore included in bootsupport/include_modules.config) review
package require textutil::wcswidth
#*** !doctools #*** !doctools
#[list_end] #[list_end]
@ -71,6 +73,7 @@ package require Tcl 8.6-
namespace eval punk::char { namespace eval punk::char {
namespace export * namespace export *
variable grapheme_widths [dict create]
# -- -------------------------------------------------------------------------- # -- --------------------------------------------------------------------------
variable encmimens ;#namespace of mime package providing reversemapencoding and mapencoding functions variable encmimens ;#namespace of mime package providing reversemapencoding and mapencoding functions
#tcllib mime requires tcl::chan::memchan,events,core and/or Trf #tcllib mime requires tcl::chan::memchan,events,core and/or Trf
@ -1259,31 +1262,31 @@ namespace eval punk::char {
variable charsets variable charsets
set hex_char [format %04x $dec_char] set hex_char [format %04x $dec_char]
set returninfo [dict create] set returninfo [dict create]
if {"dec" in $fields} { foreach f $fields {
switch -- $f {
dec {
dict set returninfo dec $dec_char dict set returninfo dec $dec_char
} }
if {"hex" in $fields} { hex {
dict set returninfo hex $hex_char dict set returninfo hex $hex_char
} }
if {"desc" in $fields} { desc {
if {[dict exists $charinfo $dec_char desc]} { if {[dict exists $charinfo $dec_char desc]} {
dict set returninfo desc [dict get $charinfo $dec_char desc] dict set returninfo desc [dict get $charinfo $dec_char desc]
} else { } else {
dict set returninfo desc "" dict set returninfo desc ""
} }
} }
if {"short" in $fields} { short {
if {[dict exists $charinfo $dec_char short]} { if {[dict exists $charinfo $dec_char short]} {
dict set returninfo desc [dict get $charinfo $dec_char short] dict set returninfo desc [dict get $charinfo $dec_char short]
} else { } else {
dict set returninfo short "" dict set returninfo short ""
} }
} }
testwidth {
#todo - expectedwidth - lookup the printing width it is *supposed* to have from unicode tables #todo - expectedwidth - lookup the printing width it is *supposed* to have from unicode tables
#testwidth is one of the main ones likely to be worthwhile excluding as querying the console via ansi takes time #testwidth is one of the main ones likely to be worthwhile excluding as querying the console via ansi takes time
if {"testwidth" in $fields} {
set existing_testwidth "" set existing_testwidth ""
if {[dict exists $charinfo $dec_char testwidth]} { if {[dict exists $charinfo $dec_char testwidth]} {
set existing_testwidth [dict get $charinfo $dec_char testwidth] set existing_testwidth [dict get $charinfo $dec_char testwidth]
@ -1300,17 +1303,16 @@ namespace eval punk::char {
dict set returninfo testwidth $existing_testwidth dict set returninfo testwidth $existing_testwidth
} }
} }
if {"char" in $fields} { char {
set char [format %c $dec_char] set char [format %c $dec_char]
dict set returninfo char $char dict set returninfo char $char
} }
memberof {
#memberof takes in the order of a few hundred microseconds if a simple scan of all ranges is taken - possibly worthwhile caching/optimising #memberof takes in the order of a few hundred microseconds if a simple scan of all ranges is taken - possibly worthwhile caching/optimising
#note that memberof is not just unicode blocks - but scripts and other non-contiguous sets consisting of multiple ranges - some of which may include ranges of a single character. (e.g WGL4) #note that memberof is not just unicode blocks - but scripts and other non-contiguous sets consisting of multiple ranges - some of which may include ranges of a single character. (e.g WGL4)
#This means there probably isn't a really efficient means of calculating membership other than scanning all the defined ranges. #This means there probably isn't a really efficient means of calculating membership other than scanning all the defined ranges.
#We could potentially populate it using a side-thread - but it seems reasonable to just cache result after first use here. #We could potentially populate it using a side-thread - but it seems reasonable to just cache result after first use here.
#some efficiency could also be gained by pre-calculating the extents for each charset which isn't a simple unicode block. (and perhaps sorting by max char) #some efficiency could also be gained by pre-calculating the extents for each charset which isn't a simple unicode block. (and perhaps sorting by max char)
if {"memberof" in $fields} {
set memberof [list] set memberof [list]
dict for {setname setinfo} $charsets { dict for {setname setinfo} $charsets {
foreach r [dict get $setinfo ranges] { foreach r [dict get $setinfo ranges] {
@ -1324,6 +1326,8 @@ namespace eval punk::char {
} }
dict set returninfo memberof $memberof dict set returninfo memberof $memberof
} }
}
}
return $returninfo return $returninfo
} }
@ -1512,32 +1516,76 @@ namespace eval punk::char {
#non-overlapping unicode blocks #non-overlapping unicode blocks
proc char_blocks {name_or_glob} { proc char_blocks {{name_or_glob *}} {
error "unicode block searching unimplemented" variable charsets
#todo - search only charsets that have settype = block #todo - more efficient datastructures?
if {![regexp {[?*]} $name_or_glob]} {
#no glob - just retrieve it
if {[dict exists $charsets $name_or_glob]} {
if {[dict get $charsets $name_or_glob settype] eq "block"} {
return [dict create $name_or_glob [dict get $charsets $name_or_glob]]
}
}
#no exact match - try case insensitive..
set name [lsearch -inline -nocase [dict keys $charsets] $name_or_glob]
if {$name ne ""} {
if {[dict get $charsets $name settype] eq "block"} {
return [dict create $name [dict get $charsets $name]]
}
}
} else {
#build a subset
set charsets_block [dict create]
dict for {k v} $charsets {
if {[string match -nocase $name_or_glob $k]} {
if {[dict get $v settype] eq "block"} {
dict set charsets_block $k $v
}
}
}
return $charsets_block
}
}
proc charset_names {{name_or_glob *}} {
variable charsets
if {![regexp {[?*]} $name_or_glob]} {
#no glob - just retrieve it
if {[dict exists $charsets $name_or_glob]} {
return [list $name_or_glob]
}
#no exact match - try case insensitive..
set name [lsearch -inline -nocase [dict keys $charsets] $name_or_glob]
if {$name ne ""} {
return [list $name]
}
} else {
if {$name_or_glob eq "*"} {
return [lsort [dict keys $charsets]]
}
return [lsort [lsearch -all -inline -nocase [dict keys $charsets] $name_or_glob]]
}
} }
#deprecated
#major named sets such as unicode blocks, scripts, and other sets such as microsoft WGL4 #major named sets such as unicode blocks, scripts, and other sets such as microsoft WGL4
#case insensitive search - possibly with globs #case insensitive search - possibly with *basic* globs (? and * only - not lb rb)
proc charset_names {{namesearch *}} { proc charset_names2 {{namesearch *}} {
variable charsets variable charsets
set sortedkeys [lsort -increasing -dictionary [dict keys $charsets]] ;#NOTE must use -dictionary to use -sorted flag below #dictionary sorting of the keys is slow! - we should obviously store it in sorted order instead of sorting entire list on retrieval - or just sort results
#set sortedkeys [lsort -increasing -dictionary [dict keys $charsets]] ;#NOTE must use -dictionary to use -sorted flag below
set sortedkeys [lsort -increasing [dict keys $charsets]]
if {$namesearch eq "*"} { if {$namesearch eq "*"} {
return $sortedkeys return $sortedkeys
} }
if {[regexp {[?*]} $namesearch]} { if {[regexp {[?*]} $namesearch]} {
#name glob search #name glob search
set matched_names [lsearch -all -inline -nocase $sortedkeys $namesearch] ;#no point using -sorted flag when -all is used return [lsearch -all -inline -nocase $sortedkeys $namesearch] ;#no point using -sorted flag when -all is used
} else {
set matched [lsearch -sorted -inline -nocase $sortedkeys $namesearch] ;#no globs - stop on first match
if {[llength $matched]} {
return [list $matched]
} else { } else {
return [list] #return [lsearch -sorted -inline -nocase $sortedkeys $namesearch] ;#no globs - bails out earlier if -sorted?
return [lsearch -inline -nocase $sortedkeys $namesearch] ;#no globs
} }
} }
return $matched_names
}
proc charsets {{namesearch *}} { proc charsets {{namesearch *}} {
package require textblock package require textblock
variable charsets variable charsets
@ -1585,7 +1633,7 @@ namespace eval punk::char {
} }
set dict_list [list] set dict_list [list]
foreach m $matches { foreach m $matches {
lappend dict_list [dict create $m [charset_dictget $name]] lappend dict_list [dict create $m [charset_dictget $m]]
} }
#return $dict_list #return $dict_list
return [join $dict_list \n] return [join $dict_list \n]
@ -1651,7 +1699,8 @@ namespace eval punk::char {
set twidth [dict get $charinfo $dec testwidth] set twidth [dict get $charinfo $dec testwidth]
} }
if {$twidth eq ""} { if {$twidth eq ""} {
set width [ansifreestring_width $ch] ;#based on unicode props #set width [ansifreestring_width $ch] ;#based on unicode props
set width [grapheme_width_cached $ch]
} else { } else {
set width $twidth set width $twidth
} }
@ -1780,7 +1829,7 @@ namespace eval punk::char {
} }
if {$twidth eq ""} { if {$twidth eq ""} {
#puts -nonewline stdout "." ;#this #puts -nonewline stdout "." ;#this
set width [char_info_testwidth $ch] ;#based on unicode props set width [char_info_testwidth $ch] ;#based on console test rather than unicode props
dict set charinfo $dec testwidth $width dict set charinfo $dec testwidth $width
} else { } else {
set width $twidth set width $twidth
@ -1792,22 +1841,172 @@ namespace eval punk::char {
puts stdout "\ncalibration done - results cached in charinfo dictionary" puts stdout "\ncalibration done - results cached in charinfo dictionary"
return [dict create charcount $charcount widths $width_results] return [dict create charcount $charcount widths $width_results]
} }
#todo - provide a char_width equivalent that is optimised for speed
#maint warning - also in overtype!
#intended for single grapheme - but will work for multiple
#cannot contain ansi or newlines
#(a cache of ansifreestring_width calls - as these are quite regex heavy)
proc grapheme_width_cached {ch} {
variable grapheme_widths
if {[dict exists $grapheme_widths $ch]} {
return [dict get $grapheme_widths $ch]
}
set width [punk::char::ansifreestring_width $ch] ;#review - can we provide faster version if we know it's a single grapheme rather than a string? (grapheme is still a string as it may have combiners/diacritics)
dict set grapheme_widths $ch $width
return $width
}
#no char_width - use grapheme_width terminology to be clearer
proc grapheme_width {char} {
error "grapheme_width unimplemented - use ansifreestring_width"
}
#return N Na W etc from unicode data
#review
proc char_uc_width_prop {char} {
error "char_uc_width unimplemented try textutil::wcswidth_type"
}
#todo - provide a grapheme_width function that is optimised for speed
proc string_width {text} { proc string_width {text} {
#burn approx 2uS (2024) checking for ansi codes - not just SGR #burn approx 2uS (2024) checking for ansi codes - not just SGR
if {[punk::ansi::ta::detect $text]} { if {[punk::ansi::ta::detect $text]} {
puts stderr "string_width detected ANSI!" puts stderr "string_width detected ANSI!"
} }
if {[string first \n $text] >= 0} { if {[string last \n $text] >= 0} {
error "string_width accepts only a single line" error "string_width accepts only a single line"
} }
tailcall ansifreestring_width $text tailcall ansifreestring_width $text
} }
#prerequisites - no ansi escapes - no newlines
#faster than textutil::wcswidth (at least for string up to a few K in length)
proc wcswidth {string} {
set codes [scan $string [string repeat %c [string length $string]]]
set width 0
foreach c $codes {
set w [textutil::wcswidth_char $c]
if {$w < 0} {
return -1
} else {
incr width $w
}
}
return $width
}
proc wcswidth2 {string} {
set codes [scan $string [string repeat %c [string length $string]]]
set widths [lmap c $codes {textutil::wcswidth_char $c}]
if {-1 in $widths} {
return -1
}
return [tcl::mathop::+ {*}$widths]
}
#prerequisites - no ansi escapes - no newlines - utf8 encoding assumed
#review - what about \r \t \b ? #review - what about \r \t \b ?
#NO processing of \b - already handled in ansi::printing_length which then calls this #NO processing of \b - already handled in ansi::printing_length which then calls this
#this version breaks string into sequences of ascii vs unicode
proc ansifreestring_width {text} { proc ansifreestring_width {text} {
#caller responsible for calling ansistrip first if text may have ansi codes - and for ensuring no newlines #caller responsible for calling ansistrip first if text may have ansi codes - and for ensuring no newlines
#we can c0 control characters after or while processing ansi escapes.
#we need to map remaining control characters to zero-width (under assumption we're not using a font/codepage that displays them - review!)
#anyway - we must allow things like raw ESC,DEL, NUL etc to pass through without error
#if {[string first \033 $text] >= 0} {
# error "string_width doesn't accept ansi escape sequences. Use punk::ansi::stripansi first"
#}
#todo - various combining diacritical marks.. from grave - to various complicated unicode joiners and composing chars etc
#as at 2023 - terminals generally seem to use the simplistic approach of tallying up individual character-widths, which means combinations that print short are reported too long by the terminal esc 6 n sequence.
#- {Combining Diacritical Marks} {ranges {{start 768 end 879 note {unicode block 0300..036F}}} description {} settype block}
#- {Combining Diacritical Marks Extended} {ranges {{start 6832 end 6911 note {unicode block 1AB0..1AFF}}} description {} settype block}
#- {Combining Diacritical Marks Supplement} {ranges {{start 7616 end 7679 note {unicode block 1DC0..1DFF}}} description {} settype block}
#- {Combining Diacritical Marks for Symbols} {ranges {{start 8400 end 8447 note {unicode block 20D0..20FF}}} description {} settype block}
#- {Combining Half Marks} {ranges {{start 65056 end 65071 note {unicode block FE20..FE2F}}} description {} settype block}
#
# initial simplistic approach is just to strip these ... todo REVIEW
#experiment to detect leading diacritics - but this isn't necessary - it's still zero-width - and if the user is splitting properly we shouldn't be getting a string with leading diacritics anyway
#(leading combiners may display in terminal as mark on rightmost prompt char which is usually a space - but won't add width even then)
#set re_leading_diacritic {^(?:[\u0300-\u036f]+|[\u1ab0-\u1aff]+|[\u1dc0-\u1dff]+|[\u20d0-\u20ff]+|[\ufe20-\ufe2f]+)}
#if {[regexp $re_leading_diacritic $text]} {
# set text " $text"
#}
set re_diacritics {[\u0300-\u036f]+|[\u1ab0-\u1aff]+|[\u1dc0-\u1dff]+|[\u20d0-\u20ff]+|[\ufe20-\ufe2f]+}
set text [regsub -all $re_diacritics $text ""]
# -- --- --- --- --- --- ---
#review
#if we strip out ZWJ \u200d (zero width combiner) - then we don't give the chance for a grapheme combining test to merge properly e.g combining emojis
#as at 2024 - textutil::wcswidth just uses the unicode east-asian width property data and doesn't seem to handle these specially - it counts this joiner and others as one wide (also BOM \uFFEF)
#TODO - once we have proper grapheme cluster splitting - work out which of these characters should be left in and/or when exactly their length-effects apply
#
#for now - strip them out
#ZWNJ \u200c should also be zero length - but as a 'non' joiner - it should add zero to the length
#ZWSP \u200b zero width space
#\uFFEFBOM/ ZWNBSP and others that should be zero width
#todo - work out proper way to mark/group zero width.
set text [string map [list \u200b "" \u200c "" \u200d "" \uFFEF ""] $text]
# -- --- --- --- --- --- ---
#we should only map control sequences to nothing after processing ones with length effects, such as \b (\x07f) or DEL \x1f
#todo - document that these shouldn't be present in input rather than explicitly checking here
#c0 controls
set re_ascii_c0 {[\U0000-\U001F]}
set text [regsub -all $re_ascii_c0 $text ""]
#c1 controls - first section of the Latin-1 Supplement block - all non-printable from a utf-8 perspective
#some or all of these may have a visual representation in other encodings e.g cp855 seems to have 1 width for them all
#we are presuming that the text supplied has been converted from any other encoding to utf8 - so we don't need to handle that here
#they should also be unlikely to be present in an ansi-free string (as is a precondition for use of this function)
set text [regsub -all {[\u0080-\u009f]+} $text ""]
#short-circuit basic cases
#support tcl pre 2023-11 - see regexp bug below
#if {![regexp {[\uFF-\U10FFFF]} $text]} {
# return [string length $text]
#}
if {![regexp "\[\uFF-\U10FFFF\]" $text]} {
return [string length $text]
}
#split just to get the standalone character widths - and then scan for other combiners (?) - or scan for clusters first?
#review
#set can_regex_high_unicode [string match [regexp -all -inline {[\U80-\U0010FFFF]} \U0001F525] \U0001F525]
#tcl pre 2023-11 - braced high unicode regexes don't work
#fixed in bug-4ed788c618 2023-11
#set uc_chars [regexp -all -inline "\[\u0100-\U10FFFF\]" $text] ;#e.g return list of chars in range only
#maintain unicode as sequences - todo - scan for grapheme clusters
#set uc_sequences [punk::ansi::ta::_perlish_split "(?:\[\u0100-\U10FFFF\])+" $text]
set uc_sequences [punk::ansi::ta::_perlish_split "(?:\[\u0000-\u00FF\])+" $text]
set len 0
foreach {uc ascii} $uc_sequences {
#puts "-ascii $ascii"
#puts "-uc $uc"
incr len [string length $ascii]
#textutil::wcswidth uses unicode data
#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
#use punk::char::wcswidth - faster than the string split in textutil::wcswidth but still uses textutil::wcswidth_char
incr len [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.
return $len
}
#kept as a fallback for review/test if textutil::wcswidth doesn't do what we require on all terminals.
#this version looks at console testwidths if they've been cached.
#It is relatively fast - but tests unicode widths char by char - so won't be useful going forward for grapheme clusters.
proc ansifreestring_width2 {text} {
#caller responsible for calling ansistrip first if text may have ansi codes - and for ensuring no newlines
@ -1831,7 +2030,7 @@ namespace eval punk::char {
# #
# initial simplistic approach is just to strip these ... todo REVIEW # initial simplistic approach is just to strip these ... todo REVIEW
#experiment to detect leading diacritics - but this isn't necessary - it's still zero-width #experiment to detect leading diacritics - but this isn't necessary - it's still zero-width - and if the user is splitting properly we shouldn't be getting a string with leading diacritics anyway
#(leading combiners may display in terminal as mark on rightmost prompt char which is usually a space - but won't add width even then) #(leading combiners may display in terminal as mark on rightmost prompt char which is usually a space - but won't add width even then)
#set re_leading_diacritic {^(?:[\u0300-\u036f]+|[\u1ab0-\u1aff]+|[\u1dc0-\u1dff]+|[\u20d0-\u20ff]+|[\ufe20-\ufe2f]+)} #set re_leading_diacritic {^(?:[\u0300-\u036f]+|[\u1ab0-\u1aff]+|[\u1dc0-\u1dff]+|[\u20d0-\u20ff]+|[\ufe20-\ufe2f]+)}
#if {[regexp $re_leading_diacritic $text]} { #if {[regexp $re_leading_diacritic $text]} {
@ -1840,10 +2039,17 @@ namespace eval punk::char {
set re_diacritics {[\u0300-\u036f]+|[\u1ab0-\u1aff]+|[\u1dc0-\u1dff]+|[\u20d0-\u20ff]+|[\ufe20-\ufe2f]+} set re_diacritics {[\u0300-\u036f]+|[\u1ab0-\u1aff]+|[\u1dc0-\u1dff]+|[\u20d0-\u20ff]+|[\ufe20-\ufe2f]+}
set text [regsub -all $re_diacritics $text ""] set text [regsub -all $re_diacritics $text ""]
#review
#if we strip out ZWJ \u0200d (zero width combiner) - then we don't give the chance for a grapheme combining test to merge properly e.g combining emojis
#as at 2024 - textutil::wcswidth doesn't seem to be aware of these - and counts the joiner as one wide
#ZWNJ \u0200c should also be zero length - but as a 'non' joiner - it should add zero to the length
#ZWSP \u0200b zero width space
#only map control sequences to nothing after processing ones with special effects, such as \b (\x07f) #we should only map control sequences to nothing after processing ones with length effects, such as \b (\x07f) or DEL \x1f
#Note DEL \x1f will only #todo - document that these shouldn't be present in input rather than explicitly checking here
set re_ascii_c0 {[\U0000-\U001F]} set re_ascii_c0 {[\U0000-\U001F]}
set text [regsub -all $re_ascii_c0 $text ""] set text [regsub -all $re_ascii_c0 $text ""]
@ -1856,7 +2062,7 @@ namespace eval punk::char {
return [string length $text] return [string length $text]
} }
#todo - check double-width chars in unicode blocks.. try to do reasonably quicky #review - wcswidth should detect these
set re_ascii_fullwidth {[\uFF01-\uFF5e]} set re_ascii_fullwidth {[\uFF01-\uFF5e]}
set doublewidth_char_count 0 set doublewidth_char_count 0
@ -1867,15 +2073,30 @@ namespace eval punk::char {
#tcl pre 2023-11 - braced high unicode regexes don't work #tcl pre 2023-11 - braced high unicode regexes don't work
#fixed in bug-4ed788c618 2023-11 #fixed in bug-4ed788c618 2023-11
#set uc_sequences [regexp -all -inline -indices {[\u0100-\U10FFFF]} $text] #set uc_sequences [regexp -all -inline -indices {[\u0100-\U10FFFF]} $text]
set uc_sequences [regexp -all -inline -indices "\[\u0100-\U10FFFF\]" $text] #set uc_sequences [regexp -all -inline -indices "\[\u0100-\U10FFFF\]" $text] ;#e.g for string: \U0001f9d1abc\U001f525ab returns {0 0} {4 4}
foreach uc_range $uc_sequences { set uc_chars [regexp -all -inline "\[\u0100-\U10FFFF\]" $text] ;#e.g return list of chars in range only
set chars [string range $text {*}$uc_range] foreach c $uc_chars {
foreach c $chars {
if {[regexp $re_ascii_fullwidth $c]} { if {[regexp $re_ascii_fullwidth $c]} {
incr doublewidth_char_count incr doublewidth_char_count
} else { } else {
#todo - replace with function that doesn't use console - just unicode data #review
# a)- terminals lie - so we could have a bad cached testwidth
# b)- textutil::wcswidth_char seems to be east-asian-width based - and not a reliable indicator of 'character cells taken by the character when printed to the terminal' despite this statement in tclllib docs.
#(character width is a complex context-dependent topic)
# c) by checking for a cached console testwidth first - we make this function less deterministic/repeatable depending on whether console tests have been run.
# d) Upstream caching of grapheme_width may also lock in a result from whatever method was first employed here
#Despite all this - the mechanism is hoped to give best effort consistency for terminals
#further work needed for combining emojis etc - which can't be done in a per character loop
#todo - see if wcswidth does any of this. It is very slow for strings that include mixed ascii/unicode - so perhaps we can use a perlish_split
# to process sequences of unicode.
#- and the user has the option to test character sets first if terminal position reporting gives better results
if {[char_info_is_testwidth_cached $c]} {
set width [char_info_testwidth_cached $c] set width [char_info_testwidth_cached $c]
} else {
#textutil::wcswidth uses unicode data
#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)
set width [textutil::wcswidth_char [scan $c %c]]
}
if {$width == 0} { if {$width == 0} {
incr zerowidth_char_count incr zerowidth_char_count
} elseif {$width == 2} { } elseif {$width == 2} {
@ -1883,11 +2104,62 @@ namespace eval punk::char {
} }
} }
} }
}
#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 [expr {[string length $text] + $doublewidth_char_count - $zerowidth_char_count}] return [expr {[string length $text] + $doublewidth_char_count - $zerowidth_char_count}]
} }
#slow - textutil::wcswidth is slow with mixed ascii uc
proc ansifreestring_width3 {text} {
#caller responsible for calling ansistrip first if text may have ansi codes - and for ensuring no newlines
#we can c0 control characters after or while processing ansi escapes.
#we need to map remaining control characters to zero-width (under assumption we're not using a font/codepage that displays them - review!)
#anyway - we must allow things like raw ESC,DEL, NUL etc to pass through without error
#if {[string first \033 $text] >= 0} {
# error "string_width doesn't accept ansi escape sequences. Use punk::ansi::stripansi first"
#}
#review - 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.
# - various combining diacritical marks.. from grave - to various complicated unicode joiners and composing chars etc
#as at 2023 - terminals generally seem to use the simplistic approach of tallying up individual character-widths, which means combinations that print short are reported too long by the terminal esc 6 n sequence.
#- {Combining Diacritical Marks} {ranges {{start 768 end 879 note {unicode block 0300..036F}}} description {} settype block}
#- {Combining Diacritical Marks Extended} {ranges {{start 6832 end 6911 note {unicode block 1AB0..1AFF}}} description {} settype block}
#- {Combining Diacritical Marks Supplement} {ranges {{start 7616 end 7679 note {unicode block 1DC0..1DFF}}} description {} settype block}
#- {Combining Diacritical Marks for Symbols} {ranges {{start 8400 end 8447 note {unicode block 20D0..20FF}}} description {} settype block}
#- {Combining Half Marks} {ranges {{start 65056 end 65071 note {unicode block FE20..FE2F}}} description {} settype block}
#
# initial simplistic approach is just to strip these ... todo REVIEW
#experiment to detect leading diacritics - but this isn't necessary - it's still zero-width - and if the user is splitting properly we shouldn't be getting a string with leading diacritics anyway
#(leading combiners may display in terminal as mark on rightmost prompt char which is usually a space - but won't add width even then)
#set re_leading_diacritic {^(?:[\u0300-\u036f]+|[\u1ab0-\u1aff]+|[\u1dc0-\u1dff]+|[\u20d0-\u20ff]+|[\ufe20-\ufe2f]+)}
#if {[regexp $re_leading_diacritic $text]} {
# set text " $text"
#}
set re_diacritics {[\u0300-\u036f]+|[\u1ab0-\u1aff]+|[\u1dc0-\u1dff]+|[\u20d0-\u20ff]+|[\ufe20-\ufe2f]+}
set text [regsub -all $re_diacritics $text ""]
#we should only map control sequences to nothing after processing ones with length effects, such as \b (\x07f) or DEL \x1f
#todo - document that these shouldn't be present in input rather than explicitly checking here
set re_ascii_c0 {[\U0000-\U001F]}
set text [regsub -all $re_ascii_c0 $text ""]
#short-circuit basic cases
#support tcl pre 2023-11 - see regexp bug below
#if {![regexp {[\uFF-\U10FFFF]} $text]} {
# return [string length $text]
#}
if {![regexp "\[\uFF-\U10FFFF\]" $text]} {
return [string length $text]
}
#slow when ascii mixed with unicode (but why?)
return [punk::wcswidth $text]
}
#This shouldn't be called on text containing ansi codes! #This shouldn't be called on text containing ansi codes!
proc strip_nonprinting_ascii {str} { proc strip_nonprinting_ascii {str} {
#review - some single-byte 'control' chars have visual representations e.g ETX as heart depending on font/codepage #review - some single-byte 'control' chars have visual representations e.g ETX as heart depending on font/codepage
@ -1901,22 +2173,14 @@ namespace eval punk::char {
return [string map $map $str] return [string map $map $str]
} }
proc char_width {char} {
error "char_width unimplemented"
}
#return N Na W etc from unicode data
proc char_uc_width_prop {char} {
error "char_uc_width unimplemented"
}
#split into plaintext and runs of combiners #split into plaintext and runs of combiners (combining diacritical marks - not ZWJ or ZWJNJ)
proc combiner_split {text} { proc combiner_split {text} {
#split into odd numbered list (or zero) in a similar way to punk::ansi::ta::_perlish_split #split into odd numbered list (or zero) in a similar way to punk::ansi::ta::_perlish_split
# #
set re_diacritics {[\u0300-\u036f]+|[\u1ab0-\u1aff]+|[\u1dc0-\u1dff]+|[\u20d0-\u20ff]+|[\ufe20-\ufe2f]+} #set re_diacritics {[\u0300-\u036f]+|[\u1ab0-\u1aff]+|[\u1dc0-\u1dff]+|[\u20d0-\u20ff]+|[\ufe20-\ufe2f]+}
set graphemes [list] set graphemes [list]
set g ""
if {[string length $text] == 0} { if {[string length $text] == 0} {
return {} return {}
} }
@ -1925,7 +2189,7 @@ namespace eval punk::char {
set strlen [string length $text] set strlen [string length $text]
#make sure our regexes aren't non-greedy - or we may not have exit condition for loop #make sure our regexes aren't non-greedy - or we may not have exit condition for loop
#review #review
while {$start < $strlen && [regexp -start $start -indices -- $re_diacritics $text match]} { while {$start < $strlen && [regexp -start $start -indices -- {[\u0300-\u036f]+|[\u1ab0-\u1aff]+|[\u1dc0-\u1dff]+|[\u20d0-\u20ff]+|[\ufe20-\ufe2f]+} $text match]} {
lassign $match matchStart matchEnd lassign $match matchStart matchEnd
#puts "->start $start ->match $matchStart $matchEnd" #puts "->start $start ->match $matchStart $matchEnd"
lappend list [string range $text $start $matchStart-1] [string range $text $matchStart $matchEnd] lappend list [string range $text $start $matchStart-1] [string range $text $matchStart $matchEnd]
@ -1936,13 +2200,19 @@ namespace eval punk::char {
#} #}
} }
lappend list [string range $text $start end] lappend list [string range $text $start end]
return $list
} }
#ZWJ ZWNJ ?
#1st shot - basic diacritics #1st shot - basic diacritics
#todo - become aware of unicode grapheme cluster boundaries #todo - become aware of unicode grapheme cluster boundaries
# #This is difficult in Tcl without unicode property based Character Classes in the regex engine
#review - this needs to be performant - it is used a lot by punk terminal/ansi features
#todo - trie data structures for unicode?
#for now we can at least combine diacritics
#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)
#This still leaves a whole class of clusters.. korean etc unhandled :/
proc grapheme_split {text} { proc grapheme_split {text} {
set graphemes [list] set graphemes [list]
set csplits [combiner_split $text] set csplits [combiner_split $text]
@ -1957,9 +2227,51 @@ namespace eval punk::char {
} }
return $graphemes return $graphemes
} }
proc grapheme_split_dec {text} {
set graphemes [list]
set csplits [combiner_split $text]
foreach {pt combiners} [lrange $csplits 0 end-1] {
set pt_decs [scan $pt [string repeat %c [string length $pt]]]
set combiner_decs [scan $combiners [string repeat %c [string length $combiners]]]
lset pt_decs end [concat [lindex $pt_decs end] $combiner_decs]
lappend graphemes {*}$pt_decs
}
#last csplit never has a combiner (_perlish_split style) - and may be empty - in which case we don't append it as a grapheme
if {[lindex $csplits end] ne ""} {
lappend graphemes {*}[scan [lindex $csplits end] [string repeat %c [string length [lindex $csplits end]]]]
}
return $graphemes
}
proc grapheme_split_dec2 {text} {
set graphemes [list]
set csplits [combiner_split $text]
foreach {pt combiners} $csplits {
set pt_decs [scan $pt [string repeat %c [string length $pt]]]
if {$combiners ne ""} {
set combiner_decs [scan $combiners [string repeat %c [string length $combiners]]]
lset pt_decs end [concat [lindex $pt_decs end] $combiner_decs]
}
lappend graphemes {*}$pt_decs
}
return $graphemes
}
proc grapheme_split2 {text} {
set graphemes [list]
set csplits [combiner_split $text]
foreach {pt combiners} [lrange $csplits 0 end-1] {
set clist [split $pt ""]
lappend graphemes {*}[lrange $clist 0 end-1] [string cat [lindex $clist end] $combiners]
}
#last csplit never has a combiner (_perlish_split style) - and may be empty - in which case we don't append it as a grapheme
if {[lindex $csplits end] ne ""} {
lappend graphemes {*}[split [lindex $csplits end] ""]
}
return $graphemes
}
# -- --- --- --- --- # -- --- --- --- ---
#will accept a single char or a string - test using console cursor position reporting #will accept a single char or a string - test using console cursor position reporting
#unreliable
proc char_info_testwidth {ch {emit 0}} { proc char_info_testwidth {ch {emit 0}} {
package require punk::console package require punk::console
#uses cursor movement and relies on console to report position.. which it may do properly for single chars - but may misreport for combinations that combine into a single glyph #uses cursor movement and relies on console to report position.. which it may do properly for single chars - but may misreport for combinations that combine into a single glyph
@ -1980,6 +2292,10 @@ namespace eval punk::char {
return $twidth return $twidth
} }
} }
proc char_info_is_testwidth_cached {char} {
variable charinfo
return [dict exists $charinfo [scan $char %c] testwidth]
}
# -- --- --- --- --- # -- --- --- --- ---

3
src/modules/punk/config-0.1.tm

@ -37,7 +37,10 @@ namespace eval punk::config {
dict set startup exec_unknown true ;#whether to use exec instead of experimental shellfilter::run dict set startup exec_unknown true ;#whether to use exec instead of experimental shellfilter::run
#dict set startup color_stdout [list cyan bold] ;#not a good idea to default #dict set startup color_stdout [list cyan bold] ;#not a good idea to default
dict set startup color_stdout [list] dict set startup color_stdout [list]
#This wraps the stderr stream as it comes in with Ansi - probably best to default to empty.. but it's useful.
dict set startup color_stderr [list red bold] dict set startup color_stderr [list red bold]
dict set startup syslog_stdout "127.0.0.1:514" dict set startup syslog_stdout "127.0.0.1:514"
dict set startup syslog_stderr "127.0.0.1:514" dict set startup syslog_stderr "127.0.0.1:514"
dict set startup syslog_active 0 dict set startup syslog_active 0

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

@ -30,6 +30,9 @@ if {"windows" eq $::tcl_platform(platform)} {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval punk::console { namespace eval punk::console {
variable tabwidth 8 ;#default only - will attempt to detect and set to that configured in terminal
#Note that windows terminal cooked mode seems to use 8 for interactive use even if set differently
#e.g typing tab characters may still be echoed 8-spaced while writing to stdout my obey the terminal's tab stops.
variable has_twapi 0 variable has_twapi 0
variable previous_stty_state_stdin "" variable previous_stty_state_stdin ""
variable previous_stty_state_stdout "" variable previous_stty_state_stdout ""
@ -572,7 +575,7 @@ namespace eval punk::console {
if {$waitvar($callid) ne "timedout"} { if {$waitvar($callid) ne "timedout"} {
after cancel $cancel_timeout_id after cancel $cancel_timeout_id
} else { } else {
puts stderr "timeout in get_ansi_response_payload" puts stderr "timeout in get_ansi_response_payload. Ansi request was:[ansistring VIEW $query]"
} }
if {$was_raw == 0} { if {$was_raw == 0} {
@ -694,25 +697,27 @@ namespace eval punk::console {
} }
} }
#a and a+ functions are not very useful when emitting directly to console
proc a {args} { #e.g puts [punk::console::a red]test[punk::console::a cyan] would produce a cyan coloured test as the commands are evaluated first
variable colour_disabled
variable ansi_wanted #proc a {args} {
if {$colour_disabled || $ansi_wanted <= 0} { # variable colour_disabled
return # variable ansi_wanted
} # if {$colour_disabled || $ansi_wanted <= 0} {
#stdout # return
tailcall ansi::a {*}$args # }
} # #stdout
proc a+ {args} { # tailcall ansi::a {*}$args
variable colour_disabled #}
variable ansi_wanted #proc a+ {args} {
if {$colour_disabled || $ansi_wanted <= 0} { # variable colour_disabled
return # variable ansi_wanted
} # if {$colour_disabled || $ansi_wanted <= 0} {
#stdout # return
tailcall ansi::a+ {*}$args # }
} # #stdout
# tailcall ansi::a+ {*}$args
#}
proc a? {args} { proc a? {args} {
#stdout #stdout
variable colour_disabled variable colour_disabled
@ -754,16 +759,27 @@ namespace eval punk::console {
variable ansi_wanted variable ansi_wanted
if {[string length $onoff]} { if {[string length $onoff]} {
set onoff [string tolower $onoff] set onoff [string tolower $onoff]
if {$onoff in [list 1 on true yes]} { switch -- $onoff {
1 -
on -
true -
yes {
set ansi_wanted 1 set ansi_wanted 1
} elseif {$onoff in [list 0 off false no]} { }
0 -
off -
false -
no {
set ansi_wanted 0 set ansi_wanted 0
} elseif {$onoff in [list default]} { }
default {
set ansi_wanted 2 set ansi_wanted 2
} else { }
default {
error "punk::console::ansi expected 0|1|on|off|true|false|yes|no|default" error "punk::console::ansi expected 0|1|on|off|true|false|yes|no|default"
} }
} }
}
catch {repl::reset_prompt} catch {repl::reset_prompt}
return [expr {$ansi_wanted}] return [expr {$ansi_wanted}]
} }
@ -868,6 +884,70 @@ 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
} }
proc get_tabstops {{inoutchannels {stdin stdout}}} {
#DECTABSR \x1b\[2\$w
#response example " ^[P2$u9/17/25/33/41/49/57/65/73/81^[\ " (where ^[ is \x1b)
#set capturingregex {(.*)(\x1b\[P2$u()\x1b\[\\)}
#set capturingregex {(.*)(\x1bP2$u((?:[0-9]+)*(?:\/[0-9]+)*)\x1b\\)$}
set capturingregex {(.*)(\x1bP2\$u(.*)\x1b\\)$}
set request "\x1b\[2\$w"
set payload [punk::console::internal::get_ansi_response_payload $request $capturingregex $inoutchannels]
set tabstops [split $payload "/"]
return $tabstops
}
#a simple estimation of tab-width under assumption console is set with even spacing.
#It's known this isn't always the case - but things like textutil::untabify2 take only a single value
#on some systems test_char_width is a similar speed to get_tabstop_apparent_width - but on some test_char_width is much slower
#we will use test_char_width as a fallback
proc get_tabstop_apparent_width {} {
set tslist [get_tabstops]
if {![llength $tslist]} {
#either terminal failed to report - or none set.
set testw [test_char_width \t]
if {[string is integer -strict $testw]} {
return $testw
}
#We don't support none - default to 8
return 8
}
#we generally expect to see a tabstop at column 1 - but it may not be set.
if {[lindex $tslist 0] eq "1"} {
if {[llength $tslist] == 1} {
set testw [test_char_width \t]
if {[string is integer -strict $testw]} {
return $testw
}
return 8
} else {
set next [lindex $tslist 1]
return [expr {$next - 1}]
}
} else {
#simplistic guess at width - review - do we need to consider leftmost tabstops as more likely to be non-representative and look further into the list?
if {[llength $tslist] == 1} {
return [lindex $tslist 0]
} else {
return [expr {[lindex $tslist 1] - [lindex $tslist 0]}]
}
}
}
#default to 8 just because it seems to be most common default in terminals
proc set_tabstop_width {{w 8}} {
set tsize [get_size]
set width [dict get $tsize columns]
set mod [expr {$width % $w}]
set max [expr {$width - $mod}]
set tstops ""
set c 1
while {$c <= $max} {
append tstops [string repeat " " $w][punk::ansi::set_tabstop]
incr c $w
}
set punk::console::tabwidth $w ;#we also attempt to read terminal's tabstops and set tabwidth to the apparent spacing of first non-1 value in tabstops list.
catch {textutil::tabify::untabify2 "" $w} ;#textutil tabify can end up uninitialised and raise errors like "can't read Spaces(<n>).." after a tabstop change This call seems to keep tabify happy - review.
puts -nonewline "[punk::ansi::clear_all_tabstops]\n[punk::ansi::set_tabstop]$tstops"
}
proc get_cursor_pos_list {} { proc get_cursor_pos_list {} {
@ -875,12 +955,14 @@ namespace eval punk::console {
} }
proc get_size {} { proc get_size {} {
if {[catch { if {[catch {
puts -nonewline [punk::ansi::cursor_off][punk::ansi::cursor_save][punk::ansi::move 2000 2000] #some terminals (conemu on windows) scroll the viewport when we make a big move down like this - a move to 1 1 immediately after cursor_save doesn't seem to fix that.
#This issue also occurs when switching back from the alternate screen buffer - so perhaps that needs to be addressed elsewhere.
puts -nonewline [punk::ansi::cursor_off][punk::ansi::cursor_save_dec][punk::ansi::move 2000 2000]
lassign [get_cursor_pos_list] lines cols lassign [get_cursor_pos_list] lines cols
puts -nonewline [punk::ansi::cursor_restore][punk::console::cursor_on];flush stdout puts -nonewline [punk::ansi::cursor_restore][punk::console::cursor_on];flush stdout
set result [list columns $cols rows $lines] set result [list columns $cols rows $lines]
} errM]} { } errM]} {
puts -nonewline [punk::ansi::cursor_restore] puts -nonewline [punk::ansi::cursor_restore_dec]
puts -nonewline [punk::ansi::cursor_on] puts -nonewline [punk::ansi::cursor_on]
error "$errM" error "$errM"
} else { } else {
@ -905,7 +987,7 @@ namespace eval punk::console {
#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.
proc test_char_width {char_or_string {emit 0}} { proc test_char_width {char_or_string {emit 0}} {
return 1 #return 1
#JMN #JMN
#puts stderr "cwtest" #puts stderr "cwtest"
variable ansi_available variable ansi_available
@ -1118,10 +1200,10 @@ namespace eval punk::console {
move $orig_row $orig_col move $orig_row $orig_col
} }
proc scroll_up {n} { proc scroll_up {n} {
puts -nonewline stdout [punk::ansi::scroll_up] puts -nonewline stdout [punk::ansi::scroll_up $n]
} }
proc scroll_down {n} { proc scroll_down {n} {
puts -nonewline stdout [punk::ansi::scroll_down] puts -nonewline stdout [punk::ansi::scroll_down $n]
} }
#review - worth the extra microseconds to inline? might be if used in for example prompt on every keypress. #review - worth the extra microseconds to inline? might be if used in for example prompt on every keypress.
@ -1136,6 +1218,18 @@ namespace eval punk::console {
#[call [fun cursor_restore]] #[call [fun cursor_restore]]
puts -nonewline \x1b\[u puts -nonewline \x1b\[u
} }
#DEC equivalents of cursor_save/cursor_restore - perhaps more widely supported?
proc cursor_save_dec {} {
#*** !doctools
#[call [fun cursor_save_dec]]
puts -nonewline \x1b7
}
proc cursor_restore_dec {} {
#*** !doctools
#[call [fun cursor_restore_dec]]
puts -nonewline \x1b8
}
proc insert_spaces {count} { proc insert_spaces {count} {
puts -nonewline stdout \x1b\[${count}@ puts -nonewline stdout \x1b\[${count}@
} }
@ -1162,6 +1256,8 @@ namespace eval punk::console {
namespace import ansi::move_row namespace import ansi::move_row
namespace import ansi::cursor_save namespace import ansi::cursor_save
namespace import ansi::cursor_restore namespace import ansi::cursor_restore
namespace import ansi::cursor_save_dec
namespace import ansi::cursor_restore_dec
namespace import ansi::scroll_down namespace import ansi::scroll_down
namespace import ansi::scroll_up namespace import ansi::scroll_up
namespace import ansi::insert_spaces namespace import ansi::insert_spaces
@ -1180,7 +1276,7 @@ namespace eval punk::console {
#set blanks [string repeat " " [expr {$col + $tw}]] #set blanks [string repeat " " [expr {$col + $tw}]]
#puts -nonewline [punk::ansi::erase_eol]$blanks;move_emit_return this $col $text #puts -nonewline [punk::ansi::erase_eol]$blanks;move_emit_return this $col $text
#puts -nonewline [move_emit_return this $col [punk::ansi::insert_spaces 150]$text] #puts -nonewline [move_emit_return this $col [punk::ansi::insert_spaces 150]$text]
cursor_save cursor_save_dec
#move_emit_return this $col [punk::ansi::move_forward 50][punk::ansi::insert_spaces 150][punk::ansi::move_back 50][punk::ansi::move_forward $col]$text #move_emit_return this $col [punk::ansi::move_forward 50][punk::ansi::insert_spaces 150][punk::ansi::move_back 50][punk::ansi::move_forward $col]$text
#puts -nonewline [punk::ansi::insert_spaces 150][punk::ansi::move_column $col]$text #puts -nonewline [punk::ansi::insert_spaces 150][punk::ansi::move_column $col]$text
puts -nonewline [punk::ansi::erase_eol][punk::ansi::move_column $col]$text puts -nonewline [punk::ansi::erase_eol][punk::ansi::move_column $col]$text
@ -1203,16 +1299,16 @@ namespace eval punk::console {
puts -nonewline stdout $commands puts -nonewline stdout $commands
return "" return ""
} }
#we can be faster and more efficient if we use the consoles cursor_save command - but each savecursor overrides any previous one. #we can be faster and more efficient if we use the consoles cursor_save_dec command - but each savecursor overrides any previous one.
#leave cursor_off/cursor_on to caller who can wrap more efficiently.. #leave cursor_off/cursor_on to caller who can wrap more efficiently..
proc cursorsave_move_emit_return {row col data args} { proc cursorsave_move_emit_return {row col data args} {
set commands "" set commands ""
append commands [punk::ansi::cursor_save] append commands [punk::ansi::cursor_save_dec]
append commands [punk::ansi::move_emit $row $col $data] append commands [punk::ansi::move_emit $row $col $data]
foreach {row col data} $args { foreach {row col data} $args {
append commands [punk::ansi::move_emit $row $col $data] append commands [punk::ansi::move_emit $row $col $data]
} }
append commands [punk::ansi::cursor_restore] append commands [punk::ansi::cursor_restore_dec]
puts -nonewline stdout $commands; flush stdout puts -nonewline stdout $commands; flush stdout
} }
proc move_emitblock_return {row col textblock} { proc move_emitblock_return {row col textblock} {
@ -1229,12 +1325,12 @@ namespace eval punk::console {
} }
proc cursorsave_move_emitblock_return {row col textblock} { proc cursorsave_move_emitblock_return {row col textblock} {
set commands "" set commands ""
append commands [punk::ansi::cursor_save] append commands [punk::ansi::cursor_save_dec]
foreach ln [split $textblock \n] { foreach ln [split $textblock \n] {
append commands [punk::ansi::move_emit $row $col $ln] append commands [punk::ansi::move_emit $row $col $ln]
incr row incr row
} }
append commands [punk::ansi::cursor_restore] append commands [punk::ansi::cursor_restore_dec]
puts -nonewline stdout $commands;flush stdout puts -nonewline stdout $commands;flush stdout
return return
} }

15
src/modules/punk/fileline-999999.0a1.0.tm

@ -368,6 +368,7 @@ namespace eval punk::fileline::class {
} else { } else {
set tail [string trimleft $opt_linebase +];#ignore + set tail [string trimleft $opt_linebase +];#ignore +
} }
#todo - switch -glob -- $tail
if {[string match eof* $tail]} { if {[string match eof* $tail]} {
set endmath [string range $tail 3 end] set endmath [string range $tail 3 end]
#todo endmath? #todo endmath?
@ -1066,9 +1067,11 @@ namespace eval punk::fileline::class {
foreach whichvar [list start end] { foreach whichvar [list start end] {
upvar 0 ${whichvar}idx index upvar 0 ${whichvar}idx index
if {![string is digit -strict $index]} { if {![string is digit -strict $index]} {
if {"end" eq $index} { switch -glob -- $index {
end {
set index $max set index $max
} elseif {[string match "*-*" $index]} { }
"*-*" {
#end-int or int-int - like lrange etc we don't accept arbitrarily complex expressions #end-int or int-int - like lrange etc we don't accept arbitrarily complex expressions
lassign [split $index -] A B lassign [split $index -] A B
if {$A eq "end"} { if {$A eq "end"} {
@ -1076,7 +1079,8 @@ namespace eval punk::fileline::class {
} else { } else {
set index [expr {$A - $B}] set index [expr {$A - $B}]
} }
} elseif {[string match "*+*" $index]} { }
"*+*" {
lassign [split $index +] A B lassign [split $index +] A B
if {$A eq "end"} { if {$A eq "end"} {
#review - this will just result in out of bounds error in final test - as desired #review - this will just result in out of bounds error in final test - as desired
@ -1085,7 +1089,8 @@ namespace eval punk::fileline::class {
} else { } else {
set index [expr {$A + $B}] set index [expr {$A + $B}]
} }
} else { }
default {
#May be something like +2 or -0 which braced expr can hanle #May be something like +2 or -0 which braced expr can hanle
#we would like to avoid unbraced expr here - as we're potentially dealing with ranges that may come from external sources. #we would like to avoid unbraced expr here - as we're potentially dealing with ranges that may come from external sources.
if {[catch {expr {$index}} index]} { if {[catch {expr {$index}} index]} {
@ -1097,6 +1102,7 @@ namespace eval punk::fileline::class {
} }
} }
} }
}
#Unlike Tcl lrange,lindex etc - we don't want to support out of bound indices. #Unlike Tcl lrange,lindex etc - we don't want to support out of bound indices.
#show the supplied index and what it was mapped to in the error message. #show the supplied index and what it was mapped to in the error message.
if {$startidx < 0 || $startidx > $max} { if {$startidx < 0 || $startidx > $max} {
@ -1308,6 +1314,7 @@ namespace eval punk::fileline {
set bomenc "" set bomenc ""
set is_reliabletxt 0 ;#see http://reliabletxt.com - only utf-8 with bom, utf-16be, utf-16le, utf-32be supported as at 2024 set is_reliabletxt 0 ;#see http://reliabletxt.com - only utf-8 with bom, utf-16be, utf-16le, utf-32be supported as at 2024
set startdata 0 set startdata 0
#todo switch -glob
if {[string match "efbbbf*" $maybe_bom]} { if {[string match "efbbbf*" $maybe_bom]} {
set bomid utf-8 set bomid utf-8
set bomenc utf-8 set bomenc utf-8

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

@ -213,13 +213,17 @@ namespace eval punk::lib {
set resultlist [list] set resultlist [list]
if {[string tolower $opt_case] eq "upper"} { switch -- [string tolower $opt_case] {
upper {
set spec X set spec X
} elseif {[string tolower $opt_case] eq "lower"} { }
lower {
set spec x set spec x
} else { }
default {
error "[namespace current]::dec2hex unknown value '$opt_case' for -case expected upper|lower" error "[namespace current]::dec2hex unknown value '$opt_case' for -case expected upper|lower"
} }
}
set fmt "%${opt_width}.${opt_width}ll${spec}" set fmt "%${opt_width}.${opt_width}ll${spec}"
set list_decimals [lmap d $list_decimals[unset list_decimals] {string map [list _ ""] [string trim $d]}] set list_decimals [lmap d $list_decimals[unset list_decimals] {string map [list _ ""] [string trim $d]}]
@ -529,6 +533,81 @@ namespace eval punk::lib {
return $answer return $answer
} }
#like textutil::adjust::indent - but doesn't strip trailing lines, and doesn't implement skip parameter.
proc indent {text {prefix " "}} {
set result [list]
foreach line [split $text \n] {
if {[string trim $line] eq ""} {
lappend result ""
} else {
lappend result $prefix[string trimright $line]
}
}
return [join $result \n]
}
proc undent {text} {
if {$text eq ""} {
return ""
}
set lines [split $text \n]
set nonblank [list]
foreach ln $lines {
if {[string trim $ln] eq ""} {
continue
}
lappend nonblank $ln
}
set lcp [longestCommonPrefix $nonblank]
if {$lcp eq ""} {
return $text
}
regexp {^([\t ]*)} $lcp _m lcp
if {$lcp eq ""} {
return $text
}
set len [string length $lcp]
set result [list]
foreach ln $lines {
if {[string trim $ln] eq ""} {
lappend result ""
} else {
lappend result [string range $ln $len end]
}
}
return [join $result \n]
}
#A version of textutil::string::longestCommonPrefixList
proc longestCommonPrefix {items} {
if {[llength $items] <= 1} {
return [lindex $items 0]
}
set items [lsort $items[unset items]]
set min [lindex $items 0]
set max [lindex $items end]
#if first and last of sorted list share a prefix - then all do (first and last of sorted list are the most different in the list)
#(sort order nothing to do with length - e.g min may be longer than max)
if {[string length $min] > [string length $max]} {
set temp $min
set min $max
set max $temp
}
set n [string length $min]
set prefix ""
set i -1
while {[incr i] < $n && ([set c [string index $min $i]] eq [string index $max $i])} {
append prefix $c
}
return $prefix
}
#test example of the technique - not necessarily particularly useful as a function, except maybe for brevity/clarity. todo - test if inlined version gives any perf advantage compared to a temp var
proc swapnumvars {namea nameb} {
upvar $namea a $nameb b
set a [expr {$a ^ $b}]
set b [expr {$a ^ $b}]
set a [expr {$a ^ $b}]
}
#e.g linesort -decreasing $data #e.g linesort -decreasing $data
proc linesort {args} { proc linesort {args} {
#*** !doctools #*** !doctools
@ -561,7 +640,6 @@ namespace eval punk::lib {
} elseif {[llength $args] == 1} { } elseif {[llength $args] == 1} {
set joinchar "\n" set joinchar "\n"
set lines [lindex $args 0] set lines [lindex $args 0]
} else { } else {
error "list_as_lines usage: list_as_lines ?-joinchar <char>? <linelist>" error "list_as_lines usage: list_as_lines ?-joinchar <char>? <linelist>"
} }
@ -632,19 +710,26 @@ namespace eval punk::lib {
-ansiresets 0\ -ansiresets 0\
] ]
dict for {o v} $arglist { dict for {o v} $arglist {
if {$o ni {-block -line -commandprefix -ansiresets}} { switch -- $o {
-block - -line - -commandprefix - -ansiresets {}
default {
error "linelist: Unrecognized option '$o' usage:$usage" error "linelist: Unrecognized option '$o' usage:$usage"
} }
} }
}
set opts [dict merge $defaults $arglist] set opts [dict merge $defaults $arglist]
# -- --- --- --- --- --- # -- --- --- --- --- ---
set opt_block [dict get $opts -block] set opt_block [dict get $opts -block]
set known_blockopts [list trimhead trimtail triminner trimall trimhead1 trimtail1 collateempty] if {[llength $opt_block]} {
foreach bo $opt_block { foreach bo $opt_block {
if {$bo ni $known_blockopts} { switch -- $bo {
trimhead - trimtail - triminner - trimall - trimhead1 - trimtail1 - collateempty {}
default {
set known_blockopts [list trimhead trimtail triminner trimall trimhead1 trimtail1 collateempty]
error "linelist: unknown -block option value: $bo known values: $known_blockopts" error "linelist: unknown -block option value: $bo known values: $known_blockopts"
} }
} }
}
#normalize certain combos #normalize certain combos
if {[set posn [lsearch $opt_block trimhead1]] >=0 && "trimhead" in $opt_block} { if {[set posn [lsearch $opt_block trimhead1]] >=0 && "trimhead" in $opt_block} {
set opt_block [lreplace $opt_block $posn $posn] set opt_block [lreplace $opt_block $posn $posn]
@ -662,14 +747,20 @@ namespace eval punk::lib {
error "linelist -block triminner not implemented - sorry" error "linelist -block triminner not implemented - sorry"
} }
}
# -- --- --- --- --- --- # -- --- --- --- --- ---
set opt_line [dict get $opts -line] set opt_line [dict get $opts -line]
set known_lineopts [list trimline trimleft trimright]
foreach lo $opt_line { foreach lo $opt_line {
if {$lo ni $known_lineopts} { switch -- $lo {
trimline - trimleft - trimright {}
default {
set known_lineopts [list trimline trimleft trimright]
error "linelist: unknown -line option value: $lo known values: $known_lineopts" error "linelist: unknown -line option value: $lo known values: $known_lineopts"
} }
} }
}
#normalize trimleft trimright combo #normalize trimleft trimright combo
if {"trimleft" in $opt_line && "trimright" in $opt_line} { if {"trimleft" in $opt_line && "trimright" in $opt_line} {
set opt_line [list "trimline"] set opt_line [list "trimline"]
@ -777,9 +868,15 @@ namespace eval punk::lib {
set linelist $transformed set linelist $transformed
} else { } else {
#INLINE punk::ansi::codetype::is_sgr_reset
#regexp {\x1b\[0*m$} $code
set re_is_sgr_reset {\x1b\[0*m$}
#INLINE punk::ansi::codetype::is_sgr
#regexp {\033\[[0-9;:]*m$} $code
set re_is_sgr {\x1b\[[0-9;:]*m$}
foreach ln $linelist { foreach ln $linelist {
set is_replay_pure_reset [punk::ansi::codetype::is_sgr_reset $replaycodes] ;#only looks at tail code - but if tail is pure reset - any prefix is ignorable #set is_replay_pure_reset [regexp {\x1b\[0*m$} $replaycodes] ;#only looks at tail code - but if tail is pure reset - any prefix is ignorable
set ansisplits [punk::ansi::ta::split_codes_single $ln] set ansisplits [punk::ansi::ta::split_codes_single $ln]
if {[llength $ansisplits]<= 1} { if {[llength $ansisplits]<= 1} {
@ -819,6 +916,11 @@ namespace eval punk::lib {
set codestack [list $code] set codestack [list $code]
} else { } else {
if {[punk::ansi::codetype::is_sgr $code]} { if {[punk::ansi::codetype::is_sgr $code]} {
#todo - proper test of each code - so we only take latest background/foreground etc.
#requires handling codes with varying numbers of parameters.
#basic simplification - remove straight dupes.
set dup_posns [lsearch -all -exact $codestack $code] ;#!must use -exact as codes have square brackets which are interpreted as glob chars.
set codestack [lremove $codestack {*}$dup_posns]
lappend codestack $code lappend codestack $code
} ;#else gx0 or other code - we don't want to stack it with SGR codes } ;#else gx0 or other code - we don't want to stack it with SGR codes
} }
@ -834,7 +936,9 @@ namespace eval punk::lib {
} }
} }
set newreplay [join $codestack ""] #set newreplay [join $codestack ""]
set newreplay [punk::ansi::codetype::sgr_merge {*}$codestack]
if {$line_has_sgr && $newreplay ne $replaycodes} { if {$line_has_sgr && $newreplay ne $replaycodes} {
#adjust if it doesn't already does a reset at start #adjust if it doesn't already does a reset at start
if {[punk::ansi::codetype::has_sgr_leadingreset $newreplay]} { if {[punk::ansi::codetype::has_sgr_leadingreset $newreplay]} {
@ -930,7 +1034,6 @@ namespace eval punk::lib {
set rawargs [lindex $ov_vals 1] set rawargs [lindex $ov_vals 1]
set known_argspecs [list -default -type -range -choices -nocase -optional -multiple -validate_without_ansi -allow_ansi -strip_ansi -ARGTYPE]
set optspec_defaults [dict create\ set optspec_defaults [dict create\
-optional 1\ -optional 1\
-allow_ansi 1\ -allow_ansi 1\
@ -960,8 +1063,8 @@ namespace eval punk::lib {
foreach ln $records { foreach ln $records {
set trimln [string trim $ln] set trimln [string trim $ln]
if {$trimln eq "" || [string index $trimln 0] eq "#"} { switch -- [string index $trimln 0] {
continue "" - # {continue}
} }
set argname [lindex $trimln 0] set argname [lindex $trimln 0]
set argspecs [lrange $trimln 1 end] set argspecs [lrange $trimln 1 end]
@ -977,10 +1080,14 @@ namespace eval punk::lib {
set is_opt 0 set is_opt 0
} }
dict for {spec specval} $argspecs { dict for {spec specval} $argspecs {
if {$spec ni $known_argspecs} { switch -- $spec {
-default - -type - -range - -choices - -nocase - -optional - -multiple - -validate_without_ansi - -allow_ansi - -strip_ansi - -ARGTYPE {}
default {
set known_argspecs [list -default -type -range -choices -nocase -optional -multiple -validate_without_ansi -allow_ansi -strip_ansi -ARGTYPE]
error "punk::lib::opts_values - unrecognised key '$spec' in specifications for argument '$argname' Known option specification keys: $known_argspecs" error "punk::lib::opts_values - unrecognised key '$spec' in specifications for argument '$argname' Known option specification keys: $known_argspecs"
} }
} }
}
set argspecs [dict merge $optspec_defaults $argspecs] set argspecs [dict merge $optspec_defaults $argspecs]
dict set arg_info $argname $argspecs dict set arg_info $argname $argspecs
if {![dict get $argspecs -optional]} { if {![dict get $argspecs -optional]} {
@ -1194,15 +1301,20 @@ namespace eval punk::lib {
set allow_ansi 0 set allow_ansi 0
} }
if {!$allow_ansi} { if {!$allow_ansi} {
foreach e $vlist { #detect should work fine directly on whole list
if {[punk::ansi::ta::detect $e]} { if {[punk::ansi::ta::detect $vlist]} {
error "Option $o for $caller contains ansi - but -allow_ansi is false. Received: '$e'" error "Option $o for $caller contains ansi - but -allow_ansi is false. Received: [ansistring VIEW $vlist]"
}
} }
#foreach e $vlist {
# if {[punk::ansi::ta::detect $e]} {
# error "Option $o for $caller contains ansi - but -allow_ansi is false. Received: '$e'"
# }
#}
} }
set vlist_check [list] set vlist_check [list]
foreach e $vlist { foreach e $vlist {
#could probably stripansi entire list safely in one go? - review
if {$validate_without_ansi} { if {$validate_without_ansi} {
lappend vlist_check [punk::ansi::stripansi $e] lappend vlist_check [punk::ansi::stripansi $e]
} else { } else {
@ -1224,21 +1336,28 @@ namespace eval punk::lib {
if {!$is_default} { if {!$is_default} {
if {[dict exists $arg_info $o -type]} { if {[dict exists $arg_info $o -type]} {
set type [dict get $arg_info $o -type] set type [dict get $arg_info $o -type]
if {[string tolower $type] in {int integer double}} { set ltype [string tolower $type]
if {[string tolower $type] in {int integer}} { switch -- $type {
int -
integer -
double {
switch -- $ltype {
int -
integer {
foreach e $vlist e_check $vlist_check { foreach e $vlist e_check $vlist_check {
if {![string is integer -strict $e_check]} { if {![string is integer -strict $e_check]} {
error "Option $o for $caller requires type 'integer'. Received: '$e'" error "Option $o for $caller requires type 'integer'. Received: '$e'"
} }
} }
} elseif {[string tolower $type] in {double}} { }
double {
foreach e $vlist e_check $vlist_check { foreach e $vlist e_check $vlist_check {
if {![string is double -strict $e_check]} { if {![string is double -strict $e_check]} {
error "Option $o for $caller requires type 'double'. Received: '$e'" error "Option $o for $caller requires type 'double'. Received: '$e'"
} }
} }
} }
}
#todo - small-value double comparisons with error-margin? review #todo - small-value double comparisons with error-margin? review
if {[dict exists $arg_info $o -range]} { if {[dict exists $arg_info $o -range]} {
lassign [dict get $arg_info $o -range] low high lassign [dict get $arg_info $o -range] low high
@ -1248,19 +1367,38 @@ namespace eval punk::lib {
} }
} }
} }
} elseif {[string tolower $type] in {bool boolean}} { }
bool -
boolean {
foreach e $vlist e_check $vlist_check { foreach e $vlist e_check $vlist_check {
if {![string is boolean -strict $e_check]} { if {![string is boolean -strict $e_check]} {
error "Option $o for $caller requires type 'boolean'. Received: '$e'" error "Option $o for $caller requires type 'boolean'. Received: '$e'"
} }
} }
} elseif {[string tolower $type] in {alnum alpha ascii control digit graph lower print punct space upper wordchar xdigit}} { }
alnum -
alpha -
ascii -
control -
digit -
graph -
lower -
print -
punct -
space -
upper -
wordchar -
xdigit {
foreach e $vlist e_check $vlist_check { foreach e $vlist e_check $vlist_check {
if {![string is [string tolower $type] $e_check]} { if {![string is [string tolower $type] $e_check]} {
error "Option $o for $caller requires type '[string tolower $type]'. Received: '$e'" error "Option $o for $caller requires type '[string tolower $type]'. Received: '$e'"
} }
} }
} elseif {[string tolower $type] in {file directory existingfile existingdirectory}} { }
file -
directory -
existingfile -
existingdirectory {
foreach e $vlist e_check $vlist_check { foreach e $vlist e_check $vlist_check {
if {!([string length $e_check]>0 && ![regexp {[\"*?<>\;]} $e_check])} { if {!([string length $e_check]>0 && ![regexp {[\"*?<>\;]} $e_check])} {
error "Option $o for $caller requires type '[string tolower $type]'. Received: '$e' which doesn't look like it could be a file or directory" error "Option $o for $caller requires type '[string tolower $type]'. Received: '$e' which doesn't look like it could be a file or directory"
@ -1279,7 +1417,9 @@ namespace eval punk::lib {
} }
} }
} }
} elseif {[string tolower $type] in {char character}} { }
char -
character {
foreach e $vlist e_check $vlist_check { foreach e $vlist e_check $vlist_check {
if {[string length != 1]} { if {[string length != 1]} {
error "Option $o for $caller requires type '[string tolower $type]'. Received: '$e' which is not a single character" error "Option $o for $caller requires type '[string tolower $type]'. Received: '$e' which is not a single character"
@ -1287,6 +1427,7 @@ namespace eval punk::lib {
} }
} }
} }
}
if {[dict exists $arg_info $o -choices]} { if {[dict exists $arg_info $o -choices]} {
set choices [dict get $arg_info $o -choices] set choices [dict get $arg_info $o -choices]
set nocase [dict get $arg_info $o -nocase] set nocase [dict get $arg_info $o -nocase]
@ -1372,6 +1513,102 @@ namespace eval punk::lib {
insert ::tcl::string::insert] insert ::tcl::string::insert]
} }
interp alias {} errortime {} punk::lib::errortime
proc errortime {script groupsize {iters 2}} {
#by use MAK from https://wiki.tcl-lang.org/page/How+to+Measure+Performance
set i 0
set times {}
if {$iters < 2} {set iters 2}
for {set i 0} {$i < $iters} {incr i} {
set result [uplevel [list time $script $groupsize]]
lappend times [lindex $result 0]
}
set average 0.0
set s2 0.0
foreach time $times {
set average [expr {$average + double($time)/$iters}]
}
foreach time $times {
set s2 [expr {$s2 + (($time-$average)*($time-$average) / ($iters-1))}]
}
set sigma [expr {int(sqrt($s2))}]
set average [expr int($average)]
return "$average +/- $sigma microseconds per iteration"
}
#test function to use with show_jump_tables
#todo - check if switch compilation to jump tables differs by Tcl version
proc switch_char_test {c} {
set dec [scan $c %c]
foreach t [list 1 2 3] {
switch -- $c {
x {
return [list $dec x $t]
}
y {
return [list $dec y $t]
}
z {
return [list $dec z $t]
}
}
}
#tcl 8.6/8.7 (at least)
#curlies must be unescaped and unbraced to work as literals in switch and enable it to compile to jumpTable
switch -- $c {
a {
return [list $dec a]
}
{"} {
return [list $dec dquote]
}
{[} {return [list $dec lb]}
{]} {return [list $dec rb]}
"{" {
return [list $dec lbrace]
}
"}" {
return [list $dec rbrace]
}
default {
return [list $dec $c]
}
}
}
#we are interested in seeing jumpTable line and following lines up until next line starting with "Command" or bracketed number e.g (164)
proc show_jump_tables {procname} {
set data [tcl::unsupported::disassemble proc $procname]
set result ""
set in_jt 0
foreach ln [split $data \n] {
set tln [string trim $ln]
if {!$in_jt} {
if {[string match *jumpTable* $ln]} {
append result $ln \n
set in_jt 1
}
} else {
if {[string match Command* $tln] || [string match "(*) *" $tln]} {
set in_jt 0
} else {
append result $ln \n
}
}
}
return $result
}
#*** !doctools #*** !doctools
#[list_end] [comment {--- end definitions namespace punk::lib ---}] #[list_end] [comment {--- end definitions namespace punk::lib ---}]
} }
@ -1480,7 +1717,9 @@ namespace eval punk::lib::system {
} ;# set escaped 0 at end } ;# set escaped 0 at end
set p [lindex $innerpartials end] set p [lindex $innerpartials end]
if {$escaped == 0} { if {$escaped == 0} {
if {$c eq {"}} { #NOTE - curly braces as switch arm keys must be unescaped and balanced. (escapes stop byte-compilation to jumpTable for switch statements for tcl8.6/8.7 at least)
switch -- $c {
{"} {
if {![info complete ${p}]} { if {![info complete ${p}]} {
lappend waiting {"} lappend waiting {"}
lappend innerpartials "" lappend innerpartials ""
@ -1499,7 +1738,8 @@ namespace eval punk::lib::system {
} }
} }
} }
} elseif {$c eq "\["} { }
{[} {
if {![info complete ${p}$c]} { if {![info complete ${p}$c]} {
lappend waiting "\]" lappend waiting "\]"
lappend innerpartials "" lappend innerpartials ""
@ -1507,7 +1747,8 @@ namespace eval punk::lib::system {
set p ${p}${c} set p ${p}${c}
lset innerpartials end $p lset innerpartials end $p
} }
} elseif {$c eq "\{"} { }
"{" {
if {![info complete ${p}$c]} { if {![info complete ${p}$c]} {
lappend waiting "\}" lappend waiting "\}"
lappend innerpartials "" lappend innerpartials ""
@ -1515,7 +1756,9 @@ namespace eval punk::lib::system {
set p ${p}${c} set p ${p}${c}
lset innerpartials end $p lset innerpartials end $p
} }
} else { }
"}" -
default {
set waitingfor [lindex $waiting end] set waitingfor [lindex $waiting end]
if {$c eq "$waitingfor"} { if {$c eq "$waitingfor"} {
set waiting [lrange $waiting 0 end-1] set waiting [lrange $waiting 0 end-1]
@ -1525,6 +1768,7 @@ namespace eval punk::lib::system {
lset innerpartials end $p lset innerpartials end $p
} }
} }
}
} else { } else {
set p ${p}${c} set p ${p}${c}
lset innerpartials end $p lset innerpartials end $p
@ -1534,14 +1778,20 @@ namespace eval punk::lib::system {
} }
set incomplete [list] set incomplete [list]
foreach w $waiting { foreach w $waiting {
if {$w eq {"}} { #to be treated as literals - curly braces must be unescaped here - and balanced - hence the left-curly empty arm.
switch -- $w {
{"} {
lappend incomplete $w lappend incomplete $w
} elseif {$w eq "\]"} { }
{]} {
lappend incomplete "\[" lappend incomplete "\["
} elseif {$w eq "\}"} { }
"{" {}
"}" {
lappend incomplete "\{" lappend incomplete "\{"
} }
} }
}
set debug 0 set debug 0
if {$debug} { if {$debug} {
foreach w $waiting p $innerpartials { foreach w $waiting p $innerpartials {

2
src/modules/punk/lib-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.

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

@ -494,10 +494,14 @@ namespace eval punk::mix::base {
if {[catch {file type $path} ftype]} { if {[catch {file type $path} ftype]} {
return [list cksum "<PATHNOTFOUND>" opts $opts] return [list cksum "<PATHNOTFOUND>" opts $opts]
} }
if {$ftype ni [list file directory]} {
#review - links? #review - links?
switch -- $ftype {
file - directory {}
default {
error "cksum_path error file type '$ftype' not supported" error "cksum_path error file type '$ftype' not supported"
} }
}
set opt_cksum_algorithm [dict get $opts -cksum_algorithm] set opt_cksum_algorithm [dict get $opts -cksum_algorithm]
@ -512,8 +516,10 @@ namespace eval punk::mix::base {
set opt_cksum_meta [dict get $opts -cksum_meta] set opt_cksum_meta [dict get $opts -cksum_meta]
set opt_use_tar [dict get $opts -cksum_usetar] set opt_use_tar [dict get $opts -cksum_usetar]
if {$ftype eq "file"} { switch -- $ftype {
if {$opt_use_tar eq "auto"} { file {
switch -- $opt_use_tar {
auto {
if {$opt_cksum_meta eq "1"} { if {$opt_cksum_meta eq "1"} {
set opt_use_tar 1 set opt_use_tar 1
} else { } else {
@ -522,7 +528,8 @@ namespace eval punk::mix::base {
set opt_cksum_meta 0 set opt_cksum_meta 0
set opt_use_tar 0 set opt_use_tar 0
} }
} elseif {$opt_use_tar eq "0"} { }
0 {
if {$opt_cksum_meta eq "1"} { if {$opt_cksum_meta eq "1"} {
puts stderr "cksum_path doesn't yet support a non-tar cksum with metadata for a file" puts stderr "cksum_path doesn't yet support a non-tar cksum with metadata for a file"
return [list error unsupported_meta_without_tar cksum "<ERR>" opts $opts] return [list error unsupported_meta_without_tar cksum "<ERR>" opts $opts]
@ -530,7 +537,8 @@ namespace eval punk::mix::base {
#meta == auto or 0 #meta == auto or 0
set opt_cksum_meta 0 set opt_cksum_meta 0
} }
} else { }
default {
#tar == 1 #tar == 1
if {$opt_cksum_meta eq "0"} { if {$opt_cksum_meta eq "0"} {
puts stderr "cksum_path doesn't yet support a tar cksum without metadata for a file" puts stderr "cksum_path doesn't yet support a tar cksum without metadata for a file"
@ -540,8 +548,11 @@ namespace eval punk::mix::base {
set opt_cksum_meta 1 set opt_cksum_meta 1
} }
} }
} elseif {$ftype eq "directory"} { }
if {$opt_use_tar eq "auto"} { }
directory {
switch -- $opt_use_tar {
auto {
if {$opt_cksum_meta in [list "auto" "1"]} { if {$opt_cksum_meta in [list "auto" "1"]} {
set opt_use_tar 1 set opt_use_tar 1
set opt_cksum_meta 1 set opt_cksum_meta 1
@ -549,10 +560,12 @@ namespace eval punk::mix::base {
puts stderr "cksum_path doesn't yet support a content-only cksum of a folder structure. Currently only files supported without metadata. For folders use cksum_path -cksum_meta 1 or auto" puts stderr "cksum_path doesn't yet support a content-only cksum of a folder structure. Currently only files supported without metadata. For folders use cksum_path -cksum_meta 1 or auto"
return [list error unsupported_directory_cksum_without_meta cksum "<ERR>" opts $opts] return [list error unsupported_directory_cksum_without_meta cksum "<ERR>" opts $opts]
} }
} elseif {$opt_use_tar eq "0"} { }
0 {
puts stderr "cksum_path doesn't yet support a cksum of a folder structure without tar. Currently only files supported without metadata. For folders use cksum_path -cksum_meta 1 or auto with -cksum_usetar 1 or auto" puts stderr "cksum_path doesn't yet support a cksum of a folder structure without tar. Currently only files supported without metadata. For folders use cksum_path -cksum_meta 1 or auto with -cksum_usetar 1 or auto"
return [list error unsupported_directory_cksum_without_tar cksum "<ERR>" opts $opts] return [list error unsupported_directory_cksum_without_tar cksum "<ERR>" opts $opts]
} else { }
default {
#tar 1 #tar 1
if {$opt_cksum_meta eq "0"} { if {$opt_cksum_meta eq "0"} {
puts stderr "cksum_path doesn't yet support a tar checksum of a folder structure without metadat. Currently only files supported without metadata. For folders use cksum_path -cksum_meta 1 or auto with -cksum_usetar 1 or auto" puts stderr "cksum_path doesn't yet support a tar checksum of a folder structure without metadat. Currently only files supported without metadata. For folders use cksum_path -cksum_meta 1 or auto with -cksum_usetar 1 or auto"
@ -563,6 +576,8 @@ namespace eval punk::mix::base {
} }
} }
} }
}
}
dict set opts_actual -cksum_meta $opt_cksum_meta dict set opts_actual -cksum_meta $opt_cksum_meta
dict set opts_actual -cksum_usetar $opt_use_tar dict set opts_actual -cksum_usetar $opt_use_tar
@ -578,30 +593,37 @@ namespace eval punk::mix::base {
puts stderr "cksum_path doesn't yet support cksum of entire volume. (todo)" puts stderr "cksum_path doesn't yet support cksum of entire volume. (todo)"
return [list error unsupported_path opts $opts] return [list error unsupported_path opts $opts]
} }
switch -- $opt_cksum_algorithm {
if {$opt_cksum_algorithm eq "sha1"} { sha1 {
package require sha1 package require sha1
set cksum_command [list sha1::sha1 -hex -file] set cksum_command [list sha1::sha1 -hex -file]
} elseif {$opt_cksum_algorithm in [list "sha2" "sha256"]} { }
sha2 - sha256 {
package require sha256 package require sha256
set cksum_command [list sha2::sha256 -hex -file] set cksum_command [list sha2::sha256 -hex -file]
} elseif {$opt_cksum_algorithm eq "md5"} { }
md5 {
package require md5 package require md5
set cksum_command [list md5::md5 -hex -file] set cksum_command [list md5::md5 -hex -file]
} elseif {$opt_cksum_algorithm eq "cksum"} { }
cksum {
package require cksum ;#tcllib package require cksum ;#tcllib
set cksum_command [list crc::cksum -format 0x%X -file] set cksum_command [list crc::cksum -format 0x%X -file]
} elseif {$opt_cksum_algorithm eq "adler32"} { }
adler32 {
set cksum_command [list cksum_adler32_file] set cksum_command [list cksum_adler32_file]
} elseif {$opt_cksum_algorithm in [list "sha3" "sha3-256"]} { }
sha3 - sha3-256 {
#todo - replace with something that doesn't call another process #todo - replace with something that doesn't call another process
#set cksum_command [list apply {{file} {lindex [exec fossil sha3sum -256 $file] 0}}] #set cksum_command [list apply {{file} {lindex [exec fossil sha3sum -256 $file] 0}}]
set cksum_command [list $sha3_implementation 256] set cksum_command [list $sha3_implementation 256]
} elseif {$opt_cksum_algorithm in [list "sha3-224" "sha3-384" "sha3-512"]} { }
sha3-224 - sha3-384 - sah3-512 {
set bits [lindex [split $opt_cksum_algorithm -] 1] set bits [lindex [split $opt_cksum_algorithm -] 1]
#set cksum_command [list apply {{bits file} {lindex [exec fossil sha3sum -$bits $file] 0}} $bits] #set cksum_command [list apply {{bits file} {lindex [exec fossil sha3sum -$bits $file] 0}} $bits]
set cksum_command [list $sha3_implementation $bits] set cksum_command [list $sha3_implementation $bits]
} }
}
set cksum "" set cksum ""
if {$opt_use_tar != 0} { if {$opt_use_tar != 0} {

73
src/modules/punk/mix/commandset/scriptwrap-999999.0a1.0.tm

@ -209,10 +209,14 @@ namespace eval punk::mix::commandset::scriptwrap {
set callposn -1 set callposn -1
set trimln [string trim $callingline_payload] set trimln [string trim $callingline_payload]
if {[string match "rem *" $trimln] || [string match "@rem *" $trimln] || [string match "REM *" $trimln] || [string match "@REM *" $trimln]} {
#if {[string match "rem *" $trimln] || [string match "@rem *" $trimln] || [string match "REM *" $trimln] || [string match "@REM *" $trimln]} {}
#ignore things that look like a call that are beind a REM #ignore things that look like a call that are beind a REM
} else { switch -glob -nocase -- $trimln {
"rem *" - "@rem *" {
}
default {
#todo - better callsite analysis. There can be data between @GOTO or @CALL and : other than just whitespace! #todo - better callsite analysis. There can be data between @GOTO or @CALL and : other than just whitespace!
#todo - allow analysis of colon-less call. May need to check list of internal commands - but what about external ones? #todo - allow analysis of colon-less call. May need to check list of internal commands - but what about external ones?
@ -696,6 +700,7 @@ namespace eval punk::mix::commandset::scriptwrap {
} }
} }
} }
} ;# end switch
incr file_offset $callingline_len ;#including per-line stored line-ending incr file_offset $callingline_len ;#including per-line stored line-ending
} }
if {[dict size $possible_target_labels_found] > 0} { if {[dict size $possible_target_labels_found] > 0} {
@ -1279,16 +1284,20 @@ namespace eval punk::mix::commandset::scriptwrap {
} }
} else { } else {
#first seen of tag name #first seen of tag name
if {$tp eq "close"} { switch -- $tp {
close {
lappend errors "line: $linenum tag $nm encountered type $p close first" lappend errors "line: $linenum tag $nm encountered type $p close first"
dict incr errortags $nm dict incr errortags $nm
} else { }
dict set tags $nm types $tp open {
dict set tags $nm types open
dict set tags $nm indent [dict get $taginfo indent] dict set tags $nm indent [dict get $taginfo indent]
if {$tp eq "open"} {
dict set tags $nm start $linenum dict set tags $nm start $linenum
dict set tags $nm taglines [list $ln] ;#first entry - another will be added on encountering matching closing tag dict set tags $nm taglines [list $ln] ;#first entry - another will be added on encountering matching closing tag
} elseif {$tp eq "openclose"} { }
openclose {
dict set tags $nm types openclose
dict set tags $nm indent [dict get $taginfo indent]
dict set tags $nm start $linenum dict set tags $nm start $linenum
dict set tags $nm end $linenum dict set tags $nm end $linenum
dict set tags $nm taglines [list $ln] ;#single entry is final result for self-closing tag dict set tags $nm taglines [list $ln] ;#single entry is final result for self-closing tag
@ -1381,7 +1390,8 @@ namespace eval punk::mix::commandset::scriptwrap {
set inputconsumed 0 set inputconsumed 0
foreach c $inputchars { foreach c $inputchars {
if {!$invar} { if {!$invar} {
if {$c eq "%"} { switch -- $c {
"%" {
set caretseq 0 set caretseq 0
set lookahead [lrange $inputchars $inputconsumed+1 end] set lookahead [lrange $inputchars $inputconsumed+1 end]
if {"%" in $lookahead} { if {"%" in $lookahead} {
@ -1390,14 +1400,16 @@ namespace eval punk::mix::commandset::scriptwrap {
} else { } else {
incr percentrun incr percentrun
} }
} elseif {$c eq "^"} { }
"^" {
if {$caretseq} { if {$caretseq} {
set caretseq 0 set caretseq 0
append labelout "^" ;#for every pair encountered in direct sequence - second gets included in label append labelout "^" ;#for every pair encountered in direct sequence - second gets included in label
} else { } else {
set caretseq 1 set caretseq 1
} }
} else { }
default {
set caretseq 0 set caretseq 0
if {$percentrun && ($c in [list 0 1 2 3 4 5 6 7 8 9])} { if {$percentrun && ($c in [list 0 1 2 3 4 5 6 7 8 9])} {
#subst %i with value - here we have no way of getting that - so use blank - user will get warning that target not found #subst %i with value - here we have no way of getting that - so use blank - user will get warning that target not found
@ -1411,21 +1423,28 @@ namespace eval punk::mix::commandset::scriptwrap {
append labelout $c append labelout $c
} }
} }
}
} else { } else {
#in var - don't do anything with carets(?) #in var - don't do anything with carets(?)
if {$c eq "%" && $percentrun == 1} { switch -- $c {
% {
if {$percentrun == 1} {
#double percent - rather than just an empty var - emit one % #double percent - rather than just an empty var - emit one %
append labelout % append labelout %
set invar 0 set invar 0
set percentrun 0 set percentrun 0
} elseif {$c eq "%"} { } else {
#presume percentrun is 0 #presume percentrun is 0
set invar 0 set invar 0
lappend varsfound $varname; set varname "" lappend varsfound $varname; set varname ""
} elseif {$c in $varterminals} { }
}
: {
#$varterminals
set invar 0 set invar 0
lappend varsfound $varname; set varname "" lappend varsfound $varname; set varname ""
} else { }
default {
if {$percentrun && ($c in [list 0 1 2 3 4 5 6 7 8 9])} { if {$percentrun && ($c in [list 0 1 2 3 4 5 6 7 8 9])} {
#review - seems to terminate var - and substitute? #review - seems to terminate var - and substitute?
#this branch untested - case where we have %i and further % - what if it was %1var% ? does %1 get substituted ? or %1var% - test #this branch untested - case where we have %i and further % - what if it was %1var% ? does %1 get substituted ? or %1var% - test
@ -1437,6 +1456,32 @@ namespace eval punk::mix::commandset::scriptwrap {
set percentrun 0 set percentrun 0
} }
} }
#if {$c eq "%" && $percentrun == 1} {
# #double percent - rather than just an empty var - emit one %
# append labelout %
# set invar 0
# set percentrun 0
#} elseif {$c eq "%"} {
# #presume percentrun is 0
# set invar 0
# lappend varsfound $varname; set varname ""
#} elseif {$c in $varterminals} {
# set invar 0
# lappend varsfound $varname; set varname ""
#} else {
# if {$percentrun && ($c in [list 0 1 2 3 4 5 6 7 8 9])} {
# #review - seems to terminate var - and substitute?
# #this branch untested - case where we have %i and further % - what if it was %1var% ? does %1 get substituted ? or %1var% - test
# set invar 0
# append varname $c
# } else {
# append varname $c
# }
# set percentrun 0
#}
}
incr inputconsumed incr inputconsumed
} }
# -------------- end % handling % # -------------- end % handling %

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

@ -880,12 +880,14 @@ namespace eval punk::ns {
foreach nsdict $with_results { foreach nsdict $with_results {
dict set opts -nsdict $nsdict dict set opts -nsdict $nsdict
set block [get_nslist {*}$opts] set block [get_nslist {*}$opts]
if {[string first \n $block] < 0} { #if {[string first \n $block] < 0} {
#single line # #single line
set width [textblock::width [list $block]] # set width [textblock::width [list $block]]
} else { #} else {
# set width [textblock::width $block]
#}
set width [textblock::width $block] set width [textblock::width $block]
}
#if multiple results or if there is only 1 result - but we are path-globbing - then we need to show location #if multiple results or if there is only 1 result - but we are path-globbing - then we need to show location
if {$count_with_results > 1 || [regexp {[*?]} [nsprefix $ns_absolute]]} { if {$count_with_results > 1 || [regexp {[*?]} [nsprefix $ns_absolute]]} {
append output \n [dict get $nsdict location] append output \n [dict get $nsdict location]
@ -1356,8 +1358,16 @@ namespace eval punk::ns {
proc corp {path} { proc corp {path} {
#thanks to Richard Suchenwirth for the original - wiki.tcl-lang.org/page/corp #thanks to Richard Suchenwirth for the original - wiki.tcl-lang.org/page/corp
#Note: modified here to support aliases and relative/absolute name (with respect to namespace .ie ::name vs name) #Note: modified here to support aliases and relative/absolute name (with respect to namespace .ie ::name vs name)
if {[info exists punk::console::tabwidth]} {
set tw $::punk::console::tabwidth
} else {
set tw 8
}
set indent [string repeat " " $tw] ;#match
#set indent [string repeat " " $tw] ;#A more sensible default for code - review
if {[info exists ::auto_index($path)]} { if {[info exists ::auto_index($path)]} {
set body "# $::auto_index($path)\n" set body "\n${indent}#corp# auto_index $::auto_index($path)"
} else { } else {
set body "" set body ""
} }
@ -1405,10 +1415,20 @@ namespace eval punk::ns {
} }
} }
if {[nsprefix $origin] ne [nsprefix [nsjoin ${targetns} $name]]} { if {[nsprefix $origin] ne [nsprefix [nsjoin ${targetns} $name]]} {
append body "# namespace origin $origin" \n append body \n "${indent}#corp# namespace origin $origin"
} }
if {$body ne "" && [string index $body end] ne "\n"} {
append body \n
}
if {![catch {package require textutil::tabify} errpkg]} {
set bodytext [info body $origin]
#punk::lib::indent preserves trailing empty lines - unlike textutil version
set bodytext [punk::lib::undent [textutil::untabify2 $bodytext $tw]]
append body [punk::lib::indent $bodytext $indent]
} else {
append body [info body $origin] append body [info body $origin]
}
set argl {} set argl {}
foreach a [info args $origin] { foreach a [info args $origin] {
if {[info default $origin $a def]} { if {[info default $origin $a def]} {
@ -1511,10 +1531,12 @@ namespace eval punk::ns {
#todo support leading solo flags such as -capture to control whether we do a static capture of local vars in the ns #todo support leading solo flags such as -capture to control whether we do a static capture of local vars in the ns
if {[string tolower $pkg_or_existing_ns] in [list :: global]} { switch -- [string tolower $pkg_or_existing_ns] {
"::" - global {
set ns :: set ns ::
set ver "";# tcl version? set ver "";# tcl version?
} else { }
default {
if {[string match ::* $pkg_or_existing_ns]} { if {[string match ::* $pkg_or_existing_ns]} {
if {![namespace exists $pkg_or_existing_ns]} { if {![namespace exists $pkg_or_existing_ns]} {
set ver [package require [string range $pkg_or_existing_ns 2 end]] set ver [package require [string range $pkg_or_existing_ns 2 end]]
@ -1527,6 +1549,7 @@ namespace eval punk::ns {
set ns ::$pkg_or_existing_ns set ns ::$pkg_or_existing_ns
} }
} }
}
if {[namespace exists $ns]} { if {[namespace exists $ns]} {
if {[llength $cmdargs]} { if {[llength $cmdargs]} {
set binding {} set binding {}

10
src/modules/punk/path-999999.0a1.0.tm

@ -126,11 +126,10 @@ namespace eval punk::path {
if {[string range $seg end end] eq "/"} { if {[string range $seg end end] eq "/"} {
set seg [string range $seg 0 end-1] ;# e.g c:/ -> c: / -> "" so that join at end doesn't double up set seg [string range $seg 0 end-1] ;# e.g c:/ -> c: / -> "" so that join at end doesn't double up
} }
if {$seg eq "*"} { switch -- $seg {
lappend pats {[^/]*} * {lappend pats {[^/]*}}
} elseif {$seg eq "**"} { ** {lappend pats {.*}}
lappend pats {.*} default {
} else {
set seg [string map [list {^ {\^} $ {\$} [} {\[} ( {\(} \{ \\\{ \\ {\\}] $seg] ;#treat regex characters in the input as literals set seg [string map [list {^ {\^} $ {\$} [} {\[} ( {\(} \{ \\\{ \\ {\\}] $seg] ;#treat regex characters in the input as literals
set seg [string map [list . {[.]}] $seg] set seg [string map [list . {[.]}] $seg]
if {[regexp {[*?]} $seg]} { if {[regexp {[*?]} $seg]} {
@ -141,6 +140,7 @@ namespace eval punk::path {
} }
} }
} }
}
return "^[join $pats /]\$" return "^[join $pats /]\$"
} }
proc globmatchpath {pathglob path args} { proc globmatchpath {pathglob path args} {

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

@ -475,6 +475,7 @@ proc ::unknown args {
puts stderr ">>>scriptrun_commandlist: $commandlist" puts stderr ">>>scriptrun_commandlist: $commandlist"
#ansiwrap for testing
#set id_stderr [shellfilter::stack::add stderr ansiwrap -settings {-colour {red bold}}] #set id_stderr [shellfilter::stack::add stderr ansiwrap -settings {-colour {red bold}}]
uplevel #0 [list ::catch [list ::shellfilter::run $commandlist -teehandle punk -inbuffering line -outbuffering none ] ::tcl::UnknownResult ::tcl::UnknownOptions] uplevel #0 [list ::catch [list ::shellfilter::run $commandlist -teehandle punk -inbuffering line -outbuffering none ] ::tcl::UnknownResult ::tcl::UnknownOptions]
@ -655,8 +656,11 @@ proc repl::start {inchan args} {
# --- # ---
variable editbuf variable editbuf
variable editbuf_list ;#command history variable editbuf_list ;#command history
variable editbuf_lineindex_submitted variable editbuf_linenum_submitted
# --- # ---
catch {
set punk::console::tabwidth [punk::console::get_tabstop_apparent_width]
}
variable running variable running
variable reading variable reading
@ -677,7 +681,7 @@ proc repl::start {inchan args} {
# --- # ---
set editbuf [punk::repl::class::class_editbuf new {}] set editbuf [punk::repl::class::class_editbuf new {}]
lappend editbuf_list $editbuf ;#current editbuf is always in the history lappend editbuf_list $editbuf ;#current editbuf is always in the history
set editbuf_lineindex_submitted -1 set editbuf_linenum_submitted 0
# --- # ---
if {$::punk::console::ansi_wanted == 2} { if {$::punk::console::ansi_wanted == 2} {
@ -1025,16 +1029,12 @@ proc repl::screen_needs_clearance {} {
return 1 return 1
} }
lassign $last_char_info c what why lassign $last_char_info c what why
if {$what in [list "stdout" "stderr" "stdout/stderr"]} { switch -- $what {
stdout - stderr - stdout/stderr {
return 1 return 1
} }
if {$c eq "\n"} {
return 0
} else {
return 1
} }
return [expr {$c ne "\n"}]
} }
namespace eval repl { namespace eval repl {
@ -1115,6 +1115,10 @@ namespace eval punk::repl::class {
#we should merge first row of newparts differently in case our chunks split a grapheme-combination? #we should merge first row of newparts differently in case our chunks split a grapheme-combination?
# #
if {$o_cursor_row < 1} {
puts stderr "add_chunk warning cursor_row < 1 - changing to minimum value 1"
set o_cursor_row 1
}
set cursor_row_idx [expr {$o_cursor_row -1}] set cursor_row_idx [expr {$o_cursor_row -1}]
set activeline [lindex $o_rendered_lines $cursor_row_idx] set activeline [lindex $o_rendered_lines $cursor_row_idx]
set new0 [lindex $newparts 0] set new0 [lindex $newparts 0]
@ -1124,7 +1128,7 @@ namespace eval punk::repl::class {
#append combined \n #append combined \n
append new0 \n append new0 \n
} }
set underlay $activeline set underlay [punk::ansi::stripansi $activeline]
set line_nextchar_col [expr {[punk::char::string_width $underlay] + 1}] set line_nextchar_col [expr {[punk::char::string_width $underlay] + 1}]
if {$o_cursor_col > $line_nextchar_col} { if {$o_cursor_col > $line_nextchar_col} {
set o_cursor_col $line_nextchar_col set o_cursor_col $line_nextchar_col
@ -1156,33 +1160,28 @@ namespace eval punk::repl::class {
set cursor_row_idx [expr {$o_cursor_row-1}] set cursor_row_idx [expr {$o_cursor_row-1}]
lset o_rendered_lines $cursor_row_idx $result lset o_rendered_lines $cursor_row_idx $result
if {[string is integer -strict $cmove]} { set nextrow $cmove
#cmove - positive,negative or zero #if {$insert_lines_below > 0} {
if {$cmove == 0} { # for {set i 0} {$i < $insert_lines_below} {incr i} {
#set nextrow [expr {$o_cursor_row + 1}] # lappend o_rendered_lines ""
# }
# set o_cursor_col 1 # set o_cursor_col 1
} elseif {$cmove == 1} { #}
#check for overflow_right and unapplied if {$insert_lines_below == 1} {
#leave cursor_column if {[string length $overflow_right]} {
} elseif {$cmove >= 1} { lappend o_rendered_lines $overflow_right
set o_cursor_col [expr {[punk::ansi::printing_length $overflow_right] +1}]
}
} else { } else {
# =<int> - absolute
set nextrow [string range $cmove 1 end]
}
if {$insert_lines_below > 0} {
for {set i 0} {$i < $insert_lines_below} {incr i} {
lappend o_rendered_lines "" lappend o_rendered_lines ""
}
set o_cursor_col 1 set o_cursor_col 1
} }
if {$insert_lines_above > 0} { } elseif {$insert_lines_above == 1} {
#for {set i 0} {$i < $insert_lines_above} {incr i} { #for {set i 0} {$i < $insert_lines_above} {incr i} {
# set o_rendered_lines [linsert $o_rendered_lines $cursor_row_idx ""] # set o_rendered_lines [linsert $o_rendered_lines $cursor_row_idx ""]
# incr nextrow -1 # incr nextrow -1
#} #}
#set o_cursor_col 1 set o_rendered_lines [linsert $o_rendered_lines $cursor_row_idx ""]
set o_cursor_col 1
} }
set o_cursor_row $nextrow set o_cursor_row $nextrow
@ -1293,23 +1292,110 @@ namespace eval punk::repl::class {
return [llength $o_rendered_lines] return [llength $o_rendered_lines]
} }
method line {idx} { method line {idx} {
if {[string is integer -strict $idx]} {
incr idx -1
}
return [lindex $o_rendered_lines $idx] return [lindex $o_rendered_lines $idx]
} }
method lines {args} { method lines {args} {
if {![llength $args]} { switch -- [llength $args] {
set range [list 0 end] 0 {return $o_rendered_lines}
} else { 1 {
set range $args set idx [lindex $args 0]
if {[string is integer -strict $idx]} {
incr idx -1
} }
return [lrange $o_rendered_lines {*}$range] return [list [lindex $o_rendered_lines $idx]]
} }
#min value 1? 2 {
method view_lines {} { lassign $args idx1 idx2
set result "" if {[string is integer -strict $idx1]} {
foreach ln $o_rendered_lines { incr idx1 -1
append result $ln \n
} }
return $result if {[string is integer -strict $idx2]} {
incr idx2 -1
}
return [lrange $o_rendered_lines $idx1 $idx2]
}
default {error "lines expected 0,1 or 2 indices"}
}
}
#todo - index base???
method lines_numbered {args} {
#build a paired list so we don't have to do various calcs on end+ end- etc checking llength
#punk::range will use lseq if available - else use it's own slower code
set max [llength $o_rendered_lines] ;#assume >=1
set nums [punk::range 1 $max]
set numline_list [list]
foreach n $nums ln $o_rendered_lines {
lappend numline_list [list $n $ln]
}
switch -- [llength $args] {
0 {return $numline_list}
1 {return [lindex $numline_list [lindex $args 0]]}
2 {return [lrange $numline_list {*}$args]}
default {error "lines expected 0,1 or 2 indices"}
}
}
#1-based
method delete_line {linenum} {
error "unimplemented"
if {$linenum eq "end"} {
set linenum [llength $o_rendered_lines]
}
if {![string is integer -strict $linenum]} {
error "delete_line accepts only end or an integer from 1 to linecount"
}
if {$linenum == 0} {
error "minimum line is 1"
}
set o_rendered_lines [lreplace $o_rendered_lines $index $index]
}
#clear data from last line only
method clear_tail {} {
set o_cursor_row [llength $o_rendered_lines]
set o_cursor_col 1
lset o_rendered_lines end ""
}
#1-based
method view_lines {args} {
set llist [my lines {*}$args]
return [join $llist \n]
}
method view_lines_numbered {args} {
set ANSI_linenum [a+ green]
set RST [a]
set llist [my lines_numbered {*}$args]
set nums [lsearch -all -inline -index 0 -subindices $llist *]
lset nums $o_cursor_row-1 "[a+ bold underline]$o_cursor_row${RST}$ANSI_linenum"
set lines [lsearch -all -inline -index 1 -subindices $llist *]
set cursorline [lindex $lines $o_cursor_row-1]
set charindex_at_cursor [ansistring COLUMNINDEX $cursorline $o_cursor_col]
if {$charindex_at_cursor ne ""} {
lassign [ansistring INDEXCOLUMNS $cursorline $charindex_at_cursor] col0 col1
#we now have the column extents of the possibly double-wide character at the cursor
#we can apply ansi just to those columns using a transparent overtype
set prefix [string repeat " " [expr {$col0 -1}]]
set linecols [punk::ansi::printing_length $cursorline]
set suffix [string repeat " " [expr {$linecols -$col1}]]
set char_at_cursor [ansistring index $cursorline $charindex_at_cursor] ;#this is the char with appropriate ansireset codes
set rawchar [punk::ansi::stripansi $char_at_cursor]
if {$rawchar eq " "} {
set charhighlight "[punk::ansi::a+ White]_[a]"
} else {
set charhighlight [punk::ansi::a+ reverse]$char_at_cursor[a]
}
set cursorline [overtype::renderline -transparent 1 -insert_mode 0 -overflow 0 $cursorline $prefix$charhighlight$suffix]
lset lines $o_cursor_row-1 $cursorline
}
set numcol "$ANSI_linenum[join $nums \n][a]"
set linecol [join $lines \n]
return [textblock::join $numcol " " $linecol]
} }
method debugview_lines {} { method debugview_lines {} {
set result "" set result ""
@ -1338,10 +1424,16 @@ namespace eval punk::repl::class {
set lastchunk [lindex $o_chunk_list end] set lastchunk [lindex $o_chunk_list end]
set parts [punk::ansi::ta::split_codes_single $lastchunk] set parts [punk::ansi::ta::split_codes_single $lastchunk]
set lastcode [lindex $parts end-1] set lastcode [lindex $parts end-1]
return [ansistring VIEW -lf 1 $lastcode] return $lastcode
#return [ansistring VIEW -lf 1 $lastcode]
}
method chunks {args} {
switch -- [llength $args] {
0 {return $o_chunk_list}
1 {return [lindex $o_chunk_list [lindex $args 0]]}
2 {return [lrange $o_chunk_list {*}$args]}
default {error "chunks expected 0,1 or 2 arguments (index or range)"}
} }
method chunks {} {
return $o_chunk_list
} }
method view_chunks {} { method view_chunks {} {
set result "" set result ""
@ -1441,6 +1533,8 @@ proc repl::repl_handler {inputchan prompt_config} {
} }
if {!$rawmode} { if {!$rawmode} {
#stdin with line-mode readable events (at least on windows for Tcl 8.7a6 to 9.0a) can get stuck with bytes pending when input longer than 100chars - even though there is a linefeed further on than that. #stdin with line-mode readable events (at least on windows for Tcl 8.7a6 to 9.0a) can get stuck with bytes pending when input longer than 100chars - even though there is a linefeed further on than that.
#This potentially affects a reasonable number of Tcl8.7 kit/tclsh binaries out in the wild. #This potentially affects a reasonable number of Tcl8.7 kit/tclsh binaries out in the wild.
#see bug https://core.tcl-lang.org/tcl/tktview/bda99f2393 (gets stdin problem when non-blocking - Windows) #see bug https://core.tcl-lang.org/tcl/tktview/bda99f2393 (gets stdin problem when non-blocking - Windows)
@ -1476,7 +1570,7 @@ proc repl::repl_handler {inputchan prompt_config} {
if {$chunksize > 0} { if {$chunksize > 0} {
if {[string index $chunk end] eq "\n"} { if {[string index $chunk end] eq "\n"} {
lappend stdinlines $waitingchunk[string range $chunk 0 end-1] lappend stdinlines $waitingchunk[string range $chunk 0 end-1]
punk::console::cursorsave_move_emitblock_return 30 30 "repl_handler num_stdinlines [llength $stdinlines] chunk:$yellow[ansistring VIEW -lf 1 $chunk][a] fblocked:[fblocked $inputchan] pending:[chan pending input stdin]" #punk::console::cursorsave_move_emitblock_return 30 30 "repl_handler num_stdinlines [llength $stdinlines] chunk:$yellow[ansistring VIEW -lf 1 $chunk][a] fblocked:[fblocked $inputchan] pending:[chan pending input stdin]"
if {![chan eof $inputchan]} { if {![chan eof $inputchan]} {
repl_handler_restorechannel $inputchan $original_input_conf repl_handler_restorechannel $inputchan $original_input_conf
@ -1512,9 +1606,9 @@ proc repl::repl_handler {inputchan prompt_config} {
set chunksize [string length $chunk] set chunksize [string length $chunk]
# -- --- --- # -- --- ---
if {$chunksize > 0} { if {$chunksize > 0} {
punk::console::cursorsave_move_emitblock_return 35 120 "chunk: [ansistring VIEW -lf 1 "...[string range $chunk end-10 end]"]" #punk::console::cursorsave_move_emitblock_return 35 120 "chunk: [ansistring VIEW -lf 1 "...[string range $chunk end-10 end]"]"
set ln $chunk ;#temp set ln $chunk ;#temp
punk::console::cursorsave_move_emitblock_return 25 30 [textblock::frame -title line "[a+ green]$waitingchunk[a][a+ red][ansistring VIEW -lf 1 $ln][a+ green]pending:[chan pending input stdin][a]"] #punk::console::cursorsave_move_emitblock_return 25 30 [textblock::frame -title line "[a+ green]$waitingchunk[a][a+ red][ansistring VIEW -lf 1 $ln][a+ green]pending:[chan pending input stdin][a]"]
if {[string index $ln end] eq "\n"} { if {[string index $ln end] eq "\n"} {
lappend stdinlines [string range $ln 0 end-1] lappend stdinlines [string range $ln 0 end-1]
incr lc incr lc
@ -1549,6 +1643,13 @@ proc repl::repl_handler {inputchan prompt_config} {
repl_handler_restorechannel $inputchan $original_input_conf repl_handler_restorechannel $inputchan $original_input_conf
} }
uplevel #0 [list repl::repl_process_data $inputchan raw-read $chunk [list] $prompt_config] uplevel #0 [list repl::repl_process_data $inputchan raw-read $chunk [list] $prompt_config]
while {[join $input_chunks_waiting($inputchan)] ne ""} {
#puts "...[llength $input_chunks_waiting($inputchan)]"
set wchunks $input_chunks_waiting($inputchan)
set ch [lindex $wchunks 0]
set input_chunks_waiting($inputchan) [lrange $wchunks 1 end]
uplevel #0 [list repl::repl_process_data $inputchan raw-waiting $ch [list] $prompt_config]
}
} }
} }
@ -1570,11 +1671,17 @@ proc repl::repl_handler {inputchan prompt_config} {
} }
set in_repl_handler [list] set in_repl_handler [list]
} }
proc repl::editbuf {args} {
variable editbuf
$editbuf {*}$args
}
interp alias {} editbuf {} ::repl::editbuf
proc repl::repl_process_data {inputchan type chunk stdinlines prompt_config} { proc repl::repl_process_data {inputchan type chunk stdinlines prompt_config} {
variable loopinstance variable loopinstance
variable loopcomplete variable loopcomplete
incr loopinstance incr loopinstance
set moredata 0
upvar ::punk::console::input_chunks_waiting input_chunks_waiting
try { try {
variable prompt_reset_flag variable prompt_reset_flag
@ -1591,7 +1698,8 @@ proc repl::repl_process_data {inputchan type chunk stdinlines prompt_config} {
# --- # ---
variable editbuf variable editbuf
variable editbuf_list variable editbuf_list
variable editbuf_lineindex_submitted variable editbuf_linenum_submitted
# --- # ---
variable readingchunk variable readingchunk
variable running variable running
@ -1672,10 +1780,11 @@ proc repl::repl_process_data {inputchan type chunk stdinlines prompt_config} {
#esc or ctrl-lb #esc or ctrl-lb
if {$chunk eq "\x1b"} { if {$chunk eq "\x1b"} {
#return #return
set readingchunk "" #set readingchunk ""
set stdinlines [list "\x1b"] set stdinlines [list "\x1b"]
set commandstr "" set commandstr ""
set chunk "" set chunk ""
$editbuf clear_tail
screen_last_char_add \x1b stdin escape screen_last_char_add \x1b stdin escape
break break
} }
@ -1696,6 +1805,9 @@ proc repl::repl_process_data {inputchan type chunk stdinlines prompt_config} {
after 1000 exit after 1000 exit
return return
} }
if {$chunk eq "\x7f"} {
set chunk "\b\x7f"
}
#ctrl-bslash #ctrl-bslash
if {$chunk eq "\x1c"} { if {$chunk eq "\x1c"} {
#try to brutally terminate process #try to brutally terminate process
@ -1735,7 +1847,7 @@ proc repl::repl_process_data {inputchan type chunk stdinlines prompt_config} {
set info [list_as_lines $lines] set info [list_as_lines $lines]
} }
} errM]} { } errM]} {
set info [textblock::frame -title [a red]error[a] $errM] set info [textblock::frame -title "[a red]error[a]" $errM]
} else { } else {
set info [textblock::frame -ansiborder [a+ green bold] -title "[a cyan]debugview_raw[a]" $info] set info [textblock::frame -ansiborder [a+ green bold] -title "[a cyan]debugview_raw[a]" $info]
} }
@ -1748,14 +1860,15 @@ proc repl::repl_process_data {inputchan type chunk stdinlines prompt_config} {
puts -nonewline [punk::ansi::cursor_on] puts -nonewline [punk::ansi::cursor_on]
} }
if {[catch { if {[catch {
set info [$editbuf view_lines] #set info [$editbuf view_lines]
set info [$editbuf view_lines_numbered]
set lines [lines_as_list -ansiresets 1 $info] set lines [lines_as_list -ansiresets 1 $info]
if {[llength $lines] > 20} { if {[llength $lines] > 20} {
set lines [lrange $lines end-19 end] set lines [lrange $lines end-19 end]
set info [list_as_lines $lines] set info [list_as_lines $lines]
} }
}]} { } editbuf_error]} {
set info [textblock::frame -title [a red]error[a] $errM] set info [textblock::frame -title "[a red]error[a]" "$editbuf_error\n$::errorInfo"]
} else { } else {
set title "[a cyan]editbuf lines [$editbuf linecount][a]" set title "[a cyan]editbuf lines [$editbuf linecount][a]"
append title "[a+ yellow bold] col:[$editbuf cursor_column] row:[$editbuf cursor_row][a]" append title "[a+ yellow bold] col:[$editbuf cursor_column] row:[$editbuf cursor_row][a]"
@ -1771,35 +1884,67 @@ proc repl::repl_process_data {inputchan type chunk stdinlines prompt_config} {
} }
set lines_unsubmitted [expr {[$editbuf linecount] - $editbuf_lineindex_submitted + 1}] set lines_unsubmitted [expr {[$editbuf linecount] - $editbuf_linenum_submitted}]
#there is always one 'line' unsubmitted - although it may be the current one being built, which may be empty string #there is always one 'line' unsubmitted - although it may be the current one being built, which may be empty string
if {$lines_unsubmitted < 1} { if {$lines_unsubmitted < 1} {
puts stderr "repl editbuf_lineindex_submitted out of sync with editbuf" puts stderr "repl editbuf_linenum_submitted out of sync with editbuf"
}
#set trailing_line_index [expr {[$editbuf linecount] -1}]
set last_line_num [$editbuf linecount]
#set nextsubmit_index [expr {$editbuf_lineindex_submitted + 1}]
set nextsubmit_line_num [expr {$editbuf_linenum_submitted + 1}]
set cursor_row [$editbuf cursor_row]
set cursor_index [expr {$cursor_row -1}]
set lastansi [$editbuf last_ansi]
if {$lastansi eq "\x1b\[A"} {
if {$cursor_row > 1} {
puts -nonewline stdout "\x1b\[A"
}
} elseif {$lastansi eq "\x1b\[B"} {
puts -nonewline stdout "\x1b\[B"
} }
flush stdout
set activeline_index [expr {[$editbuf linecount] -1}]
set nextsubmit_index [expr {$editbuf_lineindex_submitted + 1}]
if {$editbuf_lineindex_submitted == -1} { set offset 3
puts -nonewline stdout [a+ cyan][punk::ansi::move_column [expr {$offset +1}]][punk::ansi::erase_eol][$editbuf line $cursor_row][a][punk::ansi::move_column [expr {$offset + [$editbuf cursor_column]}]]
#puts -nonewline stdout $chunk
flush stdout
if {[$editbuf last_char] eq "\n"} { if {[$editbuf last_char] eq "\n"} {
lappend stdinlines [$editbuf line 0] set linelen [punk::ansi::printing_length [$editbuf line $nextsubmit_line_num]]
puts -nonewline stdout [a+ cyan bold][punk::ansi::move_column [expr {$offset +1}]][$editbuf line $nextsubmit_line_num][a][punk::ansi::move_column [expr {$offset + $linelen +1}]]
#screen_last_char_add "\n" input inputline
puts -nonewline stdout [punk::ansi::erase_eol]\n
#puts -nonewline stdout \n
screen_last_char_add "\n" input inputline
set waiting [$editbuf line end]
if {[string length $waiting] > 0} {
set waiting [a+ yellow bold]$waiting[a]
#puts stderr "waiting $waiting"
$editbuf clear_tail
lappend input_chunks_waiting($inputchan) $waiting
}
}
if {$editbuf_linenum_submitted == 0} {
#(there is no line 0 - lines start at 1)
if {[$editbuf last_char] eq "\n"} {
lappend stdinlines [$editbuf line 1]
incr lc incr lc
set editbuf_lineindex_submitted 0 set editbuf_linenum_submitted 1
} }
} else { } else {
if {$nextsubmit_index < $activeline_index} { if {$nextsubmit_line_num < $last_line_num} {
foreach ln [$editbuf lines $nextsubmit_index end-1] { foreach ln [$editbuf lines $nextsubmit_line_num end-1] {
lappend stdinlines $ln lappend stdinlines $ln
incr lc incr lc
incr editbuf_lineindex_submitted incr editbuf_linenum_submitted
}
} }
} }
puts -nonewline stdout $chunk
flush stdout
if {[string index $chunk end] eq "\n"} {
screen_last_char_add "\n" input inputline
} }
set last_cursor_colun [$editbuf cursor_column]
} else { } else {
#rputs stderr "->0byte read stdin" #rputs stderr "->0byte read stdin"
if {[chan eof $inputchan]} { if {[chan eof $inputchan]} {
@ -2186,7 +2331,8 @@ proc repl::repl_process_data {inputchan type chunk stdinlines prompt_config} {
foreach c $result { foreach c $result {
lassign $c termchan text lassign $c termchan text
if {[string length $text]} { if {[string length $text]} {
if {$termchan eq "result"} { switch -- $termchan {
result {
#rputs stdout $resultprompt[string map [list \n "\n$resultprompt"] $text] #rputs stdout $resultprompt[string map [list \n "\n$resultprompt"] $text]
set h [textblock::height $text] set h [textblock::height $text]
set promptcol [string repeat $resultprompt\n $h] set promptcol [string repeat $resultprompt\n $h]
@ -2194,17 +2340,21 @@ proc repl::repl_process_data {inputchan type chunk stdinlines prompt_config} {
rputs [textblock::join -- $promptcol $text] rputs [textblock::join -- $promptcol $text]
#puts -nonewline stdout $text #puts -nonewline stdout $text
} elseif {$termchan eq "resulterr"} { }
resulterr {
rputs stderr $resultprompt[string map [list \n "\n$resultprompt"] $text] rputs stderr $resultprompt[string map [list \n "\n$resultprompt"] $text]
} elseif {$termchan eq "info"} { }
info {
rputs stderr $infoprompt[string map [list \n "\n$infoprompt"] $text] rputs stderr $infoprompt[string map [list \n "\n$infoprompt"] $text]
} else { }
default {
#rputs -nonewline $termchan $text #rputs -nonewline $termchan $text
set chanprompt "_ " set chanprompt "_ "
rputs $termchan ${chanprompt}[string map [list \n "\n${chanprompt}"] $text] rputs $termchan ${chanprompt}[string map [list \n "\n${chanprompt}"] $text]
} }
} }
} }
}
} else { } else {
#----------------------------------------------------------- #-----------------------------------------------------------
@ -2253,19 +2403,24 @@ proc repl::repl_process_data {inputchan type chunk stdinlines prompt_config} {
foreach c $last_run_display { foreach c $last_run_display {
lassign $c termchan text lassign $c termchan text
if {[string length $text]} { if {[string length $text]} {
if {$termchan eq "result"} { switch -- $termchan {
result {
rputs stdout $resultprompt[string map [list \n "\n$resultprompt"] $text] rputs stdout $resultprompt[string map [list \n "\n$resultprompt"] $text]
#puts -nonewline stdout $text #puts -nonewline stdout $text
} elseif {$termchan eq "resulterr"} { }
resulterr {
rputs stderr $resultprompt[string map [list \n "\n$resultprompt"] $text] rputs stderr $resultprompt[string map [list \n "\n$resultprompt"] $text]
} elseif {$termchan eq "info"} { }
info {
rputs stderr $infoprompt[string map [list \n "\n$infoprompt"] $text] rputs stderr $infoprompt[string map [list \n "\n$infoprompt"] $text]
} else { }
default {
rputs -nonewline $termchan $text rputs -nonewline $termchan $text
} }
} }
} }
} }
}
set c [a yellow bold] set c [a yellow bold]
set n [a] set n [a]
@ -2318,7 +2473,8 @@ proc repl::repl_process_data {inputchan type chunk stdinlines prompt_config} {
#we haven't put the data following last le into commandstr - but we want to display proper completion status prior to enter being hit or more data coming in. #we haven't put the data following last le into commandstr - but we want to display proper completion status prior to enter being hit or more data coming in.
#this could give spurious results for large pastes where buffering chunks it in odd places.? #this could give spurious results for large pastes where buffering chunks it in odd places.?
#it does however give sensible output for the common case of a small paste where the last line ending wasn't included #it does however give sensible output for the common case of a small paste where the last line ending wasn't included
set waiting [punk::lib::system::incomplete $commandstr$readingchunk] #set waiting [punk::lib::system::incomplete $commandstr$readingchunk]
set waiting [punk::lib::system::incomplete $commandstr[$editbuf line end]]
} else { } else {
set waiting [punk::lib::system::incomplete $commandstr] set waiting [punk::lib::system::incomplete $commandstr]
} }
@ -2342,7 +2498,9 @@ proc repl::repl_process_data {inputchan type chunk stdinlines prompt_config} {
#rputs stderr "repl: no complete input line: $commandstr" #rputs stderr "repl: no complete input line: $commandstr"
#screen_last_char_add "\n" empty empty #screen_last_char_add "\n" empty empty
screen_last_char_add [string index $readingchunk end] stdinchunk stdinchunk #screen_last_char_add [string index $readingchunk end] stdinchunk stdinchunk
screen_last_char_add [string index [$editbuf line end] end] stdinchunk stdinchunk
} }
@ -2376,6 +2534,10 @@ proc repl::repl_process_data {inputchan type chunk stdinlines prompt_config} {
} }
} }
proc repl::completion {context ebuf} {
}
package provide punk::repl [namespace eval punk::repl { package provide punk::repl [namespace eval punk::repl {
variable version variable version
set version 0.1 set version 0.1

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

@ -418,29 +418,37 @@ namespace eval punk::repo {
continue continue
} }
} }
if {[string match "EDITED *" $ln]} { switch -glob -- $ln {
"EDITED *" {
set path [string trim [string range $ln [string length "EDITED "] end]] ;#should handle spaced paths set path [string trim [string range $ln [string length "EDITED "] end]] ;#should handle spaced paths
dict set pathdict $path "changed" dict set pathdict $path "changed"
} elseif {[string match "ADDED *" $ln]} { }
"ADDED *" {
set path [string trim [string range $ln [string length "ADDED "] end]] set path [string trim [string range $ln [string length "ADDED "] end]]
dict set pathdict $path "new" dict set pathdict $path "new"
} elseif {[string match "DELETED *" $ln]} { }
"DELETED *" {
set path [string trim [string range $ln [string length "DELETED "] end]] set path [string trim [string range $ln [string length "DELETED "] end]]
dict set pathdict $path "missing" dict set pathdict $path "missing"
} elseif {[string match "MISSING *" $ln]} { }
"MISSING *" {
set path [string trim [string range $ln [string length "MISSING "] end]] set path [string trim [string range $ln [string length "MISSING "] end]]
dict set pathdict $path "missing" dict set pathdict $path "missing"
} elseif {[string match "EXTRA *" $ln]} { }
"EXTRA * " {
#fossil will explicitly list files in a new folder - as opposed to git which shows just the folder #fossil will explicitly list files in a new folder - as opposed to git which shows just the folder
set path [string trim [string range $ln [string length "EXTRA "] end]] set path [string trim [string range $ln [string length "EXTRA "] end]]
dict set pathdict $path "extra" dict set pathdict $path "extra"
} elseif {[string match "UNCHANGED *" $ln]} { }
"UNCHANGED *" {
set path [string trim [string range $ln [string length "UNCHANGED "] end]] set path [string trim [string range $ln [string length "UNCHANGED "] end]]
dict set pathdict $path "unchanged" dict set pathdict $path "unchanged"
} else { }
default {
#emit for now #emit for now
puts stderr "unprocessed fossilstate line: $ln" puts stderr "unprocessed fossilstate line: $ln"
} }
}
#other entries?? #other entries??
} }
break break

18
src/modules/punk/timeinterval-999999.0a1.0.tm

@ -223,7 +223,8 @@ namespace eval punk::timeinterval {
while { $s2 ne $s2_test && $counter < 30 } { while { $s2 ne $s2_test && $counter < 30 } {
set s2_diff [expr { $s2_test - $s2 } ] set s2_diff [expr { $s2_test - $s2 } ]
puts "\ninterval_ymdhs: debug s1 $s1 s2 $s2 y $y m $m d $d h $h s $s s2_diff $s2_diff" puts "\ninterval_ymdhs: debug s1 $s1 s2 $s2 y $y m $m d $d h $h s $s s2_diff $s2_diff"
if { [expr { abs($s2_diff) } ] > 86399 } { set absdiff [expr {abs($s2_diff)}]
if { $absdiff > 86399 } {
if { $s2_diff > 0 } { if { $s2_diff > 0 } {
incr d -1 incr d -1
puts "interval_ymdhs: debug, audit adjustment. decreasing 1 day to $d" puts "interval_ymdhs: debug, audit adjustment. decreasing 1 day to $d"
@ -231,7 +232,7 @@ namespace eval punk::timeinterval {
incr d incr d
puts "interval_ymdhs: debug, audit adjustment. increasing 1 day to $d" puts "interval_ymdhs: debug, audit adjustment. increasing 1 day to $d"
} }
} elseif { [expr { abs($s2_diff) } ] > 3599 } { } elseif { $absdiff > 3599 } {
if { $s2_diff > 0 } { if { $s2_diff > 0 } {
incr h -1 incr h -1
puts "interval_ymdhs: debug, audit adjustment. decreasing 1 hour to $h" puts "interval_ymdhs: debug, audit adjustment. decreasing 1 hour to $h"
@ -239,7 +240,7 @@ namespace eval punk::timeinterval {
incr h incr h
puts "interval_ymdhs: debug, audit adjustment. increasing 1 hour to $h" puts "interval_ymdhs: debug, audit adjustment. increasing 1 hour to $h"
} }
} elseif { [expr { abs($s2_diff) } ] > 59 } { } elseif { $absdiff > 59 } {
if { $s2_diff > 0 } { if { $s2_diff > 0 } {
incr mm -1 incr mm -1
puts "interval_ymdhs: debug, audit adjustment. decreasing 1 minute to $mm" puts "interval_ymdhs: debug, audit adjustment. decreasing 1 minute to $mm"
@ -247,7 +248,7 @@ namespace eval punk::timeinterval {
incr mm incr mm
puts "interval_ymdhs: debug, audit adjustment. increasing 1 minute to $mm" puts "interval_ymdhs: debug, audit adjustment. increasing 1 minute to $mm"
} }
} elseif { [expr { abs($s2_diff) } ] > 0 } { } elseif { $absdiff > 0 } {
if { $s2_diff > 0 } { if { $s2_diff > 0 } {
incr s -1 incr s -1
puts "interval_ymdhs: debug, audit adjustment. decreasing 1 second to $s" puts "interval_ymdhs: debug, audit adjustment. decreasing 1 second to $s"
@ -449,7 +450,8 @@ namespace eval punk::timeinterval {
while { $s2 ne $s2_test && $counter < 3 } { while { $s2 ne $s2_test && $counter < 3 } {
set s2_diff [expr { $s2_test - $s2 } ] set s2_diff [expr { $s2_test - $s2 } ]
puts "\ninterval_remains_ymdhs: debug s1 $s1 s2 $s2 y $y m $m d $d h $h s $s s2_diff $s2_diff" puts "\ninterval_remains_ymdhs: debug s1 $s1 s2 $s2 y $y m $m d $d h $h s $s s2_diff $s2_diff"
if { [expr { abs($s2_diff) } ] >= 86399 } { set absdiff [expr {abs($s2_diff)}]
if { $absdiff >= 86399 } {
if { $s2_diff > 0 } { if { $s2_diff > 0 } {
incr d -1 incr d -1
puts "interval_remains_ymdhs: debug, audit adjustment. decreasing 1 day to $d" puts "interval_remains_ymdhs: debug, audit adjustment. decreasing 1 day to $d"
@ -457,7 +459,7 @@ namespace eval punk::timeinterval {
incr d incr d
puts "interval_remains_ymdhs: debug, audit adjustment. increasing 1 day to $d" puts "interval_remains_ymdhs: debug, audit adjustment. increasing 1 day to $d"
} }
} elseif { [expr { abs($s2_diff) } ] > 3599 } { } elseif { $absdiff > 3599 } {
if { $s2_diff > 0 } { if { $s2_diff > 0 } {
incr h -1 incr h -1
puts "interval_remains_ymdhs: debug, audit adjustment. decreasing 1 hour to $h" puts "interval_remains_ymdhs: debug, audit adjustment. decreasing 1 hour to $h"
@ -465,7 +467,7 @@ namespace eval punk::timeinterval {
incr h incr h
puts "interval_remains_ymdhs: debug, audit adjustment. increasing 1 hour to $h" puts "interval_remains_ymdhs: debug, audit adjustment. increasing 1 hour to $h"
} }
} elseif { [expr { abs($s2_diff) } ] > 59 } { } elseif { $absdiff > 59 } {
if { $s2_diff > 0 } { if { $s2_diff > 0 } {
incr mm -1 incr mm -1
puts "interval_remains_ymdhs: debug, audit adjustment. decreasing 1 minute to $mm" puts "interval_remains_ymdhs: debug, audit adjustment. decreasing 1 minute to $mm"
@ -473,7 +475,7 @@ namespace eval punk::timeinterval {
incr mm incr mm
puts "interval_remains_ymdhs: debug, audit adjustment. increasing 1 minute to $mm" puts "interval_remains_ymdhs: debug, audit adjustment. increasing 1 minute to $mm"
} }
} elseif { [expr { abs($s2_diff) } ] > 0 } { } elseif { $absdiff > 0 } {
if { $s2_diff > 0 } { if { $s2_diff > 0 } {
incr s -1 incr s -1
puts "interval_remains_ymdhs: debug, audit adjustment. decreasing 1 second to $s" puts "interval_remains_ymdhs: debug, audit adjustment. decreasing 1 second to $s"

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

File diff suppressed because it is too large Load Diff

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

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

42
src/modules/punkcheck-0.1.0.tm

@ -73,7 +73,11 @@ namespace eval punkcheck {
set record_list [list] set record_list [list]
if {[file exists $punkcheck_file]} { if {[file exists $punkcheck_file]} {
set tdlscript [punk::mix::util::fcat $punkcheck_file] set tdlscript [punk::mix::util::fcat $punkcheck_file]
if {[catch {
set record_list [punk::tdl::prettyparse $tdlscript] set record_list [punk::tdl::prettyparse $tdlscript]
} errparse]} {
error "punkcheck::load_records_from_file failed to parse '$punkcheck_file'\n error:$errparse"
}
} }
return $record_list return $record_list
} }
@ -131,10 +135,12 @@ namespace eval punkcheck {
#get last previous that is tagged INSTALL-RECORD,MODIFY-RECORD,DELETE-RECORD #get last previous that is tagged INSTALL-RECORD,MODIFY-RECORD,DELETE-RECORD
set revlist [lreverse $previous_records] set revlist [lreverse $previous_records]
foreach rec $revlist { foreach rec $revlist {
if {[dict get $rec tag] in [list "INSTALL-RECORD" "MODIFY-RECORD" "DELETE-RECORD" "VIRTUAL-RECORD"]} { switch -- [dict get $rec tag] {
INSTALL-RECORD - MODIFY-RECORD - DELETE-RECORD - VIRTUAL-RECORD {
return $rec return $rec
} }
} }
}
return [list] return [list]
} }
} }
@ -1487,7 +1493,8 @@ namespace eval punkcheck {
lappend files_copied $current_source_dir/$m lappend files_copied $current_source_dir/$m
incr filecount_new incr filecount_new
} else { } else {
if {$overwrite_what eq "installedsourcechanged-targets"} { switch -- $overwrite_what {
installedsourcechanged-targets {
if {[llength $changed]} { if {[llength $changed]} {
#An unrecorded installation is considered a source change (from unknown/unrecorded source to recorded) #An unrecorded installation is considered a source change (from unknown/unrecorded source to recorded)
file copy -force $current_source_dir/$m $current_target_dir file copy -force $current_source_dir/$m $current_target_dir
@ -1497,7 +1504,8 @@ namespace eval punkcheck {
set is_skip 1 set is_skip 1
lappend files_skipped $current_source_dir/$m lappend files_skipped $current_source_dir/$m
} }
} elseif {$overwrite_what eq "synced-targets"} { }
synced-targets {
if {[llength $changed]} { if {[llength $changed]} {
#only overwrite if the target checksum equals the last installed checksum (ie target is in sync with source and so hasn't been customized) #only overwrite if the target checksum equals the last installed checksum (ie target is in sync with source and so hasn't been customized)
set existing_tgt_cksum_info [punk::mix::base::lib::cksum_path $current_target_dir/$m] set existing_tgt_cksum_info [punk::mix::base::lib::cksum_path $current_target_dir/$m]
@ -1529,7 +1537,8 @@ namespace eval punkcheck {
set is_skip 1 set is_skip 1
lappend files_skipped $current_source_dir/$m lappend files_skipped $current_source_dir/$m
} }
} else { }
default {
set is_skip 1 set is_skip 1
puts stderr "Skipping file copy $m target $current_target_dir/$m already exists (use -overwrite all-targets to overwrite)" puts stderr "Skipping file copy $m target $current_target_dir/$m already exists (use -overwrite all-targets to overwrite)"
#TODO! implement newer-targets older-targets? (note ctimes/mtimes are unreliable - may not be worth implementing) #TODO! implement newer-targets older-targets? (note ctimes/mtimes are unreliable - may not be worth implementing)
@ -1537,6 +1546,7 @@ namespace eval punkcheck {
} }
} }
} }
}
set ts_now [clock microseconds] set ts_now [clock microseconds]
@ -1578,14 +1588,18 @@ namespace eval punkcheck {
set subdirs [glob -nocomplain -dir $current_source_dir -type d -tail *] set subdirs [glob -nocomplain -dir $current_source_dir -type d -tail *]
set hiddensubdirs [glob -nocomplain -dir $current_source_dir -type {hidden d} -tail *] set hiddensubdirs [glob -nocomplain -dir $current_source_dir -type {hidden d} -tail *]
foreach h $hiddensubdirs { foreach h $hiddensubdirs {
if {$h in [list "." ".."]} { switch -- $h {
"." - ".." {
continue continue
} }
default {
if {$h ni $subdirs} { if {$h ni $subdirs} {
lappend subdirs $h lappend subdirs $h
} }
} }
} }
}
}
#puts stderr "subdirs: $subdirs" #puts stderr "subdirs: $subdirs"
foreach d $subdirs { foreach d $subdirs {
set skipd 0 set skipd 0
@ -1730,9 +1744,25 @@ namespace eval punkcheck {
} }
proc file_install_record_source_changes {install_record} { proc file_install_record_source_changes {install_record} {
#reject INSTALLFAILED items ? #reject INSTALLFAILED items ?
if {[dict get $install_record tag] ni [list "QUERY-INPROGRESS" "INSTALL-RECORD" "INSTALL-SKIPPED" "INSTALL-INPROGRESS" "MODIFY-INPROGRESS" "MODIFY-RECORD" "MODIFY-SKIPPED" "VIRTUAL-INPROGRESS" "VIRTUAL-RECORD" "VIRTUAL-SKIPPED" "DELETE-RECORD" "DELETE-INPROGRESS" "DELETE-SKIPPED"]} { switch -- [dict get $install_record tag] {
"QUERY-INPROGRESS" -
"INSTALL-RECORD" -
"INSTALL-SKIPPED" -
"INSTALL-INPROGRESS" -
"MODIFY-INPROGRESS" -
"MODIFY-RECORD" -
"MODIFY-SKIPPED" -
"VIRTUAL-INPROGRESS" -
"VIRTUAL-RECORD" -
"VIRTUAL-SKIPPED" -
"DELETE-RECORD" -
"DELETE-INPROGRESS" -
"DELETE-SKIPPED" {
}
default {
error "file_install_record_source_changes bad install record: tag '[dict get $install_record tag]' not INSTALL-RECORD|SKIPPED|INSTALL-INPROGRESS|MODIFY-RECORD|MODIFY-INPROGRESS|VIRTUAL-RECORD|VIRTUAL-INPROGRESS|DELETE-RECORD|DELETE-INPROGRESS" error "file_install_record_source_changes bad install record: tag '[dict get $install_record tag]' not INSTALL-RECORD|SKIPPED|INSTALL-INPROGRESS|MODIFY-RECORD|MODIFY-INPROGRESS|VIRTUAL-RECORD|VIRTUAL-INPROGRESS|DELETE-RECORD|DELETE-INPROGRESS"
} }
}
set source_list [dict_getwithdefault $install_record body [list]] set source_list [dict_getwithdefault $install_record body [list]]
set changed [list] set changed [list]
set unchanged [list] set unchanged [list]

14
src/modules/punkcheck/cli-999999.0a1.0.tm

@ -108,12 +108,14 @@ namespace eval punkcheck::cli {
if {[dict get $r tag] eq "SOURCE"} { if {[dict get $r tag] eq "SOURCE"} {
set path [dict get $r -path] set path [dict get $r -path]
set changed [dict get $r -changed] set changed [dict get $r -changed]
if {[dict get $r -type] eq "file"} { switch -- [dict get $r -type] {
file {
lappend source_files $path lappend source_files $path
if {$changed} { if {$changed} {
lappend source_files_changed $path lappend source_files_changed $path
} }
} elseif {[dict get $r -type] eq "directory"} { }
directory {
lappend source_folders $path lappend source_folders $path
if {$changed} { if {$changed} {
lappend source_folders_changed $path lappend source_folders_changed $path
@ -121,6 +123,7 @@ namespace eval punkcheck::cli {
} }
} }
} }
}
if {[llength $source_files]} { if {[llength $source_files]} {
append pcheck " source files : [llength $source_files] (changed [llength $source_files_changed])" append pcheck " source files : [llength $source_files] (changed [llength $source_files_changed])"
} }
@ -203,12 +206,14 @@ namespace eval punkcheck::cli {
if {[dict get $r tag] eq "SOURCE"} { if {[dict get $r tag] eq "SOURCE"} {
set path [dict get $r -path] set path [dict get $r -path]
set changed [dict get $r -changed] set changed [dict get $r -changed]
if {[dict get $r -type] eq "file"} { switch -- [dict get $r -type] {
file {
lappend source_files $path lappend source_files $path
if {$changed} { if {$changed} {
lappend source_files_changed $path lappend source_files_changed $path
} }
} elseif {[dict get $r -type] eq "directory"} { }
directory {
lappend source_folders $path lappend source_folders $path
if {$changed} { if {$changed} {
lappend source_folders_changed $path lappend source_folders_changed $path
@ -216,6 +221,7 @@ namespace eval punkcheck::cli {
} }
} }
} }
}
if {[llength $source_files]} { if {[llength $source_files]} {
append pcheck " source files : [llength $source_files] (changed [llength $source_files_changed])" append pcheck " source files : [llength $source_files] (changed [llength $source_files_changed])"
} }

155
src/modules/shellfilter-0.1.9.tm

@ -165,17 +165,21 @@ namespace eval shellfilter::ansi2 {
#accept examples for foreground #accept examples for foreground
# 256f-# or 256fg-# or 256f# # 256f-# or 256fg-# or 256f#
# rgbf-<r>-<g>-<b> or rgbfg-<r>-<g>-<b> or rgbf<r>-<g>-<b> # rgbf-<r>-<g>-<b> or rgbfg-<r>-<g>-<b> or rgbf<r>-<g>-<b>
if {[string match -nocase "256f*" $i]} { switch -nocase -glob $i {
"256f*" {
set cc [string trim [string range $i 4 end] -gG] set cc [string trim [string range $i 4 end] -gG]
lappend t "38;5;$cc" lappend t "38;5;$cc"
} elseif {[string match -nocase 256b* $i]} { }
"256b*" {
set cc [string trim [string range $i 4 end] -gG] set cc [string trim [string range $i 4 end] -gG]
lappend t "48;5;$cc" lappend t "48;5;$cc"
} elseif {[string match -nocase rgbf* $i]} { }
"rgbf*" {
set rgb [string trim [string range $i 4 end] -gG] set rgb [string trim [string range $i 4 end] -gG]
lassign [split $rgb -] r g b lassign [split $rgb -] r g b
lappend t "38;2;$r;$g;$b" lappend t "38;2;$r;$g;$b"
} elseif {[string match -nocase rgbb* $i]} { }
"rgbb*" {
set rgb [string trim [string range $i 4 end] -gG] set rgb [string trim [string range $i 4 end] -gG]
lassign [split $rgb -] r g b lassign [split $rgb -] r g b
lappend t "48;2;$r;$g;$b" lappend t "48;2;$r;$g;$b"
@ -183,6 +187,7 @@ namespace eval shellfilter::ansi2 {
} }
} }
} }
}
# \033 - octal. equivalently \x1b in hex which is more common in documentation # \033 - octal. equivalently \x1b in hex which is more common in documentation
if {![llength $t]} { if {![llength $t]} {
return "" ;# a+ nonexistent should return nothing rather than a reset ( \033\[\;m is a reset even without explicit zero(s)) return "" ;# a+ nonexistent should return nothing rather than a reset ( \033\[\;m is a reset even without explicit zero(s))
@ -239,6 +244,7 @@ namespace eval shellfilter::ansi2 {
namespace eval shellfilter::ansi { namespace eval shellfilter::ansi {
#maint warning - from overtype package #maint warning - from overtype package
#stripansi is better/more comprehensive
proc stripcodes {text} { proc stripcodes {text} {
#single "final byte" in the range 0x40–0x7E (ASCII @A–Z[\]^_`a–z{|}~). #single "final byte" in the range 0x40–0x7E (ASCII @A–Z[\]^_`a–z{|}~).
dict set escape_terminals CSI [list @ \\ ^ _ ` | ~ a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z "\{" "\}"] dict set escape_terminals CSI [list @ \\ ^ _ ` | ~ a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z "\{" "\}"]
@ -686,6 +692,9 @@ namespace eval shellfilter::chan {
} }
} }
#this isn't a particularly nice thing to do to a stream - especially if someone isn't expecting ansi codes sprinkled through it.
#It can be useful for test/debugging
oo::class create ansiwrap { oo::class create ansiwrap {
variable o_trecord variable o_trecord
variable o_enc variable o_enc
@ -1024,12 +1033,15 @@ namespace eval shellfilter::stack {
proc _get_stack_floaters {stack} { proc _get_stack_floaters {stack} {
set floaters [list] set floaters [list]
foreach t [lreverse $stack] { foreach t [lreverse $stack] {
if {[dict get $t -action] eq "float"} { switch -- [dict get $t -action] {
float {
lappend floaters $t lappend floaters $t
} else { }
default {
break break
} }
} }
}
return [lreverse $floaters] return [lreverse $floaters]
} }
@ -1299,14 +1311,15 @@ namespace eval shellfilter::stack {
# but here it may help detect unexpected changes during lifetime of the stack and avoids the chance of callers incorrectly using the transform handle?) # but here it may help detect unexpected changes during lifetime of the stack and avoids the chance of callers incorrectly using the transform handle?)
# jn # jn
set transform_record [list -id $id -chan $pipename -encoding [chan configure $localchan -encoding] -transform $transformname -aside {} {*}$args] set transform_record [list -id $id -chan $pipename -encoding [chan configure $localchan -encoding] -transform $transformname -aside {} {*}$args]
switch -glob -- $action {
if {$action in [list "float" "float-locked"]} { float - float-locked {
set obj [$transformname new $transform_record] set obj [$transformname new $transform_record]
set h [chan push $localchan $obj] set h [chan push $localchan $obj]
dict set transform_record -handle $h dict set transform_record -handle $h
dict set transform_record -obj $obj dict set transform_record -obj $obj
lappend stack $transform_record lappend stack $transform_record
} elseif {$action in [list "locked" ""]} { }
"" - locked {
set floaters [_get_stack_floaters $stack] set floaters [_get_stack_floaters $stack]
if {![llength $floaters]} { if {![llength $floaters]} {
set obj [$transformname new $transform_record] set obj [$transformname new $transform_record]
@ -1318,7 +1331,8 @@ namespace eval shellfilter::stack {
set poplist $floaters set poplist $floaters
set stack [insert_transform $pipename $stack $transform_record $poplist] set stack [insert_transform $pipename $stack $transform_record $poplist]
} }
} elseif {[string match sink* $action]} { }
"sink*" {
set redirinfo [_get_stack_top_redirection $stack] set redirinfo [_get_stack_top_redirection $stack]
set idx_existing_redir [dict get $redirinfo index] set idx_existing_redir [dict get $redirinfo index]
if {$idx_existing_redir == -1} { if {$idx_existing_redir == -1} {
@ -1327,12 +1341,14 @@ namespace eval shellfilter::stack {
set poplist $stack set poplist $stack
set stack [insert_transform $pipename $stack $transform_record $poplist] set stack [insert_transform $pipename $stack $transform_record $poplist]
} else { } else {
if {$action eq "sink-replace"} { switch -glob -- $action {
"sink-replace" {
#include that index in the poplist #include that index in the poplist
set poplist [lrange $stack $idx_existing_redir end] set poplist [lrange $stack $idx_existing_redir end]
#pop all from idx_existing_redir to end, but put back 'lrange $poplist 1 end' #pop all from idx_existing_redir to end, but put back 'lrange $poplist 1 end'
set stack [insert_transform $pipename $stack $transform_record $poplist 1] set stack [insert_transform $pipename $stack $transform_record $poplist 1]
} elseif {[string match "sink-aside*" $action]} { }
"sink-aside*" {
set existing_redir_record [lindex $stack $idx_existing_redir] set existing_redir_record [lindex $stack $idx_existing_redir]
if {[string match "*locked*" [dict get $existing_redir_record -action]]} { if {[string match "*locked*" [dict get $existing_redir_record -action]]} {
set put_aside 0 set put_aside 0
@ -1368,7 +1384,8 @@ namespace eval shellfilter::stack {
dict set p -note "re-added-after-sink-aside" dict set p -note "re-added-after-sink-aside"
lappend stack $p lappend stack $p
} }
} else { }
default {
#plain "sink" #plain "sink"
#we only sink to the topmost redirecting filter - which makes sense for an output channel #we only sink to the topmost redirecting filter - which makes sense for an output channel
#For stdin.. this is more problematic as we're more likely to want to intercept the bottom most redirection. #For stdin.. this is more problematic as we're more likely to want to intercept the bottom most redirection.
@ -1379,9 +1396,12 @@ namespace eval shellfilter::stack {
set stack [insert_transform $pipename $stack $transform_record $poplist] set stack [insert_transform $pipename $stack $transform_record $poplist]
} }
} }
} else { }
}
default {
error "shellfilter::stack::add unimplemented action '$action'" error "shellfilter::stack::add unimplemented action '$action'"
} }
}
dict set pipelines $pipename stack $stack dict set pipelines $pipename stack $stack
#puts stdout "==" #puts stdout "=="
@ -1706,20 +1726,24 @@ namespace eval shellfilter {
} }
} else { } else {
#currently only () are used for word_bdepth - todo add all or some wordwrappers chars so that the word_bstack can have multiple active. #currently only () are used for word_bdepth - todo add all or some wordwrappers chars so that the word_bstack can have multiple active.
if {$char eq "("} { switch -- $char {
"(" {
incr word_bdepth incr word_bdepth
lappend word_bstack $char lappend word_bstack $char
append word $char append word $char
} elseif {$char eq ")"} { }
")" {
incr word_bdepth -1 incr word_bdepth -1
set word_bstack [lrange $word_bstack 0 end-1] set word_bstack [lrange $word_bstack 0 end-1]
append word $char append word $char
} else { }
default {
#spaces and chars added to word as it's still in a bracketed section #spaces and chars added to word as it's still in a bracketed section
append word $char append word $char
} }
} }
} }
}
} else { } else {
if {$char eq "("} { if {$char eq "("} {
@ -1801,17 +1825,21 @@ namespace eval shellfilter {
} }
} }
} else { } else {
if {$char eq "("} { switch -- $char {
"(" {
incr word_bdepth incr word_bdepth
append word $char append word $char
} elseif {$char eq ")"} { }
")" {
incr word_bdepth -1 incr word_bdepth -1
append word $char append word $char
} else { }
default {
append word $char append word $char
} }
} }
} }
}
} else { } else {
if {[regexp {[\s]} $char]} { if {[regexp {[\s]} $char]} {
#insig whitespace(?) #insig whitespace(?)
@ -1849,28 +1877,34 @@ namespace eval shellfilter {
#only double quote if argument not quoted with single or double quotes #only double quote if argument not quoted with single or double quotes
proc dquote_if_not_quoted {a} { proc dquote_if_not_quoted {a} {
if {([string range $a 0 0] eq {"}) && ([string range $a end end] eq {"})} { set wrapchars [string cat [string range $a 0 0] [string range $a end end]]
return $a switch -- $wrapchars {
} elseif {([string range $a 0 0] eq {'}) && ([string range $a end end] eq {'})} { {""} - {''} {
return $a return $a
} else { }
default {
set newinner [string map [list {"} "\\\""] $a] set newinner [string map [list {"} "\\\""] $a]
return "\"$newinner\"" return "\"$newinner\""
} }
} }
}
#proc dquote_if_not_bracketed/braced? #proc dquote_if_not_bracketed/braced?
#wrap in double quotes if not double-quoted #wrap in double quotes if not double-quoted
proc dquote_if_not_dquoted {a} { proc dquote_if_not_dquoted {a} {
if {([string range $a 0 0] eq {"}) && ([string range $a end end] eq {"})} { set wrapchars [string cat [string range $a 0 0] [string range $a end end]]
switch -- $wrapchars {
{""} {
return $a return $a
} else { }
default {
#escape any inner quotes.. #escape any inner quotes..
set newinner [string map [list {"} "\\\""] $a] set newinner [string map [list {"} "\\\""] $a]
return "\"$newinner\"" return "\"$newinner\""
} }
} }
}
proc dquote {a} { proc dquote {a} {
#escape any inner quotes.. #escape any inner quotes..
set newinner [string map [list {"} "\\\""] $a] set newinner [string map [list {"} "\\\""] $a]
@ -2122,10 +2156,28 @@ namespace eval shellfilter {
} }
set invalid_flags [list] set invalid_flags [list]
dict for {k -} $args { dict for {k -} $args {
if {$k ni $valid_flags} { switch -- $k {
-timeout -
-outprefix -
-errprefix -
-debug -
-copytempfile -
-outbuffering -
-errbuffering -
-inbuffering -
-readprocesstranslation -
-outtranslation -
-stdinhandler -
-outchan -
-errchan -
-inchan -
-teehandle {
}
default {
lappend invalid_flags $k lappend invalid_flags $k
} }
} }
}
if {[llength $invalid_flags]} { if {[llength $invalid_flags]} {
error "Unknown option(s)'$invalid_flags': must be one of '$valid_flags'" error "Unknown option(s)'$invalid_flags': must be one of '$valid_flags'"
} }
@ -2182,10 +2234,18 @@ namespace eval shellfilter {
lassign [chan pipe] rderr wrerr lassign [chan pipe] rderr wrerr
chan configure $wrerr -blocking 0 chan configure $wrerr -blocking 0
set custom_stderr ""
set lastitem [lindex $commandlist end] set lastitem [lindex $commandlist end]
#todo - ensure we can handle 2> file (space after >) #todo - ensure we can handle 2> file (space after >)
if {[string trim [lindex $commandlist end]] eq "&"} { #review - reconsider the handling of redirections such that tcl-style are handled totally separately to other shell syntaxes!
#
#note 2>@1 must ocur as last word for tcl - but 2@stdout can occur elsewhere
#(2>@stdout echoes to main stdout - not into pipeline)
#To properly do pipelines it looks like we will have to split on | and call this proc multiple times and wire it up accordingly (presumably in separate threads)
switch -- [string trim $lastitem] {
{&} {
set name [lindex $commandlist 0] set name [lindex $commandlist 0]
#background execution - stdout and stderr from child still comes here - but process is backgrounded #background execution - stdout and stderr from child still comes here - but process is backgrounded
#FIX! - this is broken for paths with backslashes for example #FIX! - this is broken for paths with backslashes for example
@ -2193,17 +2253,11 @@ namespace eval shellfilter {
set pidlist [exec {*}$commandlist] set pidlist [exec {*}$commandlist]
return [list pids $pidlist] return [list pids $pidlist]
} }
{2>&1} - {2>@1} {
#review - reconsider the handling of redirections such that tcl-style are handled totally separately to other shell syntaxes!
#
#note 2>@1 must ocur as last word for tcl - but 2@stdout can occur elsewhere
#(2>@stdout echoes to main stdout - not into pipeline)
#To properly do pipelines it looks like we will have to split on | and call this proc multiple times and wire it up accordingly (presumably in separate threads)
set custom_stderr ""
if {[string trim $lastitem] in [list {2>&1} {2>@1}]} {
set custom_stderr {2>@1} ;#use the tcl style set custom_stderr {2>@1} ;#use the tcl style
set commandlist [lrange $commandlist 0 end-1] set commandlist [lrange $commandlist 0 end-1]
} else { }
default {
# 2> filename # 2> filename
# 2>> filename # 2>> filename
# 2>@ openfileid # 2>@ openfileid
@ -2213,6 +2267,7 @@ namespace eval shellfilter {
set commandlist [lrange $commandlist 0 end-1] set commandlist [lrange $commandlist 0 end-1]
} }
} }
}
set lastitem [lindex $commandlist end] set lastitem [lindex $commandlist end]
set teefile "" ;#empty string, write, append set teefile "" ;#empty string, write, append
@ -2224,13 +2279,15 @@ namespace eval shellfilter {
::shellfilter::log::write $runtag "checking for redirections in $commandlist" ::shellfilter::log::write $runtag "checking for redirections in $commandlist"
#sometimes we see a redirection without a following space e.g >C:/somewhere #sometimes we see a redirection without a following space e.g >C:/somewhere
#normalize #normalize
if {[regexp {^>[/[:alpha:]]+} $lastitem]} { switch -regexp -- $lastitem\
{^>[/[:alpha:]]+} {
set lastitem "> [string range $lastitem 1 end]" set lastitem "> [string range $lastitem 1 end]"
} }\
if {[regexp {^>>[/[:alpha:]]+} $lastitem]} { {^>>[/[:alpha:]]+} {
set lastitem ">> [string range $lastitem 2 end]" set lastitem ">> [string range $lastitem 2 end]"
} }
#for a redirection, we assume either a 2-element list at tail of form {> {some path maybe with spaces}} #for a redirection, we assume either a 2-element list at tail of form {> {some path maybe with spaces}}
#or that the tail redirection is not wrapped.. x y z > {some path maybe with spaces} #or that the tail redirection is not wrapped.. x y z > {some path maybe with spaces}
#we can't use list methods such as llenth on a member of commandlist #we can't use list methods such as llenth on a member of commandlist
@ -2299,19 +2356,20 @@ namespace eval shellfilter {
} }
switch -- $redir {
if {$redir in [list ">>" ">"]} { ">>" - ">" {
set redirtarget [string trim $redirtarget "\""] set redirtarget [string trim $redirtarget "\""]
::shellfilter::log::write $runtag " have redirection '$redir' to '$redirtarget'" ::shellfilter::log::write $runtag " have redirection '$redir' to '$redirtarget'"
set winfile $redirtarget ;#default assumption set winfile $redirtarget ;#default assumption
if {[string match "/c/*" $redirtarget]} { switch -glob -- $redirtarget {
"/c/*" {
set winfile "c:/[string range $redirtarget 3 end]" set winfile "c:/[string range $redirtarget 3 end]"
} }
if {[string match "/mnt/c/*" $redirtarget]} { "/mnt/c/*" {
set winfile "c:/[string range $redirtarget 7 end]" set winfile "c:/[string range $redirtarget 7 end]"
} }
}
if {[file exists [file dirname $winfile]]} { if {[file exists [file dirname $winfile]]} {
#containing folder for target exists #containing folder for target exists
@ -2321,15 +2379,16 @@ namespace eval shellfilter {
set teefile "append" set teefile "append"
} }
::shellfilter::log::write $runtag "Directory exists '[file dirname $winfile]' operation:$teefile" ::shellfilter::log::write $runtag "Directory exists '[file dirname $winfile]' operation:$teefile"
} else { } else {
#we should be writing to a file.. but can't #we should be writing to a file.. but can't
::shellfilter::log::write $runtag "cannot verify directory exists '[file dirname $winfile]'" ::shellfilter::log::write $runtag "cannot verify directory exists '[file dirname $winfile]'"
} }
} else { }
default {
::shellfilter::log::write $runtag "No redir found!!" ::shellfilter::log::write $runtag "No redir found!!"
} }
}
#often first element of command list is wrapped and cannot be run directly #often first element of command list is wrapped and cannot be run directly
#e.g {{ls -l} {> {temp.tmp}}} #e.g {{ls -l} {> {temp.tmp}}}
#we will assume that if there is a single element which is a pathname containing a space - it is doubly wrapped. #we will assume that if there is a single element which is a pathname containing a space - it is doubly wrapped.

8
src/modules/shellrun-0.1.1.tm

@ -141,12 +141,12 @@ namespace eval shellrun {
set nonewline 0 set nonewline 0
} }
set idlist_stderr [list] set idlist_stderr [list]
#we leave stdout without imposed ansi colouring - because the source may be colourised #we leave stdout without imposed ansi colouring - because the source may be colourised and because ansi-wrapping a stream at whatever boundaries it comes in at isn't a really nice thing to do.
#stderr might have source colouring - but it usually doesn't seem to, and the visual distiction of red stderr is very handy for the run command. #stderr might have source colouring - but it usually doesn't seem to, and the visual distiction of red stderr can be very handy for the run command.
#A further enhancement could be to detect well-known options such as --color and/or use a configuration for specific commands that have useful colourised stderr, #A further enhancement could be to detect well-known options such as --color and/or use a configuration for specific commands that have useful colourised stderr,
#but defaulting stderr to red is a compromise. #but having an option to configure stderr to red is a compromise.
#Note that the other run commands, runout,runerr, runx don't emit in real-time - so for those commands there may be options to detect and/or post-process stdout and stderr. #Note that the other run commands, runout,runerr, runx don't emit in real-time - so for those commands there may be options to detect and/or post-process stdout and stderr.
#TODO - fix. This has no effect because the repl adds an ansiwrap transform #TODO - fix. This has no effect if/when the repl adds an ansiwrap transform
# what we probably want to do is 'aside' that transform for runxxx commands only. # what we probably want to do is 'aside' that transform for runxxx commands only.
#lappend idlist_stderr [shellfilter::stack::add stderr ansiwrap -settings {-colour {red bold}}] #lappend idlist_stderr [shellfilter::stack::add stderr ansiwrap -settings {-colour {red bold}}]

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

@ -42,6 +42,7 @@ namespace eval textblock {
#return a homogenous block of characters - ie lines all same length, all same character #return a homogenous block of characters - ie lines all same length, all same character
#printing-width in terminal columns is not necessarily same as blockwidth even if a single char is passed (could be full-width unicode character) #printing-width in terminal columns is not necessarily same as blockwidth even if a single char is passed (could be full-width unicode character)
#This can have ansi SGR codes - but to repeat blocks containing ansi-movements, the block should first be rendered to a static output with something like overtype::left
proc block {blockwidth blockheight {char " "}} { proc block {blockwidth blockheight {char " "}} {
if {$blockwidth < 0} { if {$blockwidth < 0} {
error "textblock::block blockwidth must be an integer greater than or equal to zero" error "textblock::block blockwidth must be an integer greater than or equal to zero"
@ -50,15 +51,17 @@ namespace eval textblock {
error "textblock::block blockheight must be a positive integer" error "textblock::block blockheight must be a positive integer"
} }
if {$char eq ""} {return ""} if {$char eq ""} {return ""}
#using string length is ok
if {[string length $char] == 1} { if {[string length $char] == 1} {
set row [string repeat $char $blockwidth] set row [string repeat $char $blockwidth]
set mtrx [lrepeat $blockheight $row] set mtrx [lrepeat $blockheight $row]
return [::join $mtrx \n] return [::join $mtrx \n]
} else { } else {
set charblock [string map [list \r\n \n] $char] set charblock [string map [list \r\n \n] $char]
if {[string first \n $charblock] >= 0} { if {[string last \n $charblock] >= 0} {
if {$blockwidth > 1} { if {$blockwidth > 1} {
set row [.= val $charblock {*}[lrepeat [expr {$blockwidth -1}] |> piper_blockjoin $charblock]] #set row [.= val $charblock {*}[lrepeat [expr {$blockwidth -1}] |> piper_blockjoin $charblock]] ;#building a repeated "|> command arg" list to evaluate as a pipeline. (from before textblock::join could take arbitrary num of blocks )
set row [textblock::join {*}[lrepeat $blockwidth $charblock]]
} else { } else {
set row $charblock set row $charblock
} }
@ -72,33 +75,33 @@ namespace eval textblock {
#todo - consider 'elastic tabstops' for textblocks where tab acts as a column separator and adjacent lines with the same number of tabs form a sort of table #todo - consider 'elastic tabstops' for textblocks where tab acts as a column separator and adjacent lines with the same number of tabs form a sort of table
proc width {textblock} { proc width {textblock} {
#backspaces, vertical tabs,carriage returns #backspaces, vertical tabs ?
if {$textblock eq ""} { if {$textblock eq ""} {
return 0 return 0
} }
#textutil::tabify is a reasonable hack when there are no ansi SGR codes - but probably not always what we want even then - review #textutil::tabify is a reasonable hack when there are no ansi SGR codes - but probably not always what we want even then - review
set textblock [textutil::tabify::untabify2 $textblock] if {[string last \t $textblock] >= 0} {
if {[info exists punk::console::tabwidth]} {
if {[string first \n $textblock] >= 0} { set tw $::punk::console::tabwidth
return [tcl::mathfunc::max {*}[lmap v [punk::lib::lines_as_list -- $textblock] {::punk::char::ansifreestring_width [stripansi $v]}]] } else {
set tw 8
} }
return [punk::char::ansifreestring_width [stripansi $textblock]] set textblock [textutil::tabify::untabify2 $textblock $tw]
} }
proc width_naive {textblock} { if {[punk::ansi::ta::detect $textblock]} {
# doesn't deal with backspaces, vertical tabs,carriage returns, ansi movements set textblock [punk::ansi::stripansi $textblock]
if {$textblock eq ""} {
return 0
} }
if {[string last \n $textblock] >= 0} {
set textblock [textutil::tabify::untabify2 $textblock] ;#a reasonable hack - but probably not always what we want - review return [tcl::mathfunc::max {*}[lmap v [split $textblock \n] {::punk::char::ansifreestring_width $v}]]
}
if {[string first \n $textblock] >= 0} { return [punk::char::ansifreestring_width $textblock]
return [tcl::mathfunc::max {*}[lmap v [punk::lib::lines_as_list -- $textblock] {::punk::char::string_width [stripansi $v]}]] }
#uses tcl's string length on each line. Length will include chars in ansi codes etc - this is not a 'width' determining function.
proc string_length_line_max textblock {
tcl::mathfunc::max {*}[lmap v [split $textblock \n] {string length $v}]
} }
return [punk::char::string_width [stripansi $textblock]] proc string_length_line_min textblock {
tcl::mathfunc::min {*}[lmap v [split $textblock \n] {string length $v}]
} }
proc height {textblock} { proc height {textblock} {
#This is the height as it will/would-be rendered - not the number of input lines purely in terms of le #This is the height as it will/would-be rendered - not the number of input lines purely in terms of le
@ -115,11 +118,22 @@ namespace eval textblock {
if {$textblock eq ""} { if {$textblock eq ""} {
return [dict create width 0 height 1] ;#no such thing as zero-height block - for consistency with non-empty strings having no line-endings return [dict create width 0 height 1] ;#no such thing as zero-height block - for consistency with non-empty strings having no line-endings
} }
set textblock [textutil::tabify::untabify2 $textblock] #strangely - string last (windows tcl8.7 anway) is faster than string first for large strings when the needle not in the haystack
if {[string last \t $textblock] >= 0} {
if {[info exists punk::console::tabwidth]} {
set tw $::punk::console::tabwidth
} else {
set tw 8
}
set textblock [textutil::tabify::untabify2 $textblock $tw]
}
#stripansi on entire block in one go rather than line by line - result should be the same - review - make tests #stripansi on entire block in one go rather than line by line - result should be the same - review - make tests
if {[punk::ansi::ta::detect $textblock]} {
set textblock [punk::ansi::stripansi $textblock] set textblock [punk::ansi::stripansi $textblock]
if {[string first \n $textblock] >= 0} { }
set width [tcl::mathfunc::max {*}[lmap v [punk::lib::lines_as_list -- $textblock] {::punk::char::ansifreestring_width $v}]] if {[string last \n $textblock] >= 0} {
#set width [tcl::mathfunc::max {*}[lmap v [punk::lib::lines_as_list -- $textblock] {::punk::char::ansifreestring_width $v}]]
set width [tcl::mathfunc::max {*}[lmap v [split $textblock \n] {::punk::char::ansifreestring_width $v}]]
} else { } else {
set width [punk::char::ansifreestring_width $textblock] set width [punk::char::ansifreestring_width $textblock]
} }
@ -137,8 +151,13 @@ namespace eval textblock {
if {$block eq ""} { if {$block eq ""} {
return 0 return 0
} }
set block [textutil::tabify::untabify2 $block] if {[info exists punk::console::tabwidth]} {
if {[string first \n $block] >= 0} { set tw $::punk::console::tabwidth
} else {
set tw 8
}
set block [textutil::tabify::untabify2 $block $tw]
if {[string last \n $block] >= 0} {
return [tcl::mathfunc::max {*}[lmap v [punk::lib::lines_as_list -- $block] {::punk::char::string_width [stripansi $v]}]] return [tcl::mathfunc::max {*}[lmap v [punk::lib::lines_as_list -- $block] {::punk::char::string_width [stripansi $v]}]]
} }
if {[catch {llength $block}]} { if {[catch {llength $block}]} {
@ -230,26 +249,44 @@ namespace eval textblock {
} }
return [punk::lib::list_as_lines -- $outlines] return [punk::lib::list_as_lines -- $outlines]
} }
#for joining 'rendered' blocks of plain or ansi text. Being 'rendered' means they are without ansi movement sequences as these have been processed
proc ::textblock::join {args} { proc ::textblock::join {args} {
lassign [punk::args::opts_values { #lassign [punk::lib::opts_values {
blocks -type string -multiple 1 # blocks -type string -multiple 1
} $args] _o opts _v values #} $args] _o opts _v values
set blocks [dict get $values blocks] #set blocks [dict get $values blocks]
if {[lindex $args 0] eq "--"} {
set blocks [lrange $args 1 end]
} else {
set blocks $args
}
set idx 0 set idx 0
set fordata [list] set fordata [list]
set colindices [list]
foreach b $blocks { foreach b $blocks {
set c($idx) [string repeat " " [width $b]] set c($idx) [string repeat " " [width $b]]
#lappend fordata "v($idx)" [punk::lib::lines_as_list -- $b] set w($idx) [width $b] ;#we need the width of a rendered block for per-row renderline calls
#fairly commonly a block may be a vertical list of chars e.g something like 1\n2\n3 or -\n-\n-\n-
#for determining a shortcut to avoid unnecessary ta::detect - (e.g width <=1) we can't use result of textblock::width as it strips ansi.
#testing of any decent size block line by line - even with max_string_length_line is slower than ta::detect anyway.
if {[punk::ansi::ta::detect $b]} {
lappend fordata "v($idx)" [punk::lib::lines_as_list -ansiresets 1 -- $b] lappend fordata "v($idx)" [punk::lib::lines_as_list -ansiresets 1 -- $b]
} else {
#each block is being rendered into its own empty column - we don't need resets if it has no ansi, even if blocks to left and right do have ansi
lappend fordata "v($idx)" [split $b \n]
}
lappend colindices $idx
incr idx incr idx
} }
set outlines [list] set outlines [list]
set colindices [lsort -integer -increasing [array names c]] #set colindices [lsort -integer -increasing [array names c]]
foreach {*}$fordata { foreach {*}$fordata {
set row "" set row ""
foreach colidx $colindices { foreach colidx $colindices {
append row [overtype::left $c($colidx) $v($colidx)] #we know we have a single line, no ansi in underlay and no overflow required - so we can use renderline directly
append row [overtype::renderline -width $w($colidx) -insert_mode 0 -info 0 $c($colidx) $v($colidx)]
} }
lappend outlines $row lappend outlines $row
} }
@ -292,6 +329,7 @@ namespace eval textblock {
set 2frames_b [textblock::join [textblock::frame -ansiborder $cyanb -title "plainpunks" $punks] [textblock::frame -ansiborder $greenb -title "fancypunks" $cpunks]] set 2frames_b [textblock::join [textblock::frame -ansiborder $cyanb -title "plainpunks" $punks] [textblock::frame -ansiborder $greenb -title "fancypunks" $cpunks]]
append out [textblock::frame -title "punks" $2frames_b\n$RST$2frames_a] \n append out [textblock::frame -title "punks" $2frames_b\n$RST$2frames_a] \n
append out [overtype::right [overtype::left [textblock::frame -ansiborder [a+ green bold] -type unicode_box_heavy -title ${redb}PATTERN$RST -subtitle ${redb}PUNK$RST $prightair_cyanb] "$blueb\n\n\P\nU\nN\nK$RST"] "$blueb\n\nL\nI\nF\nE"] \n append out [overtype::right [overtype::left [textblock::frame -ansiborder [a+ green bold] -type unicode_box_heavy -title ${redb}PATTERN$RST -subtitle ${redb}PUNK$RST $prightair_cyanb] "$blueb\n\n\P\nU\nN\nK$RST"] "$blueb\n\nL\nI\nF\nE"] \n
#append out [textblock::frame -title gr $gr0]
return $out return $out
} }
@ -311,8 +349,6 @@ namespace eval textblock {
} }
proc frame {args} { proc frame {args} {
package require punk::char
set contents [lindex $args end] set contents [lindex $args end]
set arglist [lrange $args 0 end-1] set arglist [lrange $args 0 end-1]
if {[llength $arglist] % 2 != 0} { if {[llength $arglist] % 2 != 0} {
@ -321,6 +357,7 @@ namespace eval textblock {
#todo args -justify left|centre|right (center) #todo args -justify left|centre|right (center)
set defaults [dict create\ set defaults [dict create\
-etabs 0\
-type unicode_box\ -type unicode_box\
-title ""\ -title ""\
-subtitle ""\ -subtitle ""\
@ -330,11 +367,15 @@ namespace eval textblock {
] ]
set opts [dict merge $defaults $arglist] set opts [dict merge $defaults $arglist]
foreach {k v} $opts { foreach {k v} $opts {
if {$k ni [dict keys $defaults]} { switch -- $k {
-etabs - -type - -title - -subtitle - -width - -ansiborder - -align {}
default {
error "frame option '$k' not understood. Valid options are [dict keys $defaults]" error "frame option '$k' not understood. Valid options are [dict keys $defaults]"
} }
} }
}
# -- --- --- --- --- --- # -- --- --- --- --- ---
set opt_etabs [dict get $opts -etabs]
set opt_type [dict get $opts -type] set opt_type [dict get $opts -type]
set known_types [list unicode_box unicode_box_heavy unicode_arc unicode_double ascii altg] set known_types [list unicode_box unicode_box_heavy unicode_arc unicode_double ascii altg]
set default_custom [dict create hl " " vl " " tlc " " trc " " blc " " brc " "] set default_custom [dict create hl " " vl " " tlc " " trc " " blc " " brc " "]
@ -344,11 +385,15 @@ namespace eval textblock {
if {[llength $opt_type] %2 == 0} { if {[llength $opt_type] %2 == 0} {
#custom dict may leave out keys - but cannot have unknown keys #custom dict may leave out keys - but cannot have unknown keys
dict for {k v} $opt_type { dict for {k v} $opt_type {
if {$k ni $custom_keys} { switch -- $k {
hl - hlt - hlb - vl - vll - vlr - tlc - trc - blc - brc {}
default {
#k not in custom_keys
set is_custom_dict_ok 0 set is_custom_dict_ok 0
break break
} }
} }
}
} else { } else {
set is_custom_dict_ok 0 set is_custom_dict_ok 0
} }
@ -364,15 +409,27 @@ namespace eval textblock {
# -- --- --- --- --- --- # -- --- --- --- --- ---
set opt_align [dict get $opts -align] set opt_align [dict get $opts -align]
set opt_align [string tolower $opt_align] set opt_align [string tolower $opt_align]
if {$opt_align ni [list left right centre center]} { switch -- $opt_align {
#these are all valid commands for overtype::<cmd> left - right - centre - center {}
default {
error "frame option -align must be left|right|centre|center - received: $$opt_align" error "frame option -align must be left|right|centre|center - received: $$opt_align"
} }
}
#these are all valid commands for overtype::<cmd>
# -- --- --- --- --- --- # -- --- --- --- --- ---
set opt_ansiborder [dict get $opts -ansiborder] set opt_ansiborder [dict get $opts -ansiborder]
# -- --- --- --- --- --- # -- --- --- --- --- ---
set contents [textutil::tabify::untabify2 $contents] if {[string last \t $contents] >= 0} {
if {[info exists punk::console::tabwidth]} {
set tw $::punk::console::tabwidth
} else {
set tw 8
}
if {$opt_etabs} {
set contents [textutil::tabify::untabify2 $contents $tw]
}
}
set contents [string map [list \r\n \n] $contents] set contents [string map [list \r\n \n] $contents]
set actual_contentwidth [width $contents] set actual_contentwidth [width $contents]
@ -394,7 +451,8 @@ namespace eval textblock {
set linecount [textblock::height $contents] set linecount [textblock::height $contents]
set rst [a] set rst [a]
set column [string repeat " " $contentwidth] ;#default - may need to override for custom frame set column [string repeat " " $contentwidth] ;#default - may need to override for custom frame
if {$opt_type eq "altg"} { switch -- $opt_type {
"altg" {
#old style ansi escape sequences with alternate graphics page G0 #old style ansi escape sequences with alternate graphics page G0
set hl [cd::hl] set hl [cd::hl]
set hlt $hl set hlt $hl
@ -409,7 +467,8 @@ namespace eval textblock {
set tbar [string repeat $hl $contentwidth] set tbar [string repeat $hl $contentwidth]
set tbar [cd::groptim $tbar] set tbar [cd::groptim $tbar]
set bbar $tbar set bbar $tbar
} elseif {$opt_type eq "ascii"} { }
"ascii" {
set hl - set hl -
set hlt - set hlt -
set hlb - set hlb -
@ -422,7 +481,8 @@ namespace eval textblock {
set brc + set brc +
set tbar [string repeat - $contentwidth] set tbar [string repeat - $contentwidth]
set bbar $tbar set bbar $tbar
} elseif {$opt_type eq "unicode_box"} { }
"unicode_box" {
#unicode box drawing set #unicode box drawing set
set hl [punk::char::charshort boxd_lhz] ;# light horizontal set hl [punk::char::charshort boxd_lhz] ;# light horizontal
set hlt $hl set hlt $hl
@ -436,7 +496,8 @@ namespace eval textblock {
set brc [punk::char::charshort boxd_lul] set brc [punk::char::charshort boxd_lul]
set tbar [string repeat $hl $contentwidth] set tbar [string repeat $hl $contentwidth]
set bbar $tbar set bbar $tbar
} elseif {$opt_type eq "unicode_box_heavy"} { }
"unicode_box_heavy" {
#unicode box drawing set #unicode box drawing set
set hl [punk::char::charshort boxd_hhz] ;# light horizontal set hl [punk::char::charshort boxd_hhz] ;# light horizontal
set hlt $hl set hlt $hl
@ -450,7 +511,8 @@ namespace eval textblock {
set brc [punk::char::charshort boxd_hul] set brc [punk::char::charshort boxd_hul]
set tbar [string repeat $hl $contentwidth] set tbar [string repeat $hl $contentwidth]
set bbar $tbar set bbar $tbar
} elseif {$opt_type eq "unicode_double"} { }
"unicode_double" {
#unicode box drawing set #unicode box drawing set
set hl [punk::char::charshort boxd_dhz] ;# double horizontal \U2550 set hl [punk::char::charshort boxd_dhz] ;# double horizontal \U2550
set hlt $hl set hlt $hl
@ -464,7 +526,8 @@ namespace eval textblock {
set brc [punk::char::charshort boxd_dul] ;#double up and left \U255D set brc [punk::char::charshort boxd_dul] ;#double up and left \U255D
set tbar [string repeat $hl $contentwidth] set tbar [string repeat $hl $contentwidth]
set bbar $tbar set bbar $tbar
} elseif {$opt_type eq "unicode_arc"} { }
"unicode_arc" {
#unicode box drawing set #unicode box drawing set
set hl [punk::char::charshort boxd_lhz] ;# light horizontal set hl [punk::char::charshort boxd_lhz] ;# light horizontal
set hlt $hl set hlt $hl
@ -478,7 +541,8 @@ namespace eval textblock {
set brc [punk::char::charshort boxd_laul] ;#light arc up and left \U256F set brc [punk::char::charshort boxd_laul] ;#light arc up and left \U256F
set tbar [string repeat $hl $contentwidth] set tbar [string repeat $hl $contentwidth]
set bbar $tbar set bbar $tbar
} else { }
default {
dict with custom_frame {} ;#extract keys as vars dict with custom_frame {} ;#extract keys as vars
if {[dict exists $custom_frame hlt]} { if {[dict exists $custom_frame hlt]} {
set hlt [dict get $custom_frame hlt] set hlt [dict get $custom_frame hlt]
@ -546,6 +610,7 @@ namespace eval textblock {
set bbar [overtype::left -overflow 0 -exposed1 " " -exposed2 " " $blank $bbar] set bbar [overtype::left -overflow 0 -exposed1 " " -exposed2 " " $blank $bbar]
} }
} }
}
#keep lhs/rhs separate? can we do vertical text on sidebars? #keep lhs/rhs separate? can we do vertical text on sidebars?
set lhs [string repeat $vll\n $linecount] set lhs [string repeat $vll\n $linecount]
set lhs [string range $lhs 0 end-1] set lhs [string range $lhs 0 end-1]

76
src/testansi/beastie.ans

@ -0,0 +1,76 @@
²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²
²²²²²²²²²²²²²²²²²²² °°°ÛÛ°°°°°°°°°°°°°°°°°°°°ÛÛ
°°°°°°°°°°°°°°°°°°°°°°°°ÛÛ°°°°°°°°°°°°°°°°°°°°
ÛÛ°°°  ÛÛ ÛÛÛ ÛÛ 
ÛÛÛ ÛÛ ÛÛ ÛÛÛ ÛÛ ÛÛ
Û ÛÛ ÛÛ ÛÛÛ Û ÜÜÜÜÜÜÜÜ
 Û ÛÛÛ ÛÛ ÛÛ ÛÛÛ Û 
Û ÛÜÜ°² ²°ÜÜÛ Û Û ÛÛÛ ÛÛ ÛÛ 
ÛÛÛ Û²±°±°°±²Û 
ÛÛÛ ÛÛ ÛÛ ÛÛÛ Û Û±²
°±±°±²Û Û ÛÛÛ ÛÛ ÛÛ ÛÛÛ 
Û °±°ß\±²/ß°±° Û ÛÛÛ
 ÛÛ ÛÛ ÛÛÛ Û ßÛ²±ÜOÞÝ
Oܱ²Ûß Û ÛÛÛ ÛÛ ÛÛ Û
ÛÛ Û Û±Û±°°±Û±Û Û ÛÛÛ 
ÛÛ ÛÛ ÛÛÛ Û ² ±²þþþþ²± ²
 Û ÛÛÛ ÛÛ ÛÛ Û±ÛÛ
²Û ²°±° ²°ÛÛ°² ±Û°± Û²ÛÛ
±Û ÛÛ ÛÛ Û²Û°±°²±±²
°±²°°²±²°°°±²Û°Û²±°Û²Û 
ÛÛ ÛÛ Û²°±ÛÛ°°°±²°±²
°°±°²Û°±Û²Û°±²±²ÛÛ±°²Û ÛÛ
 ÛÛ Û°±²±ÛÛ±°²°°°±°°
±²°²°±²°±±²°°Û°ÛÛ²±±Û 
 ÛÛ ÛÛ Û²±±ÛÛÛ°°±²
 ±°±°°²±²±°Û±±±° ±°±Û
Û²°±²Û ÛÛ ÛÛ Û°²²
ÛÛ±°± ±°±°±²°±²°±² ±²ÛÛ±²
±Û ÛÛ ÛÛ Û±ÛÛ±² 
 °±±°±°±²±Û± Û°ÛÛ±Û 
ÛÛ ÛÛ ÛÛÛ Û ±°°±²° °±²°
±² Û ÛÛÛ ÛÛ ÛÛ ÛÛÛ Û 
°±°²±± °±²°²² Û ÛÛ
Û ÛÛ ÛÛ ÛÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÛÛÜÛÜ°±²
±²±²±°±²±²ÛÜÛÛÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÛ ÛÛ 
 ÛÛÜßÛÛ °±°±°²±°² ±°±°²±°² 
ÛÛßÜ ÛÛ²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²
²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²²² °°°ÛÛ°°°°°°°°°°°
°°°°°°°°°ÛÛ°°°°°°°°°°°°°°°°°°°°°°°°ÛÛ°°°°°°°°°
°°°°°°°°°°°ÛÛ°°°  ÛÛ Û
ÛÛ ÛÛ  ÛÛÛ ÛÛ ÛÛ ÛÛÛ 
ÛÛ  ÛÛÛ ÛÛ ÛÛ ÛÛÛ 
Û ÜÜÜÜÜÜÜÜ Û  ÛÛÛ ÛÛ ÛÛ 
ÛÛÛ Û Û ÛÜÜ°² ²°ÜÜÛ Û Û  
 ÛÛÛ ÛÛ ÛÛ ÛÛÛ Û
²±°±°°±²Û  ÛÛÛ ÛÛ 
 ÛÛ ÛÛÛ Û Û±²°±±°±²Û Û  
 ÛÛÛ ÛÛ ÛÛ ÛÛÛ Û 
°±°ß\±²/ß°±° Û  ÛÛÛ 
ÛÛ ÛÛ ÛÛÛ Û ßÛ²±ÜOÞÝ
Oܱ²Ûß Û  ÛÛÛ ÛÛ ÛÛ Û
ÛÛ Û Û±Û±°°±Û±Û Û  
ÛÛÛ ÛÛ ÛÛ ÛÛÛ Û 
² ±²þþþþ²± ² Û  ÛÛÛ ÛÛ
 ÛÛ Û°ÛÛ° Û ²°±° 
²°ÛÛ°² ±Û°± Û ²ÛÛ± Û 
 ÛÛ ÛÛ Û±Û±°²° 
°²±±²°±²°°²±²°°°±²Û°Û² °±
°Û² Û ÛÛ ÛÛ ÛÛÛ°°±²±²
 °±°±²°±²°°±°²Û°±Û²Û°±²±²±
 ±°±²°ÛÛÛ ÛÛ ÛÛ ÛÛÛ
°±²²°±²± °±±²°°°±°°±²°²°±²
°±±²°±Û²²Û ±±²ÛÛ±ÛÛÛ ÛÛ Û
Û ÛÛÛ °±²±±Û°±°±°± ±°±
°°²±²±°Û±±±° ±°±Û±²°±²Û° 
ÛÛÛ ÛÛ ÛÛ ÛÛÛ °±²°
²±°± ±°±°±²°±²°±² ±²±Û±²±² ÛÛ
Û ÛÛ ÛÛ ÛÛÛ ±°±² °
±±°±°±²±Û± Û°Û²± ÛÛÛ ÛÛ
 ÛÛ ÛÛÛ Û ±°°±²° °±²°±² 
 Û  ÛÛÛ ÛÛ ÛÛ ÛÛÛ 
Û °±°²±± °±²°²² Û 
 ÛÛÛ ÛÛ ÛÛ ÛÜÜÜÜÜÜÜÜÜÜÜÜÜÛÛÜÜÜ
ÜÜÛÜ°±²±²±²±°±²±²ÛÜÜÜÜÜÛÛÜÜÜÜÜÜÜÜÜÜÜÜÛ
 ÛÛ ÛÛÜßÛÛ °±°±°²±°² ±
°±°²±°²  ÛÛßÜ ÛÛ

72
src/testansi/fish.ans

@ -0,0 +1,72 @@

 
 ワ゚゚゚ ワワワワワワワワ゚゚ロワ ワワ ワ ワワ 
 
 ロ ワロ゚゚ ワ゚ワ ゚ワ゚ロ 
゚゚゚ ワ゚ 
 ワワ゚ ワ ロ゚ロ
ロ゚゚ ロ゚ ワ ワ ロワ
゚゚ 
 ワワワワロロロワ ワ゚ワ゚ロ゚ワワワ 
 ワワワワ 
 ワワ゚ロワロワ゚ロ
 ゚ワ ゚ ゚ ゚ ゚ ゚ワ゚ロワワ 
 ワロ゚゚゚ ロ 
 ワロ゚ロ ゚ロ 
ワ゚ワ ワ゚ワ ゚ ワ ゚ワ゚ ワ゚ ゚ ゚ワワ ワロ
゚ワ゚ ゚ ロ ロ
ワワワ゚ ロ ゚ ロ ワ ゚゚ワ゚ワ ワワ ゚ ゚ワワロ
ワ ワロワ゚ ワ ロ ワ
゚゚ワ ワロワ゚゚゚゚ワ
ロワワ ロ ゚ ゚ワ゚ ワ ロワ゚ワ ワ ゚ ワロワワ゚゚ロワ
ロワ ワロ ワワ ワ ロ 
ロ ロロワ ワロ ワロ
ロロロロロロロロロワ ロ
゚ ワ ワ ゚゚ワワ ワロロロロロロロ
ロワロワ ワロ゚ロ ロ ワ゚ 
ロ ゚゚゚゚゚ ロロロ
ロロロワロロロロロロ ゚ ワワ ロ ゚ロ ワ ゚
 ロロロロロロワロロロロ
ロロ゚ワワ゚ ワ゚゚ ワロ ワロ゚゚゚
ワ ロロロロロロロロロロロ゚
ロ ロワ゚ ワ ワ ゚ ワロ ロロロロ
ロロロロロロロロロ゚ロワ゚ワロ ワ゚ワ゚ 
ロ ロロワ ワロ ゚ロ
゚ワロ゚ ゚ロワ ロロ 
ロロロワロロワ゚ロ
ワワ゚ワ゚ワ゚ ワ ゚ワ ワロ ゚゚゚゚゚゚ 
ロロ゚ロ゚゚゚゚゚゚ ワ゚ワ ワロ 
 ロ゚ ゚ ロワ゚゚ ゚゚゚゚゚゚゚゚゚
ロ゚ワ゚ワ゚ワ゚ワロ ロ ゚ ゚ロ 
 ロロワ゚ワワ゚ ゚ワロ ワ゚ ワ 
 ゚ ロ ワワ ロ ゚ワ ゚ワ ゚ロ゚ワロワ゚ロワロ゚ワ 
゚ワ ワ゚ ワワワワワワワ  
゚゚ワ ゚ ゚ワ゚ロ゚ワ゚ワワ゚゚゚
゚゚゚゚゚゚゚ワワ ゚ ワロ 
ワ ゚ ロ゚ワ゚ロロワロロ ゚ロ ワ゚ワ゚ ワ
ロ゚ ゚ワ  ロ
゚ ゚゚ワ゚ワロワロ゚ ゚
ワワ ゚ワ ロ ゚ロ ワワ゚ロ ゚
ロワ ロ゚ ロ ロ ロロワ 
 ゚ロ ワロワワ゚ワ ロ゚ ワワ
ロロロロロロロロワワ ゚ロワ ワ ロ
 ワワ゚ ロ ロワ ゚ワ ロロ
ワ ゚゚ロロワワワワロ゚ ゚ロ
 ゚ワ゚゚ ロロロロロロロロロロロロワワ ゚゚゚
ロ ゚ワ ゚ワ ロ ゚ロワワ ゚
ロ ゚ロワロ
 ゚ワ゚ワワワ ゚ロロロロ゚ロ゚ロ゚ロロワロロロ゚゚ワロ
゚ ゚ロ ロ゚ワ ロ゚ 
 ゚゚゚ワワワ ゚
 ワ゚ワ ワ ゚ロワ ゚゚゚゚゚゚゚゚゚ 
 ワ゚゚゚ ワ ワロ ワ ロ゚ 
 ゚ワ ゚゚ワワ
ロ゚ロロロロワワワワワワワワワ゚ロ ゚
ワワ ゚ ロ゚ ワロ゚ 
 ワワワワワワワロワワワワロ
゚ロワロ゚ロ ロワワ゚ワワ゚ワロ ワ゚ワ゚ワワロ゚ワワ゚゚ロロワワワワワワワワ
 ワロ゚゚ワロ゚゚ロワ゚ワロロ゚
ロロロロロロワロ゚ワロロロ ロ゚ワ゚゚ロロロロ゚ロワロ゚ワ ロワ ワ゚ ゚ワワ゚ 
 (c) Magic-MARK-er Graphics

95
src/testansi/flower.ans

@ -0,0 +1,95 @@
      leahciM  
  イロロロロ ワ゚   イロ
ロロロ ワワ ゚゚゚゚ロロ゙
 ロロロロ゚ ゚゚゚    
       :yb neercS  
 イロロロロ     イロロ
ロロワ  ゚ ロロロロ ー イ
ロロロ゙ワ イロロロ゚ワワロ゚
゚゚                 
   イロロロワ ワ゚ロ 
 イロロロロワ ゚゚ ロロロ
ワ ー イロロロ゚ワ イロロロ
゚ ワワロロ゚゚゚       
         イロロロ  
ワ   イロロロワワ゚゚゚゚
ワワ  ーーロン イロロロ
゚ワ イイロロ゚  ワロ゚
ロロ゚゚ ロ     ゚゚゚ ゚゚゚
イロロロ     イロロロワ
 ゚゚ワワワ   ワ ー  
ロロワ゚ワロ ワワ イ
ロロ゚  ワワワロロロ゚   
  ワ゚゚゚゚゚ロロロ ゙ 
 ゙ワ イロロワ  ロワ
 ゚゚゚゚ーー゚ロ゚゚゚ロロ
ロ   ワ イロロ     ワワ
ワワワ      ワワワワワワ
ワンン アイ ゙ ワワワ
ワ   ワワワ  ワロロロロロ゚゚ロロロ
ロロロワ゚゚゚゚゚ワワロ   
                     
  ゙ン アイイ゙ ゙ロロ゚
゚゚゚ ゚゚゚゚゚゚ワロロロロロロロ
イイイ゚ロロロロロロロロロロ゚゚゚  
                     
 ン ゙ アアイワ ロロロロロ
ロ゚゚゚ロロロイイロロ゚゚ワロロイ
イイ゚゚イイイイイロロロロロロワワ゚
゚                   
  ン ン  イイン ロロ゚ロ
ロロロロロロロロロロイイイイイワワ゚゚
イイイイロロロロワワワワワロロ
 ゚                 
     ゙  ワイン イロワワ
ワロロロロロロロロロロワ゚ロワワワロロロワワワ
゚゚ロ゚゚ ワロロ ゚゚    
               ン    ワ
ワワーイロ゚ロロ゚゚゚ワワワワワ゚゚
イイロロロロ゚゚゚゚イイイイイロロロ
゚ ワワロ゚゚゚゚゚゚゚   
                 ゚ワワワ
゚゚ロロロロロロロロイイイイイロイイイイ
イイロロロ゚ロロロロイイイイロロロ
゚゚ ワワワワワワワワロ   
                 ワロロロ
ロロロロロロロイイイイイロロロ゚ロイイイ
イイイロロロ゚ワワワワロロロロロロロ
ロ゚                   
          ワ ゚゚ワロロロ
ロロロロロロロロロワヷン゚ロワ゚゚゚
゚゚゚ワワロ゚゙  ワ゚゚ワワワロロロ
゚゚                
            ロロワワワ
ワワワワワワワワ゚゚゚゚゚゚ロワ
 ゚゚゚ワロロ゚゚゚゚゚  ロロ
ワワワロ           
               ゚ ロロ
           ゚゚ワワワ゚゚
゚ロイ゙゙ロイ゚゚゚゚ワワワ゚゚ 
ロ゚  ワワワ          
                 ロロ
ワ          ゚ワ゚ロワ
ロロロイイロンン゙ロイロロロロロ
ロ゚ワロ  ロ           
                    
 ロロロ           ワ゚
ワワロロロロロロ゙ロアロ゙ロロロロ
ロロロワ゚ワ  ロ゚      
                     
  ワロロワ           
 ワワロ゚ワワワロロロロロロロロロロ
ワワワ゚ワ  ワ ロ゚   
                     
    ンロワ           
    ワワワワロ゚ワワワロロワワ
ワ゚ロワワワ    ワワロ  
                      
    ンワ            
        ワワワワワワワワワ
ワワワ         ワワワ     
                      
 ワワワ                
                     
                     

201
src/testansi/fruit.ans

@ -0,0 +1,201 @@
ß ß ß 
˛ß° Ţ° 
°    °Ü °
ß ÜÜŰßÄ
˛  ÜÜ  
°cŰ ÜŰ
 ßÄţßÜ  
  ßßŰÜ 
   Ü Ü°
Ü°ß ° Ü
   Ü Ű 
  ß °°Üß
Ü° Űß°°  
°ţÜ  ÜÜ
ß  ˛°   
° ßÜ ÜÜ
  ß  ß  
ß °ÜŰ ß Ü
Äßß ° ÜÜß
Ü°   ßÜ
ß Ű ßŰ 
Ü°Ü ÜŰ   
 ÜßÜÜß°
ŰţŰÜßÜ  
Ü     
°Ü    °
ß°° ŰÜ  
ßÜ ˛ Üß 
ÜŰe nÜŰÜ
  Ű°ßßß Ü
ŰŰÜÜúţÜ
 ł   ß 
ŰßąÜß ß
 ß Üß ß
° ÜÜßÜÜßß
 Ü ÜţÜß
ß  ÜŰ  Ü
 Ü  ß  ą
 ß° Ű 
ß    ß° ß
ŰżŰ Ű°Ű
Ü Ü °ÜÜÜ
.Üe ßÜß
 °°Ü°ß° 
ŰÜÜ° ÜÜ
 °  Ü 
ßß Ű Ü 
   Ű rŰÜ Ä
ÜßÜ°   Ü
ßßßÜŰÜ  ß
  ßÜ ß°
ß ÜŰ ßß
ßÜ  ą  
   Ü° ß 
 Ü۲  ß
  ß Űß Ä
°  łÜ °
Üß ß° Ü
°Ü ß°˛ŰŰ
  Ű  ţ߲
 ßßÜ  Üß
°ß  ß   Ü  
Ü ÜÜÜ 
ßÜ  aßß
 Ü ß Ü iß
ÜÜŰ ß   
˛ Üßß Ţ ą
 Ü Üßß 
°Ü ÜÜ   ßß
° Ű ˛ Ü Üy
ÜÜ° ˛ °
   ßÜß
  Ű ß Ü
ß Ü ÜÜ
    Ű   
° ˛Ü ܲ
˛ą Ű °
ß° Ü° ˛°
°  ßß   e
 °˛ ÜŰ ß
ŰÜß ŰŰß
Ü  ŰÜ ß 
 °ß  ą
° Üß  ß
Ü  ÜŰß 
 ß  ß۲
ßßÜßßÜ°Ű
ß  °  ţ
߲ ß  
ŰŰ  °  
 ܲ  Üß
   ŰÜÜÜ
ßß°ß Üß
ßß°°  ß
ß° Ű Ű
ßß ß°ß°
ß  ßÜÜ Ä
˛°° Ü° 
°ßÜÜ ßh
ß  Ü  ßßß
ß   ÜŰß ß
ŰŰÜ  °  
ßÜ  ˛ß 
ŰÜß Ű ß
°Ü  Ű ÜÜ
 Ű°ÜŰ :ą
łß Ü ß° 
  Ü ß 
߲˛ ßÜŰ
    ܲ° 
  Ü°ą  
°Üܲ۲ Ü
 °  úÜ
ŰÜŰ ßŰÜ
 ßţÜ   
Ü  ˛ Ü  
ß ß° Ű 
 Ü ß°˛° 
Üß°ßßß
   ßŰ Üß
ÜÜßß Ü
 °  Ü Üß
Ű ŰÜ ß˛
 Ű  °SÜ
ŰßßßÜ ß
   ţÜ ß 
° ° Üţ Űß
Ű ÜÜßß 
ßŰ MÜÜą
ܲܰ°  °
° ţß  Ű
°Ű°ßÜ ß
 ßßÜÜŰÜ
Ű ßß  Ű °
ßÜ   ° 
ßţßß °ß
°ß.Ü  Ü 
ŰŰßßŰßÜ
 ˛°Ü  Ü
° ˛Ü˛ ß
ß.ÜÜ  °
ßÜ°  ßÜß
Ű   Üß°
˛Ü˛Ü ß 
ßß °ł Ű
 ß  Ü  
° °ß  ß°
Üß ţß   
 Ű Ü°Ü 
ß ˛ßßÚÜ
ÜÜŰ Ű ˛
 °Ü  ˛ß
°ß°ß   
Ű ß ÜŰÜ
ÜÜ° °  
ß°ßÜ°°°ß 
Ü˛Ű ßÜÜ
 ß  ŰÜb
˛ÜÜ ° Ü
Ü   Ü ßÜ
 ܲ   Ü 
  Ü°° ţ
 Ä  ŰßÜ
 °ÜÜ  Ű߲
°Ü Ü° ۲
  ÜŰÜ°Ü
Ü ßß ß°
ß Ü Ü ÜÜ
  °ßßßÜ 
ú Ű°Ű°
Ű° Ü°ß 
ß   ˛Üß 
 Ü   ßÜ
  Ü °ß
 ÜÜ Ü ß 
°Ü°  ÜŰ
°   ŰÜß
Ü ß Üß ˛
Üßß° ß 
  Ü Ű °Ű
ŰŰ  ţ  
ŰÜ  ß  
c  ßß  ŰÜ
Ű  ܲ°
ß°  Ü°ß 
 Ü ŰÜŰ ß
 ÜÜÜßÜÜÜ
Ű°Ű °l ß
ß  Ü°  ß
Üß  Ü ß
Ü  úÜ ˛
    ßÜ ßÄ
Ű°ßß°ßŰ
Ü ˛ ˛°
ŰßŰ ß  
ŰÜ   Üß
   ßÜ  ߲
Ű ßąłŰŰÜ
 ÜŰ  Ű  
Ü  °° 

20
src/testansi/punk.ansi

@ -0,0 +1,20 @@
╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳
╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳
╳╳╳\\\_╳╳╳╳╳╳_///╳╳╳
╳╳╳\@>╳╳╳╳<@/╳╳╳
╳╳╳╳|~╳╳╳╳╳╳~|╳╳╳╳
\_--_╳╳╳╳_--_/
╳╳╳╳\\╳/╳╳╳╳\╳//╳╳╳╳
╳╳╳╳/╳\╳╳╳╳╳╳/╳\╳╳╳╳
╳╳_+╳╳╳+_╳╳_+╳╳╳+_╳╳
╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳
╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳
╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳
╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳
╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳
╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳
╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳
╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳
╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳
╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳
╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳╳

1022
src/vendormodules/overtype-1.5.8.tm

File diff suppressed because it is too large Load Diff

2194
src/vendormodules/overtype-1.5.9.tm

File diff suppressed because it is too large Load Diff

1
src/vendormodules/textutil/wcswidth-35.1.tm

@ -8,6 +8,7 @@
# Author: Sean Woods <yoda@etoyoc.com> # Author: Sean Woods <yoda@etoyoc.com>
### ###
package provide textutil::wcswidth 35.1 package provide textutil::wcswidth 35.1
namespace eval ::textutil {}
proc ::textutil::wcswidth_type char { proc ::textutil::wcswidth_type char {
if {$char == 161} { return A } if {$char == 161} { return A }
if {$char == 164} { return A } if {$char == 164} { return A }

Loading…
Cancel
Save