Browse Source

punk::lib::tstr template literals, punk pipeline and pdict fixes, misc fixes

master
Julian Noble 6 months ago
parent
commit
25ed96003c
  1. 4
      src/modules/#tarjar-tarjar-2.3/#tarjar-loadscript-tarjar.tcl
  2. 1962
      src/modules/punk-0.1.tm
  3. 107
      src/modules/punk/aliascore-999999.0a1.0.tm
  4. 43
      src/modules/punk/ansi-999999.0a1.0.tm
  5. 82
      src/modules/punk/args-999999.0a1.0.tm
  6. 3
      src/modules/punk/basictelnet-999999.0a1.0.tm
  7. 1
      src/modules/punk/console-999999.0a1.0.tm
  8. 7
      src/modules/punk/fileline-999999.0a1.0.tm
  9. 578
      src/modules/punk/lib-999999.0a1.0.tm
  10. 37
      src/modules/punk/ns-999999.0a1.0.tm
  11. 11
      src/modules/punk/repl-0.1.tm
  12. 518
      src/modules/textblock-999999.0a1.0.tm

4
src/modules/#tarjar-tarjar-2.3/#tarjar-loadscript-tarjar.tcl

@ -2210,11 +2210,11 @@ if {[file exists $tarjar]} {
proc tarjar::_::make_sfx_zip { zipfile outfile sfx_stub } {
set in [open $zipfile r]
fconfigure $in -translation binary -encoding binary
fconfigure $in -translation binary -encoding iso8859-1
#set in_data [read $in [file size $zipfile]]
set out [open $outfile w+]
fconfigure $out -translation binary -encoding binary
fconfigure $out -translation binary -encoding iso8859-1
puts -nonewline $out $sfx_stub

1962
src/modules/punk-0.1.tm

File diff suppressed because it is too large Load Diff

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

@ -64,50 +64,55 @@ package require Tcl 8.6-
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# oo::class namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval punk::aliascore::class {
#*** !doctools
#[subsection {Namespace punk::aliascore::class}]
#[para] class definitions
if {[info commands [namespace current]::interface_sample1] eq ""} {
#*** !doctools
#[list_begin enumerated]
# oo::class create interface_sample1 {
# #*** !doctools
# #[enum] CLASS [class interface_sample1]
# #[list_begin definitions]
# method test {arg1} {
# #*** !doctools
# #[call class::interface_sample1 [method test] [arg arg1]]
# #[para] test method
# puts "test: $arg1"
# }
# #*** !doctools
# #[list_end] [comment {-- end definitions interface_sample1}]
# }
#*** !doctools
#[list_end] [comment {--- end class enumeration ---}]
}
}
#tcl::namespace::eval punk::aliascore::class {
# #*** !doctools
# #[subsection {Namespace punk::aliascore::class}]
# #[para] class definitions
# if {[info commands [namespace current]::interface_sample1] eq ""} {
# #*** !doctools
# #[list_begin enumerated]
#
# # oo::class create interface_sample1 {
# # #*** !doctools
# # #[enum] CLASS [class interface_sample1]
# # #[list_begin definitions]
#
# # method test {arg1} {
# # #*** !doctools
# # #[call class::interface_sample1 [method test] [arg arg1]]
# # #[para] test method
# # puts "test: $arg1"
# # }
#
# # #*** !doctools
# # #[list_end] [comment {-- end definitions interface_sample1}]
# # }
#
# #*** !doctools
# #[list_end] [comment {--- end class enumeration ---}]
# }
#}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Base namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval punk::aliascore {
namespace export {[a-z]*} ;# Convention: export all lowercase
tcl::namespace::eval punk::aliascore {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
variable aliases
set aliases [dict create\
list_as_lines punk::lib::list_as_lines\
lines_as_list punk::lib::lines_as_list\
linelist punk::lib::linelist\
linesort punk::lib::linesort\
pdict punk::lib::pdict\
showdict punk::lib::showdict\
ansistrip punk::ansi::stripansi\
#use absolute ns ie must be prefixed with ::
#single element commands are imported if source command already exists, otherwise aliased. multi element commands are aliased
set aliases [tcl::dict::create\
tstr ::punk::lib::tstr\
list_as_lines ::punk::lib::list_as_lines\
lines_as_list ::punk::lib::lines_as_list\
linelist ::punk::lib::linelist\
linesort ::punk::lib::linesort\
pdict ::punk::lib::pdict\
plist [list ::punk::lib::pdict -roottype list]\
showlist [list ::punk::lib::showdict -roottype list]\
showdict ::punk::lib::showdict\
ansistrip ::punk::ansi::stripansi\
]
#*** !doctools
@ -140,21 +145,37 @@ namespace eval punk::aliascore {
set existing [list]
set conflicts [list]
foreach {a cmd} $aliases {
if {[info commands ::$a] ne ""} {
if {[tcl::info::commands ::$a] ne ""} {
lappend existing $a
set existing_target [interp alias "" $a]
if {[llength $cmd] > 1} {
#use alias mechanism
set existing_target [interp alias "" $a]
} else {
#using namespace import
#check origin
set existing_target [tcl::namespace::origin $cmd]
}
if {$existing_target ne $cmd} {
#command exists in global ns but is either an alias to something else, or some other type of command
#command exists in global ns but doesn't match our defined aliases/imports
lappend conflicts $a
}
}
}
if {[llength $conflicts]} {
error "punk::aliascore::init declined to create any aliases because -force == 0 and conflicts found:$conflicts"
error "punk::aliascore::init declined to create any aliases or imports because -force == 0 and conflicts found:$conflicts"
}
}
dict for {a cmd} $aliases {
interp alias {} $a {} {*}$cmd
if {[llength $cmd] > 1} {
interp alias {} $a {} {*}$cmd
} else {
if {[tcl::info::commands $cmd] ne ""} {
#todo - ensure exported? noclobber?
tcl::namespace::eval :: [list namespace import $cmd]
} else {
interp alias {} $a {} {*}$cmd
}
}
}
return [dict keys $aliases]
}

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

@ -1872,7 +1872,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
}
$t configure -frametype {}
$t configure_column 0 -headers [list "[tcl::string::totitle $g] colours"]
$t configure_column 0 -header_colspans [list all]
$t configure_column 0 -header_colspans [list any]
$t configure -ansibase_header [a+ {*}$fc web-black Web-white]
lappend grouptables [$t print]
$t destroy
@ -1919,7 +1919,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
}
$t configure -frametype block
$t configure_column 0 -headers [list "X11"]
$t configure_column 0 -header_colspans [list all]
$t configure_column 0 -header_colspans [list any]
$t configure -ansibase_header [a+ {*}$fc web-black Web-white]
lappend comparetables [$t print]
$t destroy
@ -1940,7 +1940,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
}
$t configure -frametype block
$t configure_column 0 -headers [list "Web"]
$t configure_column 0 -header_colspans [list all]
$t configure_column 0 -header_colspans [list any]
$t configure -ansibase_header [a+ {*}$fc web-black Web-white]
lappend comparetables [$t print]
$t destroy
@ -2013,39 +2013,39 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
package require overtype ;# circular dependency - many components require overtype. Here we only need it for nice layout in the a? query proc - so we'll do a soft-dependency by only loading when needed and also wrapping in a try
package require textblock
append out [textblock::join $indent [tcl::string::map $strmap $settings_applied]] \n
append out [textblock::join $indent [tcl::string::trim $SGR_colour_map \n]] \n
append out [textblock::join $indent "Example: \[a+ bold red White underline\]text\[a] -> [a+ bold red White underline]text[a]"] \n \n
append out [textblock::join -- $indent [tcl::string::map $strmap $settings_applied]] \n
append out [textblock::join -- $indent [tcl::string::trim $SGR_colour_map \n]] \n
append out [textblock::join -- $indent "Example: \[a+ bold red White underline\]text\[a] -> [a+ bold red White underline]text[a]"] \n \n
set bgname "Web-white"
set map1 [colourmap1 -bg $bgname -forcecolour $opt_forcecolour]
set map1 [overtype::centre -transparent 1 $map1 "[a {*}$fc black $bgname]Standard colours[a]"]
set map2 [colourmap2 -bg $bgname -forcecolour $opt_forcecolour]
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 [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 [textblock::join $indent [colourblock_24 -forcecolour $opt_forcecolour]] \n
append out [textblock::join -- $indent [colourblock_24 -forcecolour $opt_forcecolour]] \n
append out \n
append out [textblock::join $indent "Example: \[a+ Term-92 term-49\]text\[a] -> [a+ {*}$fc Term-92 term-49]text[a]"] \n
append out [textblock::join $indent "Example: \[a+ Term-lightsteelblue term-gold1\]text\[a] -> [a+ {*}$fc Term-lightsteelblue term-gold1]text[a]"] \n
append out [textblock::join $indent "Example: \[a+ term-lightsteelblue Term-gold1\]text\[a] -> [a+ {*}$fc term-lightsteelblue Term-gold1]text[a]"] \n
append out [textblock::join -- $indent "Example: \[a+ Term-92 term-49\]text\[a] -> [a+ {*}$fc Term-92 term-49]text[a]"] \n
append out [textblock::join -- $indent "Example: \[a+ Term-lightsteelblue term-gold1\]text\[a] -> [a+ {*}$fc Term-lightsteelblue term-gold1]text[a]"] \n
append out [textblock::join -- $indent "Example: \[a+ term-lightsteelblue Term-gold1\]text\[a] -> [a+ {*}$fc term-lightsteelblue Term-gold1]text[a]"] \n
append out \n
append out "[a+ {*}$fc web-white]16 Million colours[a]" \n
#tcl::dict::set WEB_colour_map mediumvioletred 199-21-133 ;# #C71585
append out [textblock::join $indent "Example: \[a+ rgb-199-21-133\]text\[a] -> [a+ {*}$fc rgb-199-21-133]text[a]"] \n
append out [textblock::join $indent "Example: \[a+ Rgb#C71585\]text\[a] -> [a+ {*}$fc Rgb#C71585]text[a]"] \n
append out [textblock::join $indent "Examine a sequence: a? bold rgb-46-139-87 Rgb#C71585 "] \n
append out [textblock::join -- $indent "Example: \[a+ rgb-199-21-133\]text\[a] -> [a+ {*}$fc rgb-199-21-133]text[a]"] \n
append out [textblock::join -- $indent "Example: \[a+ Rgb#C71585\]text\[a] -> [a+ {*}$fc Rgb#C71585]text[a]"] \n
append out [textblock::join -- $indent "Examine a sequence: a? bold rgb-46-139-87 Rgb#C71585 "] \n
append out \n
append out "[a+ {*}$fc web-white]Web colours[a]" \n
append out [textblock::join $indent "To see all names use: a? web"] \n
append out [textblock::join $indent "To see specific colour groups use: a? web groupname1 groupname2..."] \n
append out [textblock::join $indent "Valid group names (can be listed in any order): basic pink red orange yellow brown purple blue cyan green white grey"] \n
append out [textblock::join -- $indent "To see all names use: a? web"] \n
append out [textblock::join -- $indent "To see specific colour groups use: a? web groupname1 groupname2..."] \n
append out [textblock::join -- $indent "Valid group names (can be listed in any order): basic pink red orange yellow brown purple blue cyan green white grey"] \n
append out \n
append out [textblock::join $indent "Example: \[a+ Web-springgreen web-crimson\]text\[a] -> [a+ {*}$fc Web-springgreen web-coral]text[a]"] \n
append out [textblock::join -- $indent "Example: \[a+ Web-springgreen web-crimson\]text\[a] -> [a+ {*}$fc Web-springgreen web-coral]text[a]"] \n
append out \n
append out "[a+ {*}$fc web-white]X11 colours[a] - mostly match Web colours" \n
append out [textblock::join $indent "To see differences: a? x11"] \n
append out [textblock::join -- $indent "To see differences: a? x11"] \n
if {[tcl::info::exists ::punk::console::colour_disabled] && $::punk::console::colour_disabled} {
append out \n
@ -4226,6 +4226,9 @@ tcl::namespace::eval punk::ansi::ta {
#can be used on dicts - but will check keys too. keys could also contain ansi and have escapes
proc detect_in_list {list} {
detect [join $list " "]
}
proc detect_in_list2 {list} {
foreach item $list {
if {[detect $item]} {
return 1

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

@ -332,7 +332,8 @@ tcl::namespace::eval punk::args {
set in_record 0
foreach rawline $linelist {
set recordsofar [tcl::string::cat $linebuild $rawline]
if {![tcl::info::complete $recordsofar]} {
#ansi colours can stop info complete from working (contain square brackets)
if {![tcl::info::complete [punk::ansi::stripansi $recordsofar]]} {
#append linebuild [string trimleft $rawline] \n
if {$in_record} {
if {[tcl::string::length $lastindent]} {
@ -436,6 +437,9 @@ tcl::namespace::eval punk::args {
}
none - any - ansistring {
}
list {
}
default {
#todo - disallow unknown types unless prefixed with custom-
@ -494,6 +498,9 @@ tcl::namespace::eval punk::args {
}
dict - dictionary {
set v dict
}
list {
}
default {
#todo - disallow unknown types unless prefixed with custom-
@ -568,7 +575,9 @@ tcl::namespace::eval punk::args {
"" - none {
if {$is_opt} {
tcl::dict::set spec_merged -type none
tcl::dict::set spec_merged -default 0 ;#-default 0 can still be overridden if -default appears after -type - we'll allow it.
if {[tcl::dict::exists $specval -optional] && [tcl::dict::get $specval -optional]} {
tcl::dict::set spec_merged -default 0 ;#-default 0 can still be overridden if -default appears after -type - we'll allow it.
}
lappend opt_solos $argname
} else {
#-solo only valid for flags
@ -687,6 +696,7 @@ tcl::namespace::eval punk::args {
}
proc arg_error {msg spec_dict {badarg ""}} {
#todo - add checks column (e.g -minlen -maxlen)
set errmsg $msg
if {![catch {package require textblock}]} {
if {[catch {
@ -798,7 +808,12 @@ tcl::namespace::eval punk::args {
#Also,we're polite enough in the errorInfo, nothing wrong with a Clint Eastwood style errorCode ;)
return -code error -errorcode {TCL WRONGARGS PUNK} $errmsg
}
#todo - a version of get_dict that supports punk::lib::tstr templating
#rename get_dict
#provide ability to look up and reuse definitions from ids etc
#
#generally we expect values to contain leading dashes only if -- specified. Otherwise no reliable way determine difference between bad flags and values
#If no eopts (--) specified we stop looking for opts at the first nondash encountered in a position we'd expect a dash - so without eopt, values could contain dashes - but not in first position after flags.
#only supports -flag val pairs, not solo options
@ -849,7 +864,7 @@ tcl::namespace::eval punk::args {
#this would be important in the case where the function to be wrapped has never been called - but the wrapper needs info about the downstream options
#we would like to avoid the ugliness of trying to parse a proc body to scrape the specification.
#we may still need to do a basic scan of the proc body to determine if it at least contains the string punk::args::get_dict - but that is slightly less odious.
error "unsupported"
error "unsupported number of arguments for punk::args::get_dict"
set inopt 0
set k ""
set i 0
@ -887,8 +902,12 @@ tcl::namespace::eval punk::args {
#for -multple true, we need to ensure we can differentiate between a default value and a first of many that happens to match the default.
#-default value must not be appended to if argname not yet in flagsreceived
#todo: -minmultiple -maxmultiple ?
set opts $opt_defaults
if {[set eopts [lsearch -exact $rawargs "--"]] >= 0} {
lappend flagsreceived --
set values [lrange $rawargs $eopts+1 end]
set arglist [lrange $rawargs 0 $eopts-1]
set maxidx [expr {[llength $arglist]-1}]
@ -908,7 +927,7 @@ tcl::namespace::eval punk::args {
#review - what if user sets first value that happens to match a default?
if {$fullopt ni $flagsreceived && [tcl::dict::exists $opt_defaults $fullopt] && ([tcl::dict::get $opt_defaults $fullopt] eq [tcl::dict::get $opts $fullopt])} {
#first occurrence of this flag, whilst stored value matches default
tcl::dict::set opts $fullopt $flagval
tcl::dict::set opts $fullopt [list $flagval]
} else {
tcl::dict::lappend opts $fullopt $flagval
}
@ -997,7 +1016,7 @@ tcl::namespace::eval punk::args {
#review - what if user sets first value that happens to match a default?
if {$fullopt ni $flagsreceived && [tcl::dict::exists $opt_defaults $fullopt] && ([tcl::dict::get $opt_defaults $fullopt] eq [tcl::dict::get $opts $fullopt])} {
#first occurrence of this flag, whilst stored value matches default
tcl::dict::set opts $fullopt $flagval
tcl::dict::set opts $fullopt [list $flagval]
} else {
tcl::dict::lappend opts $fullopt $flagval
}
@ -1079,7 +1098,7 @@ tcl::namespace::eval punk::args {
if {[tcl::dict::get $arg_info $valname -multiple]} {
if {[tcl::dict::exists $val_defaults $valname] && ([tcl::dict::get $val_defaults $valname] eq [tcl::dict::get $values_dict $valname])} {
#current stored val equals defined default - don't include default in the list we build up
tcl::dict::set values_dict $valname $val
tcl::dict::set values_dict $valname [list $val] ;#important to treat first element as a list
} else {
tcl::dict::lappend values_dict $valname $val
}
@ -1146,6 +1165,7 @@ tcl::namespace::eval punk::args {
}
#todo - truncate/summarize values in error messages
#todo - allow defaults outside of choices/ranges
@ -1205,6 +1225,9 @@ tcl::namespace::eval punk::args {
}
if {$is_default eq [llength $vlist]} {
set is_default 1
} else {
#important to set 0 here too e.g if only one element of many matches default
set is_default 0
}
}
#puts "argname:$argname v:$v is_default:$is_default"
@ -1214,6 +1237,32 @@ tcl::namespace::eval punk::args {
if {$is_default == 0} {
switch -- $type {
any {}
list {
foreach e_check $vlist_check {
if {![tcl::string::is list -strict $e_check]} {
arg_error "Option $argname for [Get_caller] requires type 'list'. Received: '$e_check'" $argspecs $argname
}
if {[tcl::dict::size $thisarg_checks]} {
tcl::dict::for {checkopt checkval} $thisarg_checks {
switch -- $checkopt {
-minlen {
# -1 for disable is as good as zero
if {[llength $e_check] < $checkval} {
arg_error "Option $argname for [Get_caller] requires list with -minlen $checkval. Received len:[llength $e_check] value:'$e_check'" $argspecs $argname
}
}
-maxlen {
if {$checkval ne "-1"} {
if {[llength $e_check] > $checkval} {
arg_error "Option $argname for [Get_caller] requires list with -maxlen $checkval. Received len:[llength $e_check] value:'$e_check'" $argspecs $argname
}
}
}
}
}
}
}
}
string {
if {[tcl::dict::size $thisarg_checks]} {
foreach e_check $vlist_check {
@ -1295,6 +1344,25 @@ tcl::namespace::eval punk::args {
if {[llength $e_check] %2 != 0} {
arg_error "Option $argname for [Get_caller] requires type 'dict' - must be key value pairs. Received: '$e_check'" $argspecs $argname
}
if {[tcl::dict::size $thisarg_checks]} {
tcl::dict::for {checkopt checkval} $thisarg_checks {
switch -- $checkopt {
-minlen {
# -1 for disable is as good as zero
if {[tcl::dict::size $e_check] < $checkval} {
arg_error "Option $argname for [Get_caller] requires dict with -minlen $checkval. Received dict size:[dict size $e_check] value:'$e_check'" $argspecs $argname
}
}
-maxlen {
if {$checkval ne "-1"} {
if {[tcl::dict::size $e_check] > $checkval} {
arg_error "Option $argname for [Get_caller] requires dict with -maxlen $checkval. Received dict size:[dict size $e_check] value:'$e_check'" $argspecs $argname
}
}
}
}
}
}
}
}
alnum -

3
src/modules/punk/basictelnet-999999.0a1.0.tm

@ -475,7 +475,8 @@ namespace eval punk::basictelnet {
reset_option_states
set sock [socket $server $port]
#fconfigure $sock -buffering none -blocking 0 -encoding binary -translation crlf -eofchar {}
fconfigure $sock -buffering none -blocking 0 -encoding binary -translation binary -eofchar {}
#fconfigure $sock -buffering none -blocking 0 -encoding binary -translation binary -eofchar {}
fconfigure $sock -buffering none -blocking 0 -encoding iso8859-1 -translation binary -eofchar {}
fconfigure stdout -buffering none
fileevent $sock readable [list [namespace current]::fromServer $sock]
chan configure stdin -blocking 0

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

@ -1811,7 +1811,6 @@ interp alias {} colour {} punk::console::colour
interp alias {} ansi {} punk::console::ansi
interp alias {} color {} punk::console::colour
interp alias {} a+ {} punk::console::code_a+
interp alias {} a= {} punk::console::code_a
interp alias {} a {} punk::console::code_a
interp alias {} a? {} punk::console::code_a?

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

@ -1259,18 +1259,17 @@ namespace eval punk::fileline {
#[para]The encoding used is as specified in the -encoding option - or from the Byte Order Mark (bom) at the beginning of the data
#[para]For Tcl 8.6 - encodings such as utf-16le may not be available - so the bytes are swapped appropriately depending on the platform byteOrder and encoding 'unicode' is used.
#[para]encoding defaults to utf-8 if no -encoding specified and no BOM was found
#[para]Specify -encoding binary to perform no encoding conversion
#[para]Whether -encoding was specified or not - by default the BOM characters are not retained in the line-data
#[para]If -includebom 1 is specified - the bom will be retained in the stored chunk and the data for line 1, but will undergo the same encoding transformation as the rest of the data
#[para]The get_bomid method of the returned object will contain an identifier for any BOM encountered.
#[para] e.g utf-8,utf-16be, utf-16le, utf-32be, utf32-le, SCSU, BOCU-1,GB18030, UTF-EBCDIC, utf-1, utf-7
#[para]If the encoding specified in the BOM isn't recognised by Tcl - the resulting data is likely to remain as the raw bytes (binary translation)
#[para]If the encoding specified in the BOM isn't recognised by Tcl - the resulting data is likely to remain as the raw bytes of whatever encoding that is.
#[para]Currently only utf-8, utf-16* and utf-32* are properly supported even though the other BOMs are detected, reported via get_bomid, and stripped from the data.
#[para]GB18030 falls back to cp936/gbk (unless a gb18030 encoding has been installed). Use -encoding binary if this isn't suitable and you need to do your own processing of the raw data.
#[para]GB18030 falls back to cp936/gbk (unless a gb18030 encoding has been installed). Use -encoding iso8859-1 if this isn't suitable and you need to do your own processing of the bytes.
set argument_specification {
-file -default {} -type existingfile
-translation -default binary
-translation -default iso8859-1
-encoding -default "\uFFFF"
-includebom -default 0
*values -min 0 -max 1

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

@ -395,26 +395,229 @@ namespace eval punk::lib {
}
}
proc pdict {args} {
#experiment with equiv of js template literals with ${expression} in templates
#e.g tstr {This is the value of x in calling scope ${$x} !}
#e.g tstr -allowcommands {This is the value of x in calling scope ${[set x]} !}
#e.g tstr -allowcommands {This is the value of [lindex $x 0] in calling scope ${[lindex [set x] 0]} !}
proc tstr {args} {
set argd [punk::args::get_dict {
*proc -name punk::lib::tstr -help "A rough equivalent of js template literals"
-allowcommands -default 0 -type none
-return -default list -choices {dict list string}
*values -min 1 -max 1
templatestring -help "This argument should be a braced string containing placeholders such as ${$var}
where $var will be substituted from the calling context"
} $args]
set templatestring [dict get $argd values templatestring]
set opt_allowcommands [dict get $argd opts -allowcommands]
set opt_return [dict get $argd opts -return]
set nocommands "-nocommands"
if {$opt_allowcommands == 1} {
set nocommands ""
}
#set parts [_tstr_split $templatestring]
set parts [_parse_tstr_parts $templatestring]
set textchunks [list]
#set expressions [list]
set params [list]
set idx 0
foreach {pt expression} $parts {
lappend textchunks $pt
incr idx ;#pt incr
#ignore last expression
if {$idx == [llength $parts]} {
break
}
#lappend expressions $expression
lappend params [uplevel 1 [list subst {*}$nocommands $expression]]
incr idx ;#expression incr
}
switch -- $opt_return {
dict {
return [dict create template $textchunks params $params]
}
list {
return [list $textchunks {*}$params]
}
string {
set out ""
foreach pt $textchunks param $params {
append out $pt $param
}
return $out
}
default {
}
}
}
#test single placeholder tstr args where single placeholder must be an int
proc tstr_test_one {args} {
set argd [punk::args::get_dict {
*proc -name tstr_test_one -help {An example/test of a function designed to be called with a js-style curly-braced Tstr.
example:
set id 2
tstr_test_one {*}[Tstr {Select * from table where id = ${$id} and etc... ;}]
}
*values -min 2 -max 2
template -type list -minlen 2 -maxlen 2 -help "This could be supplied directly as a 2 element list of each half of the sql statement -
but the Tstr method above does this for you, and also passes in the id automatically"
where -type int -help {Integer param for where clause. Tstr mechanism above will pass the id as the second parameter}
} $args]
set template [dict get $argd values template]
set where [dict get $argd values where]
set result [join [list [lindex $template 0] $where [lindex $template 1]] ""]
return $result
}
proc _parse_tstr_parts {templatestring} {
if {$templatestring eq ""} {
return [list]
}
set chars [split $templatestring ""]
set in_placeholder 0
set tchars ""
set echars ""
set parts [list]
set i 0
foreach ch $chars {
if {!$in_placeholder} {
set nextch [lindex $chars [expr {$i+1}]]
if {"$ch$nextch" eq "\$\{"} {
set in_placeholder 2 ;#2 to signify we just entered placeholder
lappend parts $tchars
set tchars ""
} else {
append tchars $ch
}
} else {
if {$ch eq "\}"} {
if {[tcl::info::complete $echars]} {
set in_placeholder 0
lappend parts $echars
set echars ""
} else {
append echars $ch
}
} else {
if {$in_placeholder == 2} {
#skip opening bracket
set in_placeholder 1
} else {
append echars $ch
}
}
}
incr i
}
if {$tchars ne ""} {
lappend parts $tchars
}
if {[llength $parts] % 2 == 0} {
#always trail with pt for consistency with _perlish_split method so we can test other mechanisms with odd-length pt/code../pt style list
lappend parts ""
}
return $parts
}
#based on punk::ansi::ta::_perlish_split
proc _tstr_split {text} {
if {$text eq ""} {
return {}
}
set list [list]
set start 0
#ideally re should allow curlies within but we will probably need a custom parser to do it
#(js allows nested string interpolation)
#set re {\$\{[^\}]*\}}
set re {\$\{(?:(?!\$\{).)*\}}
#eg regexp {\x1b(?:\(0(?:(?:(?!\x1b\(B).)*\x1b\(B)|\)0(?:(?:(?!\x1b\)B).)*\x1b\)B))} $code
#We can get $matchEnd < $matchStart; we need to ensure there is an exit condition for non-greedy empty results REVIEW
while {[regexp -start $start -indices -- $re $text match]} {
lassign $match matchStart matchEnd
#puts "->start $start ->match $matchStart $matchEnd"
if {$matchEnd < $matchStart} {
puts "e:$matchEnd < s:$matchStart"
lappend list [tcl::string::range $text $start $matchStart-1] [tcl::string::index $text $matchStart]
incr start
if {$start >= [tcl::string::length $text]} {
break
}
continue
}
lappend list [tcl::string::range $text $start $matchStart-1] [tcl::string::range $text $matchStart+2 $matchEnd-1]
set start [expr {$matchEnd+1}]
#?
if {$start >= [tcl::string::length $text]} {
break
}
}
return [lappend list [tcl::string::range $text $start end]]
}
#get info about punk nestindex key ie type: list,dict,undetermined
proc nestindex_info {args} {
set argd [punk::args::get_dict {
-parent -default ""
nestindex
} $args]
set opt_parent [dict get $argd opts -parent]
if {$opt_parent eq ""} {
set parent_type undetermined
} else {
set parent_type [nestindex_info -parent "" $opt_parent] ;#make sure we explicitly set parent of parent to empty so we don't just recurse forever doing nothing
}
}
proc pdict {args} {
set sep " [a+ Web-seagreen]=[a] "
set argspec [string map [list %sep% $sep] {
*proc -name pdict -help {Print dict keys,values to channel
(see also showdict)}
*opts -any 1
#default separator to provide similarity to tcl's parray function
-separator -default " = "
-separator -default "%sep%"
-roottype -default "dict"
-substructure -default {}
-channel -default stdout -help "existing channel - or 'none' to return as string"
*values -min 1 -max -1
dictvar -type string -help "name of dict variable"
patterns -type string -default * -multiple 1
} $args]
patterns -type string -default "*" -multiple 1
}]
#puts stderr "$argspec"
set argd [punk::args::get_dict $argspec $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]]
set isarray [uplevel 1 [list array exists $dvar]]
if {$isarray} {
set dvalue [uplevel 1 [list array get $dvar]]
dict set opts -keytemplates [list ${dvar}(%k%)]
dict set opts -keysorttype dictionary
} else {
set dvalue [uplevel 1 [list set $dvar]]
}
showdict {*}$opts $dvalue {*}$patterns
}
#TODO - much.
#showdict needs to be able to show different branches which share a root path
#e.g show key a1/b* in its entirety along with a1/c* - (or even exact duplicates)
# - specify ansi colour per pattern so different branches can be highlighted?
# - ideally we want to be able to use all the dict & list patterns from the punk pipeline system eg @head @tail # (count) etc
# - The current version is incomplete but passably usable.
# - Copy proc and attempt rework so we can get back to this as a baseline for functionality
proc showdict {args} { ;# analogous to parray (except that it takes the dict as a value)
set argd [punk::args::get_dict {
set sep " [a+ Web-seagreen]=[a] "
set argd [punk::args::get_dict [string map [list %sep% $sep] {
*id punk::lib::pdict
*proc -name punk::lib::pdict -help "display dictionary keys and values"
#todo - table tableobject
@ -423,77 +626,333 @@ namespace eval punk::lib {
-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"
-ansibase_keys -default ""
-ansibase_values -default ""
-separator -default "%sep%" -help "Separator column between keys and values"
-roottype -default "" -help "list,dict,string"
-substructure -default {}
-ansibase_keys -default "" -help "ansi list for each level in -substructure. e.g \[list \[a+ red\] \[a+ web-green\]\]"
-ansibase_values -default ""
-keytemplates -default {%k%} -type list -help "list of templates for keys at each level"
-keysorttype -default "none" -choices {none dictionary ascii integer real}
-keysortdirection -default ascending -choices {ascending descending}
-keysortdirection -default increasing -choices {increasing decreasing}
*values -min 1 -max -1
dictvalue -type dict -help "dict value"
patterns -default * -type string -multiple 1 -help "key or key glob pattern"
} $args]
dictvalue -type list -help "dict or list value"
patterns -default "*" -type string -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 opt_ansibase_key [dict get $argd opts -ansibase_keys]
set opt_ansibase_value [dict get $argd opts -ansibase_values]
set opt_keytemplates [dict get $argd opts -keytemplates]
set opt_ansibase_keys [dict get $argd opts -ansibase_keys]
set opt_ansibase_values [dict get $argd opts -ansibase_values]
set opt_return [dict get $argd opts -return]
set opt_roottype [dict get $argd opts -roottype]
set opt_structure [dict get $argd opts -substructure]
set dval [dict get $argd values dictvalue]
set patterns [dict get $argd values patterns]
set result ""
set pattern_key_index [list] ;#list of pattern_nests, same length as number of keys generated
set pattern_next_substructure [dict create]
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]
if {$opt_roottype eq "list"} {
#puts "getting keys for list"
if {[llength $dval]} {
set re_numdashnum {^([-+]{0,1}\d+)-([-+]{0,1}\d+)$}
set re_idxdashidx {^([-+]{0,1}\d+|end[-+]{1}\d+|end)-([-+]{0,1}\d+|end[-+]{1}\d+|end)$}
foreach pattern_nest $patterns {
set keyset [list]
set pattern_nest_list [split $patterns /]
set p [lindex $pattern_nest_list 0]
if {$p eq ""} {
continue
}
if {$p eq "*"} {
lappend keyset {*}[punk::lib::range 0 [llength $dval]-1] ;#compat wrapper around subset of lseq functionality
} else {
if {[string match @* $p]} {
#already in list mode - trim optional list specifier @
set p [string range $p 1 end]
}
if {[string is integer -strict $p]} {
lappend keyset $p
} elseif {[string match "?*-?*" $p]} {
#list indices with tcl8.7 underscores? be careful. Before 8.7 we could have used regexp \d on integers
#now we should map _ to "" first
set p [string map {_ {}} $p]
#lassign [textutil::split::splitx $p {\.\.}] a b
if {![regexp $re_idxdashidx $p _match a b]} {
error "unrecognised pattern $p"
}
set lower_resolve [punk::lib::lindex_resolve $dval $a] ;#-2 for too low, -1 for too high
#keep lower_resolve as separate var to lower for further checks based on which side out-of-bounds
if {${lower_resolve} == -1} {
#lower bound is above upper list range
#match with decreasing indices is still possible
set lower [expr {[llength $dval]-1}] ;#set to max
} elseif {$lower_resolve == -2} {
set lower 0
} else {
set lower $lower_resolve
}
set upper [punk::lib::lindex_resolve $dval $b]
if {$upper == -2} {
#upper bound is below list range -
if {$lower_resolve >=-1} {
set upper 0
} else {
continue
}
} elseif {$upper == -1} {
#use max
set upper [expr {[llength $dval]-1}]
#assert - upper >=0 because we have ruled out empty lists
}
#note lower can legitimately be higher than upper - lib::range, like lseq can produce sequence in reverse order
lappend keyset {*}[punk::lib::range $lower $upper]
} else {
puts stderr "list: unrecognised pattern $p"
}
}
# -- --- --- ---
#check next pattern for substructure type to use
# -- --- --- ---
set substructure ""
set pnext [lindex $pattern_nest_list 1]
if {$pnext in [list "@*k@*" "@*K@*" "@*.@*" *]} {
set substructure dict
} elseif {[string match "@??@*" $pnext] || [string match "@?@*" $pnext]} {
#all 4 or 3 len prefixes bounded by @ are dict
set substructure dict
} elseif {[string match @@* $pnext]} {
set substructure dict
} elseif {[string match @* $pnext]} {
#if we've ruled out all explicit dict patterns - @* is list
set substructure list
} elseif {$pnext eq ""} {
set substructure "string"
} else {
#plain keys are now dict because there was no list-type pattern to flip the structure type
set substructure dict
}
#puts "--pattern_nest: $pattern_nest substructure: $substructure"
dict set pattern_next_substructure $pattern_nest $substructure
# -- --- --- ---
lappend filtered_keys {*}$keyset
foreach k $keyset {
lappend pattern_key_index $pattern_nest
}
}
}
#puts stderr "list keys: $filtered_keys"
} elseif {$opt_roottype eq "dict"} {
foreach pattern_nest $patterns {
set keyset [list]
set pattern_nest_list [split $pattern_nest /]
set p [lindex $pattern_nest_list 0]
if {$p in [list "@*k@*" "@*K@*" "@*.@*" *]} {
#exact glob-for-all
lappend keyset {*}[dict keys $dval]
} elseif {[string match @@* $p]} {
set k [string range $p 2 end]
lappend keyset {*}[dict keys $dval $k]
} elseif {[string match -nocase {@k\*@*}]} {
set k [string range $p 4 end]
lappend keyset {*}[dict keys $dval $k]
} elseif {[string match {@\*@*} $p]} {
set k [string range $p 3 end]
lappend keyset {*}[dict keys $dval $k]
} elseif {[string match -nocase {@v\*@*} $p] || [string match -nocase {@\*v@*} $p]} {
#don't match @v.@
error "dict value-return only not supported here - bad pattern '$p' in '$pattern_nest'"
} else {
lappend keyset {*}[dict keys $dval $p]
}
# -- --- --- ---
#check next pattern for substructure type to use
# -- --- --- ---
set substructure ""
set pnext [lindex $pattern_nest_list 1]
if {$pnext in [list "@*k@*" "@*K@*" "@*.@*" *]} {
set substructure dict
} elseif {[string match "@??@*" $pnext] || [string match "@?@*" $pnext]} {
#all 4 or 3 len prefixes bounded by @ are dict
set substructure dict
} elseif {[string match @@* $pnext]} {
set substructure dict
} elseif {[string match @* $pnext]} {
#if we've ruled out all explicit dict patterns - @* is list
set substructure list
} elseif {$pnext eq ""} {
set substructure "string"
} else {
#plain keys are now dict because there was no list-type pattern to flip the structure type
set substructure dict
}
#puts "--pattern_nest: $pattern_nest substructure: $substructure"
dict set pattern_next_substructure $pattern_nest $substructure
# -- --- --- ---
#puts stderr "adding [llength $keyset] keys for pattern_nest: $pattern_nest"
#sort only within each pattern for now
if {$opt_keysorttype ne "none"} {
set keyset [lsort -$opt_keysorttype -$opt_keysortdirection $keyset]
}
lappend filtered_keys {*}$keyset
foreach k $keyset {
lappend pattern_key_index $pattern_nest
}
}
#todo - fix. sorting keys wrecks pattern_key_index
#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]
#}
} else {
set filtered_keys [lsort -unique -$opt_keysorttype $opt_keysortdirection $filtered_keys]
#string
puts stdout "xxxx string"
return $dval
}
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}]]
set RST [a]
switch -- $opt_return {
"tailtohead" {
#last line of key is side by side (possibly with separator) with first line of value
#This is more intelligible when terminal wrapping occurs - and is closer to what happens with parray multiline keys and values
#we still pad the key to max width so that the separator appears in the same column - which in the case of wide keys could cause that to wrap for all entries
foreach key $filtered_keys {
lassign [textblock::size $key] _kw kwidth _kh kheight
lassign [textblock::size [dict get $dval $key]] _vw vwidth _vh vheight
set kt [lindex $opt_keytemplates 0]
if {$kt eq ""} {
set kt "%k%"
}
set display_keys [lmap k $filtered_keys {tcl::string::map [list %k% $k] $kt}]
set maxl [::tcl::mathfunc::max {*}[lmap v $display_keys {textblock::width $v}]]
set kidx 0
foreach keydisplay $display_keys key $filtered_keys {
if {$opt_roottype eq "list"} {
set thisval [lindex $dval $key]
} else {
#todo - slower lsearch if -dupes 1 flag set so we can display duplicate 'keys' if var not a proper dict but rather a dict-shaped list that we want to display as a dict
# - default highlight dupes (ansi underline?)
set thisval [tcl::dict::get $dval $key]
}
if {$opt_roottype eq "dict"} {
#set substructure [lrange $opt_structure 1 end]
set nextpatterns [list]
set pattern_nest [lindex $pattern_key_index $kidx]
set nextsub [dict get $pattern_next_substructure $pattern_nest]
#which pattern nest applies to this branch
set pattern_nest_list [split $pattern_nest /]
if {[llength $pattern_nest_list]} {
set tail [lassign $pattern_nest_list parent]
set nest $tail
#if {![llength $tail]} {
# set nest *
#}
lappend nextpatterns [join $nest /]
}
#puts "k:$key dict nextpatterns: $nextpatterns"
set subansibasekeys [lrange $opt_ansibase_keys 1 end]
set nextkeytemplates [lrange $opt_keytemplates 1 end]
set nextopts [dict get $argd opts]
#dict set nextopts -substructure $nextsub
dict set nextopts -keytemplates $nextkeytemplates
dict set nextopts -ansibase_keys $subansibasekeys
dict set nextopts -roottype $nextsub
dict set nextopts -channel none
#puts stderr "showdict {*}$nextopts $thisval [lindex $args end]"
if {[llength $nextpatterns] && $nextsub ne "string"} {
set thisval [showdict {*}$nextopts -- $thisval {*}$nextpatterns]
}
} elseif {$opt_roottype eq "list"} {
set nextpatterns [list]
set pattern_nest [lindex $pattern_key_index $kidx]
set nextsub [dict get $pattern_next_substructure $pattern_nest]
set pattern_nest_list [split $pattern_nest /]
if {[llength $pattern_nest_list]} {
set nest [lrange $pattern_nest_list 1 end]
if {![llength $nest]} {
set nest *
}
lappend nextpatterns [join $nest /]
}
#puts "list nextpattern: $nextpatterns"
set nextopts [dict get $argd opts]
#dict set nextopts -substructure $substructure
dict set nextopts -channel none
dict set nextopts -roottype $nextsub
if {![llength $nextpatterns]} {
set nextpatterns *
}
if {[llength $nextpatterns] && $nextsub ne "string"} {
set thisval [showdict {*}$nextopts -- $thisval {*}$nextpatterns]
}
}
set ansibase_key [lindex $opt_ansibase_keys 0]
lassign [textblock::size $keydisplay] _kw kwidth _kh kheight
lassign [textblock::size $thisval] _vw vwidth _vh vheight
set totalheight [expr {$kheight + $vheight -1}]
set blanks_above [string repeat \n [expr {$kheight -1}]]
set blanks_below [string repeat \n [expr {$vheight -1}]]
set sepwidth [textblock::width $opt_sep]
#append result [textblock::pad $opt_ansibase_key$key$RST -width $maxl] $opt_sep $opt_ansibase_value[dict get $dval $key]$RST \n
set kblock [textblock::pad $opt_ansibase_key$key$RST$blanks_below -width $maxl]
set kblock [textblock::pad $ansibase_key$keydisplay$RST$blanks_below -width $maxl]
set sblock [textblock::pad $blanks_above$opt_sep$blanks_below -width $sepwidth]
set vblock $blanks_above$opt_ansibase_value[dict get $dval $key]$RST
set vblock $blanks_above$opt_ansibase_values$thisval$RST
#only vblock is ragged - we can do a basic join because we don't care about rhs whitespace
append result [textblock::join_basic $kblock $sblock $vblock] \n
append result [textblock::join_basic -- $kblock $sblock $vblock] \n
incr kidx
}
}
"sidebyside" {
#This is nice for multiline keys and values of reasonable length, will produce unintuitive results when line-wrapping occurs.
#use ansibase_key etc to make the output more comprehensible in that situation.
#This is why it is not the default. (review - terminal width detection and wrapping?)
set maxl [::tcl::mathfunc::max {*}[lmap v $filtered_keys {textblock::width $v}]]
foreach key $filtered_keys {
set kt [lindex $opt_keytemplates 0]
if {$kt eq ""} {
set kt "%k%"
}
set keydisplay $opt_ansibase_keys[string map [list %k% $key] $kt]$RST
#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 $opt_ansibase_key$key$RST -width $maxl] $opt_sep "$opt_ansibase_value[dict get $dval $key]$RST"] \n
append result [textblock::join -- [textblock::pad $keydisplay -width $maxl] $opt_sep "$opt_ansibase_values[dict get $dval $key]$RST"] \n
}
}
}
@ -765,19 +1224,23 @@ namespace eval punk::lib {
#[para]This means the proc may be called with something like $x+2 end-$y etc
#[para]Sometimes the actual integer index is desired.
#[para]We want to resolve the index used, without passing arbitrary expressions into the 'expr' function - which could have security risks.
#[para]lindex_resolve will parse the index expression and return -1 if the supplied index expression is out of bounds for the supplied list.
#[para]lindex_resolve will parse the index expression and return:
#[para] a) -2 if the supplied index expression is below the lower bound for the supplied list. (< 0)
#[para] b) -1 if the supplied index expression is above the upper bound for the supplied list. (> end)
#[para]Otherwise it will return an integer corresponding to the position in the list.
#[para]This is in stark contrast to Tcl list function indices which will return empty strings for out or bounds indices, or in the case of lrange, return results anyway.
#[para]Like Tcl list commands - it will produce an error if the form of the index is not acceptable
#Note that for an index such as $x+1 - we never see the '$x' as it is substituted in the calling command. We will get something like 10+1 - which we will resolve (hopefully safely) with expr
if {![llength $list]} {
return -1
}
#if {![llength $list]} {
# #review
# return ???
#}
set index [tcl::string::map {_ {}} $index] ;#forward compatibility with integers such as 1_000
if {[string is integer -strict $index]} {
#can match +i -i
if {$index < 0} {
return -1
return -2
} elseif {$index >= [llength $list]} {
return -1
} else {
@ -794,16 +1257,28 @@ namespace eval punk::lib {
return -1
}
} else {
set offset 0
#end
set index [expr {[llength $list]-1}]
if {$index < 0} {
#special case - end with empty list - treat end like a positive number out of bounds
return -1
} else {
return $index
}
}
#by now, if op = + then offset = 0 so we only need to handle the minus case
if {$offset == 0} {
set index [expr {[llength $list]-1}]
if {$index < 0} {
return -1 ;#special case
} else {
return $index
}
} else {
#by now, if op = + then offset = 0 so we only need to handle the minus case
set index [expr {([llength $list]-1) - $offset}]
}
if {$index < 0} {
return -1
return -2
} else {
return $index
}
@ -823,16 +1298,25 @@ namespace eval punk::lib {
} else {
error "bad index '$index': must be integer?\[+-\]integer? or end?\[+-\]integer?"
}
if {$index < 0 || $index >= [llength $list]} {return -1}
if {$index < 0} {
return -2
} elseif {$index >= [llength $list]} {
return -1
}
return $index
}
}
}
proc lindex_resolve2 {list index} {
set indices [list] ;#building this may be somewhat expensive in terms of storage and compute for large lists - we could use lseq in Tcl 8.7+ but that's likely unavailable here.
for {set i 0} {$i < [llength $list]} {incr i} {
lappend indices $i
}
#set indices [list] ;#building this may be somewhat expensive in terms of storage and compute for large lists - we could use lseq in Tcl 8.7+ but that's likely unavailable here.
#for {set i 0} {$i < [llength $list]} {incr i} {
# lappend indices $i
#}
if {[llength $list]} {
set indices [punk::lib::range 0 [expr {[llength $list]-1}]] ;# uses lseq if available, has fallback.
} else {
set indices [list]
}
set idx [lindex $indices $index]
if {$idx eq ""} {
return -1

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

@ -1166,24 +1166,6 @@ tcl::namespace::eval punk::ns {
lappend allooclasses $cmd
}
}
if {[catch {
if {$cmd eq ""} {
#empty command was previously marked as "::" - too confusing - nslist updated to properly display empty string.
set nsorigin [namespace origin ${location}::]
} elseif {[string match :* $cmd]} {
set nsorigin [nseval $location "::namespace origin $cmd"]
} else {
set nsorigin [namespace origin [nsjoin $location $cmd]]
}
} errM]} {
puts stderr "get_ns_dicts failed to determine origin of command '$cmd' adding to 'undetermined'"
puts stderr "error message: $errM"
lappend allundetermined $cmd
} else {
if {[nsprefix $nsorigin] ne $location} {
lappend allimported $cmd
}
}
}
default {
if {$ctype eq "imported"} {
@ -1242,6 +1224,25 @@ tcl::namespace::eval punk::ns {
}
}
#JMN
if {[catch {
if {$cmd eq ""} {
#empty command was previously marked as "::" - too confusing - nslist updated to properly display empty string.
set nsorigin [namespace origin ${location}::]
} elseif {[string match :* $cmd]} {
set nsorigin [nseval $location "::namespace origin $cmd"]
} else {
set nsorigin [namespace origin [nsjoin $location $cmd]]
}
} errM]} {
puts stderr "get_ns_dicts failed to determine origin of command '$cmd' adding to 'undetermined'"
puts stderr "error message: $errM"
lappend allundetermined $cmd
} else {
if {[nsprefix $nsorigin] ne $location} {
lappend allimported $cmd
}
}
}
if {$glob ne "*"} {
set childtailmatches [lsearch -all -inline $childtails $glob]

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

@ -1876,8 +1876,8 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config
set stdinconf [fconfigure $inputchan]
if {$::tcl_platform(platform) eq "windows" && [dict get $stdinconf -encoding] ni [list unicode utf-16 utf-8]} {
#some long console inputs are split weirdly when -encoding and -translation are left at defaults - requiring extra enter-key to get repl to process.
#experiment to see if using binary and handling line endings manually gives insight.
# - do: chan conf stdin -encoding binary -translation lf
#experiment to see if using iso8859-1 (raw bytes) and handling line endings manually gives insight.
# - do: chan conf stdin -encoding iso859-1 -translation lf
#first command after configuring stdin this way seems to be interpreted with wrong encoding - subsequent commands work - review
@ -2015,7 +2015,8 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config
set status [catch {uplevel #0 [list runraw $commandstr]} raw_result]
} else {
#puts stderr "repl uplevel 0 '$run_command_string'"
#JMN
#puts stderr "sending to codethread::runscript $run_command_string"
tsv::set codethread_$codethread status -1
thread::send -async $codethread [list punk::repl::codethread::runscript $run_command_string]
thread::mutex lock $codethread_mutex
@ -2762,8 +2763,8 @@ namespace eval repl {
code alias ::punk::set_repl_last_unknown ::repl::interphelpers::set_repl_last_unknown
code alias ::punk::get_repl_runid ::repl::interphelpers::get_repl_runid
code alias cmdtype ::repl::interphelpers::cmdtype
#JMN
#code alias cmdtype ::repl::interphelpers::cmdtype
#temporary debug aliases - deliberate violation of safety provided by safe interp
code alias escapeeval ::repl::interphelpers::escapeeval

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

@ -836,7 +836,7 @@ tcl::namespace::eval textblock {
set args_got_header_colspans 1
#check columns to left to make sure each new colspan for this column makes sense in the overall context
#user may have to adjust colspans in order left to right to avoid these check errors
#note that 'all' represents span all up to the next non-zero defined colspan.
#note that 'any' represents span all up to the next non-zero defined colspan.
set cspans [my header_colspans]
set h 0
if {[llength $v] > [tcl::dict::size $cspans]} {
@ -846,34 +846,34 @@ tcl::namespace::eval textblock {
if {$cidx == 0} {
if {[tcl::string::is integer -strict $s]} {
if {$s < 1} {
error "configure_column $cidx -header_colspans bad first value '$s' for header '$h' . First column cannot have span less than 1. use 'all' or a positive integer"
error "configure_column $cidx -header_colspans bad first value '$s' for header '$h' . First column cannot have span less than 1. use 'any' or a positive integer"
}
} else {
if {$s ne "all" && $s ne ""} {
error "configure_column $cidx -header_colspans unrecognised value '$s' for header '$h' - must be a positive integer or the keyword 'all'"
if {$s ne "any" && $s ne ""} {
error "configure_column $cidx -header_colspans unrecognised value '$s' for header '$h' - must be a positive integer or the keyword 'any'"
}
}
} else {
#if {![tcl::string::is integer -strict $s]} {
# if {$s ne "all" && $s ne ""} {
# error "configure_column $cidx -header_colspans unrecognised value '$s' for header '$h' - must be a positive integer or the keyword 'all'"
# if {$s ne "any" && $s ne ""} {
# error "configure_column $cidx -header_colspans unrecognised value '$s' for header '$h' - must be a positive integer or the keyword 'any'"
# }
#} else {
set header_spans [tcl::dict::get $cspans $h]
set remaining [lindex $header_spans 0]
if {$remaining ne "all"} {
if {$remaining ne "any"} {
incr remaining -1
}
#look at spans defined for previous cols
#we are presuming previous column entries are valid - and only validating if our new entry is ok under that assumption
for {set c 0} {$c < $cidx} {incr c} {
set span [lindex $header_spans $c]
if {$span eq "all"} {
set remaining "all"
if {$span eq "any"} {
set remaining "any"
} else {
if {$remaining eq "all"} {
if {$remaining eq "any"} {
if {$span ne "0"} {
#a previous column has ended the 'all' span
#a previous column has ended the 'any' span
set remaining [expr {$span -1}]
}
} else {
@ -886,8 +886,8 @@ tcl::namespace::eval textblock {
}
}
}
if {$remaining eq "all"} {
#any int >0 ok - what about 'all' immediately following all?
if {$remaining eq "any"} {
#any int >0 ok - what about 'any' immediately following any?
} else {
if {$remaining > 0} {
if {$s ne "0" && $s ne ""} {
@ -895,7 +895,7 @@ tcl::namespace::eval textblock {
}
} else {
if {$s == 0} {
error "configure_column $cidx -header_colspans bad span $s for header '$h'. No span in place - need >=1 or 'all'"
error "configure_column $cidx -header_colspans bad span $s for header '$h'. No span in place - need >=1 or 'any'"
}
}
}
@ -1020,10 +1020,11 @@ tcl::namespace::eval textblock {
#return a dict keyed on header index with values representing colspans
#e.g
# 0 {all 0 0 0} 1 {1 1 1 1} 2 {2 0 1 1} 3 {3 0 0 1}
# 0 {any 0 0 0} 1 {1 1 1 1} 2 {2 0 1 1} 3 {3 0 0 1}
#
method header_colspans {} {
set num_headers [my header_count_calc]
#set num_headers [my header_count_calc]
set num_headers [my header_count]
set colspans_by_header [tcl::dict::create]
tcl::dict::for {cidx cdef} $o_columndefs {
set headerlist [tcl::dict::get $cdef -headers]
@ -1033,17 +1034,17 @@ tcl::namespace::eval textblock {
set defined_span [lindex $colspans_for_column $h]
set i 0
set spanremaining [lindex $headerspans 0]
if {$spanremaining ne "all"} {
if {$spanremaining ne "any"} {
if {$spanremaining eq ""} {
set spanremaining 1
}
incr spanremaining -1
}
foreach s $headerspans {
if {$s eq "all"} {
set spanremaining "all"
if {$s eq "any"} {
set spanremaining "any"
} elseif {$s == 0} {
if {$spanremaining ne "all"} {
if {$spanremaining ne "any"} {
incr spanremaining -1
}
} else {
@ -1055,7 +1056,7 @@ tcl::namespace::eval textblock {
if {$spanremaining eq "0"} {
lappend headerspans 1
} else {
#"all" or an integer
#"any" or an integer
lappend headerspans 0
}
} else {
@ -1067,6 +1068,39 @@ tcl::namespace::eval textblock {
return $colspans_by_header
}
#e.g
# 0 {any 0 0 0} 1 {1 1 1 1} 2 {2 0 any 1} 3 {any 0 0 1}
#convert to
# 0 {4 0 0 0} 1 {1 1 1 1} 2 {2 0 1 1} 3 {3 0 0 1}
method header_colspans_numeric {} {
set hcolspans [my header_colspans]
if {![tcl::dict::size $hcolspans]} {
return
}
set numcols [llength [tcl::dict::get $hcolspans 0]] ;#assert: all are the same
tcl::dict::for {h spans} $hcolspans {
set c 0 ;#column index
foreach s $spans {
if {$s eq "any"} {
set spanlen 1
for {set i [expr {$c+1}]} {$i < $numcols} {incr i} {
#next 'any' or non-zero ends an 'any' span
if {[lindex $spans $i] ne "0"} {
break
}
incr spanlen
}
#overwrite the 'any' with it's actual span
set modified_spans [dict get $hcolspans $h]
lset modified_spans $c $spanlen
dict set hcolspans $h $modified_spans
}
incr c
}
}
return $hcolspans
}
#should be configure_headerrow ?
method configure_header {index_expression args} {
#the header data being configured or returned here is mostly derived from the column defs and if necessary written to the column defs.
@ -1103,6 +1137,10 @@ tcl::namespace::eval textblock {
#set val [tcl::dict::get $o_rowdefs $ridx $k]
set infodict [tcl::dict::create]
#todo
# -blockalignments and -textalignments lists
# must match number of values if not empty? - e.g -blockalignments {left right "" centre left ""}
#if there is a value it overrides alignments specified on the column
switch -- $k {
-values {
set header_row_items [list]
@ -1190,54 +1228,54 @@ tcl::namespace::eval textblock {
if {[llength $v]} {
set firstspan [lindex $v 0]
set first_is_ok 0
if {$firstspan eq "all"} {
if {$firstspan eq "any"} {
set first_is_ok 1
} elseif {[tcl::string::is integer -strict $firstspan] && $firstspan > 0 && $firstspan <= $numcols} {
set first_is_ok 1
}
if {!$first_is_ok} {
error "textblock::table::configure_header -colspans first value '$firstspan' must be integer > 0 & <= $numcols or the string \"all\""
error "textblock::table::configure_header -colspans first value '$firstspan' must be integer > 0 & <= $numcols or the string \"any\""
}
#we don't mind if there are less colspans specified than columns.. the tail can be deduced from the leading ones specified (review)
set remaining $firstspan
if {$remaining ne "all"} {
if {$remaining ne "any"} {
incr remaining -1
}
set spanview $v
set sidx 1
#because we allow 'all' - be careful when doing < or > comparisons - as we are mixing integer and string comparisons if we don't test for 'all' first
#because we allow 'any' - be careful when doing < or > comparisons - as we are mixing integer and string comparisons if we don't test for 'any' first
foreach span [lrange $v 1 end] {
if {$remaining eq "all"} {
if {$span eq "all"} {
set remaining "all"
if {$remaining eq "any"} {
if {$span eq "any"} {
set remaining "any"
} elseif {$span > 0} {
#ok to reset to higher val immediately or after an all and any number of following zeros
#ok to reset to higher val immediately or after an any and any number of following zeros
if {$span > ($numcols - $sidx)} {
lset spanview $sidx [a+ web-red]$span[a]
error "textblock::table::configure_header -colspans sequence incorrect at span '$span'. Require span <= [expr {$numcols-$sidx}] or \"all\".[a] $spanview"
error "textblock::table::configure_header -colspans sequence incorrect at span '$span'. Require span <= [expr {$numcols-$sidx}] or \"any\".[a] $spanview"
}
set remaining $span
incr remaining -1
} else {
#zero following an all - leave remaining as all
#zero following an any - leave remaining as any
}
} else {
if {$span eq "0"} {
if {$remaining eq "0"} {
lset spanview $sidx [a+ web-red]$span[a]
error "textblock::table::configure_header -colspans sequence incorrect at span '$span' remaining is $remaining. Require positive or \"all\" value.[a] $spanview"
error "textblock::table::configure_header -colspans sequence incorrect at span '$span' remaining is $remaining. Require positive or \"any\" value.[a] $spanview"
} else {
incr remaining -1
}
} else {
if {$remaining eq "0"} {
#ok for new span value of all or > 0
if {$span ne "all" && $span > ($numcols - $sidx)} {
#ok for new span value of any or > 0
if {$span ne "any" && $span > ($numcols - $sidx)} {
lset spanview $sidx [a+ web-red]$span[a]
error "textblock::table::configure_header -colspans sequence incorrect at span '$span'. Require span <= [expr {$numcols-$sidx}] or \"all\".[a] $spanview"
error "textblock::table::configure_header -colspans sequence incorrect at span '$span'. Require span <= [expr {$numcols-$sidx}] or \"any\".[a] $spanview"
}
set remaining $span
if {$remaining ne "all"} {
if {$remaining ne "any"} {
incr remaining -1
}
} else {
@ -1760,8 +1798,8 @@ tcl::namespace::eval textblock {
set hdrmap [tcl::dict::get $hmap only${opt_posn}]
set topseps_h [tcl::dict::get $sep_elements_horizontal top$opt_posn]
set topseps_v [tcl::dict::get $sep_elements_vertical top$opt_posn]
set midseps_h [tcl::dict::get $sep_elements_horizontal middle$opt_posn]
set topseps_v [tcl::dict::get $sep_elements_vertical top$opt_posn]
set midseps_v [tcl::dict::get $sep_elements_vertical middle$opt_posn]
set botseps_v [tcl::dict::get $sep_elements_vertical bottom$opt_posn]
set onlyseps_v [tcl::dict::get $sep_elements_vertical only$opt_posn]
@ -1795,16 +1833,19 @@ tcl::namespace::eval textblock {
#set hcolwidth [my column_width_configured $cidx]
set hcell_line_blank [tcl::string::repeat " " $hcolwidth]
set all_colspans [my header_colspans]
set all_colspans [my header_colspans_numeric]
#put our framedef calls together
set fdef_header [textblock::framedef $ftype_header]
set framedef_leftbox [textblock::framedef -joins left $ftype_header]
set framedef_headerdown_same [textblock::framedef -joins {down} $ftype_header]
set framedef_headerdown_body [textblock::framedef -joins [list down-$fname_body] $ftype_header]
#default span_extend_map - used as base to customise with specific joins
set fdef_header [textblock::framedef $ftype_header]
set span_extend_map [tcl::dict::create \
vll " "\
tlc [tcl::dict::get $fdef_header hlt]\
blc [tcl::dict::get $fdef_header hlb]\
]
set framedef_leftbox [textblock::framedef $ftype_header -joins left]
#used for colspan-zero header frames
@ -1851,7 +1892,10 @@ tcl::namespace::eval textblock {
}
#puts ">>> headerspans: $headerspans cidx: $cidx"
if {$this_span eq "all" || $this_span > 0} {
#if {$this_span eq "any" || $this_span > 0} {}
#changed to processing only numeric colspans
if {$this_span > 0} {
set startmap [tcl::dict::get $hmap $rowpos${opt_posn}]
#look at spans in header below to determine joins required at blc
if {$show_seps_v} {
@ -1882,7 +1926,7 @@ tcl::namespace::eval textblock {
# -boxlimits $hlims -boxmap $startmap -joins $header_joins $hval\
# ]
if {$this_span eq "1"} {
if {$this_span == 1} {
#write the actual value now
set cellcontents $hval
} else {
@ -1894,13 +1938,20 @@ tcl::namespace::eval textblock {
-boxlimits $hlims -boxmap $startmap -joins $header_joins $cellcontents\
]
if {$this_span ne "1"} {
if {$this_span != 1} {
#puts "===>\n$header_cell_startspan\n<==="
set spanned_parts [list $header_cell_startspan]
#assert this_span == "all" or >1 ie a header that spans other columns
#assert this_span == "any" or >1 ie a header that spans other columns
#therefore more parts to append
#set remaining_cols [lrange [tcl::dict::keys $o_columndefs] $cidx end]
set remaining_spans [lrange $headerspans $cidx+1 end]
set spanval [join $remaining_spans ""] ;#so we can test for all zeros
set spans_to_rhs 0
if {[expr {$spanval}] == 0} {
#puts stderr "SPANS TO RHS"
set spans_to_rhs 1
}
#puts ">> remaining_spans: $remaining_spans"
set spancol [expr {$cidx + 1}]
set h_lines [lrepeat $rowh ""]
@ -1944,13 +1995,11 @@ tcl::namespace::eval textblock {
if {[llength $next_spanlist]} {
set spanbelow [lindex $next_spanlist $spancol]
if {$spanbelow != 0} {
set downbox [textblock::framedef $ftype_header -joins {down}]
tcl::dict::set this_span_map blc [tcl::dict::get $downbox hlbj] ;#horizontal line bottom with down join - to same frametype
tcl::dict::set this_span_map blc [tcl::dict::get $framedef_headerdown_same hlbj] ;#horizontal line bottom with down join - to same frametype
}
} else {
#join to body
set downbox [textblock::framedef $ftype_header -joins [list down-$fname_body]]
tcl::dict::set this_span_map blc [tcl::dict::get $downbox hlbj] ;#horizontal line bottom with down join - from header frametype to body frametype
tcl::dict::set this_span_map blc [tcl::dict::get $framedef_headerdown_body hlbj] ;#horizontal line bottom with down join - from header frametype to body frametype
}
}
@ -1980,17 +2029,38 @@ tcl::namespace::eval textblock {
#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} {
set hlims $header_boxlimits_toprow
if {$spans_to_rhs} {
if {$cidx == 0} {
set fake_posn solo
} else {
set fake_posn right
}
set x_limj [my Get_boxlimits_and_joins $fake_posn $fname_body]
if {$hrow == 0} {
set x_boxlimits_toprow [tcl::dict::get $x_limj boxlimits_top]
set hlims [struct::set intersect [tcl::dict::get $o_opts_table_effective -framelimits_header] $x_boxlimits_toprow]
} else {
set x_boxlimits_position [tcl::dict::get $x_limj boxlimits]
set hlims [struct::set intersect [tcl::dict::get $o_opts_table_effective -framelimits_header] $x_boxlimits_position]
}
} else {
set hlims $header_boxlimits
if {$hrow == 0} {
set hlims $header_boxlimits_toprow
} else {
set hlims $header_boxlimits
}
}
if {!$show_seps_v} {
set hlims [struct::set difference $hlims $headerseps_v]
}
if {![tcl::dict::get $o_opts_table -show_edge]} {
#use the edge_parts corresponding to the column being written to ie use opt_posn
set hlims [struct::set difference $hlims [tcl::dict::get $::textblock::class::header_edge_parts $rowpos$opt_posn] ]
if {$spans_to_rhs} {
#assert fake_posn has been set above based on cidx and spans_to_rhs
set hlims [struct::set difference $hlims [tcl::dict::get $::textblock::class::header_edge_parts ${rowpos}$fake_posn] ]
} else {
#use the edge_parts corresponding to the column being written to ie use opt_posn
set hlims [struct::set difference $hlims [tcl::dict::get $::textblock::class::header_edge_parts $rowpos$opt_posn] ]
}
}
set spacemap [list hl " " vl " " tlc " " blc " " trc " " brc " "] ;#transparent overlay elements
@ -2005,7 +2075,21 @@ tcl::namespace::eval textblock {
#set spanned_frame [overtype::renderspace -experimental test_mode -transparent 1 $spanned_frame $hblock]
#spanned values default left - todo make configurable
#TODO
#consider that currently blockaligning for spanned columns must always use the blockalign value from the starting column of the span
#we could conceivably have an override that could be set somehow with configure_header for customization of alignments of individual spans or span sizes?
#this seems like a likely requirement. The first spanned column may well have different alignment requirements than the span.
#(e.g if first spanned col happens to be numeric it probably warrants right textalign (if not blockalign) but we don't necessarily want the spanning header or even a non-spanning header to be right aligned)
set spanned_frame [overtype::block -blockalign $col_blockalign -overflow 1 -transparent 1 $spanned_frame $hblock]
#POTENTIAL BUG (fixed with spans_to_rhs above)
#when -blockalign right and colspan extends to rhs - last char of longest of that spanlength will overlap right edge (if show_edge 1)
#we need to shift 1 to the left when doing our overtype with blockalign right
#we normally do this by using the hlims based on position - effectively we need a rhs position set of hlims for anything that colspans to the right edge
#(even though the column position may be left or inner)
} else {
#this_span == 1
@ -2301,11 +2385,9 @@ tcl::namespace::eval textblock {
error "table::get_column_cells_by_index no such index $index_expression. Valid range is $range"
}
#assert cidx is integer >=0
set num_header_rows [my header_count]
set cdef [tcl::dict::get $o_columndefs $cidx]
set headerlist [tcl::dict::get $cdef -headers]
set num_header_rows [my header_count]
set ansibase_body [tcl::dict::get $o_opts_table -ansibase_body]
set ansibase_col [tcl::dict::get $cdef -ansibase]
set textalign [tcl::dict::get $cdef -textalign]
switch -- $textalign {
@ -2316,20 +2398,23 @@ tcl::namespace::eval textblock {
}
}
set ansibase_body [tcl::dict::get $o_opts_table -ansibase_body]
set ansibase_header [tcl::dict::get $o_opts_table -ansibase_header]
#set header_underlay $ansibase_header$cell_line_blank
#set hdrwidth [my column_width_configured $cidx]
set all_colspans [my header_colspans]
#set all_colspans [my header_colspans]
#we need to replace the 'any' entries with their actual span lengths before passing any -colspan values to column_datawidth - hence use header_colspans_numeric
set all_colspans [my header_colspans_numeric]
#JMN
#store configured widths so we don't look up for each header line
set configured_widths [list]
foreach c [tcl::dict::keys $o_columndefs] {
#lappend configured_widths [my column_width $c]
#we don't just want the width of the column in the body - or the headers will get truncated
lappend configured_widths [my column_width_configured $c]
}
#set configured_widths [list]
#foreach c [tcl::dict::keys $o_columndefs] {
# #lappend configured_widths [my column_width $c]
# #we don't just want the width of the column in the body - or the headers will get truncated
# lappend configured_widths [my column_width_configured $c]
#}
set output [tcl::dict::create]
tcl::dict::set output headers [list]
@ -2342,7 +2427,7 @@ tcl::namespace::eval textblock {
set this_span [lindex $headerrow_colspans $cidx]
#set this_hdrwidth [lindex $configured_widths $cidx]
set this_hdrwidth [my column_datawidth $cidx -headers 1 -colspan $this_span] ;#widest of headers in this col with same span - allows textalign to work with blockalign
set this_hdrwidth [my column_datawidth $cidx -headers 1 -colspan $this_span -cached 1] ;#widest of headers in this col with same span - allows textalign to work with blockalign
set hcell_line_blank [tcl::string::repeat " " $this_hdrwidth]
set hcell_lines [lrepeat $header_maxdataheight $hcell_line_blank]
@ -2704,7 +2789,7 @@ tcl::namespace::eval textblock {
set width_max [expr {max($test_width,$width_max)}]
continue
}
if {$spanc eq "all" || $spanc > 1} {
if {$spanc eq "any" || $spanc > 1} {
set spanned [list] ;#spanned is other columns spanned - not including this one
set cnext [expr {$cidx +1}]
set spanlength [lindex $colspans $cnext]
@ -2773,10 +2858,12 @@ tcl::namespace::eval textblock {
set opts [tcl::dict::create\
-headers 0\
-footers 0\
-colspan *\
-colspan unspecified\
-data 1\
-cached 1\
]
#NOTE: -colspan any is not the same as *
#
#-colspan is relevant to header/footer data only
foreach {k v} $args {
switch -- $k {
@ -2789,6 +2876,17 @@ tcl::namespace::eval textblock {
}
}
set opt_colspan [tcl::dict::get $opts -colspan]
switch -- $opt_colspan {
* - unspecified {}
any {
error "method column_datawidth invalid -colspan '$opt_colspan' must be * or an integer >= 0 (use header_colspans_numeric to get actual spans)"
}
default {
if {![string is integer -strict $opt_colspan]} {
error "method column_datawidth invalid -colspan '$opt_colspan' must be * or an integer >= 0"
}
}
}
set cidx [lindex [tcl::dict::keys $o_columndefs] $index_expression]
@ -2801,26 +2899,26 @@ tcl::namespace::eval textblock {
set bwidest 0
set fwidest 0
if {[tcl::dict::get $opts -headers]} {
if {$opt_colspan eq "*"} {
if {$opt_colspan in {* unspecified}} {
set hwidest [tcl::dict::get $o_columnstates $cidx maxwidthheaderseen]
} else {
#this is not cached
# -- --- --- ---
set colheaders [tcl::dict::get $o_columndefs $cidx -headers]
set all_colspans_by_header [my header_colspans]
set all_colspans_by_header [my header_colspans_numeric]
set hlist [list]
tcl::dict::for {hrow cspans} $all_colspans_by_header {
set s [lindex $cspans $cidx]
#todo - map 'all' entries to a number?
#we should build a version of header_colspans that does this
if {$s eq $opt_colspan} {
lappend hlist [lindex $colheaders $hrow]
}
}
#set widest1 [tcl::mathfunc::max {*}[lmap v $cmds {tcl::string::length $v}]]
if {[llength $hlist]} {
set hwidest [tcl::mathfunc::max {*}[lmap v $hlist {textblock::width $v}]]
} else {
set hwidest 0
}
# -- --- --- ---
}
}
if {[tcl::dict::get $opts -data]} {
@ -2835,8 +2933,28 @@ tcl::namespace::eval textblock {
#assert cidx is >=0 integer in valid range of keys for o_columndefs
set values [list]
set hwidest 0
if {[tcl::dict::get $opts -headers]} {
lappend values {*}[tcl::dict::get $o_columndefs $cidx -headers]
if {$opt_colspan in {* unspecified}} {
lappend values {*}[tcl::dict::get $o_columndefs $cidx -headers]
} else {
# -- --- --- ---
set colheaders [tcl::dict::get $o_columndefs $cidx -headers]
set all_colspans_by_header [my header_colspans_numeric]
set hlist [list]
tcl::dict::for {hrow cspans} $all_colspans_by_header {
set s [lindex $cspans $cidx]
if {$s eq $opt_colspan} {
lappend hlist [lindex $colheaders $hrow]
}
}
if {[llength $hlist]} {
set hwidest [tcl::mathfunc::max {*}[lmap v $hlist {textblock::width $v}]]
} else {
set hwidest 0
}
# -- --- --- ---
}
}
if {[tcl::dict::get $opts -data]} {
if {[tcl::dict::exists $o_columndata $cidx]} {
@ -2847,9 +2965,10 @@ tcl::namespace::eval textblock {
lappend values {*}[tcl::dict::get $o_columndefs $cidx -footers]
}
if {[llength $values]} {
set widest [tcl::mathfunc::max {*}[lmap v $values {textblock::width $v}]]
set valwidest [tcl::mathfunc::max {*}[lmap v $values {textblock::width $v}]]
set widest [expr {max($valwidest,$hwidest)}]
} else {
set widest 0
set widest $hwidest
}
return $widest
}
@ -3143,24 +3262,43 @@ tcl::namespace::eval textblock {
set colspans_for_column [tcl::dict::get $o_columndefs $cidx -header_colspans]
set spaninfo [list]
set numcols [tcl::dict::size $o_columndefs]
#note that 'all' can occur in positions other than column 0 - meaning all remaining
#note that 'any' can occur in positions other than column 0 - meaning any remaining until next non-zero span
tcl::dict::for {hrow rawspans} $spans_by_header {
set thiscol_spanval [lindex $rawspans $cidx]
if {$thiscol_spanval eq "all" || $thiscol_spanval > 0} {
if {$thiscol_spanval eq "any" || $thiscol_spanval > 0} {
set spanstartcol $cidx ;#own column
if {$thiscol_spanval eq "all"} {
set spanlen [expr {$numcols - $cidx}]
if {$thiscol_spanval eq "any"} {
#scan right to first non-zero to get actual length of 'any' span
#REVIEW!
set spanlen 1
for {set i [expr {$cidx+1}]} {$i < $numcols} {incr i} {
#abort at next any or number or empty string
if {[lindex $rawspans $i] ne "0"} {
break
}
incr spanlen
}
#set spanlen [expr {$numcols - $cidx}]
} else {
set spanlen $thiscol_spanval
}
} else {
#look left til we see an all or a non-zero value
#look left til we see an any or a non-zero value
for {set i $cidx} {$i > -1} {incr i -1} {
set s [lindex $rawspans $i]
if {$s eq "all" || $s > 0} {
if {$s eq "any" || $s > 0} {
set spanstartcol $i
if {$s eq "all"} {
set spanlen [expr {$numcols - $i}]
if {$s eq "any"} {
#REVIEW!
#set spanlen [expr {$numcols - $i}]
set spanlen 1
#now scan right to see how long the 'any' actually is
for {set j [expr {$i+1}]} {$j < $numcols} {incr j} {
if {[lindex $rawspans $j] ne "0"} {
break
}
incr spanlen
}
} else {
set spanlen $s
}
@ -3295,7 +3433,7 @@ tcl::namespace::eval textblock {
set table [overtype::renderspace -overflow 1 -experimental test_mode -transparent $TSUB $table[unset table] $nextcol]
#JMN
#set nextcol [textblock::join [textblock::block $padwidth $height "\uFFFF"] $nextcol]
#set nextcol [textblock::join -- [textblock::block $padwidth $height "\uFFFF"] $nextcol]
#set table [overtype::renderspace -overflow 1 -experimental test_mode -transparent \uFFFF $table $nextcol]
}
incr padwidth $bodywidth
@ -3303,7 +3441,7 @@ tcl::namespace::eval textblock {
}
if {[llength $cols]} {
#return [textblock::join {*}$blocks]
#return [textblock::join -- {*}$blocks]
if {[tcl::dict::get $o_opts_table -show_edge]} {
#title is considered part of the edge ?
set offset 1 ;#make configurable?
@ -3399,11 +3537,11 @@ tcl::namespace::eval textblock {
} else {
set table [overtype::renderspace -startcolumn [expr {$padwidth + 1}] -overflow 1 -experimental test_mode -transparent $TSUB $table $nextcol]
#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]
#JMN
#set nextcol [textblock::join [textblock::block $padwidth $height "\uFFFF"] $nextcol]
#set nextcol [textblock::join -- [textblock::block $padwidth $height "\uFFFF"] $nextcol]
#set table [overtype::renderspace -overflow 1 -experimental test_mode -transparent \uFFFF $table $nextcol]
}
incr padwidth $bodywidth
@ -3411,7 +3549,7 @@ tcl::namespace::eval textblock {
}
if {[llength $cols]} {
#return [textblock::join {*}$blocks]
#return [textblock::join -- {*}$blocks]
if {[tcl::dict::get $o_opts_table -show_edge]} {
#title is considered part of the edge ?
set offset 1 ;#make configurable?
@ -3517,7 +3655,7 @@ tcl::namespace::eval textblock {
set header_build [overtype::renderspace -startcolumn [expr {$padwidth + 1}] -overflow 1 -experimental test_mode -transparent $TSUB $header_build[unset header_build] $nextcol_header[unset nextcol_header]]
}
lappend body_blocks $nextcol_body
#set body_build [textblock::join $body_build[unset body_build] $nextcol_body]
#set body_build [textblock::join -- $body_build[unset body_build] $nextcol_body]
}
incr padwidth $bodywidth
incr colposn
@ -3605,18 +3743,29 @@ tcl::namespace::eval textblock {
proc spantest {} {
set t [list_as_table -columns 5 -return tableobject {a b c d e aa bb cc dd ee X Y}]
$t configure_column 0 -headers [list span3 "1-span4\n2-span4 second line" span5/5 "span-all etc blah 123 hmmmmm" span2]
$t configure_column 0 -header_colspans {3 4 5 all 2}
$t configure_column 0 -header_colspans {3 4 5 any 2}
$t configure_column 2 -headers {"" "" "" "" c2span2_etc}
$t configure_column 2 -header_colspans {0 0 0 0 2}
$t configure -show_header 1 -ansiborder_header [a+ cyan]
return $t
}
proc spantest1 {} {
set t [list_as_table -columns 5 -return tableobject {a b c d e aa bb cc dd ee X Y}]
$t configure_column 0 -headers [list "span3-any longer than other span any" "1-span4\n2-span4 second line" "span-any short aligned?" "span5/5 longest etc blah 123" span2]
$t configure_column 0 -header_colspans {any 4 any 5 2}
$t configure_column 2 -headers {"" "" "" "" c2span2_etc}
$t configure_column 2 -header_colspans {0 0 0 0 2}
$t configure_column 3 -header_colspans {1 0 0 0 0}
$t configure -show_header 1 -ansiborder_header [a+ cyan]
$t configure_column 0 -blockalign right ;#trigger KNOWN BUG overwrite of right edge by last char of spanning col (in this case fullspan from left - but also happens for any span extending to rhs)
return $t
}
#more complex colspans
proc spantest2 {} {
set t [list_as_table -columns 5 -return tableobject {a b c d e aa bb cc dd ee X Y}]
$t configure_column 0 -headers {c0span3 c0span4 c0span1 "c0span-all etc blah 123 hmmmmm" c0span2}
$t configure_column 0 -header_colspans {3 4 1 all 2}
$t configure_column 0 -header_colspans {3 4 1 any 2}
$t configure_column 1 -header_colspans {0 0 2 0 0}
$t configure_column 2 -headers {"" "" "" "" c2span2}
$t configure_column 2 -header_colspans {0 0 0 0 2}
@ -3625,9 +3774,9 @@ tcl::namespace::eval textblock {
return $t
}
proc spantest3 {} {
set t [list_as_table -columns 5 -return tableobjec {a b c d e aa bb cc dd ee X Y}]
set t [list_as_table -columns 5 -return tableobject {a b c d e aa bb cc dd ee X Y}]
$t configure_column 0 -headers {c0span3 c0span4 c0span1 "c0span-all etc blah 123 hmmmmm" "c0span2 etc blah" c0span1}
$t configure_column 0 -header_colspans {3 4 1 all 2 1}
$t configure_column 0 -header_colspans {3 4 1 any 2 1}
$t configure_column 1 -header_colspans {0 0 4 0 0 1}
$t configure_column 1 -headers {"" "" "c1span4" "" "" "c1nospan"}
$t configure_column 2 -headers {"" "" "" "" "" c2span2}
@ -4667,6 +4816,7 @@ tcl::namespace::eval textblock {
# Already uniform blocks will join faster than textblock::join, and ragged blocks will join in a ragged manner
#"
set argd [punk::args::get_dict {
-- -type none -optional 0 -help "end of options marker -- is mandatory because joined blocks may easily conflict with flags"
-ansiresets -type any -default auto
blocks -type any -multiple 1
} $args]
@ -4726,13 +4876,22 @@ tcl::namespace::eval textblock {
-ansiresets {
if {[lindex $args 2] eq "--"} {
set blocks [lrange $args 3 end]
set ansiresets [lindex $args 1]
} else {
set blocks [lrange $args 2 end]
error "end of opts marker -- is mandatory."
}
set ansiresets [lindex $args 1]
}
default {
set blocks $args
if {[catch {tcl::prefix::match {-ansiresets} [lindex $args 0]} fullopt]} {
error "first flag must be -ansiresets or end of opts marker --"
} else {
if {[lindex $args 2] eq "--"} {
set blocks [lrange $args 3 end]
set ansiresets [lindex $args 1]
} else {
error "end of opts marker -- is mandatory"
}
}
}
}
@ -4836,11 +4995,12 @@ tcl::namespace::eval textblock {
proc example3 {{text "test\netc\nmore text"}} {
package require patternpunk
.= textblock::join [punk::lib::list_as_lines -- [list 1 2 3 4 5 6 7]] [>punk . lhs] |> .=>1 textblock::join " " |> .=>1 textblock::join $text |> .=>1 textblock::join [>punk . rhs] |> .=>1 textblock::join [punk::lib::list_as_lines -- [lrepeat 7 " | "]]
.= textblock::join -- [punk::lib::list_as_lines -- [list 1 2 3 4 5 6 7]] [>punk . lhs] |> .=>1 textblock::join -- " " |> .=>1 textblock::join -- $text |> .=>1 textblock::join -- [>punk . rhs] |> .=>1 textblock::join -- [punk::lib::list_as_lines -- [lrepeat 7 " | "]]
}
proc example2 {{text "test\netc\nmore text"}} {
package require patternpunk
.= textblock::join\
--\
[punk::lib::list_as_lines -- [list 1 2 3 4 5 6 7 8]]\
[>punk . lhs]\
" "\
@ -4900,67 +5060,96 @@ tcl::namespace::eval textblock {
}
variable frametypes
set frametypes [list light heavy arc double block block1 ascii altg]
set frametypes [list light heavy arc double block block1 block2 ascii altg]
#class::table needs to be able to determine valid frametypes
proc frametypes {} {
variable frametypes
return $frametypes
}
proc frametype {f} {
variable frametypes
set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "]
#set custom_keys [list hl hlt hlb vl vll vlr tlc trc blc brc]
if {$f ni $frametypes} {
set is_custom_dict_ok 1
if {[llength $f] %2 == 0} {
#custom dict may leave out keys - but cannot have unknown keys
foreach {k v} $f {
switch -- $k {
hl - hlt - hlb - vl - vll - vlr - tlc - trc - blc - brc {}
default {
#k not in custom_keys
set is_custom_dict_ok 0
break
switch -- $f {
light - heavy - arc - double - block - block1 - block2 - ascii - altg {
return [tcl::dict::create category predefined type $f]
}
default {
set is_custom_dict_ok 1
if {[llength $f] %2 == 0} {
#custom dict may leave out keys - but cannot have unknown keys
foreach {k v} $f {
switch -- $k {
hl - hlt - hlb - vl - vll - vlr - tlc - trc - blc - brc {}
default {
#k not in custom_keys
set is_custom_dict_ok 0
break
}
}
}
} else {
set is_custom_dict_ok 0
}
} else {
set is_custom_dict_ok 0
}
if {!$is_custom_dict_ok} {
error "frame option -type must be one of known types: $frametypes or a dictionary with any of keys hl,hlt,hlb,vl,vll,vlr,tlc,trc,blc,brc"
if {!$is_custom_dict_ok} {
error "frame option -type must be one of known types: $textblock::frametypes or a dictionary with any of keys hl,hlt,hlb,vl,vll,vlr,tlc,trc,blc,brc"
}
set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "]
set custom_frame [tcl::dict::merge $default_custom $f]
return [tcl::dict::create category custom type $custom_frame]
}
set custom_frame [tcl::dict::merge $default_custom $f]
return [tcl::dict::create category custom type $custom_frame]
} else {
return [tcl::dict::create category predefined type $f]
}
}
variable framedef_cache [tcl::dict::create]
proc framedef {f args} {
proc framedef {args} {
#unicode box drawing only provides enough characters for seamless joining of unicode boxes light and heavy.
#e.g with characters such as \u2539 Box Drawings Right Light and Left Up Heavy.
#the double glyphs in box drawing can do a limited set of joins to light lines - but not enough for seamless table layouts.
#the arc set can't even join to itself e.g with curved equivalents of T-like shapes
#we use the simplest cache_key possible - performance sensitive as called multiple times in table building.
variable framedef_cache
set cache_key [concat $f $args]
set cache_key $args
if {[tcl::dict::exists $framedef_cache $cache_key]} {
return [tcl::dict::get $framedef_cache $cache_key]
}
set argopts [lrange $args 0 end-1]
set f [lindex $args end]
#here we avoid the punk::args usage on the happy path, even though punk::args is fairly fast, in favour of an even faster literal switch on the happy path
#this means we have some duplication in where our flags/opts are defined: here in opts, and in spec below to give nicer error output without affecting performance.
#It also means we can't specify checks on the option types etc
set opts [tcl::dict::create\
-joins ""\
-boxonly 0\
]
foreach {k v} $args {
set bad_option 0
foreach {k v} $argopts {
switch -- $k {
-joins - -boxonly {
tcl::dict::set opts $k $v
}
default {
error "framedef unknown option '$k'. Known options [tcl::dict::keys $opts]"
set bad_option
break
}
}
}
if {[llength $args] % 2 == 0 || $bad_option} {
#no framedef supplied, or unrecognised opt seen
set spec [string map [list <ftlist> $::textblock::frametypes] {
*proc -name textblock::framedef
-joins -default "" -help "List of join directions, any of: up down left right
or those combined with another frametype e.g left-heavy down-light"
-boxonly -default 0 -help "-boxonly true restricts results to the corner,vertical and horizontal box elements
It excludes the extra top and side join elements htlj,hlbj,vllj,vlrj"
*values -min 1 -max 1
frametype -help "name from the predefined frametypes:<ftlist>
or an adhoc
}]
append spec \n "frametype -help \"A predefined \""
punk::args::get_dict $spec $args
return
}
set joins [tcl::dict::get $opts -joins]
set boxonly [tcl::dict::get $opts -boxonly]
@ -5986,6 +6175,7 @@ tcl::namespace::eval textblock {
}
}
block1 {
#box drawing as far as we can go without extra blocks from legacy computing unicode block - which is unfortunately not commonly supported
set hlt \u2581 ;# lower one eighth block
set hlb \u2594 ;# upper one eighth block
set vll \u258f ;# left one eighth block
@ -6002,17 +6192,19 @@ tcl::namespace::eval textblock {
set vlrj $vlr
}
blockxx {
block2 {
#the resultant table will have text appear towards top of each box
#with 'legacy' computing unicode block - hopefully these become more widely supported - as they are useful and fill in some gaps
set hlt \u2594 ;# upper one eighth block
set hlb \u2581 ;# lower one eighth block
set vll \u2595 ;# right one eighth block
set vlr \u258f ;# left one eighth block
set vlr \u2595 ;# right one eighth block
set vll \u258f ;# left one eighth block
set tlc \u2595 ;# right one eighth block
set trc \u258f ;# left one eighth block
set tlc \U1fb7d ;#legacy block
set trc \U1fb7e ;#legacy block
set blc \u2595 ;# right one eighth block
set brc \u258f ;# left one eighth block
set blc \U1fb7c ;#legacy block
set brc \U1fb7f ;#legacy block
#horizontal and vertical bar joins
set hltj $hlt
@ -6039,36 +6231,36 @@ tcl::namespace::eval textblock {
set vlrj $vlr
}
default {
set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "]
set custom_frame [tcl::dict::merge $default_custom $f]
tcl::dict::with custom_frame {} ;#extract keys as vars
if {[tcl::dict::exists $custom_frame hlt]} {
set hlt [tcl::dict::get $custom_frame hlt]
} else {
set hlt $hl
set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "] ;#only default the general types - these form defaults for more specific types if they're missing
if {[llength $f] % 2 != 0} {
#todo - retrieve usage from punk::args
error "textblock::frametype frametype '$f' is not one of the predefined frametypes: $textblock::frametypes and does not appear to be a dictionary for a custom frametype"
}
if {[tcl::dict::exists $custom_frame hlb]} {
set hlb [tcl::dict::get $custom_frame hlb]
} else {
set hlb $hl
}
if {[tcl::dict::exists $custom_frame vll]} {
set vll [tcl::dict::get $custom_frame vll]
} else {
set vll $vl
#unknown order of keys specified by user - validate before creating vars as we need more general elements to be available as defaults
dict for {k v} $f {
switch -- $k {
hl - vl - tlc - trc - blc - brc - hlt - hlb - vll - vlr - hltj - hlbj - vllj - vlrj {}
default {
error "textblock::frametype '$f' has unknown element '$k'"
}
}
}
if {[tcl::dict::exists $custom_frame vlr]} {
set vlr [tcl::dict::get $custom_frame vlr]
} else {
set vlr $vl
#verified keys - safe to extract as vars
set custom_frame [tcl::dict::merge $default_custom $f]
tcl::dict::with custom_frame {} ;#extract keys as vars
#longer j vars must be after their more specific counterparts in the list being processed by foreach
foreach t {hlt hlb vll vlr hltj hlbj vllj vlrj} {
if {[tcl::dict::exists $custom_frame $t]} {
set $t [tcl::dict::get $custom_frame $t]
} else {
#set more explicit type to it's more general counterpart if it's missing
#e.g hlt -> hl
#e.g hltj -> hlt
set $t [set [string range $t 0 end-1]]
}
}
#horizontal and vertical bar joins
set hltj $hlt
set hlbj $hlb
set vllj $vll
set vlrj $vlr
#assert vars hl vl tlc trc blc brc hlt hlb vll vlr hltj hlbj vllj vlrj are all set
#horizontal and vertical bar joins - key/variable ends with 'j'
}
}
if {$boxonly} {
@ -6270,7 +6462,7 @@ tcl::namespace::eval textblock {
}
}
switch -- $target {
"" - light - heavy - ascii - altg - arc - double - custom - block - block1 {}
"" - light - heavy - ascii - altg - arc - double - custom - block - block1 - block2 {}
default {
set is_joins_ok 0
break
@ -6473,7 +6665,7 @@ tcl::namespace::eval textblock {
set vll_width 1 ;#default for all except custom (printing width)
set vlr_width 1
set framedef [textblock::framedef $framedef -joins $opt_joins]
set framedef [textblock::framedef -joins $opt_joins $framedef]
tcl::dict::with framedef {} ;#extract vll,hlt,tlc etc vars
#puts "---> $opt_boxmap"
@ -6932,9 +7124,9 @@ tcl::namespace::eval textblock {
#Test we can join two coloured blocks
proc test_colour {} {
set b1 [a= red]1\n2\n3[a=]
set b2 [a= green]a\nb\nc[a=]
set result [textblock::join $b1 $b2]
set b1 [a red]1\n2\n3[a]
set b2 [a green]a\nb\nc[a]
set result [textblock::join -- $b1 $b2]
puts $result
#return [list $b1 $b2 $result]
return [ansistring VIEW $result]

Loading…
Cancel
Save