Browse Source

vfs work, ansi fixes + modes crm, inverse

master
Julian Noble 4 months ago
parent
commit
4de01cf13f
  1. 1297
      src/bootsupport/modules/logger-0.9.5.tm
  2. 33
      src/bootsupport/modules/natsort-0.1.1.6.tm
  3. 1191
      src/bootsupport/modules/overtype-1.6.5.tm
  4. 176
      src/bootsupport/modules/punk/ansi-0.1.1.tm
  5. 17
      src/bootsupport/modules/punk/char-0.1.0.tm
  6. 107
      src/bootsupport/modules/punk/console-0.1.1.tm
  7. 2
      src/bootsupport/modules/punk/du-0.1.0.tm
  8. 236
      src/bootsupport/modules/punk/lib-0.1.1.tm
  9. 4
      src/bootsupport/modules/punk/mix/base-0.1.tm
  10. 148
      src/bootsupport/modules/punk/mix/cli-0.3.1.tm
  11. 30
      src/bootsupport/modules/punk/mix/commandset/doc-0.1.0.tm
  12. 11
      src/bootsupport/modules/punk/mix/commandset/loadedlib-0.1.0.tm
  13. 24
      src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm
  14. 10
      src/bootsupport/modules/punk/mix/commandset/project-0.1.0.tm
  15. 4
      src/bootsupport/modules/punk/overlay-0.1.tm
  16. 450
      src/bootsupport/modules/punk/path-0.1.0.tm
  17. 46
      src/bootsupport/modules/punk/repo-0.1.1.tm
  18. 155
      src/bootsupport/modules/punk/winpath-0.1.0.tm
  19. 209
      src/bootsupport/modules/textblock-0.1.1.tm
  20. 77
      src/make.tcl
  21. 187
      src/modules/punk/ansi-999999.0a1.0.tm
  22. 4
      src/modules/punk/basictelnet-999999.0a1.0.tm
  23. 358
      src/modules/punk/blockletter-999999.0a1.0.tm
  24. 3
      src/modules/punk/blockletter-buildversion.txt
  25. 62
      src/modules/punk/console-999999.0a1.0.tm
  26. 4
      src/modules/punk/experiment-999999.0a1.0.tm
  27. 8
      src/modules/punk/mix/commandset/project-999999.0a1.0.tm
  28. 12
      src/modules/punk/repl-0.1.tm
  29. 46
      src/modules/punk/repo-999999.0a1.0.tm
  30. 137
      src/modules/textblock-999999.0a1.0.tm
  31. 77
      src/project_layouts/custom/_project/punk.basic/src/make.tcl
  32. 74
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/fileutil/paths-1.tm
  33. 504
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/fileutil/traverse-0.6.tm
  34. 33
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/natsort-0.1.1.6.tm
  35. 1191
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/overtype-1.6.5.tm
  36. 176
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/ansi-0.1.1.tm
  37. 17
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/char-0.1.0.tm
  38. 107
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/console-0.1.1.tm
  39. 2
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/du-0.1.0.tm
  40. 236
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/lib-0.1.1.tm
  41. 4
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/base-0.1.tm
  42. 148
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/cli-0.3.1.tm
  43. 30
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/doc-0.1.0.tm
  44. 11
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/loadedlib-0.1.0.tm
  45. 24
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm
  46. 10
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/project-0.1.0.tm
  47. 4
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/overlay-0.1.tm
  48. 450
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/path-0.1.0.tm
  49. 46
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repo-0.1.1.tm
  50. 155
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/winpath-0.1.0.tm
  51. 209
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/textblock-0.1.1.tm
  52. 77
      src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl
  53. 74
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/fileutil/paths-1.tm
  54. 504
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/fileutil/traverse-0.6.tm
  55. 33
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/natsort-0.1.1.6.tm
  56. 1191
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/overtype-1.6.5.tm
  57. 176
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/ansi-0.1.1.tm
  58. 17
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/char-0.1.0.tm
  59. 107
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/console-0.1.1.tm
  60. 2
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/du-0.1.0.tm
  61. 236
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/lib-0.1.1.tm
  62. 4
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/base-0.1.tm
  63. 148
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/cli-0.3.1.tm
  64. 30
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/doc-0.1.0.tm
  65. 11
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/loadedlib-0.1.0.tm
  66. 24
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm
  67. 10
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/project-0.1.0.tm
  68. 4
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/overlay-0.1.tm
  69. 450
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/path-0.1.0.tm
  70. 46
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repo-0.1.1.tm
  71. 155
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/winpath-0.1.0.tm
  72. 209
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/textblock-0.1.1.tm
  73. 77
      src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl
  74. 26
      src/runtime/mapvfs.config
  75. 1993
      src/vendormodules/overtype-1.6.5.tm
  76. BIN
      src/vendormodules/test/tomlish-1.1.1.tm
  77. 1993
      src/vfs/_vfscommon/modules/overtype-1.6.5.tm
  78. 187
      src/vfs/_vfscommon/modules/punk/ansi-0.1.1.tm
  79. 4
      src/vfs/_vfscommon/modules/punk/basictelnet-0.1.0.tm
  80. 358
      src/vfs/_vfscommon/modules/punk/blockletter-0.1.0.tm
  81. 62
      src/vfs/_vfscommon/modules/punk/console-0.1.1.tm
  82. 4
      src/vfs/_vfscommon/modules/punk/experiment-0.1.0.tm
  83. 8
      src/vfs/_vfscommon/modules/punk/mix/commandset/project-0.1.0.tm
  84. 12
      src/vfs/_vfscommon/modules/punk/repl-0.1.tm
  85. 46
      src/vfs/_vfscommon/modules/punk/repo-0.1.1.tm
  86. BIN
      src/vfs/_vfscommon/modules/test/tomlish-1.1.1.tm
  87. 137
      src/vfs/_vfscommon/modules/textblock-0.1.1.tm
  88. 111
      src/vfs/critcl.vfs/README.md
  89. 20
      src/vfs/critcl.vfs/doc/checklist.txt
  90. 73
      src/vfs/critcl.vfs/doc/critcl.man
  91. 45
      src/vfs/critcl.vfs/doc/critcl_application.man
  92. 62
      src/vfs/critcl.vfs/doc/critcl_application_package.man
  93. 161
      src/vfs/critcl.vfs/doc/critcl_bitmap.man
  94. 17
      src/vfs/critcl.vfs/doc/critcl_build.man
  95. 196
      src/vfs/critcl.vfs/doc/critcl_callback.man
  96. 16
      src/vfs/critcl.vfs/doc/critcl_changes.man
  97. 57
      src/vfs/critcl.vfs/doc/critcl_class.man
  98. 40
      src/vfs/critcl.vfs/doc/critcl_cproc.man
  99. 413
      src/vfs/critcl.vfs/doc/critcl_cutil.man
  100. 228
      src/vfs/critcl.vfs/doc/critcl_devguide.man
  101. Some files were not shown because too many files have changed in this diff Show More

1297
src/bootsupport/modules/logger-0.9.5.tm

File diff suppressed because it is too large Load Diff

33
src/bootsupport/modules/natsort-0.1.1.6.tm

@ -5,8 +5,9 @@ package require flagfilter
namespace import ::flagfilter::check_flags namespace import ::flagfilter::check_flags
namespace eval natsort { namespace eval natsort {
#REVIEW - determine and document the purpose of scriptdir being added to tm path
proc scriptdir {} { proc scriptdir {} {
set possibly_linked_script [file dirname [file normalize [file join [info script] ...]]] set possibly_linked_script [file dirname [file normalize [file join [info script] __dummy__]]]
if {[file isdirectory $possibly_linked_script]} { if {[file isdirectory $possibly_linked_script]} {
return $possibly_linked_script return $possibly_linked_script
} else { } else {
@ -14,7 +15,11 @@ namespace eval natsort {
} }
} }
if {![interp issafe]} { if {![interp issafe]} {
tcl::tm::add [scriptdir] set sdir [scriptdir]
#puts stderr "natsort tcl::tm::add $sdir"
if {$sdir ni [tcl::tm::list]} {
catch {tcl::tm::add $sdir}
}
} }
} }
@ -36,6 +41,7 @@ namespace eval natsort {
} else { } else {
puts stderr "|>natsort_call_err> unable to interpret 2nd argument to do_error: '$then' should be one of '$levels' or 'exit <numericcode>'" puts stderr "|>natsort_call_err> unable to interpret 2nd argument to do_error: '$then' should be one of '$levels' or 'exit <numericcode>'"
} }
flush stderr
if {$::tcl_interactive} { if {$::tcl_interactive} {
#may not always be desirable - but assumed to be more useful not to exit despite request, to aid in debugging #may not always be desirable - but assumed to be more useful not to exit despite request, to aid in debugging
if {[string tolower $type] eq "exit"} { if {[string tolower $type] eq "exit"} {
@ -43,6 +49,7 @@ namespace eval natsort {
if {![string is digit -strict $code]} { if {![string is digit -strict $code]} {
puts stderr "|>natsort_call_err> unable to interpret 2nd argument to do_error: '$then' should be: 'exit <numericcode>'" puts stderr "|>natsort_call_err> unable to interpret 2nd argument to do_error: '$then' should be: 'exit <numericcode>'"
} }
flush stderr
} }
return -code error $msg return -code error $msg
} else { } else {
@ -1422,6 +1429,9 @@ namespace eval natsort {
proc called_directly_namematch {} { proc called_directly_namematch {} {
global argv0 global argv0
if {[info script] eq ""} {
return 0
}
#see https://wiki.tcl-lang.org/page/main+script #see https://wiki.tcl-lang.org/page/main+script
#trailing ... let's us resolve symlinks in last component of the path (could be something else like ___ but ... seems unlikely to collide with anything in the filesystem) #trailing ... let's us resolve symlinks in last component of the path (could be something else like ___ but ... seems unlikely to collide with anything in the filesystem)
if {[info exists argv0] if {[info exists argv0]
@ -1440,12 +1450,18 @@ namespace eval natsort {
#Review issues around comparing names vs using inodes (esp with respect to samba shares) #Review issues around comparing names vs using inodes (esp with respect to samba shares)
proc called_directly_inodematch {} { proc called_directly_inodematch {} {
global argv0 global argv0
if {[info exists argv0] if {[info exists argv0]
&& [file exists [info script]] && [file exists $argv0]} { && [file exists [info script]] && [file exists $argv0]} {
file stat $argv0 argv0Info file stat $argv0 argv0Info
file stat [info script] scriptInfo file stat [info script] scriptInfo
expr {$argv0Info(dev) == $scriptInfo(dev) if {$argv0Info(ino) == 0 || $scriptInfo(ino) == 0 || $argv0Info(dev) == 0 || $scriptInfo(dev) == 0} {
&& $argv0Info(ino) == $scriptInfo(ino)} #vfs?
#e.g //zipfs:/
return 0
}
return [expr {$argv0Info(dev) == $scriptInfo(dev)
&& $argv0Info(ino) == $scriptInfo(ino)}]
} else { } else {
return 0 return 0
} }
@ -1460,6 +1476,11 @@ namespace eval natsort {
#-- choose a policy and leave the others commented. #-- choose a policy and leave the others commented.
#set is_called_directly $is_namematch #set is_called_directly $is_namematch
#set is_called_directly $is_inodematch #set is_called_directly $is_inodematch
#puts "NATSORT: called_directly_namematch - $is_namematch"
#puts "NATSORT: called_directly_inodematch - $is_inodematch"
#flush stdout
set is_called_directly [expr {$is_namematch || $is_inodematch}] set is_called_directly [expr {$is_namematch || $is_inodematch}]
#set is_called_directly [expr {$is_namematch && $is_inodematch}] #set is_called_directly [expr {$is_namematch && $is_inodematch}]
### ###
@ -1921,6 +1942,8 @@ namespace eval natsort {
#set args [check_flags -caller test1 -defaults [list -collate nocase -algorithm sort -topchars "\uFFFF" -winlike 0 -debug 0 -stacktrace 0 -showsplits 0] -required {all} -extras {none} -commandprocessors {{-cmd -default help} {-cmd -cmdarg1 -default "."} {-cmd -cmdarg2 -default j}} -values $::argv ] #set args [check_flags -caller test1 -defaults [list -collate nocase -algorithm sort -topchars "\uFFFF" -winlike 0 -debug 0 -stacktrace 0 -showsplits 0] -required {all} -extras {none} -commandprocessors {{-cmd -default help} {-cmd -cmdarg1 -default "."} {-cmd -cmdarg2 -default j}} -values $::argv ]
#set args [check_flags -caller test1 -defaults [list -collate nocase -algorithm sort -topchars "\uFFFF" -winlike 0 -debug 0 -stacktrace 0 -showsplits 0] -required {all} -extras {none} -commandprocessors {ls {ls lsdir -default "\uFFFF"}} -values $::argv ] #set args [check_flags -caller test1 -defaults [list -collate nocase -algorithm sort -topchars "\uFFFF" -winlike 0 -debug 0 -stacktrace 0 -showsplits 0] -required {all} -extras {none} -commandprocessors {ls {ls lsdir -default "\uFFFF"}} -values $::argv ]
puts stderr "natsort directcall exit"
flush stderr
exit 0 exit 0
if {$::argc} { if {$::argc} {

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

File diff suppressed because it is too large Load Diff

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

@ -553,28 +553,51 @@ tcl::namespace::eval punk::ansi {
$obj destroy $obj destroy
return $result return $result
} }
proc example {} { proc example {args} {
set base [punk::repo::find_project]
set default_ansibase [file join $base src/testansi]
set argd [punk::args::get_dict [tstr -return string {
*proc -name punk::ansi::example -help "Display .ans image files in a grid that will fit in console
"
-colwidth -default 82 -help "Width of each column - default of 82 will fit a standard 80wide ansi image (when framed)
You can specify a narrower width to truncate images on the right side"
-folder -default "${$default_ansibase}" -help "Base folder for files if relative paths are used.
Defaults to <projectbase>/src/testansi - where projectbase is determined from current directory.
"
*values -min 0 -max -1
files -default {belinda.ans bot.ans flower.ans fish.ans} -multiple true -help "List of filenames - leave empty to display 4 defaults"
}] $args]
set colwidth [dict get $argd opts -colwidth]
set ansibase [file normalize [dict get $argd opts -folder]]
set fnames [dict get $argd values files]
#assumes fixed column widths e.g 80col images will fit in 82-width frames (common standard for old ansi art) (of arbitrary height)
#todo - review dependency on punk::repo ? #todo - review dependency on punk::repo ?
package require textblock package require textblock
package require punk::repo package require punk::repo
package require punk::console package require punk::console
set fnames [list belinda.ans bot.ans flower.ans fish.ans]
set base [punk::repo::find_project]
set ansibase [file join $base src/testansi]
if {![file exists $ansibase]} { if {![file exists $ansibase]} {
puts stderr "Missing testansi folder at $base/src/testansi" puts stderr "Missing folder at $ansibase"
puts stderr "Ensure ansi test files exist: $fnames" puts stderr "Ensure ansi test files exist: $fnames"
#error "punk::ansi::example Cannot find example files" #error "punk::ansi::example Cannot find example files"
} }
set missingbase [a+ yellow][textblock::block 80 23 ?][a] set missingbase [a+ yellow][textblock::block [expr {$colwidth-2}] 23 ?][a] ;#assuming standard frame - subtract 2 for left/right borders
set pics [list] set pics [list]
foreach f $fnames { foreach f $fnames {
if {![file exists $ansibase/$f]} { if {[file pathtype $f] ne "absolute"} {
set p [overtype::left $missingbase "[a+ red bold]\nMissing file\n$ansibase/$f[a]"] set filepath [file normalize $ansibase/$f]
} else {
set filepath [file normalize $f]
}
if {![file exists $filepath]} {
set p [overtype::left $missingbase "[a+ red bold]\nMissing file\n$f[a]"]
lappend pics [tcl::dict::create filename $f pic $p status missing] lappend pics [tcl::dict::create filename $f pic $p status missing]
} else { } else {
set img [join [lines_as_list -line trimline -block trimtail [ansicat $ansibase/$f]] \n] #set img [join [lines_as_list -line trimline -block trimtail [ansicat $filepath]] \n]
#-line trimline will wreck some images
set img [join [lines_as_list -block trimtail [ansicat $filepath]] \n]
lappend pics [tcl::dict::create filename $f pic $img status ok] lappend pics [tcl::dict::create filename $f pic $img status ok]
} }
} }
@ -582,30 +605,73 @@ tcl::namespace::eval punk::ansi {
set termsize [punk::console:::get_size] set termsize [punk::console:::get_size]
set margin 4 set margin 4
set freewidth [expr {[tcl::dict::get $termsize columns]-$margin}] set freewidth [expr {[tcl::dict::get $termsize columns]-$margin}]
set per_row [expr {$freewidth / 80}] set per_row [expr {$freewidth / $colwidth}]
set rowlist [list] set rowlist [list] ;# { {<img> <img>} {<img> <img>} }
set row [list] set heightlist [list] ;# { {<h> <h> } {<h> <h> } }
set i 1 set maxheights [list] ;# { <max> <max>}
set row [list] ;#wip row
set rowh [list] ;#wip row img heights
set i 1 ;#track image index of whole pics list
set rowindex 0
foreach picinfo $pics { foreach picinfo $pics {
set subtitle "" set subtitle ""
if {[tcl::dict::get $picinfo status] ne "ok"} { if {[tcl::dict::get $picinfo status] ne "ok"} {
set subtitle [tcl::dict::get $picinfo status] set subtitle [tcl::dict::get $picinfo status]
} }
set title [tcl::dict::get $picinfo filename] set title [tcl::dict::get $picinfo filename]
lappend row [textblock::frame -subtitle $subtitle -title $title [tcl::dict::get $picinfo pic]] set fr [textblock::frame -width $colwidth -subtitle $subtitle -title $title [tcl::dict::get $picinfo pic]]
# -- --- --- ---
#we need the max height of a row element to use join_basic instead of join below
# -- --- --- ---
set fr_height [textblock::height $fr]
lappend row $fr
lappend rowh $fr_height
set rowmax [lindex $maxheights $rowindex]
if {$rowmax eq ""} {
#empty result means no maxheights entry for this row yet
set rowmax $fr_height
lappend maxheights $rowmax
} else {
if {$fr_height > $rowmax} {
set rowmax $fr_height
lset maxheights end $rowmax
}
}
# -- --- --- ---
if {$i % $per_row == 0} { if {$i % $per_row == 0} {
lappend rowlist $row lappend rowlist $row
lappend heightlist $rowh
incr rowindex
set row [list] set row [list]
set rowh [list]
} elseif {$i == [llength $pics]} { } elseif {$i == [llength $pics]} {
lappend rowlist $row lappend rowlist $row
lappend heightlist $rowh
} }
incr i incr i
} }
#puts "--> maxheights: $maxheights"
#puts "--> heightlist: $heightlist"
set result "" set result ""
foreach r $rowlist { set rowindex 0
append result [textblock::join_basic -- {*}$r] \n set blankline [string repeat " " $colwidth]
foreach imgs $rowlist heights $heightlist {
set maxheight [lindex $maxheights $rowindex]
set adjusted_row [list]
foreach i $imgs h $heights {
if {$h < $maxheight} {
#add blank lines to bottom of shorter images so join_basic can be used.
#textblock::join of ragged-height images would work and remove the need for all the height calculation
#.. but it requires much more processing
append i [string repeat \n$blankline [expr {$maxheight - $h}]]
}
lappend adjusted_row $i
}
append result [textblock::join_basic -- {*}$adjusted_row] \n
incr rowindex
} }
@ -3199,6 +3265,28 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
return \x1b8 return \x1b8
} }
# -- --- --- --- --- # -- --- --- --- ---
#CRM Show Control Character Mode
proc enable_crm {} {
return \x1b\[3h
}
proc disable_crm {} {
return \x1b\[3l
}
#DECSNM
#Note this can invert the enclosed section including any already reversed by SGR 7 - depending on terminal support.
#e.g
#set test [a+ reverse]aaa[a+ noreverse]bbb
# - $test above can't just be reversed by putting another [a+ reverse] in front of it.
# - but the following will work (even if underlying terminal doesn't support ?5 sequences)
#overtype::renderspace -width 20 [enable_inverse]$test
proc enable_inverse {} {
return \x1b\[?5h
}
proc disable_inverse {} {
return \x1b\[?5l
}
#DECAWM - automatic line wrapping #DECAWM - automatic line wrapping
proc enable_line_wrap {} { proc enable_line_wrap {} {
@ -3399,6 +3487,9 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#the purpose of backspace (or line cr) in embedded text is unclear. Should it allow some sort of character combining/overstrike as it has sometimes done historically (nroff/less)? e.g a\b` as an alternative combiner or bolding if same char #the purpose of backspace (or line cr) in embedded text is unclear. Should it allow some sort of character combining/overstrike as it has sometimes done historically (nroff/less)? e.g a\b` as an alternative combiner or bolding if same char
#This should presumably only be done if the over_strike (os) capability is enabled in the terminal. Either way - it presumably won't affect printing width? #This should presumably only be done if the over_strike (os) capability is enabled in the terminal. Either way - it presumably won't affect printing width?
set line [punk::ansi::ansistrip $line] set line [punk::ansi::ansistrip $line]
#ANSI (e.g PM/SOS) can contain \b or \n or \t but won't contribute to length
#ansistrip must come before any other processing of these chars.
#we can't use simple \b processing if we get ansi codes and aren't actually processing them (e.g moves) #we can't use simple \b processing if we get ansi codes and aren't actually processing them (e.g moves)
set line [punk::char::strip_nonprinting_ascii $line] ;#only strip nonprinting after ansistrip - some like BEL are part of ansi set line [punk::char::strip_nonprinting_ascii $line] ;#only strip nonprinting after ansistrip - some like BEL are part of ansi
@ -3748,6 +3839,7 @@ tcl::namespace::eval punk::ansi {
-filter_fg 0\ -filter_fg 0\
-filter_bg 0\ -filter_bg 0\
-filter_reset 0\ -filter_reset 0\
-info 0\
] ]
#codes *must* already have been split so that one esc per element in codelist #codes *must* already have been split so that one esc per element in codelist
@ -3760,7 +3852,8 @@ tcl::namespace::eval punk::ansi {
set opts $defaultopts_sgr_merge_singles set opts $defaultopts_sgr_merge_singles
foreach {k v} $args { foreach {k v} $args {
switch -- $k { switch -- $k {
-filter_fg - -filter_bg - -filter_reset { -filter_fg - -filter_bg - -filter_reset -
-info {
tcl::dict::set opts $k $v tcl::dict::set opts $k $v
} }
default { default {
@ -4139,19 +4232,24 @@ tcl::namespace::eval punk::ansi {
set codemerge [tcl::string::trimright $codemerge {;}] set codemerge [tcl::string::trimright $codemerge {;}]
if {$unmergeable ne ""} { if {$unmergeable ne ""} {
set unmergeable [tcl::string::trimright $unmergeable {;}] set unmergeable [tcl::string::trimright $unmergeable {;}]
return "\x1b\[${codemerge}m\x1b\[${unmergeable}m[join $othercodes ""]" set mergeresult "\x1b\[${codemerge}m\x1b\[${unmergeable}m[join $othercodes ""]"
} else { } else {
return "\x1b\[${codemerge}m[join $othercodes ""]" set mergeresult "\x1b\[${codemerge}m[join $othercodes ""]"
} }
} else { } else {
if {$unmergeable eq ""} { if {$unmergeable eq ""} {
#there were no SGR codes - not even resets #there were no SGR codes - not even resets
return [join $othercodes ""] set mergeresult [join $othercodes ""]
} else { } else {
set unmergeable [tcl::string::trimright $unmergeable {;}] set unmergeable [tcl::string::trimright $unmergeable {;}]
return "\x1b\[${unmergeable}m[join $othercodes ""]" set mergeresult "\x1b\[${unmergeable}m[join $othercodes ""]"
} }
} }
if {[tcl::dict::get $opts -info]} {
return [dict create sgr $codemerge unmergeable $unmergeable othercodes $othercodes mergeresult $mergeresult codestate $codestate]
} else {
return $mergeresult
}
} }
#has_sgr_reset - rather than support this function - create an sgr normalize function that removes dead params and brings reset to front of param list? #has_sgr_reset - rather than support this function - create an sgr normalize function that removes dead params and brings reset to front of param list?
@ -4240,7 +4338,7 @@ tcl::namespace::eval punk::ansi::ta {
#we also need to track the start of ST terminated code and not add it for replay (in the ansistring functions) #we also need to track the start of ST terminated code and not add it for replay (in the ansistring functions)
#variable re_ST {(?:\x1bX|\u0098|\x1b\^|\u009E|\x1b_|\u009F)(?:[^\x1b\007\u009c]*)(?:\x1b\\|\007|\u009c)} ;#downsides: early terminating with nests, mixes 7bit 8bit start/ends (does that exist in the wild?) #variable re_ST {(?:\x1bX|\u0098|\x1b\^|\u009E|\x1b_|\u009F)(?:[^\x1b\007\u009c]*)(?:\x1b\\|\007|\u009c)} ;#downsides: early terminating with nests, mixes 7bit 8bit start/ends (does that exist in the wild?)
#keep our 8bit/7bit start-end codes separate #keep our 8bit/7bit start-end codes separate
variable re_ST {(?:\x1bP|\x1bX|\x1b\^|\x1b_)(?:(?!\x1b\\|007).)*(?:\x1b\\|\007)|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)} variable re_ST {(?:\x1bP|\x1bX|\x1b\^|\x1b_)(?:(?!\x1b\\|\007).)*(?:\x1b\\|\007)|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)}
@ -4252,7 +4350,7 @@ tcl::namespace::eval punk::ansi::ta {
# -- --- --- --- # -- --- --- ---
#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.
variable re_ansi_detect {(?:\x1b(?:\((?:0|B)|\[(?:[\x20-\x2f\x30-\x3f]*[\x40-\x7e])|\](?:(?:[^\007]*)\007|(?:(?!\x1b\\).)*\x1b\\)|(?:P|X|\^|_)(?:(?:(?!\x1b\\|007).)*(?:\x1b\\|\007))|c|7|8|M|E|D|H|=|>|(?:#(?:3|4|5|6|8))))|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)|(?:\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]|(?:\u009d)(?:[^\u009c]*)?\u009c} variable re_ansi_detect {(?:\x1b(?:\((?:0|B)|\[(?:[\x20-\x2f\x30-\x3f]*[\x40-\x7e])|\](?:(?:[^\007]*)\007|(?:(?!\x1b\\).)*\x1b\\)|(?:P|X|\^|_)(?:(?:(?!\x1b\\|\007).)*(?:\x1b\\|\007))|c|7|8|M|E|D|H|=|>|(?:#(?:3|4|5|6|8))))|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)|(?:\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]|(?:\u009d)(?:[^\u009c]*)?\u009c}
# -- --- --- --- # -- --- --- ---
@ -5674,7 +5772,12 @@ tcl::namespace::eval punk::ansi::ansistring {
ENQ [list \x05 \u2405]\ ENQ [list \x05 \u2405]\
ACK [list \x06 \u2406]\ ACK [list \x06 \u2406]\
BEL [list \x07 \u2407]\ BEL [list \x07 \u2407]\
BS [list \x08 \u2408]\
HT [list \x09 \u2409]\
LF [list \x0a \u240a]\
VT [list \x0b \u240b]\
FF [list \x0c \u240c]\ FF [list \x0c \u240c]\
CR [list \x0d \u240d]\
SO [list \x0e \u240e]\ SO [list \x0e \u240e]\
SF [list \x0f \u240f]\ SF [list \x0f \u240f]\
DLE [list \x10 \u2410]\ DLE [list \x10 \u2410]\
@ -5688,12 +5791,15 @@ tcl::namespace::eval punk::ansi::ansistring {
CAN [list \x18 \u2418]\ CAN [list \x18 \u2418]\
EM [list \x19 \u2419]\ EM [list \x19 \u2419]\
SUB [list \x1a \u241a]\ SUB [list \x1a \u241a]\
ESC [list \x1b \u241b]\
FS [list \x1c \u241c]\ FS [list \x1c \u241c]\
GS [list \x1d \u241d]\ GS [list \x1d \u241d]\
RS [list \x1e \u241e]\ RS [list \x1e \u241e]\
US [list \x1f \u241f]\ US [list \x1f \u241f]\
SP [list \x20 \u2420]\
DEL [list \x7f \u2421]\ DEL [list \x7f \u2421]\
] ]
#alternate symbols for space #alternate symbols for space
# \u2422 Blank Symbol (b with forwardslash overly) # \u2422 Blank Symbol (b with forwardslash overly)
# \u2423 Open Box (square bracket facing up like a tray/box) # \u2423 Open Box (square bracket facing up like a tray/box)
@ -5836,6 +5942,7 @@ tcl::namespace::eval punk::ansi::ansistring {
-cr 1\ -cr 1\
-lf 0\ -lf 0\
-vt 0\ -vt 0\
-ff 1\
-ht 1\ -ht 1\
-bs 1\ -bs 1\
-sp 1\ -sp 1\
@ -5850,16 +5957,22 @@ tcl::namespace::eval punk::ansi::ansistring {
set opt_cr [tcl::dict::get $opts -cr] set opt_cr [tcl::dict::get $opts -cr]
set opt_lf [tcl::dict::get $opts -lf] set opt_lf [tcl::dict::get $opts -lf]
set opt_vt [tcl::dict::get $opts -vt] set opt_vt [tcl::dict::get $opts -vt]
set opt_ff [tcl::dict::get $opts -ff]
set opt_ht [tcl::dict::get $opts -ht] set opt_ht [tcl::dict::get $opts -ht]
set opt_bs [tcl::dict::get $opts -bs] set opt_bs [tcl::dict::get $opts -bs]
set opt_sp [tcl::dict::get $opts -sp] set opt_sp [tcl::dict::get $opts -sp]
# -- --- --- --- --- # -- --- --- --- ---
# -lf 2, -vt 2 and -ff 2 are useful for CRM mode (Show Control Character Mode) in the terminal - where a newline is expected to display after the character.
set visuals_opt $debug_visuals set visuals_opt $debug_visuals
set visuals_opt [dict remove $visuals_opt CR ESC LF VT FF HT BS SP]
if {$opt_esc} { if {$opt_esc} {
tcl::dict::set visuals_opt ESC [list \x1b \u241b] tcl::dict::set visuals_opt ESC [list \x1b \u241b]
} else {
tcl::dict::unset visuals_opt ESC
} }
if {$opt_cr} { if {$opt_cr} {
tcl::dict::set visuals_opt CR [list \x0d \u240d] tcl::dict::set visuals_opt CR [list \x0d \u240d]
@ -5870,9 +5983,20 @@ tcl::namespace::eval punk::ansi::ansistring {
if {$opt_lf == 2} { if {$opt_lf == 2} {
tcl::dict::set visuals_opt LF [list \x0a \u240a\n] tcl::dict::set visuals_opt LF [list \x0a \u240a\n]
} }
if {$opt_vt} { if {$opt_vt == 1} {
tcl::dict::set visuals_opt VT [list \x0b \u240b] tcl::dict::set visuals_opt VT [list \x0b \u240b]
} }
if {$opt_vt == 2} {
tcl::dict::set visuals_opt VT [list \x0b \u240b\n]
}
switch -exact -- $opt_ff {
1 {
tcl::dict::set visuals_opt FF [list \x0c \u240c]
}
2 {
tcl::dict::set visuals_opt FF [list \x0c \u240c\n]
}
}
if {$opt_ht} { if {$opt_ht} {
tcl::dict::set visuals_opt HT [list \x09 \u2409] tcl::dict::set visuals_opt HT [list \x09 \u2409]
} }

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

@ -552,13 +552,26 @@ tcl::namespace::eval punk::char {
string eq $s [encoding convertto $encname [encoding convertfrom $encname $s]] string eq $s [encoding convertto $encname [encoding convertfrom $encname $s]]
} }
} else { } else {
#review - use -profile?
proc encodable "s {enc [encoding system]}" { proc encodable "s {enc [encoding system]}" {
set encname [encname $enc] set encname [encname $enc]
string eq $s [encoding convertfrom $encname [encoding convertto $encname $s]] if {![catch {
string eq $s [encoding convertfrom $encname [encoding convertto $encname $s]]
} result]} {
return $result
} else {
return 0
}
} }
proc decodable "s {enc [encoding system]}" { proc decodable "s {enc [encoding system]}" {
set encname [encname $enc] set encname [encname $enc]
string eq $s [encoding convertto $encname [encoding convertfrom $encname $s]] if {![catch {
string eq $s [encoding convertto $encname [encoding convertfrom $encname $s]]
} result]} {
return $result
} else {
return 0
}
} }
} }
#-- --- --- --- --- --- --- --- #-- --- --- --- --- --- --- ---

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

@ -13,11 +13,51 @@
# @@ Meta End # @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[manpage_begin punkshell_module_punk::console 0 0.1.1]
#[copyright "2024"]
#[titledesc {punk console}] [comment {-- Name section and table of contents description --}]
#[moddesc {punk console}] [comment {-- Description at end of page heading --}]
#[require punk::console]
#[keywords module console terminal]
#[description]
#[para]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Overview]
#[para] overview of punk::console
#[subsection Concepts]
#[para]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements ## Requirements
##e.g package require frobz # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection dependencies]
#[para] packages used by punk::console
#[list_begin itemized]
package require Tcl 8.6-
package require punk::ansi package require punk::ansi
#*** !doctools
#[item] [package {Tcl 8.6-}]
#[item] [package {punk::ansi}]
#*** !doctools
#[list_end]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section API]
#if {"windows" eq $::tcl_platform(platform)} { #if {"windows" eq $::tcl_platform(platform)} {
@ -30,6 +70,13 @@ package require punk::ansi
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval punk::console { namespace eval punk::console {
#*** !doctools
#[subsection {Namespace punk::console}]
#[para]
#*** !doctools
#[list_begin definitions]
variable tabwidth 8 ;#default only - will attempt to detect and set to that configured in terminal variable tabwidth 8 ;#default only - will attempt to detect and set to that configured in terminal
#Note that windows terminal cooked mode seems to use 8 for interactive use even if set differently #Note that windows terminal cooked mode seems to use 8 for interactive use even if set differently
#e.g typing tab characters may still be echoed 8-spaced while writing to stdout my obey the terminal's tab stops. #e.g typing tab characters may still be echoed 8-spaced while writing to stdout my obey the terminal's tab stops.
@ -1028,23 +1075,37 @@ namespace eval punk::console {
return [split [get_cursor_pos $inoutchannels] ";"] return [split [get_cursor_pos $inoutchannels] ";"]
} }
#todo - determine cursor on/off state before the call to restore properly. May only be possible #todo - determine cursor on/off state before the call to restore properly.
proc get_size {{inoutchannels {stdin stdout}}} { proc get_size {{inoutchannels {stdin stdout}}} {
lassign $inoutchannels in out lassign $inoutchannels in out
#we can't reliably use [chan names] for stdin,stdout. There could be stacked channels and they may have a names such as file22fb27fe810 #we can't reliably use [chan names] for stdin,stdout. There could be stacked channels and they may have a names such as file22fb27fe810
#chan eof is faster whether chan exists or not than #chan eof is faster whether chan exists or not than
if {[catch {chan eof $in} is_eof]} { if {[catch {chan eof $out} is_eof]} {
error "punk::console::get_size input channel $in seems to be closed ([info level 1])" error "punk::console::get_size output channel $out seems to be closed ([info level 1])"
} else { } else {
if {$is_eof} { if {$is_eof} {
error "punk::console::get_size eof on input channel $in ([info level 1])" error "punk::console::get_size eof on output channel $out ([info level 1])"
} }
} }
if {[catch {chan eof $out} is_eof]} { #we don't need to care about the input channel if chan configure on the output can give us the info.
error "punk::console::get_size output channel $out seems to be closed ([info level 1])" #short circuit ansi cursor movement method if chan configure supports the -winsize value
set outconf [chan configure $out]
if {[dict exists $outconf -winsize]} {
#this mechanism is much faster than ansi cursor movements
#REVIEW check if any x-platform anomalies with this method?
#can -winsize key exist but contain erroneous info? We will check that we get 2 ints at least
lassign [dict get $outconf -winsize] cols lines
if {[string is integer -strict $cols] && [string is integer -strict $lines]} {
return [list columns $cols rows $lines]
}
#continue on to ansi mechanism if we didn't get 2 ints
}
if {[catch {chan eof $in} is_eof]} {
error "punk::console::get_size input channel $in seems to be closed ([info level 1])"
} else { } else {
if {$is_eof} { if {$is_eof} {
error "punk::console::get_size eof on output channel $out ([info level 1])" error "punk::console::get_size eof on input channel $in ([info level 1])"
} }
} }
@ -1067,18 +1128,28 @@ namespace eval punk::console {
} }
} }
#faster - but uses cursor_save - which we may want to avoid if calling during another operation which uses cursor save/restore #faster than get_size when it is using ansi mechanism - but uses cursor_save - which we may want to avoid if calling during another operation which uses cursor save/restore
proc get_size_cursorrestore {} { proc get_size_cursorrestore {{inoutchannels {stdin stdout}}} {
lassign $inoutchannels in out
#we use the same shortcircuit mechanism as get_size to avoid ansi at all if the output channel will give us the info directly
set outconf [chan configure $out]
if {[dict exists $outconf -winsize]} {
lassign [dict get $outconf -winsize] cols lines
if {[string is integer -strict $cols] && [string is integer -strict $lines]} {
return [list columns $cols rows $lines]
}
}
if {[catch { if {[catch {
#some terminals (conemu on windows) scroll the viewport when we make a big move down like this - a move to 1 1 immediately after cursor_save doesn't seem to fix that. #some terminals (conemu on windows) scroll the viewport when we make a big move down like this - a move to 1 1 immediately after cursor_save doesn't seem to fix that.
#This issue also occurs when switching back from the alternate screen buffer - so perhaps that needs to be addressed elsewhere. #This issue also occurs when switching back from the alternate screen buffer - so perhaps that needs to be addressed elsewhere.
puts -nonewline [punk::ansi::cursor_off][punk::ansi::cursor_save_dec][punk::ansi::move 2000 2000] puts -nonewline $out [punk::ansi::cursor_off][punk::ansi::cursor_save_dec][punk::ansi::move 2000 2000]
lassign [get_cursor_pos_list] lines cols lassign [get_cursor_pos_list $inoutchannels] lines cols
puts -nonewline [punk::ansi::cursor_restore][punk::console::cursor_on];flush stdout puts -nonewline $out [punk::ansi::cursor_restore][punk::console::cursor_on];flush $out
set result [list columns $cols rows $lines] set result [list columns $cols rows $lines]
} errM]} { } errM]} {
puts -nonewline [punk::ansi::cursor_restore_dec] puts -nonewline $out [punk::ansi::cursor_restore_dec]
puts -nonewline [punk::ansi::cursor_on] puts -nonewline $out [punk::ansi::cursor_on]
error "$errM" error "$errM"
} else { } else {
return $result return $result
@ -1803,6 +1874,9 @@ namespace eval punk::console {
} }
#run the test and allow warnings to be emitted to stderr on package load. User should know the terminal and/or Tcl version are not optimal for unicode character work #run the test and allow warnings to be emitted to stderr on package load. User should know the terminal and/or Tcl version are not optimal for unicode character work
#set testresult [test1] #set testresult [test1]
#*** !doctools
#[list_end] [comment {--- end definitions namespace punk::console ---}]
} }
@ -1826,3 +1900,6 @@ package provide punk::console [namespace eval punk::console {
set version 0.1.1 set version 0.1.1
}] }]
return return
#*** !doctools
#[manpage_end]

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

@ -967,7 +967,7 @@ namespace eval punk::du {
dict set effective_opts -with_times $timed_types dict set effective_opts -with_times $timed_types
dict set effective_opts -with_sizes $sized_types dict set effective_opts -with_sizes $sized_types
return [list dirs $dirs vfsmounts $vfsmounts links $links files $files filesizes [dict get $mdata_lists fsizes] sizes [dict get $mdata_lists allsizes] times [dict get $mdata_lists alltimes] flaggedhidden {} flaggedsystem {} flaggedreadonly {} altname {} opts $effective_opts errors $errors] return [list dirs $dirs vfsmounts $vfsmounts links $links files $files filesizes [dict get $mdata_lists fsizes] sizes [dict get $mdata_lists allsizes] times [dict get $mdata_lists alltimes] flaggedhidden $flaggedhidden flaggedsystem {} flaggedreadonly {} altname {} opts $effective_opts errors $errors]
} }
#zipfs attributes/behaviour fairly different to tclvfs - keep separate #zipfs attributes/behaviour fairly different to tclvfs - keep separate

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

@ -328,7 +328,17 @@ tcl::namespace::eval punk::lib::compat {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval punk::lib { namespace eval punk::lib {
tcl::namespace::export * tcl::namespace::export *
#variable xyz variable has_struct_list
set has_struct_list [expr {![catch {package require struct::list}]}]
variable has_struct_set
set has_struct_set [expr {![catch {package require struct::set}]}]
variable has_punk_ansi
set has_punk_ansi [expr {![catch {package require punk::ansi}]}]
set has_twapi 0
if {"windows" eq $::tcl_platform(platform)} {
set has_twapi [expr {![catch {package require twapi}]}]
}
#*** !doctools #*** !doctools
#[subsection {Namespace punk::lib}] #[subsection {Namespace punk::lib}]
@ -614,7 +624,9 @@ namespace eval punk::lib {
} }
proc pdict {args} { proc pdict {args} {
if {[catch {package require punk::ansi} errM]} { package require punk::args
variable has_punk_ansi
if {!$has_punk_ansi} {
set sep " = " set sep " = "
} else { } else {
#set sep " [a+ Web-seagreen]=[a] " #set sep " [a+ Web-seagreen]=[a] "
@ -691,14 +703,15 @@ namespace eval punk::lib {
# - Copy proc and attempt rework so we can get back to this as a baseline for functionality # - 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) proc showdict {args} { ;# analogous to parray (except that it takes the dict as a value)
#set sep " [a+ Web-seagreen]=[a] " #set sep " [a+ Web-seagreen]=[a] "
if {[catch {package require punk::ansi} errM]} { variable has_punk_ansi
set sep " = " if {!$has_punk_ansi} {
set RST "" set RST ""
set sep " = "
set sep_mismatch " mismatch " set sep_mismatch " mismatch "
} else { } else {
set sep " [punk::ansi::a+ Green]=[punk::ansi::a] " ;#stick to basic default colours for wider terminal support
set RST [punk::ansi::a] set RST [punk::ansi::a]
set sep_mismatch " [punk::ansi::a+ Brightred undercurly underline undt-white]mismatch[punk::ansi::a] " set sep " [punk::ansi::a+ Green]=$RST " ;#stick to basic default colours for wider terminal support
set sep_mismatch " [punk::ansi::a+ Brightred undercurly underline undt-white]mismatch$RST "
} }
package require punk ;#we need pipeline pattern matching features package require punk ;#we need pipeline pattern matching features
package require textblock package require textblock
@ -836,7 +849,7 @@ namespace eval punk::lib {
lappend keyset_structure dict lappend keyset_structure dict
} }
@* { @* {
puts ---->HERE<---- #puts "showdict ---->@*<----"
dict set pattern_this_structure $p list dict set pattern_this_structure $p list
set keys [punk::lib::range 0 [llength $dval]-1] set keys [punk::lib::range 0 [llength $dval]-1]
lappend keyset {*}$keys lappend keyset {*}$keys
@ -1405,16 +1418,29 @@ namespace eval punk::lib {
} }
proc is_list_all_in_list {small large} { proc is_list_all_in_list {small large} {
package require struct::list
package require struct::set
set small_in_large [lsort [struct::set intersect [lsort -unique $small] $large ]] set small_in_large [lsort [struct::set intersect [lsort -unique $small] $large ]]
return [struct::list equal [lsort $small] $small_in_large] return [struct::list equal [lsort $small] $small_in_large]
} }
if {!$has_struct_list || !$has_struct_set} {
set body {
package require struct::list
package require struct::set
}
append body [info body is_list_all_in_list]
proc is_list_all_in_list {small large} $body
}
proc is_list_all_ni_list {a b} { proc is_list_all_ni_list {a b} {
package require struct::set
set i [struct::set intersect $a $b] set i [struct::set intersect $a $b]
return [expr {[llength $i] == 0}] return [expr {[llength $i] == 0}]
} }
if {!$has_struct_set} {
set body {
package require struct::list
}
append body [info body is_list_all_ni_list]
proc is_list_all_ni_list {a b} $body
}
#somewhat like struct::set difference - but order preserving, and doesn't treat as a 'set' so preserves dupes in fromlist #somewhat like struct::set difference - but order preserving, and doesn't treat as a 'set' so preserves dupes in fromlist
#struct::set difference may happen to preserve ordering when items are integers, but order can't be relied on, #struct::set difference may happen to preserve ordering when items are integers, but order can't be relied on,
@ -1465,18 +1491,22 @@ namespace eval punk::lib {
return [array names tmp] return [array names tmp]
} }
package require struct::set #default/fallback implementation
if {[struct::set equal [struct::set union {a a} {}] {a}]} { proc lunique_unordered {list} {
proc lunique_unordered {list} { lunique $list
struct::set union $list {} }
} if {$has_struct_set} {
} else { if {[struct::set equal [struct::set union {a a} {}] {a}]} {
puts stderr "WARNING: struct::set union <list> <emptylist> no longer dedupes!" proc lunique_unordered {list} {
#we could also test a sequence of: struct::set add struct::set union $list {}
proc lunique_unordered {list} { }
tailcall lunique $list } else {
puts stderr "WARNING: struct::set union <list> <emptylist> no longer dedupes!"
#we could also test a sequence of: struct::set add
} }
} }
#order-preserving #order-preserving
proc lunique {list} { proc lunique {list} {
set new {} set new {}
@ -1863,14 +1893,14 @@ namespace eval punk::lib {
set opt_empty [tcl::dict::get $opts -empty_as_hex] set opt_empty [tcl::dict::get $opts -empty_as_hex]
# -- --- --- --- # -- --- --- ---
set list_largeHex [lmap h $list_largeHex[unset list_largeHex] {string map [list _ ""] [string trim $h]}] set list_largeHex [lmap h $list_largeHex[unset list_largeHex] {string map {_ ""} [string trim $h]}]
if {$opt_validate} { if {$opt_validate} {
#Note appended F so that we accept list of empty strings as per the documentation #Note appended F so that we accept list of empty strings as per the documentation
if {![string is xdigit -strict [join $list_largeHex ""]F ]} { if {![string is xdigit -strict [join $list_largeHex ""]F ]} {
error "[namespace current]::hex2dec error: non-hex digits encountered after stripping underscores and leading/trailing whitespace for each element\n $list_largeHex" error "[namespace current]::hex2dec error: non-hex digits encountered after stripping underscores and leading/trailing whitespace for each element\n $list_largeHex"
} }
} }
if {![string is xdigit -strict [string map [list _ ""] $opt_empty]]} { if {![string is xdigit -strict [string map {_ ""} $opt_empty]]} {
#mapping empty string to a value destroys any advantage of -scanonly #mapping empty string to a value destroys any advantage of -scanonly
#todo - document that -scanonly has 2 restrictions - each element must be valid hex and less than 7 chars long #todo - document that -scanonly has 2 restrictions - each element must be valid hex and less than 7 chars long
#set list_largeHex [lmap v $list_largeHex[set list_largeHex {}] {expr {$v eq ""} ? {0} : {[set v]}}] #set list_largeHex [lmap v $list_largeHex[set list_largeHex {}] {expr {$v eq ""} ? {0} : {[set v]}}]
@ -1878,7 +1908,7 @@ namespace eval punk::lib {
error "[namespace current]::hex2dec error: empty values in list cannot be mapped to non-hex $opt_empty" error "[namespace current]::hex2dec error: empty values in list cannot be mapped to non-hex $opt_empty"
} }
} else { } else {
set opt_empty [string trim [string map [list _ ""] $opt_empty]] set opt_empty [string trim [string map {_ ""} $opt_empty]]
if {[set first_empty [lsearch $list_largeHex ""]] >= 0} { if {[set first_empty [lsearch $list_largeHex ""]] >= 0} {
#set list_largeHex [lmap v $list_largeHex[set list_largeHex {}] {expr {$v eq ""} ? {$opt_empty} : {$v}}] #set list_largeHex [lmap v $list_largeHex[set list_largeHex {}] {expr {$v eq ""} ? {$opt_empty} : {$v}}]
set nonempty_head [lrange $list_largeHex 0 $first_empty-1] set nonempty_head [lrange $list_largeHex 0 $first_empty-1]
@ -1931,13 +1961,13 @@ namespace eval punk::lib {
} }
set fmt "%${opt_width}.${opt_width}ll${spec}" set fmt "%${opt_width}.${opt_width}ll${spec}"
set list_decimals [lmap d $list_decimals[unset list_decimals] {string map [list _ ""] [string trim $d]}] set list_decimals [lmap d $list_decimals[unset list_decimals] {string map {_ ""} [string trim $d]}]
if {![string is digit -strict [string map [list _ ""] $opt_empty]]} { if {![string is digit -strict [string map {_ ""} $opt_empty]]} {
if {[lsearch $list_decimals ""] >=0} { if {[lsearch $list_decimals ""] >=0} {
error "[namespace current]::dec2hex error: empty values in list cannot be mapped to non-decimal $opt_empty" error "[namespace current]::dec2hex error: empty values in list cannot be mapped to non-decimal $opt_empty"
} }
} else { } else {
set opt_empty [string map [list _ ""] $opt_empty] set opt_empty [string map {_ ""} $opt_empty]
if {[set first_empty [lsearch $list_decimals ""]] >= 0} { if {[set first_empty [lsearch $list_decimals ""]] >= 0} {
set nonempty_head [lrange $list_decimals 0 $first_empty-1] set nonempty_head [lrange $list_decimals 0 $first_empty-1]
set list_decimals [concat $nonempty_head [lmap v [lrange $list_decimals $first_empty end] {expr {$v eq ""} ? {$opt_empty} : {$v}}]] set list_decimals [concat $nonempty_head [lmap v [lrange $list_decimals $first_empty end] {expr {$v eq ""} ? {$opt_empty} : {$v}}]]
@ -2402,13 +2432,14 @@ namespace eval punk::lib {
# important for pipeline & match_assign # important for pipeline & match_assign
# -line trimline|trimleft|trimright -block trimhead|trimtail|triminner|trimall|trimhead1|trimtail1|collateempty -commandprefix {string length} ? # -line trimline|trimleft|trimright -block trimhead|trimtail|triminner|trimall|trimhead1|trimtail1|collateempty -commandprefix {string length} ?
# -block trimming only trims completely empty lines. use -line trimming to remove whitespace e.g -line trimright will clear empty lines without affecting leading whitespace on other lines that aren't pure whitespace # -block trimming only trims completely empty lines. use -line trimming to remove whitespace e.g -line trimright will clear empty lines without affecting leading whitespace on other lines that aren't pure whitespace
proc linelist {args} {
set linelist_body {
set usage "linelist ?-line trimline|trimleft|trimright? ?-block trimhead|trimtail|triminner|trimall|trimhead1|trimtail1|collateempty? -commandprefix <cmdlist> text" set usage "linelist ?-line trimline|trimleft|trimright? ?-block trimhead|trimtail|triminner|trimall|trimhead1|trimtail1|collateempty? -commandprefix <cmdlist> text"
if {[llength $args] == 0} { if {[llength $args] == 0} {
error "linelist missing textchunk argument usage:$usage" error "linelist missing textchunk argument usage:$usage"
} }
set text [lindex $args end] set text [lindex $args end]
set text [string map [list \r\n \n] $text] ;#review - option? set text [string map {\r\n \n} $text] ;#review - option?
set arglist [lrange $args 0 end-1] set arglist [lrange $args 0 end-1]
set opts [tcl::dict::create\ set opts [tcl::dict::create\
@ -2441,10 +2472,10 @@ namespace eval punk::lib {
} }
} }
#normalize certain combos #normalize certain combos
if {[set posn [lsearch $opt_block trimhead1]] >=0 && "trimhead" in $opt_block} { if {"trimhead" in $opt_block && [set posn [lsearch $opt_block trimhead1]] >=0} {
set opt_block [lreplace $opt_block $posn $posn] set opt_block [lreplace $opt_block $posn $posn]
} }
if {[set posn [lsearch $opt_block trimtail1]] >=0 && "trimtail" in $opt_block} { if {"trimtail" in $opt_block && [set posn [lsearch $opt_block trimtail1]] >=0} {
set opt_block [lreplace $opt_block $posn $posn] set opt_block [lreplace $opt_block $posn $posn]
} }
if {"trimall" in $opt_block} { if {"trimall" in $opt_block} {
@ -2594,9 +2625,10 @@ namespace eval punk::lib {
#Each resulting line should have a reset of some type at start and a pure-reset at end to stop #Each resulting line should have a reset of some type at start and a pure-reset at end to stop
#see if we can find an ST sequence that most terminals will not display for marking sections? #see if we can find an ST sequence that most terminals will not display for marking sections?
if {$opt_ansireplays} { if {$opt_ansireplays} {
package require punk::ansi #package require punk::ansi
<require_punk_ansi>
if {$opt_ansiresets} { if {$opt_ansiresets} {
set RST [punk::ansi::a] set RST "\x1b\[0m"
} else { } else {
set RST "" set RST ""
} }
@ -2721,6 +2753,15 @@ namespace eval punk::lib {
return $linelist return $linelist
} }
if {$has_punk_ansi} {
#optimise linelist as much as possible
set linelist_body [string map {<require_punk_ansi> ""} $linelist_body]
} else {
#punk ansi not avail at time of package load.
#by putting in calls to punk::ansi the user will get appropriate error messages
set linelist_body [string map {<require_punk_ansi> "package require punk::ansi"} $linelist_body]
}
proc linelist {args} $linelist_body
interp alias {} errortime {} punk::lib::errortime interp alias {} errortime {} punk::lib::errortime
@ -2846,6 +2887,133 @@ namespace eval punk::lib {
proc temperature_c_to_f {deg_celsius} { proc temperature_c_to_f {deg_celsius} {
return [expr {($deg_celsius * (9/5.0)) + 32}] return [expr {($deg_celsius * (9/5.0)) + 32}]
} }
proc interp_sync_package_paths {interp} {
if {![interp exists $interp]} {
error "interp_sync_package_paths error. interp '$interp' not found. Create it first with \[interp create $interp\]"
}
interp eval $interp [list set ::auto_path $::auto_path]
interp eval $interp {tcl::tm::remove {*}[tcl::tm::list]}
interp eval $interp [list tcl::tm::add {*}[lreverse [tcl::tm::list]]]
}
proc objclone {obj} {
append obj2 $obj {}
}
proc format_number {numbers_or_commaformattednumbers {delim ""} {groupsize ""}} {
variable has_twapi
if {$has_twapi} {
if {$delim eq "" && $groupsize eq ""} {
set localeid [twapi::get_system_default_lcid]
}
}
set results [list]
set nums [objclone $numbers_or_commaformattednumbers] ;#stops single num from getting internal rep of list
foreach inputnum $nums {
set number [objclone $inputnum]
#also handle tcl 8.7+ underscores in numbers
set number [string map [list _ "" , ""] $number]
#normalize e.g 2e4 -> 20000.0
set number [expr {$number}]
if {$has_twapi} {
if {$delim eq "" && $groupsize eq ""} {
lappend results [twapi::format_number $number $localeid -idigits -1]
continue
} else {
if {$delim eq ""} {set delim ","}
if {$groupsize eq ""} {set groupsize 3}
lappend results [twapi::format_number $number 0 -idigits -1 -sthousand $delim -sgrouping $groupsize]
continue
}
}
#todo - get configured user defaults
set delim ","
set groupsize 3
lappend results [delimit_number $number $delim $groupsize]
}
if {[llength $results] == 1} {
#keep intrep as string rather than list
return [lindex $results 0]
}
return $results
}
#from wiki https://wiki.tcl-lang.org/page/Delimiting+Numberse
# Given a number represented as a string, insert delimiters to break it up for
# readability. Normally, the delimiter will be a comma which will be inserted every
# three digits. However, the delimiter and groupsize are optional arguments,
# permitting use in other locales.
#
# The string is assumed to consist of digits, possibly preceded by spaces,
# and possibly containing a decimal point, i.e.: [:space:]*[:digit:]*\.[:digit:]*
proc delimit_number {unformattednumber {delim ","} {GroupSize 3}} {
set number [punk::objclone $unformattednumber]
set number [string map {_ ""} $number]
#normalize using expr - e.g 2e4 -> 20000.0
set number [expr {$number}]
# First, extract right hand part of number, up to and including decimal point
set point [string last "." $number];
if {$point >= 0} {
set PostDecimal [string range $number [expr $point + 1] end];
set PostDecimalP 1;
} else {
set point [expr [string length $number] + 1]
set PostDecimal "";
set PostDecimalP 0;
}
# Now extract any leading spaces. review - regex for whitespace instead of just ascii space?
set ind 0;
while {[string equal [string index $number $ind] \u0020]} {
incr ind;
}
set FirstNonSpace $ind;
set LastSpace [expr $FirstNonSpace - 1];
set LeadingSpaces [string range $number 0 $LastSpace];
# Now extract the non-fractional part of the number, omitting leading spaces.
set MainNumber [string range $number $FirstNonSpace [expr $point -1]];
# Insert commas into the non-fractional part.
set Length [string length $MainNumber];
set Phase [expr $Length % $GroupSize]
set PhaseMinusOne [expr $Phase -1];
set DelimitedMain "";
#First we deal with the extra stuff.
if {$Phase > 0} {
append DelimitedMain [string range $MainNumber 0 $PhaseMinusOne];
}
set FirstInGroup $Phase;
set LastInGroup [expr $FirstInGroup + $GroupSize -1];
while {$LastInGroup < $Length} {
if {$FirstInGroup > 0} {
append DelimitedMain $delim;
}
append DelimitedMain [string range $MainNumber $FirstInGroup $LastInGroup];
incr FirstInGroup $GroupSize
incr LastInGroup $GroupSize
}
# Reassemble the number.
if {$PostDecimalP} {
return [format "%s%s.%s" $LeadingSpaces $DelimitedMain $PostDecimal];
} else {
return [format "%s%s" $LeadingSpaces $DelimitedMain];
}
}
#*** !doctools #*** !doctools
#[list_end] [comment {--- end definitions namespace punk::lib ---}] #[list_end] [comment {--- end definitions namespace punk::lib ---}]
} }
@ -2998,7 +3166,9 @@ tcl::namespace::eval punk::lib::system {
return [concat $smallfactors [lreverse $largefactors] $x] return [concat $smallfactors [lreverse $largefactors] $x]
} }
# incomplte - report which is the innermost bracket/quote etc awaiting completion for a Tcl command
# incomplete - report which is the innermost bracket/quote etc awaiting completion for a Tcl command
#important - used by punk::repl #important - used by punk::repl
proc incomplete {partial} { proc incomplete {partial} {
#we can apparently get away without concatenating current innerpartial to previous in list - REVIEW. #we can apparently get away without concatenating current innerpartial to previous in list - REVIEW.

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

@ -35,12 +35,14 @@ namespace eval punk::mix::base {
} }
#puts stderr "punk::mix::base extension: [string trimleft $extension :]" #puts stderr "punk::mix::base extension: [string trimleft $extension :]"
if {![string length $extension]} { if {![string length $extension]} {
#if still no extension - must have been called dirctly as punk::mix::base::_cli #if still no extension - must have been called directly as punk::mix::base::_cli
if {![llength $args]} { if {![llength $args]} {
set args "help" set args "help"
} }
set extension [namespace current] set extension [namespace current]
} }
#init usually used to load commandsets (and export their names) into the extension namespace/ensemble
${extension}::_init
if {![llength $args]} { if {![llength $args]} {
if {[info exists ${extension}::default_command]} { if {[info exists ${extension}::default_command]} {
tailcall $extension [set ${extension}::default_command] tailcall $extension [set ${extension}::default_command]

148
src/bootsupport/modules/punk/mix/cli-0.3.1.tm

@ -31,47 +31,58 @@ namespace eval punk::mix::cli {
namespace eval temp_import { namespace eval temp_import {
} }
namespace ensemble create namespace ensemble create
variable initialised 0
package require punk::overlay #lazy _init - called by punk::mix::base::_cli when ensemble used
catch { proc _init {args} {
punk::overlay::import_commandset module . ::punk::mix::commandset::module variable initialised
} if {$initialised} {
punk::overlay::import_commandset debug . ::punk::mix::commandset::debug return
punk::overlay::import_commandset repo . ::punk::mix::commandset::repo }
punk::overlay::import_commandset lib . ::punk::mix::commandset::loadedlib puts stderr "punk::mix::cli::init $args"
package require punk::overlay
catch { namespace eval ::punk::mix::cli {
package require punk::mix::commandset::project catch {
punk::overlay::import_commandset project . ::punk::mix::commandset::project punk::overlay::import_commandset module . ::punk::mix::commandset::module
punk::overlay::import_commandset projects . ::punk::mix::commandset::project::collection }
} punk::overlay::import_commandset debug . ::punk::mix::commandset::debug
if {[catch { punk::overlay::import_commandset repo . ::punk::mix::commandset::repo
package require punk::mix::commandset::layout punk::overlay::import_commandset lib . ::punk::mix::commandset::loadedlib
punk::overlay::import_commandset project.layout . ::punk::mix::commandset::layout
punk::overlay::import_commandset project.layouts . ::punk::mix::commandset::layout::collection catch {
} errM]} { package require punk::mix::commandset::project
puts stderr "error loading punk::mix::commandset::layout" punk::overlay::import_commandset project . ::punk::mix::commandset::project
puts stderr $errM punk::overlay::import_commandset projects . ::punk::mix::commandset::project::collection
} }
if {[catch { if {[catch {
package require punk::mix::commandset::buildsuite package require punk::mix::commandset::layout
punk::overlay::import_commandset buildsuite . ::punk::mix::commandset::buildsuite punk::overlay::import_commandset project.layout . ::punk::mix::commandset::layout
punk::overlay::import_commandset buildsuites . ::punk::mix::commandset::buildsuite::collection punk::overlay::import_commandset project.layouts . ::punk::mix::commandset::layout::collection
} errM]} { } errM]} {
puts stderr "error loading punk::mix::commandset::buildsuite" puts stderr "error loading punk::mix::commandset::layout"
puts stderr $errM puts stderr $errM
} }
punk::overlay::import_commandset scriptwrap . ::punk::mix::commandset::scriptwrap if {[catch {
if {[catch { package require punk::mix::commandset::buildsuite
package require punk::mix::commandset::doc punk::overlay::import_commandset buildsuite . ::punk::mix::commandset::buildsuite
punk::overlay::import_commandset doc . ::punk::mix::commandset::doc punk::overlay::import_commandset buildsuites . ::punk::mix::commandset::buildsuite::collection
punk::overlay::import_commandset "" "" ::punk::mix::commandset::doc::collection } errM]} {
} errM]} { puts stderr "error loading punk::mix::commandset::buildsuite"
puts stderr "error loading punk::mix::commandset::doc" puts stderr $errM
puts stderr $errM }
punk::overlay::import_commandset scriptwrap . ::punk::mix::commandset::scriptwrap
if {[catch {
package require punk::mix::commandset::doc
punk::overlay::import_commandset doc . ::punk::mix::commandset::doc
punk::overlay::import_commandset "" "" ::punk::mix::commandset::doc::collection
} errM]} {
puts stderr "error loading punk::mix::commandset::doc"
puts stderr $errM
}
}
set initialised 1
} }
proc help {args} { proc help {args} {
#set basehelp [punk::mix::base::help -extension [namespace current] {*}$args] #set basehelp [punk::mix::base::help -extension [namespace current] {*}$args]
set basehelp [punk::mix::base help {*}$args] set basehelp [punk::mix::base help {*}$args]
@ -210,11 +221,12 @@ namespace eval punk::mix::cli {
proc validate_modulename {modulename args} { proc validate_modulename {modulename args} {
set opts [list\ set opts [list\
-errorprefix validate_modulename\ -errorprefix validate_modulename\
-strict 0\
] ]
if {[llength $args] %2 != 0} {error "validate_modulename args must be name-value pairs: received '$args'"} if {[llength $args] %2 != 0} {error "validate_modulename args must be name-value pairs: received '$args'"}
foreach {k v} $args { foreach {k v} $args {
switch -- $k { switch -- $k {
-errorprefix { -errorprefix - -strict {
dict set opts $k $v dict set opts $k $v
} }
default { default {
@ -223,8 +235,14 @@ namespace eval punk::mix::cli {
} }
} }
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- --- ---
set opt_errorprefix [dict get $opts -errorprefix] set opt_errorprefix [dict get $opts -errorprefix]
set opt_strict [dict get $opts -strict]
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- --- ---
if {$opt_strict} {
if {[regexp {[A-Z]} $modulename]} {
error "$opt_errorprefix '$modulename' contains uppercase which is not recommended as per tip 590, and option -strict is set to 1"
}
}
validate_name_not_empty_or_spaced $modulename -errorprefix $opt_errorprefix validate_name_not_empty_or_spaced $modulename -errorprefix $opt_errorprefix
set testname [string map {:: {}} $modulename] set testname [string map {:: {}} $modulename]
@ -239,6 +257,56 @@ namespace eval punk::mix::cli {
} }
return $modulename return $modulename
} }
proc confirm_modulename {modulename} {
set finalised 0
set aborted 0
while {!$finalised && !$aborted} {
#first validate with -strict 0 to confirm acceptable while ignoring case issues.
#uppercase is generally valid but not recommended - so has separate prompting.
if {[catch {validate_modulename $modulename -strict 0} errM]} {
set msg "Chosen name didn't pass validation\n"
append msg "reason: $errM\n"
append msg "Please retype the modulename. You will be given a further prompt to confirm or abort."
set modulename [util::askuser $msg]
} elseif {[regexp {[A-Z]} $modulename]} {
set msg "module names containing uppercase are not recommended (see tip 590).\n"
append msg "Please retype the module name '$modulename' to proceed.\n"
append msg "If you type it exactly as it was you will be allowed to proceed with uppercase anyway\n"
append msg "Retype it all in lowercase to use recommended naming"
set answer [util::askuser $msg]
if {[regexp {[A-Z]} $answer]} {
if {$answer eq $modulename} {
#ok - user insists
set finalised 1
} else {
#user supplied a different uppercase name - don't set finalised so we bug them again to type it two times the same way to proceed
puts stdout "A different uppercase name was supplied - reconfirmation required."
}
set modulename $answer
} else {
#user has resupplied modulename all as lowercase
if {$answer eq [string tolower $modulename]} {
set finalised 1
} else {
#.. but it doesn't match original - require rerun
}
set modulename $answer
}
} else {
set answer [util::askuser "Proceed with the module name '$modulename'? Y to continue N to abort"]
if {[string tolower $answer] eq "y"} {
set finalised 1
} else {
set aborted 1
}
}
}
if {$aborted} {
return [dict create status error reason errmsg]
} else {
return [dict create status ok modulename $modulename]
}
}
proc validate_projectname {projectname args} { proc validate_projectname {projectname args} {
set defaults [list\ set defaults [list\

30
src/bootsupport/modules/punk/mix/commandset/doc-0.1.0.tm

@ -165,7 +165,17 @@ namespace eval punk::mix::commandset::doc {
cd $original_wd cd $original_wd
} }
proc validate {} { proc validate {args} {
set argd [punk::args::get_dict {
-- -type none -optional 1 -help "end of options marker --"
-individual -type boolean -default 1
*values -min 0 -max -1
patterns -default {*} -type any -multiple 1
} $args]
set opt_individual [tcl::dict::get $argd opts -individual]
set patterns [tcl::dict::get $argd values patterns]
#todo - run and validate punk::docgen output #todo - run and validate punk::docgen output
set projectdir [punk::repo::find_project] set projectdir [punk::repo::find_project]
if {$projectdir eq ""} { if {$projectdir eq ""} {
@ -180,7 +190,23 @@ namespace eval punk::mix::commandset::doc {
set docroot $projectdir/src/doc set docroot $projectdir/src/doc
cd $docroot cd $docroot
dtplite validate $docroot if {!$opt_individual && "*" in $patterns} {
if {[catch {
dtplite validate $docroot
} errM]} {
puts stderr "commandset::doc::validate failed for projectdir '$projectdir'"
puts stderr "docroot '$docroot'"
puts stderr "dtplite error was: $errM"
}
} else {
foreach p $patterns {
set treefiles [punk::path::treefilenames $p]
foreach path $treefiles {
puts stdout "dtplite validate $path"
dtplite validate $path
}
}
}
#punk::mix::cli::lib::kettle_call lib validate-doc #punk::mix::cli::lib::kettle_call lib validate-doc

11
src/bootsupport/modules/punk/mix/commandset/loadedlib-0.1.0.tm

@ -179,7 +179,16 @@ namespace eval punk::mix::commandset::loadedlib {
return [join $loaded_libs \n] return [join $loaded_libs \n]
} }
proc info {libname} { proc info {args} {
set argspecs {
*values -min 1
libname -help "library/package name"
}
set argd [punk::args::get_dict $argspecs $args]
set libname [dict get $argd values libname]
if {[catch {package require natsort}]} { if {[catch {package require natsort}]} {
set has_natsort 0 set has_natsort 0
} else { } else {

24
src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm

@ -204,6 +204,30 @@ namespace eval punk::mix::commandset::module {
set modulename $module set modulename $module
} }
punk::mix::cli::lib::validate_modulename $modulename -errorprefix "punk::mix::commandset::module::new" punk::mix::cli::lib::validate_modulename $modulename -errorprefix "punk::mix::commandset::module::new"
if {[regexp {[A-Z]} $module]} {
set msg "module names containing uppercase are not recommended (see tip 590).\n"
append msg "Please retype the module name '$module' to proceed.\n"
append msg "If you type it exactly as it was you will be allowed to proceed with uppercase anyway\n"
append msg "Retype it all in lowercase to use recommended naming"
set answer [util::askuser $msg]
if {[regexp {[A-Z]} $answer]} {
if {$answer eq $module} {
#ok - user insists
} else {
}
} else {
#user has resupplied modulename all as lowercase
if {$answer eq [string tolower $module]} {
set module $answer
} else {
#.. but it doesn't match original - require rerun
}
}
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
#options #options
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---

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

@ -165,7 +165,7 @@ namespace eval punk::mix::commandset::project {
#user can use dev module.new manually or supply module name in -modules #user can use dev module.new manually or supply module name in -modules
set opt_modules [list] set opt_modules [list]
} else { } else {
set opt_modules [list $projectname] set opt_modules [list [string tolower $projectname]] ;#default modules to lowercase as is the modern (tip 590) recommendation for Tcl
} }
} }
# -- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- ---
@ -919,10 +919,18 @@ namespace eval punk::mix::commandset::project {
if {[llength $col_states]} { if {[llength $col_states]} {
foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes s $col_states { foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes s $col_states {
if {![file exists $wd]} {
set row [punk::ansi::a+ strike red]$row[a]
set wd [punk::ansi::a+ red]$wd[a]
}
append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode] [overtype::left $col6 $s]" \n append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode] [overtype::left $col6 $s]" \n
} }
} else { } else {
foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes { foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes {
if {![file exists $wd]} {
set row [punk::ansi::a+ strike red]$row[a]
set wd [punk::ansi::a+ red]$wd[a]
}
append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode]" \n append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode]" \n
} }
} }

4
src/bootsupport/modules/punk/overlay-0.1.tm

@ -130,6 +130,7 @@ tcl::namespace::eval ::punk::overlay {
}] }]
set imported_commands [list] set imported_commands [list]
set imported_tails [list]
set nscaller [uplevel 1 [list tcl::namespace::current]] set nscaller [uplevel 1 [list tcl::namespace::current]]
if {[catch { if {[catch {
#review - noclobber? #review - noclobber?
@ -143,7 +144,10 @@ tcl::namespace::eval ::punk::overlay {
} }
rename $cmd $import_as rename $cmd $import_as
lappend imported_commands $import_as lappend imported_commands $import_as
lappend imported_tails [namespace tail $import_as]
} }
#make imported commands exported so they are available to the ensemble
tcl::namespace::eval ${nscaller} [list namespace export {*}$imported_tails]
} errM]} { } errM]} {
puts stderr "Error loading commandset $prefix $separator $cmdnamespace" puts stderr "Error loading commandset $prefix $separator $cmdnamespace"
puts stderr "err: $errM" puts stderr "err: $errM"

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

@ -63,11 +63,11 @@ package require Tcl 8.6-
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# oo::class namespace # oo::class namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval punk::path::class { #namespace eval punk::path::class {
#*** !doctools #*** !doctools
#[subsection {Namespace punk::path::class}] #[subsection {Namespace punk::path::class}]
#[para] class definitions #[para] class definitions
if {[info commands [namespace current]::interface_sample1] eq ""} { #if {[info commands [namespace current]::interface_sample1] eq ""} {
#*** !doctools #*** !doctools
#[list_begin enumerated] #[list_begin enumerated]
@ -89,8 +89,8 @@ namespace eval punk::path::class {
#*** !doctools #*** !doctools
#[list_end] [comment {--- end class enumeration ---}] #[list_end] [comment {--- end class enumeration ---}]
} #}
} #}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
@ -105,6 +105,448 @@ namespace eval punk::path {
#[para] Core API functions for punk::path #[para] Core API functions for punk::path
#[list_begin definitions] #[list_begin definitions]
# -- ---
#punk::path::normjoin
# - simplify . and .. segments as far as possible whilst respecting specific types of root.
# -- ---
#a form of file normalize that supports //xxx to be treated as server path names
#(ie regardless of unices ignoring (generally) leading double slashes, and regardless of windows volumerelative path syntax)
#(sometimes //server.com used as a short form for urls - which doesn't seem too incompatible with this anyway)
# -- ---
#This is intended to be purely a string analysis - without reference to filesystem volumes or vfs or zipfs mountpoints etc
#
#TODO - option for caller to provide a -base below which we can't backtrack.
#This is preferable to setting policy here for example regarding forcing no trackback below //servername/share
#Our default is to allow trackback to:
# <scheme>://<something>
# <driveletter>:/
# //./<volume> (dos device volume)
# //server (while normalizing //./UNC/server to same)
# / (ordinary unix root)
# ./../<repeated> - (track back indefinitely on relpath as we are not resolving to anything physical and can't fully simplify the leading backtracks)
#
#The caller should do the file/vfs operations to determine this - not us.
# -- ---
#simplify path with respect to /./ & /../ elements - independent of platform
#NOTE: "anomalies" in standard tcl processing on windows:
#e.g file normalize {//host} -> c:/host (or e.g d:/host if we happen to be on another volume)
#file normalize {//host/share} -> //host/share
#This is because //host is treated as volume-relative in cmd/powershell and Tcl quite reasonably follows suit.
#This prevents cwd and windows commandlines from pointing to the server (above the share)
#Explorer however does allow pointing to the //server level and seeing shares as if they are directory entries.
#we are more interested in supporting the explorer-like behaviour - as while volumerelative paths are also useful on windows - they are lesser known.
#REVIEW.
#To get back to some consistent cross platform behaviour - we will treat //something as a root/volume i.e we can't backtrack above it with ".."
#note too that file split on UNC paths doesn't give a clear indication of the root
# file split //./UNC/server/share/subpath -> //./UNC server share subpath
# file split //server/share/subpath -> //server/share subpath
#TODO - disallow all change of root or change from relative path to absolute result.
#e.g normjoin relpath/../d:/secret should not return d:/secret - but ./d:/secret
# ================
#known issues:
#1)
# normjoin d://a//b//c -> d://a/b/c
# This is because we don't detect specific schemes. ie it's treated the same as https://a/b/c -> https://a/b/c
# Not considered a problem - just potentially surprising.
# To avoid it we would have to enumerate possible schemes.
# As it stands a unix system could define a 'scheme' that happens to match windows style driveletters. Consider a 'feature' ? review.
# won't fix?
#2)
# normjoin https:///real.com/../fake.com -> https:///fake.com
# The extra slash means effectively our servername is empty - this is potentially confusing but probably the right thing to do here.
# It's a concern only if upstream treats the tripple slash in this case as valid and maps it to https:// - which would probably be bad anyway.
# won't fix (review)
#3)
#similarly
# normjoin //./UNC//server/share/subpath -> ///server/share/subpath (when 2 or more slashes directly after UNC)
# normjoin ///server/share -> ///server/share
#This is effectively an empty servername in the input with 'server' being pushed one level down - and the output is consistent
# possibly won't fix - review
#4) inconsistency
# we return normalized //server/share for //./UNC/server share
# but other dos device paths are maintained
# e.g //./c:/etc
# This is because such paths could contain alternate segment names (windows shortnames) which we aren't in a position to resolve.
# caller should
# #as with 'case' below - caller will need to run a post 'file normalize'
#5) we don't normalize case like file normalize does on windows platform.
# This is intentional. It could only be done with reference to underlying filesystem which we don't want here.
#
# ================
#
#relpaths all end up with leading . - while not always the simplest form, this is ok. (helps stop inadvertent conversions to absolutes)
# Tests - TODO
# normjoin /d:/..//vfs:/test -> /vfs:/test (good - not converted to //vfs:/test)
proc normjoin {args} {
set args [lmap a $args {string map "\\\\ /" $a}]
set path [plainjoin {*}$args]
switch -exact $path {
"" {
return ""
}
/ - // {
#treated in unixlike manner - (but leading doubleslashes with subsequent data are server indication)
#// not considered a servername indicator - but /// (for consistency) is. (empty servername?)
return /
}
/// {
#if this is effectively //$emptyservername/
#then for consistency we should trail //<servername with a slash too?
#we can't transform to // or /
return ///
#assert - code below should return /// (empty server prefix) for any number of leading slashes >=3
#todo - shortcircuit that here?
}
}
# ///
set doubleslash1_posn [string first // $path]
# -- --- --- temp warning on windows only - no x-platform difference in result
#on windows //host is of type volumerelative
# whereas //host/share is of type absolute
if {"windows" eq $::tcl_platform(platform) && [file pathtype $path] eq "volumerelative"} {
#volumerelative probably only occurs on windows anyway
if {$doubleslash1_posn == 0} {
#e.g //something where no further slashes
#review - eventually get rid of this warning and require upstream to know the appropriate usecase
puts stderr "Warning - ambiguous path $path - treating as server path - not 'volumerelative'"
} else {
# /something/etc
# /mnt/c/stuff
#output will retain leading / as if on unix.
#on windows - the result would still be interpreted as volumerelative if the caller normalizes it
}
}
# -- --- ---
set is_relpath 0
#set path [string map [list \\ /] $path]
set finalparts [list]
set is_nonunc_dosdevice 0
if {[punk::winpath::is_dos_device_path $path]} {
#review
if {[string range $path 4 6] eq "UNC"} {
#convert to 'standard' //server/... path for processing
set path "/[string range $path 7 end]" ;# //server/...
} else {
#error "normjoin non-UNC dos device path '$path' not supported"
#first segment after //./ or //?/ represents the volume or drive.
#not applicable to unix - but unlikely to conflict with a genuine usecase there (review)
#we should pass through and stop navigation below //./vol
#!!!
#not anomaly in tcl (continues in tcl9)
#file exists //./c:/test -> 0
#file exists //?/c:/test -> 1
#file exists //./BootPartition/Windows -> 1
#file exists //?/BootPartition/Windows -> 0
set is_nonunc_dosdevice 1
}
}
if {$is_nonunc_dosdevice} {
#dosdevice prefix //./ or //?/ - preserve it (without trailing slash which will be put back in with join)
set prefix [string range $path 0 2]
set tail [string range $path 4 end]
set tailparts [split $tail /]
set parts [concat [list $prefix] $tailparts]
set rootindex 1 ;#disallow backtrack below //./<volume>
} else {
#note use of ordinary ::split vs file split is deliberate.
if {$doubleslash1_posn == 0} {
#this is handled differently on different platforms as far as 'file split' is concerned.
#e.g for file split //sharehost/share/path/etc
#e.g on windows: -> //sharehost/share path
#e.g on freebsd: -> / sharehost share path etc
#however..also on windows: file split //sharehost -> / sharehost
#normalize by dropping leading slash before split - and then treating first 2 segments as a root
#set parts [file split [string range $path 1 end]]
set parts [split $path /]
#assert parts here has {} {} as first 2 entries
set rootindex 2
#currently prefer can backtrack to the //zipfs:/ scheme (below the mountpoint - to browse other mounts)
#alternative handling for //zipfs:/path - don't go below mountpoint
#but we can't determine just from string if mountpoint is direct subpath or a lower one e.g //zipfs:/arbitraryname/actualmountpoint
#review - more generally //<mountmechanism>:/path ?
#todo - make an option for zipfs and others to determine the 'base'
#if {"zipfs:" eq [lindex $parts 2]} {
# set rootindex 3
#}
} else {
#path may or may not begin with a single slash here.
#treat same on unix and windows
set rootindex 0
#set parts [file split $path]
set parts [::split $path /]
#e.g /a/b/c -> {} a b c
#or relative path a/b/c -> a b c
#or c:/a/b/c -> c: a b c
if {[string match *: [lindex $parts 0]]} {
if {[lindex $parts 1] eq ""} {
#scheme://x splits to scheme: {} x
set parts [concat [list [lindex $parts 0]/] [lrange $parts 2 end]]
#e.g {scheme:/ x}
set rootindex 1 ;#disallow below first element of scheme
} else {
set rootindex 0
}
} elseif {[lindex $parts 0] ne ""} {
#relpath a/b/c
set parts [linsert $parts 0 .]
set rootindex 0
#allow backtracking arbitrarily for leading .. entries - simplify where possible
#also need to stop possible conversion to absolute path
set is_relpath 1
}
}
}
set baseparts [lrange $parts 0 $rootindex] ;#base below which we can't retreat via ".."
#puts stderr "-->baseparts:$baseparts"
#ensure that if our rootindex already spans a dotted segment (after the first one) we remove it
#must maintain initial . for relpaths to stop them converting to absolute via backtrack
#
set finalparts [list [lindex $baseparts 0]]
foreach b [lrange $baseparts 1 end] {
if {$b ni {. ..}} {
lappend finalparts $b
}
}
set baselen [expr {$rootindex + 1}]
if {$is_relpath} {
set i [expr {$rootindex+1}]
foreach p [lrange $parts $i end] {
switch -exact -- $p {
. - "" {}
.. {
switch -exact -- [lindex $finalparts end] {
. - .. {
lappend finalparts ..
}
default {
lpop finalparts
}
}
}
default {
lappend finalparts $p
}
}
incr i
}
} else {
foreach p [lrange $parts $rootindex+1 end] {
if {[llength $finalparts] <= $baselen} {
if {$p ni {. .. ""}} {
lappend finalparts $p
}
} else {
switch -exact -- $p {
. - "" {}
.. {
lpop finalparts ;#uses punk::lib::compat::lpop if on < 8.7
}
default {
lappend finalparts $p
}
}
}
}
}
puts "==>finalparts: '$finalparts'"
# using join - {"" "" server share} -> //server/share and {a b} -> a/b
if {[llength $finalparts] == 1 && [lindex $finalparts 0] eq ""} {
#backtracking on unix-style path can end up with empty string as only member of finalparts
#e.g /x/..
return /
}
set result [::join $finalparts /]
#normalize volumes and mountschemes to have trailing slash if no subpath
#e.g c: -> c:/
#//zipfs: -> //zipfs:/
if {[set lastchar [string index $result end]] eq ":"} {
if {$result eq "//zipfs:"} {
set result "//zipfs:/"
} else {
if {[string first / $result] < 0} {
set result $result/
}
}
} elseif {[string match //* $result]} {
if {![punk::winpath::is_dos_device_path $result]} {
#server
set tail [string range $result 2 end]
set tailparts [split $tail /]
if {[llength $tailparts] <=1} {
#empty // or //servername
append result /
}
}
} elseif {[llength $finalparts] == 2} {
if {[string range [lindex $finalparts 0] end-1 end] eq ":/"} {
#e.g https://server/ -> finalparts {https:/ server}
#e.g https:/// -> finalparts {https:/ ""}
#scheme based path should always return trailing slash after server component - even if server component empty.
lappend finalparts "" ;#force trailing /
return [join $finalparts /]
}
}
return $result
}
proc trim_final_slash {str} {
if {[string index $str end] eq "/"} {
return [string range $str 0 end-1]
}
return $str
}
#x-platform - punk::path::pathtype - can be used in safe interps - different concept of pathtypes to 'file pathtype'
# - no volumerelative
# - no lookup of file volumes (volume is a windows concept - but with //zipfs:/ somewhat applicable to other platforms)
# - /* as absolute (covers also //zipfs:/ (volume), //server , //./etc , //./UNC)
# - xxx:// as absolute (scheme)
# - xxx:/ or x:/ as absolute
# - x: xxx: -> as absolute (volume-basic or volume-extended)
#note also on windows - legacy name for COM devices
# COM1 = COM1:
# //./COM1 ?? review
proc pathtype {str} {
set str [string map "\\\\ /" $str]
if {[string index $str 0] eq "/"} {
#todo - look for //xxx:/ prefix (generalisation of //zipfs:/) as a 'volume' specifically {volume mount} ?? - review
# look for //server prefix as {absolute server}
# look for //./UNC/server or //?/UNC/server as {absolute server UNC} ?
# look for //./<dosdevice> as {absolute dosdevice}
return absolute
}
#only firstsegment with single colon at last position (after some non empty string) counts as volume or scheme - review
#e.g a:b:/.. or a::/.. or :/.. is not treated as volume/scheme whereas ab:/ is.
set firstslash [string first / $str]
if {$firstslash == -1} {
set firstsegment $str
} else {
set firstsegment [string range $str 0 $firstslash-1]
}
if {[set firstc [string first : $firstsegment]] > 0} {
set lhs_firstsegment [string range $firstsegment 0 $firstc-1]
set rhs_firstsegment [string range $firstsegment $firstc+1 end] ;#exclude a:b/ etc
if {$rhs_firstsegment eq ""} {
set rhs_entire_path [string range $str $firstc+1 end]
#assert lhs_firstsegment not empty since firstc > 0
#count following / sequence
set i 0
set slashes_after_firstsegment "" ;#run of slashes *directly* following first segment
while {$i < [string length $rhs_entire_path]} {
if {[string index $rhs_entire_path $i] eq "/"} {
append slashes_after_firstsegment /
} else {
break
}
incr i
}
switch -exact -- $slashes_after_firstsegment {
"" - / {
if {[string length $lhs_firstsegment] == 1} {
return {absolute volume basic}
} else {
return {absolute volume extended}
}
}
default {
#2 or more /
#this will return 'scheme' even for c:// - even though that may look like a windows volume - review
return {absolute scheme}
}
}
}
}
#assert first element of any return has been absolute or relative
return relative
}
proc plain {str} {
set str [string map "\\\\ /" $str]
set pathinfo [punk::path::pathtype $str]
if {[lindex $pathinfo 0] eq "relative" && ![string match ./* $str]} {
set str ./$str
}
if {[string index $str end] eq "/"} {
if {[string map {/ ""} $str] eq ""} {
#all slash segment
return $str
} else {
if {[lindex $pathinfo 1] ni {volume scheme}} {
return [string range $str 0 end-1]
}
}
}
return $str
}
#purely string based - no reference to filesystem knowledge
#unix-style forward slash only
proc plainjoin {args} {
set args [lmap a $args {string map "\\\\ /" $a}]
#if {[llength $args] == 1} {
# return [lindex $args 0]
#}
set out ""
foreach a $args {
if {![string length $out]} {
append out [plain $a]
} else {
set a [plain $a]
if {[string map {/ ""} $out] eq ""} {
set out [string range $out 0 end-1]
}
if {[string map {/ ""} $a] eq ""} {
#all / segment
append out [string range $a 0 end-1]
} else {
if {[string length $a] > 2 && [string match "./*" $a]} {
set a [string range $a 2 end]
}
if {[string index $out end] eq "/"} {
append out $a
} else {
append out / $a
}
}
}
}
return $out
}
proc plainjoin1 {args} {
if {[llength $args] == 1} {
return [lindex $args 0]
}
set out [trim_final_slash [lindex $args 0]]
foreach a [lrange $args 1 end] {
set a [trim_final_slash $a]
append out / $a
}
return $out
}
#intention?
#proc filepath_dotted_dirname {path} {
#}
proc strip_prefixdepth {path prefix} {
if {$prefix eq ""} {
return [norm $path]
}
return [file join \
{*}[lrange \
[file split [norm $path]] \
[llength [file split [norm $prefix]]] \
end]]
}
proc pathglob_as_re {pathglob} { proc pathglob_as_re {pathglob} {
#*** !doctools #*** !doctools

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

@ -134,13 +134,30 @@ namespace eval punk::repo {
} }
interp alias "" fossil "" punk::repo::fossil_proxy interp alias "" fossil "" punk::repo::fossil_proxy
# ---
# Calling auto_execok on an external tool can be too slow to do during package load (e.g could be 150ms)
#safe interps can't call auto_execok #safe interps can't call auto_execok
#At least let them load the package even though much of it may be unusable depending on the safe configuration #At least let them load the package even though much of it may be unusable depending on the safe configuration
catch { #catch {
if {[auto_execok fossil] ne ""} { # if {[auto_execok fossil] ne ""} {
interp alias "" FOSSIL "" {*}[auto_execok fossil] # interp alias "" FOSSIL "" {*}[auto_execok fossil]
} # }
#}
# ---
# ----------
#
#uppercase FOSSIL to bypass fossil as alias to fossil_proxy
proc establish_FOSSIL {args} {
if {![info exists ::auto_execs(FOSSIL)]} {
set ::auto_execs(FOSSIL) [auto_execok fossil] ;#may fail in safe interp
}
interp alias "" FOSSIL "" ;#delete establishment alias
FOSSIL {*}$args
} }
interp alias "" FOSSIL "" punk::repo::establish_FOSSIL
# ----------
proc askuser {question} { proc askuser {question} {
if {![catch {package require punk::lib}]} { if {![catch {package require punk::lib}]} {
@ -370,7 +387,16 @@ namespace eval punk::repo {
} }
if {$repodir eq ""} { if {$repodir eq ""} {
error "workingdir_state error: No repository found at or above path '$abspath'" puts stderr "workingdir_state error: No repository found at or above path '$abspath'"
puts stderr "args: $args"
dict set resultdict revision {}
dict set resultdict revision_iso8601 {}
dict set resultdict paths {}
dict set resultdict ahead ""
dict set resultdict behind ""
dict set resultdict error {reason "no_repo_found"}
dict set resultdict repotype none
return $resultdict
} }
set subpath [punk::path::relative $repodir $abspath] set subpath [punk::path::relative $repodir $abspath]
if {$subpath eq "."} { if {$subpath eq "."} {
@ -644,6 +670,16 @@ namespace eval punk::repo {
set path_count_fields [list unchanged changed new missing extra] set path_count_fields [list unchanged changed new missing extra]
set state_fields [list ahead behind repodir subpath repotype revision revision_iso8601] set state_fields [list ahead behind repodir subpath repotype revision revision_iso8601]
set dresult [dict create] set dresult [dict create]
if {[dict exists $repostate error]} {
foreach f $state_fields {
dict set dresult $f ""
}
foreach f $path_count_fields {
dict set dresult $f ""
}
#todo?
return $dresult
}
foreach f $state_fields { foreach f $state_fields {
dict set dresult $f [dict get $repostate $f] dict set dresult $f [dict get $repostate $f]
} }

155
src/bootsupport/modules/punk/winpath-0.1.0.tm

@ -30,7 +30,7 @@ namespace eval punk::winpath {
#\\servername\share etc or \\?\UNC\servername\share etc. #\\servername\share etc or \\?\UNC\servername\share etc.
proc is_unc_path {path} { proc is_unc_path {path} {
set strcopy_path [punk::objclone $path] set strcopy_path [punk::winpath::system::objclone $path]
set strcopy_path [string map {\\ /} $strcopy_path] ;#normalize to forward slashes for testing purposes (and forward slashes seem to be auto-translated by windows anyway) set strcopy_path [string map {\\ /} $strcopy_path] ;#normalize to forward slashes for testing purposes (and forward slashes seem to be auto-translated by windows anyway)
if {[string first "//" $strcopy_path] == 0} { if {[string first "//" $strcopy_path] == 0} {
#check for "Dos device path" syntax #check for "Dos device path" syntax
@ -77,7 +77,7 @@ namespace eval punk::winpath {
#dos device path syntax allows windows api to acces extended-length paths and filenames with illegal path characters such as trailing dots or whitespace #dos device path syntax allows windows api to acces extended-length paths and filenames with illegal path characters such as trailing dots or whitespace
#(can exist on server shares and on NTFS - but standard apps can't access without dos device syntax) #(can exist on server shares and on NTFS - but standard apps can't access without dos device syntax)
proc is_dos_device_path {path} { proc is_dos_device_path {path} {
set strcopy_path [punk::objclone $path] set strcopy_path [punk::winpath::system::objclone $path]
set strcopy_path [string map {\\ /} $strcopy_path] ;#normalize to forward slashes for testing purposes (and forward slashes seem to be auto-translated by windows anyway) set strcopy_path [string map {\\ /} $strcopy_path] ;#normalize to forward slashes for testing purposes (and forward slashes seem to be auto-translated by windows anyway)
if {[string range $strcopy_path 0 3] in {//?/ //./}} { if {[string range $strcopy_path 0 3] in {//?/ //./}} {
return 1 return 1
@ -87,7 +87,7 @@ namespace eval punk::winpath {
} }
proc strip_dos_device_prefix {path} { proc strip_dos_device_prefix {path} {
#it's unlikely to be valid to strip only //?/ from a //?/UNC path so check for it here and diver to strip that. #it's unlikely to be valid to strip only //?/ from a //?/UNC path so check for it here and diver to strip that.
#(review.. or raise error because a //?/UNC path isn't *strictly* a UNC path? ) #(review.. or raise error because a //?/UNC path isn't an ordinary dos device path? )
if {[is_unc_path $path]} { if {[is_unc_path $path]} {
return [strip_unc_path_prefix $path] return [strip_unc_path_prefix $path]
} }
@ -98,18 +98,18 @@ namespace eval punk::winpath {
} }
} }
proc strip_unc_path_prefix {path} { proc strip_unc_path_prefix {path} {
if {[is_unc_path $path]} { if {[is_unc_path_plain $path]} {
#//?/UNC/server/etc
set strcopy_path [punk::objclone $path]
set trimmedpath [string range $strcopy_path 7 end]
file pathtype $trimmedpath ;#shimmer it to path rep
return $trimmedpath
} elseif {is_unc_path_plain $path} {
#plain unc //server #plain unc //server
set strcopy_path [punk::objclone $path] set strcopy_path [punk::winpath::system::objclone $path]
set trimmedpath [string range $strcopy_path 2 end] set trimmedpath [string range $strcopy_path 2 end]
file pathtype $trimmedpath file pathtype $trimmedpath
return $trimmedpath return $trimmedpath
} elseif {is_unc_path $path} {
#//?/UNC/server/subpath or //./UNC/server/subpath
set strcopy_path [punk::winpath::system::objclone $path]
set trimmedpath [string range $strcopy_path 7 end]
file pathtype $trimmedpath ;#shimmer it to path rep
return $trimmedpath
} else { } else {
return $path return $path
} }
@ -153,7 +153,7 @@ namespace eval punk::winpath {
error $err error $err
} }
set strcopy_path [punk::objclone $path] set strcopy_path [punk::winpath::system::objclone $path]
#Note: path could still have leading double slash if it is a Dos device path: e.g. //?/c:/etc #Note: path could still have leading double slash if it is a Dos device path: e.g. //?/c:/etc
@ -225,27 +225,124 @@ namespace eval punk::winpath {
return 0 return 0
} }
proc test_ntfs_tunneling {f1 f2 args} { proc shortname {path} {
file mkdir $f1 set shortname "NA"
puts stderr "waiting 15secs..." if {[catch {
after 5000 {puts -nonewline stderr .} set shortname [dict get [file attributes $path] -shortname]
after 5000 {puts -nonewline stderr .} } errM]} {
after 5000 {puts -nonewline stderr .} puts stderr "Failed to get shortname for '$path'"
after 500 {puts stderr \n} }
file mkdir $f2 return $shortname
puts stdout "$f1 [file stat $f1]" }
puts stdout "$f2 [file stat $f2]" proc test_ntfs_tunneling {prefix args} {
file delete $f1 puts stderr "We are looking for whether any of the final $prefix files or dirs took over the ctime attribute of the original $prefix files or dirs"
puts stdout "renaming $f2 to $f1" puts stderr "We expect the ino values to get potentially reassigned depending on order of deletion/creation so matches are coincidental and not material"
file rename $f2 $f1 puts stderr "The shortnames are similarly allocated as they come - so presumably match by coincidence"
puts stdout "$f1 [file stat $f1]" puts stderr "However - if we record a file's shortname, then delete it. Recreating it by shortname within the tunneling timeframe will magically reassociate the longname"
puts stderr "use test_ntfs_tunneling2 to test shortname tunneling"
file mkdir $prefix-dir-rename
file mkdir $prefix-dir-recreate
set fd [open $prefix-file-recreate.txt w]
puts $fd "original for recreate"
close $fd
set fd [open $prefix-file-rename.txt w]
puts $fd "original for rename"
close $fd
puts stdout "ORIGINAL files/dirs"
puts stdout "$prefix-dir-rename [file stat $prefix-dir-rename] "
puts stdout "$prefix-dir-recreate [file stat $prefix-dir-recreate]"
puts stdout "$prefix-file-recreate.txt [file stat $prefix-file-recreate.txt] short:[shortname $prefix-file-recreate.txt]"
puts stdout "$prefix-file-rename.txt [file stat $prefix-file-rename.txt] short:[shortname $prefix-file-rename.txt]"
puts stderr "waiting 10secs (to have discernable ctime differences)"
after 5000
puts -nonewline stderr .
after 5000
puts -nonewline stderr .
after 500
#--
#seems to make no diff whether created or copied - no tunneling seen with dirs
#file mkdir $prefix-dir-rename-temp
file copy $prefix-dir-rename $prefix-dir-rename-temp
#--
puts stderr \n
puts stdout "$prefix-dir-rename-temp [file stat $prefix-dir-rename-temp] (temp to rename into place)"
puts stderr "deleting $prefix-dir-rename"
file delete $prefix-dir-rename
puts stdout "renaming $prefix-dir-rename-temp to $prefix-dir-rename"
file rename $prefix-dir-rename-temp $prefix-dir-rename
puts stderr "deleting $prefix-dir-recreate"
file delete $prefix-dir-recreate
puts stdout "re-creating $prefix-dir-recreate"
file mkdir $prefix-dir-recreate
puts stderr "deleting $prefix-file-recreate.txt"
file delete $prefix-file-recreate.txt
puts stderr "Recreating $prefix-file-recreate.txt"
set fd [open $prefix-file-recreate.txt w]
puts $fd "replacement"
close $fd
puts stderr "copying $prefix-file-rename.txt to $prefix-file-rename-temp.txt"
file copy $prefix-file-rename.txt $prefix-file-rename-temp.txt
puts stdout "$prefix-file-rename-temp.txt [file stat $prefix-file-rename-temp.txt] short:[shortname $prefix-file-rename-temp.txt] (status of initial temp copy)"
puts stderr "modifying temp copy before deletion of original.. (append)"
set fd [open $prefix-file-rename-temp.txt a]
puts $fd "added to file"
close $fd
puts stdout "$prefix-file-rename-temp.txt [file stat $prefix-file-rename-temp.txt] short:[shortname $prefix-file-rename-temp.txt] (status of appended temp copy)"
puts stderr "deleting $prefix-file-rename.txt"
file delete $prefix-file-rename.txt
puts stderr "renaming temp file $prefix-file-rename-temp.txt to original $prefix-file-rename.txt"
file rename $prefix-file-rename-temp.txt $prefix-file-rename.txt
puts stdout "Final files/dirs"
puts stdout "$prefix-dir-rename [file stat $prefix-dir-rename]"
puts stdout "$prefix-dir-recreate [file stat $prefix-dir-recreate]"
puts stdout "$prefix-file-recreate.txt [file stat $prefix-file-recreate.txt] short:[shortname $prefix-file-recreate.txt]"
puts stdout "$prefix-file-rename.txt [file stat $prefix-file-rename.txt] short:[shortname $prefix-file-rename.txt]"
}
proc test_ntfs_tunneling2 {prefix {waitms 15000}} {
#shortname -> longname tunneling
puts stderr "Tunneling only happens if we delete via shortname? review"
set f1 $prefix-longname-file1.txt
set f2 $prefix-longname-file2.txt
set fd [open $f1 w];close $fd
set shortname1 [shortname $f1]
puts stderr "longname:$f1 has shortname:$shortname1"
set fd [open $f2 w];close $fd
set shortname2 [shortname $f2]
puts stderr "longname:$f2 has shortname:$shortname2"
puts stderr "deleting $f1 via name $shortname1"
file delete $shortname1
puts stdout "immediately recreating $shortname1 - should retain longname $f1 via tunneling"
set fd [open $shortname1 w];close $fd
set f1_exists [file exists $f1]
puts stdout "file exists $f1 = $f1_exists"
puts stderr "deleting $f2 via name $shortname2"
file delete $shortname2
puts stderr "Waiting [expr {$waitms / 1000}] seconds.. (standard tunneling timeframe is 15 seconds if registry hasn't been tweaked)"
after $waitms
puts stdout "recreating $shortname2 after wait of $waitms ms - longname lost?"
set fd [open $shortname2 w];close $fd
set f2_exists [file exists $f2]
puts stdout "file exists $f2 = $f2_exists"
puts stdout -done-
} }
} }
namespace eval punk::winpath::system {
#get a copy of the item without affecting internal rep
proc objclone {obj} {
append obj2 $obj {}
}
}

209
src/bootsupport/modules/textblock-0.1.1.tm

@ -12,25 +12,97 @@
# Meta license <unspecified> # Meta license <unspecified>
# @@ Meta End # @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[manpage_begin punkshell_module_textblock 0 0.1.1]
#[copyright "2024"]
#[titledesc {punk textblock functions}] [comment {-- Name section and table of contents description --}]
#[moddesc {punk textblock}] [comment {-- Description at end of page heading --}]
#[require textblock]
#[keywords module utility lib]
#[description]
#[para] Ansi-aware terminal textblock manipulation
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Overview]
#[para] overview of textblock
#[subsection Concepts]
#[para]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements ## Requirements
##e.g package require frobz # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection dependencies]
#[para] packages used by textblock
#[list_begin itemized]
#*** !doctools
#[item] [package {Tcl 8.6-}]
#[item] [package {punk::args}]
#[item] [package {punk::char}]
#[item] [package {punk::ansi}]
#[item] [package {punk::lib}]
#[item] [package {overtype}]
#[item] [package {term::ansi::code::macros}]
#[item] [package {textutil}]
## Requirements
package require Tcl 8.6-
package require punk::args package require punk::args
package require punk::char package require punk::char
package require punk::ansi package require punk::ansi
package require punk::lib package require punk::lib
catch {package require patternpunk} catch {package require patternpunk}
package require overtype package require overtype
#safebase interps as at 2024-08 can't access deeper paths - even though they are below the supposed safe list.
package require term::ansi::code::macros ;#required for frame if old ansi g0 used - review - make package optional? package require term::ansi::code::macros ;#required for frame if old ansi g0 used - review - make package optional?
package require textutil package require textutil
#*** !doctools
#[list_end]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section API]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval textblock { tcl::namespace::eval textblock {
#review - what about ansi off in punk::console? #review - what about ansi off in punk::console?
tcl::namespace::import ::punk::ansi::a ::punk::ansi::a+ tcl::namespace::import ::punk::ansi::a ::punk::ansi::a+
tcl::namespace::export block frame frame_cache framedef frametypes gcross height width widthtopline join join_basic list_as_table pad testblock tcl::namespace::export block frame frame_cache framedef frametypes gcross height width widthtopline join join_basic list_as_table pad testblock
variable use_md5 ;#framecache
set use_md5 1
if {[catch {package require md5}]} {
set use_md5 0
}
proc use_md5 {{yes_no ""}} {
variable use_md5
if {$yes_no eq ""} {
return $use_md5
}
if {![string is boolean -strict $yes_no]} {
error "textblock::use_md5 requires a boolean (or empty string to query)"
}
if {$yes_no} {
package require md5
set use_md5 1
} else {
set use_md5 0
}
return $use_md5
}
tcl::namespace::eval class { tcl::namespace::eval class {
variable opts_table_defaults variable opts_table_defaults
set opts_table_defaults [tcl::dict::create\ set opts_table_defaults [tcl::dict::create\
@ -228,6 +300,7 @@ tcl::namespace::eval textblock {
} }
return $map return $map
} }
if {[tcl::info::commands [tcl::namespace::current]::table] eq ""} { if {[tcl::info::commands [tcl::namespace::current]::table] eq ""} {
#*** !doctools #*** !doctools
#[subsection {Namespace textblock::class}] #[subsection {Namespace textblock::class}]
@ -249,7 +322,7 @@ tcl::namespace::eval textblock {
oo::class create [tcl::namespace::current]::table [tcl::string::map [list %topt_keys% $topt_keys %topt_switchkeys% $switch_keys_valid_topts %copt_keys% $copt_keys %copt_switchkeys% $switch_keys_valid_copts] { oo::class create [tcl::namespace::current]::table [tcl::string::map [list %topt_keys% $topt_keys %topt_switchkeys% $switch_keys_valid_topts %copt_keys% $copt_keys %copt_switchkeys% $switch_keys_valid_copts] {
#*** !doctools #*** !doctools
#[enum] CLASS [class interface_caphandler.registry] #[enum] CLASS [class textblock::class::table]
#[list_begin definitions] #[list_begin definitions]
# [para] [emph METHODS] # [para] [emph METHODS]
variable o_opts_table ;#options as configured by user (with exception of -ansireset) variable o_opts_table ;#options as configured by user (with exception of -ansireset)
@ -3986,7 +4059,7 @@ tcl::namespace::eval textblock {
if append is chosen the new values will always start at the first column" if append is chosen the new values will always start at the first column"
-columns -default "" -type integer -help "Number of table columns -columns -default "" -type integer -help "Number of table columns
Will default to 2 if not using an existing -table object" Will default to 2 if not using an existing -table object"
*values *values -min 0 -max 1
datalist -default {} -help "flat list of table cell values which will be wrapped based on -columns value" datalist -default {} -help "flat list of table cell values which will be wrapped based on -columns value"
}] $args] }] $args]
set opts [dict get $argd opts] set opts [dict get $argd opts]
@ -4337,6 +4410,14 @@ tcl::namespace::eval textblock {
return [tcl::dict::create width $width height $height] ;#maintain order in 'image processing' standard width then height - caller may use lassign [tcl::dict::values [blocksize <data>]] width height return [tcl::dict::create width $width height $height] ;#maintain order in 'image processing' standard width then height - caller may use lassign [tcl::dict::values [blocksize <data>]] width height
} }
proc size_as_opts {textblock} {
set sz [size $textblock]
return [dict create -width [dict get $sz width] -height [dict get $sz height]]
}
proc size_as_list {textblock} {
set sz [size $textblock]
return [list [dict get $sz width] [dict get $sz height]]
}
#must be able to handle block as string with or without newlines #must be able to handle block as string with or without newlines
#if no newlines - attempt to treat as a list #if no newlines - attempt to treat as a list
#must handle whitespace-only string,list elements, and/or lines. #must handle whitespace-only string,list elements, and/or lines.
@ -5061,6 +5142,7 @@ tcl::namespace::eval textblock {
[punk::lib::list_as_lines -- [lrepeat 8 " | "]] [punk::lib::list_as_lines -- [lrepeat 8 " | "]]
} }
proc table {args} { proc table {args} {
#todo - use punk::args
upvar ::textblock::class::opts_table_defaults toptdefaults upvar ::textblock::class::opts_table_defaults toptdefaults
set defaults [tcl::dict::create\ set defaults [tcl::dict::create\
-rows [list]\ -rows [list]\
@ -5112,7 +5194,7 @@ tcl::namespace::eval textblock {
} }
variable frametypes variable frametypes
set frametypes [list light heavy arc double block block1 block2 ascii altg] set frametypes [list light heavy arc double block block1 block2 block2hack ascii altg]
#class::table needs to be able to determine valid frametypes #class::table needs to be able to determine valid frametypes
proc frametypes {} { proc frametypes {} {
variable frametypes variable frametypes
@ -5121,7 +5203,7 @@ tcl::namespace::eval textblock {
proc frametype {f} { proc frametype {f} {
#set custom_keys [list hl hlt hlb vl vll vlr tlc trc blc brc] #set custom_keys [list hl hlt hlb vl vll vlr tlc trc blc brc]
switch -- $f { switch -- $f {
light - heavy - arc - double - block - block1 - block2 - ascii - altg { light - heavy - arc - double - block - block1 - block2 - block2hack - ascii - altg {
return [tcl::dict::create category predefined type $f] return [tcl::dict::create category predefined type $f]
} }
default { default {
@ -5142,7 +5224,7 @@ tcl::namespace::eval textblock {
set is_custom_dict_ok 0 set is_custom_dict_ok 0
} }
if {!$is_custom_dict_ok} { if {!$is_custom_dict_ok} {
error "frame option -type must be one of known types: $textblock::frametypes or a dictionary with any of keys hl,hlt,hlb,vl,vll,vlr,tlc,trc,blc,brc" error "frame option -type must be one of known types: $::textblock::frametypes or a dictionary with any of keys hl,hlt,hlb,vl,vll,vlr,tlc,trc,blc,brc"
} }
set default_custom [tcl::dict::create hl " " vl " " 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] set custom_frame [tcl::dict::merge $default_custom $f]
@ -6252,9 +6334,12 @@ tcl::namespace::eval textblock {
set vlr \u2595 ;# right one eighth block set vlr \u2595 ;# right one eighth block
set vll \u258f ;# left one eighth block set vll \u258f ;# left one eighth block
#some terminals (on windows as at 2024) miscount width of these single-width blocks internally
#resulting in extra spacing that is sometimes well removed from the character itself (e.g at next ansi reset)
#This was fixed in windows-terminal based systems (2021) but persists in others.
#https://github.com/microsoft/terminal/issues/11694
set tlc \U1fb7d ;#legacy block set tlc \U1fb7d ;#legacy block
set trc \U1fb7e ;#legacy block set trc \U1fb7e ;#legacy block
set blc \U1fb7c ;#legacy block set blc \U1fb7c ;#legacy block
set brc \U1fb7f ;#legacy block set brc \U1fb7f ;#legacy block
@ -6265,6 +6350,42 @@ tcl::namespace::eval textblock {
set vlrj $vlr set vlrj $vlr
} }
block2hack {
#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 vlr \u2595 ;# right one eighth block
set vll \u258f ;# left one eighth block
#see comments in block2 regarding the problems in some terminals that this *may* hack around to some extent.
#the caller probably only needs block2hack if block2 doesn't work
#1)
#review - this hack looks sort of promising - but overtype::renderline needs fixing ?
#set tlc \U1fb7d\b ;#legacy block
#set trc \U1fb7e\b ;#legacy block
#set blc \U1fb7c\b ;#legacy block
#set brc \U1fb7f\b ;#legacy block
#2) - works on cmd.exe and some others
# a 'privacy message' is 'probably' also not supported on the old terminal but is on newer ones
#known exception - conemu on windows - displays junk for various ansi codes - (and slow terminal anyway)
#this hack has a reasonable chance of working
#except that the punk overtype library does recognise PMs
#A single backspace however is an unlikely and generally unuseful PM - so there is a corresponding hack in the renderline system to pass this PM through!
#ugly - in that we don't know the application specifics of what the PM data contains and where it's going.
set tlc \U1fb7d\x1b^\b\x1b\\ ;#legacy block
set trc \U1fb7e\x1b^\b\x1b\\ ;#legacy block
set blc \U1fb7c\x1b^\b\x1b\\ ;#legacy block
set brc \U1fb7f\x1b^\b\x1b\\ ;#legacy block
#horizontal and vertical bar joins
set hltj $hlt
set hlbj $hlb
set vllj $vll
set vlrj $vlr
}
block { block {
set hlt \u2580 ;#upper half set hlt \u2580 ;#upper half
set hlb \u2584 ;#lower half set hlb \u2584 ;#lower half
@ -6286,7 +6407,7 @@ tcl::namespace::eval textblock {
set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "] ;#only default the general types - these form defaults for more specific types if they're missing set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "] ;#only default the general types - these form defaults for more specific types if they're missing
if {[llength $f] % 2 != 0} { if {[llength $f] % 2 != 0} {
#todo - retrieve usage from punk::args #todo - retrieve usage from punk::args
error "textblock::frametype frametype '$f' is not one of the predefined frametypes: $textblock::frametypes and does not appear to be a dictionary for a custom frametype" error "textblock::frametype frametype '$f' is not one of the predefined frametypes: $::textblock::frametypes and does not appear to be a dictionary for a custom frametype"
} }
#unknown order of keys specified by user - validate before creating vars as we need more general elements to be available as defaults #unknown order of keys specified by user - validate before creating vars as we need more general elements to be available as defaults
dict for {k v} $f { dict for {k v} $f {
@ -6388,7 +6509,7 @@ tcl::namespace::eval textblock {
#options before content argument - which is allowed to be absent #options before content argument - which is allowed to be absent
#frame performance (noticeable with complex tables even of modest size) is improved significantly by frame_cache - but is still (2024) a fairly expensive operation. #frame performance (noticeable with complex tables even of modest size) is improved somewhat by frame_cache - but is still (2024) a fairly expensive operation.
# #
#consider if we can use -sticky nsew instead of -blockalign (as per Tk grid -sticky option) #consider if we can use -sticky nsew instead of -blockalign (as per Tk grid -sticky option)
# This would require some sort of expand equivalent e.g for -sticky ew or -sticky ns - for which we have to decide a meaning for text.. e.g ansi padding? # This would require some sort of expand equivalent e.g for -sticky ew or -sticky ns - for which we have to decide a meaning for text.. e.g ansi padding?
@ -6397,6 +6518,7 @@ tcl::namespace::eval textblock {
# - but we would need to maintain support for the rendered-string based operations too. # - but we would need to maintain support for the rendered-string based operations too.
proc frame {args} { proc frame {args} {
variable frametypes variable frametypes
variable use_md5
#counterintuitively - in-proc dict create each time is generally slightly faster than linked namespace var #counterintuitively - in-proc dict create each time is generally slightly faster than linked namespace var
set opts [tcl::dict::create\ set opts [tcl::dict::create\
@ -6416,7 +6538,11 @@ tcl::namespace::eval textblock {
-ellipsis 1\ -ellipsis 1\
-usecache 1\ -usecache 1\
-buildcache 1\ -buildcache 1\
-pad 1\
-crm_mode 0\
] ]
#-pad 1 is default so that simple 'textblock::frame "[a+ Red]a \nbbb[a]" extends the bg colour on the short ragged lines (and empty lines)
# for ansi art - -pad 0 is likely to be preferable
set expect_optval 0 set expect_optval 0
set argposn 0 set argposn 0
@ -6455,7 +6581,12 @@ tcl::namespace::eval textblock {
#use -buildcache 1 with -usecache 0 for debugging cache issues so we can inspect using textblock::frame_cache #use -buildcache 1 with -usecache 0 for debugging cache issues so we can inspect using textblock::frame_cache
foreach {k v} $arglist { foreach {k v} $arglist {
switch -- $k { switch -- $k {
-etabs - -type - -boxlimits - -boxmap - -joins - -title - -subtitle - -width - -height - -ansiborder - -ansibase - -blockalign - -textalign - -ellipsis - -usecache - -buildcache { -etabs - -type - -boxlimits - -boxmap - -joins
- -title - -subtitle - -width - -height
- -ansiborder - -ansibase
- -blockalign - -textalign - -ellipsis
- -crm_mode
- -usecache - -buildcache - -pad {
tcl::dict::set opts $k $v tcl::dict::set opts $k $v
} }
default { default {
@ -6471,11 +6602,13 @@ tcl::namespace::eval textblock {
set opt_boxmap [tcl::dict::get $opts -boxmap] set opt_boxmap [tcl::dict::get $opts -boxmap]
set opt_usecache [tcl::dict::get $opts -usecache] set opt_usecache [tcl::dict::get $opts -usecache]
set opt_buildcache [tcl::dict::get $opts -buildcache] set opt_buildcache [tcl::dict::get $opts -buildcache]
set opt_pad [tcl::dict::get $opts -pad]
set opt_crm_mode [tcl::dict::get $opts -crm_mode]
set usecache $opt_usecache ;#may need to override set usecache $opt_usecache ;#may need to override
set buildcache $opt_buildcache set buildcache $opt_buildcache
set custom_keys [list hl hlt hlb vl vll vlr tlc trc blc brc] set custom_keys [list hl hlt hlb vl vll vlr tlc trc blc brc]
set known_frametypes $frametypes ;# light, heavey etc as defined in textblock::frametypes variable set known_frametypes $frametypes ;# light, heavey etc as defined in the ::textblock::frametypes variable
set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "] set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "]
lassign [textblock::frametype $opt_type] _cat category _type ftype lassign [textblock::frametype $opt_type] _cat category _type ftype
@ -6614,6 +6747,19 @@ tcl::namespace::eval textblock {
} }
} }
set contents [tcl::string::map [list \r\n \n] $contents] set contents [tcl::string::map [list \r\n \n] $contents]
if {$opt_crm_mode} {
if {$opt_height eq ""} {
set h [textblock::height $contents]
} else {
set h [expr {$opt_height -2}]
}
if {$opt_width eq ""} {
set w [textblock::width $contents]
} else {
set w [expr {$opt_width -2}]
}
set contents [overtype::renderspace -crm_mode 1 -wrap 1 -width $w -height $h "" $contents]
}
set actual_contentwidth [textblock::width $contents] ;#length of longest line in contents (contents can be ragged) set actual_contentwidth [textblock::width $contents] ;#length of longest line in contents (contents can be ragged)
set actual_contentheight [textblock::height $contents] set actual_contentheight [textblock::height $contents]
} else { } else {
@ -6652,9 +6798,14 @@ tcl::namespace::eval textblock {
#review - custom frame affects frame_inner_width - exclude from caching? #review - custom frame affects frame_inner_width - exclude from caching?
#set cache_key [concat $arglist $frame_inner_width $frame_inner_height] #set cache_key [concat $arglist $frame_inner_width $frame_inner_height]
set hashables [concat $arglist $frame_inner_width $frame_inner_height] set hashables [concat $arglist $frame_inner_width $frame_inner_height]
package require md5
#set hash $hashables if {$use_md5} {
set hash [md5::md5 -hex [encoding convertto utf-8 $hashables]] ;#need fast and unique to content - not cryptographic - review #package require md5 ;#already required at package load
set hash [md5::md5 -hex [encoding convertto utf-8 $hashables]] ;#need fast and unique to content - not cryptographic - review
} else {
set hash $hashables
}
set cache_key "$hash-$frame_inner_width-$frame_inner_height-actualcontentwidth:$actual_contentwidth" set cache_key "$hash-$frame_inner_width-$frame_inner_height-actualcontentwidth:$actual_contentwidth"
#should be in a unicode private range different to that used in table construction #should be in a unicode private range different to that used in table construction
#e.g BMP PUA U+E000 -> U+F8FF - although this is commonly used for example by nerdfonts #e.g BMP PUA U+E000 -> U+F8FF - although this is commonly used for example by nerdfonts
@ -7057,15 +7208,22 @@ tcl::namespace::eval textblock {
append contents [::join [lrepeat $diff \n] ""] append contents [::join [lrepeat $diff \n] ""]
} }
set paddedcontents [textblock::pad $contents -which $pad -within_ansi 1] ;#autowidth to width of content (should match cache_patternwidth) if {$opt_pad} {
set paddedwidth [textblock::widthtopline $paddedcontents] set paddedcontents [textblock::pad $contents -which $pad -within_ansi 1] ;#autowidth to width of content (should match cache_patternwidth)
set paddedwidth [textblock::widthtopline $paddedcontents]
#review - horizontal truncation #review - horizontal truncation
if {$paddedwidth > $cache_patternwidth} { if {$paddedwidth > $cache_patternwidth} {
set paddedcontents [overtype::renderspace -width $cache_patternwidth "" $paddedcontents] set paddedcontents [overtype::renderspace -width $cache_patternwidth "" $paddedcontents]
}
#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
} else {
set cwidth [textblock::width $contents]
if {$cwidth > $cache_patternwidth} {
set contents [overtype::renderspace -width $cache_patternwidth "" $contents]
}
set contentblock [textblock::join -- $contents]
} }
#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]
@ -7183,7 +7341,6 @@ tcl::namespace::eval textblock {
#fastest to do row first then columns - because textblock::join must do line by line #fastest to do row first then columns - because textblock::join must do line by line
if {$crosscount > 1} { if {$crosscount > 1} {
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]
@ -7224,3 +7381,7 @@ package provide textblock [tcl::namespace::eval textblock {
set version 0.1.1 set version 0.1.1
}] }]
return return
#*** !doctools
#[manpage_end]

77
src/make.tcl

@ -1212,8 +1212,9 @@ foreach vfstail $vfs_tails {
set rtmountpoint //zipfs:/rtmounts/$runtime_fullname set rtmountpoint //zipfs:/rtmounts/$runtime_fullname
set changed_unchanged [$vfs_event targetset_source_changes] set changed_unchanged [$vfs_event targetset_source_changes]
set vfs_or_runtime_changed [expr {[llength [dict get $changed_unchanged changed]] || [llength [$vfs_event get_targets_exist]] < [llength [$vfs_event get_targets]]}]
if {[llength [dict get $changed_unchanged changed]] || [llength [$vfs_event get_targets_exist]] < [llength [$vfs_event get_targets]]} { if {$vfs_or_runtime_changed} {
#source .vfs folder has changes #source .vfs folder has changes
$vfs_event targetset_started $vfs_event targetset_started
# -- --- --- --- --- --- # -- --- --- --- --- ---
@ -1283,6 +1284,7 @@ foreach vfstail $vfs_tails {
puts stderr "RUNTIME capabilities unknown. Unsure if zip supported. trying anyway.." puts stderr "RUNTIME capabilities unknown. Unsure if zip supported. trying anyway.."
} }
} }
#note - as at 2024-08 - there is some discussion about the interface to mkimg - it is considered unstable (may change to -option value syntax)
puts stderr "calling: tcl::zipfs::mkimg $buildfolder/$vfsname.new $wrapvfs $wrapvfs \"\" $buildfolder/build_$runtime_fullname" puts stderr "calling: tcl::zipfs::mkimg $buildfolder/$vfsname.new $wrapvfs $wrapvfs \"\" $buildfolder/build_$runtime_fullname"
tcl::zipfs::mkimg $buildfolder/$vfsname.new $wrapvfs $wrapvfs "" $buildfolder/build_$runtime_fullname tcl::zipfs::mkimg $buildfolder/$vfsname.new $wrapvfs $wrapvfs "" $buildfolder/build_$runtime_fullname
} result ]} { } result ]} {
@ -1352,9 +1354,10 @@ foreach vfstail $vfs_tails {
if {![catch { if {![catch {
exec $pscmd | grep $targetkit exec $pscmd | grep $targetkit
} still_running]} { } still_running]} {
set still_running_lines [split [string trim $still_running] \n]
puts stdout "found $targetkit instances still running\n" puts stdout "found ([llength $still_running_lines]) $targetkit instances still running\n"
set count_killed 0 set count_killed 0
set num_to_kill [llength $still_running_lines]
foreach ln [split $still_running \n] { foreach ln [split $still_running \n] {
puts stdout " $ln" puts stdout " $ln"
@ -1387,9 +1390,6 @@ foreach vfstail $vfs_tails {
#review - *no running instance* works with windows taskkill - "*No such process*" works with kill -9 on FreeBSD and linux - other platforms? #review - *no running instance* works with windows taskkill - "*No such process*" works with kill -9 on FreeBSD and linux - other platforms?
if {![string match "*no running instance*" $errMsg] && ![string match "*No such process*" $errMsg]} { if {![string match "*no running instance*" $errMsg] && ![string match "*No such process*" $errMsg]} {
lappend failed_kits [list kit $targetkit reason "could not kill running process for $targetkit (using '$killcmd')"] lappend failed_kits [list kit $targetkit reason "could not kill running process for $targetkit (using '$killcmd')"]
$vfs_event targetset_end FAILED
$vfs_event destroy
$vfs_installer destroy
continue continue
} }
} else { } else {
@ -1397,10 +1397,15 @@ foreach vfstail $vfs_tails {
incr count_killed incr count_killed
} }
} }
if {$count_killed > 0} { if {$count_killed < $num_to_kill} {
puts stderr "\nKilled $count_killed processes. Waiting a short time before attempting to delete executable" $vfs_event targetset_end FAILED
after 1000 $vfs_event destroy
$vfs_installer destroy
continue
} }
puts stderr "\nKilled $count_killed processes. Waiting a short time before attempting to delete executable"
after 1000
} else { } else {
puts stderr "Ok.. no running '$targetkit' processes found" puts stderr "Ok.. no running '$targetkit' processes found"
} }
@ -1426,22 +1431,35 @@ foreach vfstail $vfs_tails {
# -- --- --- --- --- --- # -- --- --- --- --- ---
$vfs_event targetset_end OK $vfs_event targetset_end OK
} else {
set skipped_vfs_build 1
puts stderr "."
puts stdout "Skipping build for vfs $vfstail with runtime $rtname - no change detected"
$vfs_event targetset_end SKIPPED
}
$vfs_event destroy
$vfs_installer destroy
after 200 after 200
set deployment_folder [file dirname $sourcefolder]/bin set deployment_folder [file dirname $sourcefolder]/bin
file mkdir $deployment_folder file mkdir $deployment_folder
# -- ---------- # -- ----------
set bin_installer [punkcheck::installtrack new "make.tcl" $deployment_folder/.punkcheck] set bin_installer [punkcheck::installtrack new "make.tcl" $deployment_folder/.punkcheck]
$bin_installer set_source_target $buildfolder $deployment_folder $bin_installer set_source_target $buildfolder $deployment_folder
set bin_event [$bin_installer start_event {-make-step final_kit_install}] set bin_event [$bin_installer start_event {-make-step final_kit_install}]
$bin_event targetset_init INSTALL $deployment_folder/$targetkit $bin_event targetset_init INSTALL $deployment_folder/$targetkit
#todo - move final deployment step outside of the build vfs loop? (final deployment can fail and then isn't rerun even though _build and deployed versions differ, unless .vfs modified again) #todo - move final deployment step outside of the build vfs loop? (final deployment can fail and then isn't rerun even though _build and deployed versions differ, unless .vfs modified again)
#set last_completion [$bin_event targetset_last_complete] #set last_completion [$bin_event targetset_last_complete]
$bin_event targetset_addsource $buildfolder/$targetkit $bin_event targetset_addsource $deployment_folder/$targetkit ;#add target as a source of metadata for change detection
$bin_event targetset_started $bin_event targetset_addsource $buildfolder/$targetkit
# -- ---------- $bin_event targetset_started
# -- ----------
set changed_unchanged [$bin_event targetset_source_changes]
set built_or_installed_kit_changed [expr {[llength [dict get $changed_unchanged changed]] || [llength [$bin_event get_targets_exist]] < [llength [$bin_event get_targets]]}]
if {$built_or_installed_kit_changed} {
if {[file exists $deployment_folder/$targetkit]} { if {[file exists $deployment_folder/$targetkit]} {
puts stderr "deleting existing deployed at $deployment_folder/$targetkit" puts stderr "deleting existing deployed at $deployment_folder/$targetkit"
@ -1467,19 +1485,16 @@ foreach vfstail $vfs_tails {
# -- ---------- # -- ----------
$bin_event targetset_end OK $bin_event targetset_end OK
# -- ---------- # -- ----------
$bin_event destroy
$bin_installer destroy
} else { } else {
set skipped_vfs_build 1 set skipped_kit_install 1
puts stderr "." puts stderr "."
puts stdout "Skipping build for vfs $vfstail with runtime $rtname - no change detected" puts stdout "Skipping kit install for $targetkit with vfs $vfstail runtime $rtname - no change detected"
$vfs_event targetset_end SKIPPED $bin_event targetset_end SKIPPED
} }
$bin_event destroy
$bin_installer destroy
$vfs_event destroy
$vfs_installer destroy
} ;#end foreach targetkit } ;#end foreach targetkit
} ;#end foreach rtname in runtimes } ;#end foreach rtname in runtimes

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

@ -106,7 +106,7 @@ tcl::namespace::eval punk::ansi::class {
#overflow is a different concept - perhaps not particularly congruent with the idea of the textblock as a mini terminal emulator. #overflow is a different concept - perhaps not particularly congruent with the idea of the textblock as a mini terminal emulator.
#overflow effectively auto-expands the block(terminal?) width #overflow effectively auto-expands the block(terminal?) width
#overflow and wrap both being true won't make sense unless we implement a max_overflow concept #overflow and wrap both being true won't make sense unless we implement a max_overflow concept
set o_rendered [overtype::renderspace -overflow 0 -wrap 1 -width $w -height $h -appendlines 1 "" [$o_ansistringobj get]] set o_rendered [overtype::renderspace -expand_right 0 -wrap 1 -width $w -height $h -appendlines 1 "" [$o_ansistringobj get]]
if {$cksum eq "not-done"} { if {$cksum eq "not-done"} {
#if dimensions changed - the checksum won't have been done #if dimensions changed - the checksum won't have been done
set o_rendered_what [$o_ansistringobj checksum] set o_rendered_what [$o_ansistringobj checksum]
@ -129,7 +129,7 @@ tcl::namespace::eval punk::ansi::class {
set o_dimensions $dimensions set o_dimensions $dimensions
set rendered [overtype::renderspace -experimental {test_mode} -overflow 0 -wrap 1 -width $w -height $h -appendlines 1 "" [$o_ansistringobj get]] set rendered [overtype::renderspace -cp437 1 -expand_right 0 -wrap 1 -width $w -height $h -appendlines 1 "" [$o_ansistringobj get]]
return $rendered return $rendered
} }
method render_to_input_line {args} { method render_to_input_line {args} {
@ -176,7 +176,7 @@ tcl::namespace::eval punk::ansi::class {
if {$opt_minus ne "0"} { if {$opt_minus ne "0"} {
set chunk [tcl::string::range $chunk 0 end-$opt_minus] set chunk [tcl::string::range $chunk 0 end-$opt_minus]
} }
set rendered [overtype::renderspace -experimental {test_mode} -overflow 0 -wrap 1 -width $w -height $h -appendlines 1 "" $chunk] set rendered [overtype::renderspace -expand_right 0 -wrap 1 -width $w -height $h -appendlines 1 "" $chunk]
set marker "" set marker ""
for {set i 1} {$i <= $w} {incr i} { for {set i 1} {$i <= $w} {incr i} {
if {$i % 10 == 0} { if {$i % 10 == 0} {
@ -514,11 +514,8 @@ tcl::namespace::eval punk::ansi {
set encnames [encoding names] set encnames [encoding names]
set encoding "" set encoding ""
set dimensions "" set dimensions ""
set test_mode 0
foreach a $args { foreach a $args {
if {$a eq "test_mode"} { if {$a in $encnames} {
set test_mode 1
} elseif {$a in $encnames} {
set encoding $a set encoding $a
} else { } else {
if {[regexp {[0-9]+(?:x|X)[0-9]+} $a]} { if {[regexp {[0-9]+(?:x|X)[0-9]+} $a]} {
@ -553,28 +550,51 @@ tcl::namespace::eval punk::ansi {
$obj destroy $obj destroy
return $result return $result
} }
proc example {} { proc example {args} {
set base [punk::repo::find_project]
set default_ansibase [file join $base src/testansi]
set argd [punk::args::get_dict [tstr -return string {
*proc -name punk::ansi::example -help "Display .ans image files in a grid that will fit in console
"
-colwidth -default 82 -help "Width of each column - default of 82 will fit a standard 80wide ansi image (when framed)
You can specify a narrower width to truncate images on the right side"
-folder -default "${$default_ansibase}" -help "Base folder for files if relative paths are used.
Defaults to <projectbase>/src/testansi - where projectbase is determined from current directory.
"
*values -min 0 -max -1
files -default {belinda.ans bot.ans flower.ans fish.ans} -multiple true -help "List of filenames - leave empty to display 4 defaults"
}] $args]
set colwidth [dict get $argd opts -colwidth]
set ansibase [file normalize [dict get $argd opts -folder]]
set fnames [dict get $argd values files]
#assumes fixed column widths e.g 80col images will fit in 82-width frames (common standard for old ansi art) (of arbitrary height)
#todo - review dependency on punk::repo ? #todo - review dependency on punk::repo ?
package require textblock package require textblock
package require punk::repo package require punk::repo
package require punk::console package require punk::console
set fnames [list belinda.ans bot.ans flower.ans fish.ans]
set base [punk::repo::find_project]
set ansibase [file join $base src/testansi]
if {![file exists $ansibase]} { if {![file exists $ansibase]} {
puts stderr "Missing testansi folder at $base/src/testansi" puts stderr "Missing folder at $ansibase"
puts stderr "Ensure ansi test files exist: $fnames" puts stderr "Ensure ansi test files exist: $fnames"
#error "punk::ansi::example Cannot find example files" #error "punk::ansi::example Cannot find example files"
} }
set missingbase [a+ yellow][textblock::block 80 23 ?][a] set missingbase [a+ yellow][textblock::block [expr {$colwidth-2}] 23 ?][a] ;#assuming standard frame - subtract 2 for left/right borders
set pics [list] set pics [list]
foreach f $fnames { foreach f $fnames {
if {![file exists $ansibase/$f]} { if {[file pathtype $f] ne "absolute"} {
set p [overtype::left $missingbase "[a+ red bold]\nMissing file\n$ansibase/$f[a]"] set filepath [file normalize $ansibase/$f]
} else {
set filepath [file normalize $f]
}
if {![file exists $filepath]} {
set p [overtype::left $missingbase "[a+ red bold]\nMissing file\n$f[a]"]
lappend pics [tcl::dict::create filename $f pic $p status missing] lappend pics [tcl::dict::create filename $f pic $p status missing]
} else { } else {
set img [join [lines_as_list -line trimline -block trimtail [ansicat $ansibase/$f]] \n] #set img [join [lines_as_list -line trimline -block trimtail [ansicat $filepath]] \n]
#-line trimline will wreck some images
set img [join [lines_as_list -block trimtail [ansicat $filepath]] \n]
lappend pics [tcl::dict::create filename $f pic $img status ok] lappend pics [tcl::dict::create filename $f pic $img status ok]
} }
} }
@ -582,30 +602,73 @@ tcl::namespace::eval punk::ansi {
set termsize [punk::console:::get_size] set termsize [punk::console:::get_size]
set margin 4 set margin 4
set freewidth [expr {[tcl::dict::get $termsize columns]-$margin}] set freewidth [expr {[tcl::dict::get $termsize columns]-$margin}]
set per_row [expr {$freewidth / 80}] set per_row [expr {$freewidth / $colwidth}]
set rowlist [list] set rowlist [list] ;# { {<img> <img>} {<img> <img>} }
set row [list] set heightlist [list] ;# { {<h> <h> } {<h> <h> } }
set i 1 set maxheights [list] ;# { <max> <max>}
set row [list] ;#wip row
set rowh [list] ;#wip row img heights
set i 1 ;#track image index of whole pics list
set rowindex 0
foreach picinfo $pics { foreach picinfo $pics {
set subtitle "" set subtitle ""
if {[tcl::dict::get $picinfo status] ne "ok"} { if {[tcl::dict::get $picinfo status] ne "ok"} {
set subtitle [tcl::dict::get $picinfo status] set subtitle [tcl::dict::get $picinfo status]
} }
set title [tcl::dict::get $picinfo filename] set title [tcl::dict::get $picinfo filename]
lappend row [textblock::frame -subtitle $subtitle -title $title [tcl::dict::get $picinfo pic]] set fr [textblock::frame -width $colwidth -subtitle $subtitle -title $title [tcl::dict::get $picinfo pic]]
# -- --- --- ---
#we need the max height of a row element to use join_basic instead of join below
# -- --- --- ---
set fr_height [textblock::height $fr]
lappend row $fr
lappend rowh $fr_height
set rowmax [lindex $maxheights $rowindex]
if {$rowmax eq ""} {
#empty result means no maxheights entry for this row yet
set rowmax $fr_height
lappend maxheights $rowmax
} else {
if {$fr_height > $rowmax} {
set rowmax $fr_height
lset maxheights end $rowmax
}
}
# -- --- --- ---
if {$i % $per_row == 0} { if {$i % $per_row == 0} {
lappend rowlist $row lappend rowlist $row
lappend heightlist $rowh
incr rowindex
set row [list] set row [list]
set rowh [list]
} elseif {$i == [llength $pics]} { } elseif {$i == [llength $pics]} {
lappend rowlist $row lappend rowlist $row
lappend heightlist $rowh
} }
incr i incr i
} }
#puts "--> maxheights: $maxheights"
#puts "--> heightlist: $heightlist"
set result "" set result ""
foreach r $rowlist { set rowindex 0
append result [textblock::join_basic -- {*}$r] \n set blankline [string repeat " " $colwidth]
foreach imgs $rowlist heights $heightlist {
set maxheight [lindex $maxheights $rowindex]
set adjusted_row [list]
foreach i $imgs h $heights {
if {$h < $maxheight} {
#add blank lines to bottom of shorter images so join_basic can be used.
#textblock::join of ragged-height images would work and remove the need for all the height calculation
#.. but it requires much more processing
append i [string repeat \n$blankline [expr {$maxheight - $h}]]
}
lappend adjusted_row $i
}
append result [textblock::join_basic -- {*}$adjusted_row] \n
incr rowindex
} }
@ -3199,6 +3262,28 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
return \x1b8 return \x1b8
} }
# -- --- --- --- --- # -- --- --- --- ---
#CRM Show Control Character Mode
proc enable_crm {} {
return \x1b\[3h
}
proc disable_crm {} {
return \x1b\[3l
}
#DECSNM
#Note this can invert the enclosed section including any already reversed by SGR 7 - depending on terminal support.
#e.g
#set test [a+ reverse]aaa[a+ noreverse]bbb
# - $test above can't just be reversed by putting another [a+ reverse] in front of it.
# - but the following will work (even if underlying terminal doesn't support ?5 sequences)
#overtype::renderspace -width 20 [enable_inverse]$test
proc enable_inverse {} {
return \x1b\[?5h
}
proc disable_inverse {} {
return \x1b\[?5l
}
#DECAWM - automatic line wrapping #DECAWM - automatic line wrapping
proc enable_line_wrap {} { proc enable_line_wrap {} {
@ -3399,6 +3484,9 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#the purpose of backspace (or line cr) in embedded text is unclear. Should it allow some sort of character combining/overstrike as it has sometimes done historically (nroff/less)? e.g a\b` as an alternative combiner or bolding if same char #the purpose of backspace (or line cr) in embedded text is unclear. Should it allow some sort of character combining/overstrike as it has sometimes done historically (nroff/less)? e.g a\b` as an alternative combiner or bolding if same char
#This should presumably only be done if the over_strike (os) capability is enabled in the terminal. Either way - it presumably won't affect printing width? #This should presumably only be done if the over_strike (os) capability is enabled in the terminal. Either way - it presumably won't affect printing width?
set line [punk::ansi::ansistrip $line] set line [punk::ansi::ansistrip $line]
#ANSI (e.g PM/SOS) can contain \b or \n or \t but won't contribute to length
#ansistrip must come before any other processing of these chars.
#we can't use simple \b processing if we get ansi codes and aren't actually processing them (e.g moves) #we can't use simple \b processing if we get ansi codes and aren't actually processing them (e.g moves)
set line [punk::char::strip_nonprinting_ascii $line] ;#only strip nonprinting after ansistrip - some like BEL are part of ansi set line [punk::char::strip_nonprinting_ascii $line] ;#only strip nonprinting after ansistrip - some like BEL are part of ansi
@ -3748,6 +3836,7 @@ tcl::namespace::eval punk::ansi {
-filter_fg 0\ -filter_fg 0\
-filter_bg 0\ -filter_bg 0\
-filter_reset 0\ -filter_reset 0\
-info 0\
] ]
#codes *must* already have been split so that one esc per element in codelist #codes *must* already have been split so that one esc per element in codelist
@ -3760,7 +3849,8 @@ tcl::namespace::eval punk::ansi {
set opts $defaultopts_sgr_merge_singles set opts $defaultopts_sgr_merge_singles
foreach {k v} $args { foreach {k v} $args {
switch -- $k { switch -- $k {
-filter_fg - -filter_bg - -filter_reset { -filter_fg - -filter_bg - -filter_reset -
-info {
tcl::dict::set opts $k $v tcl::dict::set opts $k $v
} }
default { default {
@ -4139,19 +4229,24 @@ tcl::namespace::eval punk::ansi {
set codemerge [tcl::string::trimright $codemerge {;}] set codemerge [tcl::string::trimright $codemerge {;}]
if {$unmergeable ne ""} { if {$unmergeable ne ""} {
set unmergeable [tcl::string::trimright $unmergeable {;}] set unmergeable [tcl::string::trimright $unmergeable {;}]
return "\x1b\[${codemerge}m\x1b\[${unmergeable}m[join $othercodes ""]" set mergeresult "\x1b\[${codemerge}m\x1b\[${unmergeable}m[join $othercodes ""]"
} else { } else {
return "\x1b\[${codemerge}m[join $othercodes ""]" set mergeresult "\x1b\[${codemerge}m[join $othercodes ""]"
} }
} else { } else {
if {$unmergeable eq ""} { if {$unmergeable eq ""} {
#there were no SGR codes - not even resets #there were no SGR codes - not even resets
return [join $othercodes ""] set mergeresult [join $othercodes ""]
} else { } else {
set unmergeable [tcl::string::trimright $unmergeable {;}] set unmergeable [tcl::string::trimright $unmergeable {;}]
return "\x1b\[${unmergeable}m[join $othercodes ""]" set mergeresult "\x1b\[${unmergeable}m[join $othercodes ""]"
} }
} }
if {[tcl::dict::get $opts -info]} {
return [dict create sgr $codemerge unmergeable $unmergeable othercodes $othercodes mergeresult $mergeresult codestate $codestate]
} else {
return $mergeresult
}
} }
#has_sgr_reset - rather than support this function - create an sgr normalize function that removes dead params and brings reset to front of param list? #has_sgr_reset - rather than support this function - create an sgr normalize function that removes dead params and brings reset to front of param list?
@ -4240,7 +4335,7 @@ tcl::namespace::eval punk::ansi::ta {
#we also need to track the start of ST terminated code and not add it for replay (in the ansistring functions) #we also need to track the start of ST terminated code and not add it for replay (in the ansistring functions)
#variable re_ST {(?:\x1bX|\u0098|\x1b\^|\u009E|\x1b_|\u009F)(?:[^\x1b\007\u009c]*)(?:\x1b\\|\007|\u009c)} ;#downsides: early terminating with nests, mixes 7bit 8bit start/ends (does that exist in the wild?) #variable re_ST {(?:\x1bX|\u0098|\x1b\^|\u009E|\x1b_|\u009F)(?:[^\x1b\007\u009c]*)(?:\x1b\\|\007|\u009c)} ;#downsides: early terminating with nests, mixes 7bit 8bit start/ends (does that exist in the wild?)
#keep our 8bit/7bit start-end codes separate #keep our 8bit/7bit start-end codes separate
variable re_ST {(?:\x1bP|\x1bX|\x1b\^|\x1b_)(?:(?!\x1b\\|007).)*(?:\x1b\\|\007)|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)} variable re_ST {(?:\x1bP|\x1bX|\x1b\^|\x1b_)(?:(?!\x1b\\|\007).)*(?:\x1b\\|\007)|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)}
@ -4252,7 +4347,7 @@ tcl::namespace::eval punk::ansi::ta {
# -- --- --- --- # -- --- --- ---
#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.
variable re_ansi_detect {(?:\x1b(?:\((?:0|B)|\[(?:[\x20-\x2f\x30-\x3f]*[\x40-\x7e])|\](?:(?:[^\007]*)\007|(?:(?!\x1b\\).)*\x1b\\)|(?:P|X|\^|_)(?:(?:(?!\x1b\\|007).)*(?:\x1b\\|\007))|c|7|8|M|E|D|H|=|>|(?:#(?:3|4|5|6|8))))|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)|(?:\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]|(?:\u009d)(?:[^\u009c]*)?\u009c} variable re_ansi_detect {(?:\x1b(?:\((?:0|B)|\[(?:[\x20-\x2f\x30-\x3f]*[\x40-\x7e])|\](?:(?:[^\007]*)\007|(?:(?!\x1b\\).)*\x1b\\)|(?:P|X|\^|_)(?:(?:(?!\x1b\\|\007).)*(?:\x1b\\|\007))|c|7|8|M|E|D|H|=|>|(?:#(?:3|4|5|6|8))))|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)|(?:\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]|(?:\u009d)(?:[^\u009c]*)?\u009c}
# -- --- --- --- # -- --- --- ---
@ -5674,7 +5769,12 @@ tcl::namespace::eval punk::ansi::ansistring {
ENQ [list \x05 \u2405]\ ENQ [list \x05 \u2405]\
ACK [list \x06 \u2406]\ ACK [list \x06 \u2406]\
BEL [list \x07 \u2407]\ BEL [list \x07 \u2407]\
BS [list \x08 \u2408]\
HT [list \x09 \u2409]\
LF [list \x0a \u240a]\
VT [list \x0b \u240b]\
FF [list \x0c \u240c]\ FF [list \x0c \u240c]\
CR [list \x0d \u240d]\
SO [list \x0e \u240e]\ SO [list \x0e \u240e]\
SF [list \x0f \u240f]\ SF [list \x0f \u240f]\
DLE [list \x10 \u2410]\ DLE [list \x10 \u2410]\
@ -5688,12 +5788,15 @@ tcl::namespace::eval punk::ansi::ansistring {
CAN [list \x18 \u2418]\ CAN [list \x18 \u2418]\
EM [list \x19 \u2419]\ EM [list \x19 \u2419]\
SUB [list \x1a \u241a]\ SUB [list \x1a \u241a]\
ESC [list \x1b \u241b]\
FS [list \x1c \u241c]\ FS [list \x1c \u241c]\
GS [list \x1d \u241d]\ GS [list \x1d \u241d]\
RS [list \x1e \u241e]\ RS [list \x1e \u241e]\
US [list \x1f \u241f]\ US [list \x1f \u241f]\
SP [list \x20 \u2420]\
DEL [list \x7f \u2421]\ DEL [list \x7f \u2421]\
] ]
#alternate symbols for space #alternate symbols for space
# \u2422 Blank Symbol (b with forwardslash overly) # \u2422 Blank Symbol (b with forwardslash overly)
# \u2423 Open Box (square bracket facing up like a tray/box) # \u2423 Open Box (square bracket facing up like a tray/box)
@ -5836,6 +5939,7 @@ tcl::namespace::eval punk::ansi::ansistring {
-cr 1\ -cr 1\
-lf 0\ -lf 0\
-vt 0\ -vt 0\
-ff 1\
-ht 1\ -ht 1\
-bs 1\ -bs 1\
-sp 1\ -sp 1\
@ -5850,16 +5954,22 @@ tcl::namespace::eval punk::ansi::ansistring {
set opt_cr [tcl::dict::get $opts -cr] set opt_cr [tcl::dict::get $opts -cr]
set opt_lf [tcl::dict::get $opts -lf] set opt_lf [tcl::dict::get $opts -lf]
set opt_vt [tcl::dict::get $opts -vt] set opt_vt [tcl::dict::get $opts -vt]
set opt_ff [tcl::dict::get $opts -ff]
set opt_ht [tcl::dict::get $opts -ht] set opt_ht [tcl::dict::get $opts -ht]
set opt_bs [tcl::dict::get $opts -bs] set opt_bs [tcl::dict::get $opts -bs]
set opt_sp [tcl::dict::get $opts -sp] set opt_sp [tcl::dict::get $opts -sp]
# -- --- --- --- --- # -- --- --- --- ---
# -lf 2, -vt 2 and -ff 2 are useful for CRM mode (Show Control Character Mode) in the terminal - where a newline is expected to display after the character.
set visuals_opt $debug_visuals set visuals_opt $debug_visuals
set visuals_opt [dict remove $visuals_opt CR ESC LF VT FF HT BS SP]
if {$opt_esc} { if {$opt_esc} {
tcl::dict::set visuals_opt ESC [list \x1b \u241b] tcl::dict::set visuals_opt ESC [list \x1b \u241b]
} else {
tcl::dict::unset visuals_opt ESC
} }
if {$opt_cr} { if {$opt_cr} {
tcl::dict::set visuals_opt CR [list \x0d \u240d] tcl::dict::set visuals_opt CR [list \x0d \u240d]
@ -5870,9 +5980,20 @@ tcl::namespace::eval punk::ansi::ansistring {
if {$opt_lf == 2} { if {$opt_lf == 2} {
tcl::dict::set visuals_opt LF [list \x0a \u240a\n] tcl::dict::set visuals_opt LF [list \x0a \u240a\n]
} }
if {$opt_vt} { if {$opt_vt == 1} {
tcl::dict::set visuals_opt VT [list \x0b \u240b] tcl::dict::set visuals_opt VT [list \x0b \u240b]
} }
if {$opt_vt == 2} {
tcl::dict::set visuals_opt VT [list \x0b \u240b\n]
}
switch -exact -- $opt_ff {
1 {
tcl::dict::set visuals_opt FF [list \x0c \u240c]
}
2 {
tcl::dict::set visuals_opt FF [list \x0c \u240c\n]
}
}
if {$opt_ht} { if {$opt_ht} {
tcl::dict::set visuals_opt HT [list \x09 \u2409] tcl::dict::set visuals_opt HT [list \x09 \u2409]
} }

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

@ -531,7 +531,7 @@ namespace eval punk::basictelnet {
# -- --- --- --- # -- --- --- ---
set tailinfo "" set tailinfo ""
if {[string length $nextwaiting]} { if {[string length $nextwaiting]} {
set waitingdisplay [overtype::renderspace -wrap 1 -width 77 -height 1 "" [ansistring VIEW -lf 1 -vt 1 $nextwaiting]] set waitingdisplay [overtype::renderspace -cp437 1 -wrap 1 -width 77 -height 1 "" [ansistring VIEW -lf 1 -vt 1 $nextwaiting]]
set tailinfo "[a+ red]from waiting:\n $waitingdisplay[a]" set tailinfo "[a+ red]from waiting:\n $waitingdisplay[a]"
} }
::punk::basictelnet::add_debug "[a+ Yellow black]from stdin sending: [ansistring VIEW -lf 1 -vt 1 $chunk][a]\n$tailinfo\n" stdin $sock ::punk::basictelnet::add_debug "[a+ Yellow black]from stdin sending: [ansistring VIEW -lf 1 -vt 1 $chunk][a]\n$tailinfo\n" stdin $sock
@ -629,7 +629,7 @@ namespace eval punk::basictelnet {
#set rawview [ansistring VIEW -lf 1 -vt 1 [encoding convertfrom $encoding_guess $data]] #set rawview [ansistring VIEW -lf 1 -vt 1 [encoding convertfrom $encoding_guess $data]]
set rawview [ansistring VIEW -lf 1 -vt 1 $data] set rawview [ansistring VIEW -lf 1 -vt 1 $data]
#set viewblock [overtype::left -wrap 1 -width 78 -height 4 "" $rawview] #set viewblock [overtype::left -wrap 1 -width 78 -height 4 "" $rawview]
set viewblock [overtype::renderspace -experimental test_mode -wrap 1 -width 78 -height 4 "" $rawview] set viewblock [overtype::renderspace -cp437 1 -wrap 1 -width 78 -height 4 "" $rawview]
set lines [split $viewblock \n] set lines [split $viewblock \n]
if {[llength $lines] > 4} { if {[llength $lines] > 4} {
append debug_info [join [list {*}[lrange $lines 0 1] "...<[expr {[llength $lines] -4}] lines undisplayed>..." {*}[lrange $lines end-1 end]] \n] append debug_info [join [list {*}[lrange $lines 0 1] "...<[expr {[llength $lines] -4}] lines undisplayed>..." {*}[lrange $lines end-1 end]] \n]

358
src/modules/punk/blockletter-999999.0a1.0.tm

@ -0,0 +1,358 @@
# -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'pmix make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm
#
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# (C) 2024
#
# @@ Meta Begin
# Application punk::blockletter 999999.0a1.0
# Meta platform tcl
# Meta license <unspecified>
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[manpage_begin shellspy_module_punk::blockletter 0 999999.0a1.0]
#[copyright "2024"]
#[titledesc {Module API}] [comment {-- Name section and table of contents description --}]
#[moddesc {-}] [comment {-- Description at end of page heading --}]
#[require punk::blockletter]
#[keywords module]
#[description]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Overview]
#[para] overview of punk::blockletter
#[subsection Concepts]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection dependencies]
#[para] packages used by punk::blockletter
#[list_begin itemized]
package require Tcl 8.6-
package require textblock
#*** !doctools
#[item] [package {Tcl 8.6}]
#[item] [package {textblock}]
# #package require frobz
# #*** !doctools
# #[item] [package {frobz}]
#*** !doctools
#[list_end]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section API]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# oo::class namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#tcl::namespace::eval punk::blockletter::class {
#*** !doctools
#[subsection {Namespace punk::blockletter::class}]
#[para] class definitions
#if {[tcl::info::commands [tcl::namespace::current]::interface_sample1] eq ""} {
#*** !doctools
#[list_begin enumerated]
# oo::class create interface_sample1 {
# #*** !doctools
# #[enum] CLASS [class interface_sample1]
# #[list_begin definitions]
# method test {arg1} {
# #*** !doctools
# #[call class::interface_sample1 [method test] [arg arg1]]
# #[para] test method
# puts "test: $arg1"
# }
# #*** !doctools
# #[list_end] [comment {-- end definitions interface_sample1}]
# }
#*** !doctools
#[list_end] [comment {--- end class enumeration ---}]
#}
#}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Base namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::blockletter {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
#variable xyz
#*** !doctools
#[subsection {Namespace punk::blockletter}]
#[para] Core API functions for punk::blockletter
#[list_begin definitions]
#A 3x4 block font
variable default_frametype
set default_frametype {vl \u00a0 hl \u00a0 tlc \u00a0 trc \u00a0 blc \u00a0 brc \u00a0}
# colours in order for T c l T k
set logo_letter_colours [list Web-red Web-green Web-royalblue Web-purple Web-orange]
set logo_letter_colours [list Red Green Blue Purple Yellow]
proc logo {args} {
variable logo_letter_colours
variable default_frametype
set argd [punk::args::get_dict [tstr -return string {
-frametype -default {${$default_frametype}}
-outlinecolour -default "web-white"
-backgroundcolour -default {} -help "e.g Web-white
This argument is the name as accepted by punk::ansi::a+"
*values -min 0 -max 0
}] $args]
set f [dict get $argd opts -frametype]
set bd [dict get $argd opts -outlinecolour]
set bgansi [dict get $argd opts -backgroundcolour] ;#we use ta::detect to see if already ansi and apply as necessary
#standard red green blue purple yellow
lassign $logo_letter_colours c_0 c_1 c_2 c_3 c_4
set tc [merge_left_block [T -bg $c_0 -border $bd -frametype $f] [c -bg $c_1 -border $bd -frametype $f]]
set tk [merge_left_block [T -bg $c_3 -border $bd -frametype $f] [k_short -bg $c_4 -border $bd -frametype $f]]
set logo [textblock::join_basic -- $tc [l -bg $c_2 -border $bd -frametype $f] [textblock::block 2 8 " "] $tk]
if {$bgansi ne ""} {
lassign [textblock::size_as_list $logo] lwidth lheight
set w [expr {$lwidth + 2}]
set h [expr {$lheight + 2}]
if {![punk::ansi::ta::detect $bgansi]} {
set bgansi [punk::ansi::a+ $bgansi]
}
set logobg $bgansi[textblock::block $w $h " "][punk::ansi::a]
set topmargin [string repeat " " $w]
set lmargin [textblock::block 1 [expr {$h + 1}] " "]
set logo [overtype::left -transparent " " $logobg [textblock::join_basic -- $lmargin $topmargin\n$logo]]
}
return $logo
}
#for characters where it makes sense - offset left by 4 (1 'block' width)
proc merge_left {charleft textright} {
if {[string length $charleft] != 1} {
error "merge_left requires a single character as the charleft argument"
}
if {[textblock::height $charleft$textright] > 1} {
error "merge_left only operates on a plain char and a plain string with no newlines"
}
set rhs [textblock::join_basic -- [textblock::block 8 8 " "] [text $textright]]
#important to explicitly use -transparent " " (ordinary space) rather than -transparent 1 (any space?)
#This is because our frames have NBSP as filler to be non-transparent
return [overtype::left -transparent " " -overflow 1 [text $charleft] $rhs]
}
proc merge_left_block {blockleft blockright} {
set rhs [textblock::join_basic -- [textblock::block 8 8 " "] $blockright]
return [overtype::left -transparent " " -overflow 1 $blockleft $rhs]
}
proc T {args} {
set args [dict remove $args -width -height]
append out [lib::hbar {*}$args]\n
append out [textblock::join -- " " [lib::vbar {*}$args] " "]
}
proc c {args} {
set args [dict remove $args -width -height]
append out [textblock::block 12 2 " "]\n
append out [lib::hbar {*}$args]\n
append out [textblock::join -- [lib::block {*}$args] " "]\n
append out [lib::hbar {*}$args]
}
proc l {args} {
set args [dict remove $args -width -height]
append out [lib::vbar {*}[dict merge {-height 8} $args]]
}
#full height lower k
proc k {args} {
set args [dict remove $args -width -height]
set left [lib::vbar {*}[dict merge {-height 8} $args]]
set centre [textblock::block 4 4 " "]\n
append centre [lib::block {*}$args]\n
append centre [textblock::block 4 2 " "]
set right [textblock::block 4 2 " "]\n
append right [lib::block {*}$args]\n
append right [textblock::block 4 2 " "]\n
append right [lib::block {*}$args]
append out [textblock::join_basic -- $left $centre $right]
}
proc k_short {args} {
set args [dict remove $args -width -height]
append left [textblock::block 4 2 " "]\n
append left [lib::vbar {*}[dict merge {-height 6} $args]]
append centre [textblock::block 4 4 " "]\n
append centre [lib::block {*}$args]\n
append centre [textblock::block 4 2 " "]
append right [textblock::block 4 2 " "]\n
append right [lib::block {*}$args]\n
append right [textblock::block 4 2 " "]\n
append right [lib::block {*}$args]
append out [textblock::join_basic -- $left $centre $right]
}
proc text {args} {
variable default_frametype
set argd [punk::args::get_dict [tstr -return string {
-bgcolour -default "Web-red"
-bordercolour -default "web-white"
-frametype -default {${$default_frametype}}
*values -min 1 -max 1
str -help "Text to convert to blockletters
Requires terminal font to support relevant block characters"
"
}] $args]
set opts [dict get $argd opts]
set str [dict get $argd values str]
set str [string map {\r\n \n} $str]
set outblocks [list]
set literals [list \n]
foreach char [split $str ""] {
if {$char in $literals} {
lappend outblocks $char
continue
}
if {$char in [list \t \r]} {
lappend outblocks [textblock::block 1 8 $char]
continue
}
if {[info commands ::punk::blockletter::$char] ne ""} {
lappend outblocks [::punk::blockletter::$char {*}$opts]
} else {
lappend outblocks [textblock::block 12 8 $char]
}
}
return [textblock::join_basic -- {*}$outblocks]
}
#*** !doctools
#[list_end] [comment {--- end definitions namespace punk::blockletter ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Secondary API namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::blockletter::lib {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
tcl::namespace::path [tcl::namespace::parent]
#*** !doctools
#[subsection {Namespace punk::blockletter::lib}]
#[para] Secondary functions that are part of the API
#[list_begin definitions]
#proc utility1 {p1 args} {
# #*** !doctools
# #[call lib::[fun utility1] [arg p1] [opt {?option value...?}]]
# #[para]Description of utility1
# return 1
#}
proc block {args} {
upvar ::punk::blockletter::default_frametype ft
set argd [punk::args::get_dict [tstr -return string {
-height -default 2
-width -default 4
-frametype -default {${$ft}}
-bgcolour -default "Web-red"
-bordercolour -default "web-white"
*values -min 0 -max 0
}] $args]
set bg [dict get $argd opts -bgcolour]
set bd [dict get $argd opts -bordercolour]
set h [dict get $argd opts -height]
set w [dict get $argd opts -width]
set f [dict get $argd opts -frametype]
#a frame will usually be filled with empty spaces if content not specified
#fill the frame with a non-space so we can do transparent overtypes using ordinary space as the transparency character
set w_in [expr {$w -2}]
set h_in [expr {$h -2}]
if {$w_in > 0 && $h_in > 0} {
set inner [textblock::block $w_in $h_in \u00a0] ;#NBSP
textblock::frame -type $f -height $h -width $w -ansiborder [a+ $bd $bg] -ansibase [a+ $bg] $inner
} else {
#important to use no content arg - as empty string has 'height' of 1 in the textblock context (min height of any string is 1 row in the console)
textblock::frame -type $f -height $h -width $w -ansiborder [a+ $bd $bg] -ansibase [a+ $bg]
}
}
proc hbar {args} {
upvar ::punk::blockletter::default_frametype ft
set defaults [dict create\
-height 2\
-width 12\
-frametype $ft\
]
set opts [dict merge $defaults $args]
block {*}$opts
}
proc vbar {args} {
upvar ::punk::blockletter::default_frametype ft
#default height a multiple of default hbar/block height
set defaults [dict create\
-height 6\
-width 4\
-frametype $ft\
]
set opts [dict merge $defaults $args]
[namespace current]::block {*}$opts
}
#*** !doctools
#[list_end] [comment {--- end definitions namespace punk::blockletter::lib ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Internal]
#tcl::namespace::eval punk::blockletter::system {
#*** !doctools
#[subsection {Namespace punk::blockletter::system}]
#[para] Internal functions that are not part of the API
#}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide punk::blockletter [tcl::namespace::eval punk::blockletter {
variable pkg punk::blockletter
variable version
set version 999999.0a1.0
}]
return
#*** !doctools
#[manpage_end]

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

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

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

@ -1021,8 +1021,8 @@ namespace eval punk::console {
#It's known this isn't always the case - but things like textutil::untabify2 take only a single value #It's known this isn't always the case - but things like textutil::untabify2 take only a single value
#on some systems test_char_width is a similar speed to get_tabstop_apparent_width - but on some test_char_width is much slower #on some systems test_char_width is a similar speed to get_tabstop_apparent_width - but on some test_char_width is much slower
#we will use test_char_width as a fallback #we will use test_char_width as a fallback
proc get_tabstop_apparent_width {} { proc get_tabstop_apparent_width {{inoutchannels {stdin stdout}}} {
set tslist [get_tabstops] set tslist [get_tabstops $inoutchannels]
if {![llength $tslist]} { if {![llength $tslist]} {
#either terminal failed to report - or none set. #either terminal failed to report - or none set.
set testw [test_char_width \t] set testw [test_char_width \t]
@ -1075,23 +1075,37 @@ namespace eval punk::console {
return [split [get_cursor_pos $inoutchannels] ";"] return [split [get_cursor_pos $inoutchannels] ";"]
} }
#todo - determine cursor on/off state before the call to restore properly. May only be possible #todo - determine cursor on/off state before the call to restore properly.
proc get_size {{inoutchannels {stdin stdout}}} { proc get_size {{inoutchannels {stdin stdout}}} {
lassign $inoutchannels in out lassign $inoutchannels in out
#we can't reliably use [chan names] for stdin,stdout. There could be stacked channels and they may have a names such as file22fb27fe810 #we can't reliably use [chan names] for stdin,stdout. There could be stacked channels and they may have a names such as file22fb27fe810
#chan eof is faster whether chan exists or not than #chan eof is faster whether chan exists or not than
if {[catch {chan eof $in} is_eof]} { if {[catch {chan eof $out} is_eof]} {
error "punk::console::get_size input channel $in seems to be closed ([info level 1])" error "punk::console::get_size output channel $out seems to be closed ([info level 1])"
} else { } else {
if {$is_eof} { if {$is_eof} {
error "punk::console::get_size eof on input channel $in ([info level 1])" error "punk::console::get_size eof on output channel $out ([info level 1])"
} }
} }
if {[catch {chan eof $out} is_eof]} { #we don't need to care about the input channel if chan configure on the output can give us the info.
error "punk::console::get_size output channel $out seems to be closed ([info level 1])" #short circuit ansi cursor movement method if chan configure supports the -winsize value
set outconf [chan configure $out]
if {[dict exists $outconf -winsize]} {
#this mechanism is much faster than ansi cursor movements
#REVIEW check if any x-platform anomalies with this method?
#can -winsize key exist but contain erroneous info? We will check that we get 2 ints at least
lassign [dict get $outconf -winsize] cols lines
if {[string is integer -strict $cols] && [string is integer -strict $lines]} {
return [list columns $cols rows $lines]
}
#continue on to ansi mechanism if we didn't get 2 ints
}
if {[catch {chan eof $in} is_eof]} {
error "punk::console::get_size input channel $in seems to be closed ([info level 1])"
} else { } else {
if {$is_eof} { if {$is_eof} {
error "punk::console::get_size eof on output channel $out ([info level 1])" error "punk::console::get_size eof on input channel $in ([info level 1])"
} }
} }
@ -1114,18 +1128,28 @@ namespace eval punk::console {
} }
} }
#faster - but uses cursor_save - which we may want to avoid if calling during another operation which uses cursor save/restore #faster than get_size when it is using ansi mechanism - but uses cursor_save - which we may want to avoid if calling during another operation which uses cursor save/restore
proc get_size_cursorrestore {} { proc get_size_cursorrestore {{inoutchannels {stdin stdout}}} {
lassign $inoutchannels in out
#we use the same shortcircuit mechanism as get_size to avoid ansi at all if the output channel will give us the info directly
set outconf [chan configure $out]
if {[dict exists $outconf -winsize]} {
lassign [dict get $outconf -winsize] cols lines
if {[string is integer -strict $cols] && [string is integer -strict $lines]} {
return [list columns $cols rows $lines]
}
}
if {[catch { if {[catch {
#some terminals (conemu on windows) scroll the viewport when we make a big move down like this - a move to 1 1 immediately after cursor_save doesn't seem to fix that. #some terminals (conemu on windows) scroll the viewport when we make a big move down like this - a move to 1 1 immediately after cursor_save doesn't seem to fix that.
#This issue also occurs when switching back from the alternate screen buffer - so perhaps that needs to be addressed elsewhere. #This issue also occurs when switching back from the alternate screen buffer - so perhaps that needs to be addressed elsewhere.
puts -nonewline [punk::ansi::cursor_off][punk::ansi::cursor_save_dec][punk::ansi::move 2000 2000] puts -nonewline $out [punk::ansi::cursor_off][punk::ansi::cursor_save_dec][punk::ansi::move 2000 2000]
lassign [get_cursor_pos_list] lines cols lassign [get_cursor_pos_list $inoutchannels] lines cols
puts -nonewline [punk::ansi::cursor_restore][punk::console::cursor_on];flush stdout puts -nonewline $out [punk::ansi::cursor_restore][punk::console::cursor_on];flush $out
set result [list columns $cols rows $lines] set result [list columns $cols rows $lines]
} errM]} { } errM]} {
puts -nonewline [punk::ansi::cursor_restore_dec] puts -nonewline $out [punk::ansi::cursor_restore_dec]
puts -nonewline [punk::ansi::cursor_on] puts -nonewline $out [punk::ansi::cursor_on]
error "$errM" error "$errM"
} else { } else {
return $result return $result
@ -1175,7 +1199,7 @@ namespace eval punk::console {
} }
if {!$emit} { if {!$emit} {
puts -nonewline stdout \033\[2K\033\[1G ;#2K erase line 1G cursor at col1 puts -nonewline stdout \033\[2K\033\[1G ;#2K erase line, 1G cursor at col1
} }
set response "" set response ""
if {[catch { if {[catch {
@ -1405,12 +1429,12 @@ namespace eval punk::console {
proc cursor_save {} { proc cursor_save {} {
#*** !doctools #*** !doctools
#[call [fun cursor_save]] #[call [fun cursor_save]]
puts -nonewline \x1b\[s puts -nonewline stdout \x1b\[s
} }
proc cursor_restore {} { proc cursor_restore {} {
#*** !doctools #*** !doctools
#[call [fun cursor_restore]] #[call [fun cursor_restore]]
puts -nonewline \x1b\[u puts -nonewline stdout \x1b\[u
} }
#DEC equivalents of cursor_save/cursor_restore - perhaps more widely supported? #DEC equivalents of cursor_save/cursor_restore - perhaps more widely supported?
proc cursor_save_dec {} { proc cursor_save_dec {} {

4
src/modules/punk/experiment-999999.0a1.0.tm

@ -474,12 +474,12 @@ namespace eval punk::experiment {
proc render1 {} { proc render1 {} {
variable b1 variable b1
variable b2 variable b2
overtype::renderspace -overflow 1 -startcolumn 7 $b1 $b2 overtype::renderspace -expand_right 1 -startcolumn 7 $b1 $b2
} }
proc render2 {} { proc render2 {} {
variable b1 variable b1
variable b3 variable b3
overtype::renderspace -overflow 1 -transparent @ $b1 $b3 overtype::renderspace -expand_right 1 -transparent @ $b1 $b3
} }
oo::class create c1 { oo::class create c1 {

8
src/modules/punk/mix/commandset/project-999999.0a1.0.tm

@ -919,10 +919,18 @@ namespace eval punk::mix::commandset::project {
if {[llength $col_states]} { if {[llength $col_states]} {
foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes s $col_states { foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes s $col_states {
if {![file exists $wd]} {
set row [punk::ansi::a+ strike red]$row[a]
set wd [punk::ansi::a+ red]$wd[a]
}
append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode] [overtype::left $col6 $s]" \n append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode] [overtype::left $col6 $s]" \n
} }
} else { } else {
foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes { foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes {
if {![file exists $wd]} {
set row [punk::ansi::a+ strike red]$row[a]
set wd [punk::ansi::a+ red]$wd[a]
}
append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode]" \n append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode]" \n
} }
} }

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

@ -431,7 +431,7 @@ proc repl::post_operations {} {
uplevel #0 {eval $::repl::running_script} uplevel #0 {eval $::repl::running_script}
} }
#todo - tidyup so repl could be restarted #todo - tidyup so repl could be restarted
set repl::post_operations_done 0 set ::repl::post_operations_done 0
} }
@ -860,7 +860,7 @@ namespace eval punk::repl::class {
set o_cursor_col $line_nextchar_col set o_cursor_col $line_nextchar_col
} }
set mergedinfo [overtype::renderline -info 1 -overflow 1 -insert_mode $o_insert_mode -cursor_column $o_cursor_col -cursor_row $o_cursor_row $underlay $new0] set mergedinfo [overtype::renderline -info 1 -expand_right 1 -insert_mode $o_insert_mode -cursor_column $o_cursor_col -cursor_row $o_cursor_row $underlay $new0]
set result [dict get $mergedinfo result] set result [dict get $mergedinfo result]
set o_insert_mode [dict get $mergedinfo insert_mode] set o_insert_mode [dict get $mergedinfo insert_mode]
@ -934,13 +934,13 @@ namespace eval punk::repl::class {
break break
} }
} }
#puts stderr "overtype::renderline -info 1 -overflow 1 -insert_mode $o_insert_mode -cursor_column $o_cursor_col -cursor_row $o_cursor_row $activeline '$p'" #puts stderr "overtype::renderline -info 1 -expand_right 1 -insert_mode $o_insert_mode -cursor_column $o_cursor_col -cursor_row $o_cursor_row $activeline '$p'"
set underlay $activeline set underlay $activeline
set line_nextchar_col [expr {[punk::char::string_width $underlay] + 1}] set line_nextchar_col [expr {[punk::char::string_width $underlay] + 1}]
if {$o_cursor_col > $line_nextchar_col} { if {$o_cursor_col > $line_nextchar_col} {
set o_cursor_col $line_nextchar_col set o_cursor_col $line_nextchar_col
} }
set mergedinfo [overtype::renderline -info 1 -overflow 1 -insert_mode $o_insert_mode -cursor_column $o_cursor_col -cursor_row $o_cursor_row $underlay $p] set mergedinfo [overtype::renderline -info 1 -expand_right 1 -insert_mode $o_insert_mode -cursor_column $o_cursor_col -cursor_row $o_cursor_row $underlay $p]
set debug "add_chunk$i" set debug "add_chunk$i"
append debug \n $mergedinfo append debug \n $mergedinfo
append debug \n "input:[ansistring VIEW -lf 1 -vt 1 $p]" append debug \n "input:[ansistring VIEW -lf 1 -vt 1 $p]"
@ -1120,7 +1120,7 @@ namespace eval punk::repl::class {
} else { } else {
set charhighlight [punk::ansi::a+ reverse]$char_at_cursor[a] set charhighlight [punk::ansi::a+ reverse]$char_at_cursor[a]
} }
set cursorline [overtype::renderline -transparent 1 -insert_mode 0 -overflow 0 $cursorline $prefix$charhighlight$suffix] set cursorline [overtype::renderline -transparent 1 -insert_mode 0 -expand_right 0 $cursorline $prefix$charhighlight$suffix]
lset lines $o_cursor_row-1 $cursorline lset lines $o_cursor_row-1 $cursorline
} }
@ -1921,7 +1921,7 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config
if {[info complete $commandstr] && [string index $commandstr end] ne "\\"} { if {[info complete $commandstr] && [string index $commandstr end] ne "\\"} {
#set commandstr [overtype::renderline -overflow 1 "" $commandstr] #set commandstr [overtype::renderline -expand_right 1 "" $commandstr]
set ::repl::output_stdout "" set ::repl::output_stdout ""

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

@ -134,13 +134,30 @@ namespace eval punk::repo {
} }
interp alias "" fossil "" punk::repo::fossil_proxy interp alias "" fossil "" punk::repo::fossil_proxy
# ---
# Calling auto_execok on an external tool can be too slow to do during package load (e.g could be 150ms)
#safe interps can't call auto_execok #safe interps can't call auto_execok
#At least let them load the package even though much of it may be unusable depending on the safe configuration #At least let them load the package even though much of it may be unusable depending on the safe configuration
catch { #catch {
if {[auto_execok fossil] ne ""} { # if {[auto_execok fossil] ne ""} {
interp alias "" FOSSIL "" {*}[auto_execok fossil] # interp alias "" FOSSIL "" {*}[auto_execok fossil]
} # }
#}
# ---
# ----------
#
#uppercase FOSSIL to bypass fossil as alias to fossil_proxy
proc establish_FOSSIL {args} {
if {![info exists ::auto_execs(FOSSIL)]} {
set ::auto_execs(FOSSIL) [auto_execok fossil] ;#may fail in safe interp
}
interp alias "" FOSSIL "" ;#delete establishment alias
FOSSIL {*}$args
} }
interp alias "" FOSSIL "" punk::repo::establish_FOSSIL
# ----------
proc askuser {question} { proc askuser {question} {
if {![catch {package require punk::lib}]} { if {![catch {package require punk::lib}]} {
@ -370,7 +387,16 @@ namespace eval punk::repo {
} }
if {$repodir eq ""} { if {$repodir eq ""} {
error "workingdir_state error: No repository found at or above path '$abspath'" puts stderr "workingdir_state error: No repository found at or above path '$abspath'"
puts stderr "args: $args"
dict set resultdict revision {}
dict set resultdict revision_iso8601 {}
dict set resultdict paths {}
dict set resultdict ahead ""
dict set resultdict behind ""
dict set resultdict error {reason "no_repo_found"}
dict set resultdict repotype none
return $resultdict
} }
set subpath [punk::path::relative $repodir $abspath] set subpath [punk::path::relative $repodir $abspath]
if {$subpath eq "."} { if {$subpath eq "."} {
@ -644,6 +670,16 @@ namespace eval punk::repo {
set path_count_fields [list unchanged changed new missing extra] set path_count_fields [list unchanged changed new missing extra]
set state_fields [list ahead behind repodir subpath repotype revision revision_iso8601] set state_fields [list ahead behind repodir subpath repotype revision revision_iso8601]
set dresult [dict create] set dresult [dict create]
if {[dict exists $repostate error]} {
foreach f $state_fields {
dict set dresult $f ""
}
foreach f $path_count_fields {
dict set dresult $f ""
}
#todo?
return $dresult
}
foreach f $state_fields { foreach f $state_fields {
dict set dresult $f [dict get $repostate $f] dict set dresult $f [dict get $repostate $f]
} }

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

@ -60,6 +60,8 @@ package require punk::ansi
package require punk::lib package require punk::lib
catch {package require patternpunk} catch {package require patternpunk}
package require overtype package require overtype
#safebase interps as at 2024-08 can't access deeper paths - even though they are below the supposed safe list.
package require term::ansi::code::macros ;#required for frame if old ansi g0 used - review - make package optional? package require term::ansi::code::macros ;#required for frame if old ansi g0 used - review - make package optional?
package require textutil package require textutil
@ -1931,13 +1933,6 @@ tcl::namespace::eval textblock {
set hval $ansibase_header$header ;#no reset set hval $ansibase_header$header ;#no reset
set rowh [my header_height $hrow] set rowh [my header_height $hrow]
#set h_lines [lrepeat $rowh $hcell_line_blank]
#set hcell_blank [join $h_lines \n]
#set hval_lines [split $hval \n]
#set hval_lines [lrange $hval_lines 0 $rowh-1]
#set hval_block [join $hval_lines \n]
#set headercell [overtype::left -experimental test_mode $ansibase_header$hcell_blank$RST $hval_block]
if {$hrow == 0} { if {$hrow == 0} {
set hlims $header_boxlimits_toprow set hlims $header_boxlimits_toprow
set rowpos "top" set rowpos "top"
@ -2144,7 +2139,7 @@ tcl::namespace::eval textblock {
#puts $hblock #puts $hblock
#puts "==>hval:'$hval'[a]" #puts "==>hval:'$hval'[a]"
#puts "==>hval:'[ansistring VIEW $hval]'" #puts "==>hval:'[ansistring VIEW $hval]'"
#set spanned_frame [overtype::renderspace -experimental test_mode -transparent 1 $spanned_frame $hblock] #set spanned_frame [overtype::renderspace -transparent 1 $spanned_frame $hblock]
#spanned values default left - todo make configurable #spanned values default left - todo make configurable
@ -3502,11 +3497,11 @@ tcl::namespace::eval textblock {
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 -expand_right 1 -transparent $TSUB $table[unset table] $nextcol]
#JMN #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] #set table [overtype::renderspace -expand_right 1 -transparent \uFFFF $table $nextcol]
} }
incr padwidth $bodywidth incr padwidth $bodywidth
incr colposn incr colposn
@ -3607,14 +3602,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 table [overtype::renderspace -startcolumn [expr {$padwidth + 1}] -overflow 1 -experimental test_mode -transparent $TSUB $table $nextcol] set table [overtype::renderspace -startcolumn [expr {$padwidth + 1}] -expand_right 1 -transparent $TSUB $table $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 table [overtype::renderspace -overflow 1 -experimental test_mode -transparent \uFFFF $table $nextcol]
} }
incr padwidth $bodywidth incr padwidth $bodywidth
incr colposn incr colposn
@ -3724,7 +3712,7 @@ tcl::namespace::eval textblock {
lappend body_blocks $nextcol_body lappend body_blocks $nextcol_body
} else { } else {
if {$headerheight > 0} { if {$headerheight > 0} {
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]] set header_build [overtype::renderspace -startcolumn [expr {$padwidth + 1}] -expand_right 1 -transparent $TSUB $header_build[unset header_build] $nextcol_header[unset nextcol_header]]
} }
lappend body_blocks $nextcol_body 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]
@ -4057,7 +4045,7 @@ tcl::namespace::eval textblock {
if append is chosen the new values will always start at the first column" if append is chosen the new values will always start at the first column"
-columns -default "" -type integer -help "Number of table columns -columns -default "" -type integer -help "Number of table columns
Will default to 2 if not using an existing -table object" Will default to 2 if not using an existing -table object"
*values *values -min 0 -max 1
datalist -default {} -help "flat list of table cell values which will be wrapped based on -columns value" datalist -default {} -help "flat list of table cell values which will be wrapped based on -columns value"
}] $args] }] $args]
set opts [dict get $argd opts] set opts [dict get $argd opts]
@ -4408,6 +4396,14 @@ tcl::namespace::eval textblock {
return [tcl::dict::create width $width height $height] ;#maintain order in 'image processing' standard width then height - caller may use lassign [tcl::dict::values [blocksize <data>]] width height return [tcl::dict::create width $width height $height] ;#maintain order in 'image processing' standard width then height - caller may use lassign [tcl::dict::values [blocksize <data>]] width height
} }
proc size_as_opts {textblock} {
set sz [size $textblock]
return [dict create -width [dict get $sz width] -height [dict get $sz height]]
}
proc size_as_list {textblock} {
set sz [size $textblock]
return [list [dict get $sz width] [dict get $sz height]]
}
#must be able to handle block as string with or without newlines #must be able to handle block as string with or without newlines
#if no newlines - attempt to treat as a list #if no newlines - attempt to treat as a list
#must handle whitespace-only string,list elements, and/or lines. #must handle whitespace-only string,list elements, and/or lines.
@ -5132,6 +5128,7 @@ tcl::namespace::eval textblock {
[punk::lib::list_as_lines -- [lrepeat 8 " | "]] [punk::lib::list_as_lines -- [lrepeat 8 " | "]]
} }
proc table {args} { proc table {args} {
#todo - use punk::args
upvar ::textblock::class::opts_table_defaults toptdefaults upvar ::textblock::class::opts_table_defaults toptdefaults
set defaults [tcl::dict::create\ set defaults [tcl::dict::create\
-rows [list]\ -rows [list]\
@ -5183,7 +5180,7 @@ tcl::namespace::eval textblock {
} }
variable frametypes variable frametypes
set frametypes [list light heavy arc double block block1 block2 ascii altg] set frametypes [list light heavy arc double block block1 block2 block2hack ascii altg]
#class::table needs to be able to determine valid frametypes #class::table needs to be able to determine valid frametypes
proc frametypes {} { proc frametypes {} {
variable frametypes variable frametypes
@ -5192,7 +5189,7 @@ tcl::namespace::eval textblock {
proc frametype {f} { proc frametype {f} {
#set custom_keys [list hl hlt hlb vl vll vlr tlc trc blc brc] #set custom_keys [list hl hlt hlb vl vll vlr tlc trc blc brc]
switch -- $f { switch -- $f {
light - heavy - arc - double - block - block1 - block2 - ascii - altg { light - heavy - arc - double - block - block1 - block2 - block2hack - ascii - altg {
return [tcl::dict::create category predefined type $f] return [tcl::dict::create category predefined type $f]
} }
default { default {
@ -5213,7 +5210,7 @@ tcl::namespace::eval textblock {
set is_custom_dict_ok 0 set is_custom_dict_ok 0
} }
if {!$is_custom_dict_ok} { if {!$is_custom_dict_ok} {
error "frame option -type must be one of known types: $textblock::frametypes or a dictionary with any of keys hl,hlt,hlb,vl,vll,vlr,tlc,trc,blc,brc" error "frame option -type must be one of known types: $::textblock::frametypes or a dictionary with any of keys hl,hlt,hlb,vl,vll,vlr,tlc,trc,blc,brc"
} }
set default_custom [tcl::dict::create hl " " vl " " 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] set custom_frame [tcl::dict::merge $default_custom $f]
@ -6323,9 +6320,12 @@ tcl::namespace::eval textblock {
set vlr \u2595 ;# right one eighth block set vlr \u2595 ;# right one eighth block
set vll \u258f ;# left one eighth block set vll \u258f ;# left one eighth block
#some terminals (on windows as at 2024) miscount width of these single-width blocks internally
#resulting in extra spacing that is sometimes well removed from the character itself (e.g at next ansi reset)
#This was fixed in windows-terminal based systems (2021) but persists in others.
#https://github.com/microsoft/terminal/issues/11694
set tlc \U1fb7d ;#legacy block set tlc \U1fb7d ;#legacy block
set trc \U1fb7e ;#legacy block set trc \U1fb7e ;#legacy block
set blc \U1fb7c ;#legacy block set blc \U1fb7c ;#legacy block
set brc \U1fb7f ;#legacy block set brc \U1fb7f ;#legacy block
@ -6336,6 +6336,42 @@ tcl::namespace::eval textblock {
set vlrj $vlr set vlrj $vlr
} }
block2hack {
#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 vlr \u2595 ;# right one eighth block
set vll \u258f ;# left one eighth block
#see comments in block2 regarding the problems in some terminals that this *may* hack around to some extent.
#the caller probably only needs block2hack if block2 doesn't work
#1)
#review - this hack looks sort of promising - but overtype::renderline needs fixing ?
#set tlc \U1fb7d\b ;#legacy block
#set trc \U1fb7e\b ;#legacy block
#set blc \U1fb7c\b ;#legacy block
#set brc \U1fb7f\b ;#legacy block
#2) - works on cmd.exe and some others
# a 'privacy message' is 'probably' also not supported on the old terminal but is on newer ones
#known exception - conemu on windows - displays junk for various ansi codes - (and slow terminal anyway)
#this hack has a reasonable chance of working
#except that the punk overtype library does recognise PMs
#A single backspace however is an unlikely and generally unuseful PM - so there is a corresponding hack in the renderline system to pass this PM through!
#ugly - in that we don't know the application specifics of what the PM data contains and where it's going.
set tlc \U1fb7d\x1b^\b\x1b\\ ;#legacy block
set trc \U1fb7e\x1b^\b\x1b\\ ;#legacy block
set blc \U1fb7c\x1b^\b\x1b\\ ;#legacy block
set brc \U1fb7f\x1b^\b\x1b\\ ;#legacy block
#horizontal and vertical bar joins
set hltj $hlt
set hlbj $hlb
set vllj $vll
set vlrj $vlr
}
block { block {
set hlt \u2580 ;#upper half set hlt \u2580 ;#upper half
set hlb \u2584 ;#lower half set hlb \u2584 ;#lower half
@ -6357,7 +6393,7 @@ tcl::namespace::eval textblock {
set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "] ;#only default the general types - these form defaults for more specific types if they're missing set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "] ;#only default the general types - these form defaults for more specific types if they're missing
if {[llength $f] % 2 != 0} { if {[llength $f] % 2 != 0} {
#todo - retrieve usage from punk::args #todo - retrieve usage from punk::args
error "textblock::frametype frametype '$f' is not one of the predefined frametypes: $textblock::frametypes and does not appear to be a dictionary for a custom frametype" error "textblock::frametype frametype '$f' is not one of the predefined frametypes: $::textblock::frametypes and does not appear to be a dictionary for a custom frametype"
} }
#unknown order of keys specified by user - validate before creating vars as we need more general elements to be available as defaults #unknown order of keys specified by user - validate before creating vars as we need more general elements to be available as defaults
dict for {k v} $f { dict for {k v} $f {
@ -6488,7 +6524,11 @@ tcl::namespace::eval textblock {
-ellipsis 1\ -ellipsis 1\
-usecache 1\ -usecache 1\
-buildcache 1\ -buildcache 1\
-pad 1\
-crm_mode 0\
] ]
#-pad 1 is default so that simple 'textblock::frame "[a+ Red]a \nbbb[a]" extends the bg colour on the short ragged lines (and empty lines)
# for ansi art - -pad 0 is likely to be preferable
set expect_optval 0 set expect_optval 0
set argposn 0 set argposn 0
@ -6527,7 +6567,12 @@ tcl::namespace::eval textblock {
#use -buildcache 1 with -usecache 0 for debugging cache issues so we can inspect using textblock::frame_cache #use -buildcache 1 with -usecache 0 for debugging cache issues so we can inspect using textblock::frame_cache
foreach {k v} $arglist { foreach {k v} $arglist {
switch -- $k { switch -- $k {
-etabs - -type - -boxlimits - -boxmap - -joins - -title - -subtitle - -width - -height - -ansiborder - -ansibase - -blockalign - -textalign - -ellipsis - -usecache - -buildcache { -etabs - -type - -boxlimits - -boxmap - -joins
- -title - -subtitle - -width - -height
- -ansiborder - -ansibase
- -blockalign - -textalign - -ellipsis
- -crm_mode
- -usecache - -buildcache - -pad {
tcl::dict::set opts $k $v tcl::dict::set opts $k $v
} }
default { default {
@ -6543,11 +6588,13 @@ tcl::namespace::eval textblock {
set opt_boxmap [tcl::dict::get $opts -boxmap] set opt_boxmap [tcl::dict::get $opts -boxmap]
set opt_usecache [tcl::dict::get $opts -usecache] set opt_usecache [tcl::dict::get $opts -usecache]
set opt_buildcache [tcl::dict::get $opts -buildcache] set opt_buildcache [tcl::dict::get $opts -buildcache]
set opt_pad [tcl::dict::get $opts -pad]
set opt_crm_mode [tcl::dict::get $opts -crm_mode]
set usecache $opt_usecache ;#may need to override set usecache $opt_usecache ;#may need to override
set buildcache $opt_buildcache set buildcache $opt_buildcache
set custom_keys [list hl hlt hlb vl vll vlr tlc trc blc brc] set custom_keys [list hl hlt hlb vl vll vlr tlc trc blc brc]
set known_frametypes $frametypes ;# light, heavey etc as defined in textblock::frametypes variable set known_frametypes $frametypes ;# light, heavey etc as defined in the ::textblock::frametypes variable
set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "] set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "]
lassign [textblock::frametype $opt_type] _cat category _type ftype lassign [textblock::frametype $opt_type] _cat category _type ftype
@ -6686,6 +6733,19 @@ tcl::namespace::eval textblock {
} }
} }
set contents [tcl::string::map [list \r\n \n] $contents] set contents [tcl::string::map [list \r\n \n] $contents]
if {$opt_crm_mode} {
if {$opt_height eq ""} {
set h [textblock::height $contents]
} else {
set h [expr {$opt_height -2}]
}
if {$opt_width eq ""} {
set w [textblock::width $contents]
} else {
set w [expr {$opt_width -2}]
}
set contents [overtype::renderspace -crm_mode 1 -wrap 1 -width $w -height $h "" $contents]
}
set actual_contentwidth [textblock::width $contents] ;#length of longest line in contents (contents can be ragged) set actual_contentwidth [textblock::width $contents] ;#length of longest line in contents (contents can be ragged)
set actual_contentheight [textblock::height $contents] set actual_contentheight [textblock::height $contents]
} else { } else {
@ -7134,15 +7194,22 @@ tcl::namespace::eval textblock {
append contents [::join [lrepeat $diff \n] ""] append contents [::join [lrepeat $diff \n] ""]
} }
set paddedcontents [textblock::pad $contents -which $pad -within_ansi 1] ;#autowidth to width of content (should match cache_patternwidth) if {$opt_pad} {
set paddedwidth [textblock::widthtopline $paddedcontents] set paddedcontents [textblock::pad $contents -which $pad -within_ansi 1] ;#autowidth to width of content (should match cache_patternwidth)
set paddedwidth [textblock::widthtopline $paddedcontents]
#review - horizontal truncation #review - horizontal truncation
if {$paddedwidth > $cache_patternwidth} { if {$paddedwidth > $cache_patternwidth} {
set paddedcontents [overtype::renderspace -width $cache_patternwidth "" $paddedcontents] set paddedcontents [overtype::renderspace -width $cache_patternwidth "" $paddedcontents]
}
#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
} else {
set cwidth [textblock::width $contents]
if {$cwidth > $cache_patternwidth} {
set contents [overtype::renderspace -width $cache_patternwidth "" $contents]
}
set contentblock [textblock::join -- $contents]
} }
#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]

77
src/project_layouts/custom/_project/punk.basic/src/make.tcl

@ -1212,8 +1212,9 @@ foreach vfstail $vfs_tails {
set rtmountpoint //zipfs:/rtmounts/$runtime_fullname set rtmountpoint //zipfs:/rtmounts/$runtime_fullname
set changed_unchanged [$vfs_event targetset_source_changes] set changed_unchanged [$vfs_event targetset_source_changes]
set vfs_or_runtime_changed [expr {[llength [dict get $changed_unchanged changed]] || [llength [$vfs_event get_targets_exist]] < [llength [$vfs_event get_targets]]}]
if {[llength [dict get $changed_unchanged changed]] || [llength [$vfs_event get_targets_exist]] < [llength [$vfs_event get_targets]]} { if {$vfs_or_runtime_changed} {
#source .vfs folder has changes #source .vfs folder has changes
$vfs_event targetset_started $vfs_event targetset_started
# -- --- --- --- --- --- # -- --- --- --- --- ---
@ -1283,6 +1284,7 @@ foreach vfstail $vfs_tails {
puts stderr "RUNTIME capabilities unknown. Unsure if zip supported. trying anyway.." puts stderr "RUNTIME capabilities unknown. Unsure if zip supported. trying anyway.."
} }
} }
#note - as at 2024-08 - there is some discussion about the interface to mkimg - it is considered unstable (may change to -option value syntax)
puts stderr "calling: tcl::zipfs::mkimg $buildfolder/$vfsname.new $wrapvfs $wrapvfs \"\" $buildfolder/build_$runtime_fullname" puts stderr "calling: tcl::zipfs::mkimg $buildfolder/$vfsname.new $wrapvfs $wrapvfs \"\" $buildfolder/build_$runtime_fullname"
tcl::zipfs::mkimg $buildfolder/$vfsname.new $wrapvfs $wrapvfs "" $buildfolder/build_$runtime_fullname tcl::zipfs::mkimg $buildfolder/$vfsname.new $wrapvfs $wrapvfs "" $buildfolder/build_$runtime_fullname
} result ]} { } result ]} {
@ -1352,9 +1354,10 @@ foreach vfstail $vfs_tails {
if {![catch { if {![catch {
exec $pscmd | grep $targetkit exec $pscmd | grep $targetkit
} still_running]} { } still_running]} {
set still_running_lines [split [string trim $still_running] \n]
puts stdout "found $targetkit instances still running\n" puts stdout "found ([llength $still_running_lines]) $targetkit instances still running\n"
set count_killed 0 set count_killed 0
set num_to_kill [llength $still_running_lines]
foreach ln [split $still_running \n] { foreach ln [split $still_running \n] {
puts stdout " $ln" puts stdout " $ln"
@ -1387,9 +1390,6 @@ foreach vfstail $vfs_tails {
#review - *no running instance* works with windows taskkill - "*No such process*" works with kill -9 on FreeBSD and linux - other platforms? #review - *no running instance* works with windows taskkill - "*No such process*" works with kill -9 on FreeBSD and linux - other platforms?
if {![string match "*no running instance*" $errMsg] && ![string match "*No such process*" $errMsg]} { if {![string match "*no running instance*" $errMsg] && ![string match "*No such process*" $errMsg]} {
lappend failed_kits [list kit $targetkit reason "could not kill running process for $targetkit (using '$killcmd')"] lappend failed_kits [list kit $targetkit reason "could not kill running process for $targetkit (using '$killcmd')"]
$vfs_event targetset_end FAILED
$vfs_event destroy
$vfs_installer destroy
continue continue
} }
} else { } else {
@ -1397,10 +1397,15 @@ foreach vfstail $vfs_tails {
incr count_killed incr count_killed
} }
} }
if {$count_killed > 0} { if {$count_killed < $num_to_kill} {
puts stderr "\nKilled $count_killed processes. Waiting a short time before attempting to delete executable" $vfs_event targetset_end FAILED
after 1000 $vfs_event destroy
$vfs_installer destroy
continue
} }
puts stderr "\nKilled $count_killed processes. Waiting a short time before attempting to delete executable"
after 1000
} else { } else {
puts stderr "Ok.. no running '$targetkit' processes found" puts stderr "Ok.. no running '$targetkit' processes found"
} }
@ -1426,22 +1431,35 @@ foreach vfstail $vfs_tails {
# -- --- --- --- --- --- # -- --- --- --- --- ---
$vfs_event targetset_end OK $vfs_event targetset_end OK
} else {
set skipped_vfs_build 1
puts stderr "."
puts stdout "Skipping build for vfs $vfstail with runtime $rtname - no change detected"
$vfs_event targetset_end SKIPPED
}
$vfs_event destroy
$vfs_installer destroy
after 200 after 200
set deployment_folder [file dirname $sourcefolder]/bin set deployment_folder [file dirname $sourcefolder]/bin
file mkdir $deployment_folder file mkdir $deployment_folder
# -- ---------- # -- ----------
set bin_installer [punkcheck::installtrack new "make.tcl" $deployment_folder/.punkcheck] set bin_installer [punkcheck::installtrack new "make.tcl" $deployment_folder/.punkcheck]
$bin_installer set_source_target $buildfolder $deployment_folder $bin_installer set_source_target $buildfolder $deployment_folder
set bin_event [$bin_installer start_event {-make-step final_kit_install}] set bin_event [$bin_installer start_event {-make-step final_kit_install}]
$bin_event targetset_init INSTALL $deployment_folder/$targetkit $bin_event targetset_init INSTALL $deployment_folder/$targetkit
#todo - move final deployment step outside of the build vfs loop? (final deployment can fail and then isn't rerun even though _build and deployed versions differ, unless .vfs modified again) #todo - move final deployment step outside of the build vfs loop? (final deployment can fail and then isn't rerun even though _build and deployed versions differ, unless .vfs modified again)
#set last_completion [$bin_event targetset_last_complete] #set last_completion [$bin_event targetset_last_complete]
$bin_event targetset_addsource $buildfolder/$targetkit $bin_event targetset_addsource $deployment_folder/$targetkit ;#add target as a source of metadata for change detection
$bin_event targetset_started $bin_event targetset_addsource $buildfolder/$targetkit
# -- ---------- $bin_event targetset_started
# -- ----------
set changed_unchanged [$bin_event targetset_source_changes]
set built_or_installed_kit_changed [expr {[llength [dict get $changed_unchanged changed]] || [llength [$bin_event get_targets_exist]] < [llength [$bin_event get_targets]]}]
if {$built_or_installed_kit_changed} {
if {[file exists $deployment_folder/$targetkit]} { if {[file exists $deployment_folder/$targetkit]} {
puts stderr "deleting existing deployed at $deployment_folder/$targetkit" puts stderr "deleting existing deployed at $deployment_folder/$targetkit"
@ -1467,19 +1485,16 @@ foreach vfstail $vfs_tails {
# -- ---------- # -- ----------
$bin_event targetset_end OK $bin_event targetset_end OK
# -- ---------- # -- ----------
$bin_event destroy
$bin_installer destroy
} else { } else {
set skipped_vfs_build 1 set skipped_kit_install 1
puts stderr "." puts stderr "."
puts stdout "Skipping build for vfs $vfstail with runtime $rtname - no change detected" puts stdout "Skipping kit install for $targetkit with vfs $vfstail runtime $rtname - no change detected"
$vfs_event targetset_end SKIPPED $bin_event targetset_end SKIPPED
} }
$bin_event destroy
$bin_installer destroy
$vfs_event destroy
$vfs_installer destroy
} ;#end foreach targetkit } ;#end foreach targetkit
} ;#end foreach rtname in runtimes } ;#end foreach rtname in runtimes

74
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/fileutil/paths-1.tm

@ -0,0 +1,74 @@
# paths.tcl --
#
# Manage lists of search paths.
#
# Copyright (c) 2009-2019 Andreas Kupries <andreas_kupries@sourceforge.net>
#
# See the file "license.terms" for information on usage and redistribution
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
# Each object instance manages a list of paths.
# ### ### ### ######### ######### #########
## Requisites
package require Tcl 8.4
package require snit
# ### ### ### ######### ######### #########
## API
snit::type ::fileutil::paths {
# ### ### ### ######### ######### #########
## Options :: None
# ### ### ### ######### ######### #########
## Creation, destruction
# Default constructor.
# Default destructor.
# ### ### ### ######### ######### #########
## Methods :: Querying and manipulating the list of paths.
method paths {} {
return $mypaths
}
method add {path} {
set pos [lsearch $mypaths $path]
if {$pos >= 0 } return
lappend mypaths $path
return
}
method remove {path} {
set pos [lsearch $mypaths $path]
if {$pos < 0} return
set mypaths [lreplace $mypaths $pos $pos]
return
}
method clear {} {
set mypaths {}
return
}
# ### ### ### ######### ######### #########
## Internal methods :: None
# ### ### ### ######### ######### #########
## State :: List of paths.
variable mypaths {}
##
# ### ### ### ######### ######### #########
}
# ### ### ### ######### ######### #########
## Ready
package provide fileutil::paths 1
return

504
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/fileutil/traverse-0.6.tm

@ -0,0 +1,504 @@
# traverse.tcl --
#
# Directory traversal.
#
# Copyright (c) 2006-2015 by Andreas Kupries <andreas_kupries@users.sourceforge.net>
#
# See the file "license.terms" for information on usage and redistribution
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
package require Tcl 8.3
# OO core
if {[package vsatisfies [package present Tcl] 8.5]} {
# Use new Tcl 8.5a6+ features to specify the allowed packages.
# We can use anything above 1.3. This means v2 as well.
package require snit 1.3-
} else {
# For Tcl 8.{3,4} only snit1 of a suitable patchlevel is possible.
package require snit 1.3
}
package require control ; # Helpers for control structures
package require fileutil ; # -> fullnormalize
snit::type ::fileutil::traverse {
# Incremental directory traversal.
# API
# create %AUTO% basedirectory options... -> object
# next filevar -> boolean
# foreach filevar script
# files -> list (path ...)
# Options
# -prefilter command-prefix
# -filter command-prefix
# -errorcmd command-prefix
# Use cases
#
# (a) Basic incremental
# - Create and configure a traversal object.
# - Execute 'next' to retrieve one path at a time,
# until the command returns False, signaling that
# the iterator has exhausted the supply of paths.
# (The path is stored in the named variable).
#
# The execution of 'next' can be done in a loop, or via event
# processing.
# (b) Basic loop
# - Create and configure a traversal object.
# - Run a script for each path, using 'foreach'.
# This is a convenient standard wrapper around 'next'.
#
# The loop properly handles all possible Tcl result codes.
# (c) Non-incremental, non-looping.
# - Create and configure a traversal object.
# - Retrieve a list of all paths via 'files'.
# The -prefilter callback is executed for directories. Its result
# determines if the traverser recurses into the directory or not.
# The default is to always recurse into all directories. The call-
# back is invoked with a single argument, the path of the
# directory.
#
# The -filter callback is executed for all paths. Its result
# determines if the current path is a valid result, and returned
# by 'next'. The default is to accept all paths as valid. The
# callback is invoked with a single argument, the path to check.
# The -errorcmd callback is executed for all paths the traverser
# has trouble with. Like being unable to cd into them, get their
# status, etc. The default is to ignore any such problems. The
# callback is invoked with a two arguments, the path for which the
# error occured, and the error message. Errors thrown by the
# filter callbacks are handled through this callback too. Errors
# thrown by the error callback itself are not caught and ignored,
# but allowed to pass to the caller, usually of 'next'.
# Note: Low-level functionality, version and platform dependent is
# implemented in procedures, and conditioally defined for optimal
# use of features, etc. ...
# Note: Traversal is done in depth-first pre-order.
# Note: The options are handled only during
# construction. Afterward they are read-only and attempts to
# modify them will cause the system to throw errors.
# ### ### ### ######### ######### #########
## Implementation
option -filter -default {} -readonly 1
option -prefilter -default {} -readonly 1
option -errorcmd -default {} -readonly 1
constructor {basedir args} {
set _base $basedir
$self configurelist $args
return
}
method files {} {
set files {}
$self foreach f {lappend files $f}
return $files
}
method foreach {fvar body} {
upvar 1 $fvar currentfile
# (Re-)initialize the traversal state on every call.
$self Init
while {[$self next currentfile]} {
set code [catch {uplevel 1 $body} result]
# decide what to do upon the return code:
#
# 0 - the body executed successfully
# 1 - the body raised an error
# 2 - the body invoked [return]
# 3 - the body invoked [break]
# 4 - the body invoked [continue]
# everything else - return and pass on the results
#
switch -exact -- $code {
0 {}
1 {
return -errorinfo [::control::ErrorInfoAsCaller uplevel foreach] \
-errorcode $::errorCode -code error $result
}
3 {
# FRINK: nocheck
return
}
4 {}
default {
return -code $code $result
}
}
}
return
}
method next {fvar} {
upvar 1 $fvar currentfile
# Initialize on first call.
if {!$_init} {
$self Init
}
# We (still) have valid paths in the result stack, return the
# next one.
if {[llength $_results]} {
set top [lindex $_results end]
set _results [lreplace $_results end end]
set currentfile $top
return 1
}
# Take the next directory waiting in the processing stack and
# fill the result stack with all valid files and sub-
# directories contained in it. Extend the processing queue
# with all sub-directories not yet seen already (!circular
# symlinks) and accepted by the prefilter. We stop iterating
# when we either have no directories to process anymore, or
# the result stack contains at least one path we can return.
while {[llength $_pending]} {
set top [lindex $_pending end]
set _pending [lreplace $_pending end end]
# Directory accessible? Skip if not.
if {![ACCESS $top]} {
Error $top "Inacessible directory"
continue
}
# Expand the result stack with all files in the directory,
# modulo filtering.
foreach f [GLOBF $top] {
if {![Valid $f]} continue
lappend _results $f
}
# Expand the result stack with all sub-directories in the
# directory, modulo filtering. Further expand the
# processing stack with the same directories, if not seen
# yet and modulo pre-filtering.
foreach f [GLOBD $top] {
if {
[string equal [file tail $f] "."] ||
[string equal [file tail $f] ".."]
} continue
if {[Valid $f]} {
lappend _results $f
}
Enter $top $f
if {[Cycle $f]} continue
if {[Recurse $f]} {
lappend _pending $f
}
}
# Stop expanding if we have paths to return.
if {[llength $_results]} {
set top [lindex $_results end]
set _results [lreplace $_results end end]
set currentfile $top
return 1
}
}
# Allow re-initialization with next call.
set _init 0
return 0
}
# ### ### ### ######### ######### #########
## Traversal state
# * Initialization flag. Checked in 'next', reset by next when no
# more files are available. Set in 'Init'.
# * Base directory (or file) to start the traversal from.
# * Stack of prefiltered unknown directories waiting for
# processing, i.e. expansion (TOP at end).
# * Stack of valid paths waiting to be returned as results.
# * Set of directories already visited (normalized paths), for
# detection of circular symbolic links.
variable _init 0 ; # Initialization flag.
variable _base {} ; # Base directory.
variable _pending {} ; # Processing stack.
variable _results {} ; # Result stack.
# sym link handling (to break cycles, while allowing the following of non-cycle links).
# Notes
# - path parent tracking is lexical.
# - path identity tracking is based on the normalized path, i.e. the path with all
# symlinks resolved.
# Maps
# - path -> parent (easier to follow the list than doing dirname's)
# - path -> normalized (cache to avoid redundant calls of fullnormalize)
# cycle <=> A parent's normalized form (NF) is identical to the current path's NF
variable _parent -array {}
variable _norm -array {}
# ### ### ### ######### ######### #########
## Internal helpers.
proc Enter {parent path} {
#puts ___E|$path
upvar 1 _parent _parent _norm _norm
set _parent($path) $parent
set _norm($path) [fileutil::fullnormalize $path]
}
proc Cycle {path} {
upvar 1 _parent _parent _norm _norm
set nform $_norm($path)
set paren $_parent($path)
while {$paren ne {}} {
if {$_norm($paren) eq $nform} { return yes }
set paren $_parent($paren)
}
return no
}
method Init {} {
array unset _parent *
array unset _norm *
# Path ok as result?
if {[Valid $_base]} {
lappend _results $_base
}
# Expansion allowed by prefilter?
if {[file isdirectory $_base] && [Recurse $_base]} {
Enter {} $_base
lappend _pending $_base
}
# System is set up now.
set _init 1
return
}
proc Valid {path} {
#puts ___V|$path
upvar 1 options options
if {![llength $options(-filter)]} {return 1}
set path [file normalize $path]
set code [catch {uplevel \#0 [linsert $options(-filter) end $path]} valid]
if {!$code} {return $valid}
Error $path $valid
return 0
}
proc Recurse {path} {
#puts ___X|$path
upvar 1 options options _norm _norm
if {![llength $options(-prefilter)]} {return 1}
set path [file normalize $path]
set code [catch {uplevel \#0 [linsert $options(-prefilter) end $path]} valid]
if {!$code} {return $valid}
Error $path $valid
return 0
}
proc Error {path msg} {
upvar 1 options options
if {![llength $options(-errorcmd)]} return
set path [file normalize $path]
uplevel \#0 [linsert $options(-errorcmd) end $path $msg]
return
}
##
# ### ### ### ######### ######### #########
}
# ### ### ### ######### ######### #########
##
# The next three helper commands for the traverser depend strongly on
# the version of Tcl, and partially on the platform.
# 1. In Tcl 8.3 using -types f will return only true files, but not
# links to files. This changed in 8.4+ where links to files are
# returned as well. So for 8.3 we have to handle the links
# separately (-types l) and also filter on our own.
# Note that Windows file links are hard links which are reported by
# -types f, but not -types l, so we can optimize that for the two
# platforms.
#
# 2. In Tcl 8.3 we also have a crashing bug in glob (SIGABRT, "stat on
# a known file") when trying to perform 'glob -types {hidden f}' on
# a directory without e'x'ecute permissions. We code around by
# testing if we can cd into the directory (stat might return enough
# information too (mode), but possibly also not portable).
#
# For Tcl 8.2 and 8.4+ glob simply delivers an empty result
# (-nocomplain), without crashing. For them this command is defined
# so that the bytecode compiler removes it from the bytecode.
#
# This bug made the ACCESS helper necessary.
# We code around the problem by testing if we can cd into the
# directory (stat might return enough information too (mode), but
# possibly also not portable).
if {[package vsatisfies [package present Tcl] 8.5]} {
# Tcl 8.5+.
# We have to check readability of "current" on our own, glob
# changed to error out instead of returning nothing.
proc ::fileutil::traverse::ACCESS {args} {return 1}
proc ::fileutil::traverse::GLOBF {current} {
if {![file readable $current] ||
[BadLink $current]} {
return {}
}
set res [lsort -unique [concat \
[glob -nocomplain -directory $current -types f -- *] \
[glob -nocomplain -directory $current -types {hidden f} -- *]]]
# Look for broken links (They are reported as neither file nor directory).
foreach l [lsort -unique [concat \
[glob -nocomplain -directory $current -types l -- *] \
[glob -nocomplain -directory $current -types {hidden l} -- *]]] {
if {[file isfile $l]} continue
if {[file isdirectory $l]} continue
lappend res $l
}
return [lsort -unique $res]
}
proc ::fileutil::traverse::GLOBD {current} {
if {![file readable $current] ||
[BadLink $current]} {
return {}
}
lsort -unique [concat \
[glob -nocomplain -directory $current -types d -- *] \
[glob -nocomplain -directory $current -types {hidden d} -- *]]
}
proc ::fileutil::traverse::BadLink {current} {
if {[file type $current] ne "link"} { return no }
set dst [file join [file dirname $current] [file readlink $current]]
if {![file exists $dst] ||
![file readable $dst]} {
return yes
}
return no
}
} elseif {[package vsatisfies [package present Tcl] 8.4]} {
# Tcl 8.4+.
# (Ad 1) We have -directory, and -types,
# (Ad 2) Links are returned for -types f/d if they refer to files/dirs.
# (Ad 3) No bug to code around
proc ::fileutil::traverse::ACCESS {args} {return 1}
proc ::fileutil::traverse::GLOBF {current} {
set res [concat \
[glob -nocomplain -directory $current -types f -- *] \
[glob -nocomplain -directory $current -types {hidden f} -- *]]
# Look for broken links (They are reported as neither file nor directory).
foreach l [concat \
[glob -nocomplain -directory $current -types l -- *] \
[glob -nocomplain -directory $current -types {hidden l} -- *] ] {
if {[file isfile $l]} continue
if {[file isdirectory $l]} continue
lappend res $l
}
return $res
}
proc ::fileutil::traverse::GLOBD {current} {
concat \
[glob -nocomplain -directory $current -types d -- *] \
[glob -nocomplain -directory $current -types {hidden d} -- *]
}
} else {
# 8.3.
# (Ad 1) We have -directory, and -types,
# (Ad 2) Links are NOT returned for -types f/d, collect separately.
# No symbolic file links on Windows.
# (Ad 3) Bug to code around.
proc ::fileutil::traverse::ACCESS {current} {
if {[catch {
set h [pwd] ; cd $current ; cd $h
}]} {return 0}
return 1
}
if {[string equal $::tcl_platform(platform) windows]} {
proc ::fileutil::traverse::GLOBF {current} {
concat \
[glob -nocomplain -directory $current -types f -- *] \
[glob -nocomplain -directory $current -types {hidden f} -- *]]
}
} else {
proc ::fileutil::traverse::GLOBF {current} {
set l [concat \
[glob -nocomplain -directory $current -types f -- *] \
[glob -nocomplain -directory $current -types {hidden f} -- *]]
foreach x [concat \
[glob -nocomplain -directory $current -types l -- *] \
[glob -nocomplain -directory $current -types {hidden l} -- *]] {
if {[file isdirectory $x]} continue
# We have now accepted files, links to files, and broken links.
lappend l $x
}
return $l
}
}
proc ::fileutil::traverse::GLOBD {current} {
set l [concat \
[glob -nocomplain -directory $current -types d -- *] \
[glob -nocomplain -directory $current -types {hidden d} -- *]]
foreach x [concat \
[glob -nocomplain -directory $current -types l -- *] \
[glob -nocomplain -directory $current -types {hidden l} -- *]] {
if {![file isdirectory $x]} continue
lappend l $x
}
return $l
}
}
# ### ### ### ######### ######### #########
## Ready
package provide fileutil::traverse 0.6

33
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/natsort-0.1.1.6.tm

@ -5,8 +5,9 @@ package require flagfilter
namespace import ::flagfilter::check_flags namespace import ::flagfilter::check_flags
namespace eval natsort { namespace eval natsort {
#REVIEW - determine and document the purpose of scriptdir being added to tm path
proc scriptdir {} { proc scriptdir {} {
set possibly_linked_script [file dirname [file normalize [file join [info script] ...]]] set possibly_linked_script [file dirname [file normalize [file join [info script] __dummy__]]]
if {[file isdirectory $possibly_linked_script]} { if {[file isdirectory $possibly_linked_script]} {
return $possibly_linked_script return $possibly_linked_script
} else { } else {
@ -14,7 +15,11 @@ namespace eval natsort {
} }
} }
if {![interp issafe]} { if {![interp issafe]} {
tcl::tm::add [scriptdir] set sdir [scriptdir]
#puts stderr "natsort tcl::tm::add $sdir"
if {$sdir ni [tcl::tm::list]} {
catch {tcl::tm::add $sdir}
}
} }
} }
@ -36,6 +41,7 @@ namespace eval natsort {
} else { } else {
puts stderr "|>natsort_call_err> unable to interpret 2nd argument to do_error: '$then' should be one of '$levels' or 'exit <numericcode>'" puts stderr "|>natsort_call_err> unable to interpret 2nd argument to do_error: '$then' should be one of '$levels' or 'exit <numericcode>'"
} }
flush stderr
if {$::tcl_interactive} { if {$::tcl_interactive} {
#may not always be desirable - but assumed to be more useful not to exit despite request, to aid in debugging #may not always be desirable - but assumed to be more useful not to exit despite request, to aid in debugging
if {[string tolower $type] eq "exit"} { if {[string tolower $type] eq "exit"} {
@ -43,6 +49,7 @@ namespace eval natsort {
if {![string is digit -strict $code]} { if {![string is digit -strict $code]} {
puts stderr "|>natsort_call_err> unable to interpret 2nd argument to do_error: '$then' should be: 'exit <numericcode>'" puts stderr "|>natsort_call_err> unable to interpret 2nd argument to do_error: '$then' should be: 'exit <numericcode>'"
} }
flush stderr
} }
return -code error $msg return -code error $msg
} else { } else {
@ -1422,6 +1429,9 @@ namespace eval natsort {
proc called_directly_namematch {} { proc called_directly_namematch {} {
global argv0 global argv0
if {[info script] eq ""} {
return 0
}
#see https://wiki.tcl-lang.org/page/main+script #see https://wiki.tcl-lang.org/page/main+script
#trailing ... let's us resolve symlinks in last component of the path (could be something else like ___ but ... seems unlikely to collide with anything in the filesystem) #trailing ... let's us resolve symlinks in last component of the path (could be something else like ___ but ... seems unlikely to collide with anything in the filesystem)
if {[info exists argv0] if {[info exists argv0]
@ -1440,12 +1450,18 @@ namespace eval natsort {
#Review issues around comparing names vs using inodes (esp with respect to samba shares) #Review issues around comparing names vs using inodes (esp with respect to samba shares)
proc called_directly_inodematch {} { proc called_directly_inodematch {} {
global argv0 global argv0
if {[info exists argv0] if {[info exists argv0]
&& [file exists [info script]] && [file exists $argv0]} { && [file exists [info script]] && [file exists $argv0]} {
file stat $argv0 argv0Info file stat $argv0 argv0Info
file stat [info script] scriptInfo file stat [info script] scriptInfo
expr {$argv0Info(dev) == $scriptInfo(dev) if {$argv0Info(ino) == 0 || $scriptInfo(ino) == 0 || $argv0Info(dev) == 0 || $scriptInfo(dev) == 0} {
&& $argv0Info(ino) == $scriptInfo(ino)} #vfs?
#e.g //zipfs:/
return 0
}
return [expr {$argv0Info(dev) == $scriptInfo(dev)
&& $argv0Info(ino) == $scriptInfo(ino)}]
} else { } else {
return 0 return 0
} }
@ -1460,6 +1476,11 @@ namespace eval natsort {
#-- choose a policy and leave the others commented. #-- choose a policy and leave the others commented.
#set is_called_directly $is_namematch #set is_called_directly $is_namematch
#set is_called_directly $is_inodematch #set is_called_directly $is_inodematch
#puts "NATSORT: called_directly_namematch - $is_namematch"
#puts "NATSORT: called_directly_inodematch - $is_inodematch"
#flush stdout
set is_called_directly [expr {$is_namematch || $is_inodematch}] set is_called_directly [expr {$is_namematch || $is_inodematch}]
#set is_called_directly [expr {$is_namematch && $is_inodematch}] #set is_called_directly [expr {$is_namematch && $is_inodematch}]
### ###
@ -1921,6 +1942,8 @@ namespace eval natsort {
#set args [check_flags -caller test1 -defaults [list -collate nocase -algorithm sort -topchars "\uFFFF" -winlike 0 -debug 0 -stacktrace 0 -showsplits 0] -required {all} -extras {none} -commandprocessors {{-cmd -default help} {-cmd -cmdarg1 -default "."} {-cmd -cmdarg2 -default j}} -values $::argv ] #set args [check_flags -caller test1 -defaults [list -collate nocase -algorithm sort -topchars "\uFFFF" -winlike 0 -debug 0 -stacktrace 0 -showsplits 0] -required {all} -extras {none} -commandprocessors {{-cmd -default help} {-cmd -cmdarg1 -default "."} {-cmd -cmdarg2 -default j}} -values $::argv ]
#set args [check_flags -caller test1 -defaults [list -collate nocase -algorithm sort -topchars "\uFFFF" -winlike 0 -debug 0 -stacktrace 0 -showsplits 0] -required {all} -extras {none} -commandprocessors {ls {ls lsdir -default "\uFFFF"}} -values $::argv ] #set args [check_flags -caller test1 -defaults [list -collate nocase -algorithm sort -topchars "\uFFFF" -winlike 0 -debug 0 -stacktrace 0 -showsplits 0] -required {all} -extras {none} -commandprocessors {ls {ls lsdir -default "\uFFFF"}} -values $::argv ]
puts stderr "natsort directcall exit"
flush stderr
exit 0 exit 0
if {$::argc} { if {$::argc} {

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

File diff suppressed because it is too large Load Diff

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

@ -553,28 +553,51 @@ tcl::namespace::eval punk::ansi {
$obj destroy $obj destroy
return $result return $result
} }
proc example {} { proc example {args} {
set base [punk::repo::find_project]
set default_ansibase [file join $base src/testansi]
set argd [punk::args::get_dict [tstr -return string {
*proc -name punk::ansi::example -help "Display .ans image files in a grid that will fit in console
"
-colwidth -default 82 -help "Width of each column - default of 82 will fit a standard 80wide ansi image (when framed)
You can specify a narrower width to truncate images on the right side"
-folder -default "${$default_ansibase}" -help "Base folder for files if relative paths are used.
Defaults to <projectbase>/src/testansi - where projectbase is determined from current directory.
"
*values -min 0 -max -1
files -default {belinda.ans bot.ans flower.ans fish.ans} -multiple true -help "List of filenames - leave empty to display 4 defaults"
}] $args]
set colwidth [dict get $argd opts -colwidth]
set ansibase [file normalize [dict get $argd opts -folder]]
set fnames [dict get $argd values files]
#assumes fixed column widths e.g 80col images will fit in 82-width frames (common standard for old ansi art) (of arbitrary height)
#todo - review dependency on punk::repo ? #todo - review dependency on punk::repo ?
package require textblock package require textblock
package require punk::repo package require punk::repo
package require punk::console package require punk::console
set fnames [list belinda.ans bot.ans flower.ans fish.ans]
set base [punk::repo::find_project]
set ansibase [file join $base src/testansi]
if {![file exists $ansibase]} { if {![file exists $ansibase]} {
puts stderr "Missing testansi folder at $base/src/testansi" puts stderr "Missing folder at $ansibase"
puts stderr "Ensure ansi test files exist: $fnames" puts stderr "Ensure ansi test files exist: $fnames"
#error "punk::ansi::example Cannot find example files" #error "punk::ansi::example Cannot find example files"
} }
set missingbase [a+ yellow][textblock::block 80 23 ?][a] set missingbase [a+ yellow][textblock::block [expr {$colwidth-2}] 23 ?][a] ;#assuming standard frame - subtract 2 for left/right borders
set pics [list] set pics [list]
foreach f $fnames { foreach f $fnames {
if {![file exists $ansibase/$f]} { if {[file pathtype $f] ne "absolute"} {
set p [overtype::left $missingbase "[a+ red bold]\nMissing file\n$ansibase/$f[a]"] set filepath [file normalize $ansibase/$f]
} else {
set filepath [file normalize $f]
}
if {![file exists $filepath]} {
set p [overtype::left $missingbase "[a+ red bold]\nMissing file\n$f[a]"]
lappend pics [tcl::dict::create filename $f pic $p status missing] lappend pics [tcl::dict::create filename $f pic $p status missing]
} else { } else {
set img [join [lines_as_list -line trimline -block trimtail [ansicat $ansibase/$f]] \n] #set img [join [lines_as_list -line trimline -block trimtail [ansicat $filepath]] \n]
#-line trimline will wreck some images
set img [join [lines_as_list -block trimtail [ansicat $filepath]] \n]
lappend pics [tcl::dict::create filename $f pic $img status ok] lappend pics [tcl::dict::create filename $f pic $img status ok]
} }
} }
@ -582,30 +605,73 @@ tcl::namespace::eval punk::ansi {
set termsize [punk::console:::get_size] set termsize [punk::console:::get_size]
set margin 4 set margin 4
set freewidth [expr {[tcl::dict::get $termsize columns]-$margin}] set freewidth [expr {[tcl::dict::get $termsize columns]-$margin}]
set per_row [expr {$freewidth / 80}] set per_row [expr {$freewidth / $colwidth}]
set rowlist [list] set rowlist [list] ;# { {<img> <img>} {<img> <img>} }
set row [list] set heightlist [list] ;# { {<h> <h> } {<h> <h> } }
set i 1 set maxheights [list] ;# { <max> <max>}
set row [list] ;#wip row
set rowh [list] ;#wip row img heights
set i 1 ;#track image index of whole pics list
set rowindex 0
foreach picinfo $pics { foreach picinfo $pics {
set subtitle "" set subtitle ""
if {[tcl::dict::get $picinfo status] ne "ok"} { if {[tcl::dict::get $picinfo status] ne "ok"} {
set subtitle [tcl::dict::get $picinfo status] set subtitle [tcl::dict::get $picinfo status]
} }
set title [tcl::dict::get $picinfo filename] set title [tcl::dict::get $picinfo filename]
lappend row [textblock::frame -subtitle $subtitle -title $title [tcl::dict::get $picinfo pic]] set fr [textblock::frame -width $colwidth -subtitle $subtitle -title $title [tcl::dict::get $picinfo pic]]
# -- --- --- ---
#we need the max height of a row element to use join_basic instead of join below
# -- --- --- ---
set fr_height [textblock::height $fr]
lappend row $fr
lappend rowh $fr_height
set rowmax [lindex $maxheights $rowindex]
if {$rowmax eq ""} {
#empty result means no maxheights entry for this row yet
set rowmax $fr_height
lappend maxheights $rowmax
} else {
if {$fr_height > $rowmax} {
set rowmax $fr_height
lset maxheights end $rowmax
}
}
# -- --- --- ---
if {$i % $per_row == 0} { if {$i % $per_row == 0} {
lappend rowlist $row lappend rowlist $row
lappend heightlist $rowh
incr rowindex
set row [list] set row [list]
set rowh [list]
} elseif {$i == [llength $pics]} { } elseif {$i == [llength $pics]} {
lappend rowlist $row lappend rowlist $row
lappend heightlist $rowh
} }
incr i incr i
} }
#puts "--> maxheights: $maxheights"
#puts "--> heightlist: $heightlist"
set result "" set result ""
foreach r $rowlist { set rowindex 0
append result [textblock::join_basic -- {*}$r] \n set blankline [string repeat " " $colwidth]
foreach imgs $rowlist heights $heightlist {
set maxheight [lindex $maxheights $rowindex]
set adjusted_row [list]
foreach i $imgs h $heights {
if {$h < $maxheight} {
#add blank lines to bottom of shorter images so join_basic can be used.
#textblock::join of ragged-height images would work and remove the need for all the height calculation
#.. but it requires much more processing
append i [string repeat \n$blankline [expr {$maxheight - $h}]]
}
lappend adjusted_row $i
}
append result [textblock::join_basic -- {*}$adjusted_row] \n
incr rowindex
} }
@ -3199,6 +3265,28 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
return \x1b8 return \x1b8
} }
# -- --- --- --- --- # -- --- --- --- ---
#CRM Show Control Character Mode
proc enable_crm {} {
return \x1b\[3h
}
proc disable_crm {} {
return \x1b\[3l
}
#DECSNM
#Note this can invert the enclosed section including any already reversed by SGR 7 - depending on terminal support.
#e.g
#set test [a+ reverse]aaa[a+ noreverse]bbb
# - $test above can't just be reversed by putting another [a+ reverse] in front of it.
# - but the following will work (even if underlying terminal doesn't support ?5 sequences)
#overtype::renderspace -width 20 [enable_inverse]$test
proc enable_inverse {} {
return \x1b\[?5h
}
proc disable_inverse {} {
return \x1b\[?5l
}
#DECAWM - automatic line wrapping #DECAWM - automatic line wrapping
proc enable_line_wrap {} { proc enable_line_wrap {} {
@ -3399,6 +3487,9 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#the purpose of backspace (or line cr) in embedded text is unclear. Should it allow some sort of character combining/overstrike as it has sometimes done historically (nroff/less)? e.g a\b` as an alternative combiner or bolding if same char #the purpose of backspace (or line cr) in embedded text is unclear. Should it allow some sort of character combining/overstrike as it has sometimes done historically (nroff/less)? e.g a\b` as an alternative combiner or bolding if same char
#This should presumably only be done if the over_strike (os) capability is enabled in the terminal. Either way - it presumably won't affect printing width? #This should presumably only be done if the over_strike (os) capability is enabled in the terminal. Either way - it presumably won't affect printing width?
set line [punk::ansi::ansistrip $line] set line [punk::ansi::ansistrip $line]
#ANSI (e.g PM/SOS) can contain \b or \n or \t but won't contribute to length
#ansistrip must come before any other processing of these chars.
#we can't use simple \b processing if we get ansi codes and aren't actually processing them (e.g moves) #we can't use simple \b processing if we get ansi codes and aren't actually processing them (e.g moves)
set line [punk::char::strip_nonprinting_ascii $line] ;#only strip nonprinting after ansistrip - some like BEL are part of ansi set line [punk::char::strip_nonprinting_ascii $line] ;#only strip nonprinting after ansistrip - some like BEL are part of ansi
@ -3748,6 +3839,7 @@ tcl::namespace::eval punk::ansi {
-filter_fg 0\ -filter_fg 0\
-filter_bg 0\ -filter_bg 0\
-filter_reset 0\ -filter_reset 0\
-info 0\
] ]
#codes *must* already have been split so that one esc per element in codelist #codes *must* already have been split so that one esc per element in codelist
@ -3760,7 +3852,8 @@ tcl::namespace::eval punk::ansi {
set opts $defaultopts_sgr_merge_singles set opts $defaultopts_sgr_merge_singles
foreach {k v} $args { foreach {k v} $args {
switch -- $k { switch -- $k {
-filter_fg - -filter_bg - -filter_reset { -filter_fg - -filter_bg - -filter_reset -
-info {
tcl::dict::set opts $k $v tcl::dict::set opts $k $v
} }
default { default {
@ -4139,19 +4232,24 @@ tcl::namespace::eval punk::ansi {
set codemerge [tcl::string::trimright $codemerge {;}] set codemerge [tcl::string::trimright $codemerge {;}]
if {$unmergeable ne ""} { if {$unmergeable ne ""} {
set unmergeable [tcl::string::trimright $unmergeable {;}] set unmergeable [tcl::string::trimright $unmergeable {;}]
return "\x1b\[${codemerge}m\x1b\[${unmergeable}m[join $othercodes ""]" set mergeresult "\x1b\[${codemerge}m\x1b\[${unmergeable}m[join $othercodes ""]"
} else { } else {
return "\x1b\[${codemerge}m[join $othercodes ""]" set mergeresult "\x1b\[${codemerge}m[join $othercodes ""]"
} }
} else { } else {
if {$unmergeable eq ""} { if {$unmergeable eq ""} {
#there were no SGR codes - not even resets #there were no SGR codes - not even resets
return [join $othercodes ""] set mergeresult [join $othercodes ""]
} else { } else {
set unmergeable [tcl::string::trimright $unmergeable {;}] set unmergeable [tcl::string::trimright $unmergeable {;}]
return "\x1b\[${unmergeable}m[join $othercodes ""]" set mergeresult "\x1b\[${unmergeable}m[join $othercodes ""]"
} }
} }
if {[tcl::dict::get $opts -info]} {
return [dict create sgr $codemerge unmergeable $unmergeable othercodes $othercodes mergeresult $mergeresult codestate $codestate]
} else {
return $mergeresult
}
} }
#has_sgr_reset - rather than support this function - create an sgr normalize function that removes dead params and brings reset to front of param list? #has_sgr_reset - rather than support this function - create an sgr normalize function that removes dead params and brings reset to front of param list?
@ -4240,7 +4338,7 @@ tcl::namespace::eval punk::ansi::ta {
#we also need to track the start of ST terminated code and not add it for replay (in the ansistring functions) #we also need to track the start of ST terminated code and not add it for replay (in the ansistring functions)
#variable re_ST {(?:\x1bX|\u0098|\x1b\^|\u009E|\x1b_|\u009F)(?:[^\x1b\007\u009c]*)(?:\x1b\\|\007|\u009c)} ;#downsides: early terminating with nests, mixes 7bit 8bit start/ends (does that exist in the wild?) #variable re_ST {(?:\x1bX|\u0098|\x1b\^|\u009E|\x1b_|\u009F)(?:[^\x1b\007\u009c]*)(?:\x1b\\|\007|\u009c)} ;#downsides: early terminating with nests, mixes 7bit 8bit start/ends (does that exist in the wild?)
#keep our 8bit/7bit start-end codes separate #keep our 8bit/7bit start-end codes separate
variable re_ST {(?:\x1bP|\x1bX|\x1b\^|\x1b_)(?:(?!\x1b\\|007).)*(?:\x1b\\|\007)|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)} variable re_ST {(?:\x1bP|\x1bX|\x1b\^|\x1b_)(?:(?!\x1b\\|\007).)*(?:\x1b\\|\007)|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)}
@ -4252,7 +4350,7 @@ tcl::namespace::eval punk::ansi::ta {
# -- --- --- --- # -- --- --- ---
#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.
variable re_ansi_detect {(?:\x1b(?:\((?:0|B)|\[(?:[\x20-\x2f\x30-\x3f]*[\x40-\x7e])|\](?:(?:[^\007]*)\007|(?:(?!\x1b\\).)*\x1b\\)|(?:P|X|\^|_)(?:(?:(?!\x1b\\|007).)*(?:\x1b\\|\007))|c|7|8|M|E|D|H|=|>|(?:#(?:3|4|5|6|8))))|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)|(?:\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]|(?:\u009d)(?:[^\u009c]*)?\u009c} variable re_ansi_detect {(?:\x1b(?:\((?:0|B)|\[(?:[\x20-\x2f\x30-\x3f]*[\x40-\x7e])|\](?:(?:[^\007]*)\007|(?:(?!\x1b\\).)*\x1b\\)|(?:P|X|\^|_)(?:(?:(?!\x1b\\|\007).)*(?:\x1b\\|\007))|c|7|8|M|E|D|H|=|>|(?:#(?:3|4|5|6|8))))|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)|(?:\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]|(?:\u009d)(?:[^\u009c]*)?\u009c}
# -- --- --- --- # -- --- --- ---
@ -5674,7 +5772,12 @@ tcl::namespace::eval punk::ansi::ansistring {
ENQ [list \x05 \u2405]\ ENQ [list \x05 \u2405]\
ACK [list \x06 \u2406]\ ACK [list \x06 \u2406]\
BEL [list \x07 \u2407]\ BEL [list \x07 \u2407]\
BS [list \x08 \u2408]\
HT [list \x09 \u2409]\
LF [list \x0a \u240a]\
VT [list \x0b \u240b]\
FF [list \x0c \u240c]\ FF [list \x0c \u240c]\
CR [list \x0d \u240d]\
SO [list \x0e \u240e]\ SO [list \x0e \u240e]\
SF [list \x0f \u240f]\ SF [list \x0f \u240f]\
DLE [list \x10 \u2410]\ DLE [list \x10 \u2410]\
@ -5688,12 +5791,15 @@ tcl::namespace::eval punk::ansi::ansistring {
CAN [list \x18 \u2418]\ CAN [list \x18 \u2418]\
EM [list \x19 \u2419]\ EM [list \x19 \u2419]\
SUB [list \x1a \u241a]\ SUB [list \x1a \u241a]\
ESC [list \x1b \u241b]\
FS [list \x1c \u241c]\ FS [list \x1c \u241c]\
GS [list \x1d \u241d]\ GS [list \x1d \u241d]\
RS [list \x1e \u241e]\ RS [list \x1e \u241e]\
US [list \x1f \u241f]\ US [list \x1f \u241f]\
SP [list \x20 \u2420]\
DEL [list \x7f \u2421]\ DEL [list \x7f \u2421]\
] ]
#alternate symbols for space #alternate symbols for space
# \u2422 Blank Symbol (b with forwardslash overly) # \u2422 Blank Symbol (b with forwardslash overly)
# \u2423 Open Box (square bracket facing up like a tray/box) # \u2423 Open Box (square bracket facing up like a tray/box)
@ -5836,6 +5942,7 @@ tcl::namespace::eval punk::ansi::ansistring {
-cr 1\ -cr 1\
-lf 0\ -lf 0\
-vt 0\ -vt 0\
-ff 1\
-ht 1\ -ht 1\
-bs 1\ -bs 1\
-sp 1\ -sp 1\
@ -5850,16 +5957,22 @@ tcl::namespace::eval punk::ansi::ansistring {
set opt_cr [tcl::dict::get $opts -cr] set opt_cr [tcl::dict::get $opts -cr]
set opt_lf [tcl::dict::get $opts -lf] set opt_lf [tcl::dict::get $opts -lf]
set opt_vt [tcl::dict::get $opts -vt] set opt_vt [tcl::dict::get $opts -vt]
set opt_ff [tcl::dict::get $opts -ff]
set opt_ht [tcl::dict::get $opts -ht] set opt_ht [tcl::dict::get $opts -ht]
set opt_bs [tcl::dict::get $opts -bs] set opt_bs [tcl::dict::get $opts -bs]
set opt_sp [tcl::dict::get $opts -sp] set opt_sp [tcl::dict::get $opts -sp]
# -- --- --- --- --- # -- --- --- --- ---
# -lf 2, -vt 2 and -ff 2 are useful for CRM mode (Show Control Character Mode) in the terminal - where a newline is expected to display after the character.
set visuals_opt $debug_visuals set visuals_opt $debug_visuals
set visuals_opt [dict remove $visuals_opt CR ESC LF VT FF HT BS SP]
if {$opt_esc} { if {$opt_esc} {
tcl::dict::set visuals_opt ESC [list \x1b \u241b] tcl::dict::set visuals_opt ESC [list \x1b \u241b]
} else {
tcl::dict::unset visuals_opt ESC
} }
if {$opt_cr} { if {$opt_cr} {
tcl::dict::set visuals_opt CR [list \x0d \u240d] tcl::dict::set visuals_opt CR [list \x0d \u240d]
@ -5870,9 +5983,20 @@ tcl::namespace::eval punk::ansi::ansistring {
if {$opt_lf == 2} { if {$opt_lf == 2} {
tcl::dict::set visuals_opt LF [list \x0a \u240a\n] tcl::dict::set visuals_opt LF [list \x0a \u240a\n]
} }
if {$opt_vt} { if {$opt_vt == 1} {
tcl::dict::set visuals_opt VT [list \x0b \u240b] tcl::dict::set visuals_opt VT [list \x0b \u240b]
} }
if {$opt_vt == 2} {
tcl::dict::set visuals_opt VT [list \x0b \u240b\n]
}
switch -exact -- $opt_ff {
1 {
tcl::dict::set visuals_opt FF [list \x0c \u240c]
}
2 {
tcl::dict::set visuals_opt FF [list \x0c \u240c\n]
}
}
if {$opt_ht} { if {$opt_ht} {
tcl::dict::set visuals_opt HT [list \x09 \u2409] tcl::dict::set visuals_opt HT [list \x09 \u2409]
} }

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

@ -552,13 +552,26 @@ tcl::namespace::eval punk::char {
string eq $s [encoding convertto $encname [encoding convertfrom $encname $s]] string eq $s [encoding convertto $encname [encoding convertfrom $encname $s]]
} }
} else { } else {
#review - use -profile?
proc encodable "s {enc [encoding system]}" { proc encodable "s {enc [encoding system]}" {
set encname [encname $enc] set encname [encname $enc]
string eq $s [encoding convertfrom $encname [encoding convertto $encname $s]] if {![catch {
string eq $s [encoding convertfrom $encname [encoding convertto $encname $s]]
} result]} {
return $result
} else {
return 0
}
} }
proc decodable "s {enc [encoding system]}" { proc decodable "s {enc [encoding system]}" {
set encname [encname $enc] set encname [encname $enc]
string eq $s [encoding convertto $encname [encoding convertfrom $encname $s]] if {![catch {
string eq $s [encoding convertto $encname [encoding convertfrom $encname $s]]
} result]} {
return $result
} else {
return 0
}
} }
} }
#-- --- --- --- --- --- --- --- #-- --- --- --- --- --- --- ---

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

@ -13,11 +13,51 @@
# @@ Meta End # @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[manpage_begin punkshell_module_punk::console 0 0.1.1]
#[copyright "2024"]
#[titledesc {punk console}] [comment {-- Name section and table of contents description --}]
#[moddesc {punk console}] [comment {-- Description at end of page heading --}]
#[require punk::console]
#[keywords module console terminal]
#[description]
#[para]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Overview]
#[para] overview of punk::console
#[subsection Concepts]
#[para]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements ## Requirements
##e.g package require frobz # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection dependencies]
#[para] packages used by punk::console
#[list_begin itemized]
package require Tcl 8.6-
package require punk::ansi package require punk::ansi
#*** !doctools
#[item] [package {Tcl 8.6-}]
#[item] [package {punk::ansi}]
#*** !doctools
#[list_end]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section API]
#if {"windows" eq $::tcl_platform(platform)} { #if {"windows" eq $::tcl_platform(platform)} {
@ -30,6 +70,13 @@ package require punk::ansi
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval punk::console { namespace eval punk::console {
#*** !doctools
#[subsection {Namespace punk::console}]
#[para]
#*** !doctools
#[list_begin definitions]
variable tabwidth 8 ;#default only - will attempt to detect and set to that configured in terminal variable tabwidth 8 ;#default only - will attempt to detect and set to that configured in terminal
#Note that windows terminal cooked mode seems to use 8 for interactive use even if set differently #Note that windows terminal cooked mode seems to use 8 for interactive use even if set differently
#e.g typing tab characters may still be echoed 8-spaced while writing to stdout my obey the terminal's tab stops. #e.g typing tab characters may still be echoed 8-spaced while writing to stdout my obey the terminal's tab stops.
@ -1028,23 +1075,37 @@ namespace eval punk::console {
return [split [get_cursor_pos $inoutchannels] ";"] return [split [get_cursor_pos $inoutchannels] ";"]
} }
#todo - determine cursor on/off state before the call to restore properly. May only be possible #todo - determine cursor on/off state before the call to restore properly.
proc get_size {{inoutchannels {stdin stdout}}} { proc get_size {{inoutchannels {stdin stdout}}} {
lassign $inoutchannels in out lassign $inoutchannels in out
#we can't reliably use [chan names] for stdin,stdout. There could be stacked channels and they may have a names such as file22fb27fe810 #we can't reliably use [chan names] for stdin,stdout. There could be stacked channels and they may have a names such as file22fb27fe810
#chan eof is faster whether chan exists or not than #chan eof is faster whether chan exists or not than
if {[catch {chan eof $in} is_eof]} { if {[catch {chan eof $out} is_eof]} {
error "punk::console::get_size input channel $in seems to be closed ([info level 1])" error "punk::console::get_size output channel $out seems to be closed ([info level 1])"
} else { } else {
if {$is_eof} { if {$is_eof} {
error "punk::console::get_size eof on input channel $in ([info level 1])" error "punk::console::get_size eof on output channel $out ([info level 1])"
} }
} }
if {[catch {chan eof $out} is_eof]} { #we don't need to care about the input channel if chan configure on the output can give us the info.
error "punk::console::get_size output channel $out seems to be closed ([info level 1])" #short circuit ansi cursor movement method if chan configure supports the -winsize value
set outconf [chan configure $out]
if {[dict exists $outconf -winsize]} {
#this mechanism is much faster than ansi cursor movements
#REVIEW check if any x-platform anomalies with this method?
#can -winsize key exist but contain erroneous info? We will check that we get 2 ints at least
lassign [dict get $outconf -winsize] cols lines
if {[string is integer -strict $cols] && [string is integer -strict $lines]} {
return [list columns $cols rows $lines]
}
#continue on to ansi mechanism if we didn't get 2 ints
}
if {[catch {chan eof $in} is_eof]} {
error "punk::console::get_size input channel $in seems to be closed ([info level 1])"
} else { } else {
if {$is_eof} { if {$is_eof} {
error "punk::console::get_size eof on output channel $out ([info level 1])" error "punk::console::get_size eof on input channel $in ([info level 1])"
} }
} }
@ -1067,18 +1128,28 @@ namespace eval punk::console {
} }
} }
#faster - but uses cursor_save - which we may want to avoid if calling during another operation which uses cursor save/restore #faster than get_size when it is using ansi mechanism - but uses cursor_save - which we may want to avoid if calling during another operation which uses cursor save/restore
proc get_size_cursorrestore {} { proc get_size_cursorrestore {{inoutchannels {stdin stdout}}} {
lassign $inoutchannels in out
#we use the same shortcircuit mechanism as get_size to avoid ansi at all if the output channel will give us the info directly
set outconf [chan configure $out]
if {[dict exists $outconf -winsize]} {
lassign [dict get $outconf -winsize] cols lines
if {[string is integer -strict $cols] && [string is integer -strict $lines]} {
return [list columns $cols rows $lines]
}
}
if {[catch { if {[catch {
#some terminals (conemu on windows) scroll the viewport when we make a big move down like this - a move to 1 1 immediately after cursor_save doesn't seem to fix that. #some terminals (conemu on windows) scroll the viewport when we make a big move down like this - a move to 1 1 immediately after cursor_save doesn't seem to fix that.
#This issue also occurs when switching back from the alternate screen buffer - so perhaps that needs to be addressed elsewhere. #This issue also occurs when switching back from the alternate screen buffer - so perhaps that needs to be addressed elsewhere.
puts -nonewline [punk::ansi::cursor_off][punk::ansi::cursor_save_dec][punk::ansi::move 2000 2000] puts -nonewline $out [punk::ansi::cursor_off][punk::ansi::cursor_save_dec][punk::ansi::move 2000 2000]
lassign [get_cursor_pos_list] lines cols lassign [get_cursor_pos_list $inoutchannels] lines cols
puts -nonewline [punk::ansi::cursor_restore][punk::console::cursor_on];flush stdout puts -nonewline $out [punk::ansi::cursor_restore][punk::console::cursor_on];flush $out
set result [list columns $cols rows $lines] set result [list columns $cols rows $lines]
} errM]} { } errM]} {
puts -nonewline [punk::ansi::cursor_restore_dec] puts -nonewline $out [punk::ansi::cursor_restore_dec]
puts -nonewline [punk::ansi::cursor_on] puts -nonewline $out [punk::ansi::cursor_on]
error "$errM" error "$errM"
} else { } else {
return $result return $result
@ -1803,6 +1874,9 @@ namespace eval punk::console {
} }
#run the test and allow warnings to be emitted to stderr on package load. User should know the terminal and/or Tcl version are not optimal for unicode character work #run the test and allow warnings to be emitted to stderr on package load. User should know the terminal and/or Tcl version are not optimal for unicode character work
#set testresult [test1] #set testresult [test1]
#*** !doctools
#[list_end] [comment {--- end definitions namespace punk::console ---}]
} }
@ -1826,3 +1900,6 @@ package provide punk::console [namespace eval punk::console {
set version 0.1.1 set version 0.1.1
}] }]
return return
#*** !doctools
#[manpage_end]

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

@ -967,7 +967,7 @@ namespace eval punk::du {
dict set effective_opts -with_times $timed_types dict set effective_opts -with_times $timed_types
dict set effective_opts -with_sizes $sized_types dict set effective_opts -with_sizes $sized_types
return [list dirs $dirs vfsmounts $vfsmounts links $links files $files filesizes [dict get $mdata_lists fsizes] sizes [dict get $mdata_lists allsizes] times [dict get $mdata_lists alltimes] flaggedhidden {} flaggedsystem {} flaggedreadonly {} altname {} opts $effective_opts errors $errors] return [list dirs $dirs vfsmounts $vfsmounts links $links files $files filesizes [dict get $mdata_lists fsizes] sizes [dict get $mdata_lists allsizes] times [dict get $mdata_lists alltimes] flaggedhidden $flaggedhidden flaggedsystem {} flaggedreadonly {} altname {} opts $effective_opts errors $errors]
} }
#zipfs attributes/behaviour fairly different to tclvfs - keep separate #zipfs attributes/behaviour fairly different to tclvfs - keep separate

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

@ -328,7 +328,17 @@ tcl::namespace::eval punk::lib::compat {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval punk::lib { namespace eval punk::lib {
tcl::namespace::export * tcl::namespace::export *
#variable xyz variable has_struct_list
set has_struct_list [expr {![catch {package require struct::list}]}]
variable has_struct_set
set has_struct_set [expr {![catch {package require struct::set}]}]
variable has_punk_ansi
set has_punk_ansi [expr {![catch {package require punk::ansi}]}]
set has_twapi 0
if {"windows" eq $::tcl_platform(platform)} {
set has_twapi [expr {![catch {package require twapi}]}]
}
#*** !doctools #*** !doctools
#[subsection {Namespace punk::lib}] #[subsection {Namespace punk::lib}]
@ -614,7 +624,9 @@ namespace eval punk::lib {
} }
proc pdict {args} { proc pdict {args} {
if {[catch {package require punk::ansi} errM]} { package require punk::args
variable has_punk_ansi
if {!$has_punk_ansi} {
set sep " = " set sep " = "
} else { } else {
#set sep " [a+ Web-seagreen]=[a] " #set sep " [a+ Web-seagreen]=[a] "
@ -691,14 +703,15 @@ namespace eval punk::lib {
# - Copy proc and attempt rework so we can get back to this as a baseline for functionality # - 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) proc showdict {args} { ;# analogous to parray (except that it takes the dict as a value)
#set sep " [a+ Web-seagreen]=[a] " #set sep " [a+ Web-seagreen]=[a] "
if {[catch {package require punk::ansi} errM]} { variable has_punk_ansi
set sep " = " if {!$has_punk_ansi} {
set RST "" set RST ""
set sep " = "
set sep_mismatch " mismatch " set sep_mismatch " mismatch "
} else { } else {
set sep " [punk::ansi::a+ Green]=[punk::ansi::a] " ;#stick to basic default colours for wider terminal support
set RST [punk::ansi::a] set RST [punk::ansi::a]
set sep_mismatch " [punk::ansi::a+ Brightred undercurly underline undt-white]mismatch[punk::ansi::a] " set sep " [punk::ansi::a+ Green]=$RST " ;#stick to basic default colours for wider terminal support
set sep_mismatch " [punk::ansi::a+ Brightred undercurly underline undt-white]mismatch$RST "
} }
package require punk ;#we need pipeline pattern matching features package require punk ;#we need pipeline pattern matching features
package require textblock package require textblock
@ -836,7 +849,7 @@ namespace eval punk::lib {
lappend keyset_structure dict lappend keyset_structure dict
} }
@* { @* {
puts ---->HERE<---- #puts "showdict ---->@*<----"
dict set pattern_this_structure $p list dict set pattern_this_structure $p list
set keys [punk::lib::range 0 [llength $dval]-1] set keys [punk::lib::range 0 [llength $dval]-1]
lappend keyset {*}$keys lappend keyset {*}$keys
@ -1405,16 +1418,29 @@ namespace eval punk::lib {
} }
proc is_list_all_in_list {small large} { proc is_list_all_in_list {small large} {
package require struct::list
package require struct::set
set small_in_large [lsort [struct::set intersect [lsort -unique $small] $large ]] set small_in_large [lsort [struct::set intersect [lsort -unique $small] $large ]]
return [struct::list equal [lsort $small] $small_in_large] return [struct::list equal [lsort $small] $small_in_large]
} }
if {!$has_struct_list || !$has_struct_set} {
set body {
package require struct::list
package require struct::set
}
append body [info body is_list_all_in_list]
proc is_list_all_in_list {small large} $body
}
proc is_list_all_ni_list {a b} { proc is_list_all_ni_list {a b} {
package require struct::set
set i [struct::set intersect $a $b] set i [struct::set intersect $a $b]
return [expr {[llength $i] == 0}] return [expr {[llength $i] == 0}]
} }
if {!$has_struct_set} {
set body {
package require struct::list
}
append body [info body is_list_all_ni_list]
proc is_list_all_ni_list {a b} $body
}
#somewhat like struct::set difference - but order preserving, and doesn't treat as a 'set' so preserves dupes in fromlist #somewhat like struct::set difference - but order preserving, and doesn't treat as a 'set' so preserves dupes in fromlist
#struct::set difference may happen to preserve ordering when items are integers, but order can't be relied on, #struct::set difference may happen to preserve ordering when items are integers, but order can't be relied on,
@ -1465,18 +1491,22 @@ namespace eval punk::lib {
return [array names tmp] return [array names tmp]
} }
package require struct::set #default/fallback implementation
if {[struct::set equal [struct::set union {a a} {}] {a}]} { proc lunique_unordered {list} {
proc lunique_unordered {list} { lunique $list
struct::set union $list {} }
} if {$has_struct_set} {
} else { if {[struct::set equal [struct::set union {a a} {}] {a}]} {
puts stderr "WARNING: struct::set union <list> <emptylist> no longer dedupes!" proc lunique_unordered {list} {
#we could also test a sequence of: struct::set add struct::set union $list {}
proc lunique_unordered {list} { }
tailcall lunique $list } else {
puts stderr "WARNING: struct::set union <list> <emptylist> no longer dedupes!"
#we could also test a sequence of: struct::set add
} }
} }
#order-preserving #order-preserving
proc lunique {list} { proc lunique {list} {
set new {} set new {}
@ -1863,14 +1893,14 @@ namespace eval punk::lib {
set opt_empty [tcl::dict::get $opts -empty_as_hex] set opt_empty [tcl::dict::get $opts -empty_as_hex]
# -- --- --- --- # -- --- --- ---
set list_largeHex [lmap h $list_largeHex[unset list_largeHex] {string map [list _ ""] [string trim $h]}] set list_largeHex [lmap h $list_largeHex[unset list_largeHex] {string map {_ ""} [string trim $h]}]
if {$opt_validate} { if {$opt_validate} {
#Note appended F so that we accept list of empty strings as per the documentation #Note appended F so that we accept list of empty strings as per the documentation
if {![string is xdigit -strict [join $list_largeHex ""]F ]} { if {![string is xdigit -strict [join $list_largeHex ""]F ]} {
error "[namespace current]::hex2dec error: non-hex digits encountered after stripping underscores and leading/trailing whitespace for each element\n $list_largeHex" error "[namespace current]::hex2dec error: non-hex digits encountered after stripping underscores and leading/trailing whitespace for each element\n $list_largeHex"
} }
} }
if {![string is xdigit -strict [string map [list _ ""] $opt_empty]]} { if {![string is xdigit -strict [string map {_ ""} $opt_empty]]} {
#mapping empty string to a value destroys any advantage of -scanonly #mapping empty string to a value destroys any advantage of -scanonly
#todo - document that -scanonly has 2 restrictions - each element must be valid hex and less than 7 chars long #todo - document that -scanonly has 2 restrictions - each element must be valid hex and less than 7 chars long
#set list_largeHex [lmap v $list_largeHex[set list_largeHex {}] {expr {$v eq ""} ? {0} : {[set v]}}] #set list_largeHex [lmap v $list_largeHex[set list_largeHex {}] {expr {$v eq ""} ? {0} : {[set v]}}]
@ -1878,7 +1908,7 @@ namespace eval punk::lib {
error "[namespace current]::hex2dec error: empty values in list cannot be mapped to non-hex $opt_empty" error "[namespace current]::hex2dec error: empty values in list cannot be mapped to non-hex $opt_empty"
} }
} else { } else {
set opt_empty [string trim [string map [list _ ""] $opt_empty]] set opt_empty [string trim [string map {_ ""} $opt_empty]]
if {[set first_empty [lsearch $list_largeHex ""]] >= 0} { if {[set first_empty [lsearch $list_largeHex ""]] >= 0} {
#set list_largeHex [lmap v $list_largeHex[set list_largeHex {}] {expr {$v eq ""} ? {$opt_empty} : {$v}}] #set list_largeHex [lmap v $list_largeHex[set list_largeHex {}] {expr {$v eq ""} ? {$opt_empty} : {$v}}]
set nonempty_head [lrange $list_largeHex 0 $first_empty-1] set nonempty_head [lrange $list_largeHex 0 $first_empty-1]
@ -1931,13 +1961,13 @@ namespace eval punk::lib {
} }
set fmt "%${opt_width}.${opt_width}ll${spec}" set fmt "%${opt_width}.${opt_width}ll${spec}"
set list_decimals [lmap d $list_decimals[unset list_decimals] {string map [list _ ""] [string trim $d]}] set list_decimals [lmap d $list_decimals[unset list_decimals] {string map {_ ""} [string trim $d]}]
if {![string is digit -strict [string map [list _ ""] $opt_empty]]} { if {![string is digit -strict [string map {_ ""} $opt_empty]]} {
if {[lsearch $list_decimals ""] >=0} { if {[lsearch $list_decimals ""] >=0} {
error "[namespace current]::dec2hex error: empty values in list cannot be mapped to non-decimal $opt_empty" error "[namespace current]::dec2hex error: empty values in list cannot be mapped to non-decimal $opt_empty"
} }
} else { } else {
set opt_empty [string map [list _ ""] $opt_empty] set opt_empty [string map {_ ""} $opt_empty]
if {[set first_empty [lsearch $list_decimals ""]] >= 0} { if {[set first_empty [lsearch $list_decimals ""]] >= 0} {
set nonempty_head [lrange $list_decimals 0 $first_empty-1] set nonempty_head [lrange $list_decimals 0 $first_empty-1]
set list_decimals [concat $nonempty_head [lmap v [lrange $list_decimals $first_empty end] {expr {$v eq ""} ? {$opt_empty} : {$v}}]] set list_decimals [concat $nonempty_head [lmap v [lrange $list_decimals $first_empty end] {expr {$v eq ""} ? {$opt_empty} : {$v}}]]
@ -2402,13 +2432,14 @@ namespace eval punk::lib {
# important for pipeline & match_assign # important for pipeline & match_assign
# -line trimline|trimleft|trimright -block trimhead|trimtail|triminner|trimall|trimhead1|trimtail1|collateempty -commandprefix {string length} ? # -line trimline|trimleft|trimright -block trimhead|trimtail|triminner|trimall|trimhead1|trimtail1|collateempty -commandprefix {string length} ?
# -block trimming only trims completely empty lines. use -line trimming to remove whitespace e.g -line trimright will clear empty lines without affecting leading whitespace on other lines that aren't pure whitespace # -block trimming only trims completely empty lines. use -line trimming to remove whitespace e.g -line trimright will clear empty lines without affecting leading whitespace on other lines that aren't pure whitespace
proc linelist {args} {
set linelist_body {
set usage "linelist ?-line trimline|trimleft|trimright? ?-block trimhead|trimtail|triminner|trimall|trimhead1|trimtail1|collateempty? -commandprefix <cmdlist> text" set usage "linelist ?-line trimline|trimleft|trimright? ?-block trimhead|trimtail|triminner|trimall|trimhead1|trimtail1|collateempty? -commandprefix <cmdlist> text"
if {[llength $args] == 0} { if {[llength $args] == 0} {
error "linelist missing textchunk argument usage:$usage" error "linelist missing textchunk argument usage:$usage"
} }
set text [lindex $args end] set text [lindex $args end]
set text [string map [list \r\n \n] $text] ;#review - option? set text [string map {\r\n \n} $text] ;#review - option?
set arglist [lrange $args 0 end-1] set arglist [lrange $args 0 end-1]
set opts [tcl::dict::create\ set opts [tcl::dict::create\
@ -2441,10 +2472,10 @@ namespace eval punk::lib {
} }
} }
#normalize certain combos #normalize certain combos
if {[set posn [lsearch $opt_block trimhead1]] >=0 && "trimhead" in $opt_block} { if {"trimhead" in $opt_block && [set posn [lsearch $opt_block trimhead1]] >=0} {
set opt_block [lreplace $opt_block $posn $posn] set opt_block [lreplace $opt_block $posn $posn]
} }
if {[set posn [lsearch $opt_block trimtail1]] >=0 && "trimtail" in $opt_block} { if {"trimtail" in $opt_block && [set posn [lsearch $opt_block trimtail1]] >=0} {
set opt_block [lreplace $opt_block $posn $posn] set opt_block [lreplace $opt_block $posn $posn]
} }
if {"trimall" in $opt_block} { if {"trimall" in $opt_block} {
@ -2594,9 +2625,10 @@ namespace eval punk::lib {
#Each resulting line should have a reset of some type at start and a pure-reset at end to stop #Each resulting line should have a reset of some type at start and a pure-reset at end to stop
#see if we can find an ST sequence that most terminals will not display for marking sections? #see if we can find an ST sequence that most terminals will not display for marking sections?
if {$opt_ansireplays} { if {$opt_ansireplays} {
package require punk::ansi #package require punk::ansi
<require_punk_ansi>
if {$opt_ansiresets} { if {$opt_ansiresets} {
set RST [punk::ansi::a] set RST "\x1b\[0m"
} else { } else {
set RST "" set RST ""
} }
@ -2721,6 +2753,15 @@ namespace eval punk::lib {
return $linelist return $linelist
} }
if {$has_punk_ansi} {
#optimise linelist as much as possible
set linelist_body [string map {<require_punk_ansi> ""} $linelist_body]
} else {
#punk ansi not avail at time of package load.
#by putting in calls to punk::ansi the user will get appropriate error messages
set linelist_body [string map {<require_punk_ansi> "package require punk::ansi"} $linelist_body]
}
proc linelist {args} $linelist_body
interp alias {} errortime {} punk::lib::errortime interp alias {} errortime {} punk::lib::errortime
@ -2846,6 +2887,133 @@ namespace eval punk::lib {
proc temperature_c_to_f {deg_celsius} { proc temperature_c_to_f {deg_celsius} {
return [expr {($deg_celsius * (9/5.0)) + 32}] return [expr {($deg_celsius * (9/5.0)) + 32}]
} }
proc interp_sync_package_paths {interp} {
if {![interp exists $interp]} {
error "interp_sync_package_paths error. interp '$interp' not found. Create it first with \[interp create $interp\]"
}
interp eval $interp [list set ::auto_path $::auto_path]
interp eval $interp {tcl::tm::remove {*}[tcl::tm::list]}
interp eval $interp [list tcl::tm::add {*}[lreverse [tcl::tm::list]]]
}
proc objclone {obj} {
append obj2 $obj {}
}
proc format_number {numbers_or_commaformattednumbers {delim ""} {groupsize ""}} {
variable has_twapi
if {$has_twapi} {
if {$delim eq "" && $groupsize eq ""} {
set localeid [twapi::get_system_default_lcid]
}
}
set results [list]
set nums [objclone $numbers_or_commaformattednumbers] ;#stops single num from getting internal rep of list
foreach inputnum $nums {
set number [objclone $inputnum]
#also handle tcl 8.7+ underscores in numbers
set number [string map [list _ "" , ""] $number]
#normalize e.g 2e4 -> 20000.0
set number [expr {$number}]
if {$has_twapi} {
if {$delim eq "" && $groupsize eq ""} {
lappend results [twapi::format_number $number $localeid -idigits -1]
continue
} else {
if {$delim eq ""} {set delim ","}
if {$groupsize eq ""} {set groupsize 3}
lappend results [twapi::format_number $number 0 -idigits -1 -sthousand $delim -sgrouping $groupsize]
continue
}
}
#todo - get configured user defaults
set delim ","
set groupsize 3
lappend results [delimit_number $number $delim $groupsize]
}
if {[llength $results] == 1} {
#keep intrep as string rather than list
return [lindex $results 0]
}
return $results
}
#from wiki https://wiki.tcl-lang.org/page/Delimiting+Numberse
# Given a number represented as a string, insert delimiters to break it up for
# readability. Normally, the delimiter will be a comma which will be inserted every
# three digits. However, the delimiter and groupsize are optional arguments,
# permitting use in other locales.
#
# The string is assumed to consist of digits, possibly preceded by spaces,
# and possibly containing a decimal point, i.e.: [:space:]*[:digit:]*\.[:digit:]*
proc delimit_number {unformattednumber {delim ","} {GroupSize 3}} {
set number [punk::objclone $unformattednumber]
set number [string map {_ ""} $number]
#normalize using expr - e.g 2e4 -> 20000.0
set number [expr {$number}]
# First, extract right hand part of number, up to and including decimal point
set point [string last "." $number];
if {$point >= 0} {
set PostDecimal [string range $number [expr $point + 1] end];
set PostDecimalP 1;
} else {
set point [expr [string length $number] + 1]
set PostDecimal "";
set PostDecimalP 0;
}
# Now extract any leading spaces. review - regex for whitespace instead of just ascii space?
set ind 0;
while {[string equal [string index $number $ind] \u0020]} {
incr ind;
}
set FirstNonSpace $ind;
set LastSpace [expr $FirstNonSpace - 1];
set LeadingSpaces [string range $number 0 $LastSpace];
# Now extract the non-fractional part of the number, omitting leading spaces.
set MainNumber [string range $number $FirstNonSpace [expr $point -1]];
# Insert commas into the non-fractional part.
set Length [string length $MainNumber];
set Phase [expr $Length % $GroupSize]
set PhaseMinusOne [expr $Phase -1];
set DelimitedMain "";
#First we deal with the extra stuff.
if {$Phase > 0} {
append DelimitedMain [string range $MainNumber 0 $PhaseMinusOne];
}
set FirstInGroup $Phase;
set LastInGroup [expr $FirstInGroup + $GroupSize -1];
while {$LastInGroup < $Length} {
if {$FirstInGroup > 0} {
append DelimitedMain $delim;
}
append DelimitedMain [string range $MainNumber $FirstInGroup $LastInGroup];
incr FirstInGroup $GroupSize
incr LastInGroup $GroupSize
}
# Reassemble the number.
if {$PostDecimalP} {
return [format "%s%s.%s" $LeadingSpaces $DelimitedMain $PostDecimal];
} else {
return [format "%s%s" $LeadingSpaces $DelimitedMain];
}
}
#*** !doctools #*** !doctools
#[list_end] [comment {--- end definitions namespace punk::lib ---}] #[list_end] [comment {--- end definitions namespace punk::lib ---}]
} }
@ -2998,7 +3166,9 @@ tcl::namespace::eval punk::lib::system {
return [concat $smallfactors [lreverse $largefactors] $x] return [concat $smallfactors [lreverse $largefactors] $x]
} }
# incomplte - report which is the innermost bracket/quote etc awaiting completion for a Tcl command
# incomplete - report which is the innermost bracket/quote etc awaiting completion for a Tcl command
#important - used by punk::repl #important - used by punk::repl
proc incomplete {partial} { proc incomplete {partial} {
#we can apparently get away without concatenating current innerpartial to previous in list - REVIEW. #we can apparently get away without concatenating current innerpartial to previous in list - REVIEW.

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

@ -35,12 +35,14 @@ namespace eval punk::mix::base {
} }
#puts stderr "punk::mix::base extension: [string trimleft $extension :]" #puts stderr "punk::mix::base extension: [string trimleft $extension :]"
if {![string length $extension]} { if {![string length $extension]} {
#if still no extension - must have been called dirctly as punk::mix::base::_cli #if still no extension - must have been called directly as punk::mix::base::_cli
if {![llength $args]} { if {![llength $args]} {
set args "help" set args "help"
} }
set extension [namespace current] set extension [namespace current]
} }
#init usually used to load commandsets (and export their names) into the extension namespace/ensemble
${extension}::_init
if {![llength $args]} { if {![llength $args]} {
if {[info exists ${extension}::default_command]} { if {[info exists ${extension}::default_command]} {
tailcall $extension [set ${extension}::default_command] tailcall $extension [set ${extension}::default_command]

148
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/cli-0.3.1.tm

@ -31,47 +31,58 @@ namespace eval punk::mix::cli {
namespace eval temp_import { namespace eval temp_import {
} }
namespace ensemble create namespace ensemble create
variable initialised 0
package require punk::overlay #lazy _init - called by punk::mix::base::_cli when ensemble used
catch { proc _init {args} {
punk::overlay::import_commandset module . ::punk::mix::commandset::module variable initialised
} if {$initialised} {
punk::overlay::import_commandset debug . ::punk::mix::commandset::debug return
punk::overlay::import_commandset repo . ::punk::mix::commandset::repo }
punk::overlay::import_commandset lib . ::punk::mix::commandset::loadedlib puts stderr "punk::mix::cli::init $args"
package require punk::overlay
catch { namespace eval ::punk::mix::cli {
package require punk::mix::commandset::project catch {
punk::overlay::import_commandset project . ::punk::mix::commandset::project punk::overlay::import_commandset module . ::punk::mix::commandset::module
punk::overlay::import_commandset projects . ::punk::mix::commandset::project::collection }
} punk::overlay::import_commandset debug . ::punk::mix::commandset::debug
if {[catch { punk::overlay::import_commandset repo . ::punk::mix::commandset::repo
package require punk::mix::commandset::layout punk::overlay::import_commandset lib . ::punk::mix::commandset::loadedlib
punk::overlay::import_commandset project.layout . ::punk::mix::commandset::layout
punk::overlay::import_commandset project.layouts . ::punk::mix::commandset::layout::collection catch {
} errM]} { package require punk::mix::commandset::project
puts stderr "error loading punk::mix::commandset::layout" punk::overlay::import_commandset project . ::punk::mix::commandset::project
puts stderr $errM punk::overlay::import_commandset projects . ::punk::mix::commandset::project::collection
} }
if {[catch { if {[catch {
package require punk::mix::commandset::buildsuite package require punk::mix::commandset::layout
punk::overlay::import_commandset buildsuite . ::punk::mix::commandset::buildsuite punk::overlay::import_commandset project.layout . ::punk::mix::commandset::layout
punk::overlay::import_commandset buildsuites . ::punk::mix::commandset::buildsuite::collection punk::overlay::import_commandset project.layouts . ::punk::mix::commandset::layout::collection
} errM]} { } errM]} {
puts stderr "error loading punk::mix::commandset::buildsuite" puts stderr "error loading punk::mix::commandset::layout"
puts stderr $errM puts stderr $errM
} }
punk::overlay::import_commandset scriptwrap . ::punk::mix::commandset::scriptwrap if {[catch {
if {[catch { package require punk::mix::commandset::buildsuite
package require punk::mix::commandset::doc punk::overlay::import_commandset buildsuite . ::punk::mix::commandset::buildsuite
punk::overlay::import_commandset doc . ::punk::mix::commandset::doc punk::overlay::import_commandset buildsuites . ::punk::mix::commandset::buildsuite::collection
punk::overlay::import_commandset "" "" ::punk::mix::commandset::doc::collection } errM]} {
} errM]} { puts stderr "error loading punk::mix::commandset::buildsuite"
puts stderr "error loading punk::mix::commandset::doc" puts stderr $errM
puts stderr $errM }
punk::overlay::import_commandset scriptwrap . ::punk::mix::commandset::scriptwrap
if {[catch {
package require punk::mix::commandset::doc
punk::overlay::import_commandset doc . ::punk::mix::commandset::doc
punk::overlay::import_commandset "" "" ::punk::mix::commandset::doc::collection
} errM]} {
puts stderr "error loading punk::mix::commandset::doc"
puts stderr $errM
}
}
set initialised 1
} }
proc help {args} { proc help {args} {
#set basehelp [punk::mix::base::help -extension [namespace current] {*}$args] #set basehelp [punk::mix::base::help -extension [namespace current] {*}$args]
set basehelp [punk::mix::base help {*}$args] set basehelp [punk::mix::base help {*}$args]
@ -210,11 +221,12 @@ namespace eval punk::mix::cli {
proc validate_modulename {modulename args} { proc validate_modulename {modulename args} {
set opts [list\ set opts [list\
-errorprefix validate_modulename\ -errorprefix validate_modulename\
-strict 0\
] ]
if {[llength $args] %2 != 0} {error "validate_modulename args must be name-value pairs: received '$args'"} if {[llength $args] %2 != 0} {error "validate_modulename args must be name-value pairs: received '$args'"}
foreach {k v} $args { foreach {k v} $args {
switch -- $k { switch -- $k {
-errorprefix { -errorprefix - -strict {
dict set opts $k $v dict set opts $k $v
} }
default { default {
@ -223,8 +235,14 @@ namespace eval punk::mix::cli {
} }
} }
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- --- ---
set opt_errorprefix [dict get $opts -errorprefix] set opt_errorprefix [dict get $opts -errorprefix]
set opt_strict [dict get $opts -strict]
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- --- ---
if {$opt_strict} {
if {[regexp {[A-Z]} $modulename]} {
error "$opt_errorprefix '$modulename' contains uppercase which is not recommended as per tip 590, and option -strict is set to 1"
}
}
validate_name_not_empty_or_spaced $modulename -errorprefix $opt_errorprefix validate_name_not_empty_or_spaced $modulename -errorprefix $opt_errorprefix
set testname [string map {:: {}} $modulename] set testname [string map {:: {}} $modulename]
@ -239,6 +257,56 @@ namespace eval punk::mix::cli {
} }
return $modulename return $modulename
} }
proc confirm_modulename {modulename} {
set finalised 0
set aborted 0
while {!$finalised && !$aborted} {
#first validate with -strict 0 to confirm acceptable while ignoring case issues.
#uppercase is generally valid but not recommended - so has separate prompting.
if {[catch {validate_modulename $modulename -strict 0} errM]} {
set msg "Chosen name didn't pass validation\n"
append msg "reason: $errM\n"
append msg "Please retype the modulename. You will be given a further prompt to confirm or abort."
set modulename [util::askuser $msg]
} elseif {[regexp {[A-Z]} $modulename]} {
set msg "module names containing uppercase are not recommended (see tip 590).\n"
append msg "Please retype the module name '$modulename' to proceed.\n"
append msg "If you type it exactly as it was you will be allowed to proceed with uppercase anyway\n"
append msg "Retype it all in lowercase to use recommended naming"
set answer [util::askuser $msg]
if {[regexp {[A-Z]} $answer]} {
if {$answer eq $modulename} {
#ok - user insists
set finalised 1
} else {
#user supplied a different uppercase name - don't set finalised so we bug them again to type it two times the same way to proceed
puts stdout "A different uppercase name was supplied - reconfirmation required."
}
set modulename $answer
} else {
#user has resupplied modulename all as lowercase
if {$answer eq [string tolower $modulename]} {
set finalised 1
} else {
#.. but it doesn't match original - require rerun
}
set modulename $answer
}
} else {
set answer [util::askuser "Proceed with the module name '$modulename'? Y to continue N to abort"]
if {[string tolower $answer] eq "y"} {
set finalised 1
} else {
set aborted 1
}
}
}
if {$aborted} {
return [dict create status error reason errmsg]
} else {
return [dict create status ok modulename $modulename]
}
}
proc validate_projectname {projectname args} { proc validate_projectname {projectname args} {
set defaults [list\ set defaults [list\

30
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/doc-0.1.0.tm

@ -165,7 +165,17 @@ namespace eval punk::mix::commandset::doc {
cd $original_wd cd $original_wd
} }
proc validate {} { proc validate {args} {
set argd [punk::args::get_dict {
-- -type none -optional 1 -help "end of options marker --"
-individual -type boolean -default 1
*values -min 0 -max -1
patterns -default {*} -type any -multiple 1
} $args]
set opt_individual [tcl::dict::get $argd opts -individual]
set patterns [tcl::dict::get $argd values patterns]
#todo - run and validate punk::docgen output #todo - run and validate punk::docgen output
set projectdir [punk::repo::find_project] set projectdir [punk::repo::find_project]
if {$projectdir eq ""} { if {$projectdir eq ""} {
@ -180,7 +190,23 @@ namespace eval punk::mix::commandset::doc {
set docroot $projectdir/src/doc set docroot $projectdir/src/doc
cd $docroot cd $docroot
dtplite validate $docroot if {!$opt_individual && "*" in $patterns} {
if {[catch {
dtplite validate $docroot
} errM]} {
puts stderr "commandset::doc::validate failed for projectdir '$projectdir'"
puts stderr "docroot '$docroot'"
puts stderr "dtplite error was: $errM"
}
} else {
foreach p $patterns {
set treefiles [punk::path::treefilenames $p]
foreach path $treefiles {
puts stdout "dtplite validate $path"
dtplite validate $path
}
}
}
#punk::mix::cli::lib::kettle_call lib validate-doc #punk::mix::cli::lib::kettle_call lib validate-doc

11
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/loadedlib-0.1.0.tm

@ -179,7 +179,16 @@ namespace eval punk::mix::commandset::loadedlib {
return [join $loaded_libs \n] return [join $loaded_libs \n]
} }
proc info {libname} { proc info {args} {
set argspecs {
*values -min 1
libname -help "library/package name"
}
set argd [punk::args::get_dict $argspecs $args]
set libname [dict get $argd values libname]
if {[catch {package require natsort}]} { if {[catch {package require natsort}]} {
set has_natsort 0 set has_natsort 0
} else { } else {

24
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm

@ -204,6 +204,30 @@ namespace eval punk::mix::commandset::module {
set modulename $module set modulename $module
} }
punk::mix::cli::lib::validate_modulename $modulename -errorprefix "punk::mix::commandset::module::new" punk::mix::cli::lib::validate_modulename $modulename -errorprefix "punk::mix::commandset::module::new"
if {[regexp {[A-Z]} $module]} {
set msg "module names containing uppercase are not recommended (see tip 590).\n"
append msg "Please retype the module name '$module' to proceed.\n"
append msg "If you type it exactly as it was you will be allowed to proceed with uppercase anyway\n"
append msg "Retype it all in lowercase to use recommended naming"
set answer [util::askuser $msg]
if {[regexp {[A-Z]} $answer]} {
if {$answer eq $module} {
#ok - user insists
} else {
}
} else {
#user has resupplied modulename all as lowercase
if {$answer eq [string tolower $module]} {
set module $answer
} else {
#.. but it doesn't match original - require rerun
}
}
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
#options #options
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---

10
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/project-0.1.0.tm

@ -165,7 +165,7 @@ namespace eval punk::mix::commandset::project {
#user can use dev module.new manually or supply module name in -modules #user can use dev module.new manually or supply module name in -modules
set opt_modules [list] set opt_modules [list]
} else { } else {
set opt_modules [list $projectname] set opt_modules [list [string tolower $projectname]] ;#default modules to lowercase as is the modern (tip 590) recommendation for Tcl
} }
} }
# -- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- ---
@ -919,10 +919,18 @@ namespace eval punk::mix::commandset::project {
if {[llength $col_states]} { if {[llength $col_states]} {
foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes s $col_states { foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes s $col_states {
if {![file exists $wd]} {
set row [punk::ansi::a+ strike red]$row[a]
set wd [punk::ansi::a+ red]$wd[a]
}
append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode] [overtype::left $col6 $s]" \n append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode] [overtype::left $col6 $s]" \n
} }
} else { } else {
foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes { foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes {
if {![file exists $wd]} {
set row [punk::ansi::a+ strike red]$row[a]
set wd [punk::ansi::a+ red]$wd[a]
}
append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode]" \n append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode]" \n
} }
} }

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

@ -130,6 +130,7 @@ tcl::namespace::eval ::punk::overlay {
}] }]
set imported_commands [list] set imported_commands [list]
set imported_tails [list]
set nscaller [uplevel 1 [list tcl::namespace::current]] set nscaller [uplevel 1 [list tcl::namespace::current]]
if {[catch { if {[catch {
#review - noclobber? #review - noclobber?
@ -143,7 +144,10 @@ tcl::namespace::eval ::punk::overlay {
} }
rename $cmd $import_as rename $cmd $import_as
lappend imported_commands $import_as lappend imported_commands $import_as
lappend imported_tails [namespace tail $import_as]
} }
#make imported commands exported so they are available to the ensemble
tcl::namespace::eval ${nscaller} [list namespace export {*}$imported_tails]
} errM]} { } errM]} {
puts stderr "Error loading commandset $prefix $separator $cmdnamespace" puts stderr "Error loading commandset $prefix $separator $cmdnamespace"
puts stderr "err: $errM" puts stderr "err: $errM"

450
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/path-0.1.0.tm

@ -63,11 +63,11 @@ package require Tcl 8.6-
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# oo::class namespace # oo::class namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval punk::path::class { #namespace eval punk::path::class {
#*** !doctools #*** !doctools
#[subsection {Namespace punk::path::class}] #[subsection {Namespace punk::path::class}]
#[para] class definitions #[para] class definitions
if {[info commands [namespace current]::interface_sample1] eq ""} { #if {[info commands [namespace current]::interface_sample1] eq ""} {
#*** !doctools #*** !doctools
#[list_begin enumerated] #[list_begin enumerated]
@ -89,8 +89,8 @@ namespace eval punk::path::class {
#*** !doctools #*** !doctools
#[list_end] [comment {--- end class enumeration ---}] #[list_end] [comment {--- end class enumeration ---}]
} #}
} #}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
@ -105,6 +105,448 @@ namespace eval punk::path {
#[para] Core API functions for punk::path #[para] Core API functions for punk::path
#[list_begin definitions] #[list_begin definitions]
# -- ---
#punk::path::normjoin
# - simplify . and .. segments as far as possible whilst respecting specific types of root.
# -- ---
#a form of file normalize that supports //xxx to be treated as server path names
#(ie regardless of unices ignoring (generally) leading double slashes, and regardless of windows volumerelative path syntax)
#(sometimes //server.com used as a short form for urls - which doesn't seem too incompatible with this anyway)
# -- ---
#This is intended to be purely a string analysis - without reference to filesystem volumes or vfs or zipfs mountpoints etc
#
#TODO - option for caller to provide a -base below which we can't backtrack.
#This is preferable to setting policy here for example regarding forcing no trackback below //servername/share
#Our default is to allow trackback to:
# <scheme>://<something>
# <driveletter>:/
# //./<volume> (dos device volume)
# //server (while normalizing //./UNC/server to same)
# / (ordinary unix root)
# ./../<repeated> - (track back indefinitely on relpath as we are not resolving to anything physical and can't fully simplify the leading backtracks)
#
#The caller should do the file/vfs operations to determine this - not us.
# -- ---
#simplify path with respect to /./ & /../ elements - independent of platform
#NOTE: "anomalies" in standard tcl processing on windows:
#e.g file normalize {//host} -> c:/host (or e.g d:/host if we happen to be on another volume)
#file normalize {//host/share} -> //host/share
#This is because //host is treated as volume-relative in cmd/powershell and Tcl quite reasonably follows suit.
#This prevents cwd and windows commandlines from pointing to the server (above the share)
#Explorer however does allow pointing to the //server level and seeing shares as if they are directory entries.
#we are more interested in supporting the explorer-like behaviour - as while volumerelative paths are also useful on windows - they are lesser known.
#REVIEW.
#To get back to some consistent cross platform behaviour - we will treat //something as a root/volume i.e we can't backtrack above it with ".."
#note too that file split on UNC paths doesn't give a clear indication of the root
# file split //./UNC/server/share/subpath -> //./UNC server share subpath
# file split //server/share/subpath -> //server/share subpath
#TODO - disallow all change of root or change from relative path to absolute result.
#e.g normjoin relpath/../d:/secret should not return d:/secret - but ./d:/secret
# ================
#known issues:
#1)
# normjoin d://a//b//c -> d://a/b/c
# This is because we don't detect specific schemes. ie it's treated the same as https://a/b/c -> https://a/b/c
# Not considered a problem - just potentially surprising.
# To avoid it we would have to enumerate possible schemes.
# As it stands a unix system could define a 'scheme' that happens to match windows style driveletters. Consider a 'feature' ? review.
# won't fix?
#2)
# normjoin https:///real.com/../fake.com -> https:///fake.com
# The extra slash means effectively our servername is empty - this is potentially confusing but probably the right thing to do here.
# It's a concern only if upstream treats the tripple slash in this case as valid and maps it to https:// - which would probably be bad anyway.
# won't fix (review)
#3)
#similarly
# normjoin //./UNC//server/share/subpath -> ///server/share/subpath (when 2 or more slashes directly after UNC)
# normjoin ///server/share -> ///server/share
#This is effectively an empty servername in the input with 'server' being pushed one level down - and the output is consistent
# possibly won't fix - review
#4) inconsistency
# we return normalized //server/share for //./UNC/server share
# but other dos device paths are maintained
# e.g //./c:/etc
# This is because such paths could contain alternate segment names (windows shortnames) which we aren't in a position to resolve.
# caller should
# #as with 'case' below - caller will need to run a post 'file normalize'
#5) we don't normalize case like file normalize does on windows platform.
# This is intentional. It could only be done with reference to underlying filesystem which we don't want here.
#
# ================
#
#relpaths all end up with leading . - while not always the simplest form, this is ok. (helps stop inadvertent conversions to absolutes)
# Tests - TODO
# normjoin /d:/..//vfs:/test -> /vfs:/test (good - not converted to //vfs:/test)
proc normjoin {args} {
set args [lmap a $args {string map "\\\\ /" $a}]
set path [plainjoin {*}$args]
switch -exact $path {
"" {
return ""
}
/ - // {
#treated in unixlike manner - (but leading doubleslashes with subsequent data are server indication)
#// not considered a servername indicator - but /// (for consistency) is. (empty servername?)
return /
}
/// {
#if this is effectively //$emptyservername/
#then for consistency we should trail //<servername with a slash too?
#we can't transform to // or /
return ///
#assert - code below should return /// (empty server prefix) for any number of leading slashes >=3
#todo - shortcircuit that here?
}
}
# ///
set doubleslash1_posn [string first // $path]
# -- --- --- temp warning on windows only - no x-platform difference in result
#on windows //host is of type volumerelative
# whereas //host/share is of type absolute
if {"windows" eq $::tcl_platform(platform) && [file pathtype $path] eq "volumerelative"} {
#volumerelative probably only occurs on windows anyway
if {$doubleslash1_posn == 0} {
#e.g //something where no further slashes
#review - eventually get rid of this warning and require upstream to know the appropriate usecase
puts stderr "Warning - ambiguous path $path - treating as server path - not 'volumerelative'"
} else {
# /something/etc
# /mnt/c/stuff
#output will retain leading / as if on unix.
#on windows - the result would still be interpreted as volumerelative if the caller normalizes it
}
}
# -- --- ---
set is_relpath 0
#set path [string map [list \\ /] $path]
set finalparts [list]
set is_nonunc_dosdevice 0
if {[punk::winpath::is_dos_device_path $path]} {
#review
if {[string range $path 4 6] eq "UNC"} {
#convert to 'standard' //server/... path for processing
set path "/[string range $path 7 end]" ;# //server/...
} else {
#error "normjoin non-UNC dos device path '$path' not supported"
#first segment after //./ or //?/ represents the volume or drive.
#not applicable to unix - but unlikely to conflict with a genuine usecase there (review)
#we should pass through and stop navigation below //./vol
#!!!
#not anomaly in tcl (continues in tcl9)
#file exists //./c:/test -> 0
#file exists //?/c:/test -> 1
#file exists //./BootPartition/Windows -> 1
#file exists //?/BootPartition/Windows -> 0
set is_nonunc_dosdevice 1
}
}
if {$is_nonunc_dosdevice} {
#dosdevice prefix //./ or //?/ - preserve it (without trailing slash which will be put back in with join)
set prefix [string range $path 0 2]
set tail [string range $path 4 end]
set tailparts [split $tail /]
set parts [concat [list $prefix] $tailparts]
set rootindex 1 ;#disallow backtrack below //./<volume>
} else {
#note use of ordinary ::split vs file split is deliberate.
if {$doubleslash1_posn == 0} {
#this is handled differently on different platforms as far as 'file split' is concerned.
#e.g for file split //sharehost/share/path/etc
#e.g on windows: -> //sharehost/share path
#e.g on freebsd: -> / sharehost share path etc
#however..also on windows: file split //sharehost -> / sharehost
#normalize by dropping leading slash before split - and then treating first 2 segments as a root
#set parts [file split [string range $path 1 end]]
set parts [split $path /]
#assert parts here has {} {} as first 2 entries
set rootindex 2
#currently prefer can backtrack to the //zipfs:/ scheme (below the mountpoint - to browse other mounts)
#alternative handling for //zipfs:/path - don't go below mountpoint
#but we can't determine just from string if mountpoint is direct subpath or a lower one e.g //zipfs:/arbitraryname/actualmountpoint
#review - more generally //<mountmechanism>:/path ?
#todo - make an option for zipfs and others to determine the 'base'
#if {"zipfs:" eq [lindex $parts 2]} {
# set rootindex 3
#}
} else {
#path may or may not begin with a single slash here.
#treat same on unix and windows
set rootindex 0
#set parts [file split $path]
set parts [::split $path /]
#e.g /a/b/c -> {} a b c
#or relative path a/b/c -> a b c
#or c:/a/b/c -> c: a b c
if {[string match *: [lindex $parts 0]]} {
if {[lindex $parts 1] eq ""} {
#scheme://x splits to scheme: {} x
set parts [concat [list [lindex $parts 0]/] [lrange $parts 2 end]]
#e.g {scheme:/ x}
set rootindex 1 ;#disallow below first element of scheme
} else {
set rootindex 0
}
} elseif {[lindex $parts 0] ne ""} {
#relpath a/b/c
set parts [linsert $parts 0 .]
set rootindex 0
#allow backtracking arbitrarily for leading .. entries - simplify where possible
#also need to stop possible conversion to absolute path
set is_relpath 1
}
}
}
set baseparts [lrange $parts 0 $rootindex] ;#base below which we can't retreat via ".."
#puts stderr "-->baseparts:$baseparts"
#ensure that if our rootindex already spans a dotted segment (after the first one) we remove it
#must maintain initial . for relpaths to stop them converting to absolute via backtrack
#
set finalparts [list [lindex $baseparts 0]]
foreach b [lrange $baseparts 1 end] {
if {$b ni {. ..}} {
lappend finalparts $b
}
}
set baselen [expr {$rootindex + 1}]
if {$is_relpath} {
set i [expr {$rootindex+1}]
foreach p [lrange $parts $i end] {
switch -exact -- $p {
. - "" {}
.. {
switch -exact -- [lindex $finalparts end] {
. - .. {
lappend finalparts ..
}
default {
lpop finalparts
}
}
}
default {
lappend finalparts $p
}
}
incr i
}
} else {
foreach p [lrange $parts $rootindex+1 end] {
if {[llength $finalparts] <= $baselen} {
if {$p ni {. .. ""}} {
lappend finalparts $p
}
} else {
switch -exact -- $p {
. - "" {}
.. {
lpop finalparts ;#uses punk::lib::compat::lpop if on < 8.7
}
default {
lappend finalparts $p
}
}
}
}
}
puts "==>finalparts: '$finalparts'"
# using join - {"" "" server share} -> //server/share and {a b} -> a/b
if {[llength $finalparts] == 1 && [lindex $finalparts 0] eq ""} {
#backtracking on unix-style path can end up with empty string as only member of finalparts
#e.g /x/..
return /
}
set result [::join $finalparts /]
#normalize volumes and mountschemes to have trailing slash if no subpath
#e.g c: -> c:/
#//zipfs: -> //zipfs:/
if {[set lastchar [string index $result end]] eq ":"} {
if {$result eq "//zipfs:"} {
set result "//zipfs:/"
} else {
if {[string first / $result] < 0} {
set result $result/
}
}
} elseif {[string match //* $result]} {
if {![punk::winpath::is_dos_device_path $result]} {
#server
set tail [string range $result 2 end]
set tailparts [split $tail /]
if {[llength $tailparts] <=1} {
#empty // or //servername
append result /
}
}
} elseif {[llength $finalparts] == 2} {
if {[string range [lindex $finalparts 0] end-1 end] eq ":/"} {
#e.g https://server/ -> finalparts {https:/ server}
#e.g https:/// -> finalparts {https:/ ""}
#scheme based path should always return trailing slash after server component - even if server component empty.
lappend finalparts "" ;#force trailing /
return [join $finalparts /]
}
}
return $result
}
proc trim_final_slash {str} {
if {[string index $str end] eq "/"} {
return [string range $str 0 end-1]
}
return $str
}
#x-platform - punk::path::pathtype - can be used in safe interps - different concept of pathtypes to 'file pathtype'
# - no volumerelative
# - no lookup of file volumes (volume is a windows concept - but with //zipfs:/ somewhat applicable to other platforms)
# - /* as absolute (covers also //zipfs:/ (volume), //server , //./etc , //./UNC)
# - xxx:// as absolute (scheme)
# - xxx:/ or x:/ as absolute
# - x: xxx: -> as absolute (volume-basic or volume-extended)
#note also on windows - legacy name for COM devices
# COM1 = COM1:
# //./COM1 ?? review
proc pathtype {str} {
set str [string map "\\\\ /" $str]
if {[string index $str 0] eq "/"} {
#todo - look for //xxx:/ prefix (generalisation of //zipfs:/) as a 'volume' specifically {volume mount} ?? - review
# look for //server prefix as {absolute server}
# look for //./UNC/server or //?/UNC/server as {absolute server UNC} ?
# look for //./<dosdevice> as {absolute dosdevice}
return absolute
}
#only firstsegment with single colon at last position (after some non empty string) counts as volume or scheme - review
#e.g a:b:/.. or a::/.. or :/.. is not treated as volume/scheme whereas ab:/ is.
set firstslash [string first / $str]
if {$firstslash == -1} {
set firstsegment $str
} else {
set firstsegment [string range $str 0 $firstslash-1]
}
if {[set firstc [string first : $firstsegment]] > 0} {
set lhs_firstsegment [string range $firstsegment 0 $firstc-1]
set rhs_firstsegment [string range $firstsegment $firstc+1 end] ;#exclude a:b/ etc
if {$rhs_firstsegment eq ""} {
set rhs_entire_path [string range $str $firstc+1 end]
#assert lhs_firstsegment not empty since firstc > 0
#count following / sequence
set i 0
set slashes_after_firstsegment "" ;#run of slashes *directly* following first segment
while {$i < [string length $rhs_entire_path]} {
if {[string index $rhs_entire_path $i] eq "/"} {
append slashes_after_firstsegment /
} else {
break
}
incr i
}
switch -exact -- $slashes_after_firstsegment {
"" - / {
if {[string length $lhs_firstsegment] == 1} {
return {absolute volume basic}
} else {
return {absolute volume extended}
}
}
default {
#2 or more /
#this will return 'scheme' even for c:// - even though that may look like a windows volume - review
return {absolute scheme}
}
}
}
}
#assert first element of any return has been absolute or relative
return relative
}
proc plain {str} {
set str [string map "\\\\ /" $str]
set pathinfo [punk::path::pathtype $str]
if {[lindex $pathinfo 0] eq "relative" && ![string match ./* $str]} {
set str ./$str
}
if {[string index $str end] eq "/"} {
if {[string map {/ ""} $str] eq ""} {
#all slash segment
return $str
} else {
if {[lindex $pathinfo 1] ni {volume scheme}} {
return [string range $str 0 end-1]
}
}
}
return $str
}
#purely string based - no reference to filesystem knowledge
#unix-style forward slash only
proc plainjoin {args} {
set args [lmap a $args {string map "\\\\ /" $a}]
#if {[llength $args] == 1} {
# return [lindex $args 0]
#}
set out ""
foreach a $args {
if {![string length $out]} {
append out [plain $a]
} else {
set a [plain $a]
if {[string map {/ ""} $out] eq ""} {
set out [string range $out 0 end-1]
}
if {[string map {/ ""} $a] eq ""} {
#all / segment
append out [string range $a 0 end-1]
} else {
if {[string length $a] > 2 && [string match "./*" $a]} {
set a [string range $a 2 end]
}
if {[string index $out end] eq "/"} {
append out $a
} else {
append out / $a
}
}
}
}
return $out
}
proc plainjoin1 {args} {
if {[llength $args] == 1} {
return [lindex $args 0]
}
set out [trim_final_slash [lindex $args 0]]
foreach a [lrange $args 1 end] {
set a [trim_final_slash $a]
append out / $a
}
return $out
}
#intention?
#proc filepath_dotted_dirname {path} {
#}
proc strip_prefixdepth {path prefix} {
if {$prefix eq ""} {
return [norm $path]
}
return [file join \
{*}[lrange \
[file split [norm $path]] \
[llength [file split [norm $prefix]]] \
end]]
}
proc pathglob_as_re {pathglob} { proc pathglob_as_re {pathglob} {
#*** !doctools #*** !doctools

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

@ -134,13 +134,30 @@ namespace eval punk::repo {
} }
interp alias "" fossil "" punk::repo::fossil_proxy interp alias "" fossil "" punk::repo::fossil_proxy
# ---
# Calling auto_execok on an external tool can be too slow to do during package load (e.g could be 150ms)
#safe interps can't call auto_execok #safe interps can't call auto_execok
#At least let them load the package even though much of it may be unusable depending on the safe configuration #At least let them load the package even though much of it may be unusable depending on the safe configuration
catch { #catch {
if {[auto_execok fossil] ne ""} { # if {[auto_execok fossil] ne ""} {
interp alias "" FOSSIL "" {*}[auto_execok fossil] # interp alias "" FOSSIL "" {*}[auto_execok fossil]
} # }
#}
# ---
# ----------
#
#uppercase FOSSIL to bypass fossil as alias to fossil_proxy
proc establish_FOSSIL {args} {
if {![info exists ::auto_execs(FOSSIL)]} {
set ::auto_execs(FOSSIL) [auto_execok fossil] ;#may fail in safe interp
}
interp alias "" FOSSIL "" ;#delete establishment alias
FOSSIL {*}$args
} }
interp alias "" FOSSIL "" punk::repo::establish_FOSSIL
# ----------
proc askuser {question} { proc askuser {question} {
if {![catch {package require punk::lib}]} { if {![catch {package require punk::lib}]} {
@ -370,7 +387,16 @@ namespace eval punk::repo {
} }
if {$repodir eq ""} { if {$repodir eq ""} {
error "workingdir_state error: No repository found at or above path '$abspath'" puts stderr "workingdir_state error: No repository found at or above path '$abspath'"
puts stderr "args: $args"
dict set resultdict revision {}
dict set resultdict revision_iso8601 {}
dict set resultdict paths {}
dict set resultdict ahead ""
dict set resultdict behind ""
dict set resultdict error {reason "no_repo_found"}
dict set resultdict repotype none
return $resultdict
} }
set subpath [punk::path::relative $repodir $abspath] set subpath [punk::path::relative $repodir $abspath]
if {$subpath eq "."} { if {$subpath eq "."} {
@ -644,6 +670,16 @@ namespace eval punk::repo {
set path_count_fields [list unchanged changed new missing extra] set path_count_fields [list unchanged changed new missing extra]
set state_fields [list ahead behind repodir subpath repotype revision revision_iso8601] set state_fields [list ahead behind repodir subpath repotype revision revision_iso8601]
set dresult [dict create] set dresult [dict create]
if {[dict exists $repostate error]} {
foreach f $state_fields {
dict set dresult $f ""
}
foreach f $path_count_fields {
dict set dresult $f ""
}
#todo?
return $dresult
}
foreach f $state_fields { foreach f $state_fields {
dict set dresult $f [dict get $repostate $f] dict set dresult $f [dict get $repostate $f]
} }

155
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/winpath-0.1.0.tm

@ -30,7 +30,7 @@ namespace eval punk::winpath {
#\\servername\share etc or \\?\UNC\servername\share etc. #\\servername\share etc or \\?\UNC\servername\share etc.
proc is_unc_path {path} { proc is_unc_path {path} {
set strcopy_path [punk::objclone $path] set strcopy_path [punk::winpath::system::objclone $path]
set strcopy_path [string map {\\ /} $strcopy_path] ;#normalize to forward slashes for testing purposes (and forward slashes seem to be auto-translated by windows anyway) set strcopy_path [string map {\\ /} $strcopy_path] ;#normalize to forward slashes for testing purposes (and forward slashes seem to be auto-translated by windows anyway)
if {[string first "//" $strcopy_path] == 0} { if {[string first "//" $strcopy_path] == 0} {
#check for "Dos device path" syntax #check for "Dos device path" syntax
@ -77,7 +77,7 @@ namespace eval punk::winpath {
#dos device path syntax allows windows api to acces extended-length paths and filenames with illegal path characters such as trailing dots or whitespace #dos device path syntax allows windows api to acces extended-length paths and filenames with illegal path characters such as trailing dots or whitespace
#(can exist on server shares and on NTFS - but standard apps can't access without dos device syntax) #(can exist on server shares and on NTFS - but standard apps can't access without dos device syntax)
proc is_dos_device_path {path} { proc is_dos_device_path {path} {
set strcopy_path [punk::objclone $path] set strcopy_path [punk::winpath::system::objclone $path]
set strcopy_path [string map {\\ /} $strcopy_path] ;#normalize to forward slashes for testing purposes (and forward slashes seem to be auto-translated by windows anyway) set strcopy_path [string map {\\ /} $strcopy_path] ;#normalize to forward slashes for testing purposes (and forward slashes seem to be auto-translated by windows anyway)
if {[string range $strcopy_path 0 3] in {//?/ //./}} { if {[string range $strcopy_path 0 3] in {//?/ //./}} {
return 1 return 1
@ -87,7 +87,7 @@ namespace eval punk::winpath {
} }
proc strip_dos_device_prefix {path} { proc strip_dos_device_prefix {path} {
#it's unlikely to be valid to strip only //?/ from a //?/UNC path so check for it here and diver to strip that. #it's unlikely to be valid to strip only //?/ from a //?/UNC path so check for it here and diver to strip that.
#(review.. or raise error because a //?/UNC path isn't *strictly* a UNC path? ) #(review.. or raise error because a //?/UNC path isn't an ordinary dos device path? )
if {[is_unc_path $path]} { if {[is_unc_path $path]} {
return [strip_unc_path_prefix $path] return [strip_unc_path_prefix $path]
} }
@ -98,18 +98,18 @@ namespace eval punk::winpath {
} }
} }
proc strip_unc_path_prefix {path} { proc strip_unc_path_prefix {path} {
if {[is_unc_path $path]} { if {[is_unc_path_plain $path]} {
#//?/UNC/server/etc
set strcopy_path [punk::objclone $path]
set trimmedpath [string range $strcopy_path 7 end]
file pathtype $trimmedpath ;#shimmer it to path rep
return $trimmedpath
} elseif {is_unc_path_plain $path} {
#plain unc //server #plain unc //server
set strcopy_path [punk::objclone $path] set strcopy_path [punk::winpath::system::objclone $path]
set trimmedpath [string range $strcopy_path 2 end] set trimmedpath [string range $strcopy_path 2 end]
file pathtype $trimmedpath file pathtype $trimmedpath
return $trimmedpath return $trimmedpath
} elseif {is_unc_path $path} {
#//?/UNC/server/subpath or //./UNC/server/subpath
set strcopy_path [punk::winpath::system::objclone $path]
set trimmedpath [string range $strcopy_path 7 end]
file pathtype $trimmedpath ;#shimmer it to path rep
return $trimmedpath
} else { } else {
return $path return $path
} }
@ -153,7 +153,7 @@ namespace eval punk::winpath {
error $err error $err
} }
set strcopy_path [punk::objclone $path] set strcopy_path [punk::winpath::system::objclone $path]
#Note: path could still have leading double slash if it is a Dos device path: e.g. //?/c:/etc #Note: path could still have leading double slash if it is a Dos device path: e.g. //?/c:/etc
@ -225,27 +225,124 @@ namespace eval punk::winpath {
return 0 return 0
} }
proc test_ntfs_tunneling {f1 f2 args} { proc shortname {path} {
file mkdir $f1 set shortname "NA"
puts stderr "waiting 15secs..." if {[catch {
after 5000 {puts -nonewline stderr .} set shortname [dict get [file attributes $path] -shortname]
after 5000 {puts -nonewline stderr .} } errM]} {
after 5000 {puts -nonewline stderr .} puts stderr "Failed to get shortname for '$path'"
after 500 {puts stderr \n} }
file mkdir $f2 return $shortname
puts stdout "$f1 [file stat $f1]" }
puts stdout "$f2 [file stat $f2]" proc test_ntfs_tunneling {prefix args} {
file delete $f1 puts stderr "We are looking for whether any of the final $prefix files or dirs took over the ctime attribute of the original $prefix files or dirs"
puts stdout "renaming $f2 to $f1" puts stderr "We expect the ino values to get potentially reassigned depending on order of deletion/creation so matches are coincidental and not material"
file rename $f2 $f1 puts stderr "The shortnames are similarly allocated as they come - so presumably match by coincidence"
puts stdout "$f1 [file stat $f1]" puts stderr "However - if we record a file's shortname, then delete it. Recreating it by shortname within the tunneling timeframe will magically reassociate the longname"
puts stderr "use test_ntfs_tunneling2 to test shortname tunneling"
file mkdir $prefix-dir-rename
file mkdir $prefix-dir-recreate
set fd [open $prefix-file-recreate.txt w]
puts $fd "original for recreate"
close $fd
set fd [open $prefix-file-rename.txt w]
puts $fd "original for rename"
close $fd
puts stdout "ORIGINAL files/dirs"
puts stdout "$prefix-dir-rename [file stat $prefix-dir-rename] "
puts stdout "$prefix-dir-recreate [file stat $prefix-dir-recreate]"
puts stdout "$prefix-file-recreate.txt [file stat $prefix-file-recreate.txt] short:[shortname $prefix-file-recreate.txt]"
puts stdout "$prefix-file-rename.txt [file stat $prefix-file-rename.txt] short:[shortname $prefix-file-rename.txt]"
puts stderr "waiting 10secs (to have discernable ctime differences)"
after 5000
puts -nonewline stderr .
after 5000
puts -nonewline stderr .
after 500
#--
#seems to make no diff whether created or copied - no tunneling seen with dirs
#file mkdir $prefix-dir-rename-temp
file copy $prefix-dir-rename $prefix-dir-rename-temp
#--
puts stderr \n
puts stdout "$prefix-dir-rename-temp [file stat $prefix-dir-rename-temp] (temp to rename into place)"
puts stderr "deleting $prefix-dir-rename"
file delete $prefix-dir-rename
puts stdout "renaming $prefix-dir-rename-temp to $prefix-dir-rename"
file rename $prefix-dir-rename-temp $prefix-dir-rename
puts stderr "deleting $prefix-dir-recreate"
file delete $prefix-dir-recreate
puts stdout "re-creating $prefix-dir-recreate"
file mkdir $prefix-dir-recreate
puts stderr "deleting $prefix-file-recreate.txt"
file delete $prefix-file-recreate.txt
puts stderr "Recreating $prefix-file-recreate.txt"
set fd [open $prefix-file-recreate.txt w]
puts $fd "replacement"
close $fd
puts stderr "copying $prefix-file-rename.txt to $prefix-file-rename-temp.txt"
file copy $prefix-file-rename.txt $prefix-file-rename-temp.txt
puts stdout "$prefix-file-rename-temp.txt [file stat $prefix-file-rename-temp.txt] short:[shortname $prefix-file-rename-temp.txt] (status of initial temp copy)"
puts stderr "modifying temp copy before deletion of original.. (append)"
set fd [open $prefix-file-rename-temp.txt a]
puts $fd "added to file"
close $fd
puts stdout "$prefix-file-rename-temp.txt [file stat $prefix-file-rename-temp.txt] short:[shortname $prefix-file-rename-temp.txt] (status of appended temp copy)"
puts stderr "deleting $prefix-file-rename.txt"
file delete $prefix-file-rename.txt
puts stderr "renaming temp file $prefix-file-rename-temp.txt to original $prefix-file-rename.txt"
file rename $prefix-file-rename-temp.txt $prefix-file-rename.txt
puts stdout "Final files/dirs"
puts stdout "$prefix-dir-rename [file stat $prefix-dir-rename]"
puts stdout "$prefix-dir-recreate [file stat $prefix-dir-recreate]"
puts stdout "$prefix-file-recreate.txt [file stat $prefix-file-recreate.txt] short:[shortname $prefix-file-recreate.txt]"
puts stdout "$prefix-file-rename.txt [file stat $prefix-file-rename.txt] short:[shortname $prefix-file-rename.txt]"
}
proc test_ntfs_tunneling2 {prefix {waitms 15000}} {
#shortname -> longname tunneling
puts stderr "Tunneling only happens if we delete via shortname? review"
set f1 $prefix-longname-file1.txt
set f2 $prefix-longname-file2.txt
set fd [open $f1 w];close $fd
set shortname1 [shortname $f1]
puts stderr "longname:$f1 has shortname:$shortname1"
set fd [open $f2 w];close $fd
set shortname2 [shortname $f2]
puts stderr "longname:$f2 has shortname:$shortname2"
puts stderr "deleting $f1 via name $shortname1"
file delete $shortname1
puts stdout "immediately recreating $shortname1 - should retain longname $f1 via tunneling"
set fd [open $shortname1 w];close $fd
set f1_exists [file exists $f1]
puts stdout "file exists $f1 = $f1_exists"
puts stderr "deleting $f2 via name $shortname2"
file delete $shortname2
puts stderr "Waiting [expr {$waitms / 1000}] seconds.. (standard tunneling timeframe is 15 seconds if registry hasn't been tweaked)"
after $waitms
puts stdout "recreating $shortname2 after wait of $waitms ms - longname lost?"
set fd [open $shortname2 w];close $fd
set f2_exists [file exists $f2]
puts stdout "file exists $f2 = $f2_exists"
puts stdout -done-
} }
} }
namespace eval punk::winpath::system {
#get a copy of the item without affecting internal rep
proc objclone {obj} {
append obj2 $obj {}
}
}

209
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/textblock-0.1.1.tm

@ -12,25 +12,97 @@
# Meta license <unspecified> # Meta license <unspecified>
# @@ Meta End # @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[manpage_begin punkshell_module_textblock 0 0.1.1]
#[copyright "2024"]
#[titledesc {punk textblock functions}] [comment {-- Name section and table of contents description --}]
#[moddesc {punk textblock}] [comment {-- Description at end of page heading --}]
#[require textblock]
#[keywords module utility lib]
#[description]
#[para] Ansi-aware terminal textblock manipulation
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Overview]
#[para] overview of textblock
#[subsection Concepts]
#[para]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements ## Requirements
##e.g package require frobz # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection dependencies]
#[para] packages used by textblock
#[list_begin itemized]
#*** !doctools
#[item] [package {Tcl 8.6-}]
#[item] [package {punk::args}]
#[item] [package {punk::char}]
#[item] [package {punk::ansi}]
#[item] [package {punk::lib}]
#[item] [package {overtype}]
#[item] [package {term::ansi::code::macros}]
#[item] [package {textutil}]
## Requirements
package require Tcl 8.6-
package require punk::args package require punk::args
package require punk::char package require punk::char
package require punk::ansi package require punk::ansi
package require punk::lib package require punk::lib
catch {package require patternpunk} catch {package require patternpunk}
package require overtype package require overtype
#safebase interps as at 2024-08 can't access deeper paths - even though they are below the supposed safe list.
package require term::ansi::code::macros ;#required for frame if old ansi g0 used - review - make package optional? package require term::ansi::code::macros ;#required for frame if old ansi g0 used - review - make package optional?
package require textutil package require textutil
#*** !doctools
#[list_end]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section API]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval textblock { tcl::namespace::eval textblock {
#review - what about ansi off in punk::console? #review - what about ansi off in punk::console?
tcl::namespace::import ::punk::ansi::a ::punk::ansi::a+ tcl::namespace::import ::punk::ansi::a ::punk::ansi::a+
tcl::namespace::export block frame frame_cache framedef frametypes gcross height width widthtopline join join_basic list_as_table pad testblock tcl::namespace::export block frame frame_cache framedef frametypes gcross height width widthtopline join join_basic list_as_table pad testblock
variable use_md5 ;#framecache
set use_md5 1
if {[catch {package require md5}]} {
set use_md5 0
}
proc use_md5 {{yes_no ""}} {
variable use_md5
if {$yes_no eq ""} {
return $use_md5
}
if {![string is boolean -strict $yes_no]} {
error "textblock::use_md5 requires a boolean (or empty string to query)"
}
if {$yes_no} {
package require md5
set use_md5 1
} else {
set use_md5 0
}
return $use_md5
}
tcl::namespace::eval class { tcl::namespace::eval class {
variable opts_table_defaults variable opts_table_defaults
set opts_table_defaults [tcl::dict::create\ set opts_table_defaults [tcl::dict::create\
@ -228,6 +300,7 @@ tcl::namespace::eval textblock {
} }
return $map return $map
} }
if {[tcl::info::commands [tcl::namespace::current]::table] eq ""} { if {[tcl::info::commands [tcl::namespace::current]::table] eq ""} {
#*** !doctools #*** !doctools
#[subsection {Namespace textblock::class}] #[subsection {Namespace textblock::class}]
@ -249,7 +322,7 @@ tcl::namespace::eval textblock {
oo::class create [tcl::namespace::current]::table [tcl::string::map [list %topt_keys% $topt_keys %topt_switchkeys% $switch_keys_valid_topts %copt_keys% $copt_keys %copt_switchkeys% $switch_keys_valid_copts] { oo::class create [tcl::namespace::current]::table [tcl::string::map [list %topt_keys% $topt_keys %topt_switchkeys% $switch_keys_valid_topts %copt_keys% $copt_keys %copt_switchkeys% $switch_keys_valid_copts] {
#*** !doctools #*** !doctools
#[enum] CLASS [class interface_caphandler.registry] #[enum] CLASS [class textblock::class::table]
#[list_begin definitions] #[list_begin definitions]
# [para] [emph METHODS] # [para] [emph METHODS]
variable o_opts_table ;#options as configured by user (with exception of -ansireset) variable o_opts_table ;#options as configured by user (with exception of -ansireset)
@ -3986,7 +4059,7 @@ tcl::namespace::eval textblock {
if append is chosen the new values will always start at the first column" if append is chosen the new values will always start at the first column"
-columns -default "" -type integer -help "Number of table columns -columns -default "" -type integer -help "Number of table columns
Will default to 2 if not using an existing -table object" Will default to 2 if not using an existing -table object"
*values *values -min 0 -max 1
datalist -default {} -help "flat list of table cell values which will be wrapped based on -columns value" datalist -default {} -help "flat list of table cell values which will be wrapped based on -columns value"
}] $args] }] $args]
set opts [dict get $argd opts] set opts [dict get $argd opts]
@ -4337,6 +4410,14 @@ tcl::namespace::eval textblock {
return [tcl::dict::create width $width height $height] ;#maintain order in 'image processing' standard width then height - caller may use lassign [tcl::dict::values [blocksize <data>]] width height return [tcl::dict::create width $width height $height] ;#maintain order in 'image processing' standard width then height - caller may use lassign [tcl::dict::values [blocksize <data>]] width height
} }
proc size_as_opts {textblock} {
set sz [size $textblock]
return [dict create -width [dict get $sz width] -height [dict get $sz height]]
}
proc size_as_list {textblock} {
set sz [size $textblock]
return [list [dict get $sz width] [dict get $sz height]]
}
#must be able to handle block as string with or without newlines #must be able to handle block as string with or without newlines
#if no newlines - attempt to treat as a list #if no newlines - attempt to treat as a list
#must handle whitespace-only string,list elements, and/or lines. #must handle whitespace-only string,list elements, and/or lines.
@ -5061,6 +5142,7 @@ tcl::namespace::eval textblock {
[punk::lib::list_as_lines -- [lrepeat 8 " | "]] [punk::lib::list_as_lines -- [lrepeat 8 " | "]]
} }
proc table {args} { proc table {args} {
#todo - use punk::args
upvar ::textblock::class::opts_table_defaults toptdefaults upvar ::textblock::class::opts_table_defaults toptdefaults
set defaults [tcl::dict::create\ set defaults [tcl::dict::create\
-rows [list]\ -rows [list]\
@ -5112,7 +5194,7 @@ tcl::namespace::eval textblock {
} }
variable frametypes variable frametypes
set frametypes [list light heavy arc double block block1 block2 ascii altg] set frametypes [list light heavy arc double block block1 block2 block2hack ascii altg]
#class::table needs to be able to determine valid frametypes #class::table needs to be able to determine valid frametypes
proc frametypes {} { proc frametypes {} {
variable frametypes variable frametypes
@ -5121,7 +5203,7 @@ tcl::namespace::eval textblock {
proc frametype {f} { proc frametype {f} {
#set custom_keys [list hl hlt hlb vl vll vlr tlc trc blc brc] #set custom_keys [list hl hlt hlb vl vll vlr tlc trc blc brc]
switch -- $f { switch -- $f {
light - heavy - arc - double - block - block1 - block2 - ascii - altg { light - heavy - arc - double - block - block1 - block2 - block2hack - ascii - altg {
return [tcl::dict::create category predefined type $f] return [tcl::dict::create category predefined type $f]
} }
default { default {
@ -5142,7 +5224,7 @@ tcl::namespace::eval textblock {
set is_custom_dict_ok 0 set is_custom_dict_ok 0
} }
if {!$is_custom_dict_ok} { if {!$is_custom_dict_ok} {
error "frame option -type must be one of known types: $textblock::frametypes or a dictionary with any of keys hl,hlt,hlb,vl,vll,vlr,tlc,trc,blc,brc" error "frame option -type must be one of known types: $::textblock::frametypes or a dictionary with any of keys hl,hlt,hlb,vl,vll,vlr,tlc,trc,blc,brc"
} }
set default_custom [tcl::dict::create hl " " vl " " 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] set custom_frame [tcl::dict::merge $default_custom $f]
@ -6252,9 +6334,12 @@ tcl::namespace::eval textblock {
set vlr \u2595 ;# right one eighth block set vlr \u2595 ;# right one eighth block
set vll \u258f ;# left one eighth block set vll \u258f ;# left one eighth block
#some terminals (on windows as at 2024) miscount width of these single-width blocks internally
#resulting in extra spacing that is sometimes well removed from the character itself (e.g at next ansi reset)
#This was fixed in windows-terminal based systems (2021) but persists in others.
#https://github.com/microsoft/terminal/issues/11694
set tlc \U1fb7d ;#legacy block set tlc \U1fb7d ;#legacy block
set trc \U1fb7e ;#legacy block set trc \U1fb7e ;#legacy block
set blc \U1fb7c ;#legacy block set blc \U1fb7c ;#legacy block
set brc \U1fb7f ;#legacy block set brc \U1fb7f ;#legacy block
@ -6265,6 +6350,42 @@ tcl::namespace::eval textblock {
set vlrj $vlr set vlrj $vlr
} }
block2hack {
#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 vlr \u2595 ;# right one eighth block
set vll \u258f ;# left one eighth block
#see comments in block2 regarding the problems in some terminals that this *may* hack around to some extent.
#the caller probably only needs block2hack if block2 doesn't work
#1)
#review - this hack looks sort of promising - but overtype::renderline needs fixing ?
#set tlc \U1fb7d\b ;#legacy block
#set trc \U1fb7e\b ;#legacy block
#set blc \U1fb7c\b ;#legacy block
#set brc \U1fb7f\b ;#legacy block
#2) - works on cmd.exe and some others
# a 'privacy message' is 'probably' also not supported on the old terminal but is on newer ones
#known exception - conemu on windows - displays junk for various ansi codes - (and slow terminal anyway)
#this hack has a reasonable chance of working
#except that the punk overtype library does recognise PMs
#A single backspace however is an unlikely and generally unuseful PM - so there is a corresponding hack in the renderline system to pass this PM through!
#ugly - in that we don't know the application specifics of what the PM data contains and where it's going.
set tlc \U1fb7d\x1b^\b\x1b\\ ;#legacy block
set trc \U1fb7e\x1b^\b\x1b\\ ;#legacy block
set blc \U1fb7c\x1b^\b\x1b\\ ;#legacy block
set brc \U1fb7f\x1b^\b\x1b\\ ;#legacy block
#horizontal and vertical bar joins
set hltj $hlt
set hlbj $hlb
set vllj $vll
set vlrj $vlr
}
block { block {
set hlt \u2580 ;#upper half set hlt \u2580 ;#upper half
set hlb \u2584 ;#lower half set hlb \u2584 ;#lower half
@ -6286,7 +6407,7 @@ tcl::namespace::eval textblock {
set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "] ;#only default the general types - these form defaults for more specific types if they're missing set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "] ;#only default the general types - these form defaults for more specific types if they're missing
if {[llength $f] % 2 != 0} { if {[llength $f] % 2 != 0} {
#todo - retrieve usage from punk::args #todo - retrieve usage from punk::args
error "textblock::frametype frametype '$f' is not one of the predefined frametypes: $textblock::frametypes and does not appear to be a dictionary for a custom frametype" error "textblock::frametype frametype '$f' is not one of the predefined frametypes: $::textblock::frametypes and does not appear to be a dictionary for a custom frametype"
} }
#unknown order of keys specified by user - validate before creating vars as we need more general elements to be available as defaults #unknown order of keys specified by user - validate before creating vars as we need more general elements to be available as defaults
dict for {k v} $f { dict for {k v} $f {
@ -6388,7 +6509,7 @@ tcl::namespace::eval textblock {
#options before content argument - which is allowed to be absent #options before content argument - which is allowed to be absent
#frame performance (noticeable with complex tables even of modest size) is improved significantly by frame_cache - but is still (2024) a fairly expensive operation. #frame performance (noticeable with complex tables even of modest size) is improved somewhat by frame_cache - but is still (2024) a fairly expensive operation.
# #
#consider if we can use -sticky nsew instead of -blockalign (as per Tk grid -sticky option) #consider if we can use -sticky nsew instead of -blockalign (as per Tk grid -sticky option)
# This would require some sort of expand equivalent e.g for -sticky ew or -sticky ns - for which we have to decide a meaning for text.. e.g ansi padding? # This would require some sort of expand equivalent e.g for -sticky ew or -sticky ns - for which we have to decide a meaning for text.. e.g ansi padding?
@ -6397,6 +6518,7 @@ tcl::namespace::eval textblock {
# - but we would need to maintain support for the rendered-string based operations too. # - but we would need to maintain support for the rendered-string based operations too.
proc frame {args} { proc frame {args} {
variable frametypes variable frametypes
variable use_md5
#counterintuitively - in-proc dict create each time is generally slightly faster than linked namespace var #counterintuitively - in-proc dict create each time is generally slightly faster than linked namespace var
set opts [tcl::dict::create\ set opts [tcl::dict::create\
@ -6416,7 +6538,11 @@ tcl::namespace::eval textblock {
-ellipsis 1\ -ellipsis 1\
-usecache 1\ -usecache 1\
-buildcache 1\ -buildcache 1\
-pad 1\
-crm_mode 0\
] ]
#-pad 1 is default so that simple 'textblock::frame "[a+ Red]a \nbbb[a]" extends the bg colour on the short ragged lines (and empty lines)
# for ansi art - -pad 0 is likely to be preferable
set expect_optval 0 set expect_optval 0
set argposn 0 set argposn 0
@ -6455,7 +6581,12 @@ tcl::namespace::eval textblock {
#use -buildcache 1 with -usecache 0 for debugging cache issues so we can inspect using textblock::frame_cache #use -buildcache 1 with -usecache 0 for debugging cache issues so we can inspect using textblock::frame_cache
foreach {k v} $arglist { foreach {k v} $arglist {
switch -- $k { switch -- $k {
-etabs - -type - -boxlimits - -boxmap - -joins - -title - -subtitle - -width - -height - -ansiborder - -ansibase - -blockalign - -textalign - -ellipsis - -usecache - -buildcache { -etabs - -type - -boxlimits - -boxmap - -joins
- -title - -subtitle - -width - -height
- -ansiborder - -ansibase
- -blockalign - -textalign - -ellipsis
- -crm_mode
- -usecache - -buildcache - -pad {
tcl::dict::set opts $k $v tcl::dict::set opts $k $v
} }
default { default {
@ -6471,11 +6602,13 @@ tcl::namespace::eval textblock {
set opt_boxmap [tcl::dict::get $opts -boxmap] set opt_boxmap [tcl::dict::get $opts -boxmap]
set opt_usecache [tcl::dict::get $opts -usecache] set opt_usecache [tcl::dict::get $opts -usecache]
set opt_buildcache [tcl::dict::get $opts -buildcache] set opt_buildcache [tcl::dict::get $opts -buildcache]
set opt_pad [tcl::dict::get $opts -pad]
set opt_crm_mode [tcl::dict::get $opts -crm_mode]
set usecache $opt_usecache ;#may need to override set usecache $opt_usecache ;#may need to override
set buildcache $opt_buildcache set buildcache $opt_buildcache
set custom_keys [list hl hlt hlb vl vll vlr tlc trc blc brc] set custom_keys [list hl hlt hlb vl vll vlr tlc trc blc brc]
set known_frametypes $frametypes ;# light, heavey etc as defined in textblock::frametypes variable set known_frametypes $frametypes ;# light, heavey etc as defined in the ::textblock::frametypes variable
set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "] set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "]
lassign [textblock::frametype $opt_type] _cat category _type ftype lassign [textblock::frametype $opt_type] _cat category _type ftype
@ -6614,6 +6747,19 @@ tcl::namespace::eval textblock {
} }
} }
set contents [tcl::string::map [list \r\n \n] $contents] set contents [tcl::string::map [list \r\n \n] $contents]
if {$opt_crm_mode} {
if {$opt_height eq ""} {
set h [textblock::height $contents]
} else {
set h [expr {$opt_height -2}]
}
if {$opt_width eq ""} {
set w [textblock::width $contents]
} else {
set w [expr {$opt_width -2}]
}
set contents [overtype::renderspace -crm_mode 1 -wrap 1 -width $w -height $h "" $contents]
}
set actual_contentwidth [textblock::width $contents] ;#length of longest line in contents (contents can be ragged) set actual_contentwidth [textblock::width $contents] ;#length of longest line in contents (contents can be ragged)
set actual_contentheight [textblock::height $contents] set actual_contentheight [textblock::height $contents]
} else { } else {
@ -6652,9 +6798,14 @@ tcl::namespace::eval textblock {
#review - custom frame affects frame_inner_width - exclude from caching? #review - custom frame affects frame_inner_width - exclude from caching?
#set cache_key [concat $arglist $frame_inner_width $frame_inner_height] #set cache_key [concat $arglist $frame_inner_width $frame_inner_height]
set hashables [concat $arglist $frame_inner_width $frame_inner_height] set hashables [concat $arglist $frame_inner_width $frame_inner_height]
package require md5
#set hash $hashables if {$use_md5} {
set hash [md5::md5 -hex [encoding convertto utf-8 $hashables]] ;#need fast and unique to content - not cryptographic - review #package require md5 ;#already required at package load
set hash [md5::md5 -hex [encoding convertto utf-8 $hashables]] ;#need fast and unique to content - not cryptographic - review
} else {
set hash $hashables
}
set cache_key "$hash-$frame_inner_width-$frame_inner_height-actualcontentwidth:$actual_contentwidth" set cache_key "$hash-$frame_inner_width-$frame_inner_height-actualcontentwidth:$actual_contentwidth"
#should be in a unicode private range different to that used in table construction #should be in a unicode private range different to that used in table construction
#e.g BMP PUA U+E000 -> U+F8FF - although this is commonly used for example by nerdfonts #e.g BMP PUA U+E000 -> U+F8FF - although this is commonly used for example by nerdfonts
@ -7057,15 +7208,22 @@ tcl::namespace::eval textblock {
append contents [::join [lrepeat $diff \n] ""] append contents [::join [lrepeat $diff \n] ""]
} }
set paddedcontents [textblock::pad $contents -which $pad -within_ansi 1] ;#autowidth to width of content (should match cache_patternwidth) if {$opt_pad} {
set paddedwidth [textblock::widthtopline $paddedcontents] set paddedcontents [textblock::pad $contents -which $pad -within_ansi 1] ;#autowidth to width of content (should match cache_patternwidth)
set paddedwidth [textblock::widthtopline $paddedcontents]
#review - horizontal truncation #review - horizontal truncation
if {$paddedwidth > $cache_patternwidth} { if {$paddedwidth > $cache_patternwidth} {
set paddedcontents [overtype::renderspace -width $cache_patternwidth "" $paddedcontents] set paddedcontents [overtype::renderspace -width $cache_patternwidth "" $paddedcontents]
}
#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
} else {
set cwidth [textblock::width $contents]
if {$cwidth > $cache_patternwidth} {
set contents [overtype::renderspace -width $cache_patternwidth "" $contents]
}
set contentblock [textblock::join -- $contents]
} }
#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]
@ -7183,7 +7341,6 @@ tcl::namespace::eval textblock {
#fastest to do row first then columns - because textblock::join must do line by line #fastest to do row first then columns - because textblock::join must do line by line
if {$crosscount > 1} { if {$crosscount > 1} {
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]
@ -7224,3 +7381,7 @@ package provide textblock [tcl::namespace::eval textblock {
set version 0.1.1 set version 0.1.1
}] }]
return return
#*** !doctools
#[manpage_end]

77
src/project_layouts/custom/_project/punk.project-0.1/src/make.tcl

@ -1212,8 +1212,9 @@ foreach vfstail $vfs_tails {
set rtmountpoint //zipfs:/rtmounts/$runtime_fullname set rtmountpoint //zipfs:/rtmounts/$runtime_fullname
set changed_unchanged [$vfs_event targetset_source_changes] set changed_unchanged [$vfs_event targetset_source_changes]
set vfs_or_runtime_changed [expr {[llength [dict get $changed_unchanged changed]] || [llength [$vfs_event get_targets_exist]] < [llength [$vfs_event get_targets]]}]
if {[llength [dict get $changed_unchanged changed]] || [llength [$vfs_event get_targets_exist]] < [llength [$vfs_event get_targets]]} { if {$vfs_or_runtime_changed} {
#source .vfs folder has changes #source .vfs folder has changes
$vfs_event targetset_started $vfs_event targetset_started
# -- --- --- --- --- --- # -- --- --- --- --- ---
@ -1283,6 +1284,7 @@ foreach vfstail $vfs_tails {
puts stderr "RUNTIME capabilities unknown. Unsure if zip supported. trying anyway.." puts stderr "RUNTIME capabilities unknown. Unsure if zip supported. trying anyway.."
} }
} }
#note - as at 2024-08 - there is some discussion about the interface to mkimg - it is considered unstable (may change to -option value syntax)
puts stderr "calling: tcl::zipfs::mkimg $buildfolder/$vfsname.new $wrapvfs $wrapvfs \"\" $buildfolder/build_$runtime_fullname" puts stderr "calling: tcl::zipfs::mkimg $buildfolder/$vfsname.new $wrapvfs $wrapvfs \"\" $buildfolder/build_$runtime_fullname"
tcl::zipfs::mkimg $buildfolder/$vfsname.new $wrapvfs $wrapvfs "" $buildfolder/build_$runtime_fullname tcl::zipfs::mkimg $buildfolder/$vfsname.new $wrapvfs $wrapvfs "" $buildfolder/build_$runtime_fullname
} result ]} { } result ]} {
@ -1352,9 +1354,10 @@ foreach vfstail $vfs_tails {
if {![catch { if {![catch {
exec $pscmd | grep $targetkit exec $pscmd | grep $targetkit
} still_running]} { } still_running]} {
set still_running_lines [split [string trim $still_running] \n]
puts stdout "found $targetkit instances still running\n" puts stdout "found ([llength $still_running_lines]) $targetkit instances still running\n"
set count_killed 0 set count_killed 0
set num_to_kill [llength $still_running_lines]
foreach ln [split $still_running \n] { foreach ln [split $still_running \n] {
puts stdout " $ln" puts stdout " $ln"
@ -1387,9 +1390,6 @@ foreach vfstail $vfs_tails {
#review - *no running instance* works with windows taskkill - "*No such process*" works with kill -9 on FreeBSD and linux - other platforms? #review - *no running instance* works with windows taskkill - "*No such process*" works with kill -9 on FreeBSD and linux - other platforms?
if {![string match "*no running instance*" $errMsg] && ![string match "*No such process*" $errMsg]} { if {![string match "*no running instance*" $errMsg] && ![string match "*No such process*" $errMsg]} {
lappend failed_kits [list kit $targetkit reason "could not kill running process for $targetkit (using '$killcmd')"] lappend failed_kits [list kit $targetkit reason "could not kill running process for $targetkit (using '$killcmd')"]
$vfs_event targetset_end FAILED
$vfs_event destroy
$vfs_installer destroy
continue continue
} }
} else { } else {
@ -1397,10 +1397,15 @@ foreach vfstail $vfs_tails {
incr count_killed incr count_killed
} }
} }
if {$count_killed > 0} { if {$count_killed < $num_to_kill} {
puts stderr "\nKilled $count_killed processes. Waiting a short time before attempting to delete executable" $vfs_event targetset_end FAILED
after 1000 $vfs_event destroy
$vfs_installer destroy
continue
} }
puts stderr "\nKilled $count_killed processes. Waiting a short time before attempting to delete executable"
after 1000
} else { } else {
puts stderr "Ok.. no running '$targetkit' processes found" puts stderr "Ok.. no running '$targetkit' processes found"
} }
@ -1426,22 +1431,35 @@ foreach vfstail $vfs_tails {
# -- --- --- --- --- --- # -- --- --- --- --- ---
$vfs_event targetset_end OK $vfs_event targetset_end OK
} else {
set skipped_vfs_build 1
puts stderr "."
puts stdout "Skipping build for vfs $vfstail with runtime $rtname - no change detected"
$vfs_event targetset_end SKIPPED
}
$vfs_event destroy
$vfs_installer destroy
after 200 after 200
set deployment_folder [file dirname $sourcefolder]/bin set deployment_folder [file dirname $sourcefolder]/bin
file mkdir $deployment_folder file mkdir $deployment_folder
# -- ---------- # -- ----------
set bin_installer [punkcheck::installtrack new "make.tcl" $deployment_folder/.punkcheck] set bin_installer [punkcheck::installtrack new "make.tcl" $deployment_folder/.punkcheck]
$bin_installer set_source_target $buildfolder $deployment_folder $bin_installer set_source_target $buildfolder $deployment_folder
set bin_event [$bin_installer start_event {-make-step final_kit_install}] set bin_event [$bin_installer start_event {-make-step final_kit_install}]
$bin_event targetset_init INSTALL $deployment_folder/$targetkit $bin_event targetset_init INSTALL $deployment_folder/$targetkit
#todo - move final deployment step outside of the build vfs loop? (final deployment can fail and then isn't rerun even though _build and deployed versions differ, unless .vfs modified again) #todo - move final deployment step outside of the build vfs loop? (final deployment can fail and then isn't rerun even though _build and deployed versions differ, unless .vfs modified again)
#set last_completion [$bin_event targetset_last_complete] #set last_completion [$bin_event targetset_last_complete]
$bin_event targetset_addsource $buildfolder/$targetkit $bin_event targetset_addsource $deployment_folder/$targetkit ;#add target as a source of metadata for change detection
$bin_event targetset_started $bin_event targetset_addsource $buildfolder/$targetkit
# -- ---------- $bin_event targetset_started
# -- ----------
set changed_unchanged [$bin_event targetset_source_changes]
set built_or_installed_kit_changed [expr {[llength [dict get $changed_unchanged changed]] || [llength [$bin_event get_targets_exist]] < [llength [$bin_event get_targets]]}]
if {$built_or_installed_kit_changed} {
if {[file exists $deployment_folder/$targetkit]} { if {[file exists $deployment_folder/$targetkit]} {
puts stderr "deleting existing deployed at $deployment_folder/$targetkit" puts stderr "deleting existing deployed at $deployment_folder/$targetkit"
@ -1467,19 +1485,16 @@ foreach vfstail $vfs_tails {
# -- ---------- # -- ----------
$bin_event targetset_end OK $bin_event targetset_end OK
# -- ---------- # -- ----------
$bin_event destroy
$bin_installer destroy
} else { } else {
set skipped_vfs_build 1 set skipped_kit_install 1
puts stderr "." puts stderr "."
puts stdout "Skipping build for vfs $vfstail with runtime $rtname - no change detected" puts stdout "Skipping kit install for $targetkit with vfs $vfstail runtime $rtname - no change detected"
$vfs_event targetset_end SKIPPED $bin_event targetset_end SKIPPED
} }
$bin_event destroy
$bin_installer destroy
$vfs_event destroy
$vfs_installer destroy
} ;#end foreach targetkit } ;#end foreach targetkit
} ;#end foreach rtname in runtimes } ;#end foreach rtname in runtimes

74
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/fileutil/paths-1.tm

@ -0,0 +1,74 @@
# paths.tcl --
#
# Manage lists of search paths.
#
# Copyright (c) 2009-2019 Andreas Kupries <andreas_kupries@sourceforge.net>
#
# See the file "license.terms" for information on usage and redistribution
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
# Each object instance manages a list of paths.
# ### ### ### ######### ######### #########
## Requisites
package require Tcl 8.4
package require snit
# ### ### ### ######### ######### #########
## API
snit::type ::fileutil::paths {
# ### ### ### ######### ######### #########
## Options :: None
# ### ### ### ######### ######### #########
## Creation, destruction
# Default constructor.
# Default destructor.
# ### ### ### ######### ######### #########
## Methods :: Querying and manipulating the list of paths.
method paths {} {
return $mypaths
}
method add {path} {
set pos [lsearch $mypaths $path]
if {$pos >= 0 } return
lappend mypaths $path
return
}
method remove {path} {
set pos [lsearch $mypaths $path]
if {$pos < 0} return
set mypaths [lreplace $mypaths $pos $pos]
return
}
method clear {} {
set mypaths {}
return
}
# ### ### ### ######### ######### #########
## Internal methods :: None
# ### ### ### ######### ######### #########
## State :: List of paths.
variable mypaths {}
##
# ### ### ### ######### ######### #########
}
# ### ### ### ######### ######### #########
## Ready
package provide fileutil::paths 1
return

504
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/fileutil/traverse-0.6.tm

@ -0,0 +1,504 @@
# traverse.tcl --
#
# Directory traversal.
#
# Copyright (c) 2006-2015 by Andreas Kupries <andreas_kupries@users.sourceforge.net>
#
# See the file "license.terms" for information on usage and redistribution
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
package require Tcl 8.3
# OO core
if {[package vsatisfies [package present Tcl] 8.5]} {
# Use new Tcl 8.5a6+ features to specify the allowed packages.
# We can use anything above 1.3. This means v2 as well.
package require snit 1.3-
} else {
# For Tcl 8.{3,4} only snit1 of a suitable patchlevel is possible.
package require snit 1.3
}
package require control ; # Helpers for control structures
package require fileutil ; # -> fullnormalize
snit::type ::fileutil::traverse {
# Incremental directory traversal.
# API
# create %AUTO% basedirectory options... -> object
# next filevar -> boolean
# foreach filevar script
# files -> list (path ...)
# Options
# -prefilter command-prefix
# -filter command-prefix
# -errorcmd command-prefix
# Use cases
#
# (a) Basic incremental
# - Create and configure a traversal object.
# - Execute 'next' to retrieve one path at a time,
# until the command returns False, signaling that
# the iterator has exhausted the supply of paths.
# (The path is stored in the named variable).
#
# The execution of 'next' can be done in a loop, or via event
# processing.
# (b) Basic loop
# - Create and configure a traversal object.
# - Run a script for each path, using 'foreach'.
# This is a convenient standard wrapper around 'next'.
#
# The loop properly handles all possible Tcl result codes.
# (c) Non-incremental, non-looping.
# - Create and configure a traversal object.
# - Retrieve a list of all paths via 'files'.
# The -prefilter callback is executed for directories. Its result
# determines if the traverser recurses into the directory or not.
# The default is to always recurse into all directories. The call-
# back is invoked with a single argument, the path of the
# directory.
#
# The -filter callback is executed for all paths. Its result
# determines if the current path is a valid result, and returned
# by 'next'. The default is to accept all paths as valid. The
# callback is invoked with a single argument, the path to check.
# The -errorcmd callback is executed for all paths the traverser
# has trouble with. Like being unable to cd into them, get their
# status, etc. The default is to ignore any such problems. The
# callback is invoked with a two arguments, the path for which the
# error occured, and the error message. Errors thrown by the
# filter callbacks are handled through this callback too. Errors
# thrown by the error callback itself are not caught and ignored,
# but allowed to pass to the caller, usually of 'next'.
# Note: Low-level functionality, version and platform dependent is
# implemented in procedures, and conditioally defined for optimal
# use of features, etc. ...
# Note: Traversal is done in depth-first pre-order.
# Note: The options are handled only during
# construction. Afterward they are read-only and attempts to
# modify them will cause the system to throw errors.
# ### ### ### ######### ######### #########
## Implementation
option -filter -default {} -readonly 1
option -prefilter -default {} -readonly 1
option -errorcmd -default {} -readonly 1
constructor {basedir args} {
set _base $basedir
$self configurelist $args
return
}
method files {} {
set files {}
$self foreach f {lappend files $f}
return $files
}
method foreach {fvar body} {
upvar 1 $fvar currentfile
# (Re-)initialize the traversal state on every call.
$self Init
while {[$self next currentfile]} {
set code [catch {uplevel 1 $body} result]
# decide what to do upon the return code:
#
# 0 - the body executed successfully
# 1 - the body raised an error
# 2 - the body invoked [return]
# 3 - the body invoked [break]
# 4 - the body invoked [continue]
# everything else - return and pass on the results
#
switch -exact -- $code {
0 {}
1 {
return -errorinfo [::control::ErrorInfoAsCaller uplevel foreach] \
-errorcode $::errorCode -code error $result
}
3 {
# FRINK: nocheck
return
}
4 {}
default {
return -code $code $result
}
}
}
return
}
method next {fvar} {
upvar 1 $fvar currentfile
# Initialize on first call.
if {!$_init} {
$self Init
}
# We (still) have valid paths in the result stack, return the
# next one.
if {[llength $_results]} {
set top [lindex $_results end]
set _results [lreplace $_results end end]
set currentfile $top
return 1
}
# Take the next directory waiting in the processing stack and
# fill the result stack with all valid files and sub-
# directories contained in it. Extend the processing queue
# with all sub-directories not yet seen already (!circular
# symlinks) and accepted by the prefilter. We stop iterating
# when we either have no directories to process anymore, or
# the result stack contains at least one path we can return.
while {[llength $_pending]} {
set top [lindex $_pending end]
set _pending [lreplace $_pending end end]
# Directory accessible? Skip if not.
if {![ACCESS $top]} {
Error $top "Inacessible directory"
continue
}
# Expand the result stack with all files in the directory,
# modulo filtering.
foreach f [GLOBF $top] {
if {![Valid $f]} continue
lappend _results $f
}
# Expand the result stack with all sub-directories in the
# directory, modulo filtering. Further expand the
# processing stack with the same directories, if not seen
# yet and modulo pre-filtering.
foreach f [GLOBD $top] {
if {
[string equal [file tail $f] "."] ||
[string equal [file tail $f] ".."]
} continue
if {[Valid $f]} {
lappend _results $f
}
Enter $top $f
if {[Cycle $f]} continue
if {[Recurse $f]} {
lappend _pending $f
}
}
# Stop expanding if we have paths to return.
if {[llength $_results]} {
set top [lindex $_results end]
set _results [lreplace $_results end end]
set currentfile $top
return 1
}
}
# Allow re-initialization with next call.
set _init 0
return 0
}
# ### ### ### ######### ######### #########
## Traversal state
# * Initialization flag. Checked in 'next', reset by next when no
# more files are available. Set in 'Init'.
# * Base directory (or file) to start the traversal from.
# * Stack of prefiltered unknown directories waiting for
# processing, i.e. expansion (TOP at end).
# * Stack of valid paths waiting to be returned as results.
# * Set of directories already visited (normalized paths), for
# detection of circular symbolic links.
variable _init 0 ; # Initialization flag.
variable _base {} ; # Base directory.
variable _pending {} ; # Processing stack.
variable _results {} ; # Result stack.
# sym link handling (to break cycles, while allowing the following of non-cycle links).
# Notes
# - path parent tracking is lexical.
# - path identity tracking is based on the normalized path, i.e. the path with all
# symlinks resolved.
# Maps
# - path -> parent (easier to follow the list than doing dirname's)
# - path -> normalized (cache to avoid redundant calls of fullnormalize)
# cycle <=> A parent's normalized form (NF) is identical to the current path's NF
variable _parent -array {}
variable _norm -array {}
# ### ### ### ######### ######### #########
## Internal helpers.
proc Enter {parent path} {
#puts ___E|$path
upvar 1 _parent _parent _norm _norm
set _parent($path) $parent
set _norm($path) [fileutil::fullnormalize $path]
}
proc Cycle {path} {
upvar 1 _parent _parent _norm _norm
set nform $_norm($path)
set paren $_parent($path)
while {$paren ne {}} {
if {$_norm($paren) eq $nform} { return yes }
set paren $_parent($paren)
}
return no
}
method Init {} {
array unset _parent *
array unset _norm *
# Path ok as result?
if {[Valid $_base]} {
lappend _results $_base
}
# Expansion allowed by prefilter?
if {[file isdirectory $_base] && [Recurse $_base]} {
Enter {} $_base
lappend _pending $_base
}
# System is set up now.
set _init 1
return
}
proc Valid {path} {
#puts ___V|$path
upvar 1 options options
if {![llength $options(-filter)]} {return 1}
set path [file normalize $path]
set code [catch {uplevel \#0 [linsert $options(-filter) end $path]} valid]
if {!$code} {return $valid}
Error $path $valid
return 0
}
proc Recurse {path} {
#puts ___X|$path
upvar 1 options options _norm _norm
if {![llength $options(-prefilter)]} {return 1}
set path [file normalize $path]
set code [catch {uplevel \#0 [linsert $options(-prefilter) end $path]} valid]
if {!$code} {return $valid}
Error $path $valid
return 0
}
proc Error {path msg} {
upvar 1 options options
if {![llength $options(-errorcmd)]} return
set path [file normalize $path]
uplevel \#0 [linsert $options(-errorcmd) end $path $msg]
return
}
##
# ### ### ### ######### ######### #########
}
# ### ### ### ######### ######### #########
##
# The next three helper commands for the traverser depend strongly on
# the version of Tcl, and partially on the platform.
# 1. In Tcl 8.3 using -types f will return only true files, but not
# links to files. This changed in 8.4+ where links to files are
# returned as well. So for 8.3 we have to handle the links
# separately (-types l) and also filter on our own.
# Note that Windows file links are hard links which are reported by
# -types f, but not -types l, so we can optimize that for the two
# platforms.
#
# 2. In Tcl 8.3 we also have a crashing bug in glob (SIGABRT, "stat on
# a known file") when trying to perform 'glob -types {hidden f}' on
# a directory without e'x'ecute permissions. We code around by
# testing if we can cd into the directory (stat might return enough
# information too (mode), but possibly also not portable).
#
# For Tcl 8.2 and 8.4+ glob simply delivers an empty result
# (-nocomplain), without crashing. For them this command is defined
# so that the bytecode compiler removes it from the bytecode.
#
# This bug made the ACCESS helper necessary.
# We code around the problem by testing if we can cd into the
# directory (stat might return enough information too (mode), but
# possibly also not portable).
if {[package vsatisfies [package present Tcl] 8.5]} {
# Tcl 8.5+.
# We have to check readability of "current" on our own, glob
# changed to error out instead of returning nothing.
proc ::fileutil::traverse::ACCESS {args} {return 1}
proc ::fileutil::traverse::GLOBF {current} {
if {![file readable $current] ||
[BadLink $current]} {
return {}
}
set res [lsort -unique [concat \
[glob -nocomplain -directory $current -types f -- *] \
[glob -nocomplain -directory $current -types {hidden f} -- *]]]
# Look for broken links (They are reported as neither file nor directory).
foreach l [lsort -unique [concat \
[glob -nocomplain -directory $current -types l -- *] \
[glob -nocomplain -directory $current -types {hidden l} -- *]]] {
if {[file isfile $l]} continue
if {[file isdirectory $l]} continue
lappend res $l
}
return [lsort -unique $res]
}
proc ::fileutil::traverse::GLOBD {current} {
if {![file readable $current] ||
[BadLink $current]} {
return {}
}
lsort -unique [concat \
[glob -nocomplain -directory $current -types d -- *] \
[glob -nocomplain -directory $current -types {hidden d} -- *]]
}
proc ::fileutil::traverse::BadLink {current} {
if {[file type $current] ne "link"} { return no }
set dst [file join [file dirname $current] [file readlink $current]]
if {![file exists $dst] ||
![file readable $dst]} {
return yes
}
return no
}
} elseif {[package vsatisfies [package present Tcl] 8.4]} {
# Tcl 8.4+.
# (Ad 1) We have -directory, and -types,
# (Ad 2) Links are returned for -types f/d if they refer to files/dirs.
# (Ad 3) No bug to code around
proc ::fileutil::traverse::ACCESS {args} {return 1}
proc ::fileutil::traverse::GLOBF {current} {
set res [concat \
[glob -nocomplain -directory $current -types f -- *] \
[glob -nocomplain -directory $current -types {hidden f} -- *]]
# Look for broken links (They are reported as neither file nor directory).
foreach l [concat \
[glob -nocomplain -directory $current -types l -- *] \
[glob -nocomplain -directory $current -types {hidden l} -- *] ] {
if {[file isfile $l]} continue
if {[file isdirectory $l]} continue
lappend res $l
}
return $res
}
proc ::fileutil::traverse::GLOBD {current} {
concat \
[glob -nocomplain -directory $current -types d -- *] \
[glob -nocomplain -directory $current -types {hidden d} -- *]
}
} else {
# 8.3.
# (Ad 1) We have -directory, and -types,
# (Ad 2) Links are NOT returned for -types f/d, collect separately.
# No symbolic file links on Windows.
# (Ad 3) Bug to code around.
proc ::fileutil::traverse::ACCESS {current} {
if {[catch {
set h [pwd] ; cd $current ; cd $h
}]} {return 0}
return 1
}
if {[string equal $::tcl_platform(platform) windows]} {
proc ::fileutil::traverse::GLOBF {current} {
concat \
[glob -nocomplain -directory $current -types f -- *] \
[glob -nocomplain -directory $current -types {hidden f} -- *]]
}
} else {
proc ::fileutil::traverse::GLOBF {current} {
set l [concat \
[glob -nocomplain -directory $current -types f -- *] \
[glob -nocomplain -directory $current -types {hidden f} -- *]]
foreach x [concat \
[glob -nocomplain -directory $current -types l -- *] \
[glob -nocomplain -directory $current -types {hidden l} -- *]] {
if {[file isdirectory $x]} continue
# We have now accepted files, links to files, and broken links.
lappend l $x
}
return $l
}
}
proc ::fileutil::traverse::GLOBD {current} {
set l [concat \
[glob -nocomplain -directory $current -types d -- *] \
[glob -nocomplain -directory $current -types {hidden d} -- *]]
foreach x [concat \
[glob -nocomplain -directory $current -types l -- *] \
[glob -nocomplain -directory $current -types {hidden l} -- *]] {
if {![file isdirectory $x]} continue
lappend l $x
}
return $l
}
}
# ### ### ### ######### ######### #########
## Ready
package provide fileutil::traverse 0.6

33
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/natsort-0.1.1.6.tm

@ -5,8 +5,9 @@ package require flagfilter
namespace import ::flagfilter::check_flags namespace import ::flagfilter::check_flags
namespace eval natsort { namespace eval natsort {
#REVIEW - determine and document the purpose of scriptdir being added to tm path
proc scriptdir {} { proc scriptdir {} {
set possibly_linked_script [file dirname [file normalize [file join [info script] ...]]] set possibly_linked_script [file dirname [file normalize [file join [info script] __dummy__]]]
if {[file isdirectory $possibly_linked_script]} { if {[file isdirectory $possibly_linked_script]} {
return $possibly_linked_script return $possibly_linked_script
} else { } else {
@ -14,7 +15,11 @@ namespace eval natsort {
} }
} }
if {![interp issafe]} { if {![interp issafe]} {
tcl::tm::add [scriptdir] set sdir [scriptdir]
#puts stderr "natsort tcl::tm::add $sdir"
if {$sdir ni [tcl::tm::list]} {
catch {tcl::tm::add $sdir}
}
} }
} }
@ -36,6 +41,7 @@ namespace eval natsort {
} else { } else {
puts stderr "|>natsort_call_err> unable to interpret 2nd argument to do_error: '$then' should be one of '$levels' or 'exit <numericcode>'" puts stderr "|>natsort_call_err> unable to interpret 2nd argument to do_error: '$then' should be one of '$levels' or 'exit <numericcode>'"
} }
flush stderr
if {$::tcl_interactive} { if {$::tcl_interactive} {
#may not always be desirable - but assumed to be more useful not to exit despite request, to aid in debugging #may not always be desirable - but assumed to be more useful not to exit despite request, to aid in debugging
if {[string tolower $type] eq "exit"} { if {[string tolower $type] eq "exit"} {
@ -43,6 +49,7 @@ namespace eval natsort {
if {![string is digit -strict $code]} { if {![string is digit -strict $code]} {
puts stderr "|>natsort_call_err> unable to interpret 2nd argument to do_error: '$then' should be: 'exit <numericcode>'" puts stderr "|>natsort_call_err> unable to interpret 2nd argument to do_error: '$then' should be: 'exit <numericcode>'"
} }
flush stderr
} }
return -code error $msg return -code error $msg
} else { } else {
@ -1422,6 +1429,9 @@ namespace eval natsort {
proc called_directly_namematch {} { proc called_directly_namematch {} {
global argv0 global argv0
if {[info script] eq ""} {
return 0
}
#see https://wiki.tcl-lang.org/page/main+script #see https://wiki.tcl-lang.org/page/main+script
#trailing ... let's us resolve symlinks in last component of the path (could be something else like ___ but ... seems unlikely to collide with anything in the filesystem) #trailing ... let's us resolve symlinks in last component of the path (could be something else like ___ but ... seems unlikely to collide with anything in the filesystem)
if {[info exists argv0] if {[info exists argv0]
@ -1440,12 +1450,18 @@ namespace eval natsort {
#Review issues around comparing names vs using inodes (esp with respect to samba shares) #Review issues around comparing names vs using inodes (esp with respect to samba shares)
proc called_directly_inodematch {} { proc called_directly_inodematch {} {
global argv0 global argv0
if {[info exists argv0] if {[info exists argv0]
&& [file exists [info script]] && [file exists $argv0]} { && [file exists [info script]] && [file exists $argv0]} {
file stat $argv0 argv0Info file stat $argv0 argv0Info
file stat [info script] scriptInfo file stat [info script] scriptInfo
expr {$argv0Info(dev) == $scriptInfo(dev) if {$argv0Info(ino) == 0 || $scriptInfo(ino) == 0 || $argv0Info(dev) == 0 || $scriptInfo(dev) == 0} {
&& $argv0Info(ino) == $scriptInfo(ino)} #vfs?
#e.g //zipfs:/
return 0
}
return [expr {$argv0Info(dev) == $scriptInfo(dev)
&& $argv0Info(ino) == $scriptInfo(ino)}]
} else { } else {
return 0 return 0
} }
@ -1460,6 +1476,11 @@ namespace eval natsort {
#-- choose a policy and leave the others commented. #-- choose a policy and leave the others commented.
#set is_called_directly $is_namematch #set is_called_directly $is_namematch
#set is_called_directly $is_inodematch #set is_called_directly $is_inodematch
#puts "NATSORT: called_directly_namematch - $is_namematch"
#puts "NATSORT: called_directly_inodematch - $is_inodematch"
#flush stdout
set is_called_directly [expr {$is_namematch || $is_inodematch}] set is_called_directly [expr {$is_namematch || $is_inodematch}]
#set is_called_directly [expr {$is_namematch && $is_inodematch}] #set is_called_directly [expr {$is_namematch && $is_inodematch}]
### ###
@ -1921,6 +1942,8 @@ namespace eval natsort {
#set args [check_flags -caller test1 -defaults [list -collate nocase -algorithm sort -topchars "\uFFFF" -winlike 0 -debug 0 -stacktrace 0 -showsplits 0] -required {all} -extras {none} -commandprocessors {{-cmd -default help} {-cmd -cmdarg1 -default "."} {-cmd -cmdarg2 -default j}} -values $::argv ] #set args [check_flags -caller test1 -defaults [list -collate nocase -algorithm sort -topchars "\uFFFF" -winlike 0 -debug 0 -stacktrace 0 -showsplits 0] -required {all} -extras {none} -commandprocessors {{-cmd -default help} {-cmd -cmdarg1 -default "."} {-cmd -cmdarg2 -default j}} -values $::argv ]
#set args [check_flags -caller test1 -defaults [list -collate nocase -algorithm sort -topchars "\uFFFF" -winlike 0 -debug 0 -stacktrace 0 -showsplits 0] -required {all} -extras {none} -commandprocessors {ls {ls lsdir -default "\uFFFF"}} -values $::argv ] #set args [check_flags -caller test1 -defaults [list -collate nocase -algorithm sort -topchars "\uFFFF" -winlike 0 -debug 0 -stacktrace 0 -showsplits 0] -required {all} -extras {none} -commandprocessors {ls {ls lsdir -default "\uFFFF"}} -values $::argv ]
puts stderr "natsort directcall exit"
flush stderr
exit 0 exit 0
if {$::argc} { if {$::argc} {

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

File diff suppressed because it is too large Load Diff

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

@ -553,28 +553,51 @@ tcl::namespace::eval punk::ansi {
$obj destroy $obj destroy
return $result return $result
} }
proc example {} { proc example {args} {
set base [punk::repo::find_project]
set default_ansibase [file join $base src/testansi]
set argd [punk::args::get_dict [tstr -return string {
*proc -name punk::ansi::example -help "Display .ans image files in a grid that will fit in console
"
-colwidth -default 82 -help "Width of each column - default of 82 will fit a standard 80wide ansi image (when framed)
You can specify a narrower width to truncate images on the right side"
-folder -default "${$default_ansibase}" -help "Base folder for files if relative paths are used.
Defaults to <projectbase>/src/testansi - where projectbase is determined from current directory.
"
*values -min 0 -max -1
files -default {belinda.ans bot.ans flower.ans fish.ans} -multiple true -help "List of filenames - leave empty to display 4 defaults"
}] $args]
set colwidth [dict get $argd opts -colwidth]
set ansibase [file normalize [dict get $argd opts -folder]]
set fnames [dict get $argd values files]
#assumes fixed column widths e.g 80col images will fit in 82-width frames (common standard for old ansi art) (of arbitrary height)
#todo - review dependency on punk::repo ? #todo - review dependency on punk::repo ?
package require textblock package require textblock
package require punk::repo package require punk::repo
package require punk::console package require punk::console
set fnames [list belinda.ans bot.ans flower.ans fish.ans]
set base [punk::repo::find_project]
set ansibase [file join $base src/testansi]
if {![file exists $ansibase]} { if {![file exists $ansibase]} {
puts stderr "Missing testansi folder at $base/src/testansi" puts stderr "Missing folder at $ansibase"
puts stderr "Ensure ansi test files exist: $fnames" puts stderr "Ensure ansi test files exist: $fnames"
#error "punk::ansi::example Cannot find example files" #error "punk::ansi::example Cannot find example files"
} }
set missingbase [a+ yellow][textblock::block 80 23 ?][a] set missingbase [a+ yellow][textblock::block [expr {$colwidth-2}] 23 ?][a] ;#assuming standard frame - subtract 2 for left/right borders
set pics [list] set pics [list]
foreach f $fnames { foreach f $fnames {
if {![file exists $ansibase/$f]} { if {[file pathtype $f] ne "absolute"} {
set p [overtype::left $missingbase "[a+ red bold]\nMissing file\n$ansibase/$f[a]"] set filepath [file normalize $ansibase/$f]
} else {
set filepath [file normalize $f]
}
if {![file exists $filepath]} {
set p [overtype::left $missingbase "[a+ red bold]\nMissing file\n$f[a]"]
lappend pics [tcl::dict::create filename $f pic $p status missing] lappend pics [tcl::dict::create filename $f pic $p status missing]
} else { } else {
set img [join [lines_as_list -line trimline -block trimtail [ansicat $ansibase/$f]] \n] #set img [join [lines_as_list -line trimline -block trimtail [ansicat $filepath]] \n]
#-line trimline will wreck some images
set img [join [lines_as_list -block trimtail [ansicat $filepath]] \n]
lappend pics [tcl::dict::create filename $f pic $img status ok] lappend pics [tcl::dict::create filename $f pic $img status ok]
} }
} }
@ -582,30 +605,73 @@ tcl::namespace::eval punk::ansi {
set termsize [punk::console:::get_size] set termsize [punk::console:::get_size]
set margin 4 set margin 4
set freewidth [expr {[tcl::dict::get $termsize columns]-$margin}] set freewidth [expr {[tcl::dict::get $termsize columns]-$margin}]
set per_row [expr {$freewidth / 80}] set per_row [expr {$freewidth / $colwidth}]
set rowlist [list] set rowlist [list] ;# { {<img> <img>} {<img> <img>} }
set row [list] set heightlist [list] ;# { {<h> <h> } {<h> <h> } }
set i 1 set maxheights [list] ;# { <max> <max>}
set row [list] ;#wip row
set rowh [list] ;#wip row img heights
set i 1 ;#track image index of whole pics list
set rowindex 0
foreach picinfo $pics { foreach picinfo $pics {
set subtitle "" set subtitle ""
if {[tcl::dict::get $picinfo status] ne "ok"} { if {[tcl::dict::get $picinfo status] ne "ok"} {
set subtitle [tcl::dict::get $picinfo status] set subtitle [tcl::dict::get $picinfo status]
} }
set title [tcl::dict::get $picinfo filename] set title [tcl::dict::get $picinfo filename]
lappend row [textblock::frame -subtitle $subtitle -title $title [tcl::dict::get $picinfo pic]] set fr [textblock::frame -width $colwidth -subtitle $subtitle -title $title [tcl::dict::get $picinfo pic]]
# -- --- --- ---
#we need the max height of a row element to use join_basic instead of join below
# -- --- --- ---
set fr_height [textblock::height $fr]
lappend row $fr
lappend rowh $fr_height
set rowmax [lindex $maxheights $rowindex]
if {$rowmax eq ""} {
#empty result means no maxheights entry for this row yet
set rowmax $fr_height
lappend maxheights $rowmax
} else {
if {$fr_height > $rowmax} {
set rowmax $fr_height
lset maxheights end $rowmax
}
}
# -- --- --- ---
if {$i % $per_row == 0} { if {$i % $per_row == 0} {
lappend rowlist $row lappend rowlist $row
lappend heightlist $rowh
incr rowindex
set row [list] set row [list]
set rowh [list]
} elseif {$i == [llength $pics]} { } elseif {$i == [llength $pics]} {
lappend rowlist $row lappend rowlist $row
lappend heightlist $rowh
} }
incr i incr i
} }
#puts "--> maxheights: $maxheights"
#puts "--> heightlist: $heightlist"
set result "" set result ""
foreach r $rowlist { set rowindex 0
append result [textblock::join_basic -- {*}$r] \n set blankline [string repeat " " $colwidth]
foreach imgs $rowlist heights $heightlist {
set maxheight [lindex $maxheights $rowindex]
set adjusted_row [list]
foreach i $imgs h $heights {
if {$h < $maxheight} {
#add blank lines to bottom of shorter images so join_basic can be used.
#textblock::join of ragged-height images would work and remove the need for all the height calculation
#.. but it requires much more processing
append i [string repeat \n$blankline [expr {$maxheight - $h}]]
}
lappend adjusted_row $i
}
append result [textblock::join_basic -- {*}$adjusted_row] \n
incr rowindex
} }
@ -3199,6 +3265,28 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
return \x1b8 return \x1b8
} }
# -- --- --- --- --- # -- --- --- --- ---
#CRM Show Control Character Mode
proc enable_crm {} {
return \x1b\[3h
}
proc disable_crm {} {
return \x1b\[3l
}
#DECSNM
#Note this can invert the enclosed section including any already reversed by SGR 7 - depending on terminal support.
#e.g
#set test [a+ reverse]aaa[a+ noreverse]bbb
# - $test above can't just be reversed by putting another [a+ reverse] in front of it.
# - but the following will work (even if underlying terminal doesn't support ?5 sequences)
#overtype::renderspace -width 20 [enable_inverse]$test
proc enable_inverse {} {
return \x1b\[?5h
}
proc disable_inverse {} {
return \x1b\[?5l
}
#DECAWM - automatic line wrapping #DECAWM - automatic line wrapping
proc enable_line_wrap {} { proc enable_line_wrap {} {
@ -3399,6 +3487,9 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#the purpose of backspace (or line cr) in embedded text is unclear. Should it allow some sort of character combining/overstrike as it has sometimes done historically (nroff/less)? e.g a\b` as an alternative combiner or bolding if same char #the purpose of backspace (or line cr) in embedded text is unclear. Should it allow some sort of character combining/overstrike as it has sometimes done historically (nroff/less)? e.g a\b` as an alternative combiner or bolding if same char
#This should presumably only be done if the over_strike (os) capability is enabled in the terminal. Either way - it presumably won't affect printing width? #This should presumably only be done if the over_strike (os) capability is enabled in the terminal. Either way - it presumably won't affect printing width?
set line [punk::ansi::ansistrip $line] set line [punk::ansi::ansistrip $line]
#ANSI (e.g PM/SOS) can contain \b or \n or \t but won't contribute to length
#ansistrip must come before any other processing of these chars.
#we can't use simple \b processing if we get ansi codes and aren't actually processing them (e.g moves) #we can't use simple \b processing if we get ansi codes and aren't actually processing them (e.g moves)
set line [punk::char::strip_nonprinting_ascii $line] ;#only strip nonprinting after ansistrip - some like BEL are part of ansi set line [punk::char::strip_nonprinting_ascii $line] ;#only strip nonprinting after ansistrip - some like BEL are part of ansi
@ -3748,6 +3839,7 @@ tcl::namespace::eval punk::ansi {
-filter_fg 0\ -filter_fg 0\
-filter_bg 0\ -filter_bg 0\
-filter_reset 0\ -filter_reset 0\
-info 0\
] ]
#codes *must* already have been split so that one esc per element in codelist #codes *must* already have been split so that one esc per element in codelist
@ -3760,7 +3852,8 @@ tcl::namespace::eval punk::ansi {
set opts $defaultopts_sgr_merge_singles set opts $defaultopts_sgr_merge_singles
foreach {k v} $args { foreach {k v} $args {
switch -- $k { switch -- $k {
-filter_fg - -filter_bg - -filter_reset { -filter_fg - -filter_bg - -filter_reset -
-info {
tcl::dict::set opts $k $v tcl::dict::set opts $k $v
} }
default { default {
@ -4139,19 +4232,24 @@ tcl::namespace::eval punk::ansi {
set codemerge [tcl::string::trimright $codemerge {;}] set codemerge [tcl::string::trimright $codemerge {;}]
if {$unmergeable ne ""} { if {$unmergeable ne ""} {
set unmergeable [tcl::string::trimright $unmergeable {;}] set unmergeable [tcl::string::trimright $unmergeable {;}]
return "\x1b\[${codemerge}m\x1b\[${unmergeable}m[join $othercodes ""]" set mergeresult "\x1b\[${codemerge}m\x1b\[${unmergeable}m[join $othercodes ""]"
} else { } else {
return "\x1b\[${codemerge}m[join $othercodes ""]" set mergeresult "\x1b\[${codemerge}m[join $othercodes ""]"
} }
} else { } else {
if {$unmergeable eq ""} { if {$unmergeable eq ""} {
#there were no SGR codes - not even resets #there were no SGR codes - not even resets
return [join $othercodes ""] set mergeresult [join $othercodes ""]
} else { } else {
set unmergeable [tcl::string::trimright $unmergeable {;}] set unmergeable [tcl::string::trimright $unmergeable {;}]
return "\x1b\[${unmergeable}m[join $othercodes ""]" set mergeresult "\x1b\[${unmergeable}m[join $othercodes ""]"
} }
} }
if {[tcl::dict::get $opts -info]} {
return [dict create sgr $codemerge unmergeable $unmergeable othercodes $othercodes mergeresult $mergeresult codestate $codestate]
} else {
return $mergeresult
}
} }
#has_sgr_reset - rather than support this function - create an sgr normalize function that removes dead params and brings reset to front of param list? #has_sgr_reset - rather than support this function - create an sgr normalize function that removes dead params and brings reset to front of param list?
@ -4240,7 +4338,7 @@ tcl::namespace::eval punk::ansi::ta {
#we also need to track the start of ST terminated code and not add it for replay (in the ansistring functions) #we also need to track the start of ST terminated code and not add it for replay (in the ansistring functions)
#variable re_ST {(?:\x1bX|\u0098|\x1b\^|\u009E|\x1b_|\u009F)(?:[^\x1b\007\u009c]*)(?:\x1b\\|\007|\u009c)} ;#downsides: early terminating with nests, mixes 7bit 8bit start/ends (does that exist in the wild?) #variable re_ST {(?:\x1bX|\u0098|\x1b\^|\u009E|\x1b_|\u009F)(?:[^\x1b\007\u009c]*)(?:\x1b\\|\007|\u009c)} ;#downsides: early terminating with nests, mixes 7bit 8bit start/ends (does that exist in the wild?)
#keep our 8bit/7bit start-end codes separate #keep our 8bit/7bit start-end codes separate
variable re_ST {(?:\x1bP|\x1bX|\x1b\^|\x1b_)(?:(?!\x1b\\|007).)*(?:\x1b\\|\007)|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)} variable re_ST {(?:\x1bP|\x1bX|\x1b\^|\x1b_)(?:(?!\x1b\\|\007).)*(?:\x1b\\|\007)|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)}
@ -4252,7 +4350,7 @@ tcl::namespace::eval punk::ansi::ta {
# -- --- --- --- # -- --- --- ---
#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.
variable re_ansi_detect {(?:\x1b(?:\((?:0|B)|\[(?:[\x20-\x2f\x30-\x3f]*[\x40-\x7e])|\](?:(?:[^\007]*)\007|(?:(?!\x1b\\).)*\x1b\\)|(?:P|X|\^|_)(?:(?:(?!\x1b\\|007).)*(?:\x1b\\|\007))|c|7|8|M|E|D|H|=|>|(?:#(?:3|4|5|6|8))))|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)|(?:\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]|(?:\u009d)(?:[^\u009c]*)?\u009c} variable re_ansi_detect {(?:\x1b(?:\((?:0|B)|\[(?:[\x20-\x2f\x30-\x3f]*[\x40-\x7e])|\](?:(?:[^\007]*)\007|(?:(?!\x1b\\).)*\x1b\\)|(?:P|X|\^|_)(?:(?:(?!\x1b\\|\007).)*(?:\x1b\\|\007))|c|7|8|M|E|D|H|=|>|(?:#(?:3|4|5|6|8))))|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)|(?:\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]|(?:\u009d)(?:[^\u009c]*)?\u009c}
# -- --- --- --- # -- --- --- ---
@ -5674,7 +5772,12 @@ tcl::namespace::eval punk::ansi::ansistring {
ENQ [list \x05 \u2405]\ ENQ [list \x05 \u2405]\
ACK [list \x06 \u2406]\ ACK [list \x06 \u2406]\
BEL [list \x07 \u2407]\ BEL [list \x07 \u2407]\
BS [list \x08 \u2408]\
HT [list \x09 \u2409]\
LF [list \x0a \u240a]\
VT [list \x0b \u240b]\
FF [list \x0c \u240c]\ FF [list \x0c \u240c]\
CR [list \x0d \u240d]\
SO [list \x0e \u240e]\ SO [list \x0e \u240e]\
SF [list \x0f \u240f]\ SF [list \x0f \u240f]\
DLE [list \x10 \u2410]\ DLE [list \x10 \u2410]\
@ -5688,12 +5791,15 @@ tcl::namespace::eval punk::ansi::ansistring {
CAN [list \x18 \u2418]\ CAN [list \x18 \u2418]\
EM [list \x19 \u2419]\ EM [list \x19 \u2419]\
SUB [list \x1a \u241a]\ SUB [list \x1a \u241a]\
ESC [list \x1b \u241b]\
FS [list \x1c \u241c]\ FS [list \x1c \u241c]\
GS [list \x1d \u241d]\ GS [list \x1d \u241d]\
RS [list \x1e \u241e]\ RS [list \x1e \u241e]\
US [list \x1f \u241f]\ US [list \x1f \u241f]\
SP [list \x20 \u2420]\
DEL [list \x7f \u2421]\ DEL [list \x7f \u2421]\
] ]
#alternate symbols for space #alternate symbols for space
# \u2422 Blank Symbol (b with forwardslash overly) # \u2422 Blank Symbol (b with forwardslash overly)
# \u2423 Open Box (square bracket facing up like a tray/box) # \u2423 Open Box (square bracket facing up like a tray/box)
@ -5836,6 +5942,7 @@ tcl::namespace::eval punk::ansi::ansistring {
-cr 1\ -cr 1\
-lf 0\ -lf 0\
-vt 0\ -vt 0\
-ff 1\
-ht 1\ -ht 1\
-bs 1\ -bs 1\
-sp 1\ -sp 1\
@ -5850,16 +5957,22 @@ tcl::namespace::eval punk::ansi::ansistring {
set opt_cr [tcl::dict::get $opts -cr] set opt_cr [tcl::dict::get $opts -cr]
set opt_lf [tcl::dict::get $opts -lf] set opt_lf [tcl::dict::get $opts -lf]
set opt_vt [tcl::dict::get $opts -vt] set opt_vt [tcl::dict::get $opts -vt]
set opt_ff [tcl::dict::get $opts -ff]
set opt_ht [tcl::dict::get $opts -ht] set opt_ht [tcl::dict::get $opts -ht]
set opt_bs [tcl::dict::get $opts -bs] set opt_bs [tcl::dict::get $opts -bs]
set opt_sp [tcl::dict::get $opts -sp] set opt_sp [tcl::dict::get $opts -sp]
# -- --- --- --- --- # -- --- --- --- ---
# -lf 2, -vt 2 and -ff 2 are useful for CRM mode (Show Control Character Mode) in the terminal - where a newline is expected to display after the character.
set visuals_opt $debug_visuals set visuals_opt $debug_visuals
set visuals_opt [dict remove $visuals_opt CR ESC LF VT FF HT BS SP]
if {$opt_esc} { if {$opt_esc} {
tcl::dict::set visuals_opt ESC [list \x1b \u241b] tcl::dict::set visuals_opt ESC [list \x1b \u241b]
} else {
tcl::dict::unset visuals_opt ESC
} }
if {$opt_cr} { if {$opt_cr} {
tcl::dict::set visuals_opt CR [list \x0d \u240d] tcl::dict::set visuals_opt CR [list \x0d \u240d]
@ -5870,9 +5983,20 @@ tcl::namespace::eval punk::ansi::ansistring {
if {$opt_lf == 2} { if {$opt_lf == 2} {
tcl::dict::set visuals_opt LF [list \x0a \u240a\n] tcl::dict::set visuals_opt LF [list \x0a \u240a\n]
} }
if {$opt_vt} { if {$opt_vt == 1} {
tcl::dict::set visuals_opt VT [list \x0b \u240b] tcl::dict::set visuals_opt VT [list \x0b \u240b]
} }
if {$opt_vt == 2} {
tcl::dict::set visuals_opt VT [list \x0b \u240b\n]
}
switch -exact -- $opt_ff {
1 {
tcl::dict::set visuals_opt FF [list \x0c \u240c]
}
2 {
tcl::dict::set visuals_opt FF [list \x0c \u240c\n]
}
}
if {$opt_ht} { if {$opt_ht} {
tcl::dict::set visuals_opt HT [list \x09 \u2409] tcl::dict::set visuals_opt HT [list \x09 \u2409]
} }

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

@ -552,13 +552,26 @@ tcl::namespace::eval punk::char {
string eq $s [encoding convertto $encname [encoding convertfrom $encname $s]] string eq $s [encoding convertto $encname [encoding convertfrom $encname $s]]
} }
} else { } else {
#review - use -profile?
proc encodable "s {enc [encoding system]}" { proc encodable "s {enc [encoding system]}" {
set encname [encname $enc] set encname [encname $enc]
string eq $s [encoding convertfrom $encname [encoding convertto $encname $s]] if {![catch {
string eq $s [encoding convertfrom $encname [encoding convertto $encname $s]]
} result]} {
return $result
} else {
return 0
}
} }
proc decodable "s {enc [encoding system]}" { proc decodable "s {enc [encoding system]}" {
set encname [encname $enc] set encname [encname $enc]
string eq $s [encoding convertto $encname [encoding convertfrom $encname $s]] if {![catch {
string eq $s [encoding convertto $encname [encoding convertfrom $encname $s]]
} result]} {
return $result
} else {
return 0
}
} }
} }
#-- --- --- --- --- --- --- --- #-- --- --- --- --- --- --- ---

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

@ -13,11 +13,51 @@
# @@ Meta End # @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[manpage_begin punkshell_module_punk::console 0 0.1.1]
#[copyright "2024"]
#[titledesc {punk console}] [comment {-- Name section and table of contents description --}]
#[moddesc {punk console}] [comment {-- Description at end of page heading --}]
#[require punk::console]
#[keywords module console terminal]
#[description]
#[para]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Overview]
#[para] overview of punk::console
#[subsection Concepts]
#[para]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements ## Requirements
##e.g package require frobz # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection dependencies]
#[para] packages used by punk::console
#[list_begin itemized]
package require Tcl 8.6-
package require punk::ansi package require punk::ansi
#*** !doctools
#[item] [package {Tcl 8.6-}]
#[item] [package {punk::ansi}]
#*** !doctools
#[list_end]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section API]
#if {"windows" eq $::tcl_platform(platform)} { #if {"windows" eq $::tcl_platform(platform)} {
@ -30,6 +70,13 @@ package require punk::ansi
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval punk::console { namespace eval punk::console {
#*** !doctools
#[subsection {Namespace punk::console}]
#[para]
#*** !doctools
#[list_begin definitions]
variable tabwidth 8 ;#default only - will attempt to detect and set to that configured in terminal variable tabwidth 8 ;#default only - will attempt to detect and set to that configured in terminal
#Note that windows terminal cooked mode seems to use 8 for interactive use even if set differently #Note that windows terminal cooked mode seems to use 8 for interactive use even if set differently
#e.g typing tab characters may still be echoed 8-spaced while writing to stdout my obey the terminal's tab stops. #e.g typing tab characters may still be echoed 8-spaced while writing to stdout my obey the terminal's tab stops.
@ -1028,23 +1075,37 @@ namespace eval punk::console {
return [split [get_cursor_pos $inoutchannels] ";"] return [split [get_cursor_pos $inoutchannels] ";"]
} }
#todo - determine cursor on/off state before the call to restore properly. May only be possible #todo - determine cursor on/off state before the call to restore properly.
proc get_size {{inoutchannels {stdin stdout}}} { proc get_size {{inoutchannels {stdin stdout}}} {
lassign $inoutchannels in out lassign $inoutchannels in out
#we can't reliably use [chan names] for stdin,stdout. There could be stacked channels and they may have a names such as file22fb27fe810 #we can't reliably use [chan names] for stdin,stdout. There could be stacked channels and they may have a names such as file22fb27fe810
#chan eof is faster whether chan exists or not than #chan eof is faster whether chan exists or not than
if {[catch {chan eof $in} is_eof]} { if {[catch {chan eof $out} is_eof]} {
error "punk::console::get_size input channel $in seems to be closed ([info level 1])" error "punk::console::get_size output channel $out seems to be closed ([info level 1])"
} else { } else {
if {$is_eof} { if {$is_eof} {
error "punk::console::get_size eof on input channel $in ([info level 1])" error "punk::console::get_size eof on output channel $out ([info level 1])"
} }
} }
if {[catch {chan eof $out} is_eof]} { #we don't need to care about the input channel if chan configure on the output can give us the info.
error "punk::console::get_size output channel $out seems to be closed ([info level 1])" #short circuit ansi cursor movement method if chan configure supports the -winsize value
set outconf [chan configure $out]
if {[dict exists $outconf -winsize]} {
#this mechanism is much faster than ansi cursor movements
#REVIEW check if any x-platform anomalies with this method?
#can -winsize key exist but contain erroneous info? We will check that we get 2 ints at least
lassign [dict get $outconf -winsize] cols lines
if {[string is integer -strict $cols] && [string is integer -strict $lines]} {
return [list columns $cols rows $lines]
}
#continue on to ansi mechanism if we didn't get 2 ints
}
if {[catch {chan eof $in} is_eof]} {
error "punk::console::get_size input channel $in seems to be closed ([info level 1])"
} else { } else {
if {$is_eof} { if {$is_eof} {
error "punk::console::get_size eof on output channel $out ([info level 1])" error "punk::console::get_size eof on input channel $in ([info level 1])"
} }
} }
@ -1067,18 +1128,28 @@ namespace eval punk::console {
} }
} }
#faster - but uses cursor_save - which we may want to avoid if calling during another operation which uses cursor save/restore #faster than get_size when it is using ansi mechanism - but uses cursor_save - which we may want to avoid if calling during another operation which uses cursor save/restore
proc get_size_cursorrestore {} { proc get_size_cursorrestore {{inoutchannels {stdin stdout}}} {
lassign $inoutchannels in out
#we use the same shortcircuit mechanism as get_size to avoid ansi at all if the output channel will give us the info directly
set outconf [chan configure $out]
if {[dict exists $outconf -winsize]} {
lassign [dict get $outconf -winsize] cols lines
if {[string is integer -strict $cols] && [string is integer -strict $lines]} {
return [list columns $cols rows $lines]
}
}
if {[catch { if {[catch {
#some terminals (conemu on windows) scroll the viewport when we make a big move down like this - a move to 1 1 immediately after cursor_save doesn't seem to fix that. #some terminals (conemu on windows) scroll the viewport when we make a big move down like this - a move to 1 1 immediately after cursor_save doesn't seem to fix that.
#This issue also occurs when switching back from the alternate screen buffer - so perhaps that needs to be addressed elsewhere. #This issue also occurs when switching back from the alternate screen buffer - so perhaps that needs to be addressed elsewhere.
puts -nonewline [punk::ansi::cursor_off][punk::ansi::cursor_save_dec][punk::ansi::move 2000 2000] puts -nonewline $out [punk::ansi::cursor_off][punk::ansi::cursor_save_dec][punk::ansi::move 2000 2000]
lassign [get_cursor_pos_list] lines cols lassign [get_cursor_pos_list $inoutchannels] lines cols
puts -nonewline [punk::ansi::cursor_restore][punk::console::cursor_on];flush stdout puts -nonewline $out [punk::ansi::cursor_restore][punk::console::cursor_on];flush $out
set result [list columns $cols rows $lines] set result [list columns $cols rows $lines]
} errM]} { } errM]} {
puts -nonewline [punk::ansi::cursor_restore_dec] puts -nonewline $out [punk::ansi::cursor_restore_dec]
puts -nonewline [punk::ansi::cursor_on] puts -nonewline $out [punk::ansi::cursor_on]
error "$errM" error "$errM"
} else { } else {
return $result return $result
@ -1803,6 +1874,9 @@ namespace eval punk::console {
} }
#run the test and allow warnings to be emitted to stderr on package load. User should know the terminal and/or Tcl version are not optimal for unicode character work #run the test and allow warnings to be emitted to stderr on package load. User should know the terminal and/or Tcl version are not optimal for unicode character work
#set testresult [test1] #set testresult [test1]
#*** !doctools
#[list_end] [comment {--- end definitions namespace punk::console ---}]
} }
@ -1826,3 +1900,6 @@ package provide punk::console [namespace eval punk::console {
set version 0.1.1 set version 0.1.1
}] }]
return return
#*** !doctools
#[manpage_end]

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

@ -967,7 +967,7 @@ namespace eval punk::du {
dict set effective_opts -with_times $timed_types dict set effective_opts -with_times $timed_types
dict set effective_opts -with_sizes $sized_types dict set effective_opts -with_sizes $sized_types
return [list dirs $dirs vfsmounts $vfsmounts links $links files $files filesizes [dict get $mdata_lists fsizes] sizes [dict get $mdata_lists allsizes] times [dict get $mdata_lists alltimes] flaggedhidden {} flaggedsystem {} flaggedreadonly {} altname {} opts $effective_opts errors $errors] return [list dirs $dirs vfsmounts $vfsmounts links $links files $files filesizes [dict get $mdata_lists fsizes] sizes [dict get $mdata_lists allsizes] times [dict get $mdata_lists alltimes] flaggedhidden $flaggedhidden flaggedsystem {} flaggedreadonly {} altname {} opts $effective_opts errors $errors]
} }
#zipfs attributes/behaviour fairly different to tclvfs - keep separate #zipfs attributes/behaviour fairly different to tclvfs - keep separate

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

@ -328,7 +328,17 @@ tcl::namespace::eval punk::lib::compat {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval punk::lib { namespace eval punk::lib {
tcl::namespace::export * tcl::namespace::export *
#variable xyz variable has_struct_list
set has_struct_list [expr {![catch {package require struct::list}]}]
variable has_struct_set
set has_struct_set [expr {![catch {package require struct::set}]}]
variable has_punk_ansi
set has_punk_ansi [expr {![catch {package require punk::ansi}]}]
set has_twapi 0
if {"windows" eq $::tcl_platform(platform)} {
set has_twapi [expr {![catch {package require twapi}]}]
}
#*** !doctools #*** !doctools
#[subsection {Namespace punk::lib}] #[subsection {Namespace punk::lib}]
@ -614,7 +624,9 @@ namespace eval punk::lib {
} }
proc pdict {args} { proc pdict {args} {
if {[catch {package require punk::ansi} errM]} { package require punk::args
variable has_punk_ansi
if {!$has_punk_ansi} {
set sep " = " set sep " = "
} else { } else {
#set sep " [a+ Web-seagreen]=[a] " #set sep " [a+ Web-seagreen]=[a] "
@ -691,14 +703,15 @@ namespace eval punk::lib {
# - Copy proc and attempt rework so we can get back to this as a baseline for functionality # - 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) proc showdict {args} { ;# analogous to parray (except that it takes the dict as a value)
#set sep " [a+ Web-seagreen]=[a] " #set sep " [a+ Web-seagreen]=[a] "
if {[catch {package require punk::ansi} errM]} { variable has_punk_ansi
set sep " = " if {!$has_punk_ansi} {
set RST "" set RST ""
set sep " = "
set sep_mismatch " mismatch " set sep_mismatch " mismatch "
} else { } else {
set sep " [punk::ansi::a+ Green]=[punk::ansi::a] " ;#stick to basic default colours for wider terminal support
set RST [punk::ansi::a] set RST [punk::ansi::a]
set sep_mismatch " [punk::ansi::a+ Brightred undercurly underline undt-white]mismatch[punk::ansi::a] " set sep " [punk::ansi::a+ Green]=$RST " ;#stick to basic default colours for wider terminal support
set sep_mismatch " [punk::ansi::a+ Brightred undercurly underline undt-white]mismatch$RST "
} }
package require punk ;#we need pipeline pattern matching features package require punk ;#we need pipeline pattern matching features
package require textblock package require textblock
@ -836,7 +849,7 @@ namespace eval punk::lib {
lappend keyset_structure dict lappend keyset_structure dict
} }
@* { @* {
puts ---->HERE<---- #puts "showdict ---->@*<----"
dict set pattern_this_structure $p list dict set pattern_this_structure $p list
set keys [punk::lib::range 0 [llength $dval]-1] set keys [punk::lib::range 0 [llength $dval]-1]
lappend keyset {*}$keys lappend keyset {*}$keys
@ -1405,16 +1418,29 @@ namespace eval punk::lib {
} }
proc is_list_all_in_list {small large} { proc is_list_all_in_list {small large} {
package require struct::list
package require struct::set
set small_in_large [lsort [struct::set intersect [lsort -unique $small] $large ]] set small_in_large [lsort [struct::set intersect [lsort -unique $small] $large ]]
return [struct::list equal [lsort $small] $small_in_large] return [struct::list equal [lsort $small] $small_in_large]
} }
if {!$has_struct_list || !$has_struct_set} {
set body {
package require struct::list
package require struct::set
}
append body [info body is_list_all_in_list]
proc is_list_all_in_list {small large} $body
}
proc is_list_all_ni_list {a b} { proc is_list_all_ni_list {a b} {
package require struct::set
set i [struct::set intersect $a $b] set i [struct::set intersect $a $b]
return [expr {[llength $i] == 0}] return [expr {[llength $i] == 0}]
} }
if {!$has_struct_set} {
set body {
package require struct::list
}
append body [info body is_list_all_ni_list]
proc is_list_all_ni_list {a b} $body
}
#somewhat like struct::set difference - but order preserving, and doesn't treat as a 'set' so preserves dupes in fromlist #somewhat like struct::set difference - but order preserving, and doesn't treat as a 'set' so preserves dupes in fromlist
#struct::set difference may happen to preserve ordering when items are integers, but order can't be relied on, #struct::set difference may happen to preserve ordering when items are integers, but order can't be relied on,
@ -1465,18 +1491,22 @@ namespace eval punk::lib {
return [array names tmp] return [array names tmp]
} }
package require struct::set #default/fallback implementation
if {[struct::set equal [struct::set union {a a} {}] {a}]} { proc lunique_unordered {list} {
proc lunique_unordered {list} { lunique $list
struct::set union $list {} }
} if {$has_struct_set} {
} else { if {[struct::set equal [struct::set union {a a} {}] {a}]} {
puts stderr "WARNING: struct::set union <list> <emptylist> no longer dedupes!" proc lunique_unordered {list} {
#we could also test a sequence of: struct::set add struct::set union $list {}
proc lunique_unordered {list} { }
tailcall lunique $list } else {
puts stderr "WARNING: struct::set union <list> <emptylist> no longer dedupes!"
#we could also test a sequence of: struct::set add
} }
} }
#order-preserving #order-preserving
proc lunique {list} { proc lunique {list} {
set new {} set new {}
@ -1863,14 +1893,14 @@ namespace eval punk::lib {
set opt_empty [tcl::dict::get $opts -empty_as_hex] set opt_empty [tcl::dict::get $opts -empty_as_hex]
# -- --- --- --- # -- --- --- ---
set list_largeHex [lmap h $list_largeHex[unset list_largeHex] {string map [list _ ""] [string trim $h]}] set list_largeHex [lmap h $list_largeHex[unset list_largeHex] {string map {_ ""} [string trim $h]}]
if {$opt_validate} { if {$opt_validate} {
#Note appended F so that we accept list of empty strings as per the documentation #Note appended F so that we accept list of empty strings as per the documentation
if {![string is xdigit -strict [join $list_largeHex ""]F ]} { if {![string is xdigit -strict [join $list_largeHex ""]F ]} {
error "[namespace current]::hex2dec error: non-hex digits encountered after stripping underscores and leading/trailing whitespace for each element\n $list_largeHex" error "[namespace current]::hex2dec error: non-hex digits encountered after stripping underscores and leading/trailing whitespace for each element\n $list_largeHex"
} }
} }
if {![string is xdigit -strict [string map [list _ ""] $opt_empty]]} { if {![string is xdigit -strict [string map {_ ""} $opt_empty]]} {
#mapping empty string to a value destroys any advantage of -scanonly #mapping empty string to a value destroys any advantage of -scanonly
#todo - document that -scanonly has 2 restrictions - each element must be valid hex and less than 7 chars long #todo - document that -scanonly has 2 restrictions - each element must be valid hex and less than 7 chars long
#set list_largeHex [lmap v $list_largeHex[set list_largeHex {}] {expr {$v eq ""} ? {0} : {[set v]}}] #set list_largeHex [lmap v $list_largeHex[set list_largeHex {}] {expr {$v eq ""} ? {0} : {[set v]}}]
@ -1878,7 +1908,7 @@ namespace eval punk::lib {
error "[namespace current]::hex2dec error: empty values in list cannot be mapped to non-hex $opt_empty" error "[namespace current]::hex2dec error: empty values in list cannot be mapped to non-hex $opt_empty"
} }
} else { } else {
set opt_empty [string trim [string map [list _ ""] $opt_empty]] set opt_empty [string trim [string map {_ ""} $opt_empty]]
if {[set first_empty [lsearch $list_largeHex ""]] >= 0} { if {[set first_empty [lsearch $list_largeHex ""]] >= 0} {
#set list_largeHex [lmap v $list_largeHex[set list_largeHex {}] {expr {$v eq ""} ? {$opt_empty} : {$v}}] #set list_largeHex [lmap v $list_largeHex[set list_largeHex {}] {expr {$v eq ""} ? {$opt_empty} : {$v}}]
set nonempty_head [lrange $list_largeHex 0 $first_empty-1] set nonempty_head [lrange $list_largeHex 0 $first_empty-1]
@ -1931,13 +1961,13 @@ namespace eval punk::lib {
} }
set fmt "%${opt_width}.${opt_width}ll${spec}" set fmt "%${opt_width}.${opt_width}ll${spec}"
set list_decimals [lmap d $list_decimals[unset list_decimals] {string map [list _ ""] [string trim $d]}] set list_decimals [lmap d $list_decimals[unset list_decimals] {string map {_ ""} [string trim $d]}]
if {![string is digit -strict [string map [list _ ""] $opt_empty]]} { if {![string is digit -strict [string map {_ ""} $opt_empty]]} {
if {[lsearch $list_decimals ""] >=0} { if {[lsearch $list_decimals ""] >=0} {
error "[namespace current]::dec2hex error: empty values in list cannot be mapped to non-decimal $opt_empty" error "[namespace current]::dec2hex error: empty values in list cannot be mapped to non-decimal $opt_empty"
} }
} else { } else {
set opt_empty [string map [list _ ""] $opt_empty] set opt_empty [string map {_ ""} $opt_empty]
if {[set first_empty [lsearch $list_decimals ""]] >= 0} { if {[set first_empty [lsearch $list_decimals ""]] >= 0} {
set nonempty_head [lrange $list_decimals 0 $first_empty-1] set nonempty_head [lrange $list_decimals 0 $first_empty-1]
set list_decimals [concat $nonempty_head [lmap v [lrange $list_decimals $first_empty end] {expr {$v eq ""} ? {$opt_empty} : {$v}}]] set list_decimals [concat $nonempty_head [lmap v [lrange $list_decimals $first_empty end] {expr {$v eq ""} ? {$opt_empty} : {$v}}]]
@ -2402,13 +2432,14 @@ namespace eval punk::lib {
# important for pipeline & match_assign # important for pipeline & match_assign
# -line trimline|trimleft|trimright -block trimhead|trimtail|triminner|trimall|trimhead1|trimtail1|collateempty -commandprefix {string length} ? # -line trimline|trimleft|trimright -block trimhead|trimtail|triminner|trimall|trimhead1|trimtail1|collateempty -commandprefix {string length} ?
# -block trimming only trims completely empty lines. use -line trimming to remove whitespace e.g -line trimright will clear empty lines without affecting leading whitespace on other lines that aren't pure whitespace # -block trimming only trims completely empty lines. use -line trimming to remove whitespace e.g -line trimright will clear empty lines without affecting leading whitespace on other lines that aren't pure whitespace
proc linelist {args} {
set linelist_body {
set usage "linelist ?-line trimline|trimleft|trimright? ?-block trimhead|trimtail|triminner|trimall|trimhead1|trimtail1|collateempty? -commandprefix <cmdlist> text" set usage "linelist ?-line trimline|trimleft|trimright? ?-block trimhead|trimtail|triminner|trimall|trimhead1|trimtail1|collateempty? -commandprefix <cmdlist> text"
if {[llength $args] == 0} { if {[llength $args] == 0} {
error "linelist missing textchunk argument usage:$usage" error "linelist missing textchunk argument usage:$usage"
} }
set text [lindex $args end] set text [lindex $args end]
set text [string map [list \r\n \n] $text] ;#review - option? set text [string map {\r\n \n} $text] ;#review - option?
set arglist [lrange $args 0 end-1] set arglist [lrange $args 0 end-1]
set opts [tcl::dict::create\ set opts [tcl::dict::create\
@ -2441,10 +2472,10 @@ namespace eval punk::lib {
} }
} }
#normalize certain combos #normalize certain combos
if {[set posn [lsearch $opt_block trimhead1]] >=0 && "trimhead" in $opt_block} { if {"trimhead" in $opt_block && [set posn [lsearch $opt_block trimhead1]] >=0} {
set opt_block [lreplace $opt_block $posn $posn] set opt_block [lreplace $opt_block $posn $posn]
} }
if {[set posn [lsearch $opt_block trimtail1]] >=0 && "trimtail" in $opt_block} { if {"trimtail" in $opt_block && [set posn [lsearch $opt_block trimtail1]] >=0} {
set opt_block [lreplace $opt_block $posn $posn] set opt_block [lreplace $opt_block $posn $posn]
} }
if {"trimall" in $opt_block} { if {"trimall" in $opt_block} {
@ -2594,9 +2625,10 @@ namespace eval punk::lib {
#Each resulting line should have a reset of some type at start and a pure-reset at end to stop #Each resulting line should have a reset of some type at start and a pure-reset at end to stop
#see if we can find an ST sequence that most terminals will not display for marking sections? #see if we can find an ST sequence that most terminals will not display for marking sections?
if {$opt_ansireplays} { if {$opt_ansireplays} {
package require punk::ansi #package require punk::ansi
<require_punk_ansi>
if {$opt_ansiresets} { if {$opt_ansiresets} {
set RST [punk::ansi::a] set RST "\x1b\[0m"
} else { } else {
set RST "" set RST ""
} }
@ -2721,6 +2753,15 @@ namespace eval punk::lib {
return $linelist return $linelist
} }
if {$has_punk_ansi} {
#optimise linelist as much as possible
set linelist_body [string map {<require_punk_ansi> ""} $linelist_body]
} else {
#punk ansi not avail at time of package load.
#by putting in calls to punk::ansi the user will get appropriate error messages
set linelist_body [string map {<require_punk_ansi> "package require punk::ansi"} $linelist_body]
}
proc linelist {args} $linelist_body
interp alias {} errortime {} punk::lib::errortime interp alias {} errortime {} punk::lib::errortime
@ -2846,6 +2887,133 @@ namespace eval punk::lib {
proc temperature_c_to_f {deg_celsius} { proc temperature_c_to_f {deg_celsius} {
return [expr {($deg_celsius * (9/5.0)) + 32}] return [expr {($deg_celsius * (9/5.0)) + 32}]
} }
proc interp_sync_package_paths {interp} {
if {![interp exists $interp]} {
error "interp_sync_package_paths error. interp '$interp' not found. Create it first with \[interp create $interp\]"
}
interp eval $interp [list set ::auto_path $::auto_path]
interp eval $interp {tcl::tm::remove {*}[tcl::tm::list]}
interp eval $interp [list tcl::tm::add {*}[lreverse [tcl::tm::list]]]
}
proc objclone {obj} {
append obj2 $obj {}
}
proc format_number {numbers_or_commaformattednumbers {delim ""} {groupsize ""}} {
variable has_twapi
if {$has_twapi} {
if {$delim eq "" && $groupsize eq ""} {
set localeid [twapi::get_system_default_lcid]
}
}
set results [list]
set nums [objclone $numbers_or_commaformattednumbers] ;#stops single num from getting internal rep of list
foreach inputnum $nums {
set number [objclone $inputnum]
#also handle tcl 8.7+ underscores in numbers
set number [string map [list _ "" , ""] $number]
#normalize e.g 2e4 -> 20000.0
set number [expr {$number}]
if {$has_twapi} {
if {$delim eq "" && $groupsize eq ""} {
lappend results [twapi::format_number $number $localeid -idigits -1]
continue
} else {
if {$delim eq ""} {set delim ","}
if {$groupsize eq ""} {set groupsize 3}
lappend results [twapi::format_number $number 0 -idigits -1 -sthousand $delim -sgrouping $groupsize]
continue
}
}
#todo - get configured user defaults
set delim ","
set groupsize 3
lappend results [delimit_number $number $delim $groupsize]
}
if {[llength $results] == 1} {
#keep intrep as string rather than list
return [lindex $results 0]
}
return $results
}
#from wiki https://wiki.tcl-lang.org/page/Delimiting+Numberse
# Given a number represented as a string, insert delimiters to break it up for
# readability. Normally, the delimiter will be a comma which will be inserted every
# three digits. However, the delimiter and groupsize are optional arguments,
# permitting use in other locales.
#
# The string is assumed to consist of digits, possibly preceded by spaces,
# and possibly containing a decimal point, i.e.: [:space:]*[:digit:]*\.[:digit:]*
proc delimit_number {unformattednumber {delim ","} {GroupSize 3}} {
set number [punk::objclone $unformattednumber]
set number [string map {_ ""} $number]
#normalize using expr - e.g 2e4 -> 20000.0
set number [expr {$number}]
# First, extract right hand part of number, up to and including decimal point
set point [string last "." $number];
if {$point >= 0} {
set PostDecimal [string range $number [expr $point + 1] end];
set PostDecimalP 1;
} else {
set point [expr [string length $number] + 1]
set PostDecimal "";
set PostDecimalP 0;
}
# Now extract any leading spaces. review - regex for whitespace instead of just ascii space?
set ind 0;
while {[string equal [string index $number $ind] \u0020]} {
incr ind;
}
set FirstNonSpace $ind;
set LastSpace [expr $FirstNonSpace - 1];
set LeadingSpaces [string range $number 0 $LastSpace];
# Now extract the non-fractional part of the number, omitting leading spaces.
set MainNumber [string range $number $FirstNonSpace [expr $point -1]];
# Insert commas into the non-fractional part.
set Length [string length $MainNumber];
set Phase [expr $Length % $GroupSize]
set PhaseMinusOne [expr $Phase -1];
set DelimitedMain "";
#First we deal with the extra stuff.
if {$Phase > 0} {
append DelimitedMain [string range $MainNumber 0 $PhaseMinusOne];
}
set FirstInGroup $Phase;
set LastInGroup [expr $FirstInGroup + $GroupSize -1];
while {$LastInGroup < $Length} {
if {$FirstInGroup > 0} {
append DelimitedMain $delim;
}
append DelimitedMain [string range $MainNumber $FirstInGroup $LastInGroup];
incr FirstInGroup $GroupSize
incr LastInGroup $GroupSize
}
# Reassemble the number.
if {$PostDecimalP} {
return [format "%s%s.%s" $LeadingSpaces $DelimitedMain $PostDecimal];
} else {
return [format "%s%s" $LeadingSpaces $DelimitedMain];
}
}
#*** !doctools #*** !doctools
#[list_end] [comment {--- end definitions namespace punk::lib ---}] #[list_end] [comment {--- end definitions namespace punk::lib ---}]
} }
@ -2998,7 +3166,9 @@ tcl::namespace::eval punk::lib::system {
return [concat $smallfactors [lreverse $largefactors] $x] return [concat $smallfactors [lreverse $largefactors] $x]
} }
# incomplte - report which is the innermost bracket/quote etc awaiting completion for a Tcl command
# incomplete - report which is the innermost bracket/quote etc awaiting completion for a Tcl command
#important - used by punk::repl #important - used by punk::repl
proc incomplete {partial} { proc incomplete {partial} {
#we can apparently get away without concatenating current innerpartial to previous in list - REVIEW. #we can apparently get away without concatenating current innerpartial to previous in list - REVIEW.

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

@ -35,12 +35,14 @@ namespace eval punk::mix::base {
} }
#puts stderr "punk::mix::base extension: [string trimleft $extension :]" #puts stderr "punk::mix::base extension: [string trimleft $extension :]"
if {![string length $extension]} { if {![string length $extension]} {
#if still no extension - must have been called dirctly as punk::mix::base::_cli #if still no extension - must have been called directly as punk::mix::base::_cli
if {![llength $args]} { if {![llength $args]} {
set args "help" set args "help"
} }
set extension [namespace current] set extension [namespace current]
} }
#init usually used to load commandsets (and export their names) into the extension namespace/ensemble
${extension}::_init
if {![llength $args]} { if {![llength $args]} {
if {[info exists ${extension}::default_command]} { if {[info exists ${extension}::default_command]} {
tailcall $extension [set ${extension}::default_command] tailcall $extension [set ${extension}::default_command]

148
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/cli-0.3.1.tm

@ -31,47 +31,58 @@ namespace eval punk::mix::cli {
namespace eval temp_import { namespace eval temp_import {
} }
namespace ensemble create namespace ensemble create
variable initialised 0
package require punk::overlay #lazy _init - called by punk::mix::base::_cli when ensemble used
catch { proc _init {args} {
punk::overlay::import_commandset module . ::punk::mix::commandset::module variable initialised
} if {$initialised} {
punk::overlay::import_commandset debug . ::punk::mix::commandset::debug return
punk::overlay::import_commandset repo . ::punk::mix::commandset::repo }
punk::overlay::import_commandset lib . ::punk::mix::commandset::loadedlib puts stderr "punk::mix::cli::init $args"
package require punk::overlay
catch { namespace eval ::punk::mix::cli {
package require punk::mix::commandset::project catch {
punk::overlay::import_commandset project . ::punk::mix::commandset::project punk::overlay::import_commandset module . ::punk::mix::commandset::module
punk::overlay::import_commandset projects . ::punk::mix::commandset::project::collection }
} punk::overlay::import_commandset debug . ::punk::mix::commandset::debug
if {[catch { punk::overlay::import_commandset repo . ::punk::mix::commandset::repo
package require punk::mix::commandset::layout punk::overlay::import_commandset lib . ::punk::mix::commandset::loadedlib
punk::overlay::import_commandset project.layout . ::punk::mix::commandset::layout
punk::overlay::import_commandset project.layouts . ::punk::mix::commandset::layout::collection catch {
} errM]} { package require punk::mix::commandset::project
puts stderr "error loading punk::mix::commandset::layout" punk::overlay::import_commandset project . ::punk::mix::commandset::project
puts stderr $errM punk::overlay::import_commandset projects . ::punk::mix::commandset::project::collection
} }
if {[catch { if {[catch {
package require punk::mix::commandset::buildsuite package require punk::mix::commandset::layout
punk::overlay::import_commandset buildsuite . ::punk::mix::commandset::buildsuite punk::overlay::import_commandset project.layout . ::punk::mix::commandset::layout
punk::overlay::import_commandset buildsuites . ::punk::mix::commandset::buildsuite::collection punk::overlay::import_commandset project.layouts . ::punk::mix::commandset::layout::collection
} errM]} { } errM]} {
puts stderr "error loading punk::mix::commandset::buildsuite" puts stderr "error loading punk::mix::commandset::layout"
puts stderr $errM puts stderr $errM
} }
punk::overlay::import_commandset scriptwrap . ::punk::mix::commandset::scriptwrap if {[catch {
if {[catch { package require punk::mix::commandset::buildsuite
package require punk::mix::commandset::doc punk::overlay::import_commandset buildsuite . ::punk::mix::commandset::buildsuite
punk::overlay::import_commandset doc . ::punk::mix::commandset::doc punk::overlay::import_commandset buildsuites . ::punk::mix::commandset::buildsuite::collection
punk::overlay::import_commandset "" "" ::punk::mix::commandset::doc::collection } errM]} {
} errM]} { puts stderr "error loading punk::mix::commandset::buildsuite"
puts stderr "error loading punk::mix::commandset::doc" puts stderr $errM
puts stderr $errM }
punk::overlay::import_commandset scriptwrap . ::punk::mix::commandset::scriptwrap
if {[catch {
package require punk::mix::commandset::doc
punk::overlay::import_commandset doc . ::punk::mix::commandset::doc
punk::overlay::import_commandset "" "" ::punk::mix::commandset::doc::collection
} errM]} {
puts stderr "error loading punk::mix::commandset::doc"
puts stderr $errM
}
}
set initialised 1
} }
proc help {args} { proc help {args} {
#set basehelp [punk::mix::base::help -extension [namespace current] {*}$args] #set basehelp [punk::mix::base::help -extension [namespace current] {*}$args]
set basehelp [punk::mix::base help {*}$args] set basehelp [punk::mix::base help {*}$args]
@ -210,11 +221,12 @@ namespace eval punk::mix::cli {
proc validate_modulename {modulename args} { proc validate_modulename {modulename args} {
set opts [list\ set opts [list\
-errorprefix validate_modulename\ -errorprefix validate_modulename\
-strict 0\
] ]
if {[llength $args] %2 != 0} {error "validate_modulename args must be name-value pairs: received '$args'"} if {[llength $args] %2 != 0} {error "validate_modulename args must be name-value pairs: received '$args'"}
foreach {k v} $args { foreach {k v} $args {
switch -- $k { switch -- $k {
-errorprefix { -errorprefix - -strict {
dict set opts $k $v dict set opts $k $v
} }
default { default {
@ -223,8 +235,14 @@ namespace eval punk::mix::cli {
} }
} }
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- --- ---
set opt_errorprefix [dict get $opts -errorprefix] set opt_errorprefix [dict get $opts -errorprefix]
set opt_strict [dict get $opts -strict]
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- --- ---
if {$opt_strict} {
if {[regexp {[A-Z]} $modulename]} {
error "$opt_errorprefix '$modulename' contains uppercase which is not recommended as per tip 590, and option -strict is set to 1"
}
}
validate_name_not_empty_or_spaced $modulename -errorprefix $opt_errorprefix validate_name_not_empty_or_spaced $modulename -errorprefix $opt_errorprefix
set testname [string map {:: {}} $modulename] set testname [string map {:: {}} $modulename]
@ -239,6 +257,56 @@ namespace eval punk::mix::cli {
} }
return $modulename return $modulename
} }
proc confirm_modulename {modulename} {
set finalised 0
set aborted 0
while {!$finalised && !$aborted} {
#first validate with -strict 0 to confirm acceptable while ignoring case issues.
#uppercase is generally valid but not recommended - so has separate prompting.
if {[catch {validate_modulename $modulename -strict 0} errM]} {
set msg "Chosen name didn't pass validation\n"
append msg "reason: $errM\n"
append msg "Please retype the modulename. You will be given a further prompt to confirm or abort."
set modulename [util::askuser $msg]
} elseif {[regexp {[A-Z]} $modulename]} {
set msg "module names containing uppercase are not recommended (see tip 590).\n"
append msg "Please retype the module name '$modulename' to proceed.\n"
append msg "If you type it exactly as it was you will be allowed to proceed with uppercase anyway\n"
append msg "Retype it all in lowercase to use recommended naming"
set answer [util::askuser $msg]
if {[regexp {[A-Z]} $answer]} {
if {$answer eq $modulename} {
#ok - user insists
set finalised 1
} else {
#user supplied a different uppercase name - don't set finalised so we bug them again to type it two times the same way to proceed
puts stdout "A different uppercase name was supplied - reconfirmation required."
}
set modulename $answer
} else {
#user has resupplied modulename all as lowercase
if {$answer eq [string tolower $modulename]} {
set finalised 1
} else {
#.. but it doesn't match original - require rerun
}
set modulename $answer
}
} else {
set answer [util::askuser "Proceed with the module name '$modulename'? Y to continue N to abort"]
if {[string tolower $answer] eq "y"} {
set finalised 1
} else {
set aborted 1
}
}
}
if {$aborted} {
return [dict create status error reason errmsg]
} else {
return [dict create status ok modulename $modulename]
}
}
proc validate_projectname {projectname args} { proc validate_projectname {projectname args} {
set defaults [list\ set defaults [list\

30
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/doc-0.1.0.tm

@ -165,7 +165,17 @@ namespace eval punk::mix::commandset::doc {
cd $original_wd cd $original_wd
} }
proc validate {} { proc validate {args} {
set argd [punk::args::get_dict {
-- -type none -optional 1 -help "end of options marker --"
-individual -type boolean -default 1
*values -min 0 -max -1
patterns -default {*} -type any -multiple 1
} $args]
set opt_individual [tcl::dict::get $argd opts -individual]
set patterns [tcl::dict::get $argd values patterns]
#todo - run and validate punk::docgen output #todo - run and validate punk::docgen output
set projectdir [punk::repo::find_project] set projectdir [punk::repo::find_project]
if {$projectdir eq ""} { if {$projectdir eq ""} {
@ -180,7 +190,23 @@ namespace eval punk::mix::commandset::doc {
set docroot $projectdir/src/doc set docroot $projectdir/src/doc
cd $docroot cd $docroot
dtplite validate $docroot if {!$opt_individual && "*" in $patterns} {
if {[catch {
dtplite validate $docroot
} errM]} {
puts stderr "commandset::doc::validate failed for projectdir '$projectdir'"
puts stderr "docroot '$docroot'"
puts stderr "dtplite error was: $errM"
}
} else {
foreach p $patterns {
set treefiles [punk::path::treefilenames $p]
foreach path $treefiles {
puts stdout "dtplite validate $path"
dtplite validate $path
}
}
}
#punk::mix::cli::lib::kettle_call lib validate-doc #punk::mix::cli::lib::kettle_call lib validate-doc

11
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/loadedlib-0.1.0.tm

@ -179,7 +179,16 @@ namespace eval punk::mix::commandset::loadedlib {
return [join $loaded_libs \n] return [join $loaded_libs \n]
} }
proc info {libname} { proc info {args} {
set argspecs {
*values -min 1
libname -help "library/package name"
}
set argd [punk::args::get_dict $argspecs $args]
set libname [dict get $argd values libname]
if {[catch {package require natsort}]} { if {[catch {package require natsort}]} {
set has_natsort 0 set has_natsort 0
} else { } else {

24
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm

@ -204,6 +204,30 @@ namespace eval punk::mix::commandset::module {
set modulename $module set modulename $module
} }
punk::mix::cli::lib::validate_modulename $modulename -errorprefix "punk::mix::commandset::module::new" punk::mix::cli::lib::validate_modulename $modulename -errorprefix "punk::mix::commandset::module::new"
if {[regexp {[A-Z]} $module]} {
set msg "module names containing uppercase are not recommended (see tip 590).\n"
append msg "Please retype the module name '$module' to proceed.\n"
append msg "If you type it exactly as it was you will be allowed to proceed with uppercase anyway\n"
append msg "Retype it all in lowercase to use recommended naming"
set answer [util::askuser $msg]
if {[regexp {[A-Z]} $answer]} {
if {$answer eq $module} {
#ok - user insists
} else {
}
} else {
#user has resupplied modulename all as lowercase
if {$answer eq [string tolower $module]} {
set module $answer
} else {
#.. but it doesn't match original - require rerun
}
}
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
#options #options
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---

10
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/mix/commandset/project-0.1.0.tm

@ -165,7 +165,7 @@ namespace eval punk::mix::commandset::project {
#user can use dev module.new manually or supply module name in -modules #user can use dev module.new manually or supply module name in -modules
set opt_modules [list] set opt_modules [list]
} else { } else {
set opt_modules [list $projectname] set opt_modules [list [string tolower $projectname]] ;#default modules to lowercase as is the modern (tip 590) recommendation for Tcl
} }
} }
# -- --- --- --- --- --- --- --- --- --- --- --- --- # -- --- --- --- --- --- --- --- --- --- --- --- ---
@ -919,10 +919,18 @@ namespace eval punk::mix::commandset::project {
if {[llength $col_states]} { if {[llength $col_states]} {
foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes s $col_states { foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes s $col_states {
if {![file exists $wd]} {
set row [punk::ansi::a+ strike red]$row[a]
set wd [punk::ansi::a+ red]$wd[a]
}
append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode] [overtype::left $col6 $s]" \n append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode] [overtype::left $col6 $s]" \n
} }
} else { } else {
foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes { foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes {
if {![file exists $wd]} {
set row [punk::ansi::a+ strike red]$row[a]
set wd [punk::ansi::a+ red]$wd[a]
}
append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode]" \n append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode]" \n
} }
} }

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

@ -130,6 +130,7 @@ tcl::namespace::eval ::punk::overlay {
}] }]
set imported_commands [list] set imported_commands [list]
set imported_tails [list]
set nscaller [uplevel 1 [list tcl::namespace::current]] set nscaller [uplevel 1 [list tcl::namespace::current]]
if {[catch { if {[catch {
#review - noclobber? #review - noclobber?
@ -143,7 +144,10 @@ tcl::namespace::eval ::punk::overlay {
} }
rename $cmd $import_as rename $cmd $import_as
lappend imported_commands $import_as lappend imported_commands $import_as
lappend imported_tails [namespace tail $import_as]
} }
#make imported commands exported so they are available to the ensemble
tcl::namespace::eval ${nscaller} [list namespace export {*}$imported_tails]
} errM]} { } errM]} {
puts stderr "Error loading commandset $prefix $separator $cmdnamespace" puts stderr "Error loading commandset $prefix $separator $cmdnamespace"
puts stderr "err: $errM" puts stderr "err: $errM"

450
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/path-0.1.0.tm

@ -63,11 +63,11 @@ package require Tcl 8.6-
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# oo::class namespace # oo::class namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval punk::path::class { #namespace eval punk::path::class {
#*** !doctools #*** !doctools
#[subsection {Namespace punk::path::class}] #[subsection {Namespace punk::path::class}]
#[para] class definitions #[para] class definitions
if {[info commands [namespace current]::interface_sample1] eq ""} { #if {[info commands [namespace current]::interface_sample1] eq ""} {
#*** !doctools #*** !doctools
#[list_begin enumerated] #[list_begin enumerated]
@ -89,8 +89,8 @@ namespace eval punk::path::class {
#*** !doctools #*** !doctools
#[list_end] [comment {--- end class enumeration ---}] #[list_end] [comment {--- end class enumeration ---}]
} #}
} #}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
@ -105,6 +105,448 @@ namespace eval punk::path {
#[para] Core API functions for punk::path #[para] Core API functions for punk::path
#[list_begin definitions] #[list_begin definitions]
# -- ---
#punk::path::normjoin
# - simplify . and .. segments as far as possible whilst respecting specific types of root.
# -- ---
#a form of file normalize that supports //xxx to be treated as server path names
#(ie regardless of unices ignoring (generally) leading double slashes, and regardless of windows volumerelative path syntax)
#(sometimes //server.com used as a short form for urls - which doesn't seem too incompatible with this anyway)
# -- ---
#This is intended to be purely a string analysis - without reference to filesystem volumes or vfs or zipfs mountpoints etc
#
#TODO - option for caller to provide a -base below which we can't backtrack.
#This is preferable to setting policy here for example regarding forcing no trackback below //servername/share
#Our default is to allow trackback to:
# <scheme>://<something>
# <driveletter>:/
# //./<volume> (dos device volume)
# //server (while normalizing //./UNC/server to same)
# / (ordinary unix root)
# ./../<repeated> - (track back indefinitely on relpath as we are not resolving to anything physical and can't fully simplify the leading backtracks)
#
#The caller should do the file/vfs operations to determine this - not us.
# -- ---
#simplify path with respect to /./ & /../ elements - independent of platform
#NOTE: "anomalies" in standard tcl processing on windows:
#e.g file normalize {//host} -> c:/host (or e.g d:/host if we happen to be on another volume)
#file normalize {//host/share} -> //host/share
#This is because //host is treated as volume-relative in cmd/powershell and Tcl quite reasonably follows suit.
#This prevents cwd and windows commandlines from pointing to the server (above the share)
#Explorer however does allow pointing to the //server level and seeing shares as if they are directory entries.
#we are more interested in supporting the explorer-like behaviour - as while volumerelative paths are also useful on windows - they are lesser known.
#REVIEW.
#To get back to some consistent cross platform behaviour - we will treat //something as a root/volume i.e we can't backtrack above it with ".."
#note too that file split on UNC paths doesn't give a clear indication of the root
# file split //./UNC/server/share/subpath -> //./UNC server share subpath
# file split //server/share/subpath -> //server/share subpath
#TODO - disallow all change of root or change from relative path to absolute result.
#e.g normjoin relpath/../d:/secret should not return d:/secret - but ./d:/secret
# ================
#known issues:
#1)
# normjoin d://a//b//c -> d://a/b/c
# This is because we don't detect specific schemes. ie it's treated the same as https://a/b/c -> https://a/b/c
# Not considered a problem - just potentially surprising.
# To avoid it we would have to enumerate possible schemes.
# As it stands a unix system could define a 'scheme' that happens to match windows style driveletters. Consider a 'feature' ? review.
# won't fix?
#2)
# normjoin https:///real.com/../fake.com -> https:///fake.com
# The extra slash means effectively our servername is empty - this is potentially confusing but probably the right thing to do here.
# It's a concern only if upstream treats the tripple slash in this case as valid and maps it to https:// - which would probably be bad anyway.
# won't fix (review)
#3)
#similarly
# normjoin //./UNC//server/share/subpath -> ///server/share/subpath (when 2 or more slashes directly after UNC)
# normjoin ///server/share -> ///server/share
#This is effectively an empty servername in the input with 'server' being pushed one level down - and the output is consistent
# possibly won't fix - review
#4) inconsistency
# we return normalized //server/share for //./UNC/server share
# but other dos device paths are maintained
# e.g //./c:/etc
# This is because such paths could contain alternate segment names (windows shortnames) which we aren't in a position to resolve.
# caller should
# #as with 'case' below - caller will need to run a post 'file normalize'
#5) we don't normalize case like file normalize does on windows platform.
# This is intentional. It could only be done with reference to underlying filesystem which we don't want here.
#
# ================
#
#relpaths all end up with leading . - while not always the simplest form, this is ok. (helps stop inadvertent conversions to absolutes)
# Tests - TODO
# normjoin /d:/..//vfs:/test -> /vfs:/test (good - not converted to //vfs:/test)
proc normjoin {args} {
set args [lmap a $args {string map "\\\\ /" $a}]
set path [plainjoin {*}$args]
switch -exact $path {
"" {
return ""
}
/ - // {
#treated in unixlike manner - (but leading doubleslashes with subsequent data are server indication)
#// not considered a servername indicator - but /// (for consistency) is. (empty servername?)
return /
}
/// {
#if this is effectively //$emptyservername/
#then for consistency we should trail //<servername with a slash too?
#we can't transform to // or /
return ///
#assert - code below should return /// (empty server prefix) for any number of leading slashes >=3
#todo - shortcircuit that here?
}
}
# ///
set doubleslash1_posn [string first // $path]
# -- --- --- temp warning on windows only - no x-platform difference in result
#on windows //host is of type volumerelative
# whereas //host/share is of type absolute
if {"windows" eq $::tcl_platform(platform) && [file pathtype $path] eq "volumerelative"} {
#volumerelative probably only occurs on windows anyway
if {$doubleslash1_posn == 0} {
#e.g //something where no further slashes
#review - eventually get rid of this warning and require upstream to know the appropriate usecase
puts stderr "Warning - ambiguous path $path - treating as server path - not 'volumerelative'"
} else {
# /something/etc
# /mnt/c/stuff
#output will retain leading / as if on unix.
#on windows - the result would still be interpreted as volumerelative if the caller normalizes it
}
}
# -- --- ---
set is_relpath 0
#set path [string map [list \\ /] $path]
set finalparts [list]
set is_nonunc_dosdevice 0
if {[punk::winpath::is_dos_device_path $path]} {
#review
if {[string range $path 4 6] eq "UNC"} {
#convert to 'standard' //server/... path for processing
set path "/[string range $path 7 end]" ;# //server/...
} else {
#error "normjoin non-UNC dos device path '$path' not supported"
#first segment after //./ or //?/ represents the volume or drive.
#not applicable to unix - but unlikely to conflict with a genuine usecase there (review)
#we should pass through and stop navigation below //./vol
#!!!
#not anomaly in tcl (continues in tcl9)
#file exists //./c:/test -> 0
#file exists //?/c:/test -> 1
#file exists //./BootPartition/Windows -> 1
#file exists //?/BootPartition/Windows -> 0
set is_nonunc_dosdevice 1
}
}
if {$is_nonunc_dosdevice} {
#dosdevice prefix //./ or //?/ - preserve it (without trailing slash which will be put back in with join)
set prefix [string range $path 0 2]
set tail [string range $path 4 end]
set tailparts [split $tail /]
set parts [concat [list $prefix] $tailparts]
set rootindex 1 ;#disallow backtrack below //./<volume>
} else {
#note use of ordinary ::split vs file split is deliberate.
if {$doubleslash1_posn == 0} {
#this is handled differently on different platforms as far as 'file split' is concerned.
#e.g for file split //sharehost/share/path/etc
#e.g on windows: -> //sharehost/share path
#e.g on freebsd: -> / sharehost share path etc
#however..also on windows: file split //sharehost -> / sharehost
#normalize by dropping leading slash before split - and then treating first 2 segments as a root
#set parts [file split [string range $path 1 end]]
set parts [split $path /]
#assert parts here has {} {} as first 2 entries
set rootindex 2
#currently prefer can backtrack to the //zipfs:/ scheme (below the mountpoint - to browse other mounts)
#alternative handling for //zipfs:/path - don't go below mountpoint
#but we can't determine just from string if mountpoint is direct subpath or a lower one e.g //zipfs:/arbitraryname/actualmountpoint
#review - more generally //<mountmechanism>:/path ?
#todo - make an option for zipfs and others to determine the 'base'
#if {"zipfs:" eq [lindex $parts 2]} {
# set rootindex 3
#}
} else {
#path may or may not begin with a single slash here.
#treat same on unix and windows
set rootindex 0
#set parts [file split $path]
set parts [::split $path /]
#e.g /a/b/c -> {} a b c
#or relative path a/b/c -> a b c
#or c:/a/b/c -> c: a b c
if {[string match *: [lindex $parts 0]]} {
if {[lindex $parts 1] eq ""} {
#scheme://x splits to scheme: {} x
set parts [concat [list [lindex $parts 0]/] [lrange $parts 2 end]]
#e.g {scheme:/ x}
set rootindex 1 ;#disallow below first element of scheme
} else {
set rootindex 0
}
} elseif {[lindex $parts 0] ne ""} {
#relpath a/b/c
set parts [linsert $parts 0 .]
set rootindex 0
#allow backtracking arbitrarily for leading .. entries - simplify where possible
#also need to stop possible conversion to absolute path
set is_relpath 1
}
}
}
set baseparts [lrange $parts 0 $rootindex] ;#base below which we can't retreat via ".."
#puts stderr "-->baseparts:$baseparts"
#ensure that if our rootindex already spans a dotted segment (after the first one) we remove it
#must maintain initial . for relpaths to stop them converting to absolute via backtrack
#
set finalparts [list [lindex $baseparts 0]]
foreach b [lrange $baseparts 1 end] {
if {$b ni {. ..}} {
lappend finalparts $b
}
}
set baselen [expr {$rootindex + 1}]
if {$is_relpath} {
set i [expr {$rootindex+1}]
foreach p [lrange $parts $i end] {
switch -exact -- $p {
. - "" {}
.. {
switch -exact -- [lindex $finalparts end] {
. - .. {
lappend finalparts ..
}
default {
lpop finalparts
}
}
}
default {
lappend finalparts $p
}
}
incr i
}
} else {
foreach p [lrange $parts $rootindex+1 end] {
if {[llength $finalparts] <= $baselen} {
if {$p ni {. .. ""}} {
lappend finalparts $p
}
} else {
switch -exact -- $p {
. - "" {}
.. {
lpop finalparts ;#uses punk::lib::compat::lpop if on < 8.7
}
default {
lappend finalparts $p
}
}
}
}
}
puts "==>finalparts: '$finalparts'"
# using join - {"" "" server share} -> //server/share and {a b} -> a/b
if {[llength $finalparts] == 1 && [lindex $finalparts 0] eq ""} {
#backtracking on unix-style path can end up with empty string as only member of finalparts
#e.g /x/..
return /
}
set result [::join $finalparts /]
#normalize volumes and mountschemes to have trailing slash if no subpath
#e.g c: -> c:/
#//zipfs: -> //zipfs:/
if {[set lastchar [string index $result end]] eq ":"} {
if {$result eq "//zipfs:"} {
set result "//zipfs:/"
} else {
if {[string first / $result] < 0} {
set result $result/
}
}
} elseif {[string match //* $result]} {
if {![punk::winpath::is_dos_device_path $result]} {
#server
set tail [string range $result 2 end]
set tailparts [split $tail /]
if {[llength $tailparts] <=1} {
#empty // or //servername
append result /
}
}
} elseif {[llength $finalparts] == 2} {
if {[string range [lindex $finalparts 0] end-1 end] eq ":/"} {
#e.g https://server/ -> finalparts {https:/ server}
#e.g https:/// -> finalparts {https:/ ""}
#scheme based path should always return trailing slash after server component - even if server component empty.
lappend finalparts "" ;#force trailing /
return [join $finalparts /]
}
}
return $result
}
proc trim_final_slash {str} {
if {[string index $str end] eq "/"} {
return [string range $str 0 end-1]
}
return $str
}
#x-platform - punk::path::pathtype - can be used in safe interps - different concept of pathtypes to 'file pathtype'
# - no volumerelative
# - no lookup of file volumes (volume is a windows concept - but with //zipfs:/ somewhat applicable to other platforms)
# - /* as absolute (covers also //zipfs:/ (volume), //server , //./etc , //./UNC)
# - xxx:// as absolute (scheme)
# - xxx:/ or x:/ as absolute
# - x: xxx: -> as absolute (volume-basic or volume-extended)
#note also on windows - legacy name for COM devices
# COM1 = COM1:
# //./COM1 ?? review
proc pathtype {str} {
set str [string map "\\\\ /" $str]
if {[string index $str 0] eq "/"} {
#todo - look for //xxx:/ prefix (generalisation of //zipfs:/) as a 'volume' specifically {volume mount} ?? - review
# look for //server prefix as {absolute server}
# look for //./UNC/server or //?/UNC/server as {absolute server UNC} ?
# look for //./<dosdevice> as {absolute dosdevice}
return absolute
}
#only firstsegment with single colon at last position (after some non empty string) counts as volume or scheme - review
#e.g a:b:/.. or a::/.. or :/.. is not treated as volume/scheme whereas ab:/ is.
set firstslash [string first / $str]
if {$firstslash == -1} {
set firstsegment $str
} else {
set firstsegment [string range $str 0 $firstslash-1]
}
if {[set firstc [string first : $firstsegment]] > 0} {
set lhs_firstsegment [string range $firstsegment 0 $firstc-1]
set rhs_firstsegment [string range $firstsegment $firstc+1 end] ;#exclude a:b/ etc
if {$rhs_firstsegment eq ""} {
set rhs_entire_path [string range $str $firstc+1 end]
#assert lhs_firstsegment not empty since firstc > 0
#count following / sequence
set i 0
set slashes_after_firstsegment "" ;#run of slashes *directly* following first segment
while {$i < [string length $rhs_entire_path]} {
if {[string index $rhs_entire_path $i] eq "/"} {
append slashes_after_firstsegment /
} else {
break
}
incr i
}
switch -exact -- $slashes_after_firstsegment {
"" - / {
if {[string length $lhs_firstsegment] == 1} {
return {absolute volume basic}
} else {
return {absolute volume extended}
}
}
default {
#2 or more /
#this will return 'scheme' even for c:// - even though that may look like a windows volume - review
return {absolute scheme}
}
}
}
}
#assert first element of any return has been absolute or relative
return relative
}
proc plain {str} {
set str [string map "\\\\ /" $str]
set pathinfo [punk::path::pathtype $str]
if {[lindex $pathinfo 0] eq "relative" && ![string match ./* $str]} {
set str ./$str
}
if {[string index $str end] eq "/"} {
if {[string map {/ ""} $str] eq ""} {
#all slash segment
return $str
} else {
if {[lindex $pathinfo 1] ni {volume scheme}} {
return [string range $str 0 end-1]
}
}
}
return $str
}
#purely string based - no reference to filesystem knowledge
#unix-style forward slash only
proc plainjoin {args} {
set args [lmap a $args {string map "\\\\ /" $a}]
#if {[llength $args] == 1} {
# return [lindex $args 0]
#}
set out ""
foreach a $args {
if {![string length $out]} {
append out [plain $a]
} else {
set a [plain $a]
if {[string map {/ ""} $out] eq ""} {
set out [string range $out 0 end-1]
}
if {[string map {/ ""} $a] eq ""} {
#all / segment
append out [string range $a 0 end-1]
} else {
if {[string length $a] > 2 && [string match "./*" $a]} {
set a [string range $a 2 end]
}
if {[string index $out end] eq "/"} {
append out $a
} else {
append out / $a
}
}
}
}
return $out
}
proc plainjoin1 {args} {
if {[llength $args] == 1} {
return [lindex $args 0]
}
set out [trim_final_slash [lindex $args 0]]
foreach a [lrange $args 1 end] {
set a [trim_final_slash $a]
append out / $a
}
return $out
}
#intention?
#proc filepath_dotted_dirname {path} {
#}
proc strip_prefixdepth {path prefix} {
if {$prefix eq ""} {
return [norm $path]
}
return [file join \
{*}[lrange \
[file split [norm $path]] \
[llength [file split [norm $prefix]]] \
end]]
}
proc pathglob_as_re {pathglob} { proc pathglob_as_re {pathglob} {
#*** !doctools #*** !doctools

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

@ -134,13 +134,30 @@ namespace eval punk::repo {
} }
interp alias "" fossil "" punk::repo::fossil_proxy interp alias "" fossil "" punk::repo::fossil_proxy
# ---
# Calling auto_execok on an external tool can be too slow to do during package load (e.g could be 150ms)
#safe interps can't call auto_execok #safe interps can't call auto_execok
#At least let them load the package even though much of it may be unusable depending on the safe configuration #At least let them load the package even though much of it may be unusable depending on the safe configuration
catch { #catch {
if {[auto_execok fossil] ne ""} { # if {[auto_execok fossil] ne ""} {
interp alias "" FOSSIL "" {*}[auto_execok fossil] # interp alias "" FOSSIL "" {*}[auto_execok fossil]
} # }
#}
# ---
# ----------
#
#uppercase FOSSIL to bypass fossil as alias to fossil_proxy
proc establish_FOSSIL {args} {
if {![info exists ::auto_execs(FOSSIL)]} {
set ::auto_execs(FOSSIL) [auto_execok fossil] ;#may fail in safe interp
}
interp alias "" FOSSIL "" ;#delete establishment alias
FOSSIL {*}$args
} }
interp alias "" FOSSIL "" punk::repo::establish_FOSSIL
# ----------
proc askuser {question} { proc askuser {question} {
if {![catch {package require punk::lib}]} { if {![catch {package require punk::lib}]} {
@ -370,7 +387,16 @@ namespace eval punk::repo {
} }
if {$repodir eq ""} { if {$repodir eq ""} {
error "workingdir_state error: No repository found at or above path '$abspath'" puts stderr "workingdir_state error: No repository found at or above path '$abspath'"
puts stderr "args: $args"
dict set resultdict revision {}
dict set resultdict revision_iso8601 {}
dict set resultdict paths {}
dict set resultdict ahead ""
dict set resultdict behind ""
dict set resultdict error {reason "no_repo_found"}
dict set resultdict repotype none
return $resultdict
} }
set subpath [punk::path::relative $repodir $abspath] set subpath [punk::path::relative $repodir $abspath]
if {$subpath eq "."} { if {$subpath eq "."} {
@ -644,6 +670,16 @@ namespace eval punk::repo {
set path_count_fields [list unchanged changed new missing extra] set path_count_fields [list unchanged changed new missing extra]
set state_fields [list ahead behind repodir subpath repotype revision revision_iso8601] set state_fields [list ahead behind repodir subpath repotype revision revision_iso8601]
set dresult [dict create] set dresult [dict create]
if {[dict exists $repostate error]} {
foreach f $state_fields {
dict set dresult $f ""
}
foreach f $path_count_fields {
dict set dresult $f ""
}
#todo?
return $dresult
}
foreach f $state_fields { foreach f $state_fields {
dict set dresult $f [dict get $repostate $f] dict set dresult $f [dict get $repostate $f]
} }

155
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/winpath-0.1.0.tm

@ -30,7 +30,7 @@ namespace eval punk::winpath {
#\\servername\share etc or \\?\UNC\servername\share etc. #\\servername\share etc or \\?\UNC\servername\share etc.
proc is_unc_path {path} { proc is_unc_path {path} {
set strcopy_path [punk::objclone $path] set strcopy_path [punk::winpath::system::objclone $path]
set strcopy_path [string map {\\ /} $strcopy_path] ;#normalize to forward slashes for testing purposes (and forward slashes seem to be auto-translated by windows anyway) set strcopy_path [string map {\\ /} $strcopy_path] ;#normalize to forward slashes for testing purposes (and forward slashes seem to be auto-translated by windows anyway)
if {[string first "//" $strcopy_path] == 0} { if {[string first "//" $strcopy_path] == 0} {
#check for "Dos device path" syntax #check for "Dos device path" syntax
@ -77,7 +77,7 @@ namespace eval punk::winpath {
#dos device path syntax allows windows api to acces extended-length paths and filenames with illegal path characters such as trailing dots or whitespace #dos device path syntax allows windows api to acces extended-length paths and filenames with illegal path characters such as trailing dots or whitespace
#(can exist on server shares and on NTFS - but standard apps can't access without dos device syntax) #(can exist on server shares and on NTFS - but standard apps can't access without dos device syntax)
proc is_dos_device_path {path} { proc is_dos_device_path {path} {
set strcopy_path [punk::objclone $path] set strcopy_path [punk::winpath::system::objclone $path]
set strcopy_path [string map {\\ /} $strcopy_path] ;#normalize to forward slashes for testing purposes (and forward slashes seem to be auto-translated by windows anyway) set strcopy_path [string map {\\ /} $strcopy_path] ;#normalize to forward slashes for testing purposes (and forward slashes seem to be auto-translated by windows anyway)
if {[string range $strcopy_path 0 3] in {//?/ //./}} { if {[string range $strcopy_path 0 3] in {//?/ //./}} {
return 1 return 1
@ -87,7 +87,7 @@ namespace eval punk::winpath {
} }
proc strip_dos_device_prefix {path} { proc strip_dos_device_prefix {path} {
#it's unlikely to be valid to strip only //?/ from a //?/UNC path so check for it here and diver to strip that. #it's unlikely to be valid to strip only //?/ from a //?/UNC path so check for it here and diver to strip that.
#(review.. or raise error because a //?/UNC path isn't *strictly* a UNC path? ) #(review.. or raise error because a //?/UNC path isn't an ordinary dos device path? )
if {[is_unc_path $path]} { if {[is_unc_path $path]} {
return [strip_unc_path_prefix $path] return [strip_unc_path_prefix $path]
} }
@ -98,18 +98,18 @@ namespace eval punk::winpath {
} }
} }
proc strip_unc_path_prefix {path} { proc strip_unc_path_prefix {path} {
if {[is_unc_path $path]} { if {[is_unc_path_plain $path]} {
#//?/UNC/server/etc
set strcopy_path [punk::objclone $path]
set trimmedpath [string range $strcopy_path 7 end]
file pathtype $trimmedpath ;#shimmer it to path rep
return $trimmedpath
} elseif {is_unc_path_plain $path} {
#plain unc //server #plain unc //server
set strcopy_path [punk::objclone $path] set strcopy_path [punk::winpath::system::objclone $path]
set trimmedpath [string range $strcopy_path 2 end] set trimmedpath [string range $strcopy_path 2 end]
file pathtype $trimmedpath file pathtype $trimmedpath
return $trimmedpath return $trimmedpath
} elseif {is_unc_path $path} {
#//?/UNC/server/subpath or //./UNC/server/subpath
set strcopy_path [punk::winpath::system::objclone $path]
set trimmedpath [string range $strcopy_path 7 end]
file pathtype $trimmedpath ;#shimmer it to path rep
return $trimmedpath
} else { } else {
return $path return $path
} }
@ -153,7 +153,7 @@ namespace eval punk::winpath {
error $err error $err
} }
set strcopy_path [punk::objclone $path] set strcopy_path [punk::winpath::system::objclone $path]
#Note: path could still have leading double slash if it is a Dos device path: e.g. //?/c:/etc #Note: path could still have leading double slash if it is a Dos device path: e.g. //?/c:/etc
@ -225,27 +225,124 @@ namespace eval punk::winpath {
return 0 return 0
} }
proc test_ntfs_tunneling {f1 f2 args} { proc shortname {path} {
file mkdir $f1 set shortname "NA"
puts stderr "waiting 15secs..." if {[catch {
after 5000 {puts -nonewline stderr .} set shortname [dict get [file attributes $path] -shortname]
after 5000 {puts -nonewline stderr .} } errM]} {
after 5000 {puts -nonewline stderr .} puts stderr "Failed to get shortname for '$path'"
after 500 {puts stderr \n} }
file mkdir $f2 return $shortname
puts stdout "$f1 [file stat $f1]" }
puts stdout "$f2 [file stat $f2]" proc test_ntfs_tunneling {prefix args} {
file delete $f1 puts stderr "We are looking for whether any of the final $prefix files or dirs took over the ctime attribute of the original $prefix files or dirs"
puts stdout "renaming $f2 to $f1" puts stderr "We expect the ino values to get potentially reassigned depending on order of deletion/creation so matches are coincidental and not material"
file rename $f2 $f1 puts stderr "The shortnames are similarly allocated as they come - so presumably match by coincidence"
puts stdout "$f1 [file stat $f1]" puts stderr "However - if we record a file's shortname, then delete it. Recreating it by shortname within the tunneling timeframe will magically reassociate the longname"
puts stderr "use test_ntfs_tunneling2 to test shortname tunneling"
file mkdir $prefix-dir-rename
file mkdir $prefix-dir-recreate
set fd [open $prefix-file-recreate.txt w]
puts $fd "original for recreate"
close $fd
set fd [open $prefix-file-rename.txt w]
puts $fd "original for rename"
close $fd
puts stdout "ORIGINAL files/dirs"
puts stdout "$prefix-dir-rename [file stat $prefix-dir-rename] "
puts stdout "$prefix-dir-recreate [file stat $prefix-dir-recreate]"
puts stdout "$prefix-file-recreate.txt [file stat $prefix-file-recreate.txt] short:[shortname $prefix-file-recreate.txt]"
puts stdout "$prefix-file-rename.txt [file stat $prefix-file-rename.txt] short:[shortname $prefix-file-rename.txt]"
puts stderr "waiting 10secs (to have discernable ctime differences)"
after 5000
puts -nonewline stderr .
after 5000
puts -nonewline stderr .
after 500
#--
#seems to make no diff whether created or copied - no tunneling seen with dirs
#file mkdir $prefix-dir-rename-temp
file copy $prefix-dir-rename $prefix-dir-rename-temp
#--
puts stderr \n
puts stdout "$prefix-dir-rename-temp [file stat $prefix-dir-rename-temp] (temp to rename into place)"
puts stderr "deleting $prefix-dir-rename"
file delete $prefix-dir-rename
puts stdout "renaming $prefix-dir-rename-temp to $prefix-dir-rename"
file rename $prefix-dir-rename-temp $prefix-dir-rename
puts stderr "deleting $prefix-dir-recreate"
file delete $prefix-dir-recreate
puts stdout "re-creating $prefix-dir-recreate"
file mkdir $prefix-dir-recreate
puts stderr "deleting $prefix-file-recreate.txt"
file delete $prefix-file-recreate.txt
puts stderr "Recreating $prefix-file-recreate.txt"
set fd [open $prefix-file-recreate.txt w]
puts $fd "replacement"
close $fd
puts stderr "copying $prefix-file-rename.txt to $prefix-file-rename-temp.txt"
file copy $prefix-file-rename.txt $prefix-file-rename-temp.txt
puts stdout "$prefix-file-rename-temp.txt [file stat $prefix-file-rename-temp.txt] short:[shortname $prefix-file-rename-temp.txt] (status of initial temp copy)"
puts stderr "modifying temp copy before deletion of original.. (append)"
set fd [open $prefix-file-rename-temp.txt a]
puts $fd "added to file"
close $fd
puts stdout "$prefix-file-rename-temp.txt [file stat $prefix-file-rename-temp.txt] short:[shortname $prefix-file-rename-temp.txt] (status of appended temp copy)"
puts stderr "deleting $prefix-file-rename.txt"
file delete $prefix-file-rename.txt
puts stderr "renaming temp file $prefix-file-rename-temp.txt to original $prefix-file-rename.txt"
file rename $prefix-file-rename-temp.txt $prefix-file-rename.txt
puts stdout "Final files/dirs"
puts stdout "$prefix-dir-rename [file stat $prefix-dir-rename]"
puts stdout "$prefix-dir-recreate [file stat $prefix-dir-recreate]"
puts stdout "$prefix-file-recreate.txt [file stat $prefix-file-recreate.txt] short:[shortname $prefix-file-recreate.txt]"
puts stdout "$prefix-file-rename.txt [file stat $prefix-file-rename.txt] short:[shortname $prefix-file-rename.txt]"
}
proc test_ntfs_tunneling2 {prefix {waitms 15000}} {
#shortname -> longname tunneling
puts stderr "Tunneling only happens if we delete via shortname? review"
set f1 $prefix-longname-file1.txt
set f2 $prefix-longname-file2.txt
set fd [open $f1 w];close $fd
set shortname1 [shortname $f1]
puts stderr "longname:$f1 has shortname:$shortname1"
set fd [open $f2 w];close $fd
set shortname2 [shortname $f2]
puts stderr "longname:$f2 has shortname:$shortname2"
puts stderr "deleting $f1 via name $shortname1"
file delete $shortname1
puts stdout "immediately recreating $shortname1 - should retain longname $f1 via tunneling"
set fd [open $shortname1 w];close $fd
set f1_exists [file exists $f1]
puts stdout "file exists $f1 = $f1_exists"
puts stderr "deleting $f2 via name $shortname2"
file delete $shortname2
puts stderr "Waiting [expr {$waitms / 1000}] seconds.. (standard tunneling timeframe is 15 seconds if registry hasn't been tweaked)"
after $waitms
puts stdout "recreating $shortname2 after wait of $waitms ms - longname lost?"
set fd [open $shortname2 w];close $fd
set f2_exists [file exists $f2]
puts stdout "file exists $f2 = $f2_exists"
puts stdout -done-
} }
} }
namespace eval punk::winpath::system {
#get a copy of the item without affecting internal rep
proc objclone {obj} {
append obj2 $obj {}
}
}

209
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/textblock-0.1.1.tm

@ -12,25 +12,97 @@
# Meta license <unspecified> # Meta license <unspecified>
# @@ Meta End # @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[manpage_begin punkshell_module_textblock 0 0.1.1]
#[copyright "2024"]
#[titledesc {punk textblock functions}] [comment {-- Name section and table of contents description --}]
#[moddesc {punk textblock}] [comment {-- Description at end of page heading --}]
#[require textblock]
#[keywords module utility lib]
#[description]
#[para] Ansi-aware terminal textblock manipulation
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Overview]
#[para] overview of textblock
#[subsection Concepts]
#[para]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++ # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements ## Requirements
##e.g package require frobz # ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection dependencies]
#[para] packages used by textblock
#[list_begin itemized]
#*** !doctools
#[item] [package {Tcl 8.6-}]
#[item] [package {punk::args}]
#[item] [package {punk::char}]
#[item] [package {punk::ansi}]
#[item] [package {punk::lib}]
#[item] [package {overtype}]
#[item] [package {term::ansi::code::macros}]
#[item] [package {textutil}]
## Requirements
package require Tcl 8.6-
package require punk::args package require punk::args
package require punk::char package require punk::char
package require punk::ansi package require punk::ansi
package require punk::lib package require punk::lib
catch {package require patternpunk} catch {package require patternpunk}
package require overtype package require overtype
#safebase interps as at 2024-08 can't access deeper paths - even though they are below the supposed safe list.
package require term::ansi::code::macros ;#required for frame if old ansi g0 used - review - make package optional? package require term::ansi::code::macros ;#required for frame if old ansi g0 used - review - make package optional?
package require textutil package require textutil
#*** !doctools
#[list_end]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section API]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval textblock { tcl::namespace::eval textblock {
#review - what about ansi off in punk::console? #review - what about ansi off in punk::console?
tcl::namespace::import ::punk::ansi::a ::punk::ansi::a+ tcl::namespace::import ::punk::ansi::a ::punk::ansi::a+
tcl::namespace::export block frame frame_cache framedef frametypes gcross height width widthtopline join join_basic list_as_table pad testblock tcl::namespace::export block frame frame_cache framedef frametypes gcross height width widthtopline join join_basic list_as_table pad testblock
variable use_md5 ;#framecache
set use_md5 1
if {[catch {package require md5}]} {
set use_md5 0
}
proc use_md5 {{yes_no ""}} {
variable use_md5
if {$yes_no eq ""} {
return $use_md5
}
if {![string is boolean -strict $yes_no]} {
error "textblock::use_md5 requires a boolean (or empty string to query)"
}
if {$yes_no} {
package require md5
set use_md5 1
} else {
set use_md5 0
}
return $use_md5
}
tcl::namespace::eval class { tcl::namespace::eval class {
variable opts_table_defaults variable opts_table_defaults
set opts_table_defaults [tcl::dict::create\ set opts_table_defaults [tcl::dict::create\
@ -228,6 +300,7 @@ tcl::namespace::eval textblock {
} }
return $map return $map
} }
if {[tcl::info::commands [tcl::namespace::current]::table] eq ""} { if {[tcl::info::commands [tcl::namespace::current]::table] eq ""} {
#*** !doctools #*** !doctools
#[subsection {Namespace textblock::class}] #[subsection {Namespace textblock::class}]
@ -249,7 +322,7 @@ tcl::namespace::eval textblock {
oo::class create [tcl::namespace::current]::table [tcl::string::map [list %topt_keys% $topt_keys %topt_switchkeys% $switch_keys_valid_topts %copt_keys% $copt_keys %copt_switchkeys% $switch_keys_valid_copts] { oo::class create [tcl::namespace::current]::table [tcl::string::map [list %topt_keys% $topt_keys %topt_switchkeys% $switch_keys_valid_topts %copt_keys% $copt_keys %copt_switchkeys% $switch_keys_valid_copts] {
#*** !doctools #*** !doctools
#[enum] CLASS [class interface_caphandler.registry] #[enum] CLASS [class textblock::class::table]
#[list_begin definitions] #[list_begin definitions]
# [para] [emph METHODS] # [para] [emph METHODS]
variable o_opts_table ;#options as configured by user (with exception of -ansireset) variable o_opts_table ;#options as configured by user (with exception of -ansireset)
@ -3986,7 +4059,7 @@ tcl::namespace::eval textblock {
if append is chosen the new values will always start at the first column" if append is chosen the new values will always start at the first column"
-columns -default "" -type integer -help "Number of table columns -columns -default "" -type integer -help "Number of table columns
Will default to 2 if not using an existing -table object" Will default to 2 if not using an existing -table object"
*values *values -min 0 -max 1
datalist -default {} -help "flat list of table cell values which will be wrapped based on -columns value" datalist -default {} -help "flat list of table cell values which will be wrapped based on -columns value"
}] $args] }] $args]
set opts [dict get $argd opts] set opts [dict get $argd opts]
@ -4337,6 +4410,14 @@ tcl::namespace::eval textblock {
return [tcl::dict::create width $width height $height] ;#maintain order in 'image processing' standard width then height - caller may use lassign [tcl::dict::values [blocksize <data>]] width height return [tcl::dict::create width $width height $height] ;#maintain order in 'image processing' standard width then height - caller may use lassign [tcl::dict::values [blocksize <data>]] width height
} }
proc size_as_opts {textblock} {
set sz [size $textblock]
return [dict create -width [dict get $sz width] -height [dict get $sz height]]
}
proc size_as_list {textblock} {
set sz [size $textblock]
return [list [dict get $sz width] [dict get $sz height]]
}
#must be able to handle block as string with or without newlines #must be able to handle block as string with or without newlines
#if no newlines - attempt to treat as a list #if no newlines - attempt to treat as a list
#must handle whitespace-only string,list elements, and/or lines. #must handle whitespace-only string,list elements, and/or lines.
@ -5061,6 +5142,7 @@ tcl::namespace::eval textblock {
[punk::lib::list_as_lines -- [lrepeat 8 " | "]] [punk::lib::list_as_lines -- [lrepeat 8 " | "]]
} }
proc table {args} { proc table {args} {
#todo - use punk::args
upvar ::textblock::class::opts_table_defaults toptdefaults upvar ::textblock::class::opts_table_defaults toptdefaults
set defaults [tcl::dict::create\ set defaults [tcl::dict::create\
-rows [list]\ -rows [list]\
@ -5112,7 +5194,7 @@ tcl::namespace::eval textblock {
} }
variable frametypes variable frametypes
set frametypes [list light heavy arc double block block1 block2 ascii altg] set frametypes [list light heavy arc double block block1 block2 block2hack ascii altg]
#class::table needs to be able to determine valid frametypes #class::table needs to be able to determine valid frametypes
proc frametypes {} { proc frametypes {} {
variable frametypes variable frametypes
@ -5121,7 +5203,7 @@ tcl::namespace::eval textblock {
proc frametype {f} { proc frametype {f} {
#set custom_keys [list hl hlt hlb vl vll vlr tlc trc blc brc] #set custom_keys [list hl hlt hlb vl vll vlr tlc trc blc brc]
switch -- $f { switch -- $f {
light - heavy - arc - double - block - block1 - block2 - ascii - altg { light - heavy - arc - double - block - block1 - block2 - block2hack - ascii - altg {
return [tcl::dict::create category predefined type $f] return [tcl::dict::create category predefined type $f]
} }
default { default {
@ -5142,7 +5224,7 @@ tcl::namespace::eval textblock {
set is_custom_dict_ok 0 set is_custom_dict_ok 0
} }
if {!$is_custom_dict_ok} { if {!$is_custom_dict_ok} {
error "frame option -type must be one of known types: $textblock::frametypes or a dictionary with any of keys hl,hlt,hlb,vl,vll,vlr,tlc,trc,blc,brc" error "frame option -type must be one of known types: $::textblock::frametypes or a dictionary with any of keys hl,hlt,hlb,vl,vll,vlr,tlc,trc,blc,brc"
} }
set default_custom [tcl::dict::create hl " " vl " " 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] set custom_frame [tcl::dict::merge $default_custom $f]
@ -6252,9 +6334,12 @@ tcl::namespace::eval textblock {
set vlr \u2595 ;# right one eighth block set vlr \u2595 ;# right one eighth block
set vll \u258f ;# left one eighth block set vll \u258f ;# left one eighth block
#some terminals (on windows as at 2024) miscount width of these single-width blocks internally
#resulting in extra spacing that is sometimes well removed from the character itself (e.g at next ansi reset)
#This was fixed in windows-terminal based systems (2021) but persists in others.
#https://github.com/microsoft/terminal/issues/11694
set tlc \U1fb7d ;#legacy block set tlc \U1fb7d ;#legacy block
set trc \U1fb7e ;#legacy block set trc \U1fb7e ;#legacy block
set blc \U1fb7c ;#legacy block set blc \U1fb7c ;#legacy block
set brc \U1fb7f ;#legacy block set brc \U1fb7f ;#legacy block
@ -6265,6 +6350,42 @@ tcl::namespace::eval textblock {
set vlrj $vlr set vlrj $vlr
} }
block2hack {
#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 vlr \u2595 ;# right one eighth block
set vll \u258f ;# left one eighth block
#see comments in block2 regarding the problems in some terminals that this *may* hack around to some extent.
#the caller probably only needs block2hack if block2 doesn't work
#1)
#review - this hack looks sort of promising - but overtype::renderline needs fixing ?
#set tlc \U1fb7d\b ;#legacy block
#set trc \U1fb7e\b ;#legacy block
#set blc \U1fb7c\b ;#legacy block
#set brc \U1fb7f\b ;#legacy block
#2) - works on cmd.exe and some others
# a 'privacy message' is 'probably' also not supported on the old terminal but is on newer ones
#known exception - conemu on windows - displays junk for various ansi codes - (and slow terminal anyway)
#this hack has a reasonable chance of working
#except that the punk overtype library does recognise PMs
#A single backspace however is an unlikely and generally unuseful PM - so there is a corresponding hack in the renderline system to pass this PM through!
#ugly - in that we don't know the application specifics of what the PM data contains and where it's going.
set tlc \U1fb7d\x1b^\b\x1b\\ ;#legacy block
set trc \U1fb7e\x1b^\b\x1b\\ ;#legacy block
set blc \U1fb7c\x1b^\b\x1b\\ ;#legacy block
set brc \U1fb7f\x1b^\b\x1b\\ ;#legacy block
#horizontal and vertical bar joins
set hltj $hlt
set hlbj $hlb
set vllj $vll
set vlrj $vlr
}
block { block {
set hlt \u2580 ;#upper half set hlt \u2580 ;#upper half
set hlb \u2584 ;#lower half set hlb \u2584 ;#lower half
@ -6286,7 +6407,7 @@ tcl::namespace::eval textblock {
set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "] ;#only default the general types - these form defaults for more specific types if they're missing set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "] ;#only default the general types - these form defaults for more specific types if they're missing
if {[llength $f] % 2 != 0} { if {[llength $f] % 2 != 0} {
#todo - retrieve usage from punk::args #todo - retrieve usage from punk::args
error "textblock::frametype frametype '$f' is not one of the predefined frametypes: $textblock::frametypes and does not appear to be a dictionary for a custom frametype" error "textblock::frametype frametype '$f' is not one of the predefined frametypes: $::textblock::frametypes and does not appear to be a dictionary for a custom frametype"
} }
#unknown order of keys specified by user - validate before creating vars as we need more general elements to be available as defaults #unknown order of keys specified by user - validate before creating vars as we need more general elements to be available as defaults
dict for {k v} $f { dict for {k v} $f {
@ -6388,7 +6509,7 @@ tcl::namespace::eval textblock {
#options before content argument - which is allowed to be absent #options before content argument - which is allowed to be absent
#frame performance (noticeable with complex tables even of modest size) is improved significantly by frame_cache - but is still (2024) a fairly expensive operation. #frame performance (noticeable with complex tables even of modest size) is improved somewhat by frame_cache - but is still (2024) a fairly expensive operation.
# #
#consider if we can use -sticky nsew instead of -blockalign (as per Tk grid -sticky option) #consider if we can use -sticky nsew instead of -blockalign (as per Tk grid -sticky option)
# This would require some sort of expand equivalent e.g for -sticky ew or -sticky ns - for which we have to decide a meaning for text.. e.g ansi padding? # This would require some sort of expand equivalent e.g for -sticky ew or -sticky ns - for which we have to decide a meaning for text.. e.g ansi padding?
@ -6397,6 +6518,7 @@ tcl::namespace::eval textblock {
# - but we would need to maintain support for the rendered-string based operations too. # - but we would need to maintain support for the rendered-string based operations too.
proc frame {args} { proc frame {args} {
variable frametypes variable frametypes
variable use_md5
#counterintuitively - in-proc dict create each time is generally slightly faster than linked namespace var #counterintuitively - in-proc dict create each time is generally slightly faster than linked namespace var
set opts [tcl::dict::create\ set opts [tcl::dict::create\
@ -6416,7 +6538,11 @@ tcl::namespace::eval textblock {
-ellipsis 1\ -ellipsis 1\
-usecache 1\ -usecache 1\
-buildcache 1\ -buildcache 1\
-pad 1\
-crm_mode 0\
] ]
#-pad 1 is default so that simple 'textblock::frame "[a+ Red]a \nbbb[a]" extends the bg colour on the short ragged lines (and empty lines)
# for ansi art - -pad 0 is likely to be preferable
set expect_optval 0 set expect_optval 0
set argposn 0 set argposn 0
@ -6455,7 +6581,12 @@ tcl::namespace::eval textblock {
#use -buildcache 1 with -usecache 0 for debugging cache issues so we can inspect using textblock::frame_cache #use -buildcache 1 with -usecache 0 for debugging cache issues so we can inspect using textblock::frame_cache
foreach {k v} $arglist { foreach {k v} $arglist {
switch -- $k { switch -- $k {
-etabs - -type - -boxlimits - -boxmap - -joins - -title - -subtitle - -width - -height - -ansiborder - -ansibase - -blockalign - -textalign - -ellipsis - -usecache - -buildcache { -etabs - -type - -boxlimits - -boxmap - -joins
- -title - -subtitle - -width - -height
- -ansiborder - -ansibase
- -blockalign - -textalign - -ellipsis
- -crm_mode
- -usecache - -buildcache - -pad {
tcl::dict::set opts $k $v tcl::dict::set opts $k $v
} }
default { default {
@ -6471,11 +6602,13 @@ tcl::namespace::eval textblock {
set opt_boxmap [tcl::dict::get $opts -boxmap] set opt_boxmap [tcl::dict::get $opts -boxmap]
set opt_usecache [tcl::dict::get $opts -usecache] set opt_usecache [tcl::dict::get $opts -usecache]
set opt_buildcache [tcl::dict::get $opts -buildcache] set opt_buildcache [tcl::dict::get $opts -buildcache]
set opt_pad [tcl::dict::get $opts -pad]
set opt_crm_mode [tcl::dict::get $opts -crm_mode]
set usecache $opt_usecache ;#may need to override set usecache $opt_usecache ;#may need to override
set buildcache $opt_buildcache set buildcache $opt_buildcache
set custom_keys [list hl hlt hlb vl vll vlr tlc trc blc brc] set custom_keys [list hl hlt hlb vl vll vlr tlc trc blc brc]
set known_frametypes $frametypes ;# light, heavey etc as defined in textblock::frametypes variable set known_frametypes $frametypes ;# light, heavey etc as defined in the ::textblock::frametypes variable
set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "] set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "]
lassign [textblock::frametype $opt_type] _cat category _type ftype lassign [textblock::frametype $opt_type] _cat category _type ftype
@ -6614,6 +6747,19 @@ tcl::namespace::eval textblock {
} }
} }
set contents [tcl::string::map [list \r\n \n] $contents] set contents [tcl::string::map [list \r\n \n] $contents]
if {$opt_crm_mode} {
if {$opt_height eq ""} {
set h [textblock::height $contents]
} else {
set h [expr {$opt_height -2}]
}
if {$opt_width eq ""} {
set w [textblock::width $contents]
} else {
set w [expr {$opt_width -2}]
}
set contents [overtype::renderspace -crm_mode 1 -wrap 1 -width $w -height $h "" $contents]
}
set actual_contentwidth [textblock::width $contents] ;#length of longest line in contents (contents can be ragged) set actual_contentwidth [textblock::width $contents] ;#length of longest line in contents (contents can be ragged)
set actual_contentheight [textblock::height $contents] set actual_contentheight [textblock::height $contents]
} else { } else {
@ -6652,9 +6798,14 @@ tcl::namespace::eval textblock {
#review - custom frame affects frame_inner_width - exclude from caching? #review - custom frame affects frame_inner_width - exclude from caching?
#set cache_key [concat $arglist $frame_inner_width $frame_inner_height] #set cache_key [concat $arglist $frame_inner_width $frame_inner_height]
set hashables [concat $arglist $frame_inner_width $frame_inner_height] set hashables [concat $arglist $frame_inner_width $frame_inner_height]
package require md5
#set hash $hashables if {$use_md5} {
set hash [md5::md5 -hex [encoding convertto utf-8 $hashables]] ;#need fast and unique to content - not cryptographic - review #package require md5 ;#already required at package load
set hash [md5::md5 -hex [encoding convertto utf-8 $hashables]] ;#need fast and unique to content - not cryptographic - review
} else {
set hash $hashables
}
set cache_key "$hash-$frame_inner_width-$frame_inner_height-actualcontentwidth:$actual_contentwidth" set cache_key "$hash-$frame_inner_width-$frame_inner_height-actualcontentwidth:$actual_contentwidth"
#should be in a unicode private range different to that used in table construction #should be in a unicode private range different to that used in table construction
#e.g BMP PUA U+E000 -> U+F8FF - although this is commonly used for example by nerdfonts #e.g BMP PUA U+E000 -> U+F8FF - although this is commonly used for example by nerdfonts
@ -7057,15 +7208,22 @@ tcl::namespace::eval textblock {
append contents [::join [lrepeat $diff \n] ""] append contents [::join [lrepeat $diff \n] ""]
} }
set paddedcontents [textblock::pad $contents -which $pad -within_ansi 1] ;#autowidth to width of content (should match cache_patternwidth) if {$opt_pad} {
set paddedwidth [textblock::widthtopline $paddedcontents] set paddedcontents [textblock::pad $contents -which $pad -within_ansi 1] ;#autowidth to width of content (should match cache_patternwidth)
set paddedwidth [textblock::widthtopline $paddedcontents]
#review - horizontal truncation #review - horizontal truncation
if {$paddedwidth > $cache_patternwidth} { if {$paddedwidth > $cache_patternwidth} {
set paddedcontents [overtype::renderspace -width $cache_patternwidth "" $paddedcontents] set paddedcontents [overtype::renderspace -width $cache_patternwidth "" $paddedcontents]
}
#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
} else {
set cwidth [textblock::width $contents]
if {$cwidth > $cache_patternwidth} {
set contents [overtype::renderspace -width $cache_patternwidth "" $contents]
}
set contentblock [textblock::join -- $contents]
} }
#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]
@ -7183,7 +7341,6 @@ tcl::namespace::eval textblock {
#fastest to do row first then columns - because textblock::join must do line by line #fastest to do row first then columns - because textblock::join must do line by line
if {$crosscount > 1} { if {$crosscount > 1} {
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]
@ -7224,3 +7381,7 @@ package provide textblock [tcl::namespace::eval textblock {
set version 0.1.1 set version 0.1.1
}] }]
return return
#*** !doctools
#[manpage_end]

77
src/project_layouts/custom/_project/punk.shell-0.1/src/make.tcl

@ -1212,8 +1212,9 @@ foreach vfstail $vfs_tails {
set rtmountpoint //zipfs:/rtmounts/$runtime_fullname set rtmountpoint //zipfs:/rtmounts/$runtime_fullname
set changed_unchanged [$vfs_event targetset_source_changes] set changed_unchanged [$vfs_event targetset_source_changes]
set vfs_or_runtime_changed [expr {[llength [dict get $changed_unchanged changed]] || [llength [$vfs_event get_targets_exist]] < [llength [$vfs_event get_targets]]}]
if {[llength [dict get $changed_unchanged changed]] || [llength [$vfs_event get_targets_exist]] < [llength [$vfs_event get_targets]]} { if {$vfs_or_runtime_changed} {
#source .vfs folder has changes #source .vfs folder has changes
$vfs_event targetset_started $vfs_event targetset_started
# -- --- --- --- --- --- # -- --- --- --- --- ---
@ -1283,6 +1284,7 @@ foreach vfstail $vfs_tails {
puts stderr "RUNTIME capabilities unknown. Unsure if zip supported. trying anyway.." puts stderr "RUNTIME capabilities unknown. Unsure if zip supported. trying anyway.."
} }
} }
#note - as at 2024-08 - there is some discussion about the interface to mkimg - it is considered unstable (may change to -option value syntax)
puts stderr "calling: tcl::zipfs::mkimg $buildfolder/$vfsname.new $wrapvfs $wrapvfs \"\" $buildfolder/build_$runtime_fullname" puts stderr "calling: tcl::zipfs::mkimg $buildfolder/$vfsname.new $wrapvfs $wrapvfs \"\" $buildfolder/build_$runtime_fullname"
tcl::zipfs::mkimg $buildfolder/$vfsname.new $wrapvfs $wrapvfs "" $buildfolder/build_$runtime_fullname tcl::zipfs::mkimg $buildfolder/$vfsname.new $wrapvfs $wrapvfs "" $buildfolder/build_$runtime_fullname
} result ]} { } result ]} {
@ -1352,9 +1354,10 @@ foreach vfstail $vfs_tails {
if {![catch { if {![catch {
exec $pscmd | grep $targetkit exec $pscmd | grep $targetkit
} still_running]} { } still_running]} {
set still_running_lines [split [string trim $still_running] \n]
puts stdout "found $targetkit instances still running\n" puts stdout "found ([llength $still_running_lines]) $targetkit instances still running\n"
set count_killed 0 set count_killed 0
set num_to_kill [llength $still_running_lines]
foreach ln [split $still_running \n] { foreach ln [split $still_running \n] {
puts stdout " $ln" puts stdout " $ln"
@ -1387,9 +1390,6 @@ foreach vfstail $vfs_tails {
#review - *no running instance* works with windows taskkill - "*No such process*" works with kill -9 on FreeBSD and linux - other platforms? #review - *no running instance* works with windows taskkill - "*No such process*" works with kill -9 on FreeBSD and linux - other platforms?
if {![string match "*no running instance*" $errMsg] && ![string match "*No such process*" $errMsg]} { if {![string match "*no running instance*" $errMsg] && ![string match "*No such process*" $errMsg]} {
lappend failed_kits [list kit $targetkit reason "could not kill running process for $targetkit (using '$killcmd')"] lappend failed_kits [list kit $targetkit reason "could not kill running process for $targetkit (using '$killcmd')"]
$vfs_event targetset_end FAILED
$vfs_event destroy
$vfs_installer destroy
continue continue
} }
} else { } else {
@ -1397,10 +1397,15 @@ foreach vfstail $vfs_tails {
incr count_killed incr count_killed
} }
} }
if {$count_killed > 0} { if {$count_killed < $num_to_kill} {
puts stderr "\nKilled $count_killed processes. Waiting a short time before attempting to delete executable" $vfs_event targetset_end FAILED
after 1000 $vfs_event destroy
$vfs_installer destroy
continue
} }
puts stderr "\nKilled $count_killed processes. Waiting a short time before attempting to delete executable"
after 1000
} else { } else {
puts stderr "Ok.. no running '$targetkit' processes found" puts stderr "Ok.. no running '$targetkit' processes found"
} }
@ -1426,22 +1431,35 @@ foreach vfstail $vfs_tails {
# -- --- --- --- --- --- # -- --- --- --- --- ---
$vfs_event targetset_end OK $vfs_event targetset_end OK
} else {
set skipped_vfs_build 1
puts stderr "."
puts stdout "Skipping build for vfs $vfstail with runtime $rtname - no change detected"
$vfs_event targetset_end SKIPPED
}
$vfs_event destroy
$vfs_installer destroy
after 200 after 200
set deployment_folder [file dirname $sourcefolder]/bin set deployment_folder [file dirname $sourcefolder]/bin
file mkdir $deployment_folder file mkdir $deployment_folder
# -- ---------- # -- ----------
set bin_installer [punkcheck::installtrack new "make.tcl" $deployment_folder/.punkcheck] set bin_installer [punkcheck::installtrack new "make.tcl" $deployment_folder/.punkcheck]
$bin_installer set_source_target $buildfolder $deployment_folder $bin_installer set_source_target $buildfolder $deployment_folder
set bin_event [$bin_installer start_event {-make-step final_kit_install}] set bin_event [$bin_installer start_event {-make-step final_kit_install}]
$bin_event targetset_init INSTALL $deployment_folder/$targetkit $bin_event targetset_init INSTALL $deployment_folder/$targetkit
#todo - move final deployment step outside of the build vfs loop? (final deployment can fail and then isn't rerun even though _build and deployed versions differ, unless .vfs modified again) #todo - move final deployment step outside of the build vfs loop? (final deployment can fail and then isn't rerun even though _build and deployed versions differ, unless .vfs modified again)
#set last_completion [$bin_event targetset_last_complete] #set last_completion [$bin_event targetset_last_complete]
$bin_event targetset_addsource $buildfolder/$targetkit $bin_event targetset_addsource $deployment_folder/$targetkit ;#add target as a source of metadata for change detection
$bin_event targetset_started $bin_event targetset_addsource $buildfolder/$targetkit
# -- ---------- $bin_event targetset_started
# -- ----------
set changed_unchanged [$bin_event targetset_source_changes]
set built_or_installed_kit_changed [expr {[llength [dict get $changed_unchanged changed]] || [llength [$bin_event get_targets_exist]] < [llength [$bin_event get_targets]]}]
if {$built_or_installed_kit_changed} {
if {[file exists $deployment_folder/$targetkit]} { if {[file exists $deployment_folder/$targetkit]} {
puts stderr "deleting existing deployed at $deployment_folder/$targetkit" puts stderr "deleting existing deployed at $deployment_folder/$targetkit"
@ -1467,19 +1485,16 @@ foreach vfstail $vfs_tails {
# -- ---------- # -- ----------
$bin_event targetset_end OK $bin_event targetset_end OK
# -- ---------- # -- ----------
$bin_event destroy
$bin_installer destroy
} else { } else {
set skipped_vfs_build 1 set skipped_kit_install 1
puts stderr "." puts stderr "."
puts stdout "Skipping build for vfs $vfstail with runtime $rtname - no change detected" puts stdout "Skipping kit install for $targetkit with vfs $vfstail runtime $rtname - no change detected"
$vfs_event targetset_end SKIPPED $bin_event targetset_end SKIPPED
} }
$bin_event destroy
$bin_installer destroy
$vfs_event destroy
$vfs_installer destroy
} ;#end foreach targetkit } ;#end foreach targetkit
} ;#end foreach rtname in runtimes } ;#end foreach rtname in runtimes

26
src/runtime/mapvfs.config

@ -12,22 +12,32 @@
#e.g #e.g
#- myproject.vfs #- myproject.vfs
#- punk86.vfs #- punk86.vfs
tclkit86bi.exe {punk8win.vfs punkbi kit} tclkit86bi.exe {punk8win.vfs punkbi kit}
#c:\tcl.bawt tcl 8.6.13 bawt
tclkit-win64-dyn.exe {punk86bawt.vfs punkbawt kit}
#magicsplat tclkit - no Tk #magicsplat tclkit - no Tk
##tclkit8613.exe punk86.vfs ##tclkit8613.exe punk86.vfs
#magicsplat modified tclkit - added tk, changed icon #magicsplat modified tclkit - added tk, changed icon
tclkit8613punk.exe punk86.vfs {punk.vfs punk86b} tclkit8613punk.exe punk86.vfs {punk8win.vfs punk86}
#tclkit8613punk.head.exe {punk8_statictwapi.vfs punk86head} #tclkit8613punk.head.exe {punk8_statictwapi.vfs punk86head}
#tclkit87a5.exe {punk86.vfs punk87} {punk.vfs punkmain} #tclkit87a5.exe punk86.vfs punk87} {punk.vfs punkmain}
tclkit87a5.exe {punk8win.vfs punk87} tclkit87a5.exe {punk8win.vfs punk87}
##################################
#TCL9
tclsh90b2 {punk9win.vfs punk90b2 zip}
tclsh90b4_piperepl.exe {punk9win.vfs punk90b4 zip} {critcl.vfs critcl9 zip}
##################################
#experimental
tclsh90zip.exe {punk9win.vfs punk90zip zip}
##tclkit87a5bawt.exe punk86.vfs ##tclkit87a5bawt.exe punk86.vfs
##tclkit86bi.exe vfs_windows/punk86win.vfs ##tclkit86bi.exe vfs_windows/punk86win.vfs
@ -35,7 +45,5 @@ tclsh90zip.exe {punk9win.vfs punk90zip zip}
#temp hack - todo fix .exe for x-platform #temp hack - todo fix .exe for x-platform
#linux tclsh90 (zip) built with zig.build x-compile on windows #linux tclsh90 (zip) built with zig.build x-compile on windows
#tclsh90linux.exe {punk9linux.vfs punk90linux zip} #tclsh90linux.exe {punk9linux.vfs punk90linux zip}
#c:\tcl.bawt tcl 8.6.13 bawt
tclkit-win64-dyn.exe {punk86bawt.vfs punkbawt kit}

1993
src/vendormodules/overtype-1.6.5.tm

File diff suppressed because it is too large Load Diff

BIN
src/vendormodules/test/tomlish-1.1.1.tm

Binary file not shown.

1993
src/vfs/_vfscommon/modules/overtype-1.6.5.tm

File diff suppressed because it is too large Load Diff

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

@ -106,7 +106,7 @@ tcl::namespace::eval punk::ansi::class {
#overflow is a different concept - perhaps not particularly congruent with the idea of the textblock as a mini terminal emulator. #overflow is a different concept - perhaps not particularly congruent with the idea of the textblock as a mini terminal emulator.
#overflow effectively auto-expands the block(terminal?) width #overflow effectively auto-expands the block(terminal?) width
#overflow and wrap both being true won't make sense unless we implement a max_overflow concept #overflow and wrap both being true won't make sense unless we implement a max_overflow concept
set o_rendered [overtype::renderspace -overflow 0 -wrap 1 -width $w -height $h -appendlines 1 "" [$o_ansistringobj get]] set o_rendered [overtype::renderspace -expand_right 0 -wrap 1 -width $w -height $h -appendlines 1 "" [$o_ansistringobj get]]
if {$cksum eq "not-done"} { if {$cksum eq "not-done"} {
#if dimensions changed - the checksum won't have been done #if dimensions changed - the checksum won't have been done
set o_rendered_what [$o_ansistringobj checksum] set o_rendered_what [$o_ansistringobj checksum]
@ -129,7 +129,7 @@ tcl::namespace::eval punk::ansi::class {
set o_dimensions $dimensions set o_dimensions $dimensions
set rendered [overtype::renderspace -experimental {test_mode} -overflow 0 -wrap 1 -width $w -height $h -appendlines 1 "" [$o_ansistringobj get]] set rendered [overtype::renderspace -cp437 1 -expand_right 0 -wrap 1 -width $w -height $h -appendlines 1 "" [$o_ansistringobj get]]
return $rendered return $rendered
} }
method render_to_input_line {args} { method render_to_input_line {args} {
@ -176,7 +176,7 @@ tcl::namespace::eval punk::ansi::class {
if {$opt_minus ne "0"} { if {$opt_minus ne "0"} {
set chunk [tcl::string::range $chunk 0 end-$opt_minus] set chunk [tcl::string::range $chunk 0 end-$opt_minus]
} }
set rendered [overtype::renderspace -experimental {test_mode} -overflow 0 -wrap 1 -width $w -height $h -appendlines 1 "" $chunk] set rendered [overtype::renderspace -expand_right 0 -wrap 1 -width $w -height $h -appendlines 1 "" $chunk]
set marker "" set marker ""
for {set i 1} {$i <= $w} {incr i} { for {set i 1} {$i <= $w} {incr i} {
if {$i % 10 == 0} { if {$i % 10 == 0} {
@ -514,11 +514,8 @@ tcl::namespace::eval punk::ansi {
set encnames [encoding names] set encnames [encoding names]
set encoding "" set encoding ""
set dimensions "" set dimensions ""
set test_mode 0
foreach a $args { foreach a $args {
if {$a eq "test_mode"} { if {$a in $encnames} {
set test_mode 1
} elseif {$a in $encnames} {
set encoding $a set encoding $a
} else { } else {
if {[regexp {[0-9]+(?:x|X)[0-9]+} $a]} { if {[regexp {[0-9]+(?:x|X)[0-9]+} $a]} {
@ -553,28 +550,51 @@ tcl::namespace::eval punk::ansi {
$obj destroy $obj destroy
return $result return $result
} }
proc example {} { proc example {args} {
set base [punk::repo::find_project]
set default_ansibase [file join $base src/testansi]
set argd [punk::args::get_dict [tstr -return string {
*proc -name punk::ansi::example -help "Display .ans image files in a grid that will fit in console
"
-colwidth -default 82 -help "Width of each column - default of 82 will fit a standard 80wide ansi image (when framed)
You can specify a narrower width to truncate images on the right side"
-folder -default "${$default_ansibase}" -help "Base folder for files if relative paths are used.
Defaults to <projectbase>/src/testansi - where projectbase is determined from current directory.
"
*values -min 0 -max -1
files -default {belinda.ans bot.ans flower.ans fish.ans} -multiple true -help "List of filenames - leave empty to display 4 defaults"
}] $args]
set colwidth [dict get $argd opts -colwidth]
set ansibase [file normalize [dict get $argd opts -folder]]
set fnames [dict get $argd values files]
#assumes fixed column widths e.g 80col images will fit in 82-width frames (common standard for old ansi art) (of arbitrary height)
#todo - review dependency on punk::repo ? #todo - review dependency on punk::repo ?
package require textblock package require textblock
package require punk::repo package require punk::repo
package require punk::console package require punk::console
set fnames [list belinda.ans bot.ans flower.ans fish.ans]
set base [punk::repo::find_project]
set ansibase [file join $base src/testansi]
if {![file exists $ansibase]} { if {![file exists $ansibase]} {
puts stderr "Missing testansi folder at $base/src/testansi" puts stderr "Missing folder at $ansibase"
puts stderr "Ensure ansi test files exist: $fnames" puts stderr "Ensure ansi test files exist: $fnames"
#error "punk::ansi::example Cannot find example files" #error "punk::ansi::example Cannot find example files"
} }
set missingbase [a+ yellow][textblock::block 80 23 ?][a] set missingbase [a+ yellow][textblock::block [expr {$colwidth-2}] 23 ?][a] ;#assuming standard frame - subtract 2 for left/right borders
set pics [list] set pics [list]
foreach f $fnames { foreach f $fnames {
if {![file exists $ansibase/$f]} { if {[file pathtype $f] ne "absolute"} {
set p [overtype::left $missingbase "[a+ red bold]\nMissing file\n$ansibase/$f[a]"] set filepath [file normalize $ansibase/$f]
} else {
set filepath [file normalize $f]
}
if {![file exists $filepath]} {
set p [overtype::left $missingbase "[a+ red bold]\nMissing file\n$f[a]"]
lappend pics [tcl::dict::create filename $f pic $p status missing] lappend pics [tcl::dict::create filename $f pic $p status missing]
} else { } else {
set img [join [lines_as_list -line trimline -block trimtail [ansicat $ansibase/$f]] \n] #set img [join [lines_as_list -line trimline -block trimtail [ansicat $filepath]] \n]
#-line trimline will wreck some images
set img [join [lines_as_list -block trimtail [ansicat $filepath]] \n]
lappend pics [tcl::dict::create filename $f pic $img status ok] lappend pics [tcl::dict::create filename $f pic $img status ok]
} }
} }
@ -582,30 +602,73 @@ tcl::namespace::eval punk::ansi {
set termsize [punk::console:::get_size] set termsize [punk::console:::get_size]
set margin 4 set margin 4
set freewidth [expr {[tcl::dict::get $termsize columns]-$margin}] set freewidth [expr {[tcl::dict::get $termsize columns]-$margin}]
set per_row [expr {$freewidth / 80}] set per_row [expr {$freewidth / $colwidth}]
set rowlist [list] set rowlist [list] ;# { {<img> <img>} {<img> <img>} }
set row [list] set heightlist [list] ;# { {<h> <h> } {<h> <h> } }
set i 1 set maxheights [list] ;# { <max> <max>}
set row [list] ;#wip row
set rowh [list] ;#wip row img heights
set i 1 ;#track image index of whole pics list
set rowindex 0
foreach picinfo $pics { foreach picinfo $pics {
set subtitle "" set subtitle ""
if {[tcl::dict::get $picinfo status] ne "ok"} { if {[tcl::dict::get $picinfo status] ne "ok"} {
set subtitle [tcl::dict::get $picinfo status] set subtitle [tcl::dict::get $picinfo status]
} }
set title [tcl::dict::get $picinfo filename] set title [tcl::dict::get $picinfo filename]
lappend row [textblock::frame -subtitle $subtitle -title $title [tcl::dict::get $picinfo pic]] set fr [textblock::frame -width $colwidth -subtitle $subtitle -title $title [tcl::dict::get $picinfo pic]]
# -- --- --- ---
#we need the max height of a row element to use join_basic instead of join below
# -- --- --- ---
set fr_height [textblock::height $fr]
lappend row $fr
lappend rowh $fr_height
set rowmax [lindex $maxheights $rowindex]
if {$rowmax eq ""} {
#empty result means no maxheights entry for this row yet
set rowmax $fr_height
lappend maxheights $rowmax
} else {
if {$fr_height > $rowmax} {
set rowmax $fr_height
lset maxheights end $rowmax
}
}
# -- --- --- ---
if {$i % $per_row == 0} { if {$i % $per_row == 0} {
lappend rowlist $row lappend rowlist $row
lappend heightlist $rowh
incr rowindex
set row [list] set row [list]
set rowh [list]
} elseif {$i == [llength $pics]} { } elseif {$i == [llength $pics]} {
lappend rowlist $row lappend rowlist $row
lappend heightlist $rowh
} }
incr i incr i
} }
#puts "--> maxheights: $maxheights"
#puts "--> heightlist: $heightlist"
set result "" set result ""
foreach r $rowlist { set rowindex 0
append result [textblock::join_basic -- {*}$r] \n set blankline [string repeat " " $colwidth]
foreach imgs $rowlist heights $heightlist {
set maxheight [lindex $maxheights $rowindex]
set adjusted_row [list]
foreach i $imgs h $heights {
if {$h < $maxheight} {
#add blank lines to bottom of shorter images so join_basic can be used.
#textblock::join of ragged-height images would work and remove the need for all the height calculation
#.. but it requires much more processing
append i [string repeat \n$blankline [expr {$maxheight - $h}]]
}
lappend adjusted_row $i
}
append result [textblock::join_basic -- {*}$adjusted_row] \n
incr rowindex
} }
@ -3199,6 +3262,28 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
return \x1b8 return \x1b8
} }
# -- --- --- --- --- # -- --- --- --- ---
#CRM Show Control Character Mode
proc enable_crm {} {
return \x1b\[3h
}
proc disable_crm {} {
return \x1b\[3l
}
#DECSNM
#Note this can invert the enclosed section including any already reversed by SGR 7 - depending on terminal support.
#e.g
#set test [a+ reverse]aaa[a+ noreverse]bbb
# - $test above can't just be reversed by putting another [a+ reverse] in front of it.
# - but the following will work (even if underlying terminal doesn't support ?5 sequences)
#overtype::renderspace -width 20 [enable_inverse]$test
proc enable_inverse {} {
return \x1b\[?5h
}
proc disable_inverse {} {
return \x1b\[?5l
}
#DECAWM - automatic line wrapping #DECAWM - automatic line wrapping
proc enable_line_wrap {} { proc enable_line_wrap {} {
@ -3399,6 +3484,9 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#the purpose of backspace (or line cr) in embedded text is unclear. Should it allow some sort of character combining/overstrike as it has sometimes done historically (nroff/less)? e.g a\b` as an alternative combiner or bolding if same char #the purpose of backspace (or line cr) in embedded text is unclear. Should it allow some sort of character combining/overstrike as it has sometimes done historically (nroff/less)? e.g a\b` as an alternative combiner or bolding if same char
#This should presumably only be done if the over_strike (os) capability is enabled in the terminal. Either way - it presumably won't affect printing width? #This should presumably only be done if the over_strike (os) capability is enabled in the terminal. Either way - it presumably won't affect printing width?
set line [punk::ansi::ansistrip $line] set line [punk::ansi::ansistrip $line]
#ANSI (e.g PM/SOS) can contain \b or \n or \t but won't contribute to length
#ansistrip must come before any other processing of these chars.
#we can't use simple \b processing if we get ansi codes and aren't actually processing them (e.g moves) #we can't use simple \b processing if we get ansi codes and aren't actually processing them (e.g moves)
set line [punk::char::strip_nonprinting_ascii $line] ;#only strip nonprinting after ansistrip - some like BEL are part of ansi set line [punk::char::strip_nonprinting_ascii $line] ;#only strip nonprinting after ansistrip - some like BEL are part of ansi
@ -3748,6 +3836,7 @@ tcl::namespace::eval punk::ansi {
-filter_fg 0\ -filter_fg 0\
-filter_bg 0\ -filter_bg 0\
-filter_reset 0\ -filter_reset 0\
-info 0\
] ]
#codes *must* already have been split so that one esc per element in codelist #codes *must* already have been split so that one esc per element in codelist
@ -3760,7 +3849,8 @@ tcl::namespace::eval punk::ansi {
set opts $defaultopts_sgr_merge_singles set opts $defaultopts_sgr_merge_singles
foreach {k v} $args { foreach {k v} $args {
switch -- $k { switch -- $k {
-filter_fg - -filter_bg - -filter_reset { -filter_fg - -filter_bg - -filter_reset -
-info {
tcl::dict::set opts $k $v tcl::dict::set opts $k $v
} }
default { default {
@ -4139,19 +4229,24 @@ tcl::namespace::eval punk::ansi {
set codemerge [tcl::string::trimright $codemerge {;}] set codemerge [tcl::string::trimright $codemerge {;}]
if {$unmergeable ne ""} { if {$unmergeable ne ""} {
set unmergeable [tcl::string::trimright $unmergeable {;}] set unmergeable [tcl::string::trimright $unmergeable {;}]
return "\x1b\[${codemerge}m\x1b\[${unmergeable}m[join $othercodes ""]" set mergeresult "\x1b\[${codemerge}m\x1b\[${unmergeable}m[join $othercodes ""]"
} else { } else {
return "\x1b\[${codemerge}m[join $othercodes ""]" set mergeresult "\x1b\[${codemerge}m[join $othercodes ""]"
} }
} else { } else {
if {$unmergeable eq ""} { if {$unmergeable eq ""} {
#there were no SGR codes - not even resets #there were no SGR codes - not even resets
return [join $othercodes ""] set mergeresult [join $othercodes ""]
} else { } else {
set unmergeable [tcl::string::trimright $unmergeable {;}] set unmergeable [tcl::string::trimright $unmergeable {;}]
return "\x1b\[${unmergeable}m[join $othercodes ""]" set mergeresult "\x1b\[${unmergeable}m[join $othercodes ""]"
} }
} }
if {[tcl::dict::get $opts -info]} {
return [dict create sgr $codemerge unmergeable $unmergeable othercodes $othercodes mergeresult $mergeresult codestate $codestate]
} else {
return $mergeresult
}
} }
#has_sgr_reset - rather than support this function - create an sgr normalize function that removes dead params and brings reset to front of param list? #has_sgr_reset - rather than support this function - create an sgr normalize function that removes dead params and brings reset to front of param list?
@ -4240,7 +4335,7 @@ tcl::namespace::eval punk::ansi::ta {
#we also need to track the start of ST terminated code and not add it for replay (in the ansistring functions) #we also need to track the start of ST terminated code and not add it for replay (in the ansistring functions)
#variable re_ST {(?:\x1bX|\u0098|\x1b\^|\u009E|\x1b_|\u009F)(?:[^\x1b\007\u009c]*)(?:\x1b\\|\007|\u009c)} ;#downsides: early terminating with nests, mixes 7bit 8bit start/ends (does that exist in the wild?) #variable re_ST {(?:\x1bX|\u0098|\x1b\^|\u009E|\x1b_|\u009F)(?:[^\x1b\007\u009c]*)(?:\x1b\\|\007|\u009c)} ;#downsides: early terminating with nests, mixes 7bit 8bit start/ends (does that exist in the wild?)
#keep our 8bit/7bit start-end codes separate #keep our 8bit/7bit start-end codes separate
variable re_ST {(?:\x1bP|\x1bX|\x1b\^|\x1b_)(?:(?!\x1b\\|007).)*(?:\x1b\\|\007)|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)} variable re_ST {(?:\x1bP|\x1bX|\x1b\^|\x1b_)(?:(?!\x1b\\|\007).)*(?:\x1b\\|\007)|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)}
@ -4252,7 +4347,7 @@ tcl::namespace::eval punk::ansi::ta {
# -- --- --- --- # -- --- --- ---
#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.
variable re_ansi_detect {(?:\x1b(?:\((?:0|B)|\[(?:[\x20-\x2f\x30-\x3f]*[\x40-\x7e])|\](?:(?:[^\007]*)\007|(?:(?!\x1b\\).)*\x1b\\)|(?:P|X|\^|_)(?:(?:(?!\x1b\\|007).)*(?:\x1b\\|\007))|c|7|8|M|E|D|H|=|>|(?:#(?:3|4|5|6|8))))|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)|(?:\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]|(?:\u009d)(?:[^\u009c]*)?\u009c} variable re_ansi_detect {(?:\x1b(?:\((?:0|B)|\[(?:[\x20-\x2f\x30-\x3f]*[\x40-\x7e])|\](?:(?:[^\007]*)\007|(?:(?!\x1b\\).)*\x1b\\)|(?:P|X|\^|_)(?:(?:(?!\x1b\\|\007).)*(?:\x1b\\|\007))|c|7|8|M|E|D|H|=|>|(?:#(?:3|4|5|6|8))))|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)|(?:\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]|(?:\u009d)(?:[^\u009c]*)?\u009c}
# -- --- --- --- # -- --- --- ---
@ -5674,7 +5769,12 @@ tcl::namespace::eval punk::ansi::ansistring {
ENQ [list \x05 \u2405]\ ENQ [list \x05 \u2405]\
ACK [list \x06 \u2406]\ ACK [list \x06 \u2406]\
BEL [list \x07 \u2407]\ BEL [list \x07 \u2407]\
BS [list \x08 \u2408]\
HT [list \x09 \u2409]\
LF [list \x0a \u240a]\
VT [list \x0b \u240b]\
FF [list \x0c \u240c]\ FF [list \x0c \u240c]\
CR [list \x0d \u240d]\
SO [list \x0e \u240e]\ SO [list \x0e \u240e]\
SF [list \x0f \u240f]\ SF [list \x0f \u240f]\
DLE [list \x10 \u2410]\ DLE [list \x10 \u2410]\
@ -5688,12 +5788,15 @@ tcl::namespace::eval punk::ansi::ansistring {
CAN [list \x18 \u2418]\ CAN [list \x18 \u2418]\
EM [list \x19 \u2419]\ EM [list \x19 \u2419]\
SUB [list \x1a \u241a]\ SUB [list \x1a \u241a]\
ESC [list \x1b \u241b]\
FS [list \x1c \u241c]\ FS [list \x1c \u241c]\
GS [list \x1d \u241d]\ GS [list \x1d \u241d]\
RS [list \x1e \u241e]\ RS [list \x1e \u241e]\
US [list \x1f \u241f]\ US [list \x1f \u241f]\
SP [list \x20 \u2420]\
DEL [list \x7f \u2421]\ DEL [list \x7f \u2421]\
] ]
#alternate symbols for space #alternate symbols for space
# \u2422 Blank Symbol (b with forwardslash overly) # \u2422 Blank Symbol (b with forwardslash overly)
# \u2423 Open Box (square bracket facing up like a tray/box) # \u2423 Open Box (square bracket facing up like a tray/box)
@ -5836,6 +5939,7 @@ tcl::namespace::eval punk::ansi::ansistring {
-cr 1\ -cr 1\
-lf 0\ -lf 0\
-vt 0\ -vt 0\
-ff 1\
-ht 1\ -ht 1\
-bs 1\ -bs 1\
-sp 1\ -sp 1\
@ -5850,16 +5954,22 @@ tcl::namespace::eval punk::ansi::ansistring {
set opt_cr [tcl::dict::get $opts -cr] set opt_cr [tcl::dict::get $opts -cr]
set opt_lf [tcl::dict::get $opts -lf] set opt_lf [tcl::dict::get $opts -lf]
set opt_vt [tcl::dict::get $opts -vt] set opt_vt [tcl::dict::get $opts -vt]
set opt_ff [tcl::dict::get $opts -ff]
set opt_ht [tcl::dict::get $opts -ht] set opt_ht [tcl::dict::get $opts -ht]
set opt_bs [tcl::dict::get $opts -bs] set opt_bs [tcl::dict::get $opts -bs]
set opt_sp [tcl::dict::get $opts -sp] set opt_sp [tcl::dict::get $opts -sp]
# -- --- --- --- --- # -- --- --- --- ---
# -lf 2, -vt 2 and -ff 2 are useful for CRM mode (Show Control Character Mode) in the terminal - where a newline is expected to display after the character.
set visuals_opt $debug_visuals set visuals_opt $debug_visuals
set visuals_opt [dict remove $visuals_opt CR ESC LF VT FF HT BS SP]
if {$opt_esc} { if {$opt_esc} {
tcl::dict::set visuals_opt ESC [list \x1b \u241b] tcl::dict::set visuals_opt ESC [list \x1b \u241b]
} else {
tcl::dict::unset visuals_opt ESC
} }
if {$opt_cr} { if {$opt_cr} {
tcl::dict::set visuals_opt CR [list \x0d \u240d] tcl::dict::set visuals_opt CR [list \x0d \u240d]
@ -5870,9 +5980,20 @@ tcl::namespace::eval punk::ansi::ansistring {
if {$opt_lf == 2} { if {$opt_lf == 2} {
tcl::dict::set visuals_opt LF [list \x0a \u240a\n] tcl::dict::set visuals_opt LF [list \x0a \u240a\n]
} }
if {$opt_vt} { if {$opt_vt == 1} {
tcl::dict::set visuals_opt VT [list \x0b \u240b] tcl::dict::set visuals_opt VT [list \x0b \u240b]
} }
if {$opt_vt == 2} {
tcl::dict::set visuals_opt VT [list \x0b \u240b\n]
}
switch -exact -- $opt_ff {
1 {
tcl::dict::set visuals_opt FF [list \x0c \u240c]
}
2 {
tcl::dict::set visuals_opt FF [list \x0c \u240c\n]
}
}
if {$opt_ht} { if {$opt_ht} {
tcl::dict::set visuals_opt HT [list \x09 \u2409] tcl::dict::set visuals_opt HT [list \x09 \u2409]
} }

4
src/vfs/_vfscommon/modules/punk/basictelnet-0.1.0.tm

@ -531,7 +531,7 @@ namespace eval punk::basictelnet {
# -- --- --- --- # -- --- --- ---
set tailinfo "" set tailinfo ""
if {[string length $nextwaiting]} { if {[string length $nextwaiting]} {
set waitingdisplay [overtype::renderspace -wrap 1 -width 77 -height 1 "" [ansistring VIEW -lf 1 -vt 1 $nextwaiting]] set waitingdisplay [overtype::renderspace -cp437 1 -wrap 1 -width 77 -height 1 "" [ansistring VIEW -lf 1 -vt 1 $nextwaiting]]
set tailinfo "[a+ red]from waiting:\n $waitingdisplay[a]" set tailinfo "[a+ red]from waiting:\n $waitingdisplay[a]"
} }
::punk::basictelnet::add_debug "[a+ Yellow black]from stdin sending: [ansistring VIEW -lf 1 -vt 1 $chunk][a]\n$tailinfo\n" stdin $sock ::punk::basictelnet::add_debug "[a+ Yellow black]from stdin sending: [ansistring VIEW -lf 1 -vt 1 $chunk][a]\n$tailinfo\n" stdin $sock
@ -629,7 +629,7 @@ namespace eval punk::basictelnet {
#set rawview [ansistring VIEW -lf 1 -vt 1 [encoding convertfrom $encoding_guess $data]] #set rawview [ansistring VIEW -lf 1 -vt 1 [encoding convertfrom $encoding_guess $data]]
set rawview [ansistring VIEW -lf 1 -vt 1 $data] set rawview [ansistring VIEW -lf 1 -vt 1 $data]
#set viewblock [overtype::left -wrap 1 -width 78 -height 4 "" $rawview] #set viewblock [overtype::left -wrap 1 -width 78 -height 4 "" $rawview]
set viewblock [overtype::renderspace -experimental test_mode -wrap 1 -width 78 -height 4 "" $rawview] set viewblock [overtype::renderspace -cp437 1 -wrap 1 -width 78 -height 4 "" $rawview]
set lines [split $viewblock \n] set lines [split $viewblock \n]
if {[llength $lines] > 4} { if {[llength $lines] > 4} {
append debug_info [join [list {*}[lrange $lines 0 1] "...<[expr {[llength $lines] -4}] lines undisplayed>..." {*}[lrange $lines end-1 end]] \n] append debug_info [join [list {*}[lrange $lines 0 1] "...<[expr {[llength $lines] -4}] lines undisplayed>..." {*}[lrange $lines end-1 end]] \n]

358
src/vfs/_vfscommon/modules/punk/blockletter-0.1.0.tm

@ -0,0 +1,358 @@
# -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'pmix make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm
#
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# (C) 2024
#
# @@ Meta Begin
# Application punk::blockletter 0.1.0
# Meta platform tcl
# Meta license <unspecified>
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[manpage_begin shellspy_module_punk::blockletter 0 0.1.0]
#[copyright "2024"]
#[titledesc {Module API}] [comment {-- Name section and table of contents description --}]
#[moddesc {-}] [comment {-- Description at end of page heading --}]
#[require punk::blockletter]
#[keywords module]
#[description]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Overview]
#[para] overview of punk::blockletter
#[subsection Concepts]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection dependencies]
#[para] packages used by punk::blockletter
#[list_begin itemized]
package require Tcl 8.6-
package require textblock
#*** !doctools
#[item] [package {Tcl 8.6}]
#[item] [package {textblock}]
# #package require frobz
# #*** !doctools
# #[item] [package {frobz}]
#*** !doctools
#[list_end]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section API]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# oo::class namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#tcl::namespace::eval punk::blockletter::class {
#*** !doctools
#[subsection {Namespace punk::blockletter::class}]
#[para] class definitions
#if {[tcl::info::commands [tcl::namespace::current]::interface_sample1] eq ""} {
#*** !doctools
#[list_begin enumerated]
# oo::class create interface_sample1 {
# #*** !doctools
# #[enum] CLASS [class interface_sample1]
# #[list_begin definitions]
# method test {arg1} {
# #*** !doctools
# #[call class::interface_sample1 [method test] [arg arg1]]
# #[para] test method
# puts "test: $arg1"
# }
# #*** !doctools
# #[list_end] [comment {-- end definitions interface_sample1}]
# }
#*** !doctools
#[list_end] [comment {--- end class enumeration ---}]
#}
#}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Base namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::blockletter {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
#variable xyz
#*** !doctools
#[subsection {Namespace punk::blockletter}]
#[para] Core API functions for punk::blockletter
#[list_begin definitions]
#A 3x4 block font
variable default_frametype
set default_frametype {vl \u00a0 hl \u00a0 tlc \u00a0 trc \u00a0 blc \u00a0 brc \u00a0}
# colours in order for T c l T k
set logo_letter_colours [list Web-red Web-green Web-royalblue Web-purple Web-orange]
set logo_letter_colours [list Red Green Blue Purple Yellow]
proc logo {args} {
variable logo_letter_colours
variable default_frametype
set argd [punk::args::get_dict [tstr -return string {
-frametype -default {${$default_frametype}}
-outlinecolour -default "web-white"
-backgroundcolour -default {} -help "e.g Web-white
This argument is the name as accepted by punk::ansi::a+"
*values -min 0 -max 0
}] $args]
set f [dict get $argd opts -frametype]
set bd [dict get $argd opts -outlinecolour]
set bgansi [dict get $argd opts -backgroundcolour] ;#we use ta::detect to see if already ansi and apply as necessary
#standard red green blue purple yellow
lassign $logo_letter_colours c_0 c_1 c_2 c_3 c_4
set tc [merge_left_block [T -bg $c_0 -border $bd -frametype $f] [c -bg $c_1 -border $bd -frametype $f]]
set tk [merge_left_block [T -bg $c_3 -border $bd -frametype $f] [k_short -bg $c_4 -border $bd -frametype $f]]
set logo [textblock::join_basic -- $tc [l -bg $c_2 -border $bd -frametype $f] [textblock::block 2 8 " "] $tk]
if {$bgansi ne ""} {
lassign [textblock::size_as_list $logo] lwidth lheight
set w [expr {$lwidth + 2}]
set h [expr {$lheight + 2}]
if {![punk::ansi::ta::detect $bgansi]} {
set bgansi [punk::ansi::a+ $bgansi]
}
set logobg $bgansi[textblock::block $w $h " "][punk::ansi::a]
set topmargin [string repeat " " $w]
set lmargin [textblock::block 1 [expr {$h + 1}] " "]
set logo [overtype::left -transparent " " $logobg [textblock::join_basic -- $lmargin $topmargin\n$logo]]
}
return $logo
}
#for characters where it makes sense - offset left by 4 (1 'block' width)
proc merge_left {charleft textright} {
if {[string length $charleft] != 1} {
error "merge_left requires a single character as the charleft argument"
}
if {[textblock::height $charleft$textright] > 1} {
error "merge_left only operates on a plain char and a plain string with no newlines"
}
set rhs [textblock::join_basic -- [textblock::block 8 8 " "] [text $textright]]
#important to explicitly use -transparent " " (ordinary space) rather than -transparent 1 (any space?)
#This is because our frames have NBSP as filler to be non-transparent
return [overtype::left -transparent " " -overflow 1 [text $charleft] $rhs]
}
proc merge_left_block {blockleft blockright} {
set rhs [textblock::join_basic -- [textblock::block 8 8 " "] $blockright]
return [overtype::left -transparent " " -overflow 1 $blockleft $rhs]
}
proc T {args} {
set args [dict remove $args -width -height]
append out [lib::hbar {*}$args]\n
append out [textblock::join -- " " [lib::vbar {*}$args] " "]
}
proc c {args} {
set args [dict remove $args -width -height]
append out [textblock::block 12 2 " "]\n
append out [lib::hbar {*}$args]\n
append out [textblock::join -- [lib::block {*}$args] " "]\n
append out [lib::hbar {*}$args]
}
proc l {args} {
set args [dict remove $args -width -height]
append out [lib::vbar {*}[dict merge {-height 8} $args]]
}
#full height lower k
proc k {args} {
set args [dict remove $args -width -height]
set left [lib::vbar {*}[dict merge {-height 8} $args]]
set centre [textblock::block 4 4 " "]\n
append centre [lib::block {*}$args]\n
append centre [textblock::block 4 2 " "]
set right [textblock::block 4 2 " "]\n
append right [lib::block {*}$args]\n
append right [textblock::block 4 2 " "]\n
append right [lib::block {*}$args]
append out [textblock::join_basic -- $left $centre $right]
}
proc k_short {args} {
set args [dict remove $args -width -height]
append left [textblock::block 4 2 " "]\n
append left [lib::vbar {*}[dict merge {-height 6} $args]]
append centre [textblock::block 4 4 " "]\n
append centre [lib::block {*}$args]\n
append centre [textblock::block 4 2 " "]
append right [textblock::block 4 2 " "]\n
append right [lib::block {*}$args]\n
append right [textblock::block 4 2 " "]\n
append right [lib::block {*}$args]
append out [textblock::join_basic -- $left $centre $right]
}
proc text {args} {
variable default_frametype
set argd [punk::args::get_dict [tstr -return string {
-bgcolour -default "Web-red"
-bordercolour -default "web-white"
-frametype -default {${$default_frametype}}
*values -min 1 -max 1
str -help "Text to convert to blockletters
Requires terminal font to support relevant block characters"
"
}] $args]
set opts [dict get $argd opts]
set str [dict get $argd values str]
set str [string map {\r\n \n} $str]
set outblocks [list]
set literals [list \n]
foreach char [split $str ""] {
if {$char in $literals} {
lappend outblocks $char
continue
}
if {$char in [list \t \r]} {
lappend outblocks [textblock::block 1 8 $char]
continue
}
if {[info commands ::punk::blockletter::$char] ne ""} {
lappend outblocks [::punk::blockletter::$char {*}$opts]
} else {
lappend outblocks [textblock::block 12 8 $char]
}
}
return [textblock::join_basic -- {*}$outblocks]
}
#*** !doctools
#[list_end] [comment {--- end definitions namespace punk::blockletter ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Secondary API namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::blockletter::lib {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
tcl::namespace::path [tcl::namespace::parent]
#*** !doctools
#[subsection {Namespace punk::blockletter::lib}]
#[para] Secondary functions that are part of the API
#[list_begin definitions]
#proc utility1 {p1 args} {
# #*** !doctools
# #[call lib::[fun utility1] [arg p1] [opt {?option value...?}]]
# #[para]Description of utility1
# return 1
#}
proc block {args} {
upvar ::punk::blockletter::default_frametype ft
set argd [punk::args::get_dict [tstr -return string {
-height -default 2
-width -default 4
-frametype -default {${$ft}}
-bgcolour -default "Web-red"
-bordercolour -default "web-white"
*values -min 0 -max 0
}] $args]
set bg [dict get $argd opts -bgcolour]
set bd [dict get $argd opts -bordercolour]
set h [dict get $argd opts -height]
set w [dict get $argd opts -width]
set f [dict get $argd opts -frametype]
#a frame will usually be filled with empty spaces if content not specified
#fill the frame with a non-space so we can do transparent overtypes using ordinary space as the transparency character
set w_in [expr {$w -2}]
set h_in [expr {$h -2}]
if {$w_in > 0 && $h_in > 0} {
set inner [textblock::block $w_in $h_in \u00a0] ;#NBSP
textblock::frame -type $f -height $h -width $w -ansiborder [a+ $bd $bg] -ansibase [a+ $bg] $inner
} else {
#important to use no content arg - as empty string has 'height' of 1 in the textblock context (min height of any string is 1 row in the console)
textblock::frame -type $f -height $h -width $w -ansiborder [a+ $bd $bg] -ansibase [a+ $bg]
}
}
proc hbar {args} {
upvar ::punk::blockletter::default_frametype ft
set defaults [dict create\
-height 2\
-width 12\
-frametype $ft\
]
set opts [dict merge $defaults $args]
block {*}$opts
}
proc vbar {args} {
upvar ::punk::blockletter::default_frametype ft
#default height a multiple of default hbar/block height
set defaults [dict create\
-height 6\
-width 4\
-frametype $ft\
]
set opts [dict merge $defaults $args]
[namespace current]::block {*}$opts
}
#*** !doctools
#[list_end] [comment {--- end definitions namespace punk::blockletter::lib ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Internal]
#tcl::namespace::eval punk::blockletter::system {
#*** !doctools
#[subsection {Namespace punk::blockletter::system}]
#[para] Internal functions that are not part of the API
#}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide punk::blockletter [tcl::namespace::eval punk::blockletter {
variable pkg punk::blockletter
variable version
set version 0.1.0
}]
return
#*** !doctools
#[manpage_end]

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

@ -1021,8 +1021,8 @@ namespace eval punk::console {
#It's known this isn't always the case - but things like textutil::untabify2 take only a single value #It's known this isn't always the case - but things like textutil::untabify2 take only a single value
#on some systems test_char_width is a similar speed to get_tabstop_apparent_width - but on some test_char_width is much slower #on some systems test_char_width is a similar speed to get_tabstop_apparent_width - but on some test_char_width is much slower
#we will use test_char_width as a fallback #we will use test_char_width as a fallback
proc get_tabstop_apparent_width {} { proc get_tabstop_apparent_width {{inoutchannels {stdin stdout}}} {
set tslist [get_tabstops] set tslist [get_tabstops $inoutchannels]
if {![llength $tslist]} { if {![llength $tslist]} {
#either terminal failed to report - or none set. #either terminal failed to report - or none set.
set testw [test_char_width \t] set testw [test_char_width \t]
@ -1075,23 +1075,37 @@ namespace eval punk::console {
return [split [get_cursor_pos $inoutchannels] ";"] return [split [get_cursor_pos $inoutchannels] ";"]
} }
#todo - determine cursor on/off state before the call to restore properly. May only be possible #todo - determine cursor on/off state before the call to restore properly.
proc get_size {{inoutchannels {stdin stdout}}} { proc get_size {{inoutchannels {stdin stdout}}} {
lassign $inoutchannels in out lassign $inoutchannels in out
#we can't reliably use [chan names] for stdin,stdout. There could be stacked channels and they may have a names such as file22fb27fe810 #we can't reliably use [chan names] for stdin,stdout. There could be stacked channels and they may have a names such as file22fb27fe810
#chan eof is faster whether chan exists or not than #chan eof is faster whether chan exists or not than
if {[catch {chan eof $in} is_eof]} { if {[catch {chan eof $out} is_eof]} {
error "punk::console::get_size input channel $in seems to be closed ([info level 1])" error "punk::console::get_size output channel $out seems to be closed ([info level 1])"
} else { } else {
if {$is_eof} { if {$is_eof} {
error "punk::console::get_size eof on input channel $in ([info level 1])" error "punk::console::get_size eof on output channel $out ([info level 1])"
} }
} }
if {[catch {chan eof $out} is_eof]} { #we don't need to care about the input channel if chan configure on the output can give us the info.
error "punk::console::get_size output channel $out seems to be closed ([info level 1])" #short circuit ansi cursor movement method if chan configure supports the -winsize value
set outconf [chan configure $out]
if {[dict exists $outconf -winsize]} {
#this mechanism is much faster than ansi cursor movements
#REVIEW check if any x-platform anomalies with this method?
#can -winsize key exist but contain erroneous info? We will check that we get 2 ints at least
lassign [dict get $outconf -winsize] cols lines
if {[string is integer -strict $cols] && [string is integer -strict $lines]} {
return [list columns $cols rows $lines]
}
#continue on to ansi mechanism if we didn't get 2 ints
}
if {[catch {chan eof $in} is_eof]} {
error "punk::console::get_size input channel $in seems to be closed ([info level 1])"
} else { } else {
if {$is_eof} { if {$is_eof} {
error "punk::console::get_size eof on output channel $out ([info level 1])" error "punk::console::get_size eof on input channel $in ([info level 1])"
} }
} }
@ -1114,18 +1128,28 @@ namespace eval punk::console {
} }
} }
#faster - but uses cursor_save - which we may want to avoid if calling during another operation which uses cursor save/restore #faster than get_size when it is using ansi mechanism - but uses cursor_save - which we may want to avoid if calling during another operation which uses cursor save/restore
proc get_size_cursorrestore {} { proc get_size_cursorrestore {{inoutchannels {stdin stdout}}} {
lassign $inoutchannels in out
#we use the same shortcircuit mechanism as get_size to avoid ansi at all if the output channel will give us the info directly
set outconf [chan configure $out]
if {[dict exists $outconf -winsize]} {
lassign [dict get $outconf -winsize] cols lines
if {[string is integer -strict $cols] && [string is integer -strict $lines]} {
return [list columns $cols rows $lines]
}
}
if {[catch { if {[catch {
#some terminals (conemu on windows) scroll the viewport when we make a big move down like this - a move to 1 1 immediately after cursor_save doesn't seem to fix that. #some terminals (conemu on windows) scroll the viewport when we make a big move down like this - a move to 1 1 immediately after cursor_save doesn't seem to fix that.
#This issue also occurs when switching back from the alternate screen buffer - so perhaps that needs to be addressed elsewhere. #This issue also occurs when switching back from the alternate screen buffer - so perhaps that needs to be addressed elsewhere.
puts -nonewline [punk::ansi::cursor_off][punk::ansi::cursor_save_dec][punk::ansi::move 2000 2000] puts -nonewline $out [punk::ansi::cursor_off][punk::ansi::cursor_save_dec][punk::ansi::move 2000 2000]
lassign [get_cursor_pos_list] lines cols lassign [get_cursor_pos_list $inoutchannels] lines cols
puts -nonewline [punk::ansi::cursor_restore][punk::console::cursor_on];flush stdout puts -nonewline $out [punk::ansi::cursor_restore][punk::console::cursor_on];flush $out
set result [list columns $cols rows $lines] set result [list columns $cols rows $lines]
} errM]} { } errM]} {
puts -nonewline [punk::ansi::cursor_restore_dec] puts -nonewline $out [punk::ansi::cursor_restore_dec]
puts -nonewline [punk::ansi::cursor_on] puts -nonewline $out [punk::ansi::cursor_on]
error "$errM" error "$errM"
} else { } else {
return $result return $result
@ -1175,7 +1199,7 @@ namespace eval punk::console {
} }
if {!$emit} { if {!$emit} {
puts -nonewline stdout \033\[2K\033\[1G ;#2K erase line 1G cursor at col1 puts -nonewline stdout \033\[2K\033\[1G ;#2K erase line, 1G cursor at col1
} }
set response "" set response ""
if {[catch { if {[catch {
@ -1405,12 +1429,12 @@ namespace eval punk::console {
proc cursor_save {} { proc cursor_save {} {
#*** !doctools #*** !doctools
#[call [fun cursor_save]] #[call [fun cursor_save]]
puts -nonewline \x1b\[s puts -nonewline stdout \x1b\[s
} }
proc cursor_restore {} { proc cursor_restore {} {
#*** !doctools #*** !doctools
#[call [fun cursor_restore]] #[call [fun cursor_restore]]
puts -nonewline \x1b\[u puts -nonewline stdout \x1b\[u
} }
#DEC equivalents of cursor_save/cursor_restore - perhaps more widely supported? #DEC equivalents of cursor_save/cursor_restore - perhaps more widely supported?
proc cursor_save_dec {} { proc cursor_save_dec {} {

4
src/vfs/_vfscommon/modules/punk/experiment-0.1.0.tm

@ -474,12 +474,12 @@ namespace eval punk::experiment {
proc render1 {} { proc render1 {} {
variable b1 variable b1
variable b2 variable b2
overtype::renderspace -overflow 1 -startcolumn 7 $b1 $b2 overtype::renderspace -expand_right 1 -startcolumn 7 $b1 $b2
} }
proc render2 {} { proc render2 {} {
variable b1 variable b1
variable b3 variable b3
overtype::renderspace -overflow 1 -transparent @ $b1 $b3 overtype::renderspace -expand_right 1 -transparent @ $b1 $b3
} }
oo::class create c1 { oo::class create c1 {

8
src/vfs/_vfscommon/modules/punk/mix/commandset/project-0.1.0.tm

@ -919,10 +919,18 @@ namespace eval punk::mix::commandset::project {
if {[llength $col_states]} { if {[llength $col_states]} {
foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes s $col_states { foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes s $col_states {
if {![file exists $wd]} {
set row [punk::ansi::a+ strike red]$row[a]
set wd [punk::ansi::a+ red]$wd[a]
}
append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode] [overtype::left $col6 $s]" \n append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode] [overtype::left $col6 $s]" \n
} }
} else { } else {
foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes { foreach row $col_rowids wd $workdirs db $col_fnames dup $col_dupids pname $col_pnames pcode $col_pcodes {
if {![file exists $wd]} {
set row [punk::ansi::a+ strike red]$row[a]
set wd [punk::ansi::a+ red]$wd[a]
}
append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode]" \n append msg "[overtype::right $col0 $row] [overtype::left $col1 $wd] [overtype::left $col2 $db] [overtype::right $col3 $dup] [overtype::left $col4 $pname] [overtype::left $col5 $pcode]" \n
} }
} }

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

@ -431,7 +431,7 @@ proc repl::post_operations {} {
uplevel #0 {eval $::repl::running_script} uplevel #0 {eval $::repl::running_script}
} }
#todo - tidyup so repl could be restarted #todo - tidyup so repl could be restarted
set repl::post_operations_done 0 set ::repl::post_operations_done 0
} }
@ -860,7 +860,7 @@ namespace eval punk::repl::class {
set o_cursor_col $line_nextchar_col set o_cursor_col $line_nextchar_col
} }
set mergedinfo [overtype::renderline -info 1 -overflow 1 -insert_mode $o_insert_mode -cursor_column $o_cursor_col -cursor_row $o_cursor_row $underlay $new0] set mergedinfo [overtype::renderline -info 1 -expand_right 1 -insert_mode $o_insert_mode -cursor_column $o_cursor_col -cursor_row $o_cursor_row $underlay $new0]
set result [dict get $mergedinfo result] set result [dict get $mergedinfo result]
set o_insert_mode [dict get $mergedinfo insert_mode] set o_insert_mode [dict get $mergedinfo insert_mode]
@ -934,13 +934,13 @@ namespace eval punk::repl::class {
break break
} }
} }
#puts stderr "overtype::renderline -info 1 -overflow 1 -insert_mode $o_insert_mode -cursor_column $o_cursor_col -cursor_row $o_cursor_row $activeline '$p'" #puts stderr "overtype::renderline -info 1 -expand_right 1 -insert_mode $o_insert_mode -cursor_column $o_cursor_col -cursor_row $o_cursor_row $activeline '$p'"
set underlay $activeline set underlay $activeline
set line_nextchar_col [expr {[punk::char::string_width $underlay] + 1}] set line_nextchar_col [expr {[punk::char::string_width $underlay] + 1}]
if {$o_cursor_col > $line_nextchar_col} { if {$o_cursor_col > $line_nextchar_col} {
set o_cursor_col $line_nextchar_col set o_cursor_col $line_nextchar_col
} }
set mergedinfo [overtype::renderline -info 1 -overflow 1 -insert_mode $o_insert_mode -cursor_column $o_cursor_col -cursor_row $o_cursor_row $underlay $p] set mergedinfo [overtype::renderline -info 1 -expand_right 1 -insert_mode $o_insert_mode -cursor_column $o_cursor_col -cursor_row $o_cursor_row $underlay $p]
set debug "add_chunk$i" set debug "add_chunk$i"
append debug \n $mergedinfo append debug \n $mergedinfo
append debug \n "input:[ansistring VIEW -lf 1 -vt 1 $p]" append debug \n "input:[ansistring VIEW -lf 1 -vt 1 $p]"
@ -1120,7 +1120,7 @@ namespace eval punk::repl::class {
} else { } else {
set charhighlight [punk::ansi::a+ reverse]$char_at_cursor[a] set charhighlight [punk::ansi::a+ reverse]$char_at_cursor[a]
} }
set cursorline [overtype::renderline -transparent 1 -insert_mode 0 -overflow 0 $cursorline $prefix$charhighlight$suffix] set cursorline [overtype::renderline -transparent 1 -insert_mode 0 -expand_right 0 $cursorline $prefix$charhighlight$suffix]
lset lines $o_cursor_row-1 $cursorline lset lines $o_cursor_row-1 $cursorline
} }
@ -1921,7 +1921,7 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config
if {[info complete $commandstr] && [string index $commandstr end] ne "\\"} { if {[info complete $commandstr] && [string index $commandstr end] ne "\\"} {
#set commandstr [overtype::renderline -overflow 1 "" $commandstr] #set commandstr [overtype::renderline -expand_right 1 "" $commandstr]
set ::repl::output_stdout "" set ::repl::output_stdout ""

46
src/vfs/_vfscommon/modules/punk/repo-0.1.1.tm

@ -134,13 +134,30 @@ namespace eval punk::repo {
} }
interp alias "" fossil "" punk::repo::fossil_proxy interp alias "" fossil "" punk::repo::fossil_proxy
# ---
# Calling auto_execok on an external tool can be too slow to do during package load (e.g could be 150ms)
#safe interps can't call auto_execok #safe interps can't call auto_execok
#At least let them load the package even though much of it may be unusable depending on the safe configuration #At least let them load the package even though much of it may be unusable depending on the safe configuration
catch { #catch {
if {[auto_execok fossil] ne ""} { # if {[auto_execok fossil] ne ""} {
interp alias "" FOSSIL "" {*}[auto_execok fossil] # interp alias "" FOSSIL "" {*}[auto_execok fossil]
} # }
#}
# ---
# ----------
#
#uppercase FOSSIL to bypass fossil as alias to fossil_proxy
proc establish_FOSSIL {args} {
if {![info exists ::auto_execs(FOSSIL)]} {
set ::auto_execs(FOSSIL) [auto_execok fossil] ;#may fail in safe interp
}
interp alias "" FOSSIL "" ;#delete establishment alias
FOSSIL {*}$args
} }
interp alias "" FOSSIL "" punk::repo::establish_FOSSIL
# ----------
proc askuser {question} { proc askuser {question} {
if {![catch {package require punk::lib}]} { if {![catch {package require punk::lib}]} {
@ -370,7 +387,16 @@ namespace eval punk::repo {
} }
if {$repodir eq ""} { if {$repodir eq ""} {
error "workingdir_state error: No repository found at or above path '$abspath'" puts stderr "workingdir_state error: No repository found at or above path '$abspath'"
puts stderr "args: $args"
dict set resultdict revision {}
dict set resultdict revision_iso8601 {}
dict set resultdict paths {}
dict set resultdict ahead ""
dict set resultdict behind ""
dict set resultdict error {reason "no_repo_found"}
dict set resultdict repotype none
return $resultdict
} }
set subpath [punk::path::relative $repodir $abspath] set subpath [punk::path::relative $repodir $abspath]
if {$subpath eq "."} { if {$subpath eq "."} {
@ -644,6 +670,16 @@ namespace eval punk::repo {
set path_count_fields [list unchanged changed new missing extra] set path_count_fields [list unchanged changed new missing extra]
set state_fields [list ahead behind repodir subpath repotype revision revision_iso8601] set state_fields [list ahead behind repodir subpath repotype revision revision_iso8601]
set dresult [dict create] set dresult [dict create]
if {[dict exists $repostate error]} {
foreach f $state_fields {
dict set dresult $f ""
}
foreach f $path_count_fields {
dict set dresult $f ""
}
#todo?
return $dresult
}
foreach f $state_fields { foreach f $state_fields {
dict set dresult $f [dict get $repostate $f] dict set dresult $f [dict get $repostate $f]
} }

BIN
src/vfs/_vfscommon/modules/test/tomlish-1.1.1.tm

Binary file not shown.

137
src/vfs/_vfscommon/modules/textblock-0.1.1.tm

@ -60,6 +60,8 @@ package require punk::ansi
package require punk::lib package require punk::lib
catch {package require patternpunk} catch {package require patternpunk}
package require overtype package require overtype
#safebase interps as at 2024-08 can't access deeper paths - even though they are below the supposed safe list.
package require term::ansi::code::macros ;#required for frame if old ansi g0 used - review - make package optional? package require term::ansi::code::macros ;#required for frame if old ansi g0 used - review - make package optional?
package require textutil package require textutil
@ -1931,13 +1933,6 @@ tcl::namespace::eval textblock {
set hval $ansibase_header$header ;#no reset set hval $ansibase_header$header ;#no reset
set rowh [my header_height $hrow] set rowh [my header_height $hrow]
#set h_lines [lrepeat $rowh $hcell_line_blank]
#set hcell_blank [join $h_lines \n]
#set hval_lines [split $hval \n]
#set hval_lines [lrange $hval_lines 0 $rowh-1]
#set hval_block [join $hval_lines \n]
#set headercell [overtype::left -experimental test_mode $ansibase_header$hcell_blank$RST $hval_block]
if {$hrow == 0} { if {$hrow == 0} {
set hlims $header_boxlimits_toprow set hlims $header_boxlimits_toprow
set rowpos "top" set rowpos "top"
@ -2144,7 +2139,7 @@ tcl::namespace::eval textblock {
#puts $hblock #puts $hblock
#puts "==>hval:'$hval'[a]" #puts "==>hval:'$hval'[a]"
#puts "==>hval:'[ansistring VIEW $hval]'" #puts "==>hval:'[ansistring VIEW $hval]'"
#set spanned_frame [overtype::renderspace -experimental test_mode -transparent 1 $spanned_frame $hblock] #set spanned_frame [overtype::renderspace -transparent 1 $spanned_frame $hblock]
#spanned values default left - todo make configurable #spanned values default left - todo make configurable
@ -3502,11 +3497,11 @@ tcl::namespace::eval textblock {
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 -expand_right 1 -transparent $TSUB $table[unset table] $nextcol]
#JMN #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] #set table [overtype::renderspace -expand_right 1 -transparent \uFFFF $table $nextcol]
} }
incr padwidth $bodywidth incr padwidth $bodywidth
incr colposn incr colposn
@ -3607,14 +3602,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 table [overtype::renderspace -startcolumn [expr {$padwidth + 1}] -overflow 1 -experimental test_mode -transparent $TSUB $table $nextcol] set table [overtype::renderspace -startcolumn [expr {$padwidth + 1}] -expand_right 1 -transparent $TSUB $table $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 table [overtype::renderspace -overflow 1 -experimental test_mode -transparent \uFFFF $table $nextcol]
} }
incr padwidth $bodywidth incr padwidth $bodywidth
incr colposn incr colposn
@ -3724,7 +3712,7 @@ tcl::namespace::eval textblock {
lappend body_blocks $nextcol_body lappend body_blocks $nextcol_body
} else { } else {
if {$headerheight > 0} { if {$headerheight > 0} {
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]] set header_build [overtype::renderspace -startcolumn [expr {$padwidth + 1}] -expand_right 1 -transparent $TSUB $header_build[unset header_build] $nextcol_header[unset nextcol_header]]
} }
lappend body_blocks $nextcol_body 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]
@ -4057,7 +4045,7 @@ tcl::namespace::eval textblock {
if append is chosen the new values will always start at the first column" if append is chosen the new values will always start at the first column"
-columns -default "" -type integer -help "Number of table columns -columns -default "" -type integer -help "Number of table columns
Will default to 2 if not using an existing -table object" Will default to 2 if not using an existing -table object"
*values *values -min 0 -max 1
datalist -default {} -help "flat list of table cell values which will be wrapped based on -columns value" datalist -default {} -help "flat list of table cell values which will be wrapped based on -columns value"
}] $args] }] $args]
set opts [dict get $argd opts] set opts [dict get $argd opts]
@ -4408,6 +4396,14 @@ tcl::namespace::eval textblock {
return [tcl::dict::create width $width height $height] ;#maintain order in 'image processing' standard width then height - caller may use lassign [tcl::dict::values [blocksize <data>]] width height return [tcl::dict::create width $width height $height] ;#maintain order in 'image processing' standard width then height - caller may use lassign [tcl::dict::values [blocksize <data>]] width height
} }
proc size_as_opts {textblock} {
set sz [size $textblock]
return [dict create -width [dict get $sz width] -height [dict get $sz height]]
}
proc size_as_list {textblock} {
set sz [size $textblock]
return [list [dict get $sz width] [dict get $sz height]]
}
#must be able to handle block as string with or without newlines #must be able to handle block as string with or without newlines
#if no newlines - attempt to treat as a list #if no newlines - attempt to treat as a list
#must handle whitespace-only string,list elements, and/or lines. #must handle whitespace-only string,list elements, and/or lines.
@ -5132,6 +5128,7 @@ tcl::namespace::eval textblock {
[punk::lib::list_as_lines -- [lrepeat 8 " | "]] [punk::lib::list_as_lines -- [lrepeat 8 " | "]]
} }
proc table {args} { proc table {args} {
#todo - use punk::args
upvar ::textblock::class::opts_table_defaults toptdefaults upvar ::textblock::class::opts_table_defaults toptdefaults
set defaults [tcl::dict::create\ set defaults [tcl::dict::create\
-rows [list]\ -rows [list]\
@ -5183,7 +5180,7 @@ tcl::namespace::eval textblock {
} }
variable frametypes variable frametypes
set frametypes [list light heavy arc double block block1 block2 ascii altg] set frametypes [list light heavy arc double block block1 block2 block2hack ascii altg]
#class::table needs to be able to determine valid frametypes #class::table needs to be able to determine valid frametypes
proc frametypes {} { proc frametypes {} {
variable frametypes variable frametypes
@ -5192,7 +5189,7 @@ tcl::namespace::eval textblock {
proc frametype {f} { proc frametype {f} {
#set custom_keys [list hl hlt hlb vl vll vlr tlc trc blc brc] #set custom_keys [list hl hlt hlb vl vll vlr tlc trc blc brc]
switch -- $f { switch -- $f {
light - heavy - arc - double - block - block1 - block2 - ascii - altg { light - heavy - arc - double - block - block1 - block2 - block2hack - ascii - altg {
return [tcl::dict::create category predefined type $f] return [tcl::dict::create category predefined type $f]
} }
default { default {
@ -5213,7 +5210,7 @@ tcl::namespace::eval textblock {
set is_custom_dict_ok 0 set is_custom_dict_ok 0
} }
if {!$is_custom_dict_ok} { if {!$is_custom_dict_ok} {
error "frame option -type must be one of known types: $textblock::frametypes or a dictionary with any of keys hl,hlt,hlb,vl,vll,vlr,tlc,trc,blc,brc" error "frame option -type must be one of known types: $::textblock::frametypes or a dictionary with any of keys hl,hlt,hlb,vl,vll,vlr,tlc,trc,blc,brc"
} }
set default_custom [tcl::dict::create hl " " vl " " 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] set custom_frame [tcl::dict::merge $default_custom $f]
@ -6323,9 +6320,12 @@ tcl::namespace::eval textblock {
set vlr \u2595 ;# right one eighth block set vlr \u2595 ;# right one eighth block
set vll \u258f ;# left one eighth block set vll \u258f ;# left one eighth block
#some terminals (on windows as at 2024) miscount width of these single-width blocks internally
#resulting in extra spacing that is sometimes well removed from the character itself (e.g at next ansi reset)
#This was fixed in windows-terminal based systems (2021) but persists in others.
#https://github.com/microsoft/terminal/issues/11694
set tlc \U1fb7d ;#legacy block set tlc \U1fb7d ;#legacy block
set trc \U1fb7e ;#legacy block set trc \U1fb7e ;#legacy block
set blc \U1fb7c ;#legacy block set blc \U1fb7c ;#legacy block
set brc \U1fb7f ;#legacy block set brc \U1fb7f ;#legacy block
@ -6336,6 +6336,42 @@ tcl::namespace::eval textblock {
set vlrj $vlr set vlrj $vlr
} }
block2hack {
#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 vlr \u2595 ;# right one eighth block
set vll \u258f ;# left one eighth block
#see comments in block2 regarding the problems in some terminals that this *may* hack around to some extent.
#the caller probably only needs block2hack if block2 doesn't work
#1)
#review - this hack looks sort of promising - but overtype::renderline needs fixing ?
#set tlc \U1fb7d\b ;#legacy block
#set trc \U1fb7e\b ;#legacy block
#set blc \U1fb7c\b ;#legacy block
#set brc \U1fb7f\b ;#legacy block
#2) - works on cmd.exe and some others
# a 'privacy message' is 'probably' also not supported on the old terminal but is on newer ones
#known exception - conemu on windows - displays junk for various ansi codes - (and slow terminal anyway)
#this hack has a reasonable chance of working
#except that the punk overtype library does recognise PMs
#A single backspace however is an unlikely and generally unuseful PM - so there is a corresponding hack in the renderline system to pass this PM through!
#ugly - in that we don't know the application specifics of what the PM data contains and where it's going.
set tlc \U1fb7d\x1b^\b\x1b\\ ;#legacy block
set trc \U1fb7e\x1b^\b\x1b\\ ;#legacy block
set blc \U1fb7c\x1b^\b\x1b\\ ;#legacy block
set brc \U1fb7f\x1b^\b\x1b\\ ;#legacy block
#horizontal and vertical bar joins
set hltj $hlt
set hlbj $hlb
set vllj $vll
set vlrj $vlr
}
block { block {
set hlt \u2580 ;#upper half set hlt \u2580 ;#upper half
set hlb \u2584 ;#lower half set hlb \u2584 ;#lower half
@ -6357,7 +6393,7 @@ tcl::namespace::eval textblock {
set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "] ;#only default the general types - these form defaults for more specific types if they're missing set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "] ;#only default the general types - these form defaults for more specific types if they're missing
if {[llength $f] % 2 != 0} { if {[llength $f] % 2 != 0} {
#todo - retrieve usage from punk::args #todo - retrieve usage from punk::args
error "textblock::frametype frametype '$f' is not one of the predefined frametypes: $textblock::frametypes and does not appear to be a dictionary for a custom frametype" error "textblock::frametype frametype '$f' is not one of the predefined frametypes: $::textblock::frametypes and does not appear to be a dictionary for a custom frametype"
} }
#unknown order of keys specified by user - validate before creating vars as we need more general elements to be available as defaults #unknown order of keys specified by user - validate before creating vars as we need more general elements to be available as defaults
dict for {k v} $f { dict for {k v} $f {
@ -6488,7 +6524,11 @@ tcl::namespace::eval textblock {
-ellipsis 1\ -ellipsis 1\
-usecache 1\ -usecache 1\
-buildcache 1\ -buildcache 1\
-pad 1\
-crm_mode 0\
] ]
#-pad 1 is default so that simple 'textblock::frame "[a+ Red]a \nbbb[a]" extends the bg colour on the short ragged lines (and empty lines)
# for ansi art - -pad 0 is likely to be preferable
set expect_optval 0 set expect_optval 0
set argposn 0 set argposn 0
@ -6527,7 +6567,12 @@ tcl::namespace::eval textblock {
#use -buildcache 1 with -usecache 0 for debugging cache issues so we can inspect using textblock::frame_cache #use -buildcache 1 with -usecache 0 for debugging cache issues so we can inspect using textblock::frame_cache
foreach {k v} $arglist { foreach {k v} $arglist {
switch -- $k { switch -- $k {
-etabs - -type - -boxlimits - -boxmap - -joins - -title - -subtitle - -width - -height - -ansiborder - -ansibase - -blockalign - -textalign - -ellipsis - -usecache - -buildcache { -etabs - -type - -boxlimits - -boxmap - -joins
- -title - -subtitle - -width - -height
- -ansiborder - -ansibase
- -blockalign - -textalign - -ellipsis
- -crm_mode
- -usecache - -buildcache - -pad {
tcl::dict::set opts $k $v tcl::dict::set opts $k $v
} }
default { default {
@ -6543,11 +6588,13 @@ tcl::namespace::eval textblock {
set opt_boxmap [tcl::dict::get $opts -boxmap] set opt_boxmap [tcl::dict::get $opts -boxmap]
set opt_usecache [tcl::dict::get $opts -usecache] set opt_usecache [tcl::dict::get $opts -usecache]
set opt_buildcache [tcl::dict::get $opts -buildcache] set opt_buildcache [tcl::dict::get $opts -buildcache]
set opt_pad [tcl::dict::get $opts -pad]
set opt_crm_mode [tcl::dict::get $opts -crm_mode]
set usecache $opt_usecache ;#may need to override set usecache $opt_usecache ;#may need to override
set buildcache $opt_buildcache set buildcache $opt_buildcache
set custom_keys [list hl hlt hlb vl vll vlr tlc trc blc brc] set custom_keys [list hl hlt hlb vl vll vlr tlc trc blc brc]
set known_frametypes $frametypes ;# light, heavey etc as defined in textblock::frametypes variable set known_frametypes $frametypes ;# light, heavey etc as defined in the ::textblock::frametypes variable
set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "] set default_custom [tcl::dict::create hl " " vl " " tlc " " trc " " blc " " brc " "]
lassign [textblock::frametype $opt_type] _cat category _type ftype lassign [textblock::frametype $opt_type] _cat category _type ftype
@ -6686,6 +6733,19 @@ tcl::namespace::eval textblock {
} }
} }
set contents [tcl::string::map [list \r\n \n] $contents] set contents [tcl::string::map [list \r\n \n] $contents]
if {$opt_crm_mode} {
if {$opt_height eq ""} {
set h [textblock::height $contents]
} else {
set h [expr {$opt_height -2}]
}
if {$opt_width eq ""} {
set w [textblock::width $contents]
} else {
set w [expr {$opt_width -2}]
}
set contents [overtype::renderspace -crm_mode 1 -wrap 1 -width $w -height $h "" $contents]
}
set actual_contentwidth [textblock::width $contents] ;#length of longest line in contents (contents can be ragged) set actual_contentwidth [textblock::width $contents] ;#length of longest line in contents (contents can be ragged)
set actual_contentheight [textblock::height $contents] set actual_contentheight [textblock::height $contents]
} else { } else {
@ -7134,15 +7194,22 @@ tcl::namespace::eval textblock {
append contents [::join [lrepeat $diff \n] ""] append contents [::join [lrepeat $diff \n] ""]
} }
set paddedcontents [textblock::pad $contents -which $pad -within_ansi 1] ;#autowidth to width of content (should match cache_patternwidth) if {$opt_pad} {
set paddedwidth [textblock::widthtopline $paddedcontents] set paddedcontents [textblock::pad $contents -which $pad -within_ansi 1] ;#autowidth to width of content (should match cache_patternwidth)
set paddedwidth [textblock::widthtopline $paddedcontents]
#review - horizontal truncation #review - horizontal truncation
if {$paddedwidth > $cache_patternwidth} { if {$paddedwidth > $cache_patternwidth} {
set paddedcontents [overtype::renderspace -width $cache_patternwidth "" $paddedcontents] set paddedcontents [overtype::renderspace -width $cache_patternwidth "" $paddedcontents]
}
#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
} else {
set cwidth [textblock::width $contents]
if {$cwidth > $cache_patternwidth} {
set contents [overtype::renderspace -width $cache_patternwidth "" $contents]
}
set contentblock [textblock::join -- $contents]
} }
#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]

111
src/vfs/critcl.vfs/README.md

@ -0,0 +1,111 @@
# Compiled Runtime In Tcl
* Welcome to the C Runtime In Tcl, CriTcl for short, a system to
build C extension packages for Tcl on the fly, from C code
embedded within Tcl scripts, for all who wish to make their code
go faster.
# Website
* The main website of this project is http://andreas-kupries.github.io/critcl
It provides access to pre-made binaries and archives for various
platforms, and the full documentation, especially the guides to
building and using Critcl.
Because of the latter this document contains only the most basic
instructions on getting, building, and using Critcl.
# Versions
* Version 3 is the actively developed version of Critcl, with several
new features, listed in section **New Features**, below. This version
has changes to the public API which make it incompatible with packages
using Critcl version 2.x, or earlier.
* The last of version 2 is 2.1, available at the same-named tag in the
repository. This version is not developed anymore.
# Getting, Building, and Using Critcl
* Retrieve the sources:
```% git clone http://github.com/andreas-kupries/critcl```
Your working directory now contains a directory ```critcl```.
* Build and install it:
Install requisites: cmdline, md5; possibly one of tcllibc, Trf, md5c to accelerate md5.
```% cd critcl```
```% tclsh ./build.tcl install```
The generated packages are placed into the **[info library]** directory
of the **tclsh** used to run build.tcl. The **critcl** application script
is put into the directory of the **tclsh** itself (and modified to
use this executable). This may require administrative (root) permissions,
depending on the system setup.
* It is expected that a working C compiler is available. Installation and
setup of such a compiler is platform and vendor specific, and instructions
for doing so are very much outside of scope for this document. Please find
and read the documentation, how-tos, etc. for your platform or vendor.
* With critcl installed try out one of the examples:
```% cd examples/stack```
```% critcl -keep -cache B -pkg cstack.tcl```
```% critcl -keep -cache B -pkg stackc.tcl```
```% tclsh```
```> lappend auto_path [pwd]/lib```
```> package require stackc```
```> stackc create S```
```> S push FOO```
```> S size```
```> S destroy```
```> exit```
```%```
# New Features
* Declaration, export and import of C-APIs through stubs tables.
* Generation of source packages from critcl-based code containing a
TEA-based buildsystem wrapped around the raw critcl.
* Declaration, initializaton and use of user-specified configuration
options. An important use is the declaration and use of custom
build configurations, like 'link a 3rd party library dynamically,
statically, build it from copy of its sources, etc.', etc.
* This is of course not everything. For the details please read the
Changes sections of the documentation.
# Documentation
* Too much to cover here. Please go to http://andreas-kupries.github.io/critcl
for online reading, or the directories **embedded/www** and
**embedded/man** for local copies of the documentation in HTML
and nroff formats, respectively.
# History
* **2013-01-21** : Move code to from jcw to andreas-kupries.
* **2011-08-18** : Move code to public repository on GitHub
The Subversion repository at *svn://svn.equi4.com/critcl* is now obsolete.
GitHub has the new official repository for Critcl.

20
src/vfs/critcl.vfs/doc/checklist.txt

@ -0,0 +1,20 @@
When releasing:
- Run the test suite.
- Run the examples.
- Bump version in `doc/version.inc`.
- If necessary, further bump:
- The versions of `package provide/ifneeded` in files:
- `lib/critcl-app/pkgindex.tcl`
- `lib/critcl/pkgindex.tcl`
- `lib/critcl/critcl.tcl`
- The version in `doc/pkg_version.inc`.
- Regenerate the embedded documentation.
- Commit
- Push

73
src/vfs/critcl.vfs/doc/critcl.man

@ -0,0 +1,73 @@
[comment {-*- mode: tcl ; fill-column: 90 -*- doctools manpage}]
[include version.inc]
[manpage_begin critcl n [vset VERSION]]
[include include/module.inc]
[titledesc {Introduction To CriTcl}]
[description]
[para]
[include include/welcome.inc]
[include include/advert.inc]
[para]
[comment {= = == === ===== ======== ============= =====================}]
[section {History & Motivation}]
[para] [vset critcl] started life as an experiment by [vset jcw] and was a self-contained
Tcl package to build C code into a Tcl/Tk extension on the fly. It was somewhat inspired
by Brian Ingerson's [term Inline] for [term Perl], but is considerably more lightweight.
[para] It is for the last 5% to 10% when pure Tcl, which does go a long way, is not
sufficient anymore. I.e. for
[list_begin enumerated]
[enum] when the last bits of performance are needed,
[enum] access to 3rd party libraries,
[enum] hiding critical pieces of your library or application, and
[enum] simply needing features provided only by C.
[list_end]
[comment {= = == === ===== ======== ============= =====================}]
[section Overview]
To make the reader's topics of interest easy to find this documentation is roughly
organized by [vset quad], i.e. [include include/quad.inc]
[strong Note]: At this point in time the documentation consists mainly of references, and
a few how-to guides. Tutorials and Explanations are in need of expansion, this is planned.
[comment {= = == === ===== ======== ============= =====================}]
[section {Known Users}]
[include include/pkg_users.inc]
[comment {= = == === ===== ======== ============= =====================}]
[section {Tutorials - Practical Study - To Learn}]
This section is currently empty.
[comment {= = == === ===== ======== ============= =====================}]
[section {Explanations - Theoretical Knowledge - To Understand}]
This section is currently empty.
[comment {= = == === ===== ======== ============= =====================}]
[section {How-To Guides - Practical Work - To Solve Problems}]
[list_begin enumerated]
[enum] [term {How To Get The CriTcl Sources}].
[enum] [term {How To Install CriTcl}].
[enum] [term {How To Use CriTcl}] - A light introduction through examples.
[enum] [strong NEW]: [term {How To Adapt Critcl Packages for Tcl 9}].
[list_end]
[comment {= = == === ===== ======== ============= =====================}]
[section {References - Theoretical Work - To Gain Knowlegde}]
[list_begin enumerated]
[enum] [term {The CriTcl License}]
[enum] [term {CriTcl Releases & Changes}]
[include include/reference_docs.inc]
[enum] [term {Guide To The CriTcl Internals}]
[list_end]
[include include/feedback.inc]
[manpage_end]

45
src/vfs/critcl.vfs/doc/critcl_application.man

@ -0,0 +1,45 @@
[comment {-*- mode: tcl ; fill-column: 90 -*- doctools manpage}]
[comment {quadrant: reference}]
[include pkg_version.inc]
[manpage_begin critcl_application n [vset VERSION]]
[include include/module.inc]
[titledesc {CriTcl Application Reference}]
[description]
[para]
[include include/welcome.inc]
[para]
This document is the reference manpage for the [cmd critcl] command.
Its intended audience are people having to build packages using
[package critcl] for deployment. Writers of packages with embedded C
code can ignore this document.
[vset see_overview]
[para]
This application resides in the Application Layer of CriTcl.
[para][image arch_application][para].
[comment {= = == === ===== ======== ============= =====================}]
The application supports the following general command line:
[list_begin definitions]
[call [cmd critcl] [opt [arg option]...] [opt [arg file]...]]
The exact set of options supported, their meaning, and interaction is
detailed in section [sectref {Application Options}] below.
For a larger set of examples please see section "Building CriTcl Packages"
in the document about [manpage {Using CriTcl}].
[list_end]
[section {Application Options}] [include include/aoptions.inc]
[section {Package Structure}] [include include/pstructure.inc]
[comment {= = == === ===== ======== ============= =====================}]
[include include/feedback.inc]
[manpage_end]

62
src/vfs/critcl.vfs/doc/critcl_application_package.man

@ -0,0 +1,62 @@
[comment {-*- mode: tcl ; fill-column: 90 -*- doctools manpage}]
[comment {quadrant: reference}]
[include pkg_version.inc]
[manpage_begin critcl_application_package n [vset VERSION]]
[include include/module.inc]
[titledesc {CriTcl Application Package Reference}]
[require Tcl 8.6]
[require critcl::app [opt [vset VERSION]]]
[require critcl [opt [vset VERSION]]]
[require platform [opt 1.0.2]]
[require cmdline]
[description]
[para]
[include include/welcome.inc]
[para]
This document is the reference manpage for the [package critcl::app]
package. Its intended audience are developers working on critcl's
internals. [vset not_needed_for_critcl_script]
[vset see_overview]
[para]
This package resides in the Application Layer of CriTcl.
[para][image arch_application][para],
implementing the functionality of the [manpage {CriTcl Application}],
and through this, the mode [sectref {Modes Of Operation/Use} {generate package}].
The actual application is (only) a shim wrapping around this
package. It itself is build on top of the core package
[package critcl].
[comment {= = == === ===== ======== ============= =====================}]
[section API]
The package exports a single command
[list_begin definitions]
[call [cmd ::critcl::app::main] [arg commandline]]
The [arg commandline] is a list of zero or more options followed by zero or
more [vset critcl_script] files. By default, the [vset critcl_script] files
are build and the results cached. This cuts down on the time needed to
load the package. The last occurrence of [option -pkg] and [option -tea], if
provided, selects the corresponding alternative mode of operations.
For a larger set of examples please see section "Building CriTcl Packages"
in the document about [manpage {Using CriTcl}].
[list_end]
The options are:
[section {Options}] [include include/aoptions.inc]
[section {Modes Of Operation/Use}] [include include/modes.inc]
[section {Package Structure}] [include include/pstructure.inc]
[comment {= = == === ===== ======== ============= =====================}]
[include include/feedback.inc]
[manpage_end]

161
src/vfs/critcl.vfs/doc/critcl_bitmap.man

@ -0,0 +1,161 @@
[comment {-*- tcl -*- doctools manpage}]
[vset bitmap_version 1.1]
[manpage_begin critcl::bitmap n [vset bitmap_version]]
[include include/module2.inc]
[keywords singleton {Tcl Interp Association}]
[keywords bitmask bitset flags]
[titledesc {CriTcl - Wrap Support - Bitset en- and decoding}]
[require Tcl 8.6]
[require critcl [opt 3.2]]
[require critcl::bitmap [opt [vset bitmap_version]]]
[description]
[para]
[include include/welcome.inc]
[para]
This document is the reference manpage for the
[package critcl::bitmap] package. This package provides convenience
commands for advanced functionality built on top of both critcl core
and package [package critcl::iassoc].
[para] C level libraries often use bit-sets to encode many flags into a
single value. Tcl bindings to such libraries now have the task of
converting a Tcl representation of such flags (like a list of strings)
into such bit-sets, and back.
[emph Note] here that the C-level information has to be something which
already exists. The package does [emph not] create these values. This is
in contrast to the package [package critcl::enum] which creates an
enumeration based on the specified symbolic names.
[para] This package was written to make the declaration and management
of such bit-sets and their associated conversions functions easy,
hiding all attendant complexity from the user.
[para] Its intended audience are mainly developers wishing to write
Tcl packages with embedded C code.
[para] This package resides in the Core Package Layer of CriTcl.
[para][image arch_core][para]
[comment {= = == === ===== ======== ============= =====================}]
[section API]
[list_begin definitions]
[call [cmd ::critcl::bitmap::def] [arg name] [arg definition] [opt [arg exclusions]]]
This command defines two C functions for the conversion of the
[arg name]d bit-set into Tcl lists, and vice versa.
The underlying mapping tables are automatically initialized on first
access, and finalized on interpreter destruction.
[para] The [arg definition] dictionary provides the mapping from the
Tcl-level symbolic names of the flags to their C expressions (often
the name of the macro specifying the actual value).
[emph Note] here that the C-level information has to be something which
already exists. The package does [emph not] create these values. This is
in contrast to the package [package critcl::enum] which creates an
enumeration based on the specified symbolic names.
[para] The optional [arg exlusion] list is for the flags/bit-sets for
which conversion from bit-set to flag, i.e. decoding makes no
sense. One case for such, for example, are flags representing a
combination of other flags.
[para] The package generates multiple things (declarations and
definitions) with names derived from [arg name], which has to be a
proper C identifier.
[list_begin definitions]
[def [arg name]_encode]
The function for encoding a Tcl list of strings into the equivalent
bit-set.
Its signature is
[para][example_begin]
int [arg name]_encode (Tcl_Interp* interp, Tcl_Obj* flags, int* result);
[example_end]
[para] The return value of the function is a Tcl error code,
i.e. [const TCL_OK], [const TCL_ERROR], etc.
[def [arg name]_decode]
The function for decoding a bit-set into the equivalent Tcl list of
strings.
Its signature is
[para][example_begin]
Tcl_Obj* [arg name]_decode (Tcl_Interp* interp, int flags);
[example_end]
[def [arg name].h]
A header file containing the declarations for the two conversion
functions, for use by other parts of the system, if necessary.
[para] The generated file is stored in a place where it will not
interfere with the overall system outside of the package, yet also be
available for easy inclusion by package files ([cmd csources]).
[def [arg name]]
The name of a critcl argument type encapsulating the encoder function
for use by [cmd critcl::cproc].
[def [arg name]]
The name of a critcl result type encapsulating the decoder function
for use by [cmd critcl::cproc].
[list_end]
[list_end]
[comment {= = == === ===== ======== ============= =====================}]
[section Example]
The example shown below is the specification of the event flags pulled
from the draft work on a Tcl binding to Linux's inotify APIs.
[example {
package require Tcl 8.6
package require critcl 3.2
critcl::buildrequirement {
package require critcl::bitmap
}
critcl::bitmap::def tcl_inotify_events {
accessed IN_ACCESS
all IN_ALL_EVENTS
attribute IN_ATTRIB
closed IN_CLOSE
closed-nowrite IN_CLOSE_NOWRITE
closed-write IN_CLOSE_WRITE
created IN_CREATE
deleted IN_DELETE
deleted-self IN_DELETE_SELF
dir-only IN_ONLYDIR
dont-follow IN_DONT_FOLLOW
modified IN_MODIFY
move IN_MOVE
moved-from IN_MOVED_FROM
moved-self IN_MOVE_SELF
moved-to IN_MOVED_TO
oneshot IN_ONESHOT
open IN_OPEN
overflow IN_Q_OVERFLOW
unmount IN_UNMOUNT
} {
all closed move oneshot
}
# Declarations: tcl_inotify_events.h
# Encoder: int tcl_inotify_events_encode (Tcl_Interp* interp, Tcl_Obj* flags, int* result);
# Decoder: Tcl_Obj* tcl_inotify_events_decode (Tcl_Interp* interp, int flags);
# crit arg-type tcl_inotify_events
# crit res-type tcl_inotify_events
}]
[comment {= = == === ===== ======== ============= =====================}]
[include include/feedback2.inc]
[manpage_end]

17
src/vfs/critcl.vfs/doc/critcl_build.man

@ -0,0 +1,17 @@
[comment {-*- mode: tcl ; fill-column: 90 -*- doctools manpage}]
[comment {quadrant: reference}]
[include version.inc]
[manpage_begin critcl_build_tool n [vset VERSION]]
[include include/module.inc]
[titledesc {CriTcl build.tcl Tool Reference}]
[description]
[include include/welcome.inc]
The script [file build.tcl] found in the top directory of the [vset critcl] sources is the
main tool of use to a developer or maintainer of [vset critcl] itself.
[para] Invoking it a via [example {./build.tcl help}] provides the online help for this
tool, explaining the operations available, and their arguments.
[include include/feedback.inc]
[manpage_end]

196
src/vfs/critcl.vfs/doc/critcl_callback.man

@ -0,0 +1,196 @@
[vset VERSION 1.1]
[comment {-*- tcl -*- doctools manpage}]
[manpage_begin critcl::callback n [vset VERSION]]
[include include/module2.inc]
[titledesc {CriTcl - C-level Callback Utilities}]
[require Tcl 8.6]
[require critcl [opt 3.2]]
[require critcl::callback [opt [vset VERSION]]]
[description]
[para]
[include include/welcome.inc]
[para]
This document is the reference manpage for the
[package critcl::callback] package.
This package provides, via a stubs API table, data structures and
functions to manage callbacks from C to Tcl. The package has no
Tcl-level facilities.
Its intended audience are mainly developers wishing to write Tcl
packages with embedded C code who have to invoke user-specified
command (prefixes) in Tcl.
[para]
This package resides in the Support Package Layer of CriTcl.
[para][image arch_support][para]
[comment {= = == === ===== ======== ============= =====================}]
[section API]
The package API consist of one opaque data structure
([type critcl_callback_p]) and four functions operating on the same.
These functions are
[list_begin definitions]
[comment {* * ** *** ***** ******** ************* *********************}]
[call [type critcl_callback_p] [fun critcl_callback_new] \
[arg interp] [arg objc] [arg objv] [arg nargs]]
This function creates a new callback (manager) and returns it as its result.
[para]
The callback is initialized with the Tcl_Interp* [arg interp]
specifying where to run the callback, the fixed part of the command to
run in standard [arg objc]/[arg objv] notation, plus the number of
free arguments to expect after the fixed part.
[para]
The fixed part is the essentially the command prefix of the callback.
[para]
All [type Tcl_Obj*] elements of [arg objv] are protected against early
release by incrementing their reference counts. The callback
effectively takes ownership of these objects.
[comment {* * ** *** ***** ******** ************* *********************}]
[call [type void] [fun critcl_callback_extend] \
[arg callback] [arg argument]]
This function takes a [arg callback] of type [type critcl_callback_p]
and extends its fixed part with the [arg argument], taking the first
free slot for arguments to do so.
This means that after the application of this function the specified
callback has one free argument less.
[para]
With assertions active attempting to extend beyond the number of free
arguments will cause a panic. Without assertions active expect a crash
at some point.
[para]
This allows the user to extend the fixed part of the callback with
semi-fixed elements, like method names (See [sectref {Multiple methods}]).
[para]
The [arg argument] is protected against early release by incrementing
its reference count. The callback effectively takes ownership of this
object.
[comment {* * ** *** ***** ******** ************* *********************}]
[call [type void] [fun critcl_callback_destroy] \
[arg callback]]
This function takes a [arg callback] of type [type critcl_callback_p]
and releases all memory associated with it.
After application of this function the callback cannot be used anymore.
[para]
All fixed elements of the callback (owned by it) are released by
decrementing their reference counts.
[comment {* * ** *** ***** ******** ************* *********************}]
[call [type int] [fun critcl_callback_invoke] \
[arg callback] [arg objc] [arg objv]]
This function invokes the callback in the Tcl interpreter specified at
the time of construction, in the global level and namespace, with the
free arguments filled by the [type Tcl_Obj*] objects specified via
[arg objc]/[arg objv].
[para]
It returns the Tcl status of the invoked command as its result.
Any further results or error messages will be found in the result area
of the Tcl interpreter in question. The exact nature of such is
dependent on the callback itself.
[para]
With assertions active attempting to use more arguments than available
will cause a panic. Without assertions active expect a crash at some
point.
[para]
While the callback is running all [type Tcl_Obj*] elements of the
command, fixed and arguments, are protected against early release by
temporarily incrementing their reference counts.
[list_end]
[comment {= = == === ===== ======== ============= =====================}]
[section Examples]
[subsection {Simple callback}]
The example here shows the important parts of using the functions of
this package for a simple callback which is invoked with a single
argument, some kind of data to hand to the Tcl level.
[example {
// Create the callback with interpreter and command prefix in
// oc/ov, plus space for the argument
critcl_callback_p cb = critcl_callback_new (interp, oc, ov, 1);
// Invoke the callback somewhere in the C package using this one,
// with Tcl_Obj* data holding the information to pass up.
critcl_callback_invoke (cb, 1, &data);
// At the end of the lifetime, release the callback.
critcl_callback_destroy (cb);
}]
Note that the functions of this package are designed for the case
where the created callback ([const cb] above) is kept around for a
long time, and many different invokations.
[para]
Using the sequence above as is, creating and destroying the callback
each time it is invoked will yield very poor performance and lots of
undesirable memory churn.
[subsection {Multiple methods}]
While we can use the methodology of the previous section when a single
(Tcl-level) callback is invoked from different places in C, with
different methods, simply having another argument slot and filling it
an invokation time with the method object, a second methodology is
open to us due to [fun critcl_callback_extend].
[example {
// Create one callback manager per different method the callback
// will be used with. Fill the first of the two declared arguments
// with the different methods.
critcl_callback_p cb_a = critcl_callback_new (interp, oc, ov, 2);
critcl_callback_p cb_b = critcl_callback_new (interp, oc, ov, 2);
critcl_callback_extend (cb_a, Tcl_NewStringObj ("method1", -1));
critcl_callback_extend (cb_b, Tcl_NewStringObj ("method2", -1));
// After the extension we have one free argument left, for use in
// the invokations.
critcl_callback_invoke (cb_a, 1, &dataX);
critcl_callback_invoke (cb_b, 1, &dataY);
// At the end release both managers again
critcl_callback_destroy (cb_a);
critcl_callback_destroy (cb_b);
}]
The nice thing here is that the method objects are allocated only once
and automatically shared by all the calls. No memory churn to
repeatedly allocate the same string objects over and over again.
[comment {= = == === ===== ======== ============= =====================}]
[include include/feedback2.inc]
[manpage_end]

16
src/vfs/critcl.vfs/doc/critcl_changes.man

@ -0,0 +1,16 @@
[comment {-*- mode: tcl ; fill-column: 90 -*- doctools manpage}]
[comment {quadrant: reference}]
[include version.inc]
[manpage_begin critcl_changes n [vset VERSION]]
[include include/module.inc]
[titledesc {CriTcl Releases & Changes}]
[description]
[include include/welcome.inc]
[include include/advert.inc]
See the changes done in each release of [vset critcl], from the latest at the top to the
beginning of the project.
[include include/changes.inc]
[include include/feedback.inc]
[manpage_end]

57
src/vfs/critcl.vfs/doc/critcl_class.man

@ -0,0 +1,57 @@
[comment {-*- tcl -*- doctools manpage}]
[vset VERSION 1.1]
[manpage_begin critcl::class n [vset VERSION]]
[include include/module2.inc]
[keywords {C class} {C object} {C instance}]
[titledesc {CriTcl - Code Gen - C Classes}]
[require Tcl 8.6]
[require critcl [opt 3.2]]
[require critcl::class [opt [vset VERSION]]]
[description]
[para]
[include include/welcome.inc]
[para]
This document is the reference manpage for the [package critcl::class]
package. This package provides convenience commands for advanced
functionality built on top of the core.
[para] With it a user wishing to create a C level object with class
and instance commands can concentrate on specifying the class- and
instance-variables and -methods in a manner similar to a TclOO class,
while all the necessary boilerplate around it is managed by this
package.
[para] Its intended audience are mainly developers wishing to write
Tcl packages with embedded C code.
[para] This package resides in the Core Package Layer of CriTcl.
[para][image arch_core][para]
[comment {= = == === ===== ======== ============= =====================}]
[section API]
[list_begin definitions]
[call [cmd ::critcl::class::define] [arg name] [arg script]]
This is the main command to define a new class [arg name], where
[arg name] is the name of the Tcl command representing the class,
i.e. the [term {class command}]. The [arg script] provides the
specification of the class, i.e. information about included headers,
class- and instance variables, class- and instance-methods, etc.
See the section [sectref {Class Specification API}] below for the
detailed list of the available commands and their semantics.
[list_end]
[comment {= = == === ===== ======== ============= =====================}]
[section {Class Specification API}][include include/class_spec.inc]
[comment {= = == === ===== ======== ============= =====================}]
[section Example][include include/class_example.inc]
[comment {= = == === ===== ======== ============= =====================}]
[include include/feedback2.inc]
[manpage_end]

40
src/vfs/critcl.vfs/doc/critcl_cproc.man

@ -0,0 +1,40 @@
[comment {-*- mode: tcl ; fill-column: 90 -*- doctools manpage}]
[comment {quadrant: reference}]
[include pkg_version.inc]
[manpage_begin critcl_cproc_types n [vset VERSION]]
[include include/module.inc]
[titledesc {CriTcl cproc Type Reference}]
[require Tcl 8.6]
[require critcl [opt [vset VERSION]]]
[description]
[para]
[include include/welcome.inc]
[para]
This document is a breakout of the descriptions for the predefined argument- and result-types usable
with the [cmd critcl::cproc] command, as detailed in the reference manpage for the [package critcl]
package, plus the information on how to extend the predefined set with custom types. The breakout
was made to make this information easier to find (toplevel document vs. having to search the large
main reference).
[para] Its intended audience are developers wishing to write Tcl packages with embedded C code.
[section {Standard argument types}] [include include/cproc/api_stdat_cproc.inc]
[section {Standard result types}] [include include/cproc/api_stdrt_cproc.inc]
[section {Advanced: Adding types}] [include include/cproc/api_extcproc2.inc]
[section Examples]
The examples shown here have been drawn from the section "Embedding C" in the document about
[manpage {Using CriTcl}]. Please see that document for many more examples.
[include include/cproc/using_eproc.inc] [comment {%% cproc}]
[include include/cproc/using_eprocstr.inc] [comment {%% cproc, strings}]
[include include/cproc/using_eproctypes.inc] [comment {%% cproc types, intro & trivial}]
[include include/cproc/using_eproctypes2.inc] [comment {%% cproc types, semi-trivial}]
[include include/cproc/using_eproctypes3.inc] [comment {%% cproc types, support (incl alloc'd)}]
[include include/cproc/using_eproctypes4.inc] [comment {%% cproc types, results}]
[comment {= = == === ===== ======== ============= =====================}]
[include include/feedback.inc]
[manpage_end]

413
src/vfs/critcl.vfs/doc/critcl_cutil.man

@ -0,0 +1,413 @@
[vset VERSION 0.3]
[comment {-*- tcl -*- doctools manpage}]
[manpage_begin critcl::cutil n [vset VERSION]]
[include include/module2.inc]
[titledesc {CriTcl - C-level Utilities}]
[require Tcl 8.6]
[require critcl [opt 3.2]]
[require critcl::cutil [opt [vset VERSION]]]
[description]
[para]
[include include/welcome.inc]
[para]
This document is the reference manpage for the [package critcl::cutil]
package. This package encapsulates a number of C-level utilites for
easier writing of memory allocations, assertions, and narrative tracing
and provides convenience commands to make these utilities accessible
to critcl projects.
Its intended audience are mainly developers wishing to write Tcl
packages with embedded C code.
[para]
This package resides in the Core Package Layer of CriTcl.
[para][image arch_core][para]
The reason for this is that the main [package critcl] package makes
use of the facilities for narrative tracing when
[cmd {critcl::config trace}] is set, to instrument commands and
procedures.
[comment {= = == === ===== ======== ============= =====================}]
[section API]
[list_begin definitions]
[comment {* * ** *** ***** ******** ************* *********************}]
[call [cmd ::critcl::cutil::alloc]]
This command provides a number C-preprocessor macros which make the
writing of memory allocations for structures and arrays of structures
easier.
[para] When run the header file [file critcl_alloc.h] is directly made
available to the [file .critcl] file containing the command, and
becomes available for use in [cmd {#include}] directives of companion
C code declared via [cmd critcl::csources].
[para] The macros definitions and their signatures are:
[example {
type* ALLOC (type)
type* ALLOC_PLUS (type, int n)
type* NALLOC (type, int n)
type* REALLOC (type* var, type, int n)
void FREE (type* var)
void STREP (Tcl_Obj* o, char* s, int len);
void STREP_DS (Tcl_Obj* o, Tcl_DString* ds);
void STRDUP (varname, char* str);
}]
[para] The details of the semantics are explained in section
[sectref Allocation].
[para] The result of the command is an empty string.
[comment {* * ** *** ***** ******** ************* *********************}]
[call [cmd ::critcl::cutil::assertions] [opt [arg enable]]]
This command provides a number C-preprocessor macros for the writing
of assertions in C code.
[para] When invoked the header file [file critcl_assert.h] is directly
made available to the [file .critcl] file containing the command, and
becomes available for use in [cmd {#include}] directives of companion
C code declared via [cmd critcl::csources].
[para] The macro definitions and their signatures are
[example {
void ASSERT (expression, char* message);
void ASSERT_BOUNDS (int index, int size);
void STOPAFTER (int n);
}]
[para] Note that these definitions are conditional on the existence of
the macro [const CRITCL_ASSERT].
Without a [cmd {critcl::cflags -DCRITCL_ASSERT}] all assertions in the
C code are quiescent and not compiled into the object file. In other
words, assertions can be (de)activated at will during build time, as
needed by the user.
[para] For convenience this is controlled by [arg enable]. By default
([const false]) the facility available, but not active.
Using [const true] not only makes it available, but activates it as
well.
[para] The details of the semantics are explained in section
[sectref Assertions].
[para] The result of the command is an empty string.
[comment {* * ** *** ***** ******** ************* *********************}]
[call [cmd ::critcl::cutil::tracer] [opt [arg enable]]]
This command provides a number C-preprocessor macros for tracing
C-level internals.
[para] When invoked the header file [file critcl_trace.h] is directly
made available to the [file .critcl] file containing the command, and
becomes available for use in [cmd {#include}] directives of companion
C code declared via [cmd critcl::csources]. Furthermore the [file .c]
file containing the runtime support is added to the set of C companion
files
[para] The macro definitions and their signatures are
[example {
/* (de)activation of named logical streams.
* These are declarators, not statements.
*/
TRACE_ON;
TRACE_OFF;
TRACE_TAG_ON (tag_identifier);
TRACE_TAG_OFF (tag_identifier);
/*
* Higher level trace statements (convenience commands)
*/
void TRACE_FUNC (const char* format, ...);
void TRACE_FUNC_VOID;
any TRACE_RETURN (const char* format, any x);
void TRACE_RETURN_VOID;
void TRACE (const char* format, ...);
/*
* Low-level trace statements the higher level ones above
* are composed from. Scope management and output management.
*/
void TRACE_PUSH_SCOPE (const char* scope);
void TRACE_PUSH_FUNC;
void TRACE_POP;
void TRACE_HEADER (int indent);
void TRACE_ADD (const char* format, ...);
void TRACE_CLOSER;
/*
* Convert tag to the underlying status variable.
*/
TRACE_TAG_VAR (tag)
/*
* Conditional use of arbitrary code.
*/
TRACE_RUN (code);
TRACE_DO (code);
TRACE_TAG_DO (code);
}]
[para] Note that these definitions are conditional on the existence of
the macro [const CRITCL_TRACER].
Without a [cmd {critcl::cflags -DCRITCL_TRACER}] all trace
functionality in the C code is quiescent and not compiled into the
object file. In other words, tracing can be (de)activated at will
during build time, as needed by the user.
[para] For convenience this is controlled by [arg enable]. By default
([const false]) the facility available, but not active.
Using [const true] not only makes it available, but activates it as
well.
Further note that the command [cmd critcl::config] now accepts a
boolean option [const trace]. Setting it activates enter/exit tracing
in all commands based on [cmd critcl::cproc], with proper printing of
arguments and results. This implicitly activates the tracing facility
in general.
[para] The details of the semantics are explained in section
[sectref Tracing]
[para] The result of the command is an empty string.
[list_end]
[comment {= = == === ===== ======== ============= =====================}]
[section Allocation]
[list_begin definitions]
[comment {* * ** *** ***** ******** ************* *********************}]
[call [cmd {type* ALLOC (type)}]]
This macro allocates a single element of the given [arg type] and
returns a pointer to that memory.
[call [cmd {type* ALLOC_PLUS (type, int n)}]]
This macro allocates a single element of the given [arg type], plus an
additional [arg n] bytes after the structure and returns a pointer to
that memory.
[para] This is for variable-sized structures of. An example of such
could be a generic list element structure which stores management
information in the structure itself, and the value/payload immediately
after, in the same memory block.
[call [cmd {type* NALLOC (type, int n)}]]
This macro allocates [arg n] elements of the given [arg type] and
returns a pointer to that memory.
[call [cmd {type* REALLOC (type* var, type, int n)}]]
This macro expands or shrinks the memory associated with the C
variable [arg var] of type [arg type] to hold [arg n] elements of the
type. It returns a pointer to that memory.
Remember, a reallocation may move the data to a new location in memory
to satisfy the request. Returning a pointer instead of immediately
assigning it to the [arg var] allows the user to validate the new
pointer before trying to use it.
[call [cmd {void FREE (type* var)}]]
This macro releases the memory referenced by the pointer variable
[arg var].
[comment {* * ** *** ***** ******** ************* *********************}]
[call [cmd {void STREP (Tcl_Obj* o, char* s, int len)}]]
This macro properly sets the string representation of the Tcl object
[arg o] to a copy of the string [arg s], expected to be of length
[arg len].
[comment {* * ** *** ***** ******** ************* *********************}]
[call [cmd {void STREP_DS (Tcl_Obj* o, Tcl_DString* ds)}]]
This macro properly sets the string representation of the Tcl object
[arg o] to a copy of the string held by the [type DString] [arg ds].
[comment {* * ** *** ***** ******** ************* *********************}]
[call [cmd {void STRDUP (varname, char* str)}]]
This macro duplicates the string [arg str] into the heap and stores
the result into the named [type char*] variable [arg var].
[list_end]
[comment {= = == === ===== ======== ============= =====================}]
[section Assertions]
[list_begin definitions]
[comment {* * ** *** ***** ******** ************* *********************}]
[call [cmd {void ASSERT (expression, char* message}]]
This macro tests the [arg expression] and panics if it does not hold.
The specified [arg message] is used as part of the panic.
The [arg message] has to be a static string, it cannot be a variable.
[comment {* * ** *** ***** ******** ************* *********************}]
[call [cmd {void ASSERT_BOUNDS (int index, int size)}]]
This macro ensures that the [arg index] is in the
range [const 0] to [const {size-1}].
[comment {* * ** *** ***** ******** ************* *********************}]
[call [cmd {void STOPAFTER(n)}]]
This macro throws a panic after it is called [arg n] times.
Note, each separate instance of the macro has its own counter.
[list_end]
[comment {= = == === ===== ======== ============= =====================}]
[section Tracing]
All output is printed to [const stdout].
[list_begin definitions]
[comment {* * ** *** ***** ******** ************* *********************}]
[call [cmd TRACE_ON]]
[call [cmd TRACE_OFF]]
[call [cmd {TRACE_TAG_ON (identifier)}]]
[call [cmd {TRACE_TAG_OFF (identifier)}]]
These "commands" are actually declarators, for use outside of
functions. They (de)activate specific logical streams, named either
explicitly by the user, or implicitly, refering to the current file.
[para] For example:
[para][example {
TRACE_TAG_ON (lexer_in);
}]
[para] All high- and low-level trace commands producing output have
the controlling tag as an implicit argument. The scope management
commands do not take tags.
[comment {* * ** *** ***** ******** ************* *********************}]
[call [cmd {void TRACE_FUNC}]]
[call [cmd {void TRACE_TAG_FUNC (tag)}]]
[call [cmd {void TRACE_FUNC_VOID}]]
[call [cmd {void TRACE_TAG_FUNC_VOID (tag)}]]
Use these macros at the beginning of a C function to record entry into
it. The name of the entered function is an implicit argument
([var __func__]), forcing users to have a C99 compiler..
[para] The tracer's runtime maintains a stack of active functions and
expects that function return is signaled by either [fun TRACE_RETURN],
[fun TRACE_RETURN_VOID], or the equivalent forms taking a tag.
[comment {* * ** *** ***** ******** ************* *********************}]
[call [cmd {void TRACE_RETURN_VOID}]]
[call [cmd {void TRACE_TAG_RETURN_VOID (tag)}]]
Use these macros instead of [example {return}] to return from a void
function. Beyond returning from the function this also signals the
same to the tracer's runtime, popping the last entered function from
its stack of active functions.
[comment {* * ** *** ***** ******** ************* *********************}]
[call [cmd {any TRACE_RETURN ( char* format, any x)}]]
[call [cmd {any TRACE_TAG_RETURN (tag, char* format, any x)}]]
Use this macro instead of [example {return x}] to return from a
non-void function.
Beyond returning from the function with value [arg x] this also
signals the same to the tracer's runtime, popping the last entered
function from its stack of active functions.
The [arg format] is expected to be a proper formatting string for
[fun printf] and analogues, able to stringify [arg x].
[comment {* * ** *** ***** ******** ************* *********************}]
[call [cmd {void TRACE ( char* format, ...)}]]
[call [cmd {void TRACE_TAG (tag, char* format, ...)}]]
This macro is the trace facilities' equivalent of [fun printf],
printing arbitrary data under the control of the [arg format].
[para] The printed text is closed with a newline, and indented as per
the stack of active functions.
[comment {* * ** *** ***** ******** ************* *********************}]
[call [cmd {void TRACE_HEADER (int indent)}]]
[call [cmd {void TRACE_TAG_HEADER (tag, int indent)}]]
This is the low-level macro which prints the beginning of a trace
line. This prefix consists of physical location (file name and line
number), if available, indentation as per the stack of active scopes
(if activated), and the name of the active scope.
[comment {* * ** *** ***** ******** ************* *********************}]
[call [cmd {void TRACE_CLOSER}]]
[call [cmd {void TRACE_TAG_CLOSER (tag)}]]
This is the low-level macro which prints the end of a trace
line.
[comment {* * ** *** ***** ******** ************* *********************}]
[call [cmd {void TRACE_ADD (const char* format, ...)}]]
[call [cmd {void TRACE_TAG_ADD (tag, const char* format, ...)}]]
This is the low-level macro which adds formatted data to the line.
[comment {* * ** *** ***** ******** ************* *********************}]
[call [cmd {void TRACE_PUSH_SCOPE (const char* name)}]]
[call [cmd {void TRACE_PUSH_FUNC}]]
[call [cmd {void TRACE_PUSH_POP}]]
These are the low-level macros for scope management. The first two
forms push a new scope on the stack of active scopes, and the last
forms pops the last scope pushed.
[comment {* * ** *** ***** ******** ************* *********************}]
[call [cmd {TRACE_TAG_VAR (tag)}]]
Helper macro converting from a tag identifier to the name of the
underlying status variable.
[comment {* * ** *** ***** ******** ************* *********************}]
[call [cmd {TRACE_RUN (code);}]]
Conditionally insert the [arg code] at compile time when the tracing
facility is activated.
[comment {* * ** *** ***** ******** ************* *********************}]
[call [cmd {TRACE_DO (code);}]]
[call [cmd {TRACE_TAG_DO (tag, code);}]]
Insert the [arg code] at compile time when the tracing facility is
activated, and execute the same when either the implicit tag for the
file or the user-specified tag is active.
[list_end]
[comment {= = == === ===== ======== ============= =====================}]
[include include/feedback2.inc]
[manpage_end]

228
src/vfs/critcl.vfs/doc/critcl_devguide.man

@ -0,0 +1,228 @@
[comment {-*- mode: tcl ; fill-column: 90 -*- doctools manpage}]
[comment {quadrant: reference}]
[include version.inc]
[manpage_begin critcl_devguide n [vset VERSION]]
[include include/module.inc]
[titledesc {Guide To The CriTcl Internals}]
[description]
[include include/welcome.inc]
[comment {= = == === ===== ======== ============= =====================}]
[section Audience]
[para] This document is a guide for developers working on CriTcl, i.e. maintainers fixing
bugs, extending the package's functionality, etc.
[para] Please read
[list_begin enum]
[enum] [term {CriTcl - License}],
[enum] [term {CriTcl - How To Get The Sources}], and
[enum] [term {CriTcl - The Installer's Guide}]
[list_end]
first, if that was not done already.
[para] Here we assume that the sources are already available in a directory of the readers
choice, and that the reader not only know how to build and install them, but also has all
the necessary requisites to actually do so. The guide to the sources in particular also
explains which source code management system is used, where to find it, how to set it up,
etc.
[section {Playing with CriTcl}]
[include include/largeexampleref.inc]
[include include/smallexampleref.inc]
[section {Developing for CriTcl}]
[comment @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@]
[subsection {Architecture & Concepts}]
[include include/architecture.inc]
[comment @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@]
[subsection Requirements]
To develop for critcl the following packages and applications must be available in the
environment. These are all used by the [cmd build.tcl] helper application.
[list_begin definitions]
[def [syscmd dtplite]]
A Tcl application provided by Tcllib, for the validation and conversion of
[term doctools]-formatted text.
[def [syscmd dia]]
A Tcl application provided by Tklib, for the validation and conversion
of [package diagram]-formatted figures into raster images.
[para] Do not confuse this with the Gnome [syscmd dia] application, which is a graphical
editor for figures and diagrams, and completely unrelated.
[def [package fileutil]]
A Tcl package provided by Tcllib, providing file system utilities.
[def "[package vfs::mk4], [package vfs]"]
Tcl packages written in C providing access to Tcl's VFS facilities, required for the
generation of critcl starkits and starpacks.
[list_end]
[comment @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@]
[subsection {Directory structure}]
[list_begin definitions][comment {___1___}]
[comment {= = == === ===== ======== ============= =======================}]
[def Helpers]
[list_begin definitions]
[def [file build.tcl]]
This helper application provides various operations needed by a developer for critcl, like
regenerating the documentation, the figures, building and installing critcl, etc.
[para] Running the command like
[example {
./build.tcl help
}]
will provide more details about the available operations and their arguments.
[list_end]
[comment {= = == === ===== ======== ============= =======================}]
[def Documentation]
[list_begin definitions]
[def [file doc/]]
This directory contains the documentation sources, for both the text, and the figures.
The texts are written in [term doctools] format, whereas the figures are written for
tklib's [package dia](gram) package and application.
[def [file embedded/]]
This directory contains the documentation converted to regular manpages (nroff) and HTML.
It is called embedded because these files, while derived, are part of the git repository,
i.e. embedded into it. This enables us to place these files where they are visible when
serving the prject's web interface.
[list_end]
[comment {= = == === ===== ======== ============= =======================}]
[def Testsuite]
[list_begin definitions]
[def [file test/all.tcl]]
[def [file test/testutilities.tcl]]
[def [file test/*.test]]
These files are a standard testsuite based on Tcl's [package tcltest] package, with some
utility code snarfed from [package Tcllib].
[para] This currently tests only some of the [package stubs::*] packages.
[def [file test/*.tcl]]
These files (except for [file all.tcl] and [file testutilities.tcl]) are example files
(Tcl with embedded C) which can be run through critcl for testing.
[para] [strong TODO] for a maintainers: These should be converted into a proper test suite.
[list_end]
[comment {= = == === ===== ======== ============= =======================}]
[def {Package Code, General structure}]
[list_begin definitions]
[list_end]
[comment {= = == === ===== ======== ============= =======================}]
[def {Package Code, Per Package}]
[list_begin definitions][comment ----------------------PCPP]
[def [package critcl]]
[list_begin definitions][comment ---------------critcl]
[def [file lib/critcl/critcl.tcl]]
The Tcl code implementing the package.
[def [file lib/critcl/Config]]
The configuration file for the standard targets and their settings.
[def [file lib/critcl/critcl_c/]]
Various C code snippets used by the package.
This directory also contains the copies of the Tcl header files used to compile the
assembled C code, for the major brnaches of Tcl, i.e. 8.4, 8.5, and 8.6.
[list_end][comment -----------------------------critcl]
[def [package critcl::util]]
[list_begin definitions][comment ---------------critcl::util]
[def [file lib/critcl-util/util.tcl]]
The Tcl code implementing the package.
[list_end][comment -----------------------------critcl::util]
[def [package critcl::app]]
[list_begin definitions][comment ---------------critcl::app]
[def [file lib/app-critcl/critcl.tcl]]
The Tcl code implementing the package.
[list_end][comment -----------------------------critcl::app]
[def [package critcl::iassoc]]
[list_begin definitions][comment ---------------critcl::iassoc]
[def [file lib/critcl-iassoc/iassoc.tcl]]
The Tcl code implementing the package.
[def [file lib/critcl-iassoc/iassoc.h]]
C code template used by the package.
[list_end][comment -----------------------------critcl::iassoc]
[def [package critcl::class]]
[list_begin definitions][comment ---------------critcl::class]
[def [file lib/critcl-class/class.tcl]]
The Tcl code implementing the package.
[def [file lib/critcl-class/class.h]]
C code template used by the package.
[list_end][comment -----------------------------critcl::class]
[def [package stubs::*]]
[list_begin definitions][comment ---------------stubs]
[def [file lib/stubs/*]]
A set of non-public (still) packages which provide read and write access to and represent
Tcl stubs tables. These were created by taking the [file genStubs.tcl] helper application
coming with the Tcl core sources apart along its internal logical lines.
[list_end][comment -----------------------------stubs]
[def [package critclf]]
[list_begin definitions][comment ---------------critclf]
[def [file lib/critclf/]]
Arjen Markus' work on a critcl/Fortran. The code is outdated and has not been adapted to
the changes in critcl version 3 yet.
[list_end][comment -----------------------------critclf]
[def [package md5]]
[def [package md5c]]
[def [package platform]]
These are all external packages whose code has been inlined in the repository for easier
development (less dependencies to pull), and quicker deployment from the repository
(generation of starkit and -pack).
[para] [strong TODO] for maintainers: These should all be checked against their origin for
updates and changes since they were inlined.
[list_end][comment ------------------------------------PCPP]
[list_end][comment {___1___}]
[comment {TODO **** Package dependency diagram ****}]
[comment {TODO **** Diagram of the internal call graph ? ****}]
[comment {TODO **** Add test/ ****}]
[include include/feedback.inc]
[manpage_end]

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

Loading…
Cancel
Save