Browse Source

pdict/showdict, textblock performance

master
Julian Noble 5 months ago
parent
commit
2edad23c8d
  1. 50
      src/modules/punk-0.1.tm
  2. 2
      src/modules/punk/aliascore-999999.0a1.0.tm
  3. 195
      src/modules/punk/ansi-999999.0a1.0.tm
  4. 38
      src/modules/punk/args-999999.0a1.0.tm
  5. 2
      src/modules/punk/char-999999.0a1.0.tm
  6. 9
      src/modules/punk/config-0.1.tm
  7. 2
      src/modules/punk/console-999999.0a1.0.tm
  8. 90
      src/modules/punk/lib-999999.0a1.0.tm
  9. 2
      src/modules/punk/mix/cli-0.3.tm
  10. 2
      src/modules/punk/mix/commandset/module-999999.0a1.0.tm
  11. 2
      src/modules/punk/mix/commandset/scriptwrap-999999.0a1.0.tm
  12. 7
      src/modules/punk/repl-0.1.tm
  13. 4
      src/modules/shellfilter-0.1.9.tm
  14. 105
      src/modules/textblock-999999.0a1.0.tm

50
src/modules/punk-0.1.tm

@ -69,6 +69,7 @@ set punk_testd [dict create \
] \ ] \
] \ ] \
] \ ] \
e0 "multi\nline"\
] ]
#impolitely cooperative withe punk repl - todo - tone it down. #impolitely cooperative withe punk repl - todo - tone it down.
@ -727,6 +728,7 @@ 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.
#todo - see if 'string is list' improved in tcl9 vs catch {llength $list}
switch -- $index { switch -- $index {
# { # {
set active_key_type "list" set active_key_type "list"
@ -7024,53 +7026,6 @@ namespace eval punk {
return $lines return $lines
} }
proc pdict {d {pattern *}} { ;# analogous to parray (except that it takes the dict as a value)
#maxl.= $d |@keys> .=/2 lmap v {string length $v} |> .=* tcl::mathfunc::max
#set maxl [pipedata $d {dict keys $data} {list {*}$data ""} {lmap v $data {string length $v}} {tcl::mathfunc::max {*}$data} ]
#set maxl [::tcl::mathfunc::max {*}[map {string length} [dict keys $d]]]
set filtered_keys [lsort -dictionary [dict keys $d $pattern]]
if {[llength $filtered_keys]} {
set maxl [::tcl::mathfunc::max {*}[lmap v $filtered_keys {string length $v}]]
foreach key $filtered_keys {
puts stdout [format "%-*s = %s" $maxl $key [dict get $d $key]]
}
}
}
#
proc print_dict {d args} {
set defaults [dict create\
-channel ""\
-pattern *\
-cols 1\
]
set opts [dict merge $defaults $args]
# -- --- --- --- --- --- --- --- --- ---
set pattern [dict get $opts -pattern]
set channel [dict get $opts -channel]
set cols [dict get $opts -cols]
# -- --- --- --- --- --- --- --- --- ---
set out ""
set filtered_keys [lsort -dictionary [dict keys $d $pattern]]
if {[llength $filtered_keys]} {
set i 1
set maxl [::tcl::mathfunc::max {*}[lmap v $filtered_keys {string length $v}]]
foreach key $filtered_keys {
append out [format "%-*s %s " $maxl $key [dict get $d $key]]
if {$i % $cols == 0} {
set out [string range $out 0 end-1]
append out \n
}
incr i
}
}
if {$channel eq ""} {
return $out
} else {
puts $channel $out
}
}
proc ooinspect {obj} { proc ooinspect {obj} {
set obj [uplevel 1 [list namespace which -command $obj]] set obj [uplevel 1 [list namespace which -command $obj]]
@ -7624,7 +7579,6 @@ namespace eval punk {
interp alias {} inspect {} punk::inspect interp alias {} inspect {} punk::inspect
interp alias {} ooinspect {} punk::ooinspect interp alias {} ooinspect {} punk::ooinspect
interp alias {} pdict {} punk::pdict
interp alias {} linedict {} punk::linedict interp alias {} linedict {} punk::linedict
interp alias {} dictline {} punk::dictline interp alias {} dictline {} punk::dictline

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

@ -105,6 +105,8 @@ namespace eval punk::aliascore {
lines_as_list punk::lib::lines_as_list\ lines_as_list punk::lib::lines_as_list\
linelist punk::lib::linelist\ linelist punk::lib::linelist\
linesort punk::lib::linesort\ linesort punk::lib::linesort\
pdict punk::lib::pdict\
showdict punk::lib::showdict\
ansistrip punk::ansi::stripansi\ ansistrip punk::ansi::stripansi\
] ]

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

@ -203,7 +203,7 @@ tcl::namespace::eval punk::ansi::class {
set chunkdisplay_tail [lrange $chunkdisplay_lines end-$renderheight end] set chunkdisplay_tail [lrange $chunkdisplay_lines end-$renderheight end]
set chunkdisplay_block [join $chunkdisplay_tail \n] set chunkdisplay_block [join $chunkdisplay_tail \n]
#the input chunk lines are often much longer than the output.. resulting in main content being way up the screen. It's often impractical to view more than the tail of the chunkdisplay. #the input chunk lines are often much longer than the output.. resulting in main content being way up the screen. It's often impractical to view more than the tail of the chunkdisplay.
textblock::join $rendered $chunkdisplay_block textblock::join -- $rendered $chunkdisplay_block
} }
method checksum {} { method checksum {} {
@ -605,7 +605,7 @@ tcl::namespace::eval punk::ansi {
set result "" set result ""
foreach r $rowlist { foreach r $rowlist {
append result [textblock::join {*}$r] \n append result [textblock::join_basic -- {*}$r] \n
} }
@ -658,30 +658,14 @@ tcl::namespace::eval punk::ansi {
#This doesn't change the output length - so support is tricky to detec. (terminal checksum report?) #This doesn't change the output length - so support is tricky to detec. (terminal checksum report?)
#candidate for zig/c implementation? #candidate for zig/c implementation?
proc stripansi {text} { proc stripansi2 {text} {
#*** !doctools
#[call [fun stripansi] [arg text] ]
#[para]Return a string with ansi codes stripped out
#[para]Alternate graphics chars are replaced with modern unicode equivalents (e.g boxdrawing glyphs)
#using detect costs us a couple of uS - but saves time on plain text
#we should probably leave this for caller - otherwise it ends up being called more than necessary
#if {![::punk::ansi::ta::detect $text]} {
# return $text
#}
set text [convert_g0 $text];#Convert ansi borders to unicode line drawing instead of ascii letters set text [convert_g0 $text];#Convert ansi borders to unicode line drawing instead of ascii letters
join [::punk::ansi::ta::split_at_codes $text] "" join [::punk::ansi::ta::split_at_codes $text] ""
} }
proc stripansiraw {text} {
#*** !doctools
#[call [fun stripansi] [arg text] ]
#[para]Return a string with ansi codes stripped out
#[para]Alternate graphics modes will be stripped - exposing the raw characters as they appear without graphics mode.
#[para]ie instead of a horizontal line you may see: qqqqqq
join [::punk::ansi::ta::split_at_codes $text] ""
}
proc stripansi1 {text} { proc stripansi1 {text} {
#todo - character set selection - SS2 SS3 - how are they terminated? REVIEW #todo - character set selection - SS2 SS3 - how are they terminated? REVIEW
@ -754,7 +738,7 @@ tcl::namespace::eval punk::ansi {
variable map_special_graphics variable map_special_graphics
#using not \033 inside to stop greediness - review how does it compare to ".*?" #using not \033 inside to stop greediness - review how does it compare to ".*?"
#variable re_altg0_group {(?:\x1b\(0)(?:(?!\x1b\(B).)*\x1b\(B} #variable re_g0_group {(?:\x1b\(0)(?:(?!\x1b\(B).)*\x1b\(B}
#set re {\033\(0[^\033]*\033\(B} #set re {\033\(0[^\033]*\033\(B}
#set re {(?:\x1b\(0)(?:(?!\x1b\(B).)*\x1b\(B} #set re {(?:\x1b\(0)(?:(?!\x1b\(B).)*\x1b\(B}
#set re2 {\033\(0(.*)\033\(B} ;#capturing #set re2 {\033\(0(.*)\033\(B} ;#capturing
@ -766,21 +750,22 @@ tcl::namespace::eval punk::ansi {
#mqj #mqj
#m = boxd_lur #m = boxd_lur
#don't call detect_g0 in here. Leave for caller. e.g stripansi uses detect_g0 to decide whether to call this.
set re_g0_open_or_close {\x1b\(0|\x1b\(B} set re_g0_open_or_close {\x1b\(0|\x1b\(B}
set parts [::punk::ansi::ta::_perlish_split $re_g0_open_or_close $text] set parts [::punk::ansi::ta::_perlish_split $re_g0_open_or_close $text]
set out "" set out {}
set g0_on 0 set g0_on 0
foreach {other g} $parts { foreach {other g} $parts {
if {$g0_on} { if {$g0_on} {
#split for non graphics-set codes #split for non graphics-set codes
set othersplits [punk::ansi::ta::split_codes $other] ;#we don't need single codes here set othersplits [punk::ansi::ta::split_codes $other] ;#we don't need single codes here
foreach {inner_plaintext inner_codes} $othersplits { foreach {inner_plaintext inner_codes} $othersplits {
append out [tcl::string::map $map_special_graphics $inner_plaintext] $inner_codes lappend out [tcl::string::map $map_special_graphics $inner_plaintext] $inner_codes
#Simplifying assumption: no mapping required on any inner_codes - ST codes, titlesets etc don't require/use g0 content #Simplifying assumption: no mapping required on any inner_codes - ST codes, titlesets etc don't require/use g0 content
} }
} else { } else {
append out $other ;#may be a mix of plaintext and other ansi codes - put it all through. lappend out $other ;#may be a mix of plaintext and other ansi codes - put it all through.
} }
#trust our splitting regex has done the work to leave us with only \x1b\(0 or \x1b(B - test last char rather than use punk::ansi::codetype::is_gx_open/is_gx_close #trust our splitting regex has done the work to leave us with only \x1b\(0 or \x1b(B - test last char rather than use punk::ansi::codetype::is_gx_open/is_gx_close
switch -- [tcl::string::index $g end] { switch -- [tcl::string::index $g end] {
@ -792,13 +777,13 @@ tcl::namespace::eval punk::ansi {
} }
} }
} }
return $out return [join $out ""]
} }
proc convert_g0_wrong {text} { proc convert_g0_wrong {text} {
#Attempting to split on a group is wrong - because there could be other ansi codes while inside a g0 section #Attempting to split on a group is wrong - because there could be other ansi codes while inside a g0 section
#That will either stop us matching - so no conversion - or risk converting parts of the ansi codes #That will either stop us matching - so no conversion - or risk converting parts of the ansi codes
#using not \033 inside to stop greediness - review how does it compare to ".*?" #using not \033 inside to stop greediness - review how does it compare to ".*?"
#variable re_altg0_group {(?:\x1b\(0)(?:(?!\x1b\(B).)*\x1b\(B} #variable re_g0_group {(?:\x1b\(0)(?:(?!\x1b\(B).)*\x1b\(B}
set re {\033\(0[^\033]*\033\(B} set re {\033\(0[^\033]*\033\(B}
set re2 {\033\(0(.*)\033\(B} ;#capturing set re2 {\033\(0(.*)\033\(B} ;#capturing
@ -2036,7 +2021,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
set map1 [overtype::centre -transparent 1 $map1 "[a {*}$fc black $bgname]Standard colours[a]"] set map1 [overtype::centre -transparent 1 $map1 "[a {*}$fc black $bgname]Standard colours[a]"]
set map2 [colourmap2 -bg $bgname -forcecolour $opt_forcecolour] set map2 [colourmap2 -bg $bgname -forcecolour $opt_forcecolour]
set map2 [overtype::centre -transparent 1 $map2 "[a {*}$fc black $bgname]High-intensity colours[a]"] set map2 [overtype::centre -transparent 1 $map2 "[a {*}$fc black $bgname]High-intensity colours[a]"]
append out [textblock::join $indent [textblock::join $map1 $map2]] \n append out [textblock::join $indent [textblock::join -- $map1 $map2]] \n
append out "[a+ {*}$fc web-white]216 colours of 256 terminal colours (To see names, use: a? term ?pastel? ?rainbow?)[a]" \n append out "[a+ {*}$fc web-white]216 colours of 256 terminal colours (To see names, use: a? term ?pastel? ?rainbow?)[a]" \n
append out [textblock::join $indent [colourblock_216 -forcecolour $opt_forcecolour]] \n append out [textblock::join $indent [colourblock_216 -forcecolour $opt_forcecolour]] \n
append out "[a+ {*}$fc web-white]24 Greyscale colours[a]" \n append out "[a+ {*}$fc web-white]24 Greyscale colours[a]" \n
@ -4188,9 +4173,9 @@ tcl::namespace::eval punk::ansi::ta {
variable re_standalones {(?:\x1bc|\x1b7|\x1b8|\x1bM|\x1bE|\x1bD|\x1bD|\x1bH|\x1b=|\x1b>|\x1b#3|\x1b#4|\x1b#5|\x1b#6|\x1b#8)} variable re_standalones {(?:\x1bc|\x1b7|\x1b8|\x1bM|\x1bE|\x1bD|\x1bD|\x1bH|\x1b=|\x1b>|\x1b#3|\x1b#4|\x1b#5|\x1b#6|\x1b#8)}
#if we don't split on altgraphics too and separate them out - it's easy to get into a horrible mess #if we don't split on altgraphics too and separate them out - it's easy to get into a horrible mess
variable re_altg0_group {(?:\x1b\(0)(?:(?!\x1b\(B).)*\x1b\(B} variable re_g0_group {(?:\x1b\(0)(?:(?!\x1b\(B).)*\x1b\(B}
variable re_altg0_open {(?:\x1b\(0)} variable re_g0_open {(?:\x1b\(0)}
variable re_altg0_close {(?:\x1b\(B)} variable re_g0_close {(?:\x1b\(B)}
# DCS "ESC P" or "0x90" is also terminated by ST # DCS "ESC P" or "0x90" is also terminated by ST
set re_ST_open {(?:\033P|\u0090|\033X|\u0098|\033\^|\u009e|\033_|\u009f)} set re_ST_open {(?:\033P|\u0090|\033X|\u0098|\033\^|\u009e|\033_|\u009f)}
@ -4212,7 +4197,7 @@ tcl::namespace::eval punk::ansi::ta {
#default for regexes is non-newline-sensitive matching - ie matches can span lines #default for regexes is non-newline-sensitive matching - ie matches can span lines
# -- --- --- --- # -- --- --- ---
variable re_ansi_detect1 "${re_csi_code}|${re_esc_osc1}|${re_esc_osc2}|${re_esc_osc3}|${re_standalones}|${re_ST}|${re_altg0_open}|${re_altg0_close}" variable re_ansi_detect1 "${re_csi_code}|${re_esc_osc1}|${re_esc_osc2}|${re_esc_osc3}|${re_standalones}|${re_ST}|${re_g0_open}|${re_g0_close}"
# -- --- --- --- # -- --- --- ---
#handrafted TRIE version of above. Somewhat difficult to construct and maintain. TODO - find a regext TRIE generator that works with Tcl regexes #handrafted TRIE version of above. Somewhat difficult to construct and maintain. TODO - find a regext TRIE generator that works with Tcl regexes
#This does make things quicker - but it's too early to finalise the detect/split regexes (e.g missing \U0090 ) - will need to be redone. #This does make things quicker - but it's too early to finalise the detect/split regexes (e.g missing \U0090 ) - will need to be redone.
@ -4221,10 +4206,10 @@ tcl::namespace::eval punk::ansi::ta {
variable re_ansi_detect_open "${re_csi_open}|${re_osc_open}|${re_standalones}|${re_ST_open}|${re_altg0_open}" variable re_ansi_detect_open "${re_csi_open}|${re_osc_open}|${re_standalones}|${re_ST_open}|${re_g0_open}"
#may be same as detect - kept in case detect needs to diverge #may be same as detect - kept in case detect needs to diverge
#variable re_ansi_split "${re_csi_code}|${re_esc_osc1}|${re_esc_osc2}|${re_esc_osc3}|${re_standalones}|${re_ST}|${re_altg0_open}|${re_altg0_close}" #variable re_ansi_split "${re_csi_code}|${re_esc_osc1}|${re_esc_osc2}|${re_esc_osc3}|${re_standalones}|${re_ST}|${re_g0_open}|${re_g0_close}"
set re_ansi_split $re_ansi_detect set re_ansi_split $re_ansi_detect
#detect any ansi escapes #detect any ansi escapes
@ -4236,9 +4221,12 @@ tcl::namespace::eval punk::ansi::ta {
#[para] #[para]
regexp <re> $text regexp <re> $text
}] }]
proc detect_g0 {text} [string map [list <re> [list $re_g0_group]] {
regexp <re> $text
}]
#note - micro optimisation of inlining <re> gives us *almost* nothing extra. #note - micro optimisation of inlining <re> gives us *almost* nothing extra.
#left in place for detect as it's a common call that should be made as fast as possible as it's used to avoid more expensive operations such as split_... #left in place for a few such as detect/detect_g0 as we want them as fast as possible
# in general the technique doesn't seem worthwhile for this set of functions. # in general the technique doesn't seem particularly worthwhile for this set of functions.
#the performance is dominated by the complexity of the regexp #the performance is dominated by the complexity of the regexp
proc detect2 {text} { proc detect2 {text} {
variable re_ansi_detect variable re_ansi_detect
@ -4285,7 +4273,9 @@ tcl::namespace::eval punk::ansi::ta {
#*** !doctools #*** !doctools
#[call [fun length] [arg text]] #[call [fun length] [arg text]]
#[para]Return the character length after stripping ansi codes - not the printing length #[para]Return the character length after stripping ansi codes - not the printing length
tcl::string::length [stripansi $text]
#we can use stripansiraw to avoid g0 conversion - as the length should remain the same
tcl::string::length [stripansiraw $text]
} }
#todo - handle newlines #todo - handle newlines
#not in perl ta #not in perl ta
@ -4299,10 +4289,92 @@ tcl::namespace::eval punk::ansi::ta {
#not in perl ta #not in perl ta
#returns just the plaintext portions in a list #returns just the plaintext portions in a list
proc split_at_codes {text} { proc split_at_codes {str} [string map [list <re> $re_ansi_split] {
variable re_ansi_split #variable re_ansi_split
punk::ansi::internal::splitx $text ${re_ansi_split} #punk::ansi::internal::splitx $str ${re_ansi_split}
punk::ansi::ta::Do_split_at_codes $str {<re>}
}]
#it is faster to split this function out than to inline it into split_at_codes in tcl 8.7 - something to do with the use of the variable vs argument for the regexp
#literal inlining of the re in the main proc-body was slower too - but inlining it into the wrapper seems to work (a tiny bit)
#the difference is not often apparent when comparing timerate results from split_at_codes vs split_at_codes2 -
# - but in aggregate for something like textblock::periodic - we can get a bit over 5% faster (e.g 136ms vs 149ms)
proc Do_split_at_codes {str regexp} {
if {$str eq ""} {
return {}
}
#no infinite loop check on regexp like splitx does because we should have tested re_ansi_split during development
set list {}
set start 0
while {[regexp -start $start -indices -- $regexp $str match submatch]} {
lassign $submatch subStart subEnd
lassign $match matchStart matchEnd
incr matchStart -1
incr matchEnd
lappend list [tcl::string::range $str $start $matchStart]
if {$subStart >= $start} {
lappend list [tcl::string::range $str $subStart $subEnd]
}
set start $matchEnd
}
lappend list [tcl::string::range $str $start end]
return $list
}
proc Do_split_at_codes_join {str regexp} {
if {$str eq ""} {
return {}
}
#no infinite loop check on regexp like splitx does because we should have tested re_ansi_split during development
set list {}
set start 0
while {[regexp -start $start -indices -- $regexp $str match submatch]} {
lassign $submatch subStart subEnd
lassign $match matchStart matchEnd
incr matchStart -1
incr matchEnd
lappend list [tcl::string::range $str $start $matchStart]
if {$subStart >= $start} {
lappend list [tcl::string::range $str $subStart $subEnd]
}
set start $matchEnd
}
lappend list [tcl::string::range $str $start end]
return [join $list ""]
} }
proc split_at_codes2 {str} [string map [list <re> $re_ansi_split] {
#variable re_ansi_split
#punk::ansi::internal::splitx $str ${re_ansi_split}
#set regexp $re_ansi_split
#set regexp {<re>}
#inline splitx to avoid regex checks
#from textutil::split::splitx
# Bugfix 476988
if {$str eq ""} {
return {}
}
#if {[regexp $regexp {}]} {
# return -code error \
# "splitting on regexp \"$re_ansi_split\" would cause infinite loop"
#}
#no infinite loop check on regexp like splitx does because we should have tested re_ansi_split during development
set list {}
set start 0
while {[regexp -start $start -indices -- {<re>} $str match submatch]} {
lassign $submatch subStart subEnd
lassign $match matchStart matchEnd
incr matchStart -1
incr matchEnd
lappend list [tcl::string::range $str $start $matchStart]
if {$subStart >= $start} {
lappend list [tcl::string::range $str $subStart $subEnd]
}
set start $matchEnd
}
lappend list [tcl::string::range $str $start end]
return $list
}]
# -- --- --- --- --- --- # -- --- --- --- --- ---
#Split $text to a list containing alternating ANSI colour codes and text. #Split $text to a list containing alternating ANSI colour codes and text.
@ -4333,7 +4405,7 @@ tcl::namespace::eval punk::ansi::ta {
#review - tcl greedy expressions may match multiple in one element #review - tcl greedy expressions may match multiple in one element
proc _perlish_split {re text} { proc _perlish_split {re text} {
if {[tcl::string::length $text] == 0} { if {$text eq ""} {
return {} return {}
} }
set list [list] set list [list]
@ -5351,6 +5423,39 @@ tcl::namespace::eval punk::ansi::class {
} }
} }
} }
tcl::namespace::eval punk::ansi {
proc stripansi {text} [string map [list <re> $::punk::ansi::ta::re_ansi_split] {
#*** !doctools
#[call [fun stripansi] [arg text] ]
#[para]Return a string with ansi codes stripped out
#[para]Alternate graphics chars are replaced with modern unicode equivalents (e.g boxdrawing glyphs)
#using detect costs us a couple of uS - but saves time on plain text
#we should probably leave this for caller - otherwise it ends up being called more than necessary
#if {![::punk::ansi::ta::detect $text]} {
# return $text
#}
#alternate graphics codes are not the norm
# - so save a few uS in the common case by only calling convert_g0 if we detect
if {[punk::ansi::ta::detect_g0 $text]} {
set text [convert_g0 $text];#Convert ansi borders to unicode line drawing instead of ascii letters
}
punk::ansi::ta::Do_split_at_codes_join $text {<re>}
}]
proc stripansiraw {text} [string map [list <re> $::punk::ansi::ta::re_ansi_split] {
#*** !doctools
#[call [fun stripansi] [arg text] ]
#[para]Return a string with ansi codes stripped out
#[para]Alternate graphics modes will be stripped rather than converted to unicode - exposing the raw ascii characters as they appear without graphics mode.
#[para]ie instead of a horizontal line you may see: qqqqqq
#join [::punk::ansi::ta::split_at_codes $text] ""
punk::ansi::ta::Do_split_at_codes_join $text {<re>}
}]
}
tcl::namespace::eval punk::ansi::ansistring { tcl::namespace::eval punk::ansi::ansistring {
#*** !doctools #*** !doctools
#[subsection {Namespace punk::ansi::ansistring}] #[subsection {Namespace punk::ansi::ansistring}]
@ -6280,10 +6385,10 @@ tcl::namespace::eval punk::ansi::internal {
proc splitx {str {regexp {[\t \r\n]+}}} { proc splitx {str {regexp {[\t \r\n]+}}} {
#from textutil::split::splitx #from textutil::split::splitx
# Bugfix 476988 # Bugfix 476988
if {[tcl::string::length $str] == 0} { if {$str eq ""} {
return {} return {}
} }
if {[tcl::string::length $regexp] == 0} { if {$regexp eq ""} {
return [::split $str ""] return [::split $str ""]
} }
if {[regexp $regexp {}]} { if {[regexp $regexp {}]} {
@ -6293,8 +6398,10 @@ tcl::namespace::eval punk::ansi::internal {
set list {} set list {}
set start 0 set start 0
while {[regexp -start $start -indices -- $regexp $str match submatch]} { while {[regexp -start $start -indices -- $regexp $str match submatch]} {
foreach {subStart subEnd} $submatch break #foreach {subStart subEnd} $submatch break
foreach {matchStart matchEnd} $match break lassign $submatch subStart subEnd
#foreach {matchStart matchEnd} $match break
lassign $match matchStart matchEnd
incr matchStart -1 incr matchStart -1
incr matchEnd incr matchEnd
lappend list [tcl::string::range $str $start $matchStart] lappend list [tcl::string::range $str $start $matchStart]

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

@ -401,7 +401,7 @@ tcl::namespace::eval punk::args {
set spec_id $starspecs set spec_id $starspecs
} }
proc { proc {
#allow arbitrary #allow arbitrary - review
set proc_info $starspecs set proc_info $starspecs
} }
opts { opts {
@ -416,7 +416,9 @@ tcl::namespace::eval punk::args {
tcl::dict::set optspec_defaults $k $v tcl::dict::set optspec_defaults $k $v
} }
-nominlen - -nomaxlen - -norange - -nochoices - -nochoicelabels { -nominlen - -nomaxlen - -norange - -nochoices - -nochoicelabels {
tcl::dict::unset optspec_defaults $k if {$v} {
tcl::dict::unset optspec_defaults $k
}
} }
-type { -type {
switch -- $v { switch -- $v {
@ -450,7 +452,11 @@ tcl::namespace::eval punk::args {
tcl::dict::set optspec_defaults $k $v tcl::dict::set optspec_defaults $k $v
} }
default { default {
error "punk::args::Get_argspecs - unrecognised key '$k' in *opts line. Known keys: -anyopts" set known { -any -anyopts -minlen -maxlen -range -choices -choicelabels\
-nominlen -nomaxlen -norange -nochoices -nochoicelabels\
-type -optional -allow_ansi -validate_without_ansi -strip_ansi -multiple\
}
error "punk::args::Get_argspecs - unrecognised key '$k' in *opts line. Known keys: $known"
} }
} }
} }
@ -471,7 +477,9 @@ tcl::namespace::eval punk::args {
tcl::dict::set valspec_defaults $k $v tcl::dict::set valspec_defaults $k $v
} }
-nominlen - -nomaxlen - -norange - -nochoices - -nochoicelabels { -nominlen - -nomaxlen - -norange - -nochoices - -nochoicelabels {
tcl::dict::unset valspec_defaults $k if {$v} {
tcl::dict::unset valspec_defaults $k
}
} }
-type { -type {
switch -- $v { switch -- $v {
@ -501,14 +509,19 @@ tcl::namespace::eval punk::args {
tcl::dict::set valspec_defaults $k $v tcl::dict::set valspec_defaults $k $v
} }
default { default {
error "punk::args::Get_argspecs - unrecognised key '$k' in *opts line. Known keys: -anyopts" set known { -min -minvalues -max -maxvalues\
-minlen -maxlen -range -choices -choicelabels\
-nominlen -nomaxlen -norange -nochoices -nochoicelabels\
-type -optional -allow_ansi -validate_without_ansi -strip_ansi -multiple\
}
error "punk::args::Get_argspecs - unrecognised key '$k' in *values line. Known keys: $known"
} }
} }
} }
} }
default { default {
error "punk::args::Get_argspecs - unrecognised * line in. Expected *proc *opts or *values - use **name if paramname needs to be *name" error "punk::args::Get_argspecs - unrecognised * line in '$ln'. Expected *proc *opts or *values - use **name if paramname needs to be *name"
} }
} }
continue continue
@ -563,7 +576,7 @@ tcl::namespace::eval punk::args {
} }
} }
any - ansistring { any - ansistring {
tcl::dict::set spec_merged -type dict tcl::dict::set spec_merged -type any
} }
default { default {
#allow custom unknown types through for now. Todo - require unknown types to begin with custom- REVIEW #allow custom unknown types through for now. Todo - require unknown types to begin with custom- REVIEW
@ -696,7 +709,7 @@ tcl::namespace::eval punk::args {
} else { } else {
set prochelp_display "" set prochelp_display ""
} }
$t add_column -headers $blank_header_col $t add_column -headers $blank_header_col -minwidth 3
$t add_column -headers $blank_header_col $t add_column -headers $blank_header_col
$t add_column -headers $blank_header_col $t add_column -headers $blank_header_col
$t add_column -headers $blank_header_col $t add_column -headers $blank_header_col
@ -781,7 +794,9 @@ tcl::namespace::eval punk::args {
} else { } else {
#todo - something boring #todo - something boring
} }
error $errmsg #add PUNK to the tail end of the more usual -errorcode {TCL WRONGARGS} so we maintain reasonable compat with things looking for TCL WRONGARGS - but also differentiate it.
#Also,we're polite enough in the errorInfo, nothing wrong with a Clint Eastwood style errorCode ;)
return -code error -errorcode {TCL WRONGARGS PUNK} $errmsg
} }
#generally we expect values to contain leading dashes only if -- specified. Otherwise no reliable way determine difference between bad flags and values #generally we expect values to contain leading dashes only if -- specified. Otherwise no reliable way determine difference between bad flags and values
@ -964,7 +979,10 @@ tcl::namespace::eval punk::args {
set maxidx [expr {[llength $rawargs]-1}] set maxidx [expr {[llength $rawargs]-1}]
for {set i 0} {$i <= $maxidx} {incr i} { for {set i 0} {$i <= $maxidx} {incr i} {
set a [lindex $rawargs $i] set a [lindex $rawargs $i]
if {![tcl::string::match -* $a]} { #we can automatically rule out arguments containing whitespace from the set of simple flags beginning with a dash
#This helps for example when first value is a dict or list in which the first item happens to begin with a dash
#explicit -- still safer in many cases, but this is a reasonable and fast enough test
if {![tcl::string::match -* $a] || [regexp {\s+} $a]} {
#assume beginning of positional args #assume beginning of positional args
incr i -1 incr i -1
break break

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

@ -1608,7 +1608,7 @@ tcl::namespace::eval punk::char {
set charset_names [linsert $charset_names 0 "Set Name"] set charset_names [linsert $charset_names 0 "Set Name"]
set settype_list [linsert $settype_list 0 "Set Type"] set settype_list [linsert $settype_list 0 "Set Type"]
return [textblock::join [list_as_lines -- $charset_names] " " [list_as_lines $settype_list]] return [textblock::join -- [list_as_lines -- $charset_names] " " [list_as_lines $settype_list]]
} }
proc charset_defget {exactname} { proc charset_defget {exactname} {
variable charsets variable charsets

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

@ -59,7 +59,8 @@ tcl::namespace::eval punk::config {
#startup color_stdout - parameters as suitable for punk::ansi::a+ (test with 'punk::ansi::a?') e.g "cyan bold" ;#not a good idea to default #startup color_stdout - parameters as suitable for punk::ansi::a+ (test with 'punk::ansi::a?') e.g "cyan bold" ;#not a good idea to default
set default_color_stdout "" set default_color_stdout ""
#This wraps the stderr stream as it comes in with Ansi - probably best to default to empty.. but it's useful. #This wraps the stderr stream as it comes in with Ansi - probably best to default to empty.. but it's useful.
set default_color_stderr "red bold" #set default_color_stderr "red bold"
set default_color_stderr "web-lightsalmon"
set homedir "" set homedir ""
if {[catch { if {[catch {
@ -253,7 +254,7 @@ tcl::namespace::eval punk::config {
proc configure {args} { proc configure {args} {
set argd [punk::args::get_dict { set argd [punk::args::get_dict {
whichconfig -type string -choices {startup running} whichconfig -type string -choices {startup running}
}] }]
} }
@ -265,10 +266,10 @@ tcl::namespace::eval punk::config {
switch -- $whichconfig { switch -- $whichconfig {
config - startup - startup-config - startup-configuration { config - startup - startup-config - startup-configuration {
#show *startup* config - different behaviour may be confusing to those used to router startup and running configs #show *startup* config - different behaviour may be confusing to those used to router startup and running configs
return [punk::print_dict $startup] return [punk::lib::showdict $startup]
} }
running - running-config - running-configuration { running - running-config - running-configuration {
return [punk::print_dict $running] return [punk::lib::showdict $running]
} }
} }

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

@ -780,7 +780,7 @@ namespace eval punk::console {
#stdout #stdout
variable ansi_wanted variable ansi_wanted
if {$ansi_wanted <= 0} { if {$ansi_wanted <= 0} {
puts -nonewline [punk::ansi::stripansi [::punk::ansi::a?]] puts -nonewline [punk::ansi::stripansiraw [::punk::ansi::a?]]
} else { } else {
tailcall ansi::a? {*}$args tailcall ansi::a? {*}$args
} }

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

@ -395,6 +395,96 @@ namespace eval punk::lib {
} }
} }
proc pdict {args} {
set argd [punk::args::get_dict {
*opts -any 1
#default separator to provide similarity to tcl's parray function
-separator -default " = "
-channel -default stdout
*values -min 1 -max -1
dictvar -type string -help "name of dict variable"
patterns -type string -default * -multiple 1
} $args]
set opts [dict get $argd opts]
set dvar [dict get $argd values dictvar]
set patterns [dict get $argd values patterns]
set dvalue [uplevel 1 [list set $dvar]]
showdict {*}$opts $dvalue {*}$patterns
}
proc showdict {args} { ;# analogous to parray (except that it takes the dict as a value)
set argd [punk::args::get_dict {
*id punk::lib::pdict
*proc -name punk::lib::pdict -help "display dictionary keys and values"
-channel -default none
-trimright -default 1 -type boolean -help "trim whitespace off rhs of each line
This can help prevent a single long line that wraps in terminal from making every line wrap due to long rhs padding
"
-separator -default " " -help "Separator column between keys and values"
-keysorttype -default "none" -choices {none dictionary ascii integer real}
-keysortdirection -default ascending -choices {ascending descending}
*values -min 1 -max -1
dictvalue -type dict -help "dict value"
patterns -type string -default * -multiple 1 -help "key or key glob pattern"
} $args]
set opt_sep [dict get $argd opts -separator]
set opt_keysorttype [dict get $argd opts -keysorttype]
set opt_keysortdirection [dict get $argd opts -keysortdirection]
set opt_trimright [dict get $argd opts -trimright]
set dval [dict get $argd values dictvalue]
set patterns [dict get $argd values patterns]
set result ""
set filtered_keys [list]
foreach p $patterns {
lappend filtered_keys {*}[dict keys $dval $p]
}
if {$opt_keysorttype eq "none"} {
#we can only get duplicate keys if there are multiple patterns supplied
#ignore keysortdirection - doesn't apply
if {[llength $patterns] > 1} {
#order-maintaining (order of keys as they appear in dict)
set filtered_keys [punk::lib::lunique $filtered_keys]
}
} else {
set filtered_keys [lsort -unique -$opt_keysorttype $opt_keysortdirection $filtered_keys]
}
if {[llength $filtered_keys]} {
#both keys and values could have newline characters.
#simple use of 'format' won't cut it for more complex dict keys/values
#use block::width or our columns won't align in some cases
set maxl [::tcl::mathfunc::max {*}[lmap v $filtered_keys {textblock::width $v}]]
foreach key $filtered_keys {
#append result [format "%-*s = %s" $maxl $key [dict get $dval $key]] \n
#differing height blocks (ie ragged) so we need a full textblock::join rather than join_basic
append result [textblock::join -- [textblock::pad $key -width $maxl] $opt_sep [dict get $dval $key]] \n
}
}
if {$opt_trimright} {
set result [::join [lines_as_list -line trimright $result] \n]
}
if {[string last \n $result] == [string length $result]-1} {
set result [string range $result 0 end-1]
}
#stdout/stderr can exist but not be in 'chan names' (e.g when transforms in place)
set chan [dict get $argd opts -channel]
switch -- $chan {
stderr - stdout {
puts $chan $result
}
none {
return $result
}
default {
#review - check member of chan names?
#just try outputting to the supplied channel for now
puts $chan $result
}
}
}
proc is_list_all_in_list {small large} { proc is_list_all_in_list {small large} {
package require struct::list package require struct::list
package require struct::set package require struct::set

2
src/modules/punk/mix/cli-0.3.tm

@ -367,7 +367,7 @@ namespace eval punk::mix::cli {
if {"project" in $repotypes} { if {"project" in $repotypes} {
#punk project #punk project
if {![catch {package require textblock; package require patternpunk}]} { if {![catch {package require textblock; package require patternpunk}]} {
set result [textblock::join [>punk . logo] " " $result] set result [textblock::join -- [>punk . logo] " " $result]
append result \n append result \n
} }
} }

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

@ -111,7 +111,7 @@ namespace eval punk::mix::commandset::module {
set tablewidth [expr {$widest(name) + 1 + $widest(pathtype) + 1 + $widest(name)}] set tablewidth [expr {$widest(name) + 1 + $widest(pathtype) + 1 + $widest(name)}]
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)] " " [overtype::left $col(pathtype) $title(pathtype)] " " [overtype::left $col(path) $title(path)]]" \n append table "[textblock::join -- [overtype::left $col(name) $title(name)] " " [overtype::left $col(pathtype) $title(pathtype)] " " [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 {

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

@ -73,7 +73,7 @@ namespace eval punk::mix::commandset::scriptwrap {
set tablewidth [expr {$widest(name) + 1 + $widest(pathtype) + $widest(path)}] set tablewidth [expr {$widest(name) + 1 + $widest(pathtype) + $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)] " " [overtype::left $col(pathtype) $title(pathtype)] " " [overtype::left $col(path) $title(path)]] \n append table [textblock::join -- [overtype::left $col(name) $title(name)] " " [overtype::left $col(pathtype) $title(pathtype)] " " [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 {

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

@ -1115,7 +1115,7 @@ namespace eval punk::repl::class {
set numcol "$ANSI_linenum[join $nums \n][a]" set numcol "$ANSI_linenum[join $nums \n][a]"
set linecol [join $lines \n] set linecol [join $lines \n]
return [textblock::join $numcol " " $linecol] return [textblock::join -- $numcol " " $linecol]
} }
method debugview_lines {} { method debugview_lines {} {
set result "" set result ""
@ -2212,7 +2212,7 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config
set h [textblock::height $text] set h [textblock::height $text]
set promptcol [string repeat $resultprompt\n $h] set promptcol [string repeat $resultprompt\n $h]
set promptcol [string range $promptcol 0 end-1] set promptcol [string range $promptcol 0 end-1]
rputs [textblock::join -- $promptcol $text] rputs [textblock::join_basic -- $promptcol $text]
#puts -nonewline stdout $text #puts -nonewline stdout $text
} }
@ -2265,7 +2265,8 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config
set h [textblock::height $result] set h [textblock::height $result]
set promptcol [string repeat $resultprompt\n $h] set promptcol [string repeat $resultprompt\n $h]
set promptcol [string range $promptcol 0 end-1] set promptcol [string range $promptcol 0 end-1]
rputs [textblock::join -- $promptcol $result] #promptcol is uniform-width lines, result may not be. We are ok to join with ragged rhs col here, so use join_basic instead of join
rputs [textblock::join_basic -- $promptcol $result]
#orig #orig
#rputs $resultprompt[string map [list \r\n "\n$resultprompt" \n "\n$resultprompt"] $result] #rputs $resultprompt[string map [list \r\n "\n$resultprompt" \n "\n$resultprompt"] $result]

4
src/modules/shellfilter-0.1.9.tm

@ -716,7 +716,8 @@ namespace eval shellfilter::chan {
} else { } else {
append emit $trailing_pt append emit $trailing_pt
} }
#set o_buffered "" #the previous o_buffered formed the data we emitted - nothing new to buffer because we emitted all parts including the trailing plaintext
set o_buffered ""
} }
@ -765,6 +766,7 @@ namespace eval shellfilter::chan {
set emit [tcl::encoding::convertto $o_enc $o_buffered] set emit [tcl::encoding::convertto $o_enc $o_buffered]
set o_buffered "" set o_buffered ""
return $emit return $emit
return
} }
method write {transform_handle bytes} { method write {transform_handle bytes} {
set instring [tcl::encoding::convertfrom $o_enc $bytes] set instring [tcl::encoding::convertfrom $o_enc $bytes]

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

@ -1886,7 +1886,7 @@ tcl::namespace::eval textblock {
set cellcontents $hval set cellcontents $hval
} else { } else {
#just write an empty vertical placeholder. The spanned value will be overtyped below #just write an empty vertical placeholder. The spanned value will be overtyped below
set cellcontents [join [lrepeat [llength [split $hval \n]] ""] \n] set cellcontents [::join [lrepeat [llength [split $hval \n]] ""] \n]
} }
set header_cell_startspan [textblock::frame -blockalign $col_blockalign -ellipsis 0 -usecache 1 -width [expr {$hcolwidth+2}] -type [tcl::dict::get $ftypes header]\ set header_cell_startspan [textblock::frame -blockalign $col_blockalign -ellipsis 0 -usecache 1 -width [expr {$hcolwidth+2}] -type [tcl::dict::get $ftypes header]\
-ansibase $ansibase_header -ansiborder $ansiborder_final\ -ansibase $ansibase_header -ansiborder $ansiborder_final\
@ -1975,7 +1975,9 @@ tcl::namespace::eval textblock {
incr i incr i
} }
set spanned_frame [textblock::join {*}$spanned_parts] #JMN
#spanned_parts are all built with textblock::frame - therefore uniform-width lines - can use join_basic
set spanned_frame [textblock::join_basic -- {*}$spanned_parts]
if {$hrow == 0} { if {$hrow == 0} {
set hlims $header_boxlimits_toprow set hlims $header_boxlimits_toprow
@ -2006,7 +2008,7 @@ tcl::namespace::eval textblock {
} else { } else {
#this_span == 1 #this_span == 1
set spanned_frame [textblock::join $header_cell_startspan] set spanned_frame [textblock::join_basic -- $header_cell_startspan]
} }
@ -2084,7 +2086,7 @@ tcl::namespace::eval textblock {
lappend adjusted_lines $ln lappend adjusted_lines $ln
} }
} }
set part_header [join $adjusted_lines \n] set part_header [::join $adjusted_lines \n]
#append output $part_header \n #append output $part_header \n
} }
@ -2213,7 +2215,7 @@ tcl::namespace::eval textblock {
} }
} }
set rowframe [textblock::frame -type [tcl::dict::get $ftypes body] -width [expr {$colwidth+2}] -blockalign $col_blockalign -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c] set rowframe [textblock::frame -type [tcl::dict::get $ftypes body] -width [expr {$colwidth+2}] -blockalign $col_blockalign -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c]
set return_bodywidth [textblock::width $rowframe] set return_bodywidth [textblock::widthtopline $rowframe] ;#frame lines always same width - just look at top line
append part_body $rowframe \n append part_body $rowframe \n
} else { } else {
if {$r == $rmax} { if {$r == $rmax} {
@ -2553,7 +2555,7 @@ tcl::namespace::eval textblock {
lappend cols [my get_column_by_index $c -position inner] " " lappend cols [my get_column_by_index $c -position inner] " "
} }
} }
append result [textblock::join {*}$cols] append result [textblock::join -- {*}$cols]
return $result return $result
} }
#column width including headers - but without colspan consideration #column width including headers - but without colspan consideration
@ -2895,7 +2897,7 @@ tcl::namespace::eval textblock {
incr colposn incr colposn
} }
if {[llength $blocks]} { if {[llength $blocks]} {
return [textblock::join {*}$blocks] return [textblock::join -- {*}$blocks]
} else { } else {
return "No columns matched" return "No columns matched"
} }
@ -3288,7 +3290,7 @@ tcl::namespace::eval textblock {
set table $nextcol set table $nextcol
set height [textblock::height $table] ;#only need to get height once at start set height [textblock::height $table] ;#only need to get height once at start
} else { } else {
set nextcol [textblock::join [textblock::block $padwidth $height $TSUB] $nextcol] set nextcol [textblock::join -- [textblock::block $padwidth $height $TSUB] $nextcol]
set table [overtype::renderspace -overflow 1 -experimental test_mode -transparent $TSUB $table[unset table] $nextcol] set table [overtype::renderspace -overflow 1 -experimental test_mode -transparent $TSUB $table[unset table] $nextcol]
#JMN #JMN
@ -3522,7 +3524,8 @@ tcl::namespace::eval textblock {
if {![llength $body_blocks]} { if {![llength $body_blocks]} {
set body_build "" set body_build ""
} else { } else {
set body_build [textblock::join {*}$body_blocks] #body blocks should not be ragged - so can use join_basic
set body_build [textblock::join_basic -- {*}$body_blocks]
} }
if {$headerheight > 0} { if {$headerheight > 0} {
set table [tcl::string::cat $header_build \n $body_build] set table [tcl::string::cat $header_build \n $body_build]
@ -3906,7 +3909,7 @@ tcl::namespace::eval textblock {
if {[tcl::string::last \n $charblock] >= 0} { if {[tcl::string::last \n $charblock] >= 0} {
if {$blockwidth > 1} { if {$blockwidth > 1} {
#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 [.= 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]] set row [textblock::join_basic -- {*}[lrepeat $blockwidth $charblock]]
} else { } else {
set row $charblock set row $charblock
} }
@ -3973,9 +3976,9 @@ tcl::namespace::eval textblock {
lappend clist ${ansicode}$c$RST lappend clist ${ansicode}$c$RST
} }
if {"noreset" in $colour} { if {"noreset" in $colour} {
return [textblock::join -ansiresets 0 {*}$clist] return [textblock::join_basic -ansiresets 0 -- {*}$clist]
} else { } else {
return [textblock::join {*}$clist] return [textblock::join_basic -- {*}$clist]
} }
} elseif {"rainbow" in $colour} { } elseif {"rainbow" in $colour} {
#direction must be horizontal #direction must be horizontal
@ -4024,7 +4027,8 @@ tcl::namespace::eval textblock {
set textblock [textutil::tabify::untabify2 $textblock $tw] set textblock [textutil::tabify::untabify2 $textblock $tw]
} }
if {[punk::ansi::ta::detect $textblock]} { if {[punk::ansi::ta::detect $textblock]} {
set textblock [punk::ansi::stripansi $textblock] #stripansiraw slightly faster than stripansi - and won't affect width (avoid detect_g0/conversions)
set textblock [punk::ansi::stripansiraw $textblock]
} }
if {[tcl::string::last \n $textblock] >= 0} { if {[tcl::string::last \n $textblock] >= 0} {
return [tcl::mathfunc::max {*}[lmap v [split $textblock \n] {::punk::char::ansifreestring_width $v}]] return [tcl::mathfunc::max {*}[lmap v [split $textblock \n] {::punk::char::ansifreestring_width $v}]]
@ -4040,7 +4044,7 @@ tcl::namespace::eval textblock {
set tl $textblock set tl $textblock
} }
if {[punk::ansi::ta::detect $tl]} { if {[punk::ansi::ta::detect $tl]} {
set tl [punk::ansi::stripansi $tl] set tl [punk::ansi::stripansiraw $tl]
} }
return [punk::char::ansifreestring_width $tl] return [punk::char::ansifreestring_width $tl]
} }
@ -4075,9 +4079,9 @@ tcl::namespace::eval textblock {
} }
set textblock [textutil::tabify::untabify2 $textblock $tw] 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 #stripansiraw 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]} { if {[punk::ansi::ta::detect $textblock]} {
set textblock [punk::ansi::stripansi $textblock] set textblock [punk::ansi::stripansiraw $textblock]
} }
if {[tcl::string::last \n $textblock] >= 0} { if {[tcl::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 [punk::lib::lines_as_list -- $textblock] {::punk::char::ansifreestring_width $v}]]
@ -4578,6 +4582,48 @@ tcl::namespace::eval textblock {
return [punk::lib::list_as_lines -- $outlines] return [punk::lib::list_as_lines -- $outlines]
} }
#join without regard to each line length in a block (no padding added to make each block uniform)
proc ::textblock::join_basic {args} {
#*proc -name textblock::join_basic -help "Join blocks line by line but don't add padding on each line to enforce uniform width.
# Already uniform blocks will join faster than textblock::join, and ragged blocks will join in a ragged manner
#"
set argd [punk::args::get_dict {
-ansiresets -type any -default auto
blocks -type any -multiple 1
} $args]
set ansiresets [tcl::dict::get $argd opts -ansiresets]
set blocks [tcl::dict::get $argd values blocks]
#-ansireplays is always on (if ansi detected)
# -- is a legimate block
#this makes for a somewhat messy api.. -- is required if first block is actually -- (or the very unlikely case the first block is intended to be -ansiresets)
if {![llength $blocks]} {
return
}
set idx 0
set fordata [list]
set colindices [list]
foreach b $blocks {
if {[punk::ansi::ta::detect $b]} {
lappend fordata "v($idx)" [punk::lib::lines_as_list -ansireplays 1 -ansiresets $ansiresets -- $b]
} else {
lappend fordata "v($idx)" [split $b \n]
}
lappend colindices $idx
incr idx
}
set outlines [list]
foreach {*}$fordata {
set row {}
foreach colidx $colindices {
lappend row $v($colidx)
}
lappend outlines [::join $row ""]
}
return [::join $outlines \n]
}
#for joining 'rendered' blocks of plain or ansi text. Being 'rendered' means they are without ansi movement sequences as these have been processed #for joining 'rendered' blocks of plain or ansi text. Being 'rendered' means they are without ansi movement sequences as these have been processed
#they may however still be 'ragged' ie differing line lengths #they may however still be 'ragged' ie differing line lengths
proc ::textblock::join {args} { proc ::textblock::join {args} {
@ -4619,7 +4665,7 @@ tcl::namespace::eval textblock {
set fordata [list] set fordata [list]
set colindices [list] set colindices [list]
foreach b $blocks { foreach b $blocks {
set w($idx) [width $b] ;#we need the width of a rendered block for per-row renderline calls set w($idx) [width $b] ;#we need the width of a rendered block for per-row renderline calls or padding
#set c($idx) [tcl::string::repeat " " [set w($idx)]] #set c($idx) [tcl::string::repeat " " [set w($idx)]]
#fairly commonly a block may be a vertical list of chars e.g something like 1\n2\n3 or -\n-\n-\n- #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. #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.
@ -4688,22 +4734,22 @@ tcl::namespace::eval textblock {
set blue [a+ {*}$fc blue];set blueb [a+ {*}$fc blue bold] set blue [a+ {*}$fc blue];set blueb [a+ {*}$fc blue bold]
set RST [a] set RST [a]
set gr0 [punk::ansi::g0 abcdefghijklm\nnopqrstuvwxyz] set gr0 [punk::ansi::g0 abcdefghijklm\nnopqrstuvwxyz]
set punks [textblock::join $pleft $pright] set punks [textblock::join -- $pleft $pright]
set pleft_greenb $greenb$pleft$RST set pleft_greenb $greenb$pleft$RST
set pright_redb $redb$pright$RST set pright_redb $redb$pright$RST
set prightair_cyanb $cyanb$prightair$RST set prightair_cyanb $cyanb$prightair$RST
set cpunks [textblock::join $pleft_greenb $pright_redb] set cpunks [textblock::join -- $pleft_greenb $pright_redb]
set out "" set out ""
append out $punks \n append out $punks \n
append out $cpunks \n append out $cpunks \n
append out [textblock::join $punks $cpunks] \n append out [textblock::join -- $punks $cpunks] \n
set 2frames_a [textblock::join [textblock::frame $cpunks] [textblock::frame $punks]] set 2frames_a [textblock::join -- [textblock::frame $cpunks] [textblock::frame $punks]]
append out $2frames_a \n append out $2frames_a \n
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
set fancy [overtype::right [overtype::left [textblock::frame -ansiborder [a+ green bold] -type 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"] set fancy [overtype::right [overtype::left [textblock::frame -ansiborder [a+ green bold] -type 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"]
set spantable [[spantest] print] set spantable [[spantest] print]
append out [textblock::join $fancy " " $spantable] \n append out [textblock::join -- $fancy " " $spantable] \n
#append out [textblock::frame -title gr $gr0] #append out [textblock::frame -title gr $gr0]
append out [textblock::periodic -forcecolour $opt_forcecolour] append out [textblock::periodic -forcecolour $opt_forcecolour]
return $out return $out
@ -5985,9 +6031,9 @@ tcl::namespace::eval textblock {
tcl::dict::for {k v} $frame_cache { tcl::dict::for {k v} $frame_cache {
lassign $v _f frame _used used lassign $v _f frame _used used
#set fwidth [textblock::widthtopline $frame] set fwidth [textblock::widthtopline $frame]
#review - are cached frames uniform width lines? #review - are cached frames uniform width lines?
set fwidth [textblock::width $frame] #set fwidth [textblock::width $frame]
set frameinfo "$k used:$used " set frameinfo "$k used:$used "
set allinone_width [expr {[tcl::string::length $frameinfo] + $fwidth}] set allinone_width [expr {[tcl::string::length $frameinfo] + $fwidth}]
if {$allinone_width >= $termwidth} { if {$allinone_width >= $termwidth} {
@ -5995,7 +6041,7 @@ tcl::namespace::eval textblock {
append out "$frameinfo\n" append out "$frameinfo\n"
append out $frame \n append out $frame \n
} else { } else {
append out [textblock::join $frameinfo $frame]\n append out [textblock::join -- $frameinfo $frame]\n
} }
append out \n ;# frames used to build tables often have joins - keep a line in between for clarity append out \n ;# frames used to build tables often have joins - keep a line in between for clarity
} }
@ -6684,7 +6730,8 @@ tcl::namespace::eval textblock {
if {$paddedwidth > $cache_patternwidth} { if {$paddedwidth > $cache_patternwidth} {
set paddedcontents [overtype::renderspace -width $cache_patternwidth "" $paddedcontents] set paddedcontents [overtype::renderspace -width $cache_patternwidth "" $paddedcontents]
} }
set contentblock [textblock::join $paddedcontents] ;#make sure each line has ansi replays #important to supply end of opts -- to textblock::join - particularly here with arbitrary data
set contentblock [textblock::join -- $paddedcontents] ;#make sure each line has ansi replays
set tlines [split $template \n] set tlines [split $template \n]
@ -6794,7 +6841,7 @@ tcl::namespace::eval textblock {
if {$crosscount > 1} { if {$crosscount > 1} {
package require textblock package require textblock
set row [textblock::join {*}[lrepeat $crosscount $onecross]] set row [textblock::join -- {*}[lrepeat $crosscount $onecross]]
set rows [lrepeat $crosscount $row] set rows [lrepeat $crosscount $row]
set out [::join $rows \n] set out [::join $rows \n]
} else { } else {

Loading…
Cancel
Save