Browse Source

overtype,punk::winlnk,punk::args,punk::console etc fixes.

master
Julian Noble 4 months ago
parent
commit
d6d6ea8de5
  1. 48
      src/bootsupport/modules/overtype-1.7.4.tm
  2. 515
      src/bootsupport/modules/punk-0.1.tm
  3. 1
      src/bootsupport/modules/punk/aliascore-0.1.0.tm
  4. 43
      src/bootsupport/modules/punk/ansi-0.1.1.tm
  5. 259
      src/bootsupport/modules/punk/args-0.2.1.tm
  6. 438
      src/bootsupport/modules/punk/args/moduledoc/tclcore-0.1.0.tm
  7. 174
      src/bootsupport/modules/punk/auto_exec-0.1.0.tm
  8. 34
      src/bootsupport/modules/punk/config-0.1.tm
  9. 2562
      src/bootsupport/modules/punk/console-0.1.1.tm
  10. 34
      src/bootsupport/modules/punk/lib-0.1.6.tm
  11. 7
      src/bootsupport/modules/punk/nav/fs-0.1.0.tm
  12. 36
      src/bootsupport/modules/punk/nav/ns-0.1.0.tm
  13. 32
      src/bootsupport/modules/punk/ns-0.1.0.tm
  14. 21
      src/bootsupport/modules/punk/repl-0.1.2.tm
  15. 25
      src/bootsupport/modules/punk/winlnk-0.1.1.tm
  16. 45
      src/bootsupport/modules/textblock-0.1.3.tm
  17. 17
      src/modules/overtype-999999.0a1.0.tm
  18. 515
      src/modules/punk-0.1.tm
  19. 1
      src/modules/punk/aliascore-999999.0a1.0.tm
  20. 43
      src/modules/punk/ansi-999999.0a1.0.tm
  21. 259
      src/modules/punk/args-999999.0a1.0.tm
  22. 438
      src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm
  23. 174
      src/modules/punk/auto_exec-999999.0a1.0.tm
  24. 34
      src/modules/punk/config-0.1.tm
  25. 2562
      src/modules/punk/console-999999.0a1.0.tm
  26. 192
      src/modules/punk/imap4-999999.0a1.0.tm
  27. 34
      src/modules/punk/lib-999999.0a1.0.tm
  28. 7
      src/modules/punk/nav/fs-999999.0a1.0.tm
  29. 36
      src/modules/punk/nav/ns-999999.0a1.0.tm
  30. 32
      src/modules/punk/ns-999999.0a1.0.tm
  31. 21
      src/modules/punk/repl-999999.0a1.0.tm
  32. 25
      src/modules/punk/winlnk-999999.0a1.0.tm
  33. 35
      src/modules/test/punk/#modpod-args-999999.0a1.0/args-0.1.5_testsuites/args/args.test
  34. 12
      src/modules/test/punk/#modpod-args-999999.0a1.0/args-0.1.5_testsuites/args/choices.test
  35. 45
      src/modules/textblock-999999.0a1.0.tm
  36. 48
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/overtype-1.7.4.tm
  37. 515
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk-0.1.tm
  38. 1
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/aliascore-0.1.0.tm
  39. 43
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/ansi-0.1.1.tm
  40. 259
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/args-0.2.1.tm
  41. 438
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/args/moduledoc/tclcore-0.1.0.tm
  42. 174
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/auto_exec-0.1.0.tm
  43. 34
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/config-0.1.tm
  44. 2562
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/console-0.1.1.tm
  45. 34
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/lib-0.1.6.tm
  46. 7
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/nav/fs-0.1.0.tm
  47. 36
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/nav/ns-0.1.0.tm
  48. 32
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/ns-0.1.0.tm
  49. 21
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repl-0.1.2.tm
  50. 25
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/winlnk-0.1.1.tm
  51. 45
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/textblock-0.1.3.tm
  52. 48
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/overtype-1.7.4.tm
  53. 515
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk-0.1.tm
  54. 1
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/aliascore-0.1.0.tm
  55. 43
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/ansi-0.1.1.tm
  56. 259
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/args-0.2.1.tm
  57. 438
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/args/moduledoc/tclcore-0.1.0.tm
  58. 174
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/auto_exec-0.1.0.tm
  59. 34
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/config-0.1.tm
  60. 2562
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/console-0.1.1.tm
  61. 34
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/lib-0.1.6.tm
  62. 7
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/nav/fs-0.1.0.tm
  63. 36
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/nav/ns-0.1.0.tm
  64. 32
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/ns-0.1.0.tm
  65. 21
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repl-0.1.2.tm
  66. 25
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/winlnk-0.1.1.tm
  67. 45
      src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/textblock-0.1.3.tm
  68. 17
      src/vfs/_vfscommon.vfs/modules/overtype-1.7.4.tm
  69. 28
      src/vfs/_vfscommon.vfs/modules/punk-0.1.tm
  70. 1
      src/vfs/_vfscommon.vfs/modules/punk/aliascore-0.1.0.tm
  71. 43
      src/vfs/_vfscommon.vfs/modules/punk/ansi-0.1.1.tm
  72. 203
      src/vfs/_vfscommon.vfs/modules/punk/args-0.2.1.tm
  73. 438
      src/vfs/_vfscommon.vfs/modules/punk/args/moduledoc/tclcore-0.1.0.tm
  74. 174
      src/vfs/_vfscommon.vfs/modules/punk/auto_exec-0.1.0.tm
  75. 34
      src/vfs/_vfscommon.vfs/modules/punk/config-0.1.tm
  76. 273
      src/vfs/_vfscommon.vfs/modules/punk/console-0.1.1.tm
  77. 192
      src/vfs/_vfscommon.vfs/modules/punk/imap4-0.9.1.tm
  78. 34
      src/vfs/_vfscommon.vfs/modules/punk/lib-0.1.6.tm
  79. 7
      src/vfs/_vfscommon.vfs/modules/punk/nav/fs-0.1.0.tm
  80. 36
      src/vfs/_vfscommon.vfs/modules/punk/nav/ns-0.1.0.tm
  81. 7
      src/vfs/_vfscommon.vfs/modules/punk/repl-0.1.2.tm
  82. 25
      src/vfs/_vfscommon.vfs/modules/punk/winlnk-0.1.1.tm
  83. BIN
      src/vfs/_vfscommon.vfs/modules/test/punk/args-0.1.5.tm
  84. 45
      src/vfs/_vfscommon.vfs/modules/textblock-0.1.3.tm

48
src/bootsupport/modules/overtype-1.7.4.tm

@ -461,8 +461,21 @@ tcl::namespace::eval overtype {
if {$underblock eq ""} {
set underlines [lrepeat $renderheight ""]
} else {
set underblock [textblock::join_basic -- $underblock] ;#ensure properly rendered - ansi per-line resets & replays
set underlines [split $underblock \n]
#----
#this splits into lines - only to rejoin - which is inefficient.
#It also has code to handle joining multiple blocks - but we only have one in this case.
#set underblock [textblock::join_basic_raw $underblock];#ensure properly rendered - ansi per-line resets & replays
#set underlines [split $underblock \n]
#----
if {[punk::ansi::ta::detectcode $underblock]} {
#-ansireplays 1 quite expensive e.g ~15us for only 3 short lines on a 2026 threadripper pro
set underlines [punk::lib::linelist -ansireplays 1 $underblock]
} else {
set underlines [split $underblock \n]
}
}
#if {$underblock eq ""} {
# set blank "\x1b\[0m\x1b\[0m"
@ -881,8 +894,9 @@ tcl::namespace::eval overtype {
set cursor_saved_position [tcl::dict::create]
set cursor_saved_attributes ""
} else {
#FUTURE: Handle restore without save case
#Should move to home position and reset ansi SGR when no save data available
#TODO
#?restore without save?
#should move to home position and reset ansi SGR?
#puts stderr "overtype::renderspace cursor_restore without save data available"
}
#If we were inserting prior to hitting the cursor_restore - there could be overflow_right data - generally the overtype functions aren't for inserting - but ansi can enable it
@ -1195,7 +1209,7 @@ tcl::namespace::eval overtype {
wrapmoveforward {
#doesn't seem to be used by fruit.ans testfile
#used by dzds.ans
#FIXED: cursor_forward can move deep into the next line or span multiple lines - handled below
#note that cursor_forward may move deep into the next line - or even span multiple lines !TODO
set c $renderwidth
set r $post_render_row
if {$post_render_col > $renderwidth} {
@ -2571,9 +2585,8 @@ tcl::namespace::eval overtype {
lset overmap 0 "$startpadding[lindex $overmap 0]"
} else {
if {[punk::ansi::ta::detect $overdata]} {
#FUTURE: Optimize for large files with no newlines
#Currently wastefully calling split_codes_single repeatedly on mostly the same data.
#Consider caching or streaming approach for 200K+ input files.
#TODO!! rework this.
#e.g 200K+ input file with no newlines - we are wastefully calling split_codes_single repeatedly on mostly the same data.
#set overmap [punk::ansi::ta::split_codes_single $startpadding$overdata]
set overmap [punk::ansi::ta::split_codes_single $overdata]
lset overmap 0 "$startpadding[lindex $overmap 0]"
@ -2599,9 +2612,9 @@ tcl::namespace::eval overtype {
#???
set colcursor $opt_colstart
#FUTURE: Create a virtual column object for cleaner column tracking
#Currently need to refer to column1 or columnmin/columnmax without calculating offsets due to startcolumn.
#Need to clarify what start column means from ANSI code movement perspective - offset perspective is unclear.
#TODO - make a little virtual column object
#we need to refer to column1 or columnmin? or columnmax without calculating offsets due to to startcolumn
#need to lock-down what start column means from perspective of ANSI codes moving around - the offset perspective is unclear and a mess.
#set re_diacritics {[\u0300-\u036f]+|[\u1ab0-\u1aff]+|[\u1dc0-\u1dff]+|[\u20d0-\u20ff]+|[\ufe20-\ufe2f]+}
@ -3046,9 +3059,10 @@ tcl::namespace::eval overtype {
set instruction overflow_splitchar
break
} elseif {$owidth > 2} {
#FUTURE: Handle wide graphemes and tabs
#Could be tab with length dependent on tabstops/elastic tabstop settings
#? tab?
#TODO!
puts stderr "overtype::renderline long overtext grapheme '[ansistring VIEW -lf 1 -vt 1 $ch]' not handled"
#tab of some length dependent on tabstops/elastic tabstop settings?
}
} elseif {$idx >= $overflow_idx} {
#REVIEW
@ -3393,7 +3407,8 @@ tcl::namespace::eval overtype {
#we've mapped 7 and 8bit escapes to values we can handle as literals in switch statements to take advantange of jump tables.
switch -- $leadernorm {
1006 {
#FUTURE: Implement mouse event handling
#TODO
#
switch -- [tcl::string::index $codenorm end] {
M {
puts stderr "mousedown $codenorm"
@ -3843,7 +3858,7 @@ tcl::namespace::eval overtype {
#(for use with selective erase: DECSED and DECSEL)
set param [tcl::string::range $codenorm 4 end-2]
if {$param eq ""} {set param 0}
#FUTURE: Store DECSCA like SGR in stacks for replay capability
#TODO - store like SGR in stacks - replays?
switch -exact -- $param {
0 - 2 {
#canerase
@ -4423,7 +4438,8 @@ tcl::namespace::eval overtype {
} else {
set sos_content [string range $code 2 end-2] ;#ST is \x1b\\
}
#FUTURE: Return SOS content in useful form to the caller
#return in some useful form to the caller
#TODO!
lappend sos_list [list string $sos_content row $cursor_row column $cursor_column]
puts stderr "overtype::renderline ESCX SOS UNIMPLEMENTED. code [ansistring VIEW -lf 1 -vt 1 -nul 1 $code]"
}

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

@ -341,7 +341,7 @@ namespace eval punk {
#}
#safest? could be a link?
foreach match [glob -nocomplain -dir $dir -tail {*}$lookfor] {
foreach match [glob -nocomplain -dir $dir -tail -- {*}$lookfor] {
set file [file join $dir $match]
if {[file exists $file] && ![file isdirectory $file]} {
#set assoc [extension_open_association [file extension $file]]
@ -6277,21 +6277,55 @@ namespace eval punk {
namespace eval argdoc {
punk::args::define {
@id -id ::punk::path
@cmd -name "punk::path" -help\
"Introspection of the PATH environment variable.
@cmd -name "punk::path"\
-summary\
"Display PATH executable shadowing and conflicts with TCL commands"\
-help\
{Introspection of the PATH environment variable.
This tool will examine executables within each PATH entry and show which binaries
are overshadowed by earlier PATH entries. It can also be used to examine the contents of each PATH entry, and to filter results using glob patterns."
are overshadowed by earlier PATH entries.
It can also be used to examine the contents of each PATH entry, and to filter results using glob patterns.
${[punk::args::helpers::example {
#show all executables in all PATH entries
punk::path
#show all executables in all PATH entries that contain 'Windows' in the path
punk::path -pathglob *Windows*
#show all executables in all PATH entries that contain 'scoop' in the path,
#and filter the executables to show only those that are named dir, ls or start with 'ca'
punk::path -pathglob *scoop* dir ls ca*
#show all executables that conflict with TCL commands starting with 'a' in the current namespace.
punk::path {*}[nscommandlist a*]
#show all executables that conflict with TCL commands resolvable from the current namespace.
punk::path {*}[info commands]
}]}
see also the punk::auto_exec package.
}
@opts
-binglobs -type list -default {*} -help "glob pattern to filter results. Default '*' to include all entries."
-pathglob -type string -default {*} -multiple true -help "Case insensitive glob pattern to filter path entries. Default '*' to include all PATH directories."
@values -min 0 -max -1
glob -type string -default {*} -multiple true -optional 1 -help "Case insensitive glob pattern to filter path entries. Default '*' to include all PATH directories."
binglob -type list -default {*} -multiple true -optional 1 -help "glob pattern to filter results. Default '*' to include all entries."
}
}
variable d_path_info
variable d_bin_info
variable d_index_executables
#there is still a potential conflict regarding auto_execok on windows - which has some cmd.exe builtins as auto-executable
#- but these are not actually executable files on the filesystem - so they won't be found by our path search
#- but they will be found when not masked by a tcl command.
proc path {args} {
variable d_path_info
variable d_bin_info
variable d_index_executables
set is_windows [expr {$::tcl_platform(platform) eq "windows"}]
set argd [punk::args::parse $args withid ::punk::path]
lassign [dict values $argd] leaders opts values received
set binglobs [dict get $opts -binglobs]
set globs [dict get $values glob]
set pathglobs [dict get $opts -pathglob]
set binglobs [dict get $values binglob]
if {$::tcl_platform(platform) eq "windows"} {
set sep ";"
} else {
@ -6299,14 +6333,18 @@ namespace eval punk {
set sep ":"
}
set all_paths [split [string trimright $::env(PATH) $sep] $sep]
set filtered_paths $all_paths
if {[llength $globs]} {
set filtered_paths [list]
foreach p $all_paths {
foreach g $globs {
if {[string match -nocase $g $p]} {
lappend filtered_paths $p
break
if {[llength $pathglobs]} {
if {[lsearch -exact $pathglobs "*"] >= 0} {
#if we have a wildcard glob then the others are irrelevant - we want to match all paths
set matched_paths $all_paths
} else {
set matched_paths [list]
foreach p $all_paths {
foreach pg $pathglobs {
if {[string match -nocase $pg $p]} {
lappend matched_paths $p
break
}
}
}
}
@ -6344,6 +6382,60 @@ namespace eval punk {
#and the actual executable names (with case and extensions as they appear on the filesystem). We will also build a
#dict keyed by path index which contains the list of executables in that path - to make it easy to show which
#executables are overshadowed by which paths.
if {$is_windows} {
#Sometimes PATHEXT includes an entry of just a dot - which means files with no extension are considered executable.
#We need to account for this in our glob pattern.
set pathexts [list]
if {[info exists ::env(PATHEXT)]} {
set env_pathexts [split $::env(PATHEXT) ";"]
#set pathexts [lmap e $env_pathexts {string tolower $e}]
foreach pe $env_pathexts {
if {$pe eq "."} {
continue
}
lappend pathexts [string tolower $pe]
}
} else {
set env_pathexts [list]
#default PATHEXT if not set - according to Microsoft docs
set pathexts [list .com .exe .bat .cmd]
}
foreach bg $binglobs {
set has_pathext 0
foreach pe $pathexts {
if {[string match -nocase "*$pe" $bg]} {
set has_pathext 1
break
}
}
if {!$has_pathext} {
foreach pe $pathexts {
set globext "$bg$pe"
if {$globext ni $binglobs} {
lappend binglobs "$bg$pe"
}
}
}
}
set lc_binglobs [lmap e $binglobs {string tolower $e}]
if {"." in $pathexts} {
foreach bg $binglobs {
set has_pathext 0
foreach pe $pathexts {
if {[string match -nocase "*$pe" $bg]} {
set base [string range $bg 0 [expr {[string length $bg] - [string length $pe] - 1}]]
set has_pathext 1
break
}
}
if {$has_pathext} {
if {[string tolower $base] ni $lc_binglobs} {
lappend binglobs "$base"
}
}
}
}
}
set d_path_info [dict create] ;#key is normalized path (e.g case-insensitive on windows).
set d_bin_info [dict create] ;#key is normalized executable name (e.g case-insensitive on windows, or callable with extensions stripped off).
@ -6355,63 +6447,21 @@ namespace eval punk {
} else {
set pnorm $p
}
if {[string length $pnorm] > 1} {
set lastchar [string index $pnorm end]
if {$lastchar eq "/" || $lastchar eq "\\"} {
set pnorm [string range $pnorm 0 end-1]
}
}
if {![dict exists $d_path_info $pnorm]} {
dict set d_path_info $pnorm [dict create original_paths [list $p] indices [list $path_idx]]
set executables [list]
if {[file isdirectory $p]} {
#get all files that are executable in this path.
#If we don't normalize the path here - then trailing backslashes on windows can cause a problem with the -tail glob returning a leading slash on the executable names.
#also as we don't necessarily normalize the resulting final path with executable - we want the case to be correct.
set pnormglob [file normalize $p]
if {$::tcl_platform(platform) eq "windows"} {
#Sometimes PATHEXT includes an entry of just a dot - which means files with no extension are considered executable.
#We need to account for this in our glob pattern.
set pathexts [list]
if {[info exists ::env(PATHEXT)]} {
set env_pathexts [split $::env(PATHEXT) ";"]
#set pathexts [lmap e $env_pathexts {string tolower $e}]
foreach pe $env_pathexts {
if {$pe eq "."} {
continue
}
lappend pathexts [string tolower $pe]
}
} else {
set env_pathexts [list]
#default PATHEXT if not set - according to Microsoft docs
set pathexts [list .com .exe .bat .cmd]
}
foreach bg $binglobs {
set has_pathext 0
foreach pe $pathexts {
if {[string match -nocase "*$pe" $bg]} {
set has_pathext 1
break
}
}
if {!$has_pathext} {
foreach pe $pathexts {
lappend binglobs "$bg$pe"
}
}
}
set lc_binglobs [lmap e $binglobs {string tolower $e}]
if {"." in $pathexts} {
foreach bg $binglobs {
set has_pathext 0
foreach pe $pathexts {
if {[string match -nocase "*$pe" $bg]} {
set base [string range $bg 0 [expr {[string length $bg] - [string length $pe] - 1}]]
set has_pathext 1
break
}
}
if {$has_pathext} {
if {[string tolower $base] ni $lc_binglobs} {
lappend binglobs "$base"
}
}
}
}
#TCL's glob on windows is case-insensitive, but in some cases return the result with the case as globbed for regardless of the actual case on the filesystem.
#(This seems to occur when the pattern does *not* contain a wildcard and is probably a bug)
@ -6421,34 +6471,51 @@ namespace eval punk {
# but tcl's glob does not respect the case of even the character-class pattern - so this is not a reliable workaround).
#see punk::fglob for a work-in-progress glob implementation which gives us more control over case sensitivity and the case of results on windows.
set globresults [lsort -unique [glob -nocomplain -directory $pnormglob -types {f x} {*}$binglobs]]
#-----------------------
#JJJ
#set globresults [lsort -unique [glob -nocomplain -directory $pnormglob -types {f x} {*}$binglobs]]
#set executables [list]
#foreach e $globresults {
# puts stderr "glob result: $e"
# puts stderr "normalized executable name: [file tail [file normalize [string range $e 0 end]]]]"
# lappend executables [file tail [file normalize $e]]
#}
#-----------------------
#track all executables in the path - even those that don't match the binglobs
#use fglob to get the actual case of the executables on windows - as glob seems to return the case as globbed for rather than the actual case on the filesystem in some cases.
#this doesn't run a full 'file normalize' on the results which affects whether a more efficient internal representation is stored
#fglob with single glob argument should already return a unique list.
set folder_exes [fglob -nocomplain -directory $pnormglob -types {f x} *]
set executables [list]
foreach e $globresults {
puts stderr "glob result: $e"
puts stderr "normalized executable name: [file tail [file normalize [string range $e 0 end]]]]"
lappend executables [file tail [file normalize $e]]
foreach e $folder_exes {
lappend executables [file tail $e]
}
} else {
set executables [lsort -unique [glob -nocomplain -directory $p -types {f x} -tail {*}$binglobs]]
#set executables [lsort -unique [glob -nocomplain -directory $p -types {f x} -tail {*}$binglobs]]
set executables [lsort -unique [glob -nocomplain -directory $p -types {f x} -tail *]]
}
}
dict set d_index_executables $path_idx $executables
foreach exe $executables {
#todo - other case-insensitive platforms/filesystems.
if {$::tcl_platform(platform) eq "windows"} {
set exenorm [string tolower $exe]
set exe_key [string tolower $exe]
} else {
set exenorm $exe
#on case
set exe_key $exe
}
if {![dict exists $d_bin_info $exenorm]} {
dict set d_bin_info $exenorm [dict create path_indices [list $path_idx] paths [list $p] executable_names [list $exe]]
if {![dict exists $d_bin_info $exe_key]} {
dict set d_bin_info $exe_key [dict create path_indices [list $path_idx] paths [list $p] executable_names [list $exe]]
} else {
#dict lappend d_bin_info $exenorm path_indices $path_idx paths $p executable_names $exe
set bindata [dict get $d_bin_info $exenorm]
#dict lappend d_bin_info $exe_key path_indices $path_idx paths $p executable_names $exe
set bindata [dict get $d_bin_info $exe_key]
dict lappend bindata path_indices $path_idx
dict lappend bindata paths $p
dict lappend bindata executable_names $exe
dict set d_bin_info $exenorm $bindata
dict set d_bin_info $exe_key $bindata
}
}
} else {
@ -6467,16 +6534,16 @@ namespace eval punk {
set executables [dict get $d_index_executables [lindex [dict get $d_path_info $pnorm indices] 0]] ;#get executables for this path
foreach exe $executables {
if {$::tcl_platform(platform) eq "windows"} {
set exenorm [string tolower $exe]
set exe_key [string tolower $exe]
} else {
set exenorm $exe
set exe_key $exe
}
#dict lappend d_bin_info $exenorm path_indices $path_idx paths $p executable_names $exe
set bindata [dict get $d_bin_info $exenorm]
#dict lappend d_bin_info $exe_key path_indices $path_idx paths $p executable_names $exe
set bindata [dict get $d_bin_info $exe_key]
dict lappend bindata path_indices $path_idx
dict lappend bindata paths $p
dict lappend bindata executable_names $exe
dict set d_bin_info $exenorm $bindata
dict set d_bin_info $exe_key $bindata
}
}
@ -6484,18 +6551,255 @@ namespace eval punk {
}
#temporary debug output to check dicts are being built correctly
set debug ""
append debug "Path info dict:" \n
append debug [showdict $d_path_info] \n
append debug "Binary info dict:" \n
append debug [showdict $d_bin_info] \n
append debug "Index executables dict:" \n
append debug [showdict $d_index_executables] \n
#return $debug
puts stdout $debug
#set debug ""
#append debug "Path info dict:" \n
#append debug [showdict $d_path_info] \n
#append debug "Binary info dict:" \n
#append debug [showdict $d_bin_info {*}$binglobs] \n
##append debug "Index executables dict:" \n
##append debug [showdict $d_index_executables] \n
##return $debug
#puts stdout $debug
#dict for {p pinfo} $d_path_info {
# set original_paths [dict get $pinfo original_paths]
# set indices [dict get $pinfo indices]
# puts stdout "Path: $p"
# puts stdout " Original paths: $original_paths"
# puts stdout " Indices in PATH: $indices"
# if {[dict exists $d_index_executables [lindex $indices 0]]} {
# set executables [dict get $d_index_executables [lindex $indices 0]]
# puts stdout " Executables: [llength $executables]"
# } else {
# puts stdout " Executables: (not a directory or no executables found)"
# }
#}
set nscaller [uplevel 1 {::tcl::namespace::current}]
set context_commands [namespace eval $nscaller {info commands}]
#process paths in order they appear in the original PATH.
set pidx 0
#use a punk::textblock::table for formatting.
set rows [list]
set headers [list "idx" "Path" "exe\nCount" "Shadow\nCount" "Executables" "TCL context\nConflicts"]
set ERR [punk::ansi::a+ red bold]
set RST [punk::ansi::a]
set STR [punk::ansi::a+ strike]
set SDW [punk::ansi::a+ red strike]
set WRN [punk::ansi::a+ yellow bold]
set subcols 2
foreach p $all_paths {
#if {$p ni $matched_paths} {
# incr pidx
# continue
#}
set thisrow [list $pidx]
set pnorm [string tolower $p]
if {[string length $pnorm] > 1} {
set lastchar [string index $pnorm end]
if {$lastchar eq "/" || $lastchar eq "\\"} {
set pnorm [string range $pnorm 0 end-1]
}
}
set pinfo [dict get $d_path_info $pnorm]
set original_paths [dict get $pinfo original_paths]
set indices [dict get $pinfo indices]
if {[lindex $indices 0] == $pidx} {
#this is the first occurrence of this path in the original PATH.
set overshadowed [list]
set conflicts [list]
lappend thisrow $p
if {[dict exists $d_index_executables $pidx]} {
set executables [dict get $d_index_executables $pidx]
lappend thisrow [llength $executables]
set display_executables [list]
foreach exe $executables {
set matched_binglob 0
foreach bg $binglobs {
#review - -nocase only on case-insensitive platforms/filesystems?
#- but it is simpler to just apply it to all platforms here rather than trying to determine case-sensitivity of each path.
if {[string match -nocase $bg $exe]} {
set matched_binglob 1
continue
}
}
set exe_key [string tolower $exe]
if {[dict exists $d_bin_info $exe_key]} {
set bindata [dict get $d_bin_info $exe_key]
set path_indices [dict get $bindata path_indices]
set is_overshadowed 0
foreach pi $path_indices {
if {$pi < $pidx} {
lappend overshadowed $exe
set is_overshadowed 1
break
}
}
if {$matched_binglob} {
if {$is_windows} {
#check for matches in context_commands - which are case-insensitive on windows
#the context_commands are however case sensitive.
#we want to mark conflicts in one of two ways in the conflicts column.
#- if there is a case-insensitive match but not a case-sensitive match
#- then we have a conflict but not an exact match - so we will mark this with orange style.
#If there is an exact match in context_commands - then we will mark this with the red style
#to indicate that this executable is overshadowed by a command in the current context.
#we may have multiple tcl commands that conflict with the same executable.
#e.g DIG and dig.
if {[llength [set ncmatches [lsearch -all -inline -nocase $context_commands [file rootname $exe]]]]} {
if {[set exactmatch [lsearch -exact $context_commands [file rootname $exe]]] ne ""} {
foreach nc $ncmatches {
set nc [namespace eval $nscaller [list namespace origin $nc]]
if {$nc eq $exactmatch} {
lappend conflicts $ERR$nc$RST
} else {
lappend conflicts "$WRN$nc$RST"
}
}
} else {
foreach nc $ncmatches {
set nc [namespace eval $nscaller [namespace origin $nc]]
lappend conflicts "$WRN$nc$RST"
}
}
} else {
if {[llength [set ncmatches [lsearch -all -inline -nocase $context_commands $exe]]]} {
if {[set exactmatch [lsearch -exact $context_commands $exe]] ne ""} {
foreach nc $ncmatches {
set nc [namespace eval $nscaller [namespace origin $nc]]
if {$nc eq $exactmatch} {
lappend conflicts $ERR$nc$RST
} else {
lappend conflicts "$WRN$nc$RST"
}
}
} else {
foreach nc $ncmatches {
set nc [namespace eval $nscaller [namespace origin $nc]]
lappend conflicts "$WRN$nc$RST"
}
}
}
}
} else {
#check for any exact matches in context_commands
if {$exe in $context_commands} {
lappend conflicts $ERR$exe$RST
}
}
if {$is_overshadowed} {
lappend display_executables "$SDW$exe$RST"
} else {
lappend display_executables $exe
}
}
} else {
#executable not found in bin_info dict - this shouldn't happen - but if it does we will just treat it as not overshadowed and include it in the display.
lappend display_executables $WRN$exe$RST
}
}
if {[llength $overshadowed]} {
lappend thisrow "$ERR[llength $overshadowed]$RST"
} else {
lappend thisrow "0"
}
if {[llength $display_executables]} {
lappend thisrow [textblock::list_as_table -columns $subcols -show_edge 0 $display_executables]
} else {
lappend thisrow ""
}
if {[llength $conflicts]} {
#lappend thisrow [textblock::list_as_table -columns $subcols -show_edge 0 $conflicts]
lappend thisrow [join $conflicts \n]
} else {
lappend thisrow ""
}
} else {
lappend thisrow ""
lappend thisrow ""
lappend thisrow ""
lappend thisrow "(not a directory or no executables found)"
lappend thisrow ""
}
} else {
#this is a duplicate path entry - we want to show it as a duplicate of the original path entry.
set original_path_idx [lindex $indices 0]
set original_path [lindex [dict get $d_path_info $pnorm original_paths] 0]
#duplicate paths might be cased differently.
lappend thisrow "$ERR$p (repeated pathentry)\n original at index $original_path_idx as\n$original_path$RST"
set overshadowed [list]
set conflicts [list]
set display_executables [list]
if {[dict exists $d_index_executables $original_path_idx]} {
set executables [dict get $d_index_executables $original_path_idx]
lappend thisrow [llength $executables]
foreach exe $executables {
set exe_key [string tolower $exe]
if {[dict exists $d_bin_info $exe_key]} {
set bindata [dict get $d_bin_info $exe_key]
set path_indices [dict get $bindata path_indices]
set is_overshadowed 0
foreach pi $path_indices {
if {$pi < $pidx} {
lappend overshadowed $exe
set is_overshadowed 1
break
}
}
#dupe will always have all exes as overshadowed by the original.
#don't need to waste time and screen space to display duplicate info - the user should tidy up the PATH.
#if {$is_overshadowed} {
# lappend display_executables "$SDW$exe$RST"
#} else {
# lappend display_executables $exe
#}
}
}
} else {
#this shouldn't happen - but if it does we will just treat it as not overshadowed and include it in the display.
lappend thisrow "(not a directory or no executables found)"
}
if {[llength $overshadowed]} {
lappend thisrow "$ERR[llength $overshadowed]$RST"
} else {
lappend thisrow "0"
}
if {[llength $display_executables]} {
lappend thisrow [textblock::list_as_table -columns $subcols -show_edge 0 $display_executables]
} else {
lappend thisrow ""
}
lappend thisrow "" ;#don't show conflict info for duplicate paths - as the user should tidy up the PATH to remove duplicates, and the conflict info will be the same as the original path entry.
}
if {[llength $matched_paths] < [llength $all_paths]} {
#if there is any filtering of paths - then we want to show all these paths whether or not there are any matches for binglobs
if {$p in $matched_paths} {
lappend rows $thisrow
}
} else {
#no specific filtering of paths - so only show rows where there are matches for binglobs
if {[lsearch -exact $binglobs "*"] >= 0} {
lappend rows $thisrow
} else {
#end-1 is the executables column.
#if there are no matches for binglobs then we'll hide the row.
if {[string length [lindex $thisrow end-1]] > 0} {
lappend rows $thisrow
}
}
}
incr pidx
}
set t [textblock::table -return tableobject -rows $rows -headers $headers]
return [$t print]
}
#-------------------------------------------------------------------
@ -8024,8 +8328,8 @@ namespace eval punk {
set title "[a+ brightgreen] Filesystem navigation: "
set cmdinfo [list]
lappend cmdinfo [list ./ "?${I}glob${NI}?" "view/change dir, list dirs."]
lappend cmdinfo [list ../ "?${I}path${NI}" "go up one dir, then to path if given"]
lappend cmdinfo [list .// "?${I}glob${NI}?" "view/change dir, list dirs and files"]
lappend cmdinfo [list ../ "?${I}path${NI}" "go up one dir, then to path if given"]
lappend cmdinfo [list newdir "${I}subdir${NI}..." "make new dir or dirs and show status"]
lappend cmdinfo [list fcat "${I}file ?file?...${NI}" "cat file(s)"]
set t [textblock::class::table new -minwidth 80 -show_seps 0]
@ -8238,11 +8542,33 @@ namespace eval punk {
lappend chunks [list stdout $text]
}
console - term - terminal {
set term_env_vars {TERM TERM_PROGRAM TERM_PROGRAM_VERSION}
set term_dict [dict create]
foreach e $term_env_vars {
if {[info exists ::env($e)]} {
dict set term_dict $e [set ::env($e)]
} else {
dict set term_dict $e "(NOT SET)"
}
}
set text "Terminal environment variables:\n"
append text [punk::lib::showdict $term_dict] \n
lappend chunks [list stdout $text]
set text ""
if {[catch {package require punk::console} result]} {
set text "Unable to load punk::console package - cannot test\n$result"
lappend chunks [list stdout $text]
} else {
if {![catch {punk::console::class_info} console_class_info]} {
set text "Terminal class info (from device secondary attributes query to terminal):\n"
append text [punk::lib::showdict $console_class_info] \n
} else {
set text "Unable to query terminal class info - err:$console_class_info\n"
}
lappend chunks [list stdout $text]
set indent [string repeat " " [string length "WARNING: "]]
lappend cstring_tests [dict create\
type "PM "\
@ -8339,7 +8665,7 @@ namespace eval punk {
}
}
if {![string length $warningblock]} {
set text "No terminal warnings\n"
set text "[a+ green]No terminal warnings[a]\n"
lappend chunks [list stdout $text]
}
}
@ -8351,6 +8677,7 @@ namespace eval punk {
"tcl" "Tcl version warnings"\
"env|environment" "punkshell environment vars"\
"console|terminal" "Some console behaviour tests and warnings"\
"*" "Try to find help on the topic as a command or external executable"\
]
set t [textblock::class::table new -show_seps 0]

1
src/bootsupport/modules/punk/aliascore-0.1.0.tm

@ -117,6 +117,7 @@ tcl::namespace::eval punk::aliascore {
plist {::punk::lib::pdict -roottype list}\
showlist {::punk::lib::showdict -roottype list}\
rehash ::punk::auto_exec::rehash\
hash ::punk::auto_exec::hash\
showdict ::punk::lib::showdict\
ansistrip ::punk::ansi::ansistrip\
stripansi ::punk::ansi::ansistrip\

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

@ -3920,7 +3920,10 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
}
lappend PUNKARGS [list {
@id -id ::punk::ansi::a+
@cmd -name "punk::ansi::a+" -help\
@cmd -name "punk::ansi::a+"\
-summary\
"ANSI SGR code generator with no reset prefix"\
-help\
"Returns an ANSI sgr escape sequence based on the list of supplied codes.
Unlike punk::ansi::a - it is not prefixed with an ANSI reset.
"
@ -3935,7 +3938,10 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
lappend PUNKARGS [list {
@id -id ::punk::ansi::a
@cmd -name "punk::ansi::a" -help\
@cmd -name "punk::ansi::a"\
-summary\
"ANSI SGR code generator with reset prefix"\
-help\
"Returns an ANSI sgr escape sequence based on the list of supplied codes.
Unlike punk::ansi::a+ - it is prefixed with an ANSI reset.
"
@ -6865,7 +6871,14 @@ tcl::namespace::eval punk::ansi::ta {
#may be same as detect - kept in case detect needs to diverge
#variable re_ansi_split "${re_csi_code}|${re_esc_osc1}|${re_esc_osc2}|${re_esc_osc3}|${re_standalones}|${re_ST}|${re_g0_open}|${re_g0_close}"
set re_ansi_split $re_ansi_detect
#experiment with const for a regex - seems to make no difference to performance - but it does make it clear that the regex is not intended to be modified at runtime
if {[catch {const re_ansi_split $re_ansi_detect}]} {
#tcl 9 has const but tcl 8 doesn't - so we just set it as a normal variable
variable re_ansi_split
set re_ansi_split $re_ansi_detect
}
variable re_ansi_split_multi
if {[string first (?x) $re_ansi_split] == 0} {
set re_ansi_split_multi "(?x)(?:[string range ${re_ansi_split} 4 end])+"
@ -7161,7 +7174,7 @@ tcl::namespace::eval punk::ansi::ta {
#micro optimisations on split_codes to avoid function calls and make re var local tend to yield very little benefit (sub uS diff on calls that commonly take 10s/100s of uSeconds)
#like split_codes - but each ansi-escape is split out separately (with empty string of plaintext between codes so even/odd indices for plain ansi still holds)
#- the slightly simpler regex than split_codes means that it will be slightly faster than keeping the codes grouped.
#- the regex is slighly simpler than for split_codes - but split_codes is faster when there are consecutive codes.
proc split_codes_single {text} {
if {$text eq ""} {
return {}
@ -7177,7 +7190,26 @@ tcl::namespace::eval punk::ansi::ta {
#set next [lindex $cr 1]+1 ;#text index-expression for string range
}
lappend list [tcl::string::range $text $next end]
return $list
}
proc split_codes_single2 {text} {
return [_perlish_split2 $::punk::ansi::ta::re_ansi_split $text]
}
proc split_codes_single3 {text} {
#no faster
if {$text eq ""} {
return {}
}
variable re_ansi_split
set next 0
set coderanges [regexp -indices -all -inline -- $re_ansi_split $text]
set list [lrepeat [expr {[llength $coderanges]*2}] ""]
set r 0
foreach cr $coderanges {
ledit list $r $r+1 [tcl::string::range $text $next [lindex $cr 0]-1] [tcl::string::range $text [lindex $cr 0] [lindex $cr 1]]
set next [expr {[lindex $cr 1]+1}]
incr r
}
return [list {*}$list [tcl::string::range $text $next end]]
}
proc split_codes_single2 {text} {
variable re_ansi_split
@ -7202,7 +7234,6 @@ tcl::namespace::eval punk::ansi::ta {
set next [expr {[lindex $cr 1]+1}]
}
lappend list [tcl::string::range $text $next end]
return $list
}
proc _perlish_split2 {re text} {
if {$text eq ""} {

259
src/bootsupport/modules/punk/args-0.2.1.tm

@ -771,9 +771,9 @@ tcl::namespace::eval punk::args {
literal(<string>)
(exact match for string)
literalprefix(<string>)
(prefix match for string, other literal and literalprefix
(tcl::prefix::match of string, other literal and literalprefix
entries specified as alternates using | are used in the
calculation)
unique prefix calculation)
stringstartswith(<string>)
(value must match glob <string>*)
The value of string must not contain pipe char '|'
@ -785,7 +785,7 @@ tcl::namespace::eval punk::args {
e.g literalprefix(text)|literalprefix(binary)
(when all in the pipe-delimited type-alternates set are
literal or literalprefix - this is similar to the -choices
option)
option with -choiceprefix true)
and more.. (todo - document here)
@ -906,6 +906,8 @@ tcl::namespace::eval punk::args {
is preserved.
-minsize (type dependant)
-maxsize (type dependant)
-mincap {only valid for regex type - min number of captures}
-maxcap {only valid for regex type - max number of captures}
-range (type dependant - only valid if -type is a single item)
-typeranges (list with same number of elements as -type)
-help <string>
@ -2529,6 +2531,15 @@ tcl::namespace::eval punk::args {
#review -solo 1 vs -type none ? conflicting values?
tcl::dict::set spec_merged $spec $specval
}
-mincap - -maxcap {
#todo - allow as default for @leaders, @opts and @values when default -type there is regex or regexp?
#only applies to type regex
set tp [tcl::dict::get $spec_merged -type]
if {![string match *regex* $tp]} {
error "punk::args::resolve - invalid use of '$spec' key for argument '$argname'. '$spec' only applies to arguments with a type of regex or regexp. argument has type '$tp' @id:$DEF_definition_id"
}
tcl::dict::set spec_merged $spec $specval
}
-range {
#allow simple case to be specified without additional list wrapping
#only multi-types require full list specification
@ -2624,7 +2635,9 @@ tcl::namespace::eval punk::args {
-range -typeranges\
-default -defaultdisplaytype -typedefaults\
-minsize -maxsize -choices -choicegroups\
-mincap -maxcap\
-choicemultiple -choicecolumns -choiceprefix -choiceprefixdenylist -choiceprefixreservelist -choicerestricted\
-choicelabels -choiceinfo \
-unindentedfields\
-nocase -optional -multiple -validate_ansistripped -allow_ansi -strip_ansi -help\
-multipleunique -choicemultipleunique -choicemultipleuniqueset\
@ -3816,7 +3829,8 @@ tcl::namespace::eval punk::args {
set arg_error_CLR_info(check) [a+ brightgreen bold]
set arg_error_CLR_info(choiceprefix) [a+ brightgreen bold]
set arg_error_CLR_info(groupname) [a+ cyan bold]
set arg_error_CLR_info(ansiborder) [a+ brightcyan bold]
#set arg_error_CLR_info(ansiborder) [a+ brightcyan bold]
set arg_error_CLR_info(ansiborder) [a+ term-grey23 bold]
set arg_error_CLR_info(ansibase_header) [a+ cyan]
set arg_error_CLR_info(ansibase_body) [a+ white]
variable arg_error_CLR_error
@ -5236,13 +5250,13 @@ tcl::namespace::eval punk::args {
switch -- $tailtype {
withid {
#JJJ
#set id [lindex $opts_and_vals 0]
set deflist [raw_def [lindex $opts_and_vals 0]]
if {[llength $deflist] == 0} {
if {[llength $opts_and_vals] != 1} {
#error "punk::args::parse - invalid call. Expected exactly one argument after 'withid'"
punk::args::parse $args withid ::punk::args::parse
}
set id [lindex $opts_and_vals 0]
error "punk::args::parse - no such id: $id"
}
}
@ -5415,7 +5429,8 @@ tcl::namespace::eval punk::args {
}
#return number of values we can assign to cater for variable length clauses such as {"elseif" expr "?then?" body}
#return number of values we can assign to cater for variable length clauses such as:
# {"elseif" expr "?then?" body}
#review - efficiency? each time we call this - we are looking ahead at the same info
proc _get_dict_can_assign_value {idx values nameidx names namesreceived formdict} {
set ARG_INFO [dict get $formdict ARG_INFO]
@ -5426,12 +5441,23 @@ tcl::namespace::eval punk::args {
#todo - work backwards with any (optional or not) literals at tail that match our values - and remove from assignability.
set ridx 0
#puts "-=============- thisname:'$thisname' thistype:'$thistype' tailnames:'$tailnames' all_remaining:'$all_remaining' [info level -2]"
foreach clausename [lreverse $tailnames] {
#puts "=============== clausename:$clausename all_remaining: $all_remaining"
#puts "=============== thisname:'$thisname' thistype:'$thistype' clausename:'$clausename' all_remaining:'$all_remaining'"
set clause_is_multiple [dict get $ARG_INFO $clausename -multiple]
set clause_is_optional [dict get $ARG_INFO $clausename -optional]
set typelist [dict get $ARG_INFO $clausename -type]
#---------------
#review - not quite right to look for literal* in typelist
#- we should be looking for any type-alternate that starts with literal( or literalprefix(
#- but for now we require the whole type to be literal* if it's a literal match type.
# We should probably also support stringstartswith(*) and stringendswith(*) too.
#also consider that -choices {abc def} is effectively a literal match type too - we should support that here as well.
if {[lsearch $typelist literal*] == -1} {
break
}
#---------------
set max_clause_length [llength $typelist]
if {$max_clause_length == 1} {
#basic case
@ -5452,28 +5478,50 @@ tcl::namespace::eval punk::args {
}
#foreach tp_alternative [split $tp |] {}
foreach tp_alternative [_split_type_expression $tp] {
set tp_alternatives [_split_type_expression $tp]
foreach tp_alternative $tp_alternatives {
switch -exact -- [lindex $tp_alternative 0] {
literal {
set litinfo [string range $tp 7 end] ;#get bracketed part if of form literal(xxx)
set match [lindex $tp_alternative 1]
set match [lindex $tp_alternative 1] ;#was bracketed part if of form literal(xxx)
if {$v eq $match} {
set alloc_ok 1
ledit all_remaining end end
if {![dict get $ARG_INFO $clausename -multiple]} {
if {!$clause_is_multiple} {
ledit tailnames end end
}
#the type (or one of the possible type alternates) matched a literal
break
}
}
literalprefix {
set prefix_of [lindex $tp_alternative 1]
#get list of literal and literalprefix values in the current list of tp_alternatives so we can construct list of alternatives for tcl::prefix::match prefix calculation.
#todo - consider if this clause also has -choices {abc def} - we should support those as well here as literal matches for the purposes of calculating the prefix match.
# (this is somewhat of an edge case but sometimes it's useful to specify a -type when -choices is used with -choicerestricted false, to allow only specific values not in the choices list.)
set comparelist [list]
foreach alt $tp_alternatives {
switch -exact -- [lindex $alt 0] {
literal - literalprefix {
lappend comparelist [lindex $alt 1]
}
}
}
set fullmatch [tcl::prefix::match -error "" $comparelist $v]
if {$fullmatch eq $prefix_of} {
set alloc_ok 1
ledit all_remaining end end
if {!$clause_is_multiple} {
ledit tailnames end end
}
break
}
}
stringstartswith {
set pfx [lindex $tp_alternative 1]
if {[string match "$pfx*" $v]} {
set alloc_ok 1
set alloc_ok 1
ledit all_remaining end end
if {![dict get $ARG_INFO $clausename -multiple]} {
if {!$clause_is_multiple} {
ledit tailnames end end
}
break
@ -5483,10 +5531,9 @@ tcl::namespace::eval punk::args {
stringendswith {
set sfx [lindex $tp_alternative 1]
if {[string match "*$sfx" $v]} {
set alloc_ok 1
set alloc_ok 1
ledit all_remaining end end
if {![dict get $ARG_INFO $clausename -multiple]} {
if {!$clause_is_multiple} {
ledit tailnames end end
}
break
@ -5497,7 +5544,7 @@ tcl::namespace::eval punk::args {
}
}
if {!$alloc_ok} {
if {![dict get $ARG_INFO $clausename -optional]} {
if {!$clause_is_optional} {
break
}
}
@ -5519,6 +5566,7 @@ tcl::namespace::eval punk::args {
set reverse_type_index 0
#todo handle type-alternates
# for example: -type {string literal(x)|literal(y)}
# -type {string literal(max)|literal(min)|int}
foreach tp $rtypelist {
#set rv [lindex $rcvals end-$alloc_count]
set rv [lindex $all_remaining end-$alloc_count]
@ -5528,8 +5576,24 @@ tcl::namespace::eval punk::args {
set clause_member_optional 0
}
set tp [string trim $tp ?]
puts "_get_dict_can_assign_value: checking tp '$tp' against value '$rv'"
switch -glob -- $tp {
literal* {
"literal(*" {
set litmatch [string range $tp 8 end-1]
if {$rv eq $litmatch} {
set alloc_ok 1 ;#we need at least one literal-match to set alloc_ok
incr alloc_count
} else {
if {$clause_member_optional} {
#
} else {
set alloc_ok 0
break
}
}
}
XXXliteral* {
#JJJ
set litinfo [string range $tp 7 end]
set match [string range $litinfo 1 end-1]
#todo -literalprefix
@ -5594,7 +5658,7 @@ tcl::namespace::eval punk::args {
#set all_remaining [lrange $all_remaining end-$n end]
set all_remaining [lrange $all_remaining 0 end-$alloc_count]
#don't lpop if -multiple true
if {![dict get $ARG_INFO $clausename -multiple]} {
if {!$clause_is_multiple} {
#lpop tailnames
ledit tailnames end end
}
@ -6421,13 +6485,48 @@ tcl::namespace::eval punk::args {
break
}
regex - regexp {
#todo - allow -min and -max to specify number of allowed subexpressions(capture groups) present in regex?
if {[catch {regexp -about $e_check} re_about_msg]} {
set msg "$argclass $argname for %caller% requires type regexp. $re_about_msg. Received: '$e_check'"
lset clause_results $c_idx $a_idx [list errorcode [list PUNKARGS VALIDATION [list typemismatch $type] -badarg $argname -argspecs $argspecs] msg $msg]
} else {
lset clause_results $c_idx $a_idx 1
break
#optional -mincap and -maxcap specify number of allowed subexpressions(capture groups) present in regex
set num_caps [lindex $re_about_msg 0]
set mincap 0 ;#default
set maxcap -1 ;#default -1 for unlimited
if {[dict exists $thisarg_checks -mincap]} {
set mincap [dict get $thisarg_checks -mincap]
}
if {[dict exists $thisarg_checks -maxcap]} {
set maxcap [dict get $thisarg_checks -maxcap]
}
if {$maxcap == -1 && $mincap == 0} {
#no cap limits - just accept the regex as valid
lset clause_results $c_idx $a_idx 1
break
} else {
#we have at least one cap limit - we need to count the number of subexpressions in the regex and check it against the limits
if {$maxcap == -1} {
#unlimited maxcap - just check mincap
if {$num_caps < $mincap} {
set msg "$argclass $argname for %caller% requires type regexp with at least $mincap capture groups. Received regex has only $num_caps capture groups. Regex: '$e_check'"
lset clause_results $c_idx $a_idx [list errorcode [list PUNKARGS VALIDATION [list typemismatch $type] -badarg $argname -argspecs $argspecs] msg $msg]
} else {
lset clause_results $c_idx $a_idx 1
break
}
} else {
if {$num_caps < $mincap} {
set msg "$argclass $argname for %caller% requires type regexp with at least $mincap capture groups. Received regex has only $num_caps capture groups. Regex: '$e_check'"
lset clause_results $c_idx $a_idx [list errorcode [list PUNKARGS VALIDATION [list typemismatch $type] -badarg $argname -argspecs $argspecs] msg $msg]
} elseif {$num_caps > $maxcap} {
set msg "$argclass $argname for %caller% requires type regexp with no more than $maxcap capture groups. Received regex has $num_caps capture groups. Regex: '$e_check'"
lset clause_results $c_idx $a_idx [list errorcode [list PUNKARGS VALIDATION [list typemismatch $type] -badarg $argname -argspecs $argspecs] msg $msg]
} else {
lset clause_results $c_idx $a_idx 1
break
}
}
} ;#every leaf of this nested if should have an lset clause_results with 1 for pass or errorcode/msg for fail
}
}
indexexpression {
@ -6776,10 +6875,30 @@ tcl::namespace::eval punk::args {
break
}
}
path -
file -
directory -
directory {
#see comments in existingpath/existingfile/existingdirectory case about the challenges of validating filesystem paths in a general way that works across platforms and use cases.
if {[tcl::string::length $e_check]==0 || [string first \0 $e_check] >= 0} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which doesn't look like it could be a path, file or directory"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
}
lset clause_results $c_idx $a_idx 1
}
existingpath -
existingfile -
existingdirectory {
#do we need types for relative vs absolute paths? readable writable executable owned?
#on windows limit to certain file extensions?
#fileutil::magic::filetype?
#Perhaps these are steps too far for a general validation framework.
#consider - callback validation functions instead?
#ideally we want to define callback validation functions that can work not just on a single argument at a time.
#e.g for testing that 2 file arguments do or don't refer to the same file or are in same directory or same filesystem etc.
#we have to support file and directory names on all platforms - and even characters illegal on a filesystem/platform may need to be passed.
#For example a file/folder may be created with an illegal name on a platform (or mounted on it) and be mapped to another string on the filesystem
#- yet it may remain accessible to commands such as file stat etc via the string with 'illegal' characters as well as its underlying stored (mapped) name.
@ -6790,17 +6909,43 @@ tcl::namespace::eval punk::args {
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
}
if {$type eq "existingfile"} {
if {![file exists $e_check]} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which is not an existing file"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
# -------------------------------------------------------
#review - what do we want to happen with links?
#on unix TCL's file readlink should reliably give us a path to determine the type pointed to.
#on windows we can do so if the link happens to be a junction.
#however on windows we can also have symbolic links which are not junctions and which may point to files or directories
#- but unfortunately tcl's file readlink doesn't seem to be able to read them at all - raises an error.
#(the error seems to be different for a file vs a directory target - but this seems an unreliable mechanism to determine the type of the target)
#At the moment TCL's 'file isfile' and 'file isdirectory' both seem to do the right things for links
#despite the above - treating them as the type of their target
# review whether this is reliable in all cases on windows.
# -------------------------------------------------------
#windows shortcuts (.lnk files) can point to a file or directory - but we can quite reasonably treat them only as files,
#as users *probably* won't have the expectation that a shortcut which points to a directory should be treated as a directory.
switch -exact -- $type {
existingpath {
if {![file exists $e_check]} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which is not an existing path"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
}
}
} elseif {$type eq "existingdirectory"} {
if {![file isdirectory $e_check]} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which is not an existing directory"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
existingfile {
if {![file isfile $e_check]} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which is not an existing file"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
}
}
existingdirectory {
if {![file isdirectory $e_check]} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which is not an existing directory"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
}
}
}
lset clause_results $c_idx $a_idx 1
@ -6809,6 +6954,11 @@ tcl::namespace::eval punk::args {
existingportabledirectory -
portablefile -
portabledirectory {
#review - many absolute paths are not strictly portable when considered as a whole e.g /usr/local/bin c:/test
#- but the idea was more about the directory and file name components being portable excluding the first component.
#this concept may need work as it's unintuitive what it means to be a portable file/directory vs not.
#what about windows specific paths such as //?/ //./ or UNC paths?
if {[tcl::string::length $e_check]==0 || [string first \0 $e_check] >= 0 || [punk::winpath::illegalname_test $e_check]} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which doesn't look like it could be a portable file or directory (must pass punk::winpath::illegalname_test)"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
@ -8699,7 +8849,7 @@ tcl::namespace::eval punk::args {
set leadername [lindex $LEADER_NAMES $nameidx]
set ldr [lindex $leaders $ldridx]
if {$leadername ne ""} {
set leadertypelist [tcl::dict::get $argstate $leadername -type]
set leadertypelist [tcl::dict::get $argstate $leadername -type] ;#often a single type, but can be a list of types (possibly with some optional) for a type that is a clause accepting multiple values.
set leader_clause_size [llength $leadertypelist]
set assign_d [_get_dict_can_assign_value $ldridx $leaders $nameidx $LEADER_NAMES $leadernames_received $formdict]
@ -8738,11 +8888,23 @@ tcl::namespace::eval punk::args {
set clauseval $resultlist
incr ldridx [expr {$consumed - 1}]
#not quite right.. this sets the -type for all clauses - but they should run independently
#e.g if expr {} elseif 2 {script2} elseif 3 then {script3} (where elseif clause defined as "literal(elseif) expr ?literal(then)? script")
#the elseif 2 {script2} will raise an error because the newtypelist from elseif 3 then {script3} overwrote the newtypelist where then was given the type ?omitted-...?
#not quite right.. this modifies the -type for all clauses with this name - but for -multiple true each instance should really be considered separately.
#e.g when a subelement-containing clause is allowed to appear multiple times (-multiple true)
# - we may hava a situation where the supplied arguments do and don't omit optional subelements,
# and the newtypelist from one clause may overwrite the newtypelist from the other clause where the optional subelement was omitted in one arg, but not in the other arg.
# - if expr {} elseif 2 {script2} elseif 3 then {script3}
# - (where elseif clause defined as "literal(elseif) expr ?literal(then)? script")
# The elseif 2 {script2} will reassign the type as "literal(elseif) expr ?omitted-literal(then)? script"
# when the elseif 3 then {script3} is processed, 'then' is now considered against the type ?ommitted-literal(then)?
#which (as a non-recognised type is therefore not validated ) will then
# allow any value instead of 'then' to pass.
tcl::dict::set argstate $leadername -type $newtypelist ;#(possible ?omitted-<type>? and ?defaulted-<type>? entries
#see argument_clause_typestate in value processing loop below for more handling of this issue regarding -multiple true clauses with optional subelements
#todo - synchronize with value processing loop below
#- consider refactor to a common procedure for handling this issue of tracking updated typelist state for optional subelements in -multiple true clauses
#incorrect -don't update default -type info.
#tcl::dict::set argstate $leadername -type $newtypelist ;#(possible ?omitted-<type>? and ?defaulted-<type>? entries
}
if {[tcl::dict::get $argstate $leadername -multiple]} {
@ -8904,7 +9066,7 @@ tcl::namespace::eval punk::args {
}
#incorrect - we shouldn't update the default. see argument_clause_typestate dict of lists of -type
tcl::dict::set argstate $valname -type $newtypelist ;#(possible ?omitted-<type>? and ?defaulted-<type>? entries
#tcl::dict::set argstate $valname -type $newtypelist ;#(possible ?omitted-<type>? and ?defaulted-<type>? and ?validated-<type>? entries
}
if {[tcl::dict::get $argstate $valname -multiple]} {
@ -9206,7 +9368,7 @@ tcl::namespace::eval punk::args {
}
set vlist_typelist [list]
if {[dict exists $argument_clause_typestate $argname]} {
#lookup saved newtypelist (argument_clause_typelist) from can_assign_value result where some optionals were given type ?omitted-<tp>? or ?defaulted-<tp>?
#lookup saved newtypelist (argument_clause_typelist) from can_assign_value result where some optionals were given type ?omitted-<tp>? or ?defaulted-<tp>? or ?validated-<tp>?.
# args.test: parse_withdef_value_clause_missing_optional_multiple
set vlist_typelist [dict get $argument_clause_typestate $argname]
} else {
@ -9315,11 +9477,12 @@ tcl::namespace::eval punk::args {
#fast fail on the wrong number of choices
if {[llength $c_list] < $choicemultiple_min} {
set msg "$argclass $argname for %caller% requires at least $choicemultiple_min choices. Received [llength $c_list] choices."
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list choicecount [llength $c_list] minchoices $choicemultiple_min maxchoices $choicemultiple_max] -badarg $argname]] $msg
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list choicecount [llength $c_list] minchoices $choicemultiple_min maxchoices $choicemultiple_max] -badarg $argname -argspecs $argspecs]] $msg
#return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list optionmissing $full_missing received $flagsreceived] -argspecs $argspecs]] $msg
}
if {$choicemultiple_max != -1 && [llength $c_list] > $choicemultiple_max} {
set msg "$argclass $argname for %caller% requires at most $choicemultiple_max choices. Received [llength $c_list] choices."
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list choicecount [llength $c_list] minchoices $choicemultiple_min maxchoices $choicemultiple_max] -badarg $argname]] $msg
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list choicecount [llength $c_list] minchoices $choicemultiple_min maxchoices $choicemultiple_max] -badarg $argname -argspecs $argspecs]] $msg
}
#-----------------------------------
@ -9435,7 +9598,19 @@ tcl::namespace::eval punk::args {
}
tcl::dict::set $dname $argname_or_ident $existing
} else {
lset existing $element_index $choice_idx $chosen
#test required.
# punk::args::parse {{read write w}} withdef @values {mode -type list -choices {read write} -choicemultiple {1 -1}}
#puts ">>> clause_size $clause_size"
#puts ">>> existing $existing"
#puts ">>> lset existing $element_index $choice_idx $chosen"
if {$clause_size == 1} {
#e.g -type list
#we have multiple choices allowed for a single element clause because that clause type is a list.
lset existing $choice_idx $chosen
} else {
#e.g -type {any any}
lset existing $element_index $choice_idx $chosen
}
tcl::dict::set $dname $argname_or_ident $existing
}
}

438
src/bootsupport/modules/punk/args/moduledoc/tclcore-0.1.0.tm

@ -102,7 +102,8 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
set manbase_tcl "https://tcl.tk/man/tcl/TclCmd"
set manbase_ext .htm
} else {
set manbase_tcl "https://tcl.tk/man/tcl9.0/TclCmd"
set tclv [info tclversion] ;#e.g 9.0 9.1
set manbase_tcl "https://tcl.tk/man/tcl${tclv}/TclCmd"
set manbase_ext .html
}
proc manpage_tcl {cmd} {
@ -1468,7 +1469,10 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
namespace eval argdoc {
lappend PUNKARGS [list {
@id -id ::tcl::chan::blocked
@cmd -name "Built-in: tcl::chan::blocked" -help\
@cmd -name "Built-in: tcl::chan::blocked"\
-summary\
"Test whether the last input operation failed because it would have blocked."\
-help\
"This tests whether the last input operation on the channel called ${$I}channel${$NI}
failed because it would otherwise have caused the process to block, and returns 1
if that was the case. It returns 0 otherwise. Note that this only ever returns 1
@ -1481,15 +1485,19 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
lappend PUNKARGS [list {
@id -id ::tcl::chan::close
@cmd -name "Built-in: tcl::chan::close" -help\
@cmd -name "Built-in: tcl::chan::close"\
-summary\
"Close and destroy a channel."\
-help\
"Close and destroy the channel called channel. Note that this deletes all existing file-events
registered on the channel. If the direction argument (which must be read or write or any
registered on the channel. If the direction argument (which must be ${$B}read${$N} or ${$B}write${$N} or any
unique abbreviation of them) is present, the channel will only be half-closed, so that it can
go from being read-write to write-only or read-only respectively. If a read-only channel is
closed for reading, it is the same as if the channel is fully closed, and respectively similar
for write-only channels. Without the direction argument, the channel is closed for both reading
and writing (but only if those directions are currently open). It is an error to close a
read-only channel for writing, or a write-only channel for reading.
As part of closing the channel, all buffered output is flushed to the channel's output device
(only if the channel is ceasing to be writable), any buffered input is discarded (only if the
channel is ceasing to be readable), the underlying operating system resource is closed and
@ -1540,6 +1548,7 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
{Query/set channel configuration options}\
-help\
{Query or set the configuration options of the channel named ${$I}channel${$NI}
If no ${$I}optionName${$NI} or ${$I}value${$NI} arguments are supplied, the
command returns a list containing alternating option names and values for the
channel. If ${$I}optionName${$NI} is supplied but no ${$I}value${$NI} then the
@ -1809,6 +1818,57 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
}
}]
lappend PUNKARGS [list {
@id -id ::tcl::chan::create
@cmd -name "Built-in: tcl::chan::create"\
-summary\
"Create new script level channel."\
-help\
"This subcommand creates a new script level channel using the command prefix ${$I}cmdPrefix${$NI} as its handler.
Any such channel is called a ${$B}reflected${$N} channel. The specified command prefix, ${$I}cmdPrefix${$NI}, must be a non-empty list,
and should provide the API described in the ${$B}refchan${$N} manual page. The handle of the new channel is returned as the
result of the ${$B}chan create${$N} command, and the channel is open. Use either ${$B}close${$N} or ${$B}chan close${$N} to remove the channel.
The argument mode specifies if the new channel is opened for reading, writing, or both. It has to be a list
containing any of the strings “read” or “write”, The list must have at least one element, as a channel you can
neither write to nor read from makes no sense. The handler command for the new channel must support the chosen mode,
or an error is thrown.
The command prefix is executed in the global namespace, at the top of call stack, following the appending of arguments
as described in the ${$B}refchan${$N} manual page. Command resolution happens at the time of the call. Renaming the command, or
destroying it means that the next call of a handler method may fail, causing the channel command invoking the handler
to fail as well. Depending on the subcommand being invoked, the error message may not be able to explain the reason
for that failure.
Every channel created with this subcommand knows which interpreter it was created in, and only ever executes its
handler command in that interpreter, even if the channel was shared with and/or was moved into a different interpreter.
Each reflected channel also knows the thread it was created in, and executes its handler command only in that thread,
even if the channel was moved into a different thread. To this end all invocations of the handler are forwarded to the
original thread by posting special events to it. This means that the original thread (i.e. the thread that executed the
${$B}chan create${$N} command) must have an active event loop, i.e. it must be able to process such events. Otherwise the thread
sending them will block indefinitely. Deadlock may occur.
Note that this permits the creation of a channel whose two endpoints live in two different threads, providing a
stream-oriented bridge between these threads. In other words, we can provide a way for regular stream communication
between threads instead of having to send commands.
When a thread or interpreter is deleted, all channels created with this subcommand and using this thread/interpreter as
their computing base are deleted as well, in all interpreters they have been shared with or moved into, and in whatever
thread they have been transferred to. While this pulls the rug out under the other thread(s) and/or interpreter(s),
this cannot be avoided. Trying to use such a channel will cause the generation of a regular error about unknown channel
handles.
This subcommand is ${$B}safe${$N} and made accessible to safe interpreters. While it arranges for the execution of arbitrary Tcl
code the system also makes sure that the code is always executed within the safe interpreter."
@values -min 2 -max 2
#man page says must be at least one element in mode list.
#man page doesn't limit list to 2 elements long despite there being only 2 mode values
# - suggests things such as {r write read w ...} without limit on length is allowed
mode -type list -choices {read write} -choicemultiple {1 -1} -help\
"list of at least one of read write or abbreviations of these"
cmdprefix -type string
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::eof
@cmd -name "Built-in: tcl::chan::eof"\
@ -1823,7 +1883,57 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
""
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
#event
lappend PUNKARGS [list {
@id -id ::tcl::chan::event
@cmd -name "Built-in: tcl::chan::event"\
-summary\
"Create, delete or query a file event handler."\
-help\
"Arrange for the Tcl script script to be installed as a file event handler to be called whenever the channel
called channel enters the state described by event (which must be either readable or writable); only one such
handler may be installed per event per channel at a time. If script is the empty string, the current handler
is deleted (this also happens if the channel is closed or the interpreter deleted). If script is omitted, the
currently installed script is returned (or an empty string if no such handler is installed). The callback is
only performed if the event loop is being serviced (e.g. via vwait or update).
A file event handler is a binding between a channel and a script, such that the script is evaluated whenever
the channel becomes readable or writable. File event handlers are most commonly used to allow data to be
received from another process on an event-driven basis, so that the receiver can continue to interact with the
user or with other channels while waiting for the data to arrive. If an application invokes ${$B}chan gets${$N} or
${$B}chan read${$N} on a blocking channel when there is no input data available, the process will block; until the input
data arrives, it will not be able to service other events, so it will appear to the user to “freeze up”.
With ${$B}chan event${$N}, the process can tell when data is present and only invoke ${$B}chan gets${$N} or ${$B}chan read${$N} when they
will not block.
A channel is considered to be readable if there is unread data available on the underlying device. A channel is
also considered to be readable if there is unread data in an input buffer, except in the special case where the
most recent attempt to read from the channel was a ${$B}chan gets${$N} call that could not find a complete line in the
input buffer. This feature allows a file to be read a line at a time in non-blocking mode using events.
A channel is also considered to be readable if an end of file or error condition is present on the underlying
file or device. It is important for script to check for these conditions and handle them appropriately;
for example, if there is no special check for end of file, an infinite loop may occur where script reads no
data, returns, and is immediately invoked again.
A channel is considered to be writable if at least one byte of data can be written to the underlying file or
device without blocking, or if an error condition is present on the underlying file or device. Note that client
sockets opened in asynchronous mode become writable when they become connected or if the connection fails.
Event-driven I/O works best for channels that have been placed into non-blocking mode with the chan configure
command. In blocking mode, a ${$B}chan puts${$N} command may block if you give it more data than the underlying file or
device can accept, and a ${$B}chan gets${$N} or ${$B}chan read${$N} command will block if you attempt to read more data than is
ready; no events will be processed while the commands block. In non-blocking mode ${$B}chan puts${$N}, ${$B}chan read${$N}, and
${$B}chan gets${$N} never block.
The script for a file event is executed at global level (outside the context of any Tcl procedure) in the
interpreter in which the chan event command was invoked. If an error occurs while executing the script then the
command registered with interp bgerror is used to report the error. In addition, the file event handler is
deleted if it ever returns an error; this is done in order to prevent infinite loops due to buggy handlers."
@values -min 2 -max 3
channel
event -choices {readable writable}
script -optional 1
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::flush
@cmd -name "Built-in: tcl::chan::flush"\
@ -1878,9 +1988,58 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
channel
varName -optional 1
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
#isbinary
#names
#pending
lappend PUNKARGS [list {
@id -id ::tcl::chan::isbinary
@cmd -name "Built-in: tcl::chan::isbinary"\
-summary\
"Test if channel is binary (encoding iso8859-1, eofchar {}, translation lf)."\
-help\
"Test whether the channel called ${$I}channel${$NI} is a binary channel, returning 1 if it is and, and 0 otherwise.
A binary channel is a channel with iso8859-1 encoding, -eofchar set to {} and -translation set to lf."
@values -min 1 -max 1
channel
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
#chan names - deviation from online manual to add point about channel names and transformations
lappend PUNKARGS [list {
@id -id ::tcl::chan::names
@cmd -name "Built-in: tcl::chan::names"\
-summary\
"List all channel names. (toplevel)"\
-help\
{Produces a list of all channel names (*).
If pattern is specified, only those channel names that match it (according to the rules of string match)
will be returned.
* Note that the channel names returned are not necessarily the same as the channel names that are visible
in a given interpreter.
For example, if channel transformations are in use on stdin, stdout, or stderr, the channel names returned
will different for those channels.
e.g you may still be able to call ${$B}puts stdout "hello"${$N} even though ${$B}chan names${$N} does not return 'stdout'
It may instead show in the result list as something like 'file17f99e788b0'.
See the documentation for chan push for more details on this.}
@values -min 0 -max 1
pattern -optional 1 -default "*"
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::pending
@cmd -name "Built-in: tcl::chan::pending"\
-summary\
"Number of pending bytes buffered."\
-help\
"Depending on whether mode is input or output, returns the number of bytes of input or output (respectively)
currently buffered internally for channel (especially useful in a readable event callback to impose
application-specific limits on input line lengths to avoid a potential denial-of-service attack where a
hostile user crafts an extremely long line that exceeds the available memory to buffer it). Returns -1 if
the channel was not opened for the mode in question."
@values -min 2 -max 2
mode -choices {input output}
channel -type string
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::pipe
@cmd -name "Built-in: tcl::chan::pipe"\
@ -1921,6 +2080,24 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
channel -type string
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::push
@cmd -name "Built-in: tcl::chan::push"\
-summary\
"Add a new transformation on top of channel."\
-help\
"Adds a new transformation on top of the channel ${$I}channel${$NI}.
The ${$I}cmdPrefix${$NI} argument describes a list of one or more words which represent a handler
that will be used to implement the transformation. The command prefix must provide the
API described in the ${$B}transchan${$N} manual page. The result of this subcommand is a handle to
the transformation. Note that it is important to make sure that the transformation is
capable of supporting the channel mode that it is used with or this can make the channel
neither readable nor writable."
@values -min 2 -max 2
channel -type string
cmdPrefix -type string
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::puts
@cmd -name "Built-in: tcl::chan::puts"\
@ -2262,7 +2439,9 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
is equivalent to a false result. The key/value pairs
are tested in the order in which the keys were inserted
into the dictionary."
vars -type list -minsize 2 -maxsize 2 -typesynopsis {{keyVariable valueVariable}} -optional 0
vars -type list -minsize 2 -maxsize 2 -typesynopsis {{keyVariable valueVariable}} -optional 0 -help\
"Two element list of variable names to be used for the
key and value respectively"
script -type script
@form -form value
@ -2421,7 +2600,10 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
lappend PUNKARGS [list {
@id -id ::tcl::dict::map
@cmd -name "Built-in: tcl::dict::map" -help\
@cmd -name "Built-in: tcl::dict::map"\
-summary\
"Apply a transformation to each value of a dictionary, returning a new dictionary."\
-help\
"This command applies a transformation to each element of a dictionary,
returning a new dictionary. It takes three arguments: the first is a
two-element list of variable names (for the key and value respectively of
@ -2919,6 +3101,7 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
name -type string
} "@doc -name Manpage: -url [manpage_tcl file]" ]
#tcl 9+
lappend PUNKARGS [list {
@id -id ::tcl::file::home
@cmd -name "Built-in: tcl::file::home" -help\
@ -2952,6 +3135,48 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
#join
#link
lappend PUNKARGS [list {
@id -id ::tcl::file::link
@cmd -name "Built-in: tcl::file::link"\
-summary\
"Create a link or return the value of a link."\
-help\
"If only one argument is given, that argument is assumed to be linkName, and this command returns the value
of the link given by linkName (i.e. the name of the file it points to). If linkName is not a link or its
value cannot be read (as, for example, seems to be the case with hard links, which look just like ordinary
files), then an error is returned.
If 2 arguments are given, then these are assumed to be linkName and target. If linkName already exists, or
if target does not exist, an error will be returned. Otherwise, Tcl creates a new link called linkName which
points to the existing filesystem object at target (which is also the returned value), where the type of the
link is platform-specific (on Unix a symbolic link will be the default). This is useful for the case where
the user wishes to create a link in a cross-platform way, and does not care what type of link is created.
If the user wishes to make a link of a specific type only, (and signal an error if for some reason that is
not possible), then the optional -linktype argument should be given. Accepted values for -linktype are
“-symbolic” and “-hard”.
On Unix, symbolic links can be made to relative paths, and those paths must be relative to the actual
linkName's location (not to the cwd), but on all other platforms where relative links are not supported,
target paths will always be converted to absolute, normalized form before the link is created
(and therefore relative paths are interpreted as relative to the cwd). When creating links on filesystems
that either do not support any links, or do not support the specific type requested, an error message will
be returned. Most Unix platforms support both symbolic and hard links (the latter for files only).
Windows supports symbolic directory links and hard file links on NTFS drives.
"
@opts -type none -parsekey "-LINKTYPE" -group "linktype" -grouphelp\
""
-symbolic -typedefaults "-symbolic" -help\
""
-hard -typedefaults "-hard" -help\
"
"
@opts -parsekey "" -group ""
@values -min 1 -max 2
linkName -type string -optional 0
target -type string -optional 1
} "@doc -name Manpage: -url [manpage_tcl file]" ]
#lstat
lappend PUNKARGS [list {
@ -2986,8 +3211,37 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
name -type string
time -type integer -optional 1
} "@doc -name Manpage: -url [manpage_tcl file]"]
#nativename
#normalize
lappend PUNKARGS [list {
@id -id ::tcl::file::nativename
@cmd -name "Built-in: tcl::file::nativename"\
-summary\
{Platform-specific name of the file.}\
-help\
"Returns the platform-specific name of the file. This is useful if the filename is needed to pass
to a platform-specific call, such as to a subprocess via ${$B}exec${$N} under Windows (see EXAMPLES below)."
@values -min 1 -max 1
name -optional 0 -type string
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::normalize
@cmd -name "Built-in: tcl::file::normalize"\
-summary\
{Unique normalized path.}\
-help\
"Returns a unique normalized path representation for the file-system object (file, directory, link, etc),
whose string value can be used as a unique identifier for it. A normalized path is an absolute path which
has all “../” and “./” removed. Also it is one which is in the “standard” format for the native platform.
On Unix, this means the segments leading up to the path must be free of symbolic links/aliases (but the
very last path component may be a symbolic link), and on Windows it also means we want the long form with
that form's case-dependence (which gives us a unique, case-dependent path). The one exception concerning
the last link in the path is necessary, because Tcl or the user may wish to operate on the actual
symbolic link itself (for example ${$B}file delete${$N}, ${$B}file rename${$N}, ${$B}file copy${$N} are defined to operate on symbolic
links, not on the things that they point to)."
@values -min 1 -max 1
name -optional 0 -type string
} "@doc -name Manpage: -url [manpage_tcl file]"]
#owned
#pathtype
lappend PUNKARGS [list {
@ -3015,6 +3269,24 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
} "@doc -name Manpage: -url [manpage_tcl file]"]
#rename (2 forms)
lappend PUNKARGS [list {
@id -id ::tcl::file::rename
@cmd -name "Built-in: tcl::file::rename"\
-summary\
{Rename file or folder.}\
-help\
""
#----------------------------------------------
@form -form "tofile"
@opts
-force -type none -optional 1 -default 0
-- -type none -optional 1
@values -min 2 -max 2
source -optional 0 -type string
#----------------------------------------------
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::rootname
@cmd -name "Built-in: tcl::file::rootname"\
@ -3030,14 +3302,134 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
#separator
#size
#split
#stat
#system
#tail
#tempdir
#tempfile
lappend PUNKARGS [list {
@id -id ::tcl::file::stat
@cmd -name "Built-in: tcl::file::stat"\
-summary\
{Get file metadata - status information.}\
-help\
"Invokes the stat kernel call on name, and returns a dictionary with the information returned from
the kernel call. If varName is given, it uses the variable to hold the information. VarName is
treated as an array variable, and in such case the command returns the empty string. The following
elements are set: ${$B}atime${$N}, ${$B}ctime${$N}, ${$B}dev${$N}, ${$B}gid${$N}, ${$B}ino${$N}, ${$B}mode${$N}, ${$B}mtime${$N}, ${$B}nlink${$N}, ${$B}size${$N}, ${$B}type${$N}, ${$B}uid${$N}.
Each element except ${$B}type${$N} is a decimal string with the value of the corresponding field from the
stat return structure; see the manual entry for stat for details on the meanings of the values.
The type element gives the type of the file in the same form returned by the command ${$B}file type${$N}."
@values -min 1 -max 1
name -optional 0 -type string
varName -type string -optional 1
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::system
@cmd -name "Built-in: tcl::file::system"\
-summary\
{filesystem info for path}\
-help\
"Returns a list of one or two elements, the first of which is the name of the filesystem to use for
the file, and the second, if given, an arbitrary string representing the filesystem-specific nature
or type of the location within that filesystem. If a filesystem only supports one type of file, the
second element may not be supplied. For example the native files have a first element “native”, and
a second element which when given is a platform-specific type name for the file's system
(e.g. “NTFS”, “FAT”, on Windows). A generic virtual file system might return the list “vfs ftp” to
represent a file on a remote ftp site mounted as a virtual filesystem through an extension called
“vfs”. If the file does not belong to any filesystem, an error is generated."
@values -min 1 -max 1
name -optional 0 -type string
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::tail
@cmd -name "Built-in: tcl::file::tail"\
-summary\
{Last filesystem component of path}\
-help\
"Returns all of the characters in the last filesystem component of ${$I}name${$NI}.
Any trailing directory separator in ${$I}name${$NI} is ignored. If ${$I}name${$NI} contains no separators then returns ${$I}name${$NI}.
So, ${$B}file tail a/b${$N}, ${$B}file tail a/b/${$N} and ${$B}file tail b${$N} all return ${$B}b${$N}."
@values -min 1 -max 1
name -optional 0 -type string
} "@doc -name Manpage: -url [manpage_tcl file]"]
#tempdir tcl 9+ only?
lappend PUNKARGS [list {
@id -id ::tcl::file::tempdir
@cmd -name "Built-in: tcl::file::tempdir"\
-summary\
{Create a temporary directory.}\
-help\
"Creates a temporary directory (guaranteed to be newly created and writable by the current script)
and returns its name. If template is given, it specifies one of or both of the existing directory
(on a filesystem controlled by the operating system) to contain the temporary directory, and the
base part of the directory name; it is considered to have the location of the directory if there
is a directory separator in the name, and the base part is everything after the last directory
separator (if non-empty). The default containing directory is determined by system-specific
operations, and the default base name prefix is “tcl”.
The following output is typical and illustrative; the actual output will vary between platforms:
${[punk::args::helpers::example {
% ${$B}file tempdir${$N}
/var/tmp/tcl_u0kuy5
% ${$B}file tempdir /tmp/myapp${$N}
/tmp/myapp_8o7r9L
% ${$B}file tempdir /tmp/${$N}
/tmp/tcl_1m0JHD
% ${$B}file tempdir myapp${$N}
/var/tmp/myapp_0ihS0n
}]}
"
@values -min 0 -max 1
template -type string -optional 1
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::tempfile
@cmd -name "Built-in: tcl::file::tempfile"\
-summary\
{Create temp file and return open channel.}\
-help\
"Creates a temporary file and returns a read-write channel opened on that file.
If the nameVar is given, it specifies a variable that the name of the temporary
file will be written into; if absent, Tcl will attempt to arrange for the
temporary file to be deleted once it is no longer required. If the template is
present, it specifies parts of the template of the filename to use when creating
it (such as the directory, base-name or extension) though some platforms may
ignore some or all of these parts and use a built-in default instead.
Note that temporary files are only ever created on the native filesystem.
As such, they can be relied upon to be used with operating-system native APIs
and external programs that require a filename."
@values -min 0 -max 2
nameVar -type string -optional 1
template -type string -optional 1
} "@doc -name Manpage: -url [manpage_tcl file]"]
#tildeexpand
#type
#volumes
lappend PUNKARGS [list {
@id -id ::tcl::file::type
@cmd -name "Built-in: tcl::file::type"\
-summary\
{Type of file name.}\
-help\
"Returns a string giving the type of file name, which will be one of
${$B}file${$N}, ${$B}directory${$N}, ${$B}characterSpecial${$N}, ${$B}blockSpecial${$N}, ${$B}fifo${$N}, ${$B}link${$N}, or ${$B}socket${$N}."
@values -min 0 -max 0
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::volumes
@cmd -name "Built-in: tcl::file::volumes"\
-summary\
"List volumes mounted on the system."\
-help\
"Returns the absolute paths to the volumes mounted on the system, as a proper Tcl list.
Without any additional virtual filesystems mounted as root volumes, on UNIX, the command
will return “//zipfs:/”/ or “/”, (in case of a --disable-zipfs build), since all
filesystems are locally mounted. On Windows, it will return a list of the available
local drives (e.g. “//zipfs:/ C:/”). If any virtual filesystem has mounted additional
volumes, they will be in the returned list too."
@values -min 0 -max 0
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::writable
@ -6694,21 +7086,21 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
start -type number|expr
..|to -type string -choices {.. to} -optional 1
end -type number|expr
"by step" -type {literal(by) number|expr} -optional 1
"by step" -type {?literalprefix(by)? number|expr} -optional 1
@form -form start_count
@leaders -min 0 -max 0
@values -min 3 -max 5
start -type number|expr
count -type literal
count -type literalprefix(count)
countelements -type number|expr
"by step" -type {literal(by) number|expr} -optional 1
"by step" -type {literalprefix(by) number|expr} -optional 1
@form -form count
@leaders -min 0 -max 0
@values -min 1 -max 3
countelements -type number|expr
"by step" -type {literal(by) number|expr} -optional 1
"by step" -type {literalprefix(by) number|expr} -optional 1
} "@doc -name Manpage: -url [manpage_tcl lseq]"\
{

174
src/bootsupport/modules/punk/auto_exec-0.1.0.tm

@ -56,7 +56,9 @@ tcl::namespace::eval punk::auto_exec {
-help\
{Clear/refresh the autoexec commands in the ::auto_execs array.
This is analogous to the 'rehash' command in shells such as csh, tcsh and zsh.
This is analogous to the 'rehash' command in shells such as csh, tcsh and zsh,
or 'hash -r' in other shells such as bash.
It updates the shell's hash table of executable commands.
This can be useful after installing new software, adjusting the environment PATH directories, or (on windows) making
@ -64,7 +66,9 @@ tcl::namespace::eval punk::auto_exec {
commands are up to date with the current state of the system.
If refresh is false (the default), then all autoexec commands are cleared and will re-register as commands are called.
If refresh is true, then all existing autoexec commands are re-registered by calling auto_execok for each of them again.}
If refresh is true, then all existing autoexec commands are re-registered by calling auto_execok for each of them again.
see also ::punk::auto_exec::hash}
@opts
@values -min 0 -max 1
refresh -type boolean -default 0 -help\
@ -85,6 +89,172 @@ tcl::namespace::eval punk::auto_exec {
}
return
}
namespace eval argdoc {
lappend PUNKARGS [list {
@id -id "::punk::auto_exec::hash"
@cmd -name "punk::auto_exec::hash"\
-summary\
"Manage the hash table of autoexec commands cached in ::auto_execs."\
-help\
{see also ::punk::auto_exec::rehash}
#---------------------
@form -form {show_or_set}
@opts -min 0 -max 0
@values -min 0 -max -1
name -type string -multiple 1 -optional 1 -default {} -help\
"One or more autoexec command names to set.
If no names are provided, then all autoexec commands in the hash table will be shown."
#---------------------
@form -form {rehash}
@opts -min 1 -max 1
-r -type none -optional 0 -help\
"Clear autoexec commands from the hash table"
@values -min 0 -max 0
#---------------------
@form -form {test}
@opts
-t -type none -optional 0 -default "" -help\
"The name of the autoexec command name to display."
@values -min 1 -max -1
name -type string -multiple 1 -help\
"One or more autoexec command names to display information for.
If only a single name is provided, then the output will be the raw command string
associated with that autoexec command in the hash table.
If multiple names are provided, then the output will be a string containing each
name and its associated command string on a separate line."
#---------------------
@form -form {delete}
@opts
-d -type none -optional 0 -help\
"Delete specified autoexec commands from the hash table."
@values -min 1 -max -1
name -type string -multiple 1 -help\
"One or more autoexec command names to delete from the hash table."
#---------------------
#todo?
#-p <path> <name> (manually assign)
#-l (build a list of hash -p <path> <name> entries for all autoexec commands that can be used in a script to pre-populate the hash table without needing to call auto_execok for each command at runtime)
#---------------------
@form -form {help}
@opts -min 1 -max 1 -anyopts 1
--help -type none -optional 0 -help\
"Display usage information for this command."
@values -min 0 -max -1
ignored -type any -multiple 1 -optional 1 -help\
"Additional arguments that are ignored when --help is used"
}]
}
proc hash {args} {
set arg1 [lindex $args 0]
#select parsing form based on first argument
switch -- $arg1 {
-r {
set form rehash
}
-t {
set form test
}
-d {
set form delete
}
--help {
set form help
}
default {
#like bash in this context, we won't allow an option-like entry to be treated as an executable name
if {[string match -* $arg1]} {
puts stderr "hash: ${arg1}: invalid option"
#return [punk::args::usage -scheme error ::punk::auto_exec::hash]
set msg "hash: usage:\n"
append msg [punk::ns::synopsis ::punk::auto_exec::hash]
error $msg
}
set form show_or_set
}
}
set argd [punk::args::parse $args -form $form withid ::punk::auto_exec::hash]
lassign [dict values $argd] _leaders opts values received
global auto_execs
switch -- $form {
rehash {
unset -nocomplain auto_execs
}
test {
#like bash - we'll provide only the path if there is a single name provided, but if there are multiple names we'll provide both the name and path for each.
set names [dict get $values name]
if {[llength $names] == 1} {
set nm [lindex $names 0]
if {[info exists auto_execs($nm)]} {
return [set auto_execs($nm)]
} else {
#review
puts stderr "hash: $nm: not found"
return ""
}
}
set result ""
foreach nm $names {
if {[info exists auto_execs($nm)]} {
append result "$nm [set auto_execs($nm)]\n"
} else {
#review
puts stderr "$hash: nm: not found"
}
}
if {$result ne ""} {
set result [string trimright $result \n]
}
return $result
}
delete {
set names [dict get $values name]
foreach nm $names {
unset -nocomplain auto_execs($nm)
}
}
help {
return [punk::args::usage ::punk::auto_exec::hash]
}
default {
set requested_names [dict get $values name]
if {[llength $requested_names] == 0} {
#show all
set hashed_names [array names auto_execs]
#todo - record and return 'hits' like bash does?
set result ""
foreach nm $hashed_names {
set cached [set auto_execs($nm)]
#unlike some shells - we cache negative results (for absolute paths) that don't exist.
#as we're attempting to be close to behaviour of bash, don't output empty results for negative cache entries.
if {$cached ne ""} {
append result $cached \n
}
}
if {$result ne ""} {
set result [string trimright $result \n]
}
return $result
} else {
#rehash each requested name if it exists, otherwise display an msg on stderr for that name.
foreach nm $requested_names {
set aexec [auto_execok $nm]
if {$aexec ne ""} {
set auto_execs($nm) $aexec
} else {
puts stderr "hash: $nm: not found"
}
}
return
}
}
}
}
variable PUNKARGS
lappend PUNKARGS [list {

34
src/bootsupport/modules/punk/config-0.1.tm

@ -503,16 +503,33 @@ tcl::namespace::eval punk::config {
key -type string -optional 1
newvalue -optional 1
}]
proc configure {args} {
set argd [punk::args::parse $args withid ::punk::config::configure]
lassign [dict values $argd] leaders opts values received solos
set whichconfig [dict get $argd leaders whichconfig]
proc configure {whichconfig args} {
#set argd [punk::args::parse $args withid ::punk::config::configure]
#lassign [dict values $argd] leaders opts values received solos
#set whichconfig [dict get $argd leaders whichconfig]
set values [dict create]
switch -- [llength $args] {
0 {
}
1 {
dict set values key [lindex $args 0]
}
2 {
dict set values newvalue [lindex $args 1]
}
default {
error "Too many arguments. Expected at most 2 (key [newvalue])"
}
}
variable configdata
if {"running" ni [dict keys $configdata]} {
init
Apply startup
}
switch -- $whichconfig {
set fullwhich [tcl::prefix::match -error "" {defaults startup-configuration running-configuration} $whichconfig]
switch -- $fullwhich {
defaults {
set configrecords [dict get $configdata defaults]
}
@ -522,12 +539,15 @@ tcl::namespace::eval punk::config {
running-configuration {
set configrecords [dict get $configdata running]
}
default {
error "Unknown config name '$whichconfig' - try defaults or startup-configuration or running-configuration"
}
}
if {![dict exists $received key]} {
if {![dict exists $values key]} {
return $configrecords
}
set key [dict get $values key]
if {![dict exists $received newvalue]} {
if {![dict exists $values newvalue]} {
return [dict get $configrecords $key]
}
error "setting value not implemented"

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

File diff suppressed because it is too large Load Diff

34
src/bootsupport/modules/punk/lib-0.1.6.tm

@ -94,19 +94,16 @@ tcl::namespace::eval punk::lib::ensemble {
set routinetail [tcl::namespace::tail $routine]
if {![string match ::* $extension]} {
set extension [uplevel 1 [
list [tcl::namespace::which namespace] current]]::$extension
set extension [uplevel 1 [list [tcl::namespace::which namespace] current]]::$extension
}
if {![tcl::namespace::exists $extension]} {
error [list {no such namespace} $extension]
}
set extension [tcl::namespace::eval $extension [
list [tcl::namespace::which namespace] current]]
set extension [tcl::namespace::eval $extension [list [tcl::namespace::which namespace] current]]
tcl::namespace::eval $extension [
list [tcl::namespace::which namespace] export *]
tcl::namespace::eval $extension [list [tcl::namespace::which namespace] export *]
while 1 {
set renamed ${routinens}::${routinetail}_[clock clicks] ;#clock clicks unlikely to collide when not directly consecutive such as: list [clock clicks] [clock clicks]
@ -140,7 +137,7 @@ tcl::namespace::eval punk::lib::check {
if {"windows" ne $::tcl_platform(platform)} {
set bug 0
} else {
set tmpdir [file tempdir]
set tmpdir [file tempdir] ;#tcl 9+
set testfile [file join $tmpdir "bugtest"]
set fd [open $testfile w]
puts $fd test
@ -4759,14 +4756,21 @@ namespace eval punk::lib {
foreach ln $linelist {
#set is_replay_pure_reset [regexp {\x1b\[0*m$} $replaycodes] ;#only looks at tail code - but if tail is pure reset - any prefix is ignorable
set ansisplits [punk::ansi::ta::split_codes_single $ln] ;#REVIEW - this split accounts for a large portion of the time taken to run this function.
if {[llength $ansisplits]<= 1} {
if {![punk::ansi::ta::detect $ln]} {
#plaintext only - no ansi codes in line
lappend transformed [string cat $replaycodes $ln $RST]
#leave replaycodes as is for next line
set nextreplay $replaycodes
} else {
set replaycodes $nextreplay
continue
}
set ansisplits [punk::ansi::ta::split_codes_single $ln] ;#REVIEW - this split seems to account for a large portion of the time taken to run this function.
#if {[llength $ansisplits]<= 1} {
# #plaintext only - no ansi codes in line
# lappend transformed [string cat $replaycodes $ln $RST]
# #leave replaycodes as is for next line
# set nextreplay $replaycodes
#} else {
set tail $RST
set lastcode [lindex $ansisplits end-1] ;#may or may not be SGR
if {[punk::ansi::codetype::is_sgr_reset $lastcode]} {
@ -4821,7 +4825,7 @@ namespace eval punk::lib {
#set newreplay [join $codestack ""]
set newreplay [punk::ansi::codetype::sgr_merge_list {*}$codestack]
if {$line_has_sgr && $newreplay ne $replaycodes} {
if {$RST ne "" && $line_has_sgr && $newreplay ne $replaycodes} {
#adjust if it doesn't already does a reset at start
if {[punk::ansi::codetype::has_sgr_leadingreset $newreplay]} {
set nextreplay $newreplay
@ -4838,7 +4842,7 @@ namespace eval punk::lib {
} else {
lappend transformed [string cat $replaycodes $ln $tail]
}
}
#}
set replaycodes $nextreplay
}
set linelist $transformed
@ -5505,7 +5509,7 @@ tcl::namespace::eval punk::lib::debug {
namespace eval ::punk::args::register {
#use fully qualified so 8.6 doesn't find existing var in global namespace
lappend ::punk::args::register::NAMESPACES ::punk::lib
lappend ::punk::args::register::NAMESPACES ::punk::lib ::punk::lib::ensemble
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready

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

@ -330,8 +330,11 @@ tcl::namespace::eval punk::nav::fs {
punk::args::define {
@id -id ::punk::nav::fs::d/
@cmd -name punk::nav::fs::d/ -help\
{List directories or directories and files in the current directory or in the
@cmd -name punk::nav::fs::d/\
-summary\
"Navigate and list directories and files"\
-help\
{Navigate/List directories or directories and files in the current directory or in the
targets specified with the fileglob_or_target glob pattern(s).
If a single target is specified without glob characters, and it exists as a directory,

36
src/bootsupport/modules/punk/nav/ns-0.1.0.tm

@ -33,6 +33,40 @@ tcl::namespace::eval punk::nav::ns {
}
namespace path {::punk::ns}
namespace eval argdoc {
lappend PUNKARGS [list {
@id -id ::punk::nav::ns::ns/
@cmd -name punk::nav::ns::ns/\
-summary\
"Navigate and list namespaces and commands"\
-help\
{Navigate/List namespaces or namespaces and commands in the current namespace or in the
targets specified with the nsglob pattern(s).
This function is provided via aliases as n/ n// and n/// with v being inferred from the alias
The n/ n// and n/// forms are more convenient for interactive use.
examples:
n/ - list namespaces below current namespace
n// - list namespaces and commands below current namespace
n/ p* - list namespaces below current matching p*
n// p* - list namespaces below current and commands in current matching p*
}
@values -min 1 -max -1 -type string
v -type string -choices {/ //} -help\
"
/ - list namespaces only
// - list namespaces and commands
/// - list namespaces, commands and commands resolvable via 'namespace path'
"
nsglob -type string -optional true -multiple true -help\
"A glob pattern supporting placeholders * and ?, to filter results.
If multiple patterns are supplied, then a listing for each pattern is returned.
If no patterns are supplied, then all items are listed."
}]
}
proc ns/ {v {ns_or_glob ""} args} {
variable ns_current ;#change active ns of repl by setting ns_current
@ -227,8 +261,6 @@ tcl::namespace::eval punk::nav::ns {
}
}
}

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

@ -3711,6 +3711,30 @@ y" {return quirkykeyscript}
}
}
punk::args::define {
@id -id ::punk::ns::nscommands
@cmd -name punk::ns::nscommands\
-summary\
"List current namespace commands one per line."\
-help\
"Display commands in the current namespace, or optionally within specified namespaces.
Namespaces to search can be specified as arguments, with optional glob patterns.
Examples:
'nscommands' - list all commands in the current namespace
'nscommands foo*' - list all commands in the current namespace with names starting with 'foo'
'nscommands foo* bar*' - list all commands in the current namespace with names starting with 'foo' or 'bar'"
@leaders -min 0 -max 0
@opts
-raw -type none -help\
"Output raw command names with no ANSI color codes.
Useful for scripting or when color codes would be undesirable."
@values -min 1 -max -1
glob -multiple 1 -optional 1 -default * -help\
"Namespace patterns to search for commands. If not specified, defaults to '*',
which searches the current namespace. Patterns can include glob characters (* and ?).
Examples: 'foo*' to match namespaces starting with 'foo', '*::bar' to match namespaces
ending with 'bar'."
}
proc nscommands {args} {
set commandns [uplevel 1 [list ::tcl::namespace::current]]
set commandlist [::list]
@ -3803,6 +3827,7 @@ y" {return quirkykeyscript}
}
}
interp alias {} nscommands {} punk::ns::nscommands
proc nscommandlist {{ns *}} {
set nsparts [nsparts_cached $ns]
set tail [lindex $nsparts end]
@ -4051,6 +4076,13 @@ y" {return quirkykeyscript}
#eg because parent interp called something like: interp0 alias ::thread::id ::thread::id
#make sure we don't perform an infinite loop
if {$tgt ne $resolved} {
#--------------
#unqualified alias target - need to resolve to fully qualified for cmdwhich lookup to work correctly
#jmn - todo test/review
if {![string match ::* $tgt]} {
set tgt ::$tgt
}
#--------------
set whichinfo [uplevel 1 [list ::punk::ns::cmdwhich $tgt]]
set origin [dict get $whichinfo origin]
set origintype [dict get $whichinfo origintype]

21
src/bootsupport/modules/punk/repl-0.1.2.tm

@ -2948,8 +2948,11 @@ namespace eval repl {
if {[catch {package require thread} errM]} {
puts stdout ">>repl::init initscript lib load fail on package require thread\n$errM"
puts stdout ">>repl::init auto_path : $::auto_path"
puts stdout ">>repl::init tcl::tm::list: [tcl::tm::list]"
if {[catch {package require Thread} errM2]} {
puts stdout ">>repl::init initscript lib load fail on package require Thread\n$errM2"
puts stdout ">>repl::init auto_path : $::auto_path"
puts stdout ">>repl::init tcl::tm::list: [tcl::tm::list]"
}
}
#-----
@ -3395,9 +3398,11 @@ namespace eval repl {
set v [lindex $versions end]
set path [lindex [package ifneeded $pkg $v] end]
if {[file extension $path] in {.tcl .tm}} {
if {![catch {readFile $path} data]} {
if {![catch {readFile $path} packagedef]} {
code eval [list info script $path]
code eval $data
code eval $packagedef
#jjj
code eval [list package provide $pkg $v] ;#ensure package is marked as provided in interp even if it doesn't call package provide itself
code eval [list info script $prior_infoscript]
} else {
error "safe - failed to read $path"
@ -3705,6 +3710,10 @@ namespace eval repl {
#puts stderr [join $::auto_path \n]
#puts stderr -----
#punk::console is not loaded at this point
#puts "--------------provide punk::console : [package provide punk::console]"
#puts "--------------punk::console commands: [info commands ::punk::console::*]"
if {[catch {
package require punk::args
package require punk::config
@ -3714,6 +3723,10 @@ namespace eval repl {
#Requiring it shouldn't trigger application - but zipfs/vfs interactions confused it in some early versions
package require natsort
#catch {package require packageTrace}
if {[catch {package require punk::console} errM]} {
#review
puts stderr "failed to load punk::console - \n$errM\n$::errorInfo"
}
package require punk
package require shellrun
package require shellfilter

25
src/bootsupport/modules/punk/winlnk-0.1.1.tm

@ -733,18 +733,23 @@ tcl::namespace::eval punk::winlnk {
set r [binary scan $lenfield su count_chars] ;# su is for unsigned short in little endian order
set string_value ""
if {[Header_Has_LinkFlag $contents "IsUnicode"]} {
#string is UTF-16LE encoded
#string is UTF-16LE encoded - we have this encoding available in tcl 9+ - but not in 8.6
set numbytes [expr {2 * $count_chars}]
set string_bytes [string range $contents $start+2 [expr {$start + 2 + $numbytes - 1}]]
#consider using tcl encoding convertfrom utf-16le instead of manually parsing the UTF-16LE bytes - this would be more robust and handle edge cases better (e.g. surrogate pairs, non-BMP characters, etc.)
set string_value [encoding convertfrom utf-16le $string_bytes]
#for {set i 0} {$i < [string length $string_bytes]} {
# set char_bytes [string range $string_bytes $i [expr {$i + 1}]]
# set r [binary scan $char_bytes su char] ;# s for unsigned short
# append string_value [format %c $char]
# incr i 1 ;# skip the next byte since it's part of the UTF-16LE encoding
#}
#use tcl encoding convertfrom utf-16le when we can instead of manually parsing the UTF-16LE bytes
#- this should be more robust and handle edge cases better (e.g. surrogate pairs, non-BMP characters, etc.)
if {[catch {set string_value [encoding convertfrom utf-16le $string_bytes]} err]} {
#puts stderr "Error converting UTF-16LE string: $err"
#set string_value ""
for {set i 0} {$i < [string length $string_bytes]} {incr i} {
set char_bytes [string range $string_bytes $i $i+1]
set r [binary scan $char_bytes su char] ;# su for unsigned short
append string_value [format %c $char]
incr i 1 ;# skip the next byte since it's part of the UTF-16LE encoding
}
}
} else {
set numbytes $count_chars
set string_bytes [string range $contents $start+2 [expr {$start + 2 + $numbytes - 1}]]

45
src/bootsupport/modules/textblock-0.1.3.tm

@ -2107,6 +2107,7 @@ tcl::namespace::eval textblock {
set cidx [lindex [tcl::dict::keys $o_columndefs] $index_expression]
set colwidth [my column_width $cidx]
set fwidth [expr {$colwidth + 2}]
set col_blockalign [tcl::dict::get $o_columndefs $cidx -blockalign]
@ -2509,18 +2510,19 @@ tcl::namespace::eval textblock {
set border_ansi $body_ansibase$body_ansiborder
}
set ansibase $body_ansibase$opt_col_ansibase
set r 0
set ftblock [expr {[tcl::dict::get $o_opts_table -frametype] eq "block"}]
set do_show_edge [tcl::dict::get $o_opts_table -show_edge]
foreach c $cells {
#cells in column - each new c is in a different row
set row_ansibase [tcl::dict::get $o_rowdefs $r -ansibase]
set row_bg ""
set row_ansibase [tcl::dict::get $o_rowdefs $r -ansibase]
if {$row_ansibase ne ""} {
set row_bg [punk::ansi::codetype::sgr_merge_singles [list $row_ansibase] -filter_fg 1]
}
set ansibase $body_ansibase$opt_col_ansibase
#todo - joinleft,joinright,joindown based on opts in args
set cell_ansibase ""
@ -2602,7 +2604,7 @@ tcl::namespace::eval textblock {
} else {
set blims $blims_only_headerless
}
if {![tcl::dict::get $o_opts_table -show_edge]} {
if {!$do_show_edge} {
set blims [struct::set difference $blims [tcl::dict::get $::textblock::class::table_edge_parts only$opt_posn] ]
}
} else {
@ -2612,11 +2614,11 @@ tcl::namespace::eval textblock {
} else {
set blims $blims_top_headerless
}
if {![tcl::dict::get $o_opts_table -show_edge]} {
if {!$do_show_edge} {
set blims [struct::set difference $blims [tcl::dict::get $::textblock::class::table_edge_parts top$opt_posn] ]
}
}
set rowframe [textblock::frame -checkargs 0 -type [tcl::dict::get $ftypes body] -width [expr {$colwidth+2}] -blockalign $col_blockalign -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c]
set rowframe [textblock::frame -checkargs 0 -type $ftype_body -width [expr {$colwidth+2}] -blockalign $col_blockalign -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c]
set return_bodywidth [textblock::widthtopline $rowframe] ;#frame lines always same width - just look at top line
append part_body $rowframe \n
} else {
@ -2624,22 +2626,26 @@ tcl::namespace::eval textblock {
set joins [lremove $joins [lsearch $joins down*]]
set bmap $botmap
set blims $blims_bot
if {![tcl::dict::get $o_opts_table -show_edge]} {
if {!$do_show_edge} {
set blims [struct::set difference $blims [tcl::dict::get $::textblock::class::table_edge_parts bottom$opt_posn] ]
}
} else {
set bmap $midmap
set blims $blims_mid ;#will only be reduced from boxlimits if -show_seps was processed above
if {![tcl::dict::get $o_opts_table -show_edge]} {
if {!$do_show_edge} {
set blims [struct::set difference $blims [tcl::dict::get $::textblock::class::table_edge_parts middle$opt_posn] ]
}
}
append part_body [textblock::frame -checkargs 0 -type [tcl::dict::get $ftypes body] -width [expr {$colwidth+2}] -blockalign $col_blockalign -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c]\n
#append part_body [textblock::frame -checkargs 0 -type [tcl::dict::get $ftypes body] -width [expr {$colwidth+2}] -blockalign $col_blockalign -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c]\n
append part_body [textblock::frame -checkargs 0 -type $ftype_body -width [expr {$colwidth+2}] -blockalign $col_blockalign -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c]\n
}
incr r
}
#return empty (zero content height) row if no rows
if {![llength $cells]} {
set basebg [punk::ansi::codetype::sgr_merge_singles [list $body_ansibase] -filter_fg 1]
set ansiborder_final [punk::ansi::codetype::sgr_merge [list $basebg $body_ansiborder]]
set joins [lremove $joins [lsearch $joins down*]]
#we need to know the width of the column to setup the empty cell properly
#even if no header displayed - we should take account of any defined column widths
@ -2661,7 +2667,9 @@ tcl::namespace::eval textblock {
append part_body [tcl::string::repeat " " $colwidth] \n
set return_bodywidth $colwidth
} else {
set emptyframe [textblock::frame -checkargs 0 -width [expr {$colwidth + 2}] -type [tcl::dict::get $ftypes body] -boxlimits $blims -boxmap $onlymap -joins $joins]
#set emptyframe [textblock::frame -checkargs 0 -width [expr {$colwidth + 2}] -type [tcl::dict::get $ftypes body] -boxlimits $blims -boxmap $onlymap -joins $joins]
# -blockalign probably not relevant for an empty row.
set emptyframe [textblock::frame -checkargs 0 -type $ftype_body -width [expr {$colwidth+2}] -ansibase $body_ansibase -ansiborder $ansiborder_final -boxlimits $blims -boxmap $onlymap -joins $joins]
append part_body $emptyframe \n
set return_bodywidth [textblock::width $emptyframe]
}
@ -5741,7 +5749,10 @@ tcl::namespace::eval textblock {
@id -id ::textblock::join_basic
@cmd -name textblock::join_basic -help\
"Join blocks of text line by line but don't add padding on each line to enforce uniform width.
Already uniform blocks will join faster than textblock::join, and ragged blocks will join in a ragged manner
Already uniform blocks will join faster than textblock::join, and ragged blocks will join in a ragged manner.
see also textblock::join_basic_raw - a thin wrapper around split and join for the common case of joining blocks without any options,
and is intended to avoid the overhead of argument parsing.
"
-ansiresets -type any -default auto
-- -type none -optional 0 -help "end of options marker -- is mandatory because joined blocks may easily conflict with flags"
@ -5787,7 +5798,21 @@ tcl::namespace::eval textblock {
}
return [::join $outlines \n]
}
punk::args::define {
@id -id ::textblock::join_basic_raw
@cmd -name textblock::join_basic_raw -help\
"Join blocks of text line by line but don't add padding on each line to enforce uniform width.
Already uniform blocks will join faster than textblock::join, and ragged blocks will join in a ragged manner.
This version is a thin wrapper around split and join for the common case of joining blocks without any options,
and is intended to avoid the overhead of argument parsing.
"
@values
blocks -type any -multiple 1
}
proc ::textblock::join_basic_raw {args} {
#do not use any argument parsing libs - this is intended as a thin wrapper around split and join for the common case of joining blocks without any options,
#and we want to avoid the overhead of argument parsing.
#no options. -*, -- are legimate blocks
set blocklists [lrepeat [llength $args] ""]
set blocklengths [lrepeat [expr {[llength $args]+1}] 0] ;#add 1 to ensure never empty - used only for rowcount max calc

17
src/modules/overtype-999999.0a1.0.tm

@ -461,8 +461,21 @@ tcl::namespace::eval overtype {
if {$underblock eq ""} {
set underlines [lrepeat $renderheight ""]
} else {
set underblock [textblock::join_basic -- $underblock] ;#ensure properly rendered - ansi per-line resets & replays
set underlines [split $underblock \n]
#----
#this splits into lines - only to rejoin - which is inefficient.
#It also has code to handle joining multiple blocks - but we only have one in this case.
#set underblock [textblock::join_basic_raw $underblock];#ensure properly rendered - ansi per-line resets & replays
#set underlines [split $underblock \n]
#----
if {[punk::ansi::ta::detectcode $underblock]} {
#-ansireplays 1 quite expensive e.g ~15us for only 3 short lines on a 2026 threadripper pro
set underlines [punk::lib::linelist -ansireplays 1 $underblock]
} else {
set underlines [split $underblock \n]
}
}
#if {$underblock eq ""} {
# set blank "\x1b\[0m\x1b\[0m"

515
src/modules/punk-0.1.tm

@ -341,7 +341,7 @@ namespace eval punk {
#}
#safest? could be a link?
foreach match [glob -nocomplain -dir $dir -tail {*}$lookfor] {
foreach match [glob -nocomplain -dir $dir -tail -- {*}$lookfor] {
set file [file join $dir $match]
if {[file exists $file] && ![file isdirectory $file]} {
#set assoc [extension_open_association [file extension $file]]
@ -6277,21 +6277,55 @@ namespace eval punk {
namespace eval argdoc {
punk::args::define {
@id -id ::punk::path
@cmd -name "punk::path" -help\
"Introspection of the PATH environment variable.
@cmd -name "punk::path"\
-summary\
"Display PATH executable shadowing and conflicts with TCL commands"\
-help\
{Introspection of the PATH environment variable.
This tool will examine executables within each PATH entry and show which binaries
are overshadowed by earlier PATH entries. It can also be used to examine the contents of each PATH entry, and to filter results using glob patterns."
are overshadowed by earlier PATH entries.
It can also be used to examine the contents of each PATH entry, and to filter results using glob patterns.
${[punk::args::helpers::example {
#show all executables in all PATH entries
punk::path
#show all executables in all PATH entries that contain 'Windows' in the path
punk::path -pathglob *Windows*
#show all executables in all PATH entries that contain 'scoop' in the path,
#and filter the executables to show only those that are named dir, ls or start with 'ca'
punk::path -pathglob *scoop* dir ls ca*
#show all executables that conflict with TCL commands starting with 'a' in the current namespace.
punk::path {*}[nscommandlist a*]
#show all executables that conflict with TCL commands resolvable from the current namespace.
punk::path {*}[info commands]
}]}
see also the punk::auto_exec package.
}
@opts
-binglobs -type list -default {*} -help "glob pattern to filter results. Default '*' to include all entries."
-pathglob -type string -default {*} -multiple true -help "Case insensitive glob pattern to filter path entries. Default '*' to include all PATH directories."
@values -min 0 -max -1
glob -type string -default {*} -multiple true -optional 1 -help "Case insensitive glob pattern to filter path entries. Default '*' to include all PATH directories."
binglob -type list -default {*} -multiple true -optional 1 -help "glob pattern to filter results. Default '*' to include all entries."
}
}
variable d_path_info
variable d_bin_info
variable d_index_executables
#there is still a potential conflict regarding auto_execok on windows - which has some cmd.exe builtins as auto-executable
#- but these are not actually executable files on the filesystem - so they won't be found by our path search
#- but they will be found when not masked by a tcl command.
proc path {args} {
variable d_path_info
variable d_bin_info
variable d_index_executables
set is_windows [expr {$::tcl_platform(platform) eq "windows"}]
set argd [punk::args::parse $args withid ::punk::path]
lassign [dict values $argd] leaders opts values received
set binglobs [dict get $opts -binglobs]
set globs [dict get $values glob]
set pathglobs [dict get $opts -pathglob]
set binglobs [dict get $values binglob]
if {$::tcl_platform(platform) eq "windows"} {
set sep ";"
} else {
@ -6299,14 +6333,18 @@ namespace eval punk {
set sep ":"
}
set all_paths [split [string trimright $::env(PATH) $sep] $sep]
set filtered_paths $all_paths
if {[llength $globs]} {
set filtered_paths [list]
foreach p $all_paths {
foreach g $globs {
if {[string match -nocase $g $p]} {
lappend filtered_paths $p
break
if {[llength $pathglobs]} {
if {[lsearch -exact $pathglobs "*"] >= 0} {
#if we have a wildcard glob then the others are irrelevant - we want to match all paths
set matched_paths $all_paths
} else {
set matched_paths [list]
foreach p $all_paths {
foreach pg $pathglobs {
if {[string match -nocase $pg $p]} {
lappend matched_paths $p
break
}
}
}
}
@ -6344,6 +6382,60 @@ namespace eval punk {
#and the actual executable names (with case and extensions as they appear on the filesystem). We will also build a
#dict keyed by path index which contains the list of executables in that path - to make it easy to show which
#executables are overshadowed by which paths.
if {$is_windows} {
#Sometimes PATHEXT includes an entry of just a dot - which means files with no extension are considered executable.
#We need to account for this in our glob pattern.
set pathexts [list]
if {[info exists ::env(PATHEXT)]} {
set env_pathexts [split $::env(PATHEXT) ";"]
#set pathexts [lmap e $env_pathexts {string tolower $e}]
foreach pe $env_pathexts {
if {$pe eq "."} {
continue
}
lappend pathexts [string tolower $pe]
}
} else {
set env_pathexts [list]
#default PATHEXT if not set - according to Microsoft docs
set pathexts [list .com .exe .bat .cmd]
}
foreach bg $binglobs {
set has_pathext 0
foreach pe $pathexts {
if {[string match -nocase "*$pe" $bg]} {
set has_pathext 1
break
}
}
if {!$has_pathext} {
foreach pe $pathexts {
set globext "$bg$pe"
if {$globext ni $binglobs} {
lappend binglobs "$bg$pe"
}
}
}
}
set lc_binglobs [lmap e $binglobs {string tolower $e}]
if {"." in $pathexts} {
foreach bg $binglobs {
set has_pathext 0
foreach pe $pathexts {
if {[string match -nocase "*$pe" $bg]} {
set base [string range $bg 0 [expr {[string length $bg] - [string length $pe] - 1}]]
set has_pathext 1
break
}
}
if {$has_pathext} {
if {[string tolower $base] ni $lc_binglobs} {
lappend binglobs "$base"
}
}
}
}
}
set d_path_info [dict create] ;#key is normalized path (e.g case-insensitive on windows).
set d_bin_info [dict create] ;#key is normalized executable name (e.g case-insensitive on windows, or callable with extensions stripped off).
@ -6355,63 +6447,21 @@ namespace eval punk {
} else {
set pnorm $p
}
if {[string length $pnorm] > 1} {
set lastchar [string index $pnorm end]
if {$lastchar eq "/" || $lastchar eq "\\"} {
set pnorm [string range $pnorm 0 end-1]
}
}
if {![dict exists $d_path_info $pnorm]} {
dict set d_path_info $pnorm [dict create original_paths [list $p] indices [list $path_idx]]
set executables [list]
if {[file isdirectory $p]} {
#get all files that are executable in this path.
#If we don't normalize the path here - then trailing backslashes on windows can cause a problem with the -tail glob returning a leading slash on the executable names.
#also as we don't necessarily normalize the resulting final path with executable - we want the case to be correct.
set pnormglob [file normalize $p]
if {$::tcl_platform(platform) eq "windows"} {
#Sometimes PATHEXT includes an entry of just a dot - which means files with no extension are considered executable.
#We need to account for this in our glob pattern.
set pathexts [list]
if {[info exists ::env(PATHEXT)]} {
set env_pathexts [split $::env(PATHEXT) ";"]
#set pathexts [lmap e $env_pathexts {string tolower $e}]
foreach pe $env_pathexts {
if {$pe eq "."} {
continue
}
lappend pathexts [string tolower $pe]
}
} else {
set env_pathexts [list]
#default PATHEXT if not set - according to Microsoft docs
set pathexts [list .com .exe .bat .cmd]
}
foreach bg $binglobs {
set has_pathext 0
foreach pe $pathexts {
if {[string match -nocase "*$pe" $bg]} {
set has_pathext 1
break
}
}
if {!$has_pathext} {
foreach pe $pathexts {
lappend binglobs "$bg$pe"
}
}
}
set lc_binglobs [lmap e $binglobs {string tolower $e}]
if {"." in $pathexts} {
foreach bg $binglobs {
set has_pathext 0
foreach pe $pathexts {
if {[string match -nocase "*$pe" $bg]} {
set base [string range $bg 0 [expr {[string length $bg] - [string length $pe] - 1}]]
set has_pathext 1
break
}
}
if {$has_pathext} {
if {[string tolower $base] ni $lc_binglobs} {
lappend binglobs "$base"
}
}
}
}
#TCL's glob on windows is case-insensitive, but in some cases return the result with the case as globbed for regardless of the actual case on the filesystem.
#(This seems to occur when the pattern does *not* contain a wildcard and is probably a bug)
@ -6421,34 +6471,51 @@ namespace eval punk {
# but tcl's glob does not respect the case of even the character-class pattern - so this is not a reliable workaround).
#see punk::fglob for a work-in-progress glob implementation which gives us more control over case sensitivity and the case of results on windows.
set globresults [lsort -unique [glob -nocomplain -directory $pnormglob -types {f x} {*}$binglobs]]
#-----------------------
#JJJ
#set globresults [lsort -unique [glob -nocomplain -directory $pnormglob -types {f x} {*}$binglobs]]
#set executables [list]
#foreach e $globresults {
# puts stderr "glob result: $e"
# puts stderr "normalized executable name: [file tail [file normalize [string range $e 0 end]]]]"
# lappend executables [file tail [file normalize $e]]
#}
#-----------------------
#track all executables in the path - even those that don't match the binglobs
#use fglob to get the actual case of the executables on windows - as glob seems to return the case as globbed for rather than the actual case on the filesystem in some cases.
#this doesn't run a full 'file normalize' on the results which affects whether a more efficient internal representation is stored
#fglob with single glob argument should already return a unique list.
set folder_exes [fglob -nocomplain -directory $pnormglob -types {f x} *]
set executables [list]
foreach e $globresults {
puts stderr "glob result: $e"
puts stderr "normalized executable name: [file tail [file normalize [string range $e 0 end]]]]"
lappend executables [file tail [file normalize $e]]
foreach e $folder_exes {
lappend executables [file tail $e]
}
} else {
set executables [lsort -unique [glob -nocomplain -directory $p -types {f x} -tail {*}$binglobs]]
#set executables [lsort -unique [glob -nocomplain -directory $p -types {f x} -tail {*}$binglobs]]
set executables [lsort -unique [glob -nocomplain -directory $p -types {f x} -tail *]]
}
}
dict set d_index_executables $path_idx $executables
foreach exe $executables {
#todo - other case-insensitive platforms/filesystems.
if {$::tcl_platform(platform) eq "windows"} {
set exenorm [string tolower $exe]
set exe_key [string tolower $exe]
} else {
set exenorm $exe
#on case
set exe_key $exe
}
if {![dict exists $d_bin_info $exenorm]} {
dict set d_bin_info $exenorm [dict create path_indices [list $path_idx] paths [list $p] executable_names [list $exe]]
if {![dict exists $d_bin_info $exe_key]} {
dict set d_bin_info $exe_key [dict create path_indices [list $path_idx] paths [list $p] executable_names [list $exe]]
} else {
#dict lappend d_bin_info $exenorm path_indices $path_idx paths $p executable_names $exe
set bindata [dict get $d_bin_info $exenorm]
#dict lappend d_bin_info $exe_key path_indices $path_idx paths $p executable_names $exe
set bindata [dict get $d_bin_info $exe_key]
dict lappend bindata path_indices $path_idx
dict lappend bindata paths $p
dict lappend bindata executable_names $exe
dict set d_bin_info $exenorm $bindata
dict set d_bin_info $exe_key $bindata
}
}
} else {
@ -6467,16 +6534,16 @@ namespace eval punk {
set executables [dict get $d_index_executables [lindex [dict get $d_path_info $pnorm indices] 0]] ;#get executables for this path
foreach exe $executables {
if {$::tcl_platform(platform) eq "windows"} {
set exenorm [string tolower $exe]
set exe_key [string tolower $exe]
} else {
set exenorm $exe
set exe_key $exe
}
#dict lappend d_bin_info $exenorm path_indices $path_idx paths $p executable_names $exe
set bindata [dict get $d_bin_info $exenorm]
#dict lappend d_bin_info $exe_key path_indices $path_idx paths $p executable_names $exe
set bindata [dict get $d_bin_info $exe_key]
dict lappend bindata path_indices $path_idx
dict lappend bindata paths $p
dict lappend bindata executable_names $exe
dict set d_bin_info $exenorm $bindata
dict set d_bin_info $exe_key $bindata
}
}
@ -6484,18 +6551,255 @@ namespace eval punk {
}
#temporary debug output to check dicts are being built correctly
set debug ""
append debug "Path info dict:" \n
append debug [showdict $d_path_info] \n
append debug "Binary info dict:" \n
append debug [showdict $d_bin_info] \n
append debug "Index executables dict:" \n
append debug [showdict $d_index_executables] \n
#return $debug
puts stdout $debug
#set debug ""
#append debug "Path info dict:" \n
#append debug [showdict $d_path_info] \n
#append debug "Binary info dict:" \n
#append debug [showdict $d_bin_info {*}$binglobs] \n
##append debug "Index executables dict:" \n
##append debug [showdict $d_index_executables] \n
##return $debug
#puts stdout $debug
#dict for {p pinfo} $d_path_info {
# set original_paths [dict get $pinfo original_paths]
# set indices [dict get $pinfo indices]
# puts stdout "Path: $p"
# puts stdout " Original paths: $original_paths"
# puts stdout " Indices in PATH: $indices"
# if {[dict exists $d_index_executables [lindex $indices 0]]} {
# set executables [dict get $d_index_executables [lindex $indices 0]]
# puts stdout " Executables: [llength $executables]"
# } else {
# puts stdout " Executables: (not a directory or no executables found)"
# }
#}
set nscaller [uplevel 1 {::tcl::namespace::current}]
set context_commands [namespace eval $nscaller {info commands}]
#process paths in order they appear in the original PATH.
set pidx 0
#use a punk::textblock::table for formatting.
set rows [list]
set headers [list "idx" "Path" "exe\nCount" "Shadow\nCount" "Executables" "TCL context\nConflicts"]
set ERR [punk::ansi::a+ red bold]
set RST [punk::ansi::a]
set STR [punk::ansi::a+ strike]
set SDW [punk::ansi::a+ red strike]
set WRN [punk::ansi::a+ yellow bold]
set subcols 2
foreach p $all_paths {
#if {$p ni $matched_paths} {
# incr pidx
# continue
#}
set thisrow [list $pidx]
set pnorm [string tolower $p]
if {[string length $pnorm] > 1} {
set lastchar [string index $pnorm end]
if {$lastchar eq "/" || $lastchar eq "\\"} {
set pnorm [string range $pnorm 0 end-1]
}
}
set pinfo [dict get $d_path_info $pnorm]
set original_paths [dict get $pinfo original_paths]
set indices [dict get $pinfo indices]
if {[lindex $indices 0] == $pidx} {
#this is the first occurrence of this path in the original PATH.
set overshadowed [list]
set conflicts [list]
lappend thisrow $p
if {[dict exists $d_index_executables $pidx]} {
set executables [dict get $d_index_executables $pidx]
lappend thisrow [llength $executables]
set display_executables [list]
foreach exe $executables {
set matched_binglob 0
foreach bg $binglobs {
#review - -nocase only on case-insensitive platforms/filesystems?
#- but it is simpler to just apply it to all platforms here rather than trying to determine case-sensitivity of each path.
if {[string match -nocase $bg $exe]} {
set matched_binglob 1
continue
}
}
set exe_key [string tolower $exe]
if {[dict exists $d_bin_info $exe_key]} {
set bindata [dict get $d_bin_info $exe_key]
set path_indices [dict get $bindata path_indices]
set is_overshadowed 0
foreach pi $path_indices {
if {$pi < $pidx} {
lappend overshadowed $exe
set is_overshadowed 1
break
}
}
if {$matched_binglob} {
if {$is_windows} {
#check for matches in context_commands - which are case-insensitive on windows
#the context_commands are however case sensitive.
#we want to mark conflicts in one of two ways in the conflicts column.
#- if there is a case-insensitive match but not a case-sensitive match
#- then we have a conflict but not an exact match - so we will mark this with orange style.
#If there is an exact match in context_commands - then we will mark this with the red style
#to indicate that this executable is overshadowed by a command in the current context.
#we may have multiple tcl commands that conflict with the same executable.
#e.g DIG and dig.
if {[llength [set ncmatches [lsearch -all -inline -nocase $context_commands [file rootname $exe]]]]} {
if {[set exactmatch [lsearch -exact $context_commands [file rootname $exe]]] ne ""} {
foreach nc $ncmatches {
set nc [namespace eval $nscaller [list namespace origin $nc]]
if {$nc eq $exactmatch} {
lappend conflicts $ERR$nc$RST
} else {
lappend conflicts "$WRN$nc$RST"
}
}
} else {
foreach nc $ncmatches {
set nc [namespace eval $nscaller [namespace origin $nc]]
lappend conflicts "$WRN$nc$RST"
}
}
} else {
if {[llength [set ncmatches [lsearch -all -inline -nocase $context_commands $exe]]]} {
if {[set exactmatch [lsearch -exact $context_commands $exe]] ne ""} {
foreach nc $ncmatches {
set nc [namespace eval $nscaller [namespace origin $nc]]
if {$nc eq $exactmatch} {
lappend conflicts $ERR$nc$RST
} else {
lappend conflicts "$WRN$nc$RST"
}
}
} else {
foreach nc $ncmatches {
set nc [namespace eval $nscaller [namespace origin $nc]]
lappend conflicts "$WRN$nc$RST"
}
}
}
}
} else {
#check for any exact matches in context_commands
if {$exe in $context_commands} {
lappend conflicts $ERR$exe$RST
}
}
if {$is_overshadowed} {
lappend display_executables "$SDW$exe$RST"
} else {
lappend display_executables $exe
}
}
} else {
#executable not found in bin_info dict - this shouldn't happen - but if it does we will just treat it as not overshadowed and include it in the display.
lappend display_executables $WRN$exe$RST
}
}
if {[llength $overshadowed]} {
lappend thisrow "$ERR[llength $overshadowed]$RST"
} else {
lappend thisrow "0"
}
if {[llength $display_executables]} {
lappend thisrow [textblock::list_as_table -columns $subcols -show_edge 0 $display_executables]
} else {
lappend thisrow ""
}
if {[llength $conflicts]} {
#lappend thisrow [textblock::list_as_table -columns $subcols -show_edge 0 $conflicts]
lappend thisrow [join $conflicts \n]
} else {
lappend thisrow ""
}
} else {
lappend thisrow ""
lappend thisrow ""
lappend thisrow ""
lappend thisrow "(not a directory or no executables found)"
lappend thisrow ""
}
} else {
#this is a duplicate path entry - we want to show it as a duplicate of the original path entry.
set original_path_idx [lindex $indices 0]
set original_path [lindex [dict get $d_path_info $pnorm original_paths] 0]
#duplicate paths might be cased differently.
lappend thisrow "$ERR$p (repeated pathentry)\n original at index $original_path_idx as\n$original_path$RST"
set overshadowed [list]
set conflicts [list]
set display_executables [list]
if {[dict exists $d_index_executables $original_path_idx]} {
set executables [dict get $d_index_executables $original_path_idx]
lappend thisrow [llength $executables]
foreach exe $executables {
set exe_key [string tolower $exe]
if {[dict exists $d_bin_info $exe_key]} {
set bindata [dict get $d_bin_info $exe_key]
set path_indices [dict get $bindata path_indices]
set is_overshadowed 0
foreach pi $path_indices {
if {$pi < $pidx} {
lappend overshadowed $exe
set is_overshadowed 1
break
}
}
#dupe will always have all exes as overshadowed by the original.
#don't need to waste time and screen space to display duplicate info - the user should tidy up the PATH.
#if {$is_overshadowed} {
# lappend display_executables "$SDW$exe$RST"
#} else {
# lappend display_executables $exe
#}
}
}
} else {
#this shouldn't happen - but if it does we will just treat it as not overshadowed and include it in the display.
lappend thisrow "(not a directory or no executables found)"
}
if {[llength $overshadowed]} {
lappend thisrow "$ERR[llength $overshadowed]$RST"
} else {
lappend thisrow "0"
}
if {[llength $display_executables]} {
lappend thisrow [textblock::list_as_table -columns $subcols -show_edge 0 $display_executables]
} else {
lappend thisrow ""
}
lappend thisrow "" ;#don't show conflict info for duplicate paths - as the user should tidy up the PATH to remove duplicates, and the conflict info will be the same as the original path entry.
}
if {[llength $matched_paths] < [llength $all_paths]} {
#if there is any filtering of paths - then we want to show all these paths whether or not there are any matches for binglobs
if {$p in $matched_paths} {
lappend rows $thisrow
}
} else {
#no specific filtering of paths - so only show rows where there are matches for binglobs
if {[lsearch -exact $binglobs "*"] >= 0} {
lappend rows $thisrow
} else {
#end-1 is the executables column.
#if there are no matches for binglobs then we'll hide the row.
if {[string length [lindex $thisrow end-1]] > 0} {
lappend rows $thisrow
}
}
}
incr pidx
}
set t [textblock::table -return tableobject -rows $rows -headers $headers]
return [$t print]
}
#-------------------------------------------------------------------
@ -8024,8 +8328,8 @@ namespace eval punk {
set title "[a+ brightgreen] Filesystem navigation: "
set cmdinfo [list]
lappend cmdinfo [list ./ "?${I}glob${NI}?" "view/change dir, list dirs."]
lappend cmdinfo [list ../ "?${I}path${NI}" "go up one dir, then to path if given"]
lappend cmdinfo [list .// "?${I}glob${NI}?" "view/change dir, list dirs and files"]
lappend cmdinfo [list ../ "?${I}path${NI}" "go up one dir, then to path if given"]
lappend cmdinfo [list newdir "${I}subdir${NI}..." "make new dir or dirs and show status"]
lappend cmdinfo [list fcat "${I}file ?file?...${NI}" "cat file(s)"]
set t [textblock::class::table new -minwidth 80 -show_seps 0]
@ -8238,11 +8542,33 @@ namespace eval punk {
lappend chunks [list stdout $text]
}
console - term - terminal {
set term_env_vars {TERM TERM_PROGRAM TERM_PROGRAM_VERSION}
set term_dict [dict create]
foreach e $term_env_vars {
if {[info exists ::env($e)]} {
dict set term_dict $e [set ::env($e)]
} else {
dict set term_dict $e "(NOT SET)"
}
}
set text "Terminal environment variables:\n"
append text [punk::lib::showdict $term_dict] \n
lappend chunks [list stdout $text]
set text ""
if {[catch {package require punk::console} result]} {
set text "Unable to load punk::console package - cannot test\n$result"
lappend chunks [list stdout $text]
} else {
if {![catch {punk::console::class_info} console_class_info]} {
set text "Terminal class info (from device secondary attributes query to terminal):\n"
append text [punk::lib::showdict $console_class_info] \n
} else {
set text "Unable to query terminal class info - err:$console_class_info\n"
}
lappend chunks [list stdout $text]
set indent [string repeat " " [string length "WARNING: "]]
lappend cstring_tests [dict create\
type "PM "\
@ -8339,7 +8665,7 @@ namespace eval punk {
}
}
if {![string length $warningblock]} {
set text "No terminal warnings\n"
set text "[a+ green]No terminal warnings[a]\n"
lappend chunks [list stdout $text]
}
}
@ -8351,6 +8677,7 @@ namespace eval punk {
"tcl" "Tcl version warnings"\
"env|environment" "punkshell environment vars"\
"console|terminal" "Some console behaviour tests and warnings"\
"*" "Try to find help on the topic as a command or external executable"\
]
set t [textblock::class::table new -show_seps 0]

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

@ -117,6 +117,7 @@ tcl::namespace::eval punk::aliascore {
plist {::punk::lib::pdict -roottype list}\
showlist {::punk::lib::showdict -roottype list}\
rehash ::punk::auto_exec::rehash\
hash ::punk::auto_exec::hash\
showdict ::punk::lib::showdict\
ansistrip ::punk::ansi::ansistrip\
stripansi ::punk::ansi::ansistrip\

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

@ -3920,7 +3920,10 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
}
lappend PUNKARGS [list {
@id -id ::punk::ansi::a+
@cmd -name "punk::ansi::a+" -help\
@cmd -name "punk::ansi::a+"\
-summary\
"ANSI SGR code generator with no reset prefix"\
-help\
"Returns an ANSI sgr escape sequence based on the list of supplied codes.
Unlike punk::ansi::a - it is not prefixed with an ANSI reset.
"
@ -3935,7 +3938,10 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
lappend PUNKARGS [list {
@id -id ::punk::ansi::a
@cmd -name "punk::ansi::a" -help\
@cmd -name "punk::ansi::a"\
-summary\
"ANSI SGR code generator with reset prefix"\
-help\
"Returns an ANSI sgr escape sequence based on the list of supplied codes.
Unlike punk::ansi::a+ - it is prefixed with an ANSI reset.
"
@ -6865,7 +6871,14 @@ tcl::namespace::eval punk::ansi::ta {
#may be same as detect - kept in case detect needs to diverge
#variable re_ansi_split "${re_csi_code}|${re_esc_osc1}|${re_esc_osc2}|${re_esc_osc3}|${re_standalones}|${re_ST}|${re_g0_open}|${re_g0_close}"
set re_ansi_split $re_ansi_detect
#experiment with const for a regex - seems to make no difference to performance - but it does make it clear that the regex is not intended to be modified at runtime
if {[catch {const re_ansi_split $re_ansi_detect}]} {
#tcl 9 has const but tcl 8 doesn't - so we just set it as a normal variable
variable re_ansi_split
set re_ansi_split $re_ansi_detect
}
variable re_ansi_split_multi
if {[string first (?x) $re_ansi_split] == 0} {
set re_ansi_split_multi "(?x)(?:[string range ${re_ansi_split} 4 end])+"
@ -7161,7 +7174,7 @@ tcl::namespace::eval punk::ansi::ta {
#micro optimisations on split_codes to avoid function calls and make re var local tend to yield very little benefit (sub uS diff on calls that commonly take 10s/100s of uSeconds)
#like split_codes - but each ansi-escape is split out separately (with empty string of plaintext between codes so even/odd indices for plain ansi still holds)
#- the slightly simpler regex than split_codes means that it will be slightly faster than keeping the codes grouped.
#- the regex is slighly simpler than for split_codes - but split_codes is faster when there are consecutive codes.
proc split_codes_single {text} {
if {$text eq ""} {
return {}
@ -7177,7 +7190,26 @@ tcl::namespace::eval punk::ansi::ta {
#set next [lindex $cr 1]+1 ;#text index-expression for string range
}
lappend list [tcl::string::range $text $next end]
return $list
}
proc split_codes_single2 {text} {
return [_perlish_split2 $::punk::ansi::ta::re_ansi_split $text]
}
proc split_codes_single3 {text} {
#no faster
if {$text eq ""} {
return {}
}
variable re_ansi_split
set next 0
set coderanges [regexp -indices -all -inline -- $re_ansi_split $text]
set list [lrepeat [expr {[llength $coderanges]*2}] ""]
set r 0
foreach cr $coderanges {
ledit list $r $r+1 [tcl::string::range $text $next [lindex $cr 0]-1] [tcl::string::range $text [lindex $cr 0] [lindex $cr 1]]
set next [expr {[lindex $cr 1]+1}]
incr r
}
return [list {*}$list [tcl::string::range $text $next end]]
}
proc split_codes_single2 {text} {
variable re_ansi_split
@ -7202,7 +7234,6 @@ tcl::namespace::eval punk::ansi::ta {
set next [expr {[lindex $cr 1]+1}]
}
lappend list [tcl::string::range $text $next end]
return $list
}
proc _perlish_split2 {re text} {
if {$text eq ""} {

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

@ -771,9 +771,9 @@ tcl::namespace::eval punk::args {
literal(<string>)
(exact match for string)
literalprefix(<string>)
(prefix match for string, other literal and literalprefix
(tcl::prefix::match of string, other literal and literalprefix
entries specified as alternates using | are used in the
calculation)
unique prefix calculation)
stringstartswith(<string>)
(value must match glob <string>*)
The value of string must not contain pipe char '|'
@ -785,7 +785,7 @@ tcl::namespace::eval punk::args {
e.g literalprefix(text)|literalprefix(binary)
(when all in the pipe-delimited type-alternates set are
literal or literalprefix - this is similar to the -choices
option)
option with -choiceprefix true)
and more.. (todo - document here)
@ -906,6 +906,8 @@ tcl::namespace::eval punk::args {
is preserved.
-minsize (type dependant)
-maxsize (type dependant)
-mincap {only valid for regex type - min number of captures}
-maxcap {only valid for regex type - max number of captures}
-range (type dependant - only valid if -type is a single item)
-typeranges (list with same number of elements as -type)
-help <string>
@ -2529,6 +2531,15 @@ tcl::namespace::eval punk::args {
#review -solo 1 vs -type none ? conflicting values?
tcl::dict::set spec_merged $spec $specval
}
-mincap - -maxcap {
#todo - allow as default for @leaders, @opts and @values when default -type there is regex or regexp?
#only applies to type regex
set tp [tcl::dict::get $spec_merged -type]
if {![string match *regex* $tp]} {
error "punk::args::resolve - invalid use of '$spec' key for argument '$argname'. '$spec' only applies to arguments with a type of regex or regexp. argument has type '$tp' @id:$DEF_definition_id"
}
tcl::dict::set spec_merged $spec $specval
}
-range {
#allow simple case to be specified without additional list wrapping
#only multi-types require full list specification
@ -2624,7 +2635,9 @@ tcl::namespace::eval punk::args {
-range -typeranges\
-default -defaultdisplaytype -typedefaults\
-minsize -maxsize -choices -choicegroups\
-mincap -maxcap\
-choicemultiple -choicecolumns -choiceprefix -choiceprefixdenylist -choiceprefixreservelist -choicerestricted\
-choicelabels -choiceinfo \
-unindentedfields\
-nocase -optional -multiple -validate_ansistripped -allow_ansi -strip_ansi -help\
-multipleunique -choicemultipleunique -choicemultipleuniqueset\
@ -3816,7 +3829,8 @@ tcl::namespace::eval punk::args {
set arg_error_CLR_info(check) [a+ brightgreen bold]
set arg_error_CLR_info(choiceprefix) [a+ brightgreen bold]
set arg_error_CLR_info(groupname) [a+ cyan bold]
set arg_error_CLR_info(ansiborder) [a+ brightcyan bold]
#set arg_error_CLR_info(ansiborder) [a+ brightcyan bold]
set arg_error_CLR_info(ansiborder) [a+ term-grey23 bold]
set arg_error_CLR_info(ansibase_header) [a+ cyan]
set arg_error_CLR_info(ansibase_body) [a+ white]
variable arg_error_CLR_error
@ -5236,13 +5250,13 @@ tcl::namespace::eval punk::args {
switch -- $tailtype {
withid {
#JJJ
#set id [lindex $opts_and_vals 0]
set deflist [raw_def [lindex $opts_and_vals 0]]
if {[llength $deflist] == 0} {
if {[llength $opts_and_vals] != 1} {
#error "punk::args::parse - invalid call. Expected exactly one argument after 'withid'"
punk::args::parse $args withid ::punk::args::parse
}
set id [lindex $opts_and_vals 0]
error "punk::args::parse - no such id: $id"
}
}
@ -5415,7 +5429,8 @@ tcl::namespace::eval punk::args {
}
#return number of values we can assign to cater for variable length clauses such as {"elseif" expr "?then?" body}
#return number of values we can assign to cater for variable length clauses such as:
# {"elseif" expr "?then?" body}
#review - efficiency? each time we call this - we are looking ahead at the same info
proc _get_dict_can_assign_value {idx values nameidx names namesreceived formdict} {
set ARG_INFO [dict get $formdict ARG_INFO]
@ -5426,12 +5441,23 @@ tcl::namespace::eval punk::args {
#todo - work backwards with any (optional or not) literals at tail that match our values - and remove from assignability.
set ridx 0
#puts "-=============- thisname:'$thisname' thistype:'$thistype' tailnames:'$tailnames' all_remaining:'$all_remaining' [info level -2]"
foreach clausename [lreverse $tailnames] {
#puts "=============== clausename:$clausename all_remaining: $all_remaining"
#puts "=============== thisname:'$thisname' thistype:'$thistype' clausename:'$clausename' all_remaining:'$all_remaining'"
set clause_is_multiple [dict get $ARG_INFO $clausename -multiple]
set clause_is_optional [dict get $ARG_INFO $clausename -optional]
set typelist [dict get $ARG_INFO $clausename -type]
#---------------
#review - not quite right to look for literal* in typelist
#- we should be looking for any type-alternate that starts with literal( or literalprefix(
#- but for now we require the whole type to be literal* if it's a literal match type.
# We should probably also support stringstartswith(*) and stringendswith(*) too.
#also consider that -choices {abc def} is effectively a literal match type too - we should support that here as well.
if {[lsearch $typelist literal*] == -1} {
break
}
#---------------
set max_clause_length [llength $typelist]
if {$max_clause_length == 1} {
#basic case
@ -5452,28 +5478,50 @@ tcl::namespace::eval punk::args {
}
#foreach tp_alternative [split $tp |] {}
foreach tp_alternative [_split_type_expression $tp] {
set tp_alternatives [_split_type_expression $tp]
foreach tp_alternative $tp_alternatives {
switch -exact -- [lindex $tp_alternative 0] {
literal {
set litinfo [string range $tp 7 end] ;#get bracketed part if of form literal(xxx)
set match [lindex $tp_alternative 1]
set match [lindex $tp_alternative 1] ;#was bracketed part if of form literal(xxx)
if {$v eq $match} {
set alloc_ok 1
ledit all_remaining end end
if {![dict get $ARG_INFO $clausename -multiple]} {
if {!$clause_is_multiple} {
ledit tailnames end end
}
#the type (or one of the possible type alternates) matched a literal
break
}
}
literalprefix {
set prefix_of [lindex $tp_alternative 1]
#get list of literal and literalprefix values in the current list of tp_alternatives so we can construct list of alternatives for tcl::prefix::match prefix calculation.
#todo - consider if this clause also has -choices {abc def} - we should support those as well here as literal matches for the purposes of calculating the prefix match.
# (this is somewhat of an edge case but sometimes it's useful to specify a -type when -choices is used with -choicerestricted false, to allow only specific values not in the choices list.)
set comparelist [list]
foreach alt $tp_alternatives {
switch -exact -- [lindex $alt 0] {
literal - literalprefix {
lappend comparelist [lindex $alt 1]
}
}
}
set fullmatch [tcl::prefix::match -error "" $comparelist $v]
if {$fullmatch eq $prefix_of} {
set alloc_ok 1
ledit all_remaining end end
if {!$clause_is_multiple} {
ledit tailnames end end
}
break
}
}
stringstartswith {
set pfx [lindex $tp_alternative 1]
if {[string match "$pfx*" $v]} {
set alloc_ok 1
set alloc_ok 1
ledit all_remaining end end
if {![dict get $ARG_INFO $clausename -multiple]} {
if {!$clause_is_multiple} {
ledit tailnames end end
}
break
@ -5483,10 +5531,9 @@ tcl::namespace::eval punk::args {
stringendswith {
set sfx [lindex $tp_alternative 1]
if {[string match "*$sfx" $v]} {
set alloc_ok 1
set alloc_ok 1
ledit all_remaining end end
if {![dict get $ARG_INFO $clausename -multiple]} {
if {!$clause_is_multiple} {
ledit tailnames end end
}
break
@ -5497,7 +5544,7 @@ tcl::namespace::eval punk::args {
}
}
if {!$alloc_ok} {
if {![dict get $ARG_INFO $clausename -optional]} {
if {!$clause_is_optional} {
break
}
}
@ -5519,6 +5566,7 @@ tcl::namespace::eval punk::args {
set reverse_type_index 0
#todo handle type-alternates
# for example: -type {string literal(x)|literal(y)}
# -type {string literal(max)|literal(min)|int}
foreach tp $rtypelist {
#set rv [lindex $rcvals end-$alloc_count]
set rv [lindex $all_remaining end-$alloc_count]
@ -5528,8 +5576,24 @@ tcl::namespace::eval punk::args {
set clause_member_optional 0
}
set tp [string trim $tp ?]
puts "_get_dict_can_assign_value: checking tp '$tp' against value '$rv'"
switch -glob -- $tp {
literal* {
"literal(*" {
set litmatch [string range $tp 8 end-1]
if {$rv eq $litmatch} {
set alloc_ok 1 ;#we need at least one literal-match to set alloc_ok
incr alloc_count
} else {
if {$clause_member_optional} {
#
} else {
set alloc_ok 0
break
}
}
}
XXXliteral* {
#JJJ
set litinfo [string range $tp 7 end]
set match [string range $litinfo 1 end-1]
#todo -literalprefix
@ -5594,7 +5658,7 @@ tcl::namespace::eval punk::args {
#set all_remaining [lrange $all_remaining end-$n end]
set all_remaining [lrange $all_remaining 0 end-$alloc_count]
#don't lpop if -multiple true
if {![dict get $ARG_INFO $clausename -multiple]} {
if {!$clause_is_multiple} {
#lpop tailnames
ledit tailnames end end
}
@ -6421,13 +6485,48 @@ tcl::namespace::eval punk::args {
break
}
regex - regexp {
#todo - allow -min and -max to specify number of allowed subexpressions(capture groups) present in regex?
if {[catch {regexp -about $e_check} re_about_msg]} {
set msg "$argclass $argname for %caller% requires type regexp. $re_about_msg. Received: '$e_check'"
lset clause_results $c_idx $a_idx [list errorcode [list PUNKARGS VALIDATION [list typemismatch $type] -badarg $argname -argspecs $argspecs] msg $msg]
} else {
lset clause_results $c_idx $a_idx 1
break
#optional -mincap and -maxcap specify number of allowed subexpressions(capture groups) present in regex
set num_caps [lindex $re_about_msg 0]
set mincap 0 ;#default
set maxcap -1 ;#default -1 for unlimited
if {[dict exists $thisarg_checks -mincap]} {
set mincap [dict get $thisarg_checks -mincap]
}
if {[dict exists $thisarg_checks -maxcap]} {
set maxcap [dict get $thisarg_checks -maxcap]
}
if {$maxcap == -1 && $mincap == 0} {
#no cap limits - just accept the regex as valid
lset clause_results $c_idx $a_idx 1
break
} else {
#we have at least one cap limit - we need to count the number of subexpressions in the regex and check it against the limits
if {$maxcap == -1} {
#unlimited maxcap - just check mincap
if {$num_caps < $mincap} {
set msg "$argclass $argname for %caller% requires type regexp with at least $mincap capture groups. Received regex has only $num_caps capture groups. Regex: '$e_check'"
lset clause_results $c_idx $a_idx [list errorcode [list PUNKARGS VALIDATION [list typemismatch $type] -badarg $argname -argspecs $argspecs] msg $msg]
} else {
lset clause_results $c_idx $a_idx 1
break
}
} else {
if {$num_caps < $mincap} {
set msg "$argclass $argname for %caller% requires type regexp with at least $mincap capture groups. Received regex has only $num_caps capture groups. Regex: '$e_check'"
lset clause_results $c_idx $a_idx [list errorcode [list PUNKARGS VALIDATION [list typemismatch $type] -badarg $argname -argspecs $argspecs] msg $msg]
} elseif {$num_caps > $maxcap} {
set msg "$argclass $argname for %caller% requires type regexp with no more than $maxcap capture groups. Received regex has $num_caps capture groups. Regex: '$e_check'"
lset clause_results $c_idx $a_idx [list errorcode [list PUNKARGS VALIDATION [list typemismatch $type] -badarg $argname -argspecs $argspecs] msg $msg]
} else {
lset clause_results $c_idx $a_idx 1
break
}
}
} ;#every leaf of this nested if should have an lset clause_results with 1 for pass or errorcode/msg for fail
}
}
indexexpression {
@ -6776,10 +6875,30 @@ tcl::namespace::eval punk::args {
break
}
}
path -
file -
directory -
directory {
#see comments in existingpath/existingfile/existingdirectory case about the challenges of validating filesystem paths in a general way that works across platforms and use cases.
if {[tcl::string::length $e_check]==0 || [string first \0 $e_check] >= 0} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which doesn't look like it could be a path, file or directory"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
}
lset clause_results $c_idx $a_idx 1
}
existingpath -
existingfile -
existingdirectory {
#do we need types for relative vs absolute paths? readable writable executable owned?
#on windows limit to certain file extensions?
#fileutil::magic::filetype?
#Perhaps these are steps too far for a general validation framework.
#consider - callback validation functions instead?
#ideally we want to define callback validation functions that can work not just on a single argument at a time.
#e.g for testing that 2 file arguments do or don't refer to the same file or are in same directory or same filesystem etc.
#we have to support file and directory names on all platforms - and even characters illegal on a filesystem/platform may need to be passed.
#For example a file/folder may be created with an illegal name on a platform (or mounted on it) and be mapped to another string on the filesystem
#- yet it may remain accessible to commands such as file stat etc via the string with 'illegal' characters as well as its underlying stored (mapped) name.
@ -6790,17 +6909,43 @@ tcl::namespace::eval punk::args {
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
}
if {$type eq "existingfile"} {
if {![file exists $e_check]} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which is not an existing file"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
# -------------------------------------------------------
#review - what do we want to happen with links?
#on unix TCL's file readlink should reliably give us a path to determine the type pointed to.
#on windows we can do so if the link happens to be a junction.
#however on windows we can also have symbolic links which are not junctions and which may point to files or directories
#- but unfortunately tcl's file readlink doesn't seem to be able to read them at all - raises an error.
#(the error seems to be different for a file vs a directory target - but this seems an unreliable mechanism to determine the type of the target)
#At the moment TCL's 'file isfile' and 'file isdirectory' both seem to do the right things for links
#despite the above - treating them as the type of their target
# review whether this is reliable in all cases on windows.
# -------------------------------------------------------
#windows shortcuts (.lnk files) can point to a file or directory - but we can quite reasonably treat them only as files,
#as users *probably* won't have the expectation that a shortcut which points to a directory should be treated as a directory.
switch -exact -- $type {
existingpath {
if {![file exists $e_check]} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which is not an existing path"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
}
}
} elseif {$type eq "existingdirectory"} {
if {![file isdirectory $e_check]} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which is not an existing directory"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
existingfile {
if {![file isfile $e_check]} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which is not an existing file"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
}
}
existingdirectory {
if {![file isdirectory $e_check]} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which is not an existing directory"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
}
}
}
lset clause_results $c_idx $a_idx 1
@ -6809,6 +6954,11 @@ tcl::namespace::eval punk::args {
existingportabledirectory -
portablefile -
portabledirectory {
#review - many absolute paths are not strictly portable when considered as a whole e.g /usr/local/bin c:/test
#- but the idea was more about the directory and file name components being portable excluding the first component.
#this concept may need work as it's unintuitive what it means to be a portable file/directory vs not.
#what about windows specific paths such as //?/ //./ or UNC paths?
if {[tcl::string::length $e_check]==0 || [string first \0 $e_check] >= 0 || [punk::winpath::illegalname_test $e_check]} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which doesn't look like it could be a portable file or directory (must pass punk::winpath::illegalname_test)"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
@ -8699,7 +8849,7 @@ tcl::namespace::eval punk::args {
set leadername [lindex $LEADER_NAMES $nameidx]
set ldr [lindex $leaders $ldridx]
if {$leadername ne ""} {
set leadertypelist [tcl::dict::get $argstate $leadername -type]
set leadertypelist [tcl::dict::get $argstate $leadername -type] ;#often a single type, but can be a list of types (possibly with some optional) for a type that is a clause accepting multiple values.
set leader_clause_size [llength $leadertypelist]
set assign_d [_get_dict_can_assign_value $ldridx $leaders $nameidx $LEADER_NAMES $leadernames_received $formdict]
@ -8738,11 +8888,23 @@ tcl::namespace::eval punk::args {
set clauseval $resultlist
incr ldridx [expr {$consumed - 1}]
#not quite right.. this sets the -type for all clauses - but they should run independently
#e.g if expr {} elseif 2 {script2} elseif 3 then {script3} (where elseif clause defined as "literal(elseif) expr ?literal(then)? script")
#the elseif 2 {script2} will raise an error because the newtypelist from elseif 3 then {script3} overwrote the newtypelist where then was given the type ?omitted-...?
#not quite right.. this modifies the -type for all clauses with this name - but for -multiple true each instance should really be considered separately.
#e.g when a subelement-containing clause is allowed to appear multiple times (-multiple true)
# - we may hava a situation where the supplied arguments do and don't omit optional subelements,
# and the newtypelist from one clause may overwrite the newtypelist from the other clause where the optional subelement was omitted in one arg, but not in the other arg.
# - if expr {} elseif 2 {script2} elseif 3 then {script3}
# - (where elseif clause defined as "literal(elseif) expr ?literal(then)? script")
# The elseif 2 {script2} will reassign the type as "literal(elseif) expr ?omitted-literal(then)? script"
# when the elseif 3 then {script3} is processed, 'then' is now considered against the type ?ommitted-literal(then)?
#which (as a non-recognised type is therefore not validated ) will then
# allow any value instead of 'then' to pass.
tcl::dict::set argstate $leadername -type $newtypelist ;#(possible ?omitted-<type>? and ?defaulted-<type>? entries
#see argument_clause_typestate in value processing loop below for more handling of this issue regarding -multiple true clauses with optional subelements
#todo - synchronize with value processing loop below
#- consider refactor to a common procedure for handling this issue of tracking updated typelist state for optional subelements in -multiple true clauses
#incorrect -don't update default -type info.
#tcl::dict::set argstate $leadername -type $newtypelist ;#(possible ?omitted-<type>? and ?defaulted-<type>? entries
}
if {[tcl::dict::get $argstate $leadername -multiple]} {
@ -8904,7 +9066,7 @@ tcl::namespace::eval punk::args {
}
#incorrect - we shouldn't update the default. see argument_clause_typestate dict of lists of -type
tcl::dict::set argstate $valname -type $newtypelist ;#(possible ?omitted-<type>? and ?defaulted-<type>? entries
#tcl::dict::set argstate $valname -type $newtypelist ;#(possible ?omitted-<type>? and ?defaulted-<type>? and ?validated-<type>? entries
}
if {[tcl::dict::get $argstate $valname -multiple]} {
@ -9206,7 +9368,7 @@ tcl::namespace::eval punk::args {
}
set vlist_typelist [list]
if {[dict exists $argument_clause_typestate $argname]} {
#lookup saved newtypelist (argument_clause_typelist) from can_assign_value result where some optionals were given type ?omitted-<tp>? or ?defaulted-<tp>?
#lookup saved newtypelist (argument_clause_typelist) from can_assign_value result where some optionals were given type ?omitted-<tp>? or ?defaulted-<tp>? or ?validated-<tp>?.
# args.test: parse_withdef_value_clause_missing_optional_multiple
set vlist_typelist [dict get $argument_clause_typestate $argname]
} else {
@ -9315,11 +9477,12 @@ tcl::namespace::eval punk::args {
#fast fail on the wrong number of choices
if {[llength $c_list] < $choicemultiple_min} {
set msg "$argclass $argname for %caller% requires at least $choicemultiple_min choices. Received [llength $c_list] choices."
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list choicecount [llength $c_list] minchoices $choicemultiple_min maxchoices $choicemultiple_max] -badarg $argname]] $msg
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list choicecount [llength $c_list] minchoices $choicemultiple_min maxchoices $choicemultiple_max] -badarg $argname -argspecs $argspecs]] $msg
#return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list optionmissing $full_missing received $flagsreceived] -argspecs $argspecs]] $msg
}
if {$choicemultiple_max != -1 && [llength $c_list] > $choicemultiple_max} {
set msg "$argclass $argname for %caller% requires at most $choicemultiple_max choices. Received [llength $c_list] choices."
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list choicecount [llength $c_list] minchoices $choicemultiple_min maxchoices $choicemultiple_max] -badarg $argname]] $msg
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list choicecount [llength $c_list] minchoices $choicemultiple_min maxchoices $choicemultiple_max] -badarg $argname -argspecs $argspecs]] $msg
}
#-----------------------------------
@ -9435,7 +9598,19 @@ tcl::namespace::eval punk::args {
}
tcl::dict::set $dname $argname_or_ident $existing
} else {
lset existing $element_index $choice_idx $chosen
#test required.
# punk::args::parse {{read write w}} withdef @values {mode -type list -choices {read write} -choicemultiple {1 -1}}
#puts ">>> clause_size $clause_size"
#puts ">>> existing $existing"
#puts ">>> lset existing $element_index $choice_idx $chosen"
if {$clause_size == 1} {
#e.g -type list
#we have multiple choices allowed for a single element clause because that clause type is a list.
lset existing $choice_idx $chosen
} else {
#e.g -type {any any}
lset existing $element_index $choice_idx $chosen
}
tcl::dict::set $dname $argname_or_ident $existing
}
}

438
src/modules/punk/args/moduledoc/tclcore-999999.0a1.0.tm

@ -102,7 +102,8 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
set manbase_tcl "https://tcl.tk/man/tcl/TclCmd"
set manbase_ext .htm
} else {
set manbase_tcl "https://tcl.tk/man/tcl9.0/TclCmd"
set tclv [info tclversion] ;#e.g 9.0 9.1
set manbase_tcl "https://tcl.tk/man/tcl${tclv}/TclCmd"
set manbase_ext .html
}
proc manpage_tcl {cmd} {
@ -1468,7 +1469,10 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
namespace eval argdoc {
lappend PUNKARGS [list {
@id -id ::tcl::chan::blocked
@cmd -name "Built-in: tcl::chan::blocked" -help\
@cmd -name "Built-in: tcl::chan::blocked"\
-summary\
"Test whether the last input operation failed because it would have blocked."\
-help\
"This tests whether the last input operation on the channel called ${$I}channel${$NI}
failed because it would otherwise have caused the process to block, and returns 1
if that was the case. It returns 0 otherwise. Note that this only ever returns 1
@ -1481,15 +1485,19 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
lappend PUNKARGS [list {
@id -id ::tcl::chan::close
@cmd -name "Built-in: tcl::chan::close" -help\
@cmd -name "Built-in: tcl::chan::close"\
-summary\
"Close and destroy a channel."\
-help\
"Close and destroy the channel called channel. Note that this deletes all existing file-events
registered on the channel. If the direction argument (which must be read or write or any
registered on the channel. If the direction argument (which must be ${$B}read${$N} or ${$B}write${$N} or any
unique abbreviation of them) is present, the channel will only be half-closed, so that it can
go from being read-write to write-only or read-only respectively. If a read-only channel is
closed for reading, it is the same as if the channel is fully closed, and respectively similar
for write-only channels. Without the direction argument, the channel is closed for both reading
and writing (but only if those directions are currently open). It is an error to close a
read-only channel for writing, or a write-only channel for reading.
As part of closing the channel, all buffered output is flushed to the channel's output device
(only if the channel is ceasing to be writable), any buffered input is discarded (only if the
channel is ceasing to be readable), the underlying operating system resource is closed and
@ -1540,6 +1548,7 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
{Query/set channel configuration options}\
-help\
{Query or set the configuration options of the channel named ${$I}channel${$NI}
If no ${$I}optionName${$NI} or ${$I}value${$NI} arguments are supplied, the
command returns a list containing alternating option names and values for the
channel. If ${$I}optionName${$NI} is supplied but no ${$I}value${$NI} then the
@ -1809,6 +1818,57 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
}
}]
lappend PUNKARGS [list {
@id -id ::tcl::chan::create
@cmd -name "Built-in: tcl::chan::create"\
-summary\
"Create new script level channel."\
-help\
"This subcommand creates a new script level channel using the command prefix ${$I}cmdPrefix${$NI} as its handler.
Any such channel is called a ${$B}reflected${$N} channel. The specified command prefix, ${$I}cmdPrefix${$NI}, must be a non-empty list,
and should provide the API described in the ${$B}refchan${$N} manual page. The handle of the new channel is returned as the
result of the ${$B}chan create${$N} command, and the channel is open. Use either ${$B}close${$N} or ${$B}chan close${$N} to remove the channel.
The argument mode specifies if the new channel is opened for reading, writing, or both. It has to be a list
containing any of the strings “read” or “write”, The list must have at least one element, as a channel you can
neither write to nor read from makes no sense. The handler command for the new channel must support the chosen mode,
or an error is thrown.
The command prefix is executed in the global namespace, at the top of call stack, following the appending of arguments
as described in the ${$B}refchan${$N} manual page. Command resolution happens at the time of the call. Renaming the command, or
destroying it means that the next call of a handler method may fail, causing the channel command invoking the handler
to fail as well. Depending on the subcommand being invoked, the error message may not be able to explain the reason
for that failure.
Every channel created with this subcommand knows which interpreter it was created in, and only ever executes its
handler command in that interpreter, even if the channel was shared with and/or was moved into a different interpreter.
Each reflected channel also knows the thread it was created in, and executes its handler command only in that thread,
even if the channel was moved into a different thread. To this end all invocations of the handler are forwarded to the
original thread by posting special events to it. This means that the original thread (i.e. the thread that executed the
${$B}chan create${$N} command) must have an active event loop, i.e. it must be able to process such events. Otherwise the thread
sending them will block indefinitely. Deadlock may occur.
Note that this permits the creation of a channel whose two endpoints live in two different threads, providing a
stream-oriented bridge between these threads. In other words, we can provide a way for regular stream communication
between threads instead of having to send commands.
When a thread or interpreter is deleted, all channels created with this subcommand and using this thread/interpreter as
their computing base are deleted as well, in all interpreters they have been shared with or moved into, and in whatever
thread they have been transferred to. While this pulls the rug out under the other thread(s) and/or interpreter(s),
this cannot be avoided. Trying to use such a channel will cause the generation of a regular error about unknown channel
handles.
This subcommand is ${$B}safe${$N} and made accessible to safe interpreters. While it arranges for the execution of arbitrary Tcl
code the system also makes sure that the code is always executed within the safe interpreter."
@values -min 2 -max 2
#man page says must be at least one element in mode list.
#man page doesn't limit list to 2 elements long despite there being only 2 mode values
# - suggests things such as {r write read w ...} without limit on length is allowed
mode -type list -choices {read write} -choicemultiple {1 -1} -help\
"list of at least one of read write or abbreviations of these"
cmdprefix -type string
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::eof
@cmd -name "Built-in: tcl::chan::eof"\
@ -1823,7 +1883,57 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
""
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
#event
lappend PUNKARGS [list {
@id -id ::tcl::chan::event
@cmd -name "Built-in: tcl::chan::event"\
-summary\
"Create, delete or query a file event handler."\
-help\
"Arrange for the Tcl script script to be installed as a file event handler to be called whenever the channel
called channel enters the state described by event (which must be either readable or writable); only one such
handler may be installed per event per channel at a time. If script is the empty string, the current handler
is deleted (this also happens if the channel is closed or the interpreter deleted). If script is omitted, the
currently installed script is returned (or an empty string if no such handler is installed). The callback is
only performed if the event loop is being serviced (e.g. via vwait or update).
A file event handler is a binding between a channel and a script, such that the script is evaluated whenever
the channel becomes readable or writable. File event handlers are most commonly used to allow data to be
received from another process on an event-driven basis, so that the receiver can continue to interact with the
user or with other channels while waiting for the data to arrive. If an application invokes ${$B}chan gets${$N} or
${$B}chan read${$N} on a blocking channel when there is no input data available, the process will block; until the input
data arrives, it will not be able to service other events, so it will appear to the user to “freeze up”.
With ${$B}chan event${$N}, the process can tell when data is present and only invoke ${$B}chan gets${$N} or ${$B}chan read${$N} when they
will not block.
A channel is considered to be readable if there is unread data available on the underlying device. A channel is
also considered to be readable if there is unread data in an input buffer, except in the special case where the
most recent attempt to read from the channel was a ${$B}chan gets${$N} call that could not find a complete line in the
input buffer. This feature allows a file to be read a line at a time in non-blocking mode using events.
A channel is also considered to be readable if an end of file or error condition is present on the underlying
file or device. It is important for script to check for these conditions and handle them appropriately;
for example, if there is no special check for end of file, an infinite loop may occur where script reads no
data, returns, and is immediately invoked again.
A channel is considered to be writable if at least one byte of data can be written to the underlying file or
device without blocking, or if an error condition is present on the underlying file or device. Note that client
sockets opened in asynchronous mode become writable when they become connected or if the connection fails.
Event-driven I/O works best for channels that have been placed into non-blocking mode with the chan configure
command. In blocking mode, a ${$B}chan puts${$N} command may block if you give it more data than the underlying file or
device can accept, and a ${$B}chan gets${$N} or ${$B}chan read${$N} command will block if you attempt to read more data than is
ready; no events will be processed while the commands block. In non-blocking mode ${$B}chan puts${$N}, ${$B}chan read${$N}, and
${$B}chan gets${$N} never block.
The script for a file event is executed at global level (outside the context of any Tcl procedure) in the
interpreter in which the chan event command was invoked. If an error occurs while executing the script then the
command registered with interp bgerror is used to report the error. In addition, the file event handler is
deleted if it ever returns an error; this is done in order to prevent infinite loops due to buggy handlers."
@values -min 2 -max 3
channel
event -choices {readable writable}
script -optional 1
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::flush
@cmd -name "Built-in: tcl::chan::flush"\
@ -1878,9 +1988,58 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
channel
varName -optional 1
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
#isbinary
#names
#pending
lappend PUNKARGS [list {
@id -id ::tcl::chan::isbinary
@cmd -name "Built-in: tcl::chan::isbinary"\
-summary\
"Test if channel is binary (encoding iso8859-1, eofchar {}, translation lf)."\
-help\
"Test whether the channel called ${$I}channel${$NI} is a binary channel, returning 1 if it is and, and 0 otherwise.
A binary channel is a channel with iso8859-1 encoding, -eofchar set to {} and -translation set to lf."
@values -min 1 -max 1
channel
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
#chan names - deviation from online manual to add point about channel names and transformations
lappend PUNKARGS [list {
@id -id ::tcl::chan::names
@cmd -name "Built-in: tcl::chan::names"\
-summary\
"List all channel names. (toplevel)"\
-help\
{Produces a list of all channel names (*).
If pattern is specified, only those channel names that match it (according to the rules of string match)
will be returned.
* Note that the channel names returned are not necessarily the same as the channel names that are visible
in a given interpreter.
For example, if channel transformations are in use on stdin, stdout, or stderr, the channel names returned
will different for those channels.
e.g you may still be able to call ${$B}puts stdout "hello"${$N} even though ${$B}chan names${$N} does not return 'stdout'
It may instead show in the result list as something like 'file17f99e788b0'.
See the documentation for chan push for more details on this.}
@values -min 0 -max 1
pattern -optional 1 -default "*"
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::pending
@cmd -name "Built-in: tcl::chan::pending"\
-summary\
"Number of pending bytes buffered."\
-help\
"Depending on whether mode is input or output, returns the number of bytes of input or output (respectively)
currently buffered internally for channel (especially useful in a readable event callback to impose
application-specific limits on input line lengths to avoid a potential denial-of-service attack where a
hostile user crafts an extremely long line that exceeds the available memory to buffer it). Returns -1 if
the channel was not opened for the mode in question."
@values -min 2 -max 2
mode -choices {input output}
channel -type string
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::pipe
@cmd -name "Built-in: tcl::chan::pipe"\
@ -1921,6 +2080,24 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
channel -type string
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::push
@cmd -name "Built-in: tcl::chan::push"\
-summary\
"Add a new transformation on top of channel."\
-help\
"Adds a new transformation on top of the channel ${$I}channel${$NI}.
The ${$I}cmdPrefix${$NI} argument describes a list of one or more words which represent a handler
that will be used to implement the transformation. The command prefix must provide the
API described in the ${$B}transchan${$N} manual page. The result of this subcommand is a handle to
the transformation. Note that it is important to make sure that the transformation is
capable of supporting the channel mode that it is used with or this can make the channel
neither readable nor writable."
@values -min 2 -max 2
channel -type string
cmdPrefix -type string
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::puts
@cmd -name "Built-in: tcl::chan::puts"\
@ -2262,7 +2439,9 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
is equivalent to a false result. The key/value pairs
are tested in the order in which the keys were inserted
into the dictionary."
vars -type list -minsize 2 -maxsize 2 -typesynopsis {{keyVariable valueVariable}} -optional 0
vars -type list -minsize 2 -maxsize 2 -typesynopsis {{keyVariable valueVariable}} -optional 0 -help\
"Two element list of variable names to be used for the
key and value respectively"
script -type script
@form -form value
@ -2421,7 +2600,10 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
lappend PUNKARGS [list {
@id -id ::tcl::dict::map
@cmd -name "Built-in: tcl::dict::map" -help\
@cmd -name "Built-in: tcl::dict::map"\
-summary\
"Apply a transformation to each value of a dictionary, returning a new dictionary."\
-help\
"This command applies a transformation to each element of a dictionary,
returning a new dictionary. It takes three arguments: the first is a
two-element list of variable names (for the key and value respectively of
@ -2919,6 +3101,7 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
name -type string
} "@doc -name Manpage: -url [manpage_tcl file]" ]
#tcl 9+
lappend PUNKARGS [list {
@id -id ::tcl::file::home
@cmd -name "Built-in: tcl::file::home" -help\
@ -2952,6 +3135,48 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
#join
#link
lappend PUNKARGS [list {
@id -id ::tcl::file::link
@cmd -name "Built-in: tcl::file::link"\
-summary\
"Create a link or return the value of a link."\
-help\
"If only one argument is given, that argument is assumed to be linkName, and this command returns the value
of the link given by linkName (i.e. the name of the file it points to). If linkName is not a link or its
value cannot be read (as, for example, seems to be the case with hard links, which look just like ordinary
files), then an error is returned.
If 2 arguments are given, then these are assumed to be linkName and target. If linkName already exists, or
if target does not exist, an error will be returned. Otherwise, Tcl creates a new link called linkName which
points to the existing filesystem object at target (which is also the returned value), where the type of the
link is platform-specific (on Unix a symbolic link will be the default). This is useful for the case where
the user wishes to create a link in a cross-platform way, and does not care what type of link is created.
If the user wishes to make a link of a specific type only, (and signal an error if for some reason that is
not possible), then the optional -linktype argument should be given. Accepted values for -linktype are
“-symbolic” and “-hard”.
On Unix, symbolic links can be made to relative paths, and those paths must be relative to the actual
linkName's location (not to the cwd), but on all other platforms where relative links are not supported,
target paths will always be converted to absolute, normalized form before the link is created
(and therefore relative paths are interpreted as relative to the cwd). When creating links on filesystems
that either do not support any links, or do not support the specific type requested, an error message will
be returned. Most Unix platforms support both symbolic and hard links (the latter for files only).
Windows supports symbolic directory links and hard file links on NTFS drives.
"
@opts -type none -parsekey "-LINKTYPE" -group "linktype" -grouphelp\
""
-symbolic -typedefaults "-symbolic" -help\
""
-hard -typedefaults "-hard" -help\
"
"
@opts -parsekey "" -group ""
@values -min 1 -max 2
linkName -type string -optional 0
target -type string -optional 1
} "@doc -name Manpage: -url [manpage_tcl file]" ]
#lstat
lappend PUNKARGS [list {
@ -2986,8 +3211,37 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
name -type string
time -type integer -optional 1
} "@doc -name Manpage: -url [manpage_tcl file]"]
#nativename
#normalize
lappend PUNKARGS [list {
@id -id ::tcl::file::nativename
@cmd -name "Built-in: tcl::file::nativename"\
-summary\
{Platform-specific name of the file.}\
-help\
"Returns the platform-specific name of the file. This is useful if the filename is needed to pass
to a platform-specific call, such as to a subprocess via ${$B}exec${$N} under Windows (see EXAMPLES below)."
@values -min 1 -max 1
name -optional 0 -type string
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::normalize
@cmd -name "Built-in: tcl::file::normalize"\
-summary\
{Unique normalized path.}\
-help\
"Returns a unique normalized path representation for the file-system object (file, directory, link, etc),
whose string value can be used as a unique identifier for it. A normalized path is an absolute path which
has all “../” and “./” removed. Also it is one which is in the “standard” format for the native platform.
On Unix, this means the segments leading up to the path must be free of symbolic links/aliases (but the
very last path component may be a symbolic link), and on Windows it also means we want the long form with
that form's case-dependence (which gives us a unique, case-dependent path). The one exception concerning
the last link in the path is necessary, because Tcl or the user may wish to operate on the actual
symbolic link itself (for example ${$B}file delete${$N}, ${$B}file rename${$N}, ${$B}file copy${$N} are defined to operate on symbolic
links, not on the things that they point to)."
@values -min 1 -max 1
name -optional 0 -type string
} "@doc -name Manpage: -url [manpage_tcl file]"]
#owned
#pathtype
lappend PUNKARGS [list {
@ -3015,6 +3269,24 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
} "@doc -name Manpage: -url [manpage_tcl file]"]
#rename (2 forms)
lappend PUNKARGS [list {
@id -id ::tcl::file::rename
@cmd -name "Built-in: tcl::file::rename"\
-summary\
{Rename file or folder.}\
-help\
""
#----------------------------------------------
@form -form "tofile"
@opts
-force -type none -optional 1 -default 0
-- -type none -optional 1
@values -min 2 -max 2
source -optional 0 -type string
#----------------------------------------------
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::rootname
@cmd -name "Built-in: tcl::file::rootname"\
@ -3030,14 +3302,134 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
#separator
#size
#split
#stat
#system
#tail
#tempdir
#tempfile
lappend PUNKARGS [list {
@id -id ::tcl::file::stat
@cmd -name "Built-in: tcl::file::stat"\
-summary\
{Get file metadata - status information.}\
-help\
"Invokes the stat kernel call on name, and returns a dictionary with the information returned from
the kernel call. If varName is given, it uses the variable to hold the information. VarName is
treated as an array variable, and in such case the command returns the empty string. The following
elements are set: ${$B}atime${$N}, ${$B}ctime${$N}, ${$B}dev${$N}, ${$B}gid${$N}, ${$B}ino${$N}, ${$B}mode${$N}, ${$B}mtime${$N}, ${$B}nlink${$N}, ${$B}size${$N}, ${$B}type${$N}, ${$B}uid${$N}.
Each element except ${$B}type${$N} is a decimal string with the value of the corresponding field from the
stat return structure; see the manual entry for stat for details on the meanings of the values.
The type element gives the type of the file in the same form returned by the command ${$B}file type${$N}."
@values -min 1 -max 1
name -optional 0 -type string
varName -type string -optional 1
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::system
@cmd -name "Built-in: tcl::file::system"\
-summary\
{filesystem info for path}\
-help\
"Returns a list of one or two elements, the first of which is the name of the filesystem to use for
the file, and the second, if given, an arbitrary string representing the filesystem-specific nature
or type of the location within that filesystem. If a filesystem only supports one type of file, the
second element may not be supplied. For example the native files have a first element “native”, and
a second element which when given is a platform-specific type name for the file's system
(e.g. “NTFS”, “FAT”, on Windows). A generic virtual file system might return the list “vfs ftp” to
represent a file on a remote ftp site mounted as a virtual filesystem through an extension called
“vfs”. If the file does not belong to any filesystem, an error is generated."
@values -min 1 -max 1
name -optional 0 -type string
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::tail
@cmd -name "Built-in: tcl::file::tail"\
-summary\
{Last filesystem component of path}\
-help\
"Returns all of the characters in the last filesystem component of ${$I}name${$NI}.
Any trailing directory separator in ${$I}name${$NI} is ignored. If ${$I}name${$NI} contains no separators then returns ${$I}name${$NI}.
So, ${$B}file tail a/b${$N}, ${$B}file tail a/b/${$N} and ${$B}file tail b${$N} all return ${$B}b${$N}."
@values -min 1 -max 1
name -optional 0 -type string
} "@doc -name Manpage: -url [manpage_tcl file]"]
#tempdir tcl 9+ only?
lappend PUNKARGS [list {
@id -id ::tcl::file::tempdir
@cmd -name "Built-in: tcl::file::tempdir"\
-summary\
{Create a temporary directory.}\
-help\
"Creates a temporary directory (guaranteed to be newly created and writable by the current script)
and returns its name. If template is given, it specifies one of or both of the existing directory
(on a filesystem controlled by the operating system) to contain the temporary directory, and the
base part of the directory name; it is considered to have the location of the directory if there
is a directory separator in the name, and the base part is everything after the last directory
separator (if non-empty). The default containing directory is determined by system-specific
operations, and the default base name prefix is “tcl”.
The following output is typical and illustrative; the actual output will vary between platforms:
${[punk::args::helpers::example {
% ${$B}file tempdir${$N}
/var/tmp/tcl_u0kuy5
% ${$B}file tempdir /tmp/myapp${$N}
/tmp/myapp_8o7r9L
% ${$B}file tempdir /tmp/${$N}
/tmp/tcl_1m0JHD
% ${$B}file tempdir myapp${$N}
/var/tmp/myapp_0ihS0n
}]}
"
@values -min 0 -max 1
template -type string -optional 1
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::tempfile
@cmd -name "Built-in: tcl::file::tempfile"\
-summary\
{Create temp file and return open channel.}\
-help\
"Creates a temporary file and returns a read-write channel opened on that file.
If the nameVar is given, it specifies a variable that the name of the temporary
file will be written into; if absent, Tcl will attempt to arrange for the
temporary file to be deleted once it is no longer required. If the template is
present, it specifies parts of the template of the filename to use when creating
it (such as the directory, base-name or extension) though some platforms may
ignore some or all of these parts and use a built-in default instead.
Note that temporary files are only ever created on the native filesystem.
As such, they can be relied upon to be used with operating-system native APIs
and external programs that require a filename."
@values -min 0 -max 2
nameVar -type string -optional 1
template -type string -optional 1
} "@doc -name Manpage: -url [manpage_tcl file]"]
#tildeexpand
#type
#volumes
lappend PUNKARGS [list {
@id -id ::tcl::file::type
@cmd -name "Built-in: tcl::file::type"\
-summary\
{Type of file name.}\
-help\
"Returns a string giving the type of file name, which will be one of
${$B}file${$N}, ${$B}directory${$N}, ${$B}characterSpecial${$N}, ${$B}blockSpecial${$N}, ${$B}fifo${$N}, ${$B}link${$N}, or ${$B}socket${$N}."
@values -min 0 -max 0
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::volumes
@cmd -name "Built-in: tcl::file::volumes"\
-summary\
"List volumes mounted on the system."\
-help\
"Returns the absolute paths to the volumes mounted on the system, as a proper Tcl list.
Without any additional virtual filesystems mounted as root volumes, on UNIX, the command
will return “//zipfs:/”/ or “/”, (in case of a --disable-zipfs build), since all
filesystems are locally mounted. On Windows, it will return a list of the available
local drives (e.g. “//zipfs:/ C:/”). If any virtual filesystem has mounted additional
volumes, they will be in the returned list too."
@values -min 0 -max 0
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::writable
@ -6694,21 +7086,21 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
start -type number|expr
..|to -type string -choices {.. to} -optional 1
end -type number|expr
"by step" -type {literal(by) number|expr} -optional 1
"by step" -type {?literalprefix(by)? number|expr} -optional 1
@form -form start_count
@leaders -min 0 -max 0
@values -min 3 -max 5
start -type number|expr
count -type literal
count -type literalprefix(count)
countelements -type number|expr
"by step" -type {literal(by) number|expr} -optional 1
"by step" -type {literalprefix(by) number|expr} -optional 1
@form -form count
@leaders -min 0 -max 0
@values -min 1 -max 3
countelements -type number|expr
"by step" -type {literal(by) number|expr} -optional 1
"by step" -type {literalprefix(by) number|expr} -optional 1
} "@doc -name Manpage: -url [manpage_tcl lseq]"\
{

174
src/modules/punk/auto_exec-999999.0a1.0.tm

@ -56,7 +56,9 @@ tcl::namespace::eval punk::auto_exec {
-help\
{Clear/refresh the autoexec commands in the ::auto_execs array.
This is analogous to the 'rehash' command in shells such as csh, tcsh and zsh.
This is analogous to the 'rehash' command in shells such as csh, tcsh and zsh,
or 'hash -r' in other shells such as bash.
It updates the shell's hash table of executable commands.
This can be useful after installing new software, adjusting the environment PATH directories, or (on windows) making
@ -64,7 +66,9 @@ tcl::namespace::eval punk::auto_exec {
commands are up to date with the current state of the system.
If refresh is false (the default), then all autoexec commands are cleared and will re-register as commands are called.
If refresh is true, then all existing autoexec commands are re-registered by calling auto_execok for each of them again.}
If refresh is true, then all existing autoexec commands are re-registered by calling auto_execok for each of them again.
see also ::punk::auto_exec::hash}
@opts
@values -min 0 -max 1
refresh -type boolean -default 0 -help\
@ -85,6 +89,172 @@ tcl::namespace::eval punk::auto_exec {
}
return
}
namespace eval argdoc {
lappend PUNKARGS [list {
@id -id "::punk::auto_exec::hash"
@cmd -name "punk::auto_exec::hash"\
-summary\
"Manage the hash table of autoexec commands cached in ::auto_execs."\
-help\
{see also ::punk::auto_exec::rehash}
#---------------------
@form -form {show_or_set}
@opts -min 0 -max 0
@values -min 0 -max -1
name -type string -multiple 1 -optional 1 -default {} -help\
"One or more autoexec command names to set.
If no names are provided, then all autoexec commands in the hash table will be shown."
#---------------------
@form -form {rehash}
@opts -min 1 -max 1
-r -type none -optional 0 -help\
"Clear autoexec commands from the hash table"
@values -min 0 -max 0
#---------------------
@form -form {test}
@opts
-t -type none -optional 0 -default "" -help\
"The name of the autoexec command name to display."
@values -min 1 -max -1
name -type string -multiple 1 -help\
"One or more autoexec command names to display information for.
If only a single name is provided, then the output will be the raw command string
associated with that autoexec command in the hash table.
If multiple names are provided, then the output will be a string containing each
name and its associated command string on a separate line."
#---------------------
@form -form {delete}
@opts
-d -type none -optional 0 -help\
"Delete specified autoexec commands from the hash table."
@values -min 1 -max -1
name -type string -multiple 1 -help\
"One or more autoexec command names to delete from the hash table."
#---------------------
#todo?
#-p <path> <name> (manually assign)
#-l (build a list of hash -p <path> <name> entries for all autoexec commands that can be used in a script to pre-populate the hash table without needing to call auto_execok for each command at runtime)
#---------------------
@form -form {help}
@opts -min 1 -max 1 -anyopts 1
--help -type none -optional 0 -help\
"Display usage information for this command."
@values -min 0 -max -1
ignored -type any -multiple 1 -optional 1 -help\
"Additional arguments that are ignored when --help is used"
}]
}
proc hash {args} {
set arg1 [lindex $args 0]
#select parsing form based on first argument
switch -- $arg1 {
-r {
set form rehash
}
-t {
set form test
}
-d {
set form delete
}
--help {
set form help
}
default {
#like bash in this context, we won't allow an option-like entry to be treated as an executable name
if {[string match -* $arg1]} {
puts stderr "hash: ${arg1}: invalid option"
#return [punk::args::usage -scheme error ::punk::auto_exec::hash]
set msg "hash: usage:\n"
append msg [punk::ns::synopsis ::punk::auto_exec::hash]
error $msg
}
set form show_or_set
}
}
set argd [punk::args::parse $args -form $form withid ::punk::auto_exec::hash]
lassign [dict values $argd] _leaders opts values received
global auto_execs
switch -- $form {
rehash {
unset -nocomplain auto_execs
}
test {
#like bash - we'll provide only the path if there is a single name provided, but if there are multiple names we'll provide both the name and path for each.
set names [dict get $values name]
if {[llength $names] == 1} {
set nm [lindex $names 0]
if {[info exists auto_execs($nm)]} {
return [set auto_execs($nm)]
} else {
#review
puts stderr "hash: $nm: not found"
return ""
}
}
set result ""
foreach nm $names {
if {[info exists auto_execs($nm)]} {
append result "$nm [set auto_execs($nm)]\n"
} else {
#review
puts stderr "$hash: nm: not found"
}
}
if {$result ne ""} {
set result [string trimright $result \n]
}
return $result
}
delete {
set names [dict get $values name]
foreach nm $names {
unset -nocomplain auto_execs($nm)
}
}
help {
return [punk::args::usage ::punk::auto_exec::hash]
}
default {
set requested_names [dict get $values name]
if {[llength $requested_names] == 0} {
#show all
set hashed_names [array names auto_execs]
#todo - record and return 'hits' like bash does?
set result ""
foreach nm $hashed_names {
set cached [set auto_execs($nm)]
#unlike some shells - we cache negative results (for absolute paths) that don't exist.
#as we're attempting to be close to behaviour of bash, don't output empty results for negative cache entries.
if {$cached ne ""} {
append result $cached \n
}
}
if {$result ne ""} {
set result [string trimright $result \n]
}
return $result
} else {
#rehash each requested name if it exists, otherwise display an msg on stderr for that name.
foreach nm $requested_names {
set aexec [auto_execok $nm]
if {$aexec ne ""} {
set auto_execs($nm) $aexec
} else {
puts stderr "hash: $nm: not found"
}
}
return
}
}
}
}
variable PUNKARGS
lappend PUNKARGS [list {

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

@ -503,16 +503,33 @@ tcl::namespace::eval punk::config {
key -type string -optional 1
newvalue -optional 1
}]
proc configure {args} {
set argd [punk::args::parse $args withid ::punk::config::configure]
lassign [dict values $argd] leaders opts values received solos
set whichconfig [dict get $argd leaders whichconfig]
proc configure {whichconfig args} {
#set argd [punk::args::parse $args withid ::punk::config::configure]
#lassign [dict values $argd] leaders opts values received solos
#set whichconfig [dict get $argd leaders whichconfig]
set values [dict create]
switch -- [llength $args] {
0 {
}
1 {
dict set values key [lindex $args 0]
}
2 {
dict set values newvalue [lindex $args 1]
}
default {
error "Too many arguments. Expected at most 2 (key [newvalue])"
}
}
variable configdata
if {"running" ni [dict keys $configdata]} {
init
Apply startup
}
switch -- $whichconfig {
set fullwhich [tcl::prefix::match -error "" {defaults startup-configuration running-configuration} $whichconfig]
switch -- $fullwhich {
defaults {
set configrecords [dict get $configdata defaults]
}
@ -522,12 +539,15 @@ tcl::namespace::eval punk::config {
running-configuration {
set configrecords [dict get $configdata running]
}
default {
error "Unknown config name '$whichconfig' - try defaults or startup-configuration or running-configuration"
}
}
if {![dict exists $received key]} {
if {![dict exists $values key]} {
return $configrecords
}
set key [dict get $values key]
if {![dict exists $received newvalue]} {
if {![dict exists $values newvalue]} {
return [dict get $configrecords $key]
}
error "setting value not implemented"

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

File diff suppressed because it is too large Load Diff

192
src/modules/punk/imap4-999999.0a1.0.tm

@ -468,10 +468,15 @@ tcl::namespace::eval punk::imap4::proto {
lappend PUNKARGS [list {
@id -id ::punk::imap4::proto::has_capability
@cmd -name punk::imap4::proto::has_capability -help\
"Return a list of the server capabilities last received,
or a boolean indicating if a particular capability was
present."
@cmd -name punk::imap4::proto::has_capability\
-summary\
"List capabilities or test existence of a specific capability."\
-help\
"Returns a list of the server capabilities last received when called
with no argument.
Returns boolean indicating if a particular capability was
present when called with a capability argument. The capability argument is case-insensitive and should be specified in the same form as it would be expected to be received from"
@leaders -min 1 -max 1
chan -optional 0 -help\
"existing channel for an open IMAP connection"
@ -1803,6 +1808,9 @@ tcl::namespace::eval punk::imap4 {
}
return $result
}
proc lastlog {chan} {
showlog $chan [lastrequesttag $chan]
}
#protocol callbacks to api cache namespace
#msginfo
@ -2158,7 +2166,7 @@ tcl::namespace::eval punk::imap4 {
set chan [dict get $leaders chan]
set mailbox [dict get $values mailbox]
selectmbox $chan SELECT $mailbox
_selectmbox $chan SELECT $mailbox
}
lappend PUNKARGS [list {
@ -2188,10 +2196,10 @@ tcl::namespace::eval punk::imap4 {
set chan [dict get $leaders chan]
set mailbox [dict get $values mailbox]
selectmbox $chan EXAMINE $mailbox
_selectmbox $chan EXAMINE $mailbox
}
# General function for selection.
proc selectmbox {chan cmd mailbox} {
proc _selectmbox {chan cmd mailbox} {
upvar ::punk::imap4::proto::info info
variable mboxinfo
variable msginfo
@ -2319,27 +2327,54 @@ tcl::namespace::eval punk::imap4 {
A mailbox must be SELECTed first and an appropriate
sequence-set supplied for the message(s) of interest."
@leaders -min 1 -max 1
chan
chan -help\
"The channel on which to send the FETCH command.
This should be a channel returned by CONNECT or STARTTLS and that has had SELECT or
EXAMINE issued on it to select a mailbox."
@opts
-inline -type none
-inline -type none -help\
{If specified, the requested data will be returned
in the return value of this command, rather than
being stored in the msginfo cache for retrieval
using msginfo or showlog.
${[punk::args::helpers::example {
showdict [FETCH $chan -inline 1:3 UID FLAGS] */*
}]}
${[punk::args::helpers::example {
showdict [FETCH $chan -inline 1:3 {BODY.PEEK[HEADER.FIELDS (received)]}] {*/*/*/@*}
}]}
}
@values -min 2 -max -1
#todo - use same sequence-set definition across argdefs
sequence-set -help\
"Message sequence set.
1 is the lowest valid sequence number.
* represents the maximum message sequence number
in the mailbox.
e.g
1
2:2
1:3
3,5,9:10
1,10:*
*:5
*
"
1 is the lowest valid sequence number.
* represents the maximum message sequence number
in the mailbox.
e.g
1
2:2
1:3
3,5,9:10
1,10:*
*:5
*
"
queryitems -default {} -help\
"Some common FETCH queries are shown here, but
"
The data items to be fetched for each message in the sequence-set.
A value ending with a colon e.g received: or To: will be interpreted
as a request for all headers with that name.
Such a query could return multiple values for a single message.
(This is likely in particular for the Received: header)
The value(s) will be returned in the msginfo cache (and/or -inline) with the header
name and colon (lower cased) as the key.
Some common FETCH queries are shown here, but
this list isn't exhaustive."\
-multiple 1 -optional 0 -choiceprefix 0 -choicerestricted 0 -choicecolumns 2 -choices {
ALL FAST FULL BODY BODYSTRUCTURE ENVELOPE FLAGS INTERNALDATE
@ -2422,8 +2457,10 @@ tcl::namespace::eval punk::imap4 {
punk::imap4::proto::requirestate $chan SELECT
#parse each seqrange to give it a chance to raise error for bad values
#also store for use in -inline processing
set range_list [list]
foreach seqrange [split $sequenceset ,] {
parse_seq-range $chan $seqrange
lappend range_list [parse_seq-range $chan $seqrange]
}
set items {}
@ -2542,13 +2579,24 @@ tcl::namespace::eval punk::imap4 {
#This is divergent from tcllib::imap4 which returned untagged lists that the client would match
#based on assumed simple value queries such as specific properties and headers that are individually specified.
set fetchresult [dict create]
for {set i $start} {$i <= $end} {incr i} {
set flagdict [dict get $msginfo $chan $i]
#extract the fields that were added for this request_tag only
dict for {f finfo} $flagdict {
if {[dict get $finfo request] eq $request_tag} {
#lappend msgrecord [list $f $finfo]
dict set fetchresult $f $finfo
foreach r $range_list {
lassign $r start end
#puts stderr "fetching range $start:$end"
for {set i $start} {$i <= $end} {incr i} {
set flagdict [dict get $msginfo $chan $i]
dict for {f finfo} $flagdict {
#puts stderr "checking field $f for request $request_tag"
#extract the fields that were added for this request_tag only
if {[dict get $finfo request] eq $request_tag} {
if {[dict exists $fetchresult $f]} {
#merge with existing info for this field
set existing [dict get $fetchresult $f]
dict set existing $i $finfo
dict set fetchresult $f $existing
} else {
dict set fetchresult $f $i $finfo
}
}
}
}
}
@ -2710,8 +2758,9 @@ tcl::namespace::eval punk::imap4 {
@id -id ::punk::imap4::CAPABILITY
@cmd -name punk::imap4::CAPABILITY -help\
"send CAPABILITY command to the server.
The cached results can be checked with
the punk::imap4::has_capability command."
The cached results can be checked with the punk::imap4::has_capability command.
With no arguments has_capability will list all capabilities of the server.
With an argument, it will check for that capability and return a boolean."
@leaders -min 1 -max 1
chan -optional 0
@opts
@ -3148,16 +3197,52 @@ tcl::namespace::eval punk::imap4 {
lappend PUNKARGS [list {
@id -id "::punk::imap4::FOLDERS"
@cmd -name "punk::imap4::FOLDERS" -help\
"List of folders"
@cmd -name "punk::imap4::FOLDERS"\
-summary\
"List folders and flags"\
-help\
{List of folders with their flags.
(Wrapper over IMAP4 protocol's LIST command)
Returns only a 0 (success) or 1 (failure) if -inline is not specified.
The caller can then query the returned information with the folderinfo command.
${[punk::args::helpers::example {
set folders [folderinfo $channelname flags]
}]}
#This will return a list of lists of the form:
{{foldername {{\flag1} {\flag2} ...}} {foldername2 {{\flag1} {\flag2} ...}} ...}
Note the apparent extra bracing around the flags - this is an artifact of how Tcl
represents strings in a list when they have certain characters such as escapes.
${[punk::args::helpers::example {
% lindex $folders 0 1
{\Subscribed} {\HasNoChildren}
% lindex $folder 0 1 0
\Subscribed
}]}
If -inline is specified, this returns a list of 2 element lists of the form:
{foldername {flag1 flag2 ...}}
Note the flags have been converted to lowercase and stripped of any leading backslash
- this is a design choice to make it easier for tcl script users to work with the flags,
If you need the exact IMAP flags, you can query the folderinfo command instead without
using -inline.
}
@leaders -min 1 -max 1
chan
chan -help\
"existing channel for an open IMAP connection"
@opts
-ignorestate -type none
-inline -type none
@values -min 0 -max 2
ref -default ""
mailboxpattern -default "*"
ref -default "" -help\
""
mailboxpattern -default "*" -help\
"The mailbox name pattern with which to query the folders. See IMAP RFC9051 for details on mailbox name patterns and wildcards."
}]
# List of folders
proc FOLDERS {args} {
@ -4263,18 +4348,18 @@ tcl::namespace::eval punk::imap4 {
# get_topic_ functions add more to auto-include in about topics
# -------------------------------------------------------------
proc get_topic_Description {} {
punk::args::lib::tstr [string trim {
punk::args::lib::tstr -indent " " [string trim {
package punk::imap4
A fork from tcllib imap4 module
imap4 - imap client-side tcl implementation of imap protocol
imap4 - imap client-side tcl implementation of IMAP protocol
} \n]
}
proc get_topic_License {} {
return "X11"
return " X11"
}
proc get_topic_Version {} {
return "$::punk::imap4::version"
return " $::punk::imap4::version"
}
proc get_topic_Contributors {} {
set authors {{Salvatore Sanfilippo <antirez@invece.org>} {Nicola Hall <nicci.hall@gmail.com>} {Magnatune <magnatune@users.sourceforge.net>} {Julian Noble <julian@precisium.com.au>}}
@ -4285,10 +4370,31 @@ tcl::namespace::eval punk::imap4 {
if {[string index $contributors end] eq "\n"} {
set contributors [string range $contributors 0 end-1]
}
return $contributors
return [punk::lib::tstr -indent " " $contributors]
}
proc get_topic_API {} {
set B [punk::ansi::a+ bold]
set N [punk::ansi::a+ normal]
punk::args::lib::tstr -indent " " -allowcommands [string trim {
The API is currently in development and subject to change.
${[punk::args::helpers::example {
#For starting point, see output of:
i CONNECT
}]}
Capitalized function names such as FOLDERS and FETCH will generally perform network operations
against the server and require a channel argument.
They will generally return 0 on success and 1 on failure by default.
(many will have a -inline option to return data directly instead of using info)
Lowercase function names such as ${$B}folderinfo${$N} will generally also require a channel
argument but will not perform network operations by default and instead return information
from the most recent successful network operation.
} \n]
}
proc get_topic_notes {} {
punk::args::lib::tstr -return string {
punk::args::lib::tstr -indent " " -return string {
X11 license - is MIT with additional clause regarding use of contributor names.
}
}

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

@ -94,19 +94,16 @@ tcl::namespace::eval punk::lib::ensemble {
set routinetail [tcl::namespace::tail $routine]
if {![string match ::* $extension]} {
set extension [uplevel 1 [
list [tcl::namespace::which namespace] current]]::$extension
set extension [uplevel 1 [list [tcl::namespace::which namespace] current]]::$extension
}
if {![tcl::namespace::exists $extension]} {
error [list {no such namespace} $extension]
}
set extension [tcl::namespace::eval $extension [
list [tcl::namespace::which namespace] current]]
set extension [tcl::namespace::eval $extension [list [tcl::namespace::which namespace] current]]
tcl::namespace::eval $extension [
list [tcl::namespace::which namespace] export *]
tcl::namespace::eval $extension [list [tcl::namespace::which namespace] export *]
while 1 {
set renamed ${routinens}::${routinetail}_[clock clicks] ;#clock clicks unlikely to collide when not directly consecutive such as: list [clock clicks] [clock clicks]
@ -140,7 +137,7 @@ tcl::namespace::eval punk::lib::check {
if {"windows" ne $::tcl_platform(platform)} {
set bug 0
} else {
set tmpdir [file tempdir]
set tmpdir [file tempdir] ;#tcl 9+
set testfile [file join $tmpdir "bugtest"]
set fd [open $testfile w]
puts $fd test
@ -4759,14 +4756,21 @@ namespace eval punk::lib {
foreach ln $linelist {
#set is_replay_pure_reset [regexp {\x1b\[0*m$} $replaycodes] ;#only looks at tail code - but if tail is pure reset - any prefix is ignorable
set ansisplits [punk::ansi::ta::split_codes_single $ln] ;#REVIEW - this split accounts for a large portion of the time taken to run this function.
if {[llength $ansisplits]<= 1} {
if {![punk::ansi::ta::detect $ln]} {
#plaintext only - no ansi codes in line
lappend transformed [string cat $replaycodes $ln $RST]
#leave replaycodes as is for next line
set nextreplay $replaycodes
} else {
set replaycodes $nextreplay
continue
}
set ansisplits [punk::ansi::ta::split_codes_single $ln] ;#REVIEW - this split seems to account for a large portion of the time taken to run this function.
#if {[llength $ansisplits]<= 1} {
# #plaintext only - no ansi codes in line
# lappend transformed [string cat $replaycodes $ln $RST]
# #leave replaycodes as is for next line
# set nextreplay $replaycodes
#} else {
set tail $RST
set lastcode [lindex $ansisplits end-1] ;#may or may not be SGR
if {[punk::ansi::codetype::is_sgr_reset $lastcode]} {
@ -4821,7 +4825,7 @@ namespace eval punk::lib {
#set newreplay [join $codestack ""]
set newreplay [punk::ansi::codetype::sgr_merge_list {*}$codestack]
if {$line_has_sgr && $newreplay ne $replaycodes} {
if {$RST ne "" && $line_has_sgr && $newreplay ne $replaycodes} {
#adjust if it doesn't already does a reset at start
if {[punk::ansi::codetype::has_sgr_leadingreset $newreplay]} {
set nextreplay $newreplay
@ -4838,7 +4842,7 @@ namespace eval punk::lib {
} else {
lappend transformed [string cat $replaycodes $ln $tail]
}
}
#}
set replaycodes $nextreplay
}
set linelist $transformed
@ -5505,7 +5509,7 @@ tcl::namespace::eval punk::lib::debug {
namespace eval ::punk::args::register {
#use fully qualified so 8.6 doesn't find existing var in global namespace
lappend ::punk::args::register::NAMESPACES ::punk::lib
lappend ::punk::args::register::NAMESPACES ::punk::lib ::punk::lib::ensemble
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready

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

@ -330,8 +330,11 @@ tcl::namespace::eval punk::nav::fs {
punk::args::define {
@id -id ::punk::nav::fs::d/
@cmd -name punk::nav::fs::d/ -help\
{List directories or directories and files in the current directory or in the
@cmd -name punk::nav::fs::d/\
-summary\
"Navigate and list directories and files"\
-help\
{Navigate/List directories or directories and files in the current directory or in the
targets specified with the fileglob_or_target glob pattern(s).
If a single target is specified without glob characters, and it exists as a directory,

36
src/modules/punk/nav/ns-999999.0a1.0.tm

@ -33,6 +33,40 @@ tcl::namespace::eval punk::nav::ns {
}
namespace path {::punk::ns}
namespace eval argdoc {
lappend PUNKARGS [list {
@id -id ::punk::nav::ns::ns/
@cmd -name punk::nav::ns::ns/\
-summary\
"Navigate and list namespaces and commands"\
-help\
{Navigate/List namespaces or namespaces and commands in the current namespace or in the
targets specified with the nsglob pattern(s).
This function is provided via aliases as n/ n// and n/// with v being inferred from the alias
The n/ n// and n/// forms are more convenient for interactive use.
examples:
n/ - list namespaces below current namespace
n// - list namespaces and commands below current namespace
n/ p* - list namespaces below current matching p*
n// p* - list namespaces below current and commands in current matching p*
}
@values -min 1 -max -1 -type string
v -type string -choices {/ //} -help\
"
/ - list namespaces only
// - list namespaces and commands
/// - list namespaces, commands and commands resolvable via 'namespace path'
"
nsglob -type string -optional true -multiple true -help\
"A glob pattern supporting placeholders * and ?, to filter results.
If multiple patterns are supplied, then a listing for each pattern is returned.
If no patterns are supplied, then all items are listed."
}]
}
proc ns/ {v {ns_or_glob ""} args} {
variable ns_current ;#change active ns of repl by setting ns_current
@ -227,8 +261,6 @@ tcl::namespace::eval punk::nav::ns {
}
}
}

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

@ -3711,6 +3711,30 @@ y" {return quirkykeyscript}
}
}
punk::args::define {
@id -id ::punk::ns::nscommands
@cmd -name punk::ns::nscommands\
-summary\
"List current namespace commands one per line."\
-help\
"Display commands in the current namespace, or optionally within specified namespaces.
Namespaces to search can be specified as arguments, with optional glob patterns.
Examples:
'nscommands' - list all commands in the current namespace
'nscommands foo*' - list all commands in the current namespace with names starting with 'foo'
'nscommands foo* bar*' - list all commands in the current namespace with names starting with 'foo' or 'bar'"
@leaders -min 0 -max 0
@opts
-raw -type none -help\
"Output raw command names with no ANSI color codes.
Useful for scripting or when color codes would be undesirable."
@values -min 1 -max -1
glob -multiple 1 -optional 1 -default * -help\
"Namespace patterns to search for commands. If not specified, defaults to '*',
which searches the current namespace. Patterns can include glob characters (* and ?).
Examples: 'foo*' to match namespaces starting with 'foo', '*::bar' to match namespaces
ending with 'bar'."
}
proc nscommands {args} {
set commandns [uplevel 1 [list ::tcl::namespace::current]]
set commandlist [::list]
@ -3803,6 +3827,7 @@ y" {return quirkykeyscript}
}
}
interp alias {} nscommands {} punk::ns::nscommands
proc nscommandlist {{ns *}} {
set nsparts [nsparts_cached $ns]
set tail [lindex $nsparts end]
@ -4051,6 +4076,13 @@ y" {return quirkykeyscript}
#eg because parent interp called something like: interp0 alias ::thread::id ::thread::id
#make sure we don't perform an infinite loop
if {$tgt ne $resolved} {
#--------------
#unqualified alias target - need to resolve to fully qualified for cmdwhich lookup to work correctly
#jmn - todo test/review
if {![string match ::* $tgt]} {
set tgt ::$tgt
}
#--------------
set whichinfo [uplevel 1 [list ::punk::ns::cmdwhich $tgt]]
set origin [dict get $whichinfo origin]
set origintype [dict get $whichinfo origintype]

21
src/modules/punk/repl-999999.0a1.0.tm

@ -2948,8 +2948,11 @@ namespace eval repl {
if {[catch {package require thread} errM]} {
puts stdout ">>repl::init initscript lib load fail on package require thread\n$errM"
puts stdout ">>repl::init auto_path : $::auto_path"
puts stdout ">>repl::init tcl::tm::list: [tcl::tm::list]"
if {[catch {package require Thread} errM2]} {
puts stdout ">>repl::init initscript lib load fail on package require Thread\n$errM2"
puts stdout ">>repl::init auto_path : $::auto_path"
puts stdout ">>repl::init tcl::tm::list: [tcl::tm::list]"
}
}
#-----
@ -3395,9 +3398,11 @@ namespace eval repl {
set v [lindex $versions end]
set path [lindex [package ifneeded $pkg $v] end]
if {[file extension $path] in {.tcl .tm}} {
if {![catch {readFile $path} data]} {
if {![catch {readFile $path} packagedef]} {
code eval [list info script $path]
code eval $data
code eval $packagedef
#jjj
code eval [list package provide $pkg $v] ;#ensure package is marked as provided in interp even if it doesn't call package provide itself
code eval [list info script $prior_infoscript]
} else {
error "safe - failed to read $path"
@ -3705,6 +3710,10 @@ namespace eval repl {
#puts stderr [join $::auto_path \n]
#puts stderr -----
#punk::console is not loaded at this point
#puts "--------------provide punk::console : [package provide punk::console]"
#puts "--------------punk::console commands: [info commands ::punk::console::*]"
if {[catch {
package require punk::args
package require punk::config
@ -3714,6 +3723,10 @@ namespace eval repl {
#Requiring it shouldn't trigger application - but zipfs/vfs interactions confused it in some early versions
package require natsort
#catch {package require packageTrace}
if {[catch {package require punk::console} errM]} {
#review
puts stderr "failed to load punk::console - \n$errM\n$::errorInfo"
}
package require punk
package require shellrun
package require shellfilter

25
src/modules/punk/winlnk-999999.0a1.0.tm

@ -733,18 +733,23 @@ tcl::namespace::eval punk::winlnk {
set r [binary scan $lenfield su count_chars] ;# su is for unsigned short in little endian order
set string_value ""
if {[Header_Has_LinkFlag $contents "IsUnicode"]} {
#string is UTF-16LE encoded
#string is UTF-16LE encoded - we have this encoding available in tcl 9+ - but not in 8.6
set numbytes [expr {2 * $count_chars}]
set string_bytes [string range $contents $start+2 [expr {$start + 2 + $numbytes - 1}]]
#consider using tcl encoding convertfrom utf-16le instead of manually parsing the UTF-16LE bytes - this would be more robust and handle edge cases better (e.g. surrogate pairs, non-BMP characters, etc.)
set string_value [encoding convertfrom utf-16le $string_bytes]
#for {set i 0} {$i < [string length $string_bytes]} {
# set char_bytes [string range $string_bytes $i [expr {$i + 1}]]
# set r [binary scan $char_bytes su char] ;# s for unsigned short
# append string_value [format %c $char]
# incr i 1 ;# skip the next byte since it's part of the UTF-16LE encoding
#}
#use tcl encoding convertfrom utf-16le when we can instead of manually parsing the UTF-16LE bytes
#- this should be more robust and handle edge cases better (e.g. surrogate pairs, non-BMP characters, etc.)
if {[catch {set string_value [encoding convertfrom utf-16le $string_bytes]} err]} {
#puts stderr "Error converting UTF-16LE string: $err"
#set string_value ""
for {set i 0} {$i < [string length $string_bytes]} {incr i} {
set char_bytes [string range $string_bytes $i $i+1]
set r [binary scan $char_bytes su char] ;# su for unsigned short
append string_value [format %c $char]
incr i 1 ;# skip the next byte since it's part of the UTF-16LE encoding
}
}
} else {
set numbytes $count_chars
set string_bytes [string range $contents $start+2 [expr {$start + 2 + $numbytes - 1}]]

35
src/modules/test/punk/#modpod-args-999999.0a1.0/args-0.1.5_testsuites/args/args.test

@ -368,8 +368,41 @@ namespace eval ::testspace {
]
#todo - test L1 parsed to Lit1 not arg
# test L1 parsed to Lit1 not arg
#punk::args::parse {x y L1} withdef @values (arg -multiple 1) {lit1 -type literal(L1) -optional 1} {lit2 -type literal(L2) -optional 1}
test parse_withdef_value_leading_multiple_not_greedy_with_trailing_literal {Test value clause with leading -multiple true clause is not greedy when trailing literal can be matched}\
-setup $common -body {
set docids [list]
set argd [punk::args::parse {x y L1} withdef @values {arg -multiple 1} {lit1 -type literal(L1) -optional 1} {lit2 -type literal(L2) -optional 1}]
lappend docids [dict get $argd id]
lappend result [dict get $argd values]
}\
-cleanup {
foreach id $docids {
punk::args::undefine $id 1
}
}\
-result [list\
{arg {x y} lit1 L1}
]
#test the same with literalprefix type
test parse_withdef_value_leading_multiple_not_greedy_with_trailing_literalprefix {
-setup $common -body {
set docids [list]
set argd [punk::args::parse {x y te} withdef @values {arg -multiple 1} {lit1 -type literalprefix(test) -optional 1} {lit2 -type literalprefix(other) -optional 1}]
lappend docids [dict get $argd id]
lappend result [dict get $argd values]
}\
-cleanup {
foreach id $docids {
punk::args::undefine $id 1
}
}\
-result [list\
{arg {x y} lit1 test}\
]
}
#todo
#see i -form 1 file copy -- x

12
src/modules/test/punk/#modpod-args-999999.0a1.0/args-0.1.5_testsuites/args/choices.test

@ -123,8 +123,18 @@ namespace eval ::testspace {
-result [list\
{X {aa {cc aa} {aa bb cc}}}
]
#todo - decide on whether -choicemultiple should disallow duplicates in result by default
# -choicemultiple allows duplicates in result by default (default for -choicemultipleunique 0)
test choicemultiple_list {test -choices with both -multiple and -choicemultiple}\
-setup $common -body {
set argd [punk::args::parse {{read write w}} withdef @values {mode -type list -choices {read write} -choicemultiple {1 -1}}]
lappend result [dict get $argd values]
}\
-cleanup {
}\
-result [list\
{mode {read write write}}
]
test choice_multielement_clause {test -choice with a clause-length greater than 1}\
-setup $common -body {

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

@ -2107,6 +2107,7 @@ tcl::namespace::eval textblock {
set cidx [lindex [tcl::dict::keys $o_columndefs] $index_expression]
set colwidth [my column_width $cidx]
set fwidth [expr {$colwidth + 2}]
set col_blockalign [tcl::dict::get $o_columndefs $cidx -blockalign]
@ -2509,18 +2510,19 @@ tcl::namespace::eval textblock {
set border_ansi $body_ansibase$body_ansiborder
}
set ansibase $body_ansibase$opt_col_ansibase
set r 0
set ftblock [expr {[tcl::dict::get $o_opts_table -frametype] eq "block"}]
set do_show_edge [tcl::dict::get $o_opts_table -show_edge]
foreach c $cells {
#cells in column - each new c is in a different row
set row_ansibase [tcl::dict::get $o_rowdefs $r -ansibase]
set row_bg ""
set row_ansibase [tcl::dict::get $o_rowdefs $r -ansibase]
if {$row_ansibase ne ""} {
set row_bg [punk::ansi::codetype::sgr_merge_singles [list $row_ansibase] -filter_fg 1]
}
set ansibase $body_ansibase$opt_col_ansibase
#todo - joinleft,joinright,joindown based on opts in args
set cell_ansibase ""
@ -2602,7 +2604,7 @@ tcl::namespace::eval textblock {
} else {
set blims $blims_only_headerless
}
if {![tcl::dict::get $o_opts_table -show_edge]} {
if {!$do_show_edge} {
set blims [struct::set difference $blims [tcl::dict::get $::textblock::class::table_edge_parts only$opt_posn] ]
}
} else {
@ -2612,11 +2614,11 @@ tcl::namespace::eval textblock {
} else {
set blims $blims_top_headerless
}
if {![tcl::dict::get $o_opts_table -show_edge]} {
if {!$do_show_edge} {
set blims [struct::set difference $blims [tcl::dict::get $::textblock::class::table_edge_parts top$opt_posn] ]
}
}
set rowframe [textblock::frame -checkargs 0 -type [tcl::dict::get $ftypes body] -width [expr {$colwidth+2}] -blockalign $col_blockalign -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c]
set rowframe [textblock::frame -checkargs 0 -type $ftype_body -width [expr {$colwidth+2}] -blockalign $col_blockalign -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c]
set return_bodywidth [textblock::widthtopline $rowframe] ;#frame lines always same width - just look at top line
append part_body $rowframe \n
} else {
@ -2624,22 +2626,26 @@ tcl::namespace::eval textblock {
set joins [lremove $joins [lsearch $joins down*]]
set bmap $botmap
set blims $blims_bot
if {![tcl::dict::get $o_opts_table -show_edge]} {
if {!$do_show_edge} {
set blims [struct::set difference $blims [tcl::dict::get $::textblock::class::table_edge_parts bottom$opt_posn] ]
}
} else {
set bmap $midmap
set blims $blims_mid ;#will only be reduced from boxlimits if -show_seps was processed above
if {![tcl::dict::get $o_opts_table -show_edge]} {
if {!$do_show_edge} {
set blims [struct::set difference $blims [tcl::dict::get $::textblock::class::table_edge_parts middle$opt_posn] ]
}
}
append part_body [textblock::frame -checkargs 0 -type [tcl::dict::get $ftypes body] -width [expr {$colwidth+2}] -blockalign $col_blockalign -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c]\n
#append part_body [textblock::frame -checkargs 0 -type [tcl::dict::get $ftypes body] -width [expr {$colwidth+2}] -blockalign $col_blockalign -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c]\n
append part_body [textblock::frame -checkargs 0 -type $ftype_body -width [expr {$colwidth+2}] -blockalign $col_blockalign -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c]\n
}
incr r
}
#return empty (zero content height) row if no rows
if {![llength $cells]} {
set basebg [punk::ansi::codetype::sgr_merge_singles [list $body_ansibase] -filter_fg 1]
set ansiborder_final [punk::ansi::codetype::sgr_merge [list $basebg $body_ansiborder]]
set joins [lremove $joins [lsearch $joins down*]]
#we need to know the width of the column to setup the empty cell properly
#even if no header displayed - we should take account of any defined column widths
@ -2661,7 +2667,9 @@ tcl::namespace::eval textblock {
append part_body [tcl::string::repeat " " $colwidth] \n
set return_bodywidth $colwidth
} else {
set emptyframe [textblock::frame -checkargs 0 -width [expr {$colwidth + 2}] -type [tcl::dict::get $ftypes body] -boxlimits $blims -boxmap $onlymap -joins $joins]
#set emptyframe [textblock::frame -checkargs 0 -width [expr {$colwidth + 2}] -type [tcl::dict::get $ftypes body] -boxlimits $blims -boxmap $onlymap -joins $joins]
# -blockalign probably not relevant for an empty row.
set emptyframe [textblock::frame -checkargs 0 -type $ftype_body -width [expr {$colwidth+2}] -ansibase $body_ansibase -ansiborder $ansiborder_final -boxlimits $blims -boxmap $onlymap -joins $joins]
append part_body $emptyframe \n
set return_bodywidth [textblock::width $emptyframe]
}
@ -5741,7 +5749,10 @@ tcl::namespace::eval textblock {
@id -id ::textblock::join_basic
@cmd -name textblock::join_basic -help\
"Join blocks of text line by line but don't add padding on each line to enforce uniform width.
Already uniform blocks will join faster than textblock::join, and ragged blocks will join in a ragged manner
Already uniform blocks will join faster than textblock::join, and ragged blocks will join in a ragged manner.
see also textblock::join_basic_raw - a thin wrapper around split and join for the common case of joining blocks without any options,
and is intended to avoid the overhead of argument parsing.
"
-ansiresets -type any -default auto
-- -type none -optional 0 -help "end of options marker -- is mandatory because joined blocks may easily conflict with flags"
@ -5787,7 +5798,21 @@ tcl::namespace::eval textblock {
}
return [::join $outlines \n]
}
punk::args::define {
@id -id ::textblock::join_basic_raw
@cmd -name textblock::join_basic_raw -help\
"Join blocks of text line by line but don't add padding on each line to enforce uniform width.
Already uniform blocks will join faster than textblock::join, and ragged blocks will join in a ragged manner.
This version is a thin wrapper around split and join for the common case of joining blocks without any options,
and is intended to avoid the overhead of argument parsing.
"
@values
blocks -type any -multiple 1
}
proc ::textblock::join_basic_raw {args} {
#do not use any argument parsing libs - this is intended as a thin wrapper around split and join for the common case of joining blocks without any options,
#and we want to avoid the overhead of argument parsing.
#no options. -*, -- are legimate blocks
set blocklists [lrepeat [llength $args] ""]
set blocklengths [lrepeat [expr {[llength $args]+1}] 0] ;#add 1 to ensure never empty - used only for rowcount max calc

48
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/overtype-1.7.4.tm

@ -461,8 +461,21 @@ tcl::namespace::eval overtype {
if {$underblock eq ""} {
set underlines [lrepeat $renderheight ""]
} else {
set underblock [textblock::join_basic -- $underblock] ;#ensure properly rendered - ansi per-line resets & replays
set underlines [split $underblock \n]
#----
#this splits into lines - only to rejoin - which is inefficient.
#It also has code to handle joining multiple blocks - but we only have one in this case.
#set underblock [textblock::join_basic_raw $underblock];#ensure properly rendered - ansi per-line resets & replays
#set underlines [split $underblock \n]
#----
if {[punk::ansi::ta::detectcode $underblock]} {
#-ansireplays 1 quite expensive e.g ~15us for only 3 short lines on a 2026 threadripper pro
set underlines [punk::lib::linelist -ansireplays 1 $underblock]
} else {
set underlines [split $underblock \n]
}
}
#if {$underblock eq ""} {
# set blank "\x1b\[0m\x1b\[0m"
@ -881,8 +894,9 @@ tcl::namespace::eval overtype {
set cursor_saved_position [tcl::dict::create]
set cursor_saved_attributes ""
} else {
#FUTURE: Handle restore without save case
#Should move to home position and reset ansi SGR when no save data available
#TODO
#?restore without save?
#should move to home position and reset ansi SGR?
#puts stderr "overtype::renderspace cursor_restore without save data available"
}
#If we were inserting prior to hitting the cursor_restore - there could be overflow_right data - generally the overtype functions aren't for inserting - but ansi can enable it
@ -1195,7 +1209,7 @@ tcl::namespace::eval overtype {
wrapmoveforward {
#doesn't seem to be used by fruit.ans testfile
#used by dzds.ans
#FIXED: cursor_forward can move deep into the next line or span multiple lines - handled below
#note that cursor_forward may move deep into the next line - or even span multiple lines !TODO
set c $renderwidth
set r $post_render_row
if {$post_render_col > $renderwidth} {
@ -2571,9 +2585,8 @@ tcl::namespace::eval overtype {
lset overmap 0 "$startpadding[lindex $overmap 0]"
} else {
if {[punk::ansi::ta::detect $overdata]} {
#FUTURE: Optimize for large files with no newlines
#Currently wastefully calling split_codes_single repeatedly on mostly the same data.
#Consider caching or streaming approach for 200K+ input files.
#TODO!! rework this.
#e.g 200K+ input file with no newlines - we are wastefully calling split_codes_single repeatedly on mostly the same data.
#set overmap [punk::ansi::ta::split_codes_single $startpadding$overdata]
set overmap [punk::ansi::ta::split_codes_single $overdata]
lset overmap 0 "$startpadding[lindex $overmap 0]"
@ -2599,9 +2612,9 @@ tcl::namespace::eval overtype {
#???
set colcursor $opt_colstart
#FUTURE: Create a virtual column object for cleaner column tracking
#Currently need to refer to column1 or columnmin/columnmax without calculating offsets due to startcolumn.
#Need to clarify what start column means from ANSI code movement perspective - offset perspective is unclear.
#TODO - make a little virtual column object
#we need to refer to column1 or columnmin? or columnmax without calculating offsets due to to startcolumn
#need to lock-down what start column means from perspective of ANSI codes moving around - the offset perspective is unclear and a mess.
#set re_diacritics {[\u0300-\u036f]+|[\u1ab0-\u1aff]+|[\u1dc0-\u1dff]+|[\u20d0-\u20ff]+|[\ufe20-\ufe2f]+}
@ -3046,9 +3059,10 @@ tcl::namespace::eval overtype {
set instruction overflow_splitchar
break
} elseif {$owidth > 2} {
#FUTURE: Handle wide graphemes and tabs
#Could be tab with length dependent on tabstops/elastic tabstop settings
#? tab?
#TODO!
puts stderr "overtype::renderline long overtext grapheme '[ansistring VIEW -lf 1 -vt 1 $ch]' not handled"
#tab of some length dependent on tabstops/elastic tabstop settings?
}
} elseif {$idx >= $overflow_idx} {
#REVIEW
@ -3393,7 +3407,8 @@ tcl::namespace::eval overtype {
#we've mapped 7 and 8bit escapes to values we can handle as literals in switch statements to take advantange of jump tables.
switch -- $leadernorm {
1006 {
#FUTURE: Implement mouse event handling
#TODO
#
switch -- [tcl::string::index $codenorm end] {
M {
puts stderr "mousedown $codenorm"
@ -3843,7 +3858,7 @@ tcl::namespace::eval overtype {
#(for use with selective erase: DECSED and DECSEL)
set param [tcl::string::range $codenorm 4 end-2]
if {$param eq ""} {set param 0}
#FUTURE: Store DECSCA like SGR in stacks for replay capability
#TODO - store like SGR in stacks - replays?
switch -exact -- $param {
0 - 2 {
#canerase
@ -4423,7 +4438,8 @@ tcl::namespace::eval overtype {
} else {
set sos_content [string range $code 2 end-2] ;#ST is \x1b\\
}
#FUTURE: Return SOS content in useful form to the caller
#return in some useful form to the caller
#TODO!
lappend sos_list [list string $sos_content row $cursor_row column $cursor_column]
puts stderr "overtype::renderline ESCX SOS UNIMPLEMENTED. code [ansistring VIEW -lf 1 -vt 1 -nul 1 $code]"
}

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

@ -341,7 +341,7 @@ namespace eval punk {
#}
#safest? could be a link?
foreach match [glob -nocomplain -dir $dir -tail {*}$lookfor] {
foreach match [glob -nocomplain -dir $dir -tail -- {*}$lookfor] {
set file [file join $dir $match]
if {[file exists $file] && ![file isdirectory $file]} {
#set assoc [extension_open_association [file extension $file]]
@ -6277,21 +6277,55 @@ namespace eval punk {
namespace eval argdoc {
punk::args::define {
@id -id ::punk::path
@cmd -name "punk::path" -help\
"Introspection of the PATH environment variable.
@cmd -name "punk::path"\
-summary\
"Display PATH executable shadowing and conflicts with TCL commands"\
-help\
{Introspection of the PATH environment variable.
This tool will examine executables within each PATH entry and show which binaries
are overshadowed by earlier PATH entries. It can also be used to examine the contents of each PATH entry, and to filter results using glob patterns."
are overshadowed by earlier PATH entries.
It can also be used to examine the contents of each PATH entry, and to filter results using glob patterns.
${[punk::args::helpers::example {
#show all executables in all PATH entries
punk::path
#show all executables in all PATH entries that contain 'Windows' in the path
punk::path -pathglob *Windows*
#show all executables in all PATH entries that contain 'scoop' in the path,
#and filter the executables to show only those that are named dir, ls or start with 'ca'
punk::path -pathglob *scoop* dir ls ca*
#show all executables that conflict with TCL commands starting with 'a' in the current namespace.
punk::path {*}[nscommandlist a*]
#show all executables that conflict with TCL commands resolvable from the current namespace.
punk::path {*}[info commands]
}]}
see also the punk::auto_exec package.
}
@opts
-binglobs -type list -default {*} -help "glob pattern to filter results. Default '*' to include all entries."
-pathglob -type string -default {*} -multiple true -help "Case insensitive glob pattern to filter path entries. Default '*' to include all PATH directories."
@values -min 0 -max -1
glob -type string -default {*} -multiple true -optional 1 -help "Case insensitive glob pattern to filter path entries. Default '*' to include all PATH directories."
binglob -type list -default {*} -multiple true -optional 1 -help "glob pattern to filter results. Default '*' to include all entries."
}
}
variable d_path_info
variable d_bin_info
variable d_index_executables
#there is still a potential conflict regarding auto_execok on windows - which has some cmd.exe builtins as auto-executable
#- but these are not actually executable files on the filesystem - so they won't be found by our path search
#- but they will be found when not masked by a tcl command.
proc path {args} {
variable d_path_info
variable d_bin_info
variable d_index_executables
set is_windows [expr {$::tcl_platform(platform) eq "windows"}]
set argd [punk::args::parse $args withid ::punk::path]
lassign [dict values $argd] leaders opts values received
set binglobs [dict get $opts -binglobs]
set globs [dict get $values glob]
set pathglobs [dict get $opts -pathglob]
set binglobs [dict get $values binglob]
if {$::tcl_platform(platform) eq "windows"} {
set sep ";"
} else {
@ -6299,14 +6333,18 @@ namespace eval punk {
set sep ":"
}
set all_paths [split [string trimright $::env(PATH) $sep] $sep]
set filtered_paths $all_paths
if {[llength $globs]} {
set filtered_paths [list]
foreach p $all_paths {
foreach g $globs {
if {[string match -nocase $g $p]} {
lappend filtered_paths $p
break
if {[llength $pathglobs]} {
if {[lsearch -exact $pathglobs "*"] >= 0} {
#if we have a wildcard glob then the others are irrelevant - we want to match all paths
set matched_paths $all_paths
} else {
set matched_paths [list]
foreach p $all_paths {
foreach pg $pathglobs {
if {[string match -nocase $pg $p]} {
lappend matched_paths $p
break
}
}
}
}
@ -6344,6 +6382,60 @@ namespace eval punk {
#and the actual executable names (with case and extensions as they appear on the filesystem). We will also build a
#dict keyed by path index which contains the list of executables in that path - to make it easy to show which
#executables are overshadowed by which paths.
if {$is_windows} {
#Sometimes PATHEXT includes an entry of just a dot - which means files with no extension are considered executable.
#We need to account for this in our glob pattern.
set pathexts [list]
if {[info exists ::env(PATHEXT)]} {
set env_pathexts [split $::env(PATHEXT) ";"]
#set pathexts [lmap e $env_pathexts {string tolower $e}]
foreach pe $env_pathexts {
if {$pe eq "."} {
continue
}
lappend pathexts [string tolower $pe]
}
} else {
set env_pathexts [list]
#default PATHEXT if not set - according to Microsoft docs
set pathexts [list .com .exe .bat .cmd]
}
foreach bg $binglobs {
set has_pathext 0
foreach pe $pathexts {
if {[string match -nocase "*$pe" $bg]} {
set has_pathext 1
break
}
}
if {!$has_pathext} {
foreach pe $pathexts {
set globext "$bg$pe"
if {$globext ni $binglobs} {
lappend binglobs "$bg$pe"
}
}
}
}
set lc_binglobs [lmap e $binglobs {string tolower $e}]
if {"." in $pathexts} {
foreach bg $binglobs {
set has_pathext 0
foreach pe $pathexts {
if {[string match -nocase "*$pe" $bg]} {
set base [string range $bg 0 [expr {[string length $bg] - [string length $pe] - 1}]]
set has_pathext 1
break
}
}
if {$has_pathext} {
if {[string tolower $base] ni $lc_binglobs} {
lappend binglobs "$base"
}
}
}
}
}
set d_path_info [dict create] ;#key is normalized path (e.g case-insensitive on windows).
set d_bin_info [dict create] ;#key is normalized executable name (e.g case-insensitive on windows, or callable with extensions stripped off).
@ -6355,63 +6447,21 @@ namespace eval punk {
} else {
set pnorm $p
}
if {[string length $pnorm] > 1} {
set lastchar [string index $pnorm end]
if {$lastchar eq "/" || $lastchar eq "\\"} {
set pnorm [string range $pnorm 0 end-1]
}
}
if {![dict exists $d_path_info $pnorm]} {
dict set d_path_info $pnorm [dict create original_paths [list $p] indices [list $path_idx]]
set executables [list]
if {[file isdirectory $p]} {
#get all files that are executable in this path.
#If we don't normalize the path here - then trailing backslashes on windows can cause a problem with the -tail glob returning a leading slash on the executable names.
#also as we don't necessarily normalize the resulting final path with executable - we want the case to be correct.
set pnormglob [file normalize $p]
if {$::tcl_platform(platform) eq "windows"} {
#Sometimes PATHEXT includes an entry of just a dot - which means files with no extension are considered executable.
#We need to account for this in our glob pattern.
set pathexts [list]
if {[info exists ::env(PATHEXT)]} {
set env_pathexts [split $::env(PATHEXT) ";"]
#set pathexts [lmap e $env_pathexts {string tolower $e}]
foreach pe $env_pathexts {
if {$pe eq "."} {
continue
}
lappend pathexts [string tolower $pe]
}
} else {
set env_pathexts [list]
#default PATHEXT if not set - according to Microsoft docs
set pathexts [list .com .exe .bat .cmd]
}
foreach bg $binglobs {
set has_pathext 0
foreach pe $pathexts {
if {[string match -nocase "*$pe" $bg]} {
set has_pathext 1
break
}
}
if {!$has_pathext} {
foreach pe $pathexts {
lappend binglobs "$bg$pe"
}
}
}
set lc_binglobs [lmap e $binglobs {string tolower $e}]
if {"." in $pathexts} {
foreach bg $binglobs {
set has_pathext 0
foreach pe $pathexts {
if {[string match -nocase "*$pe" $bg]} {
set base [string range $bg 0 [expr {[string length $bg] - [string length $pe] - 1}]]
set has_pathext 1
break
}
}
if {$has_pathext} {
if {[string tolower $base] ni $lc_binglobs} {
lappend binglobs "$base"
}
}
}
}
#TCL's glob on windows is case-insensitive, but in some cases return the result with the case as globbed for regardless of the actual case on the filesystem.
#(This seems to occur when the pattern does *not* contain a wildcard and is probably a bug)
@ -6421,34 +6471,51 @@ namespace eval punk {
# but tcl's glob does not respect the case of even the character-class pattern - so this is not a reliable workaround).
#see punk::fglob for a work-in-progress glob implementation which gives us more control over case sensitivity and the case of results on windows.
set globresults [lsort -unique [glob -nocomplain -directory $pnormglob -types {f x} {*}$binglobs]]
#-----------------------
#JJJ
#set globresults [lsort -unique [glob -nocomplain -directory $pnormglob -types {f x} {*}$binglobs]]
#set executables [list]
#foreach e $globresults {
# puts stderr "glob result: $e"
# puts stderr "normalized executable name: [file tail [file normalize [string range $e 0 end]]]]"
# lappend executables [file tail [file normalize $e]]
#}
#-----------------------
#track all executables in the path - even those that don't match the binglobs
#use fglob to get the actual case of the executables on windows - as glob seems to return the case as globbed for rather than the actual case on the filesystem in some cases.
#this doesn't run a full 'file normalize' on the results which affects whether a more efficient internal representation is stored
#fglob with single glob argument should already return a unique list.
set folder_exes [fglob -nocomplain -directory $pnormglob -types {f x} *]
set executables [list]
foreach e $globresults {
puts stderr "glob result: $e"
puts stderr "normalized executable name: [file tail [file normalize [string range $e 0 end]]]]"
lappend executables [file tail [file normalize $e]]
foreach e $folder_exes {
lappend executables [file tail $e]
}
} else {
set executables [lsort -unique [glob -nocomplain -directory $p -types {f x} -tail {*}$binglobs]]
#set executables [lsort -unique [glob -nocomplain -directory $p -types {f x} -tail {*}$binglobs]]
set executables [lsort -unique [glob -nocomplain -directory $p -types {f x} -tail *]]
}
}
dict set d_index_executables $path_idx $executables
foreach exe $executables {
#todo - other case-insensitive platforms/filesystems.
if {$::tcl_platform(platform) eq "windows"} {
set exenorm [string tolower $exe]
set exe_key [string tolower $exe]
} else {
set exenorm $exe
#on case
set exe_key $exe
}
if {![dict exists $d_bin_info $exenorm]} {
dict set d_bin_info $exenorm [dict create path_indices [list $path_idx] paths [list $p] executable_names [list $exe]]
if {![dict exists $d_bin_info $exe_key]} {
dict set d_bin_info $exe_key [dict create path_indices [list $path_idx] paths [list $p] executable_names [list $exe]]
} else {
#dict lappend d_bin_info $exenorm path_indices $path_idx paths $p executable_names $exe
set bindata [dict get $d_bin_info $exenorm]
#dict lappend d_bin_info $exe_key path_indices $path_idx paths $p executable_names $exe
set bindata [dict get $d_bin_info $exe_key]
dict lappend bindata path_indices $path_idx
dict lappend bindata paths $p
dict lappend bindata executable_names $exe
dict set d_bin_info $exenorm $bindata
dict set d_bin_info $exe_key $bindata
}
}
} else {
@ -6467,16 +6534,16 @@ namespace eval punk {
set executables [dict get $d_index_executables [lindex [dict get $d_path_info $pnorm indices] 0]] ;#get executables for this path
foreach exe $executables {
if {$::tcl_platform(platform) eq "windows"} {
set exenorm [string tolower $exe]
set exe_key [string tolower $exe]
} else {
set exenorm $exe
set exe_key $exe
}
#dict lappend d_bin_info $exenorm path_indices $path_idx paths $p executable_names $exe
set bindata [dict get $d_bin_info $exenorm]
#dict lappend d_bin_info $exe_key path_indices $path_idx paths $p executable_names $exe
set bindata [dict get $d_bin_info $exe_key]
dict lappend bindata path_indices $path_idx
dict lappend bindata paths $p
dict lappend bindata executable_names $exe
dict set d_bin_info $exenorm $bindata
dict set d_bin_info $exe_key $bindata
}
}
@ -6484,18 +6551,255 @@ namespace eval punk {
}
#temporary debug output to check dicts are being built correctly
set debug ""
append debug "Path info dict:" \n
append debug [showdict $d_path_info] \n
append debug "Binary info dict:" \n
append debug [showdict $d_bin_info] \n
append debug "Index executables dict:" \n
append debug [showdict $d_index_executables] \n
#return $debug
puts stdout $debug
#set debug ""
#append debug "Path info dict:" \n
#append debug [showdict $d_path_info] \n
#append debug "Binary info dict:" \n
#append debug [showdict $d_bin_info {*}$binglobs] \n
##append debug "Index executables dict:" \n
##append debug [showdict $d_index_executables] \n
##return $debug
#puts stdout $debug
#dict for {p pinfo} $d_path_info {
# set original_paths [dict get $pinfo original_paths]
# set indices [dict get $pinfo indices]
# puts stdout "Path: $p"
# puts stdout " Original paths: $original_paths"
# puts stdout " Indices in PATH: $indices"
# if {[dict exists $d_index_executables [lindex $indices 0]]} {
# set executables [dict get $d_index_executables [lindex $indices 0]]
# puts stdout " Executables: [llength $executables]"
# } else {
# puts stdout " Executables: (not a directory or no executables found)"
# }
#}
set nscaller [uplevel 1 {::tcl::namespace::current}]
set context_commands [namespace eval $nscaller {info commands}]
#process paths in order they appear in the original PATH.
set pidx 0
#use a punk::textblock::table for formatting.
set rows [list]
set headers [list "idx" "Path" "exe\nCount" "Shadow\nCount" "Executables" "TCL context\nConflicts"]
set ERR [punk::ansi::a+ red bold]
set RST [punk::ansi::a]
set STR [punk::ansi::a+ strike]
set SDW [punk::ansi::a+ red strike]
set WRN [punk::ansi::a+ yellow bold]
set subcols 2
foreach p $all_paths {
#if {$p ni $matched_paths} {
# incr pidx
# continue
#}
set thisrow [list $pidx]
set pnorm [string tolower $p]
if {[string length $pnorm] > 1} {
set lastchar [string index $pnorm end]
if {$lastchar eq "/" || $lastchar eq "\\"} {
set pnorm [string range $pnorm 0 end-1]
}
}
set pinfo [dict get $d_path_info $pnorm]
set original_paths [dict get $pinfo original_paths]
set indices [dict get $pinfo indices]
if {[lindex $indices 0] == $pidx} {
#this is the first occurrence of this path in the original PATH.
set overshadowed [list]
set conflicts [list]
lappend thisrow $p
if {[dict exists $d_index_executables $pidx]} {
set executables [dict get $d_index_executables $pidx]
lappend thisrow [llength $executables]
set display_executables [list]
foreach exe $executables {
set matched_binglob 0
foreach bg $binglobs {
#review - -nocase only on case-insensitive platforms/filesystems?
#- but it is simpler to just apply it to all platforms here rather than trying to determine case-sensitivity of each path.
if {[string match -nocase $bg $exe]} {
set matched_binglob 1
continue
}
}
set exe_key [string tolower $exe]
if {[dict exists $d_bin_info $exe_key]} {
set bindata [dict get $d_bin_info $exe_key]
set path_indices [dict get $bindata path_indices]
set is_overshadowed 0
foreach pi $path_indices {
if {$pi < $pidx} {
lappend overshadowed $exe
set is_overshadowed 1
break
}
}
if {$matched_binglob} {
if {$is_windows} {
#check for matches in context_commands - which are case-insensitive on windows
#the context_commands are however case sensitive.
#we want to mark conflicts in one of two ways in the conflicts column.
#- if there is a case-insensitive match but not a case-sensitive match
#- then we have a conflict but not an exact match - so we will mark this with orange style.
#If there is an exact match in context_commands - then we will mark this with the red style
#to indicate that this executable is overshadowed by a command in the current context.
#we may have multiple tcl commands that conflict with the same executable.
#e.g DIG and dig.
if {[llength [set ncmatches [lsearch -all -inline -nocase $context_commands [file rootname $exe]]]]} {
if {[set exactmatch [lsearch -exact $context_commands [file rootname $exe]]] ne ""} {
foreach nc $ncmatches {
set nc [namespace eval $nscaller [list namespace origin $nc]]
if {$nc eq $exactmatch} {
lappend conflicts $ERR$nc$RST
} else {
lappend conflicts "$WRN$nc$RST"
}
}
} else {
foreach nc $ncmatches {
set nc [namespace eval $nscaller [namespace origin $nc]]
lappend conflicts "$WRN$nc$RST"
}
}
} else {
if {[llength [set ncmatches [lsearch -all -inline -nocase $context_commands $exe]]]} {
if {[set exactmatch [lsearch -exact $context_commands $exe]] ne ""} {
foreach nc $ncmatches {
set nc [namespace eval $nscaller [namespace origin $nc]]
if {$nc eq $exactmatch} {
lappend conflicts $ERR$nc$RST
} else {
lappend conflicts "$WRN$nc$RST"
}
}
} else {
foreach nc $ncmatches {
set nc [namespace eval $nscaller [namespace origin $nc]]
lappend conflicts "$WRN$nc$RST"
}
}
}
}
} else {
#check for any exact matches in context_commands
if {$exe in $context_commands} {
lappend conflicts $ERR$exe$RST
}
}
if {$is_overshadowed} {
lappend display_executables "$SDW$exe$RST"
} else {
lappend display_executables $exe
}
}
} else {
#executable not found in bin_info dict - this shouldn't happen - but if it does we will just treat it as not overshadowed and include it in the display.
lappend display_executables $WRN$exe$RST
}
}
if {[llength $overshadowed]} {
lappend thisrow "$ERR[llength $overshadowed]$RST"
} else {
lappend thisrow "0"
}
if {[llength $display_executables]} {
lappend thisrow [textblock::list_as_table -columns $subcols -show_edge 0 $display_executables]
} else {
lappend thisrow ""
}
if {[llength $conflicts]} {
#lappend thisrow [textblock::list_as_table -columns $subcols -show_edge 0 $conflicts]
lappend thisrow [join $conflicts \n]
} else {
lappend thisrow ""
}
} else {
lappend thisrow ""
lappend thisrow ""
lappend thisrow ""
lappend thisrow "(not a directory or no executables found)"
lappend thisrow ""
}
} else {
#this is a duplicate path entry - we want to show it as a duplicate of the original path entry.
set original_path_idx [lindex $indices 0]
set original_path [lindex [dict get $d_path_info $pnorm original_paths] 0]
#duplicate paths might be cased differently.
lappend thisrow "$ERR$p (repeated pathentry)\n original at index $original_path_idx as\n$original_path$RST"
set overshadowed [list]
set conflicts [list]
set display_executables [list]
if {[dict exists $d_index_executables $original_path_idx]} {
set executables [dict get $d_index_executables $original_path_idx]
lappend thisrow [llength $executables]
foreach exe $executables {
set exe_key [string tolower $exe]
if {[dict exists $d_bin_info $exe_key]} {
set bindata [dict get $d_bin_info $exe_key]
set path_indices [dict get $bindata path_indices]
set is_overshadowed 0
foreach pi $path_indices {
if {$pi < $pidx} {
lappend overshadowed $exe
set is_overshadowed 1
break
}
}
#dupe will always have all exes as overshadowed by the original.
#don't need to waste time and screen space to display duplicate info - the user should tidy up the PATH.
#if {$is_overshadowed} {
# lappend display_executables "$SDW$exe$RST"
#} else {
# lappend display_executables $exe
#}
}
}
} else {
#this shouldn't happen - but if it does we will just treat it as not overshadowed and include it in the display.
lappend thisrow "(not a directory or no executables found)"
}
if {[llength $overshadowed]} {
lappend thisrow "$ERR[llength $overshadowed]$RST"
} else {
lappend thisrow "0"
}
if {[llength $display_executables]} {
lappend thisrow [textblock::list_as_table -columns $subcols -show_edge 0 $display_executables]
} else {
lappend thisrow ""
}
lappend thisrow "" ;#don't show conflict info for duplicate paths - as the user should tidy up the PATH to remove duplicates, and the conflict info will be the same as the original path entry.
}
if {[llength $matched_paths] < [llength $all_paths]} {
#if there is any filtering of paths - then we want to show all these paths whether or not there are any matches for binglobs
if {$p in $matched_paths} {
lappend rows $thisrow
}
} else {
#no specific filtering of paths - so only show rows where there are matches for binglobs
if {[lsearch -exact $binglobs "*"] >= 0} {
lappend rows $thisrow
} else {
#end-1 is the executables column.
#if there are no matches for binglobs then we'll hide the row.
if {[string length [lindex $thisrow end-1]] > 0} {
lappend rows $thisrow
}
}
}
incr pidx
}
set t [textblock::table -return tableobject -rows $rows -headers $headers]
return [$t print]
}
#-------------------------------------------------------------------
@ -8024,8 +8328,8 @@ namespace eval punk {
set title "[a+ brightgreen] Filesystem navigation: "
set cmdinfo [list]
lappend cmdinfo [list ./ "?${I}glob${NI}?" "view/change dir, list dirs."]
lappend cmdinfo [list ../ "?${I}path${NI}" "go up one dir, then to path if given"]
lappend cmdinfo [list .// "?${I}glob${NI}?" "view/change dir, list dirs and files"]
lappend cmdinfo [list ../ "?${I}path${NI}" "go up one dir, then to path if given"]
lappend cmdinfo [list newdir "${I}subdir${NI}..." "make new dir or dirs and show status"]
lappend cmdinfo [list fcat "${I}file ?file?...${NI}" "cat file(s)"]
set t [textblock::class::table new -minwidth 80 -show_seps 0]
@ -8238,11 +8542,33 @@ namespace eval punk {
lappend chunks [list stdout $text]
}
console - term - terminal {
set term_env_vars {TERM TERM_PROGRAM TERM_PROGRAM_VERSION}
set term_dict [dict create]
foreach e $term_env_vars {
if {[info exists ::env($e)]} {
dict set term_dict $e [set ::env($e)]
} else {
dict set term_dict $e "(NOT SET)"
}
}
set text "Terminal environment variables:\n"
append text [punk::lib::showdict $term_dict] \n
lappend chunks [list stdout $text]
set text ""
if {[catch {package require punk::console} result]} {
set text "Unable to load punk::console package - cannot test\n$result"
lappend chunks [list stdout $text]
} else {
if {![catch {punk::console::class_info} console_class_info]} {
set text "Terminal class info (from device secondary attributes query to terminal):\n"
append text [punk::lib::showdict $console_class_info] \n
} else {
set text "Unable to query terminal class info - err:$console_class_info\n"
}
lappend chunks [list stdout $text]
set indent [string repeat " " [string length "WARNING: "]]
lappend cstring_tests [dict create\
type "PM "\
@ -8339,7 +8665,7 @@ namespace eval punk {
}
}
if {![string length $warningblock]} {
set text "No terminal warnings\n"
set text "[a+ green]No terminal warnings[a]\n"
lappend chunks [list stdout $text]
}
}
@ -8351,6 +8677,7 @@ namespace eval punk {
"tcl" "Tcl version warnings"\
"env|environment" "punkshell environment vars"\
"console|terminal" "Some console behaviour tests and warnings"\
"*" "Try to find help on the topic as a command or external executable"\
]
set t [textblock::class::table new -show_seps 0]

1
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/aliascore-0.1.0.tm

@ -117,6 +117,7 @@ tcl::namespace::eval punk::aliascore {
plist {::punk::lib::pdict -roottype list}\
showlist {::punk::lib::showdict -roottype list}\
rehash ::punk::auto_exec::rehash\
hash ::punk::auto_exec::hash\
showdict ::punk::lib::showdict\
ansistrip ::punk::ansi::ansistrip\
stripansi ::punk::ansi::ansistrip\

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

@ -3920,7 +3920,10 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
}
lappend PUNKARGS [list {
@id -id ::punk::ansi::a+
@cmd -name "punk::ansi::a+" -help\
@cmd -name "punk::ansi::a+"\
-summary\
"ANSI SGR code generator with no reset prefix"\
-help\
"Returns an ANSI sgr escape sequence based on the list of supplied codes.
Unlike punk::ansi::a - it is not prefixed with an ANSI reset.
"
@ -3935,7 +3938,10 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
lappend PUNKARGS [list {
@id -id ::punk::ansi::a
@cmd -name "punk::ansi::a" -help\
@cmd -name "punk::ansi::a"\
-summary\
"ANSI SGR code generator with reset prefix"\
-help\
"Returns an ANSI sgr escape sequence based on the list of supplied codes.
Unlike punk::ansi::a+ - it is prefixed with an ANSI reset.
"
@ -6865,7 +6871,14 @@ tcl::namespace::eval punk::ansi::ta {
#may be same as detect - kept in case detect needs to diverge
#variable re_ansi_split "${re_csi_code}|${re_esc_osc1}|${re_esc_osc2}|${re_esc_osc3}|${re_standalones}|${re_ST}|${re_g0_open}|${re_g0_close}"
set re_ansi_split $re_ansi_detect
#experiment with const for a regex - seems to make no difference to performance - but it does make it clear that the regex is not intended to be modified at runtime
if {[catch {const re_ansi_split $re_ansi_detect}]} {
#tcl 9 has const but tcl 8 doesn't - so we just set it as a normal variable
variable re_ansi_split
set re_ansi_split $re_ansi_detect
}
variable re_ansi_split_multi
if {[string first (?x) $re_ansi_split] == 0} {
set re_ansi_split_multi "(?x)(?:[string range ${re_ansi_split} 4 end])+"
@ -7161,7 +7174,7 @@ tcl::namespace::eval punk::ansi::ta {
#micro optimisations on split_codes to avoid function calls and make re var local tend to yield very little benefit (sub uS diff on calls that commonly take 10s/100s of uSeconds)
#like split_codes - but each ansi-escape is split out separately (with empty string of plaintext between codes so even/odd indices for plain ansi still holds)
#- the slightly simpler regex than split_codes means that it will be slightly faster than keeping the codes grouped.
#- the regex is slighly simpler than for split_codes - but split_codes is faster when there are consecutive codes.
proc split_codes_single {text} {
if {$text eq ""} {
return {}
@ -7177,7 +7190,26 @@ tcl::namespace::eval punk::ansi::ta {
#set next [lindex $cr 1]+1 ;#text index-expression for string range
}
lappend list [tcl::string::range $text $next end]
return $list
}
proc split_codes_single2 {text} {
return [_perlish_split2 $::punk::ansi::ta::re_ansi_split $text]
}
proc split_codes_single3 {text} {
#no faster
if {$text eq ""} {
return {}
}
variable re_ansi_split
set next 0
set coderanges [regexp -indices -all -inline -- $re_ansi_split $text]
set list [lrepeat [expr {[llength $coderanges]*2}] ""]
set r 0
foreach cr $coderanges {
ledit list $r $r+1 [tcl::string::range $text $next [lindex $cr 0]-1] [tcl::string::range $text [lindex $cr 0] [lindex $cr 1]]
set next [expr {[lindex $cr 1]+1}]
incr r
}
return [list {*}$list [tcl::string::range $text $next end]]
}
proc split_codes_single2 {text} {
variable re_ansi_split
@ -7202,7 +7234,6 @@ tcl::namespace::eval punk::ansi::ta {
set next [expr {[lindex $cr 1]+1}]
}
lappend list [tcl::string::range $text $next end]
return $list
}
proc _perlish_split2 {re text} {
if {$text eq ""} {

259
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/args-0.2.1.tm

@ -771,9 +771,9 @@ tcl::namespace::eval punk::args {
literal(<string>)
(exact match for string)
literalprefix(<string>)
(prefix match for string, other literal and literalprefix
(tcl::prefix::match of string, other literal and literalprefix
entries specified as alternates using | are used in the
calculation)
unique prefix calculation)
stringstartswith(<string>)
(value must match glob <string>*)
The value of string must not contain pipe char '|'
@ -785,7 +785,7 @@ tcl::namespace::eval punk::args {
e.g literalprefix(text)|literalprefix(binary)
(when all in the pipe-delimited type-alternates set are
literal or literalprefix - this is similar to the -choices
option)
option with -choiceprefix true)
and more.. (todo - document here)
@ -906,6 +906,8 @@ tcl::namespace::eval punk::args {
is preserved.
-minsize (type dependant)
-maxsize (type dependant)
-mincap {only valid for regex type - min number of captures}
-maxcap {only valid for regex type - max number of captures}
-range (type dependant - only valid if -type is a single item)
-typeranges (list with same number of elements as -type)
-help <string>
@ -2529,6 +2531,15 @@ tcl::namespace::eval punk::args {
#review -solo 1 vs -type none ? conflicting values?
tcl::dict::set spec_merged $spec $specval
}
-mincap - -maxcap {
#todo - allow as default for @leaders, @opts and @values when default -type there is regex or regexp?
#only applies to type regex
set tp [tcl::dict::get $spec_merged -type]
if {![string match *regex* $tp]} {
error "punk::args::resolve - invalid use of '$spec' key for argument '$argname'. '$spec' only applies to arguments with a type of regex or regexp. argument has type '$tp' @id:$DEF_definition_id"
}
tcl::dict::set spec_merged $spec $specval
}
-range {
#allow simple case to be specified without additional list wrapping
#only multi-types require full list specification
@ -2624,7 +2635,9 @@ tcl::namespace::eval punk::args {
-range -typeranges\
-default -defaultdisplaytype -typedefaults\
-minsize -maxsize -choices -choicegroups\
-mincap -maxcap\
-choicemultiple -choicecolumns -choiceprefix -choiceprefixdenylist -choiceprefixreservelist -choicerestricted\
-choicelabels -choiceinfo \
-unindentedfields\
-nocase -optional -multiple -validate_ansistripped -allow_ansi -strip_ansi -help\
-multipleunique -choicemultipleunique -choicemultipleuniqueset\
@ -3816,7 +3829,8 @@ tcl::namespace::eval punk::args {
set arg_error_CLR_info(check) [a+ brightgreen bold]
set arg_error_CLR_info(choiceprefix) [a+ brightgreen bold]
set arg_error_CLR_info(groupname) [a+ cyan bold]
set arg_error_CLR_info(ansiborder) [a+ brightcyan bold]
#set arg_error_CLR_info(ansiborder) [a+ brightcyan bold]
set arg_error_CLR_info(ansiborder) [a+ term-grey23 bold]
set arg_error_CLR_info(ansibase_header) [a+ cyan]
set arg_error_CLR_info(ansibase_body) [a+ white]
variable arg_error_CLR_error
@ -5236,13 +5250,13 @@ tcl::namespace::eval punk::args {
switch -- $tailtype {
withid {
#JJJ
#set id [lindex $opts_and_vals 0]
set deflist [raw_def [lindex $opts_and_vals 0]]
if {[llength $deflist] == 0} {
if {[llength $opts_and_vals] != 1} {
#error "punk::args::parse - invalid call. Expected exactly one argument after 'withid'"
punk::args::parse $args withid ::punk::args::parse
}
set id [lindex $opts_and_vals 0]
error "punk::args::parse - no such id: $id"
}
}
@ -5415,7 +5429,8 @@ tcl::namespace::eval punk::args {
}
#return number of values we can assign to cater for variable length clauses such as {"elseif" expr "?then?" body}
#return number of values we can assign to cater for variable length clauses such as:
# {"elseif" expr "?then?" body}
#review - efficiency? each time we call this - we are looking ahead at the same info
proc _get_dict_can_assign_value {idx values nameidx names namesreceived formdict} {
set ARG_INFO [dict get $formdict ARG_INFO]
@ -5426,12 +5441,23 @@ tcl::namespace::eval punk::args {
#todo - work backwards with any (optional or not) literals at tail that match our values - and remove from assignability.
set ridx 0
#puts "-=============- thisname:'$thisname' thistype:'$thistype' tailnames:'$tailnames' all_remaining:'$all_remaining' [info level -2]"
foreach clausename [lreverse $tailnames] {
#puts "=============== clausename:$clausename all_remaining: $all_remaining"
#puts "=============== thisname:'$thisname' thistype:'$thistype' clausename:'$clausename' all_remaining:'$all_remaining'"
set clause_is_multiple [dict get $ARG_INFO $clausename -multiple]
set clause_is_optional [dict get $ARG_INFO $clausename -optional]
set typelist [dict get $ARG_INFO $clausename -type]
#---------------
#review - not quite right to look for literal* in typelist
#- we should be looking for any type-alternate that starts with literal( or literalprefix(
#- but for now we require the whole type to be literal* if it's a literal match type.
# We should probably also support stringstartswith(*) and stringendswith(*) too.
#also consider that -choices {abc def} is effectively a literal match type too - we should support that here as well.
if {[lsearch $typelist literal*] == -1} {
break
}
#---------------
set max_clause_length [llength $typelist]
if {$max_clause_length == 1} {
#basic case
@ -5452,28 +5478,50 @@ tcl::namespace::eval punk::args {
}
#foreach tp_alternative [split $tp |] {}
foreach tp_alternative [_split_type_expression $tp] {
set tp_alternatives [_split_type_expression $tp]
foreach tp_alternative $tp_alternatives {
switch -exact -- [lindex $tp_alternative 0] {
literal {
set litinfo [string range $tp 7 end] ;#get bracketed part if of form literal(xxx)
set match [lindex $tp_alternative 1]
set match [lindex $tp_alternative 1] ;#was bracketed part if of form literal(xxx)
if {$v eq $match} {
set alloc_ok 1
ledit all_remaining end end
if {![dict get $ARG_INFO $clausename -multiple]} {
if {!$clause_is_multiple} {
ledit tailnames end end
}
#the type (or one of the possible type alternates) matched a literal
break
}
}
literalprefix {
set prefix_of [lindex $tp_alternative 1]
#get list of literal and literalprefix values in the current list of tp_alternatives so we can construct list of alternatives for tcl::prefix::match prefix calculation.
#todo - consider if this clause also has -choices {abc def} - we should support those as well here as literal matches for the purposes of calculating the prefix match.
# (this is somewhat of an edge case but sometimes it's useful to specify a -type when -choices is used with -choicerestricted false, to allow only specific values not in the choices list.)
set comparelist [list]
foreach alt $tp_alternatives {
switch -exact -- [lindex $alt 0] {
literal - literalprefix {
lappend comparelist [lindex $alt 1]
}
}
}
set fullmatch [tcl::prefix::match -error "" $comparelist $v]
if {$fullmatch eq $prefix_of} {
set alloc_ok 1
ledit all_remaining end end
if {!$clause_is_multiple} {
ledit tailnames end end
}
break
}
}
stringstartswith {
set pfx [lindex $tp_alternative 1]
if {[string match "$pfx*" $v]} {
set alloc_ok 1
set alloc_ok 1
ledit all_remaining end end
if {![dict get $ARG_INFO $clausename -multiple]} {
if {!$clause_is_multiple} {
ledit tailnames end end
}
break
@ -5483,10 +5531,9 @@ tcl::namespace::eval punk::args {
stringendswith {
set sfx [lindex $tp_alternative 1]
if {[string match "*$sfx" $v]} {
set alloc_ok 1
set alloc_ok 1
ledit all_remaining end end
if {![dict get $ARG_INFO $clausename -multiple]} {
if {!$clause_is_multiple} {
ledit tailnames end end
}
break
@ -5497,7 +5544,7 @@ tcl::namespace::eval punk::args {
}
}
if {!$alloc_ok} {
if {![dict get $ARG_INFO $clausename -optional]} {
if {!$clause_is_optional} {
break
}
}
@ -5519,6 +5566,7 @@ tcl::namespace::eval punk::args {
set reverse_type_index 0
#todo handle type-alternates
# for example: -type {string literal(x)|literal(y)}
# -type {string literal(max)|literal(min)|int}
foreach tp $rtypelist {
#set rv [lindex $rcvals end-$alloc_count]
set rv [lindex $all_remaining end-$alloc_count]
@ -5528,8 +5576,24 @@ tcl::namespace::eval punk::args {
set clause_member_optional 0
}
set tp [string trim $tp ?]
puts "_get_dict_can_assign_value: checking tp '$tp' against value '$rv'"
switch -glob -- $tp {
literal* {
"literal(*" {
set litmatch [string range $tp 8 end-1]
if {$rv eq $litmatch} {
set alloc_ok 1 ;#we need at least one literal-match to set alloc_ok
incr alloc_count
} else {
if {$clause_member_optional} {
#
} else {
set alloc_ok 0
break
}
}
}
XXXliteral* {
#JJJ
set litinfo [string range $tp 7 end]
set match [string range $litinfo 1 end-1]
#todo -literalprefix
@ -5594,7 +5658,7 @@ tcl::namespace::eval punk::args {
#set all_remaining [lrange $all_remaining end-$n end]
set all_remaining [lrange $all_remaining 0 end-$alloc_count]
#don't lpop if -multiple true
if {![dict get $ARG_INFO $clausename -multiple]} {
if {!$clause_is_multiple} {
#lpop tailnames
ledit tailnames end end
}
@ -6421,13 +6485,48 @@ tcl::namespace::eval punk::args {
break
}
regex - regexp {
#todo - allow -min and -max to specify number of allowed subexpressions(capture groups) present in regex?
if {[catch {regexp -about $e_check} re_about_msg]} {
set msg "$argclass $argname for %caller% requires type regexp. $re_about_msg. Received: '$e_check'"
lset clause_results $c_idx $a_idx [list errorcode [list PUNKARGS VALIDATION [list typemismatch $type] -badarg $argname -argspecs $argspecs] msg $msg]
} else {
lset clause_results $c_idx $a_idx 1
break
#optional -mincap and -maxcap specify number of allowed subexpressions(capture groups) present in regex
set num_caps [lindex $re_about_msg 0]
set mincap 0 ;#default
set maxcap -1 ;#default -1 for unlimited
if {[dict exists $thisarg_checks -mincap]} {
set mincap [dict get $thisarg_checks -mincap]
}
if {[dict exists $thisarg_checks -maxcap]} {
set maxcap [dict get $thisarg_checks -maxcap]
}
if {$maxcap == -1 && $mincap == 0} {
#no cap limits - just accept the regex as valid
lset clause_results $c_idx $a_idx 1
break
} else {
#we have at least one cap limit - we need to count the number of subexpressions in the regex and check it against the limits
if {$maxcap == -1} {
#unlimited maxcap - just check mincap
if {$num_caps < $mincap} {
set msg "$argclass $argname for %caller% requires type regexp with at least $mincap capture groups. Received regex has only $num_caps capture groups. Regex: '$e_check'"
lset clause_results $c_idx $a_idx [list errorcode [list PUNKARGS VALIDATION [list typemismatch $type] -badarg $argname -argspecs $argspecs] msg $msg]
} else {
lset clause_results $c_idx $a_idx 1
break
}
} else {
if {$num_caps < $mincap} {
set msg "$argclass $argname for %caller% requires type regexp with at least $mincap capture groups. Received regex has only $num_caps capture groups. Regex: '$e_check'"
lset clause_results $c_idx $a_idx [list errorcode [list PUNKARGS VALIDATION [list typemismatch $type] -badarg $argname -argspecs $argspecs] msg $msg]
} elseif {$num_caps > $maxcap} {
set msg "$argclass $argname for %caller% requires type regexp with no more than $maxcap capture groups. Received regex has $num_caps capture groups. Regex: '$e_check'"
lset clause_results $c_idx $a_idx [list errorcode [list PUNKARGS VALIDATION [list typemismatch $type] -badarg $argname -argspecs $argspecs] msg $msg]
} else {
lset clause_results $c_idx $a_idx 1
break
}
}
} ;#every leaf of this nested if should have an lset clause_results with 1 for pass or errorcode/msg for fail
}
}
indexexpression {
@ -6776,10 +6875,30 @@ tcl::namespace::eval punk::args {
break
}
}
path -
file -
directory -
directory {
#see comments in existingpath/existingfile/existingdirectory case about the challenges of validating filesystem paths in a general way that works across platforms and use cases.
if {[tcl::string::length $e_check]==0 || [string first \0 $e_check] >= 0} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which doesn't look like it could be a path, file or directory"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
}
lset clause_results $c_idx $a_idx 1
}
existingpath -
existingfile -
existingdirectory {
#do we need types for relative vs absolute paths? readable writable executable owned?
#on windows limit to certain file extensions?
#fileutil::magic::filetype?
#Perhaps these are steps too far for a general validation framework.
#consider - callback validation functions instead?
#ideally we want to define callback validation functions that can work not just on a single argument at a time.
#e.g for testing that 2 file arguments do or don't refer to the same file or are in same directory or same filesystem etc.
#we have to support file and directory names on all platforms - and even characters illegal on a filesystem/platform may need to be passed.
#For example a file/folder may be created with an illegal name on a platform (or mounted on it) and be mapped to another string on the filesystem
#- yet it may remain accessible to commands such as file stat etc via the string with 'illegal' characters as well as its underlying stored (mapped) name.
@ -6790,17 +6909,43 @@ tcl::namespace::eval punk::args {
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
}
if {$type eq "existingfile"} {
if {![file exists $e_check]} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which is not an existing file"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
# -------------------------------------------------------
#review - what do we want to happen with links?
#on unix TCL's file readlink should reliably give us a path to determine the type pointed to.
#on windows we can do so if the link happens to be a junction.
#however on windows we can also have symbolic links which are not junctions and which may point to files or directories
#- but unfortunately tcl's file readlink doesn't seem to be able to read them at all - raises an error.
#(the error seems to be different for a file vs a directory target - but this seems an unreliable mechanism to determine the type of the target)
#At the moment TCL's 'file isfile' and 'file isdirectory' both seem to do the right things for links
#despite the above - treating them as the type of their target
# review whether this is reliable in all cases on windows.
# -------------------------------------------------------
#windows shortcuts (.lnk files) can point to a file or directory - but we can quite reasonably treat them only as files,
#as users *probably* won't have the expectation that a shortcut which points to a directory should be treated as a directory.
switch -exact -- $type {
existingpath {
if {![file exists $e_check]} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which is not an existing path"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
}
}
} elseif {$type eq "existingdirectory"} {
if {![file isdirectory $e_check]} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which is not an existing directory"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
existingfile {
if {![file isfile $e_check]} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which is not an existing file"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
}
}
existingdirectory {
if {![file isdirectory $e_check]} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which is not an existing directory"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
}
}
}
lset clause_results $c_idx $a_idx 1
@ -6809,6 +6954,11 @@ tcl::namespace::eval punk::args {
existingportabledirectory -
portablefile -
portabledirectory {
#review - many absolute paths are not strictly portable when considered as a whole e.g /usr/local/bin c:/test
#- but the idea was more about the directory and file name components being portable excluding the first component.
#this concept may need work as it's unintuitive what it means to be a portable file/directory vs not.
#what about windows specific paths such as //?/ //./ or UNC paths?
if {[tcl::string::length $e_check]==0 || [string first \0 $e_check] >= 0 || [punk::winpath::illegalname_test $e_check]} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which doesn't look like it could be a portable file or directory (must pass punk::winpath::illegalname_test)"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
@ -8699,7 +8849,7 @@ tcl::namespace::eval punk::args {
set leadername [lindex $LEADER_NAMES $nameidx]
set ldr [lindex $leaders $ldridx]
if {$leadername ne ""} {
set leadertypelist [tcl::dict::get $argstate $leadername -type]
set leadertypelist [tcl::dict::get $argstate $leadername -type] ;#often a single type, but can be a list of types (possibly with some optional) for a type that is a clause accepting multiple values.
set leader_clause_size [llength $leadertypelist]
set assign_d [_get_dict_can_assign_value $ldridx $leaders $nameidx $LEADER_NAMES $leadernames_received $formdict]
@ -8738,11 +8888,23 @@ tcl::namespace::eval punk::args {
set clauseval $resultlist
incr ldridx [expr {$consumed - 1}]
#not quite right.. this sets the -type for all clauses - but they should run independently
#e.g if expr {} elseif 2 {script2} elseif 3 then {script3} (where elseif clause defined as "literal(elseif) expr ?literal(then)? script")
#the elseif 2 {script2} will raise an error because the newtypelist from elseif 3 then {script3} overwrote the newtypelist where then was given the type ?omitted-...?
#not quite right.. this modifies the -type for all clauses with this name - but for -multiple true each instance should really be considered separately.
#e.g when a subelement-containing clause is allowed to appear multiple times (-multiple true)
# - we may hava a situation where the supplied arguments do and don't omit optional subelements,
# and the newtypelist from one clause may overwrite the newtypelist from the other clause where the optional subelement was omitted in one arg, but not in the other arg.
# - if expr {} elseif 2 {script2} elseif 3 then {script3}
# - (where elseif clause defined as "literal(elseif) expr ?literal(then)? script")
# The elseif 2 {script2} will reassign the type as "literal(elseif) expr ?omitted-literal(then)? script"
# when the elseif 3 then {script3} is processed, 'then' is now considered against the type ?ommitted-literal(then)?
#which (as a non-recognised type is therefore not validated ) will then
# allow any value instead of 'then' to pass.
tcl::dict::set argstate $leadername -type $newtypelist ;#(possible ?omitted-<type>? and ?defaulted-<type>? entries
#see argument_clause_typestate in value processing loop below for more handling of this issue regarding -multiple true clauses with optional subelements
#todo - synchronize with value processing loop below
#- consider refactor to a common procedure for handling this issue of tracking updated typelist state for optional subelements in -multiple true clauses
#incorrect -don't update default -type info.
#tcl::dict::set argstate $leadername -type $newtypelist ;#(possible ?omitted-<type>? and ?defaulted-<type>? entries
}
if {[tcl::dict::get $argstate $leadername -multiple]} {
@ -8904,7 +9066,7 @@ tcl::namespace::eval punk::args {
}
#incorrect - we shouldn't update the default. see argument_clause_typestate dict of lists of -type
tcl::dict::set argstate $valname -type $newtypelist ;#(possible ?omitted-<type>? and ?defaulted-<type>? entries
#tcl::dict::set argstate $valname -type $newtypelist ;#(possible ?omitted-<type>? and ?defaulted-<type>? and ?validated-<type>? entries
}
if {[tcl::dict::get $argstate $valname -multiple]} {
@ -9206,7 +9368,7 @@ tcl::namespace::eval punk::args {
}
set vlist_typelist [list]
if {[dict exists $argument_clause_typestate $argname]} {
#lookup saved newtypelist (argument_clause_typelist) from can_assign_value result where some optionals were given type ?omitted-<tp>? or ?defaulted-<tp>?
#lookup saved newtypelist (argument_clause_typelist) from can_assign_value result where some optionals were given type ?omitted-<tp>? or ?defaulted-<tp>? or ?validated-<tp>?.
# args.test: parse_withdef_value_clause_missing_optional_multiple
set vlist_typelist [dict get $argument_clause_typestate $argname]
} else {
@ -9315,11 +9477,12 @@ tcl::namespace::eval punk::args {
#fast fail on the wrong number of choices
if {[llength $c_list] < $choicemultiple_min} {
set msg "$argclass $argname for %caller% requires at least $choicemultiple_min choices. Received [llength $c_list] choices."
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list choicecount [llength $c_list] minchoices $choicemultiple_min maxchoices $choicemultiple_max] -badarg $argname]] $msg
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list choicecount [llength $c_list] minchoices $choicemultiple_min maxchoices $choicemultiple_max] -badarg $argname -argspecs $argspecs]] $msg
#return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list optionmissing $full_missing received $flagsreceived] -argspecs $argspecs]] $msg
}
if {$choicemultiple_max != -1 && [llength $c_list] > $choicemultiple_max} {
set msg "$argclass $argname for %caller% requires at most $choicemultiple_max choices. Received [llength $c_list] choices."
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list choicecount [llength $c_list] minchoices $choicemultiple_min maxchoices $choicemultiple_max] -badarg $argname]] $msg
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list choicecount [llength $c_list] minchoices $choicemultiple_min maxchoices $choicemultiple_max] -badarg $argname -argspecs $argspecs]] $msg
}
#-----------------------------------
@ -9435,7 +9598,19 @@ tcl::namespace::eval punk::args {
}
tcl::dict::set $dname $argname_or_ident $existing
} else {
lset existing $element_index $choice_idx $chosen
#test required.
# punk::args::parse {{read write w}} withdef @values {mode -type list -choices {read write} -choicemultiple {1 -1}}
#puts ">>> clause_size $clause_size"
#puts ">>> existing $existing"
#puts ">>> lset existing $element_index $choice_idx $chosen"
if {$clause_size == 1} {
#e.g -type list
#we have multiple choices allowed for a single element clause because that clause type is a list.
lset existing $choice_idx $chosen
} else {
#e.g -type {any any}
lset existing $element_index $choice_idx $chosen
}
tcl::dict::set $dname $argname_or_ident $existing
}
}

438
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/args/moduledoc/tclcore-0.1.0.tm

@ -102,7 +102,8 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
set manbase_tcl "https://tcl.tk/man/tcl/TclCmd"
set manbase_ext .htm
} else {
set manbase_tcl "https://tcl.tk/man/tcl9.0/TclCmd"
set tclv [info tclversion] ;#e.g 9.0 9.1
set manbase_tcl "https://tcl.tk/man/tcl${tclv}/TclCmd"
set manbase_ext .html
}
proc manpage_tcl {cmd} {
@ -1468,7 +1469,10 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
namespace eval argdoc {
lappend PUNKARGS [list {
@id -id ::tcl::chan::blocked
@cmd -name "Built-in: tcl::chan::blocked" -help\
@cmd -name "Built-in: tcl::chan::blocked"\
-summary\
"Test whether the last input operation failed because it would have blocked."\
-help\
"This tests whether the last input operation on the channel called ${$I}channel${$NI}
failed because it would otherwise have caused the process to block, and returns 1
if that was the case. It returns 0 otherwise. Note that this only ever returns 1
@ -1481,15 +1485,19 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
lappend PUNKARGS [list {
@id -id ::tcl::chan::close
@cmd -name "Built-in: tcl::chan::close" -help\
@cmd -name "Built-in: tcl::chan::close"\
-summary\
"Close and destroy a channel."\
-help\
"Close and destroy the channel called channel. Note that this deletes all existing file-events
registered on the channel. If the direction argument (which must be read or write or any
registered on the channel. If the direction argument (which must be ${$B}read${$N} or ${$B}write${$N} or any
unique abbreviation of them) is present, the channel will only be half-closed, so that it can
go from being read-write to write-only or read-only respectively. If a read-only channel is
closed for reading, it is the same as if the channel is fully closed, and respectively similar
for write-only channels. Without the direction argument, the channel is closed for both reading
and writing (but only if those directions are currently open). It is an error to close a
read-only channel for writing, or a write-only channel for reading.
As part of closing the channel, all buffered output is flushed to the channel's output device
(only if the channel is ceasing to be writable), any buffered input is discarded (only if the
channel is ceasing to be readable), the underlying operating system resource is closed and
@ -1540,6 +1548,7 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
{Query/set channel configuration options}\
-help\
{Query or set the configuration options of the channel named ${$I}channel${$NI}
If no ${$I}optionName${$NI} or ${$I}value${$NI} arguments are supplied, the
command returns a list containing alternating option names and values for the
channel. If ${$I}optionName${$NI} is supplied but no ${$I}value${$NI} then the
@ -1809,6 +1818,57 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
}
}]
lappend PUNKARGS [list {
@id -id ::tcl::chan::create
@cmd -name "Built-in: tcl::chan::create"\
-summary\
"Create new script level channel."\
-help\
"This subcommand creates a new script level channel using the command prefix ${$I}cmdPrefix${$NI} as its handler.
Any such channel is called a ${$B}reflected${$N} channel. The specified command prefix, ${$I}cmdPrefix${$NI}, must be a non-empty list,
and should provide the API described in the ${$B}refchan${$N} manual page. The handle of the new channel is returned as the
result of the ${$B}chan create${$N} command, and the channel is open. Use either ${$B}close${$N} or ${$B}chan close${$N} to remove the channel.
The argument mode specifies if the new channel is opened for reading, writing, or both. It has to be a list
containing any of the strings “read” or “write”, The list must have at least one element, as a channel you can
neither write to nor read from makes no sense. The handler command for the new channel must support the chosen mode,
or an error is thrown.
The command prefix is executed in the global namespace, at the top of call stack, following the appending of arguments
as described in the ${$B}refchan${$N} manual page. Command resolution happens at the time of the call. Renaming the command, or
destroying it means that the next call of a handler method may fail, causing the channel command invoking the handler
to fail as well. Depending on the subcommand being invoked, the error message may not be able to explain the reason
for that failure.
Every channel created with this subcommand knows which interpreter it was created in, and only ever executes its
handler command in that interpreter, even if the channel was shared with and/or was moved into a different interpreter.
Each reflected channel also knows the thread it was created in, and executes its handler command only in that thread,
even if the channel was moved into a different thread. To this end all invocations of the handler are forwarded to the
original thread by posting special events to it. This means that the original thread (i.e. the thread that executed the
${$B}chan create${$N} command) must have an active event loop, i.e. it must be able to process such events. Otherwise the thread
sending them will block indefinitely. Deadlock may occur.
Note that this permits the creation of a channel whose two endpoints live in two different threads, providing a
stream-oriented bridge between these threads. In other words, we can provide a way for regular stream communication
between threads instead of having to send commands.
When a thread or interpreter is deleted, all channels created with this subcommand and using this thread/interpreter as
their computing base are deleted as well, in all interpreters they have been shared with or moved into, and in whatever
thread they have been transferred to. While this pulls the rug out under the other thread(s) and/or interpreter(s),
this cannot be avoided. Trying to use such a channel will cause the generation of a regular error about unknown channel
handles.
This subcommand is ${$B}safe${$N} and made accessible to safe interpreters. While it arranges for the execution of arbitrary Tcl
code the system also makes sure that the code is always executed within the safe interpreter."
@values -min 2 -max 2
#man page says must be at least one element in mode list.
#man page doesn't limit list to 2 elements long despite there being only 2 mode values
# - suggests things such as {r write read w ...} without limit on length is allowed
mode -type list -choices {read write} -choicemultiple {1 -1} -help\
"list of at least one of read write or abbreviations of these"
cmdprefix -type string
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::eof
@cmd -name "Built-in: tcl::chan::eof"\
@ -1823,7 +1883,57 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
""
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
#event
lappend PUNKARGS [list {
@id -id ::tcl::chan::event
@cmd -name "Built-in: tcl::chan::event"\
-summary\
"Create, delete or query a file event handler."\
-help\
"Arrange for the Tcl script script to be installed as a file event handler to be called whenever the channel
called channel enters the state described by event (which must be either readable or writable); only one such
handler may be installed per event per channel at a time. If script is the empty string, the current handler
is deleted (this also happens if the channel is closed or the interpreter deleted). If script is omitted, the
currently installed script is returned (or an empty string if no such handler is installed). The callback is
only performed if the event loop is being serviced (e.g. via vwait or update).
A file event handler is a binding between a channel and a script, such that the script is evaluated whenever
the channel becomes readable or writable. File event handlers are most commonly used to allow data to be
received from another process on an event-driven basis, so that the receiver can continue to interact with the
user or with other channels while waiting for the data to arrive. If an application invokes ${$B}chan gets${$N} or
${$B}chan read${$N} on a blocking channel when there is no input data available, the process will block; until the input
data arrives, it will not be able to service other events, so it will appear to the user to “freeze up”.
With ${$B}chan event${$N}, the process can tell when data is present and only invoke ${$B}chan gets${$N} or ${$B}chan read${$N} when they
will not block.
A channel is considered to be readable if there is unread data available on the underlying device. A channel is
also considered to be readable if there is unread data in an input buffer, except in the special case where the
most recent attempt to read from the channel was a ${$B}chan gets${$N} call that could not find a complete line in the
input buffer. This feature allows a file to be read a line at a time in non-blocking mode using events.
A channel is also considered to be readable if an end of file or error condition is present on the underlying
file or device. It is important for script to check for these conditions and handle them appropriately;
for example, if there is no special check for end of file, an infinite loop may occur where script reads no
data, returns, and is immediately invoked again.
A channel is considered to be writable if at least one byte of data can be written to the underlying file or
device without blocking, or if an error condition is present on the underlying file or device. Note that client
sockets opened in asynchronous mode become writable when they become connected or if the connection fails.
Event-driven I/O works best for channels that have been placed into non-blocking mode with the chan configure
command. In blocking mode, a ${$B}chan puts${$N} command may block if you give it more data than the underlying file or
device can accept, and a ${$B}chan gets${$N} or ${$B}chan read${$N} command will block if you attempt to read more data than is
ready; no events will be processed while the commands block. In non-blocking mode ${$B}chan puts${$N}, ${$B}chan read${$N}, and
${$B}chan gets${$N} never block.
The script for a file event is executed at global level (outside the context of any Tcl procedure) in the
interpreter in which the chan event command was invoked. If an error occurs while executing the script then the
command registered with interp bgerror is used to report the error. In addition, the file event handler is
deleted if it ever returns an error; this is done in order to prevent infinite loops due to buggy handlers."
@values -min 2 -max 3
channel
event -choices {readable writable}
script -optional 1
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::flush
@cmd -name "Built-in: tcl::chan::flush"\
@ -1878,9 +1988,58 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
channel
varName -optional 1
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
#isbinary
#names
#pending
lappend PUNKARGS [list {
@id -id ::tcl::chan::isbinary
@cmd -name "Built-in: tcl::chan::isbinary"\
-summary\
"Test if channel is binary (encoding iso8859-1, eofchar {}, translation lf)."\
-help\
"Test whether the channel called ${$I}channel${$NI} is a binary channel, returning 1 if it is and, and 0 otherwise.
A binary channel is a channel with iso8859-1 encoding, -eofchar set to {} and -translation set to lf."
@values -min 1 -max 1
channel
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
#chan names - deviation from online manual to add point about channel names and transformations
lappend PUNKARGS [list {
@id -id ::tcl::chan::names
@cmd -name "Built-in: tcl::chan::names"\
-summary\
"List all channel names. (toplevel)"\
-help\
{Produces a list of all channel names (*).
If pattern is specified, only those channel names that match it (according to the rules of string match)
will be returned.
* Note that the channel names returned are not necessarily the same as the channel names that are visible
in a given interpreter.
For example, if channel transformations are in use on stdin, stdout, or stderr, the channel names returned
will different for those channels.
e.g you may still be able to call ${$B}puts stdout "hello"${$N} even though ${$B}chan names${$N} does not return 'stdout'
It may instead show in the result list as something like 'file17f99e788b0'.
See the documentation for chan push for more details on this.}
@values -min 0 -max 1
pattern -optional 1 -default "*"
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::pending
@cmd -name "Built-in: tcl::chan::pending"\
-summary\
"Number of pending bytes buffered."\
-help\
"Depending on whether mode is input or output, returns the number of bytes of input or output (respectively)
currently buffered internally for channel (especially useful in a readable event callback to impose
application-specific limits on input line lengths to avoid a potential denial-of-service attack where a
hostile user crafts an extremely long line that exceeds the available memory to buffer it). Returns -1 if
the channel was not opened for the mode in question."
@values -min 2 -max 2
mode -choices {input output}
channel -type string
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::pipe
@cmd -name "Built-in: tcl::chan::pipe"\
@ -1921,6 +2080,24 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
channel -type string
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::push
@cmd -name "Built-in: tcl::chan::push"\
-summary\
"Add a new transformation on top of channel."\
-help\
"Adds a new transformation on top of the channel ${$I}channel${$NI}.
The ${$I}cmdPrefix${$NI} argument describes a list of one or more words which represent a handler
that will be used to implement the transformation. The command prefix must provide the
API described in the ${$B}transchan${$N} manual page. The result of this subcommand is a handle to
the transformation. Note that it is important to make sure that the transformation is
capable of supporting the channel mode that it is used with or this can make the channel
neither readable nor writable."
@values -min 2 -max 2
channel -type string
cmdPrefix -type string
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::puts
@cmd -name "Built-in: tcl::chan::puts"\
@ -2262,7 +2439,9 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
is equivalent to a false result. The key/value pairs
are tested in the order in which the keys were inserted
into the dictionary."
vars -type list -minsize 2 -maxsize 2 -typesynopsis {{keyVariable valueVariable}} -optional 0
vars -type list -minsize 2 -maxsize 2 -typesynopsis {{keyVariable valueVariable}} -optional 0 -help\
"Two element list of variable names to be used for the
key and value respectively"
script -type script
@form -form value
@ -2421,7 +2600,10 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
lappend PUNKARGS [list {
@id -id ::tcl::dict::map
@cmd -name "Built-in: tcl::dict::map" -help\
@cmd -name "Built-in: tcl::dict::map"\
-summary\
"Apply a transformation to each value of a dictionary, returning a new dictionary."\
-help\
"This command applies a transformation to each element of a dictionary,
returning a new dictionary. It takes three arguments: the first is a
two-element list of variable names (for the key and value respectively of
@ -2919,6 +3101,7 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
name -type string
} "@doc -name Manpage: -url [manpage_tcl file]" ]
#tcl 9+
lappend PUNKARGS [list {
@id -id ::tcl::file::home
@cmd -name "Built-in: tcl::file::home" -help\
@ -2952,6 +3135,48 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
#join
#link
lappend PUNKARGS [list {
@id -id ::tcl::file::link
@cmd -name "Built-in: tcl::file::link"\
-summary\
"Create a link or return the value of a link."\
-help\
"If only one argument is given, that argument is assumed to be linkName, and this command returns the value
of the link given by linkName (i.e. the name of the file it points to). If linkName is not a link or its
value cannot be read (as, for example, seems to be the case with hard links, which look just like ordinary
files), then an error is returned.
If 2 arguments are given, then these are assumed to be linkName and target. If linkName already exists, or
if target does not exist, an error will be returned. Otherwise, Tcl creates a new link called linkName which
points to the existing filesystem object at target (which is also the returned value), where the type of the
link is platform-specific (on Unix a symbolic link will be the default). This is useful for the case where
the user wishes to create a link in a cross-platform way, and does not care what type of link is created.
If the user wishes to make a link of a specific type only, (and signal an error if for some reason that is
not possible), then the optional -linktype argument should be given. Accepted values for -linktype are
“-symbolic” and “-hard”.
On Unix, symbolic links can be made to relative paths, and those paths must be relative to the actual
linkName's location (not to the cwd), but on all other platforms where relative links are not supported,
target paths will always be converted to absolute, normalized form before the link is created
(and therefore relative paths are interpreted as relative to the cwd). When creating links on filesystems
that either do not support any links, or do not support the specific type requested, an error message will
be returned. Most Unix platforms support both symbolic and hard links (the latter for files only).
Windows supports symbolic directory links and hard file links on NTFS drives.
"
@opts -type none -parsekey "-LINKTYPE" -group "linktype" -grouphelp\
""
-symbolic -typedefaults "-symbolic" -help\
""
-hard -typedefaults "-hard" -help\
"
"
@opts -parsekey "" -group ""
@values -min 1 -max 2
linkName -type string -optional 0
target -type string -optional 1
} "@doc -name Manpage: -url [manpage_tcl file]" ]
#lstat
lappend PUNKARGS [list {
@ -2986,8 +3211,37 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
name -type string
time -type integer -optional 1
} "@doc -name Manpage: -url [manpage_tcl file]"]
#nativename
#normalize
lappend PUNKARGS [list {
@id -id ::tcl::file::nativename
@cmd -name "Built-in: tcl::file::nativename"\
-summary\
{Platform-specific name of the file.}\
-help\
"Returns the platform-specific name of the file. This is useful if the filename is needed to pass
to a platform-specific call, such as to a subprocess via ${$B}exec${$N} under Windows (see EXAMPLES below)."
@values -min 1 -max 1
name -optional 0 -type string
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::normalize
@cmd -name "Built-in: tcl::file::normalize"\
-summary\
{Unique normalized path.}\
-help\
"Returns a unique normalized path representation for the file-system object (file, directory, link, etc),
whose string value can be used as a unique identifier for it. A normalized path is an absolute path which
has all “../” and “./” removed. Also it is one which is in the “standard” format for the native platform.
On Unix, this means the segments leading up to the path must be free of symbolic links/aliases (but the
very last path component may be a symbolic link), and on Windows it also means we want the long form with
that form's case-dependence (which gives us a unique, case-dependent path). The one exception concerning
the last link in the path is necessary, because Tcl or the user may wish to operate on the actual
symbolic link itself (for example ${$B}file delete${$N}, ${$B}file rename${$N}, ${$B}file copy${$N} are defined to operate on symbolic
links, not on the things that they point to)."
@values -min 1 -max 1
name -optional 0 -type string
} "@doc -name Manpage: -url [manpage_tcl file]"]
#owned
#pathtype
lappend PUNKARGS [list {
@ -3015,6 +3269,24 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
} "@doc -name Manpage: -url [manpage_tcl file]"]
#rename (2 forms)
lappend PUNKARGS [list {
@id -id ::tcl::file::rename
@cmd -name "Built-in: tcl::file::rename"\
-summary\
{Rename file or folder.}\
-help\
""
#----------------------------------------------
@form -form "tofile"
@opts
-force -type none -optional 1 -default 0
-- -type none -optional 1
@values -min 2 -max 2
source -optional 0 -type string
#----------------------------------------------
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::rootname
@cmd -name "Built-in: tcl::file::rootname"\
@ -3030,14 +3302,134 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
#separator
#size
#split
#stat
#system
#tail
#tempdir
#tempfile
lappend PUNKARGS [list {
@id -id ::tcl::file::stat
@cmd -name "Built-in: tcl::file::stat"\
-summary\
{Get file metadata - status information.}\
-help\
"Invokes the stat kernel call on name, and returns a dictionary with the information returned from
the kernel call. If varName is given, it uses the variable to hold the information. VarName is
treated as an array variable, and in such case the command returns the empty string. The following
elements are set: ${$B}atime${$N}, ${$B}ctime${$N}, ${$B}dev${$N}, ${$B}gid${$N}, ${$B}ino${$N}, ${$B}mode${$N}, ${$B}mtime${$N}, ${$B}nlink${$N}, ${$B}size${$N}, ${$B}type${$N}, ${$B}uid${$N}.
Each element except ${$B}type${$N} is a decimal string with the value of the corresponding field from the
stat return structure; see the manual entry for stat for details on the meanings of the values.
The type element gives the type of the file in the same form returned by the command ${$B}file type${$N}."
@values -min 1 -max 1
name -optional 0 -type string
varName -type string -optional 1
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::system
@cmd -name "Built-in: tcl::file::system"\
-summary\
{filesystem info for path}\
-help\
"Returns a list of one or two elements, the first of which is the name of the filesystem to use for
the file, and the second, if given, an arbitrary string representing the filesystem-specific nature
or type of the location within that filesystem. If a filesystem only supports one type of file, the
second element may not be supplied. For example the native files have a first element “native”, and
a second element which when given is a platform-specific type name for the file's system
(e.g. “NTFS”, “FAT”, on Windows). A generic virtual file system might return the list “vfs ftp” to
represent a file on a remote ftp site mounted as a virtual filesystem through an extension called
“vfs”. If the file does not belong to any filesystem, an error is generated."
@values -min 1 -max 1
name -optional 0 -type string
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::tail
@cmd -name "Built-in: tcl::file::tail"\
-summary\
{Last filesystem component of path}\
-help\
"Returns all of the characters in the last filesystem component of ${$I}name${$NI}.
Any trailing directory separator in ${$I}name${$NI} is ignored. If ${$I}name${$NI} contains no separators then returns ${$I}name${$NI}.
So, ${$B}file tail a/b${$N}, ${$B}file tail a/b/${$N} and ${$B}file tail b${$N} all return ${$B}b${$N}."
@values -min 1 -max 1
name -optional 0 -type string
} "@doc -name Manpage: -url [manpage_tcl file]"]
#tempdir tcl 9+ only?
lappend PUNKARGS [list {
@id -id ::tcl::file::tempdir
@cmd -name "Built-in: tcl::file::tempdir"\
-summary\
{Create a temporary directory.}\
-help\
"Creates a temporary directory (guaranteed to be newly created and writable by the current script)
and returns its name. If template is given, it specifies one of or both of the existing directory
(on a filesystem controlled by the operating system) to contain the temporary directory, and the
base part of the directory name; it is considered to have the location of the directory if there
is a directory separator in the name, and the base part is everything after the last directory
separator (if non-empty). The default containing directory is determined by system-specific
operations, and the default base name prefix is “tcl”.
The following output is typical and illustrative; the actual output will vary between platforms:
${[punk::args::helpers::example {
% ${$B}file tempdir${$N}
/var/tmp/tcl_u0kuy5
% ${$B}file tempdir /tmp/myapp${$N}
/tmp/myapp_8o7r9L
% ${$B}file tempdir /tmp/${$N}
/tmp/tcl_1m0JHD
% ${$B}file tempdir myapp${$N}
/var/tmp/myapp_0ihS0n
}]}
"
@values -min 0 -max 1
template -type string -optional 1
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::tempfile
@cmd -name "Built-in: tcl::file::tempfile"\
-summary\
{Create temp file and return open channel.}\
-help\
"Creates a temporary file and returns a read-write channel opened on that file.
If the nameVar is given, it specifies a variable that the name of the temporary
file will be written into; if absent, Tcl will attempt to arrange for the
temporary file to be deleted once it is no longer required. If the template is
present, it specifies parts of the template of the filename to use when creating
it (such as the directory, base-name or extension) though some platforms may
ignore some or all of these parts and use a built-in default instead.
Note that temporary files are only ever created on the native filesystem.
As such, they can be relied upon to be used with operating-system native APIs
and external programs that require a filename."
@values -min 0 -max 2
nameVar -type string -optional 1
template -type string -optional 1
} "@doc -name Manpage: -url [manpage_tcl file]"]
#tildeexpand
#type
#volumes
lappend PUNKARGS [list {
@id -id ::tcl::file::type
@cmd -name "Built-in: tcl::file::type"\
-summary\
{Type of file name.}\
-help\
"Returns a string giving the type of file name, which will be one of
${$B}file${$N}, ${$B}directory${$N}, ${$B}characterSpecial${$N}, ${$B}blockSpecial${$N}, ${$B}fifo${$N}, ${$B}link${$N}, or ${$B}socket${$N}."
@values -min 0 -max 0
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::volumes
@cmd -name "Built-in: tcl::file::volumes"\
-summary\
"List volumes mounted on the system."\
-help\
"Returns the absolute paths to the volumes mounted on the system, as a proper Tcl list.
Without any additional virtual filesystems mounted as root volumes, on UNIX, the command
will return “//zipfs:/”/ or “/”, (in case of a --disable-zipfs build), since all
filesystems are locally mounted. On Windows, it will return a list of the available
local drives (e.g. “//zipfs:/ C:/”). If any virtual filesystem has mounted additional
volumes, they will be in the returned list too."
@values -min 0 -max 0
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::writable
@ -6694,21 +7086,21 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
start -type number|expr
..|to -type string -choices {.. to} -optional 1
end -type number|expr
"by step" -type {literal(by) number|expr} -optional 1
"by step" -type {?literalprefix(by)? number|expr} -optional 1
@form -form start_count
@leaders -min 0 -max 0
@values -min 3 -max 5
start -type number|expr
count -type literal
count -type literalprefix(count)
countelements -type number|expr
"by step" -type {literal(by) number|expr} -optional 1
"by step" -type {literalprefix(by) number|expr} -optional 1
@form -form count
@leaders -min 0 -max 0
@values -min 1 -max 3
countelements -type number|expr
"by step" -type {literal(by) number|expr} -optional 1
"by step" -type {literalprefix(by) number|expr} -optional 1
} "@doc -name Manpage: -url [manpage_tcl lseq]"\
{

174
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/auto_exec-0.1.0.tm

@ -56,7 +56,9 @@ tcl::namespace::eval punk::auto_exec {
-help\
{Clear/refresh the autoexec commands in the ::auto_execs array.
This is analogous to the 'rehash' command in shells such as csh, tcsh and zsh.
This is analogous to the 'rehash' command in shells such as csh, tcsh and zsh,
or 'hash -r' in other shells such as bash.
It updates the shell's hash table of executable commands.
This can be useful after installing new software, adjusting the environment PATH directories, or (on windows) making
@ -64,7 +66,9 @@ tcl::namespace::eval punk::auto_exec {
commands are up to date with the current state of the system.
If refresh is false (the default), then all autoexec commands are cleared and will re-register as commands are called.
If refresh is true, then all existing autoexec commands are re-registered by calling auto_execok for each of them again.}
If refresh is true, then all existing autoexec commands are re-registered by calling auto_execok for each of them again.
see also ::punk::auto_exec::hash}
@opts
@values -min 0 -max 1
refresh -type boolean -default 0 -help\
@ -85,6 +89,172 @@ tcl::namespace::eval punk::auto_exec {
}
return
}
namespace eval argdoc {
lappend PUNKARGS [list {
@id -id "::punk::auto_exec::hash"
@cmd -name "punk::auto_exec::hash"\
-summary\
"Manage the hash table of autoexec commands cached in ::auto_execs."\
-help\
{see also ::punk::auto_exec::rehash}
#---------------------
@form -form {show_or_set}
@opts -min 0 -max 0
@values -min 0 -max -1
name -type string -multiple 1 -optional 1 -default {} -help\
"One or more autoexec command names to set.
If no names are provided, then all autoexec commands in the hash table will be shown."
#---------------------
@form -form {rehash}
@opts -min 1 -max 1
-r -type none -optional 0 -help\
"Clear autoexec commands from the hash table"
@values -min 0 -max 0
#---------------------
@form -form {test}
@opts
-t -type none -optional 0 -default "" -help\
"The name of the autoexec command name to display."
@values -min 1 -max -1
name -type string -multiple 1 -help\
"One or more autoexec command names to display information for.
If only a single name is provided, then the output will be the raw command string
associated with that autoexec command in the hash table.
If multiple names are provided, then the output will be a string containing each
name and its associated command string on a separate line."
#---------------------
@form -form {delete}
@opts
-d -type none -optional 0 -help\
"Delete specified autoexec commands from the hash table."
@values -min 1 -max -1
name -type string -multiple 1 -help\
"One or more autoexec command names to delete from the hash table."
#---------------------
#todo?
#-p <path> <name> (manually assign)
#-l (build a list of hash -p <path> <name> entries for all autoexec commands that can be used in a script to pre-populate the hash table without needing to call auto_execok for each command at runtime)
#---------------------
@form -form {help}
@opts -min 1 -max 1 -anyopts 1
--help -type none -optional 0 -help\
"Display usage information for this command."
@values -min 0 -max -1
ignored -type any -multiple 1 -optional 1 -help\
"Additional arguments that are ignored when --help is used"
}]
}
proc hash {args} {
set arg1 [lindex $args 0]
#select parsing form based on first argument
switch -- $arg1 {
-r {
set form rehash
}
-t {
set form test
}
-d {
set form delete
}
--help {
set form help
}
default {
#like bash in this context, we won't allow an option-like entry to be treated as an executable name
if {[string match -* $arg1]} {
puts stderr "hash: ${arg1}: invalid option"
#return [punk::args::usage -scheme error ::punk::auto_exec::hash]
set msg "hash: usage:\n"
append msg [punk::ns::synopsis ::punk::auto_exec::hash]
error $msg
}
set form show_or_set
}
}
set argd [punk::args::parse $args -form $form withid ::punk::auto_exec::hash]
lassign [dict values $argd] _leaders opts values received
global auto_execs
switch -- $form {
rehash {
unset -nocomplain auto_execs
}
test {
#like bash - we'll provide only the path if there is a single name provided, but if there are multiple names we'll provide both the name and path for each.
set names [dict get $values name]
if {[llength $names] == 1} {
set nm [lindex $names 0]
if {[info exists auto_execs($nm)]} {
return [set auto_execs($nm)]
} else {
#review
puts stderr "hash: $nm: not found"
return ""
}
}
set result ""
foreach nm $names {
if {[info exists auto_execs($nm)]} {
append result "$nm [set auto_execs($nm)]\n"
} else {
#review
puts stderr "$hash: nm: not found"
}
}
if {$result ne ""} {
set result [string trimright $result \n]
}
return $result
}
delete {
set names [dict get $values name]
foreach nm $names {
unset -nocomplain auto_execs($nm)
}
}
help {
return [punk::args::usage ::punk::auto_exec::hash]
}
default {
set requested_names [dict get $values name]
if {[llength $requested_names] == 0} {
#show all
set hashed_names [array names auto_execs]
#todo - record and return 'hits' like bash does?
set result ""
foreach nm $hashed_names {
set cached [set auto_execs($nm)]
#unlike some shells - we cache negative results (for absolute paths) that don't exist.
#as we're attempting to be close to behaviour of bash, don't output empty results for negative cache entries.
if {$cached ne ""} {
append result $cached \n
}
}
if {$result ne ""} {
set result [string trimright $result \n]
}
return $result
} else {
#rehash each requested name if it exists, otherwise display an msg on stderr for that name.
foreach nm $requested_names {
set aexec [auto_execok $nm]
if {$aexec ne ""} {
set auto_execs($nm) $aexec
} else {
puts stderr "hash: $nm: not found"
}
}
return
}
}
}
}
variable PUNKARGS
lappend PUNKARGS [list {

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

@ -503,16 +503,33 @@ tcl::namespace::eval punk::config {
key -type string -optional 1
newvalue -optional 1
}]
proc configure {args} {
set argd [punk::args::parse $args withid ::punk::config::configure]
lassign [dict values $argd] leaders opts values received solos
set whichconfig [dict get $argd leaders whichconfig]
proc configure {whichconfig args} {
#set argd [punk::args::parse $args withid ::punk::config::configure]
#lassign [dict values $argd] leaders opts values received solos
#set whichconfig [dict get $argd leaders whichconfig]
set values [dict create]
switch -- [llength $args] {
0 {
}
1 {
dict set values key [lindex $args 0]
}
2 {
dict set values newvalue [lindex $args 1]
}
default {
error "Too many arguments. Expected at most 2 (key [newvalue])"
}
}
variable configdata
if {"running" ni [dict keys $configdata]} {
init
Apply startup
}
switch -- $whichconfig {
set fullwhich [tcl::prefix::match -error "" {defaults startup-configuration running-configuration} $whichconfig]
switch -- $fullwhich {
defaults {
set configrecords [dict get $configdata defaults]
}
@ -522,12 +539,15 @@ tcl::namespace::eval punk::config {
running-configuration {
set configrecords [dict get $configdata running]
}
default {
error "Unknown config name '$whichconfig' - try defaults or startup-configuration or running-configuration"
}
}
if {![dict exists $received key]} {
if {![dict exists $values key]} {
return $configrecords
}
set key [dict get $values key]
if {![dict exists $received newvalue]} {
if {![dict exists $values newvalue]} {
return [dict get $configrecords $key]
}
error "setting value not implemented"

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

File diff suppressed because it is too large Load Diff

34
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/lib-0.1.6.tm

@ -94,19 +94,16 @@ tcl::namespace::eval punk::lib::ensemble {
set routinetail [tcl::namespace::tail $routine]
if {![string match ::* $extension]} {
set extension [uplevel 1 [
list [tcl::namespace::which namespace] current]]::$extension
set extension [uplevel 1 [list [tcl::namespace::which namespace] current]]::$extension
}
if {![tcl::namespace::exists $extension]} {
error [list {no such namespace} $extension]
}
set extension [tcl::namespace::eval $extension [
list [tcl::namespace::which namespace] current]]
set extension [tcl::namespace::eval $extension [list [tcl::namespace::which namespace] current]]
tcl::namespace::eval $extension [
list [tcl::namespace::which namespace] export *]
tcl::namespace::eval $extension [list [tcl::namespace::which namespace] export *]
while 1 {
set renamed ${routinens}::${routinetail}_[clock clicks] ;#clock clicks unlikely to collide when not directly consecutive such as: list [clock clicks] [clock clicks]
@ -140,7 +137,7 @@ tcl::namespace::eval punk::lib::check {
if {"windows" ne $::tcl_platform(platform)} {
set bug 0
} else {
set tmpdir [file tempdir]
set tmpdir [file tempdir] ;#tcl 9+
set testfile [file join $tmpdir "bugtest"]
set fd [open $testfile w]
puts $fd test
@ -4759,14 +4756,21 @@ namespace eval punk::lib {
foreach ln $linelist {
#set is_replay_pure_reset [regexp {\x1b\[0*m$} $replaycodes] ;#only looks at tail code - but if tail is pure reset - any prefix is ignorable
set ansisplits [punk::ansi::ta::split_codes_single $ln] ;#REVIEW - this split accounts for a large portion of the time taken to run this function.
if {[llength $ansisplits]<= 1} {
if {![punk::ansi::ta::detect $ln]} {
#plaintext only - no ansi codes in line
lappend transformed [string cat $replaycodes $ln $RST]
#leave replaycodes as is for next line
set nextreplay $replaycodes
} else {
set replaycodes $nextreplay
continue
}
set ansisplits [punk::ansi::ta::split_codes_single $ln] ;#REVIEW - this split seems to account for a large portion of the time taken to run this function.
#if {[llength $ansisplits]<= 1} {
# #plaintext only - no ansi codes in line
# lappend transformed [string cat $replaycodes $ln $RST]
# #leave replaycodes as is for next line
# set nextreplay $replaycodes
#} else {
set tail $RST
set lastcode [lindex $ansisplits end-1] ;#may or may not be SGR
if {[punk::ansi::codetype::is_sgr_reset $lastcode]} {
@ -4821,7 +4825,7 @@ namespace eval punk::lib {
#set newreplay [join $codestack ""]
set newreplay [punk::ansi::codetype::sgr_merge_list {*}$codestack]
if {$line_has_sgr && $newreplay ne $replaycodes} {
if {$RST ne "" && $line_has_sgr && $newreplay ne $replaycodes} {
#adjust if it doesn't already does a reset at start
if {[punk::ansi::codetype::has_sgr_leadingreset $newreplay]} {
set nextreplay $newreplay
@ -4838,7 +4842,7 @@ namespace eval punk::lib {
} else {
lappend transformed [string cat $replaycodes $ln $tail]
}
}
#}
set replaycodes $nextreplay
}
set linelist $transformed
@ -5505,7 +5509,7 @@ tcl::namespace::eval punk::lib::debug {
namespace eval ::punk::args::register {
#use fully qualified so 8.6 doesn't find existing var in global namespace
lappend ::punk::args::register::NAMESPACES ::punk::lib
lappend ::punk::args::register::NAMESPACES ::punk::lib ::punk::lib::ensemble
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready

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

@ -330,8 +330,11 @@ tcl::namespace::eval punk::nav::fs {
punk::args::define {
@id -id ::punk::nav::fs::d/
@cmd -name punk::nav::fs::d/ -help\
{List directories or directories and files in the current directory or in the
@cmd -name punk::nav::fs::d/\
-summary\
"Navigate and list directories and files"\
-help\
{Navigate/List directories or directories and files in the current directory or in the
targets specified with the fileglob_or_target glob pattern(s).
If a single target is specified without glob characters, and it exists as a directory,

36
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/nav/ns-0.1.0.tm

@ -33,6 +33,40 @@ tcl::namespace::eval punk::nav::ns {
}
namespace path {::punk::ns}
namespace eval argdoc {
lappend PUNKARGS [list {
@id -id ::punk::nav::ns::ns/
@cmd -name punk::nav::ns::ns/\
-summary\
"Navigate and list namespaces and commands"\
-help\
{Navigate/List namespaces or namespaces and commands in the current namespace or in the
targets specified with the nsglob pattern(s).
This function is provided via aliases as n/ n// and n/// with v being inferred from the alias
The n/ n// and n/// forms are more convenient for interactive use.
examples:
n/ - list namespaces below current namespace
n// - list namespaces and commands below current namespace
n/ p* - list namespaces below current matching p*
n// p* - list namespaces below current and commands in current matching p*
}
@values -min 1 -max -1 -type string
v -type string -choices {/ //} -help\
"
/ - list namespaces only
// - list namespaces and commands
/// - list namespaces, commands and commands resolvable via 'namespace path'
"
nsglob -type string -optional true -multiple true -help\
"A glob pattern supporting placeholders * and ?, to filter results.
If multiple patterns are supplied, then a listing for each pattern is returned.
If no patterns are supplied, then all items are listed."
}]
}
proc ns/ {v {ns_or_glob ""} args} {
variable ns_current ;#change active ns of repl by setting ns_current
@ -227,8 +261,6 @@ tcl::namespace::eval punk::nav::ns {
}
}
}

32
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/ns-0.1.0.tm

@ -3711,6 +3711,30 @@ y" {return quirkykeyscript}
}
}
punk::args::define {
@id -id ::punk::ns::nscommands
@cmd -name punk::ns::nscommands\
-summary\
"List current namespace commands one per line."\
-help\
"Display commands in the current namespace, or optionally within specified namespaces.
Namespaces to search can be specified as arguments, with optional glob patterns.
Examples:
'nscommands' - list all commands in the current namespace
'nscommands foo*' - list all commands in the current namespace with names starting with 'foo'
'nscommands foo* bar*' - list all commands in the current namespace with names starting with 'foo' or 'bar'"
@leaders -min 0 -max 0
@opts
-raw -type none -help\
"Output raw command names with no ANSI color codes.
Useful for scripting or when color codes would be undesirable."
@values -min 1 -max -1
glob -multiple 1 -optional 1 -default * -help\
"Namespace patterns to search for commands. If not specified, defaults to '*',
which searches the current namespace. Patterns can include glob characters (* and ?).
Examples: 'foo*' to match namespaces starting with 'foo', '*::bar' to match namespaces
ending with 'bar'."
}
proc nscommands {args} {
set commandns [uplevel 1 [list ::tcl::namespace::current]]
set commandlist [::list]
@ -3803,6 +3827,7 @@ y" {return quirkykeyscript}
}
}
interp alias {} nscommands {} punk::ns::nscommands
proc nscommandlist {{ns *}} {
set nsparts [nsparts_cached $ns]
set tail [lindex $nsparts end]
@ -4051,6 +4076,13 @@ y" {return quirkykeyscript}
#eg because parent interp called something like: interp0 alias ::thread::id ::thread::id
#make sure we don't perform an infinite loop
if {$tgt ne $resolved} {
#--------------
#unqualified alias target - need to resolve to fully qualified for cmdwhich lookup to work correctly
#jmn - todo test/review
if {![string match ::* $tgt]} {
set tgt ::$tgt
}
#--------------
set whichinfo [uplevel 1 [list ::punk::ns::cmdwhich $tgt]]
set origin [dict get $whichinfo origin]
set origintype [dict get $whichinfo origintype]

21
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repl-0.1.2.tm

@ -2948,8 +2948,11 @@ namespace eval repl {
if {[catch {package require thread} errM]} {
puts stdout ">>repl::init initscript lib load fail on package require thread\n$errM"
puts stdout ">>repl::init auto_path : $::auto_path"
puts stdout ">>repl::init tcl::tm::list: [tcl::tm::list]"
if {[catch {package require Thread} errM2]} {
puts stdout ">>repl::init initscript lib load fail on package require Thread\n$errM2"
puts stdout ">>repl::init auto_path : $::auto_path"
puts stdout ">>repl::init tcl::tm::list: [tcl::tm::list]"
}
}
#-----
@ -3395,9 +3398,11 @@ namespace eval repl {
set v [lindex $versions end]
set path [lindex [package ifneeded $pkg $v] end]
if {[file extension $path] in {.tcl .tm}} {
if {![catch {readFile $path} data]} {
if {![catch {readFile $path} packagedef]} {
code eval [list info script $path]
code eval $data
code eval $packagedef
#jjj
code eval [list package provide $pkg $v] ;#ensure package is marked as provided in interp even if it doesn't call package provide itself
code eval [list info script $prior_infoscript]
} else {
error "safe - failed to read $path"
@ -3705,6 +3710,10 @@ namespace eval repl {
#puts stderr [join $::auto_path \n]
#puts stderr -----
#punk::console is not loaded at this point
#puts "--------------provide punk::console : [package provide punk::console]"
#puts "--------------punk::console commands: [info commands ::punk::console::*]"
if {[catch {
package require punk::args
package require punk::config
@ -3714,6 +3723,10 @@ namespace eval repl {
#Requiring it shouldn't trigger application - but zipfs/vfs interactions confused it in some early versions
package require natsort
#catch {package require packageTrace}
if {[catch {package require punk::console} errM]} {
#review
puts stderr "failed to load punk::console - \n$errM\n$::errorInfo"
}
package require punk
package require shellrun
package require shellfilter

25
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/winlnk-0.1.1.tm

@ -733,18 +733,23 @@ tcl::namespace::eval punk::winlnk {
set r [binary scan $lenfield su count_chars] ;# su is for unsigned short in little endian order
set string_value ""
if {[Header_Has_LinkFlag $contents "IsUnicode"]} {
#string is UTF-16LE encoded
#string is UTF-16LE encoded - we have this encoding available in tcl 9+ - but not in 8.6
set numbytes [expr {2 * $count_chars}]
set string_bytes [string range $contents $start+2 [expr {$start + 2 + $numbytes - 1}]]
#consider using tcl encoding convertfrom utf-16le instead of manually parsing the UTF-16LE bytes - this would be more robust and handle edge cases better (e.g. surrogate pairs, non-BMP characters, etc.)
set string_value [encoding convertfrom utf-16le $string_bytes]
#for {set i 0} {$i < [string length $string_bytes]} {
# set char_bytes [string range $string_bytes $i [expr {$i + 1}]]
# set r [binary scan $char_bytes su char] ;# s for unsigned short
# append string_value [format %c $char]
# incr i 1 ;# skip the next byte since it's part of the UTF-16LE encoding
#}
#use tcl encoding convertfrom utf-16le when we can instead of manually parsing the UTF-16LE bytes
#- this should be more robust and handle edge cases better (e.g. surrogate pairs, non-BMP characters, etc.)
if {[catch {set string_value [encoding convertfrom utf-16le $string_bytes]} err]} {
#puts stderr "Error converting UTF-16LE string: $err"
#set string_value ""
for {set i 0} {$i < [string length $string_bytes]} {incr i} {
set char_bytes [string range $string_bytes $i $i+1]
set r [binary scan $char_bytes su char] ;# su for unsigned short
append string_value [format %c $char]
incr i 1 ;# skip the next byte since it's part of the UTF-16LE encoding
}
}
} else {
set numbytes $count_chars
set string_bytes [string range $contents $start+2 [expr {$start + 2 + $numbytes - 1}]]

45
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/textblock-0.1.3.tm

@ -2107,6 +2107,7 @@ tcl::namespace::eval textblock {
set cidx [lindex [tcl::dict::keys $o_columndefs] $index_expression]
set colwidth [my column_width $cidx]
set fwidth [expr {$colwidth + 2}]
set col_blockalign [tcl::dict::get $o_columndefs $cidx -blockalign]
@ -2509,18 +2510,19 @@ tcl::namespace::eval textblock {
set border_ansi $body_ansibase$body_ansiborder
}
set ansibase $body_ansibase$opt_col_ansibase
set r 0
set ftblock [expr {[tcl::dict::get $o_opts_table -frametype] eq "block"}]
set do_show_edge [tcl::dict::get $o_opts_table -show_edge]
foreach c $cells {
#cells in column - each new c is in a different row
set row_ansibase [tcl::dict::get $o_rowdefs $r -ansibase]
set row_bg ""
set row_ansibase [tcl::dict::get $o_rowdefs $r -ansibase]
if {$row_ansibase ne ""} {
set row_bg [punk::ansi::codetype::sgr_merge_singles [list $row_ansibase] -filter_fg 1]
}
set ansibase $body_ansibase$opt_col_ansibase
#todo - joinleft,joinright,joindown based on opts in args
set cell_ansibase ""
@ -2602,7 +2604,7 @@ tcl::namespace::eval textblock {
} else {
set blims $blims_only_headerless
}
if {![tcl::dict::get $o_opts_table -show_edge]} {
if {!$do_show_edge} {
set blims [struct::set difference $blims [tcl::dict::get $::textblock::class::table_edge_parts only$opt_posn] ]
}
} else {
@ -2612,11 +2614,11 @@ tcl::namespace::eval textblock {
} else {
set blims $blims_top_headerless
}
if {![tcl::dict::get $o_opts_table -show_edge]} {
if {!$do_show_edge} {
set blims [struct::set difference $blims [tcl::dict::get $::textblock::class::table_edge_parts top$opt_posn] ]
}
}
set rowframe [textblock::frame -checkargs 0 -type [tcl::dict::get $ftypes body] -width [expr {$colwidth+2}] -blockalign $col_blockalign -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c]
set rowframe [textblock::frame -checkargs 0 -type $ftype_body -width [expr {$colwidth+2}] -blockalign $col_blockalign -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c]
set return_bodywidth [textblock::widthtopline $rowframe] ;#frame lines always same width - just look at top line
append part_body $rowframe \n
} else {
@ -2624,22 +2626,26 @@ tcl::namespace::eval textblock {
set joins [lremove $joins [lsearch $joins down*]]
set bmap $botmap
set blims $blims_bot
if {![tcl::dict::get $o_opts_table -show_edge]} {
if {!$do_show_edge} {
set blims [struct::set difference $blims [tcl::dict::get $::textblock::class::table_edge_parts bottom$opt_posn] ]
}
} else {
set bmap $midmap
set blims $blims_mid ;#will only be reduced from boxlimits if -show_seps was processed above
if {![tcl::dict::get $o_opts_table -show_edge]} {
if {!$do_show_edge} {
set blims [struct::set difference $blims [tcl::dict::get $::textblock::class::table_edge_parts middle$opt_posn] ]
}
}
append part_body [textblock::frame -checkargs 0 -type [tcl::dict::get $ftypes body] -width [expr {$colwidth+2}] -blockalign $col_blockalign -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c]\n
#append part_body [textblock::frame -checkargs 0 -type [tcl::dict::get $ftypes body] -width [expr {$colwidth+2}] -blockalign $col_blockalign -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c]\n
append part_body [textblock::frame -checkargs 0 -type $ftype_body -width [expr {$colwidth+2}] -blockalign $col_blockalign -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c]\n
}
incr r
}
#return empty (zero content height) row if no rows
if {![llength $cells]} {
set basebg [punk::ansi::codetype::sgr_merge_singles [list $body_ansibase] -filter_fg 1]
set ansiborder_final [punk::ansi::codetype::sgr_merge [list $basebg $body_ansiborder]]
set joins [lremove $joins [lsearch $joins down*]]
#we need to know the width of the column to setup the empty cell properly
#even if no header displayed - we should take account of any defined column widths
@ -2661,7 +2667,9 @@ tcl::namespace::eval textblock {
append part_body [tcl::string::repeat " " $colwidth] \n
set return_bodywidth $colwidth
} else {
set emptyframe [textblock::frame -checkargs 0 -width [expr {$colwidth + 2}] -type [tcl::dict::get $ftypes body] -boxlimits $blims -boxmap $onlymap -joins $joins]
#set emptyframe [textblock::frame -checkargs 0 -width [expr {$colwidth + 2}] -type [tcl::dict::get $ftypes body] -boxlimits $blims -boxmap $onlymap -joins $joins]
# -blockalign probably not relevant for an empty row.
set emptyframe [textblock::frame -checkargs 0 -type $ftype_body -width [expr {$colwidth+2}] -ansibase $body_ansibase -ansiborder $ansiborder_final -boxlimits $blims -boxmap $onlymap -joins $joins]
append part_body $emptyframe \n
set return_bodywidth [textblock::width $emptyframe]
}
@ -5741,7 +5749,10 @@ tcl::namespace::eval textblock {
@id -id ::textblock::join_basic
@cmd -name textblock::join_basic -help\
"Join blocks of text line by line but don't add padding on each line to enforce uniform width.
Already uniform blocks will join faster than textblock::join, and ragged blocks will join in a ragged manner
Already uniform blocks will join faster than textblock::join, and ragged blocks will join in a ragged manner.
see also textblock::join_basic_raw - a thin wrapper around split and join for the common case of joining blocks without any options,
and is intended to avoid the overhead of argument parsing.
"
-ansiresets -type any -default auto
-- -type none -optional 0 -help "end of options marker -- is mandatory because joined blocks may easily conflict with flags"
@ -5787,7 +5798,21 @@ tcl::namespace::eval textblock {
}
return [::join $outlines \n]
}
punk::args::define {
@id -id ::textblock::join_basic_raw
@cmd -name textblock::join_basic_raw -help\
"Join blocks of text line by line but don't add padding on each line to enforce uniform width.
Already uniform blocks will join faster than textblock::join, and ragged blocks will join in a ragged manner.
This version is a thin wrapper around split and join for the common case of joining blocks without any options,
and is intended to avoid the overhead of argument parsing.
"
@values
blocks -type any -multiple 1
}
proc ::textblock::join_basic_raw {args} {
#do not use any argument parsing libs - this is intended as a thin wrapper around split and join for the common case of joining blocks without any options,
#and we want to avoid the overhead of argument parsing.
#no options. -*, -- are legimate blocks
set blocklists [lrepeat [llength $args] ""]
set blocklengths [lrepeat [expr {[llength $args]+1}] 0] ;#add 1 to ensure never empty - used only for rowcount max calc

48
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/overtype-1.7.4.tm

@ -461,8 +461,21 @@ tcl::namespace::eval overtype {
if {$underblock eq ""} {
set underlines [lrepeat $renderheight ""]
} else {
set underblock [textblock::join_basic -- $underblock] ;#ensure properly rendered - ansi per-line resets & replays
set underlines [split $underblock \n]
#----
#this splits into lines - only to rejoin - which is inefficient.
#It also has code to handle joining multiple blocks - but we only have one in this case.
#set underblock [textblock::join_basic_raw $underblock];#ensure properly rendered - ansi per-line resets & replays
#set underlines [split $underblock \n]
#----
if {[punk::ansi::ta::detectcode $underblock]} {
#-ansireplays 1 quite expensive e.g ~15us for only 3 short lines on a 2026 threadripper pro
set underlines [punk::lib::linelist -ansireplays 1 $underblock]
} else {
set underlines [split $underblock \n]
}
}
#if {$underblock eq ""} {
# set blank "\x1b\[0m\x1b\[0m"
@ -881,8 +894,9 @@ tcl::namespace::eval overtype {
set cursor_saved_position [tcl::dict::create]
set cursor_saved_attributes ""
} else {
#FUTURE: Handle restore without save case
#Should move to home position and reset ansi SGR when no save data available
#TODO
#?restore without save?
#should move to home position and reset ansi SGR?
#puts stderr "overtype::renderspace cursor_restore without save data available"
}
#If we were inserting prior to hitting the cursor_restore - there could be overflow_right data - generally the overtype functions aren't for inserting - but ansi can enable it
@ -1195,7 +1209,7 @@ tcl::namespace::eval overtype {
wrapmoveforward {
#doesn't seem to be used by fruit.ans testfile
#used by dzds.ans
#FIXED: cursor_forward can move deep into the next line or span multiple lines - handled below
#note that cursor_forward may move deep into the next line - or even span multiple lines !TODO
set c $renderwidth
set r $post_render_row
if {$post_render_col > $renderwidth} {
@ -2571,9 +2585,8 @@ tcl::namespace::eval overtype {
lset overmap 0 "$startpadding[lindex $overmap 0]"
} else {
if {[punk::ansi::ta::detect $overdata]} {
#FUTURE: Optimize for large files with no newlines
#Currently wastefully calling split_codes_single repeatedly on mostly the same data.
#Consider caching or streaming approach for 200K+ input files.
#TODO!! rework this.
#e.g 200K+ input file with no newlines - we are wastefully calling split_codes_single repeatedly on mostly the same data.
#set overmap [punk::ansi::ta::split_codes_single $startpadding$overdata]
set overmap [punk::ansi::ta::split_codes_single $overdata]
lset overmap 0 "$startpadding[lindex $overmap 0]"
@ -2599,9 +2612,9 @@ tcl::namespace::eval overtype {
#???
set colcursor $opt_colstart
#FUTURE: Create a virtual column object for cleaner column tracking
#Currently need to refer to column1 or columnmin/columnmax without calculating offsets due to startcolumn.
#Need to clarify what start column means from ANSI code movement perspective - offset perspective is unclear.
#TODO - make a little virtual column object
#we need to refer to column1 or columnmin? or columnmax without calculating offsets due to to startcolumn
#need to lock-down what start column means from perspective of ANSI codes moving around - the offset perspective is unclear and a mess.
#set re_diacritics {[\u0300-\u036f]+|[\u1ab0-\u1aff]+|[\u1dc0-\u1dff]+|[\u20d0-\u20ff]+|[\ufe20-\ufe2f]+}
@ -3046,9 +3059,10 @@ tcl::namespace::eval overtype {
set instruction overflow_splitchar
break
} elseif {$owidth > 2} {
#FUTURE: Handle wide graphemes and tabs
#Could be tab with length dependent on tabstops/elastic tabstop settings
#? tab?
#TODO!
puts stderr "overtype::renderline long overtext grapheme '[ansistring VIEW -lf 1 -vt 1 $ch]' not handled"
#tab of some length dependent on tabstops/elastic tabstop settings?
}
} elseif {$idx >= $overflow_idx} {
#REVIEW
@ -3393,7 +3407,8 @@ tcl::namespace::eval overtype {
#we've mapped 7 and 8bit escapes to values we can handle as literals in switch statements to take advantange of jump tables.
switch -- $leadernorm {
1006 {
#FUTURE: Implement mouse event handling
#TODO
#
switch -- [tcl::string::index $codenorm end] {
M {
puts stderr "mousedown $codenorm"
@ -3843,7 +3858,7 @@ tcl::namespace::eval overtype {
#(for use with selective erase: DECSED and DECSEL)
set param [tcl::string::range $codenorm 4 end-2]
if {$param eq ""} {set param 0}
#FUTURE: Store DECSCA like SGR in stacks for replay capability
#TODO - store like SGR in stacks - replays?
switch -exact -- $param {
0 - 2 {
#canerase
@ -4423,7 +4438,8 @@ tcl::namespace::eval overtype {
} else {
set sos_content [string range $code 2 end-2] ;#ST is \x1b\\
}
#FUTURE: Return SOS content in useful form to the caller
#return in some useful form to the caller
#TODO!
lappend sos_list [list string $sos_content row $cursor_row column $cursor_column]
puts stderr "overtype::renderline ESCX SOS UNIMPLEMENTED. code [ansistring VIEW -lf 1 -vt 1 -nul 1 $code]"
}

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

@ -341,7 +341,7 @@ namespace eval punk {
#}
#safest? could be a link?
foreach match [glob -nocomplain -dir $dir -tail {*}$lookfor] {
foreach match [glob -nocomplain -dir $dir -tail -- {*}$lookfor] {
set file [file join $dir $match]
if {[file exists $file] && ![file isdirectory $file]} {
#set assoc [extension_open_association [file extension $file]]
@ -6277,21 +6277,55 @@ namespace eval punk {
namespace eval argdoc {
punk::args::define {
@id -id ::punk::path
@cmd -name "punk::path" -help\
"Introspection of the PATH environment variable.
@cmd -name "punk::path"\
-summary\
"Display PATH executable shadowing and conflicts with TCL commands"\
-help\
{Introspection of the PATH environment variable.
This tool will examine executables within each PATH entry and show which binaries
are overshadowed by earlier PATH entries. It can also be used to examine the contents of each PATH entry, and to filter results using glob patterns."
are overshadowed by earlier PATH entries.
It can also be used to examine the contents of each PATH entry, and to filter results using glob patterns.
${[punk::args::helpers::example {
#show all executables in all PATH entries
punk::path
#show all executables in all PATH entries that contain 'Windows' in the path
punk::path -pathglob *Windows*
#show all executables in all PATH entries that contain 'scoop' in the path,
#and filter the executables to show only those that are named dir, ls or start with 'ca'
punk::path -pathglob *scoop* dir ls ca*
#show all executables that conflict with TCL commands starting with 'a' in the current namespace.
punk::path {*}[nscommandlist a*]
#show all executables that conflict with TCL commands resolvable from the current namespace.
punk::path {*}[info commands]
}]}
see also the punk::auto_exec package.
}
@opts
-binglobs -type list -default {*} -help "glob pattern to filter results. Default '*' to include all entries."
-pathglob -type string -default {*} -multiple true -help "Case insensitive glob pattern to filter path entries. Default '*' to include all PATH directories."
@values -min 0 -max -1
glob -type string -default {*} -multiple true -optional 1 -help "Case insensitive glob pattern to filter path entries. Default '*' to include all PATH directories."
binglob -type list -default {*} -multiple true -optional 1 -help "glob pattern to filter results. Default '*' to include all entries."
}
}
variable d_path_info
variable d_bin_info
variable d_index_executables
#there is still a potential conflict regarding auto_execok on windows - which has some cmd.exe builtins as auto-executable
#- but these are not actually executable files on the filesystem - so they won't be found by our path search
#- but they will be found when not masked by a tcl command.
proc path {args} {
variable d_path_info
variable d_bin_info
variable d_index_executables
set is_windows [expr {$::tcl_platform(platform) eq "windows"}]
set argd [punk::args::parse $args withid ::punk::path]
lassign [dict values $argd] leaders opts values received
set binglobs [dict get $opts -binglobs]
set globs [dict get $values glob]
set pathglobs [dict get $opts -pathglob]
set binglobs [dict get $values binglob]
if {$::tcl_platform(platform) eq "windows"} {
set sep ";"
} else {
@ -6299,14 +6333,18 @@ namespace eval punk {
set sep ":"
}
set all_paths [split [string trimright $::env(PATH) $sep] $sep]
set filtered_paths $all_paths
if {[llength $globs]} {
set filtered_paths [list]
foreach p $all_paths {
foreach g $globs {
if {[string match -nocase $g $p]} {
lappend filtered_paths $p
break
if {[llength $pathglobs]} {
if {[lsearch -exact $pathglobs "*"] >= 0} {
#if we have a wildcard glob then the others are irrelevant - we want to match all paths
set matched_paths $all_paths
} else {
set matched_paths [list]
foreach p $all_paths {
foreach pg $pathglobs {
if {[string match -nocase $pg $p]} {
lappend matched_paths $p
break
}
}
}
}
@ -6344,6 +6382,60 @@ namespace eval punk {
#and the actual executable names (with case and extensions as they appear on the filesystem). We will also build a
#dict keyed by path index which contains the list of executables in that path - to make it easy to show which
#executables are overshadowed by which paths.
if {$is_windows} {
#Sometimes PATHEXT includes an entry of just a dot - which means files with no extension are considered executable.
#We need to account for this in our glob pattern.
set pathexts [list]
if {[info exists ::env(PATHEXT)]} {
set env_pathexts [split $::env(PATHEXT) ";"]
#set pathexts [lmap e $env_pathexts {string tolower $e}]
foreach pe $env_pathexts {
if {$pe eq "."} {
continue
}
lappend pathexts [string tolower $pe]
}
} else {
set env_pathexts [list]
#default PATHEXT if not set - according to Microsoft docs
set pathexts [list .com .exe .bat .cmd]
}
foreach bg $binglobs {
set has_pathext 0
foreach pe $pathexts {
if {[string match -nocase "*$pe" $bg]} {
set has_pathext 1
break
}
}
if {!$has_pathext} {
foreach pe $pathexts {
set globext "$bg$pe"
if {$globext ni $binglobs} {
lappend binglobs "$bg$pe"
}
}
}
}
set lc_binglobs [lmap e $binglobs {string tolower $e}]
if {"." in $pathexts} {
foreach bg $binglobs {
set has_pathext 0
foreach pe $pathexts {
if {[string match -nocase "*$pe" $bg]} {
set base [string range $bg 0 [expr {[string length $bg] - [string length $pe] - 1}]]
set has_pathext 1
break
}
}
if {$has_pathext} {
if {[string tolower $base] ni $lc_binglobs} {
lappend binglobs "$base"
}
}
}
}
}
set d_path_info [dict create] ;#key is normalized path (e.g case-insensitive on windows).
set d_bin_info [dict create] ;#key is normalized executable name (e.g case-insensitive on windows, or callable with extensions stripped off).
@ -6355,63 +6447,21 @@ namespace eval punk {
} else {
set pnorm $p
}
if {[string length $pnorm] > 1} {
set lastchar [string index $pnorm end]
if {$lastchar eq "/" || $lastchar eq "\\"} {
set pnorm [string range $pnorm 0 end-1]
}
}
if {![dict exists $d_path_info $pnorm]} {
dict set d_path_info $pnorm [dict create original_paths [list $p] indices [list $path_idx]]
set executables [list]
if {[file isdirectory $p]} {
#get all files that are executable in this path.
#If we don't normalize the path here - then trailing backslashes on windows can cause a problem with the -tail glob returning a leading slash on the executable names.
#also as we don't necessarily normalize the resulting final path with executable - we want the case to be correct.
set pnormglob [file normalize $p]
if {$::tcl_platform(platform) eq "windows"} {
#Sometimes PATHEXT includes an entry of just a dot - which means files with no extension are considered executable.
#We need to account for this in our glob pattern.
set pathexts [list]
if {[info exists ::env(PATHEXT)]} {
set env_pathexts [split $::env(PATHEXT) ";"]
#set pathexts [lmap e $env_pathexts {string tolower $e}]
foreach pe $env_pathexts {
if {$pe eq "."} {
continue
}
lappend pathexts [string tolower $pe]
}
} else {
set env_pathexts [list]
#default PATHEXT if not set - according to Microsoft docs
set pathexts [list .com .exe .bat .cmd]
}
foreach bg $binglobs {
set has_pathext 0
foreach pe $pathexts {
if {[string match -nocase "*$pe" $bg]} {
set has_pathext 1
break
}
}
if {!$has_pathext} {
foreach pe $pathexts {
lappend binglobs "$bg$pe"
}
}
}
set lc_binglobs [lmap e $binglobs {string tolower $e}]
if {"." in $pathexts} {
foreach bg $binglobs {
set has_pathext 0
foreach pe $pathexts {
if {[string match -nocase "*$pe" $bg]} {
set base [string range $bg 0 [expr {[string length $bg] - [string length $pe] - 1}]]
set has_pathext 1
break
}
}
if {$has_pathext} {
if {[string tolower $base] ni $lc_binglobs} {
lappend binglobs "$base"
}
}
}
}
#TCL's glob on windows is case-insensitive, but in some cases return the result with the case as globbed for regardless of the actual case on the filesystem.
#(This seems to occur when the pattern does *not* contain a wildcard and is probably a bug)
@ -6421,34 +6471,51 @@ namespace eval punk {
# but tcl's glob does not respect the case of even the character-class pattern - so this is not a reliable workaround).
#see punk::fglob for a work-in-progress glob implementation which gives us more control over case sensitivity and the case of results on windows.
set globresults [lsort -unique [glob -nocomplain -directory $pnormglob -types {f x} {*}$binglobs]]
#-----------------------
#JJJ
#set globresults [lsort -unique [glob -nocomplain -directory $pnormglob -types {f x} {*}$binglobs]]
#set executables [list]
#foreach e $globresults {
# puts stderr "glob result: $e"
# puts stderr "normalized executable name: [file tail [file normalize [string range $e 0 end]]]]"
# lappend executables [file tail [file normalize $e]]
#}
#-----------------------
#track all executables in the path - even those that don't match the binglobs
#use fglob to get the actual case of the executables on windows - as glob seems to return the case as globbed for rather than the actual case on the filesystem in some cases.
#this doesn't run a full 'file normalize' on the results which affects whether a more efficient internal representation is stored
#fglob with single glob argument should already return a unique list.
set folder_exes [fglob -nocomplain -directory $pnormglob -types {f x} *]
set executables [list]
foreach e $globresults {
puts stderr "glob result: $e"
puts stderr "normalized executable name: [file tail [file normalize [string range $e 0 end]]]]"
lappend executables [file tail [file normalize $e]]
foreach e $folder_exes {
lappend executables [file tail $e]
}
} else {
set executables [lsort -unique [glob -nocomplain -directory $p -types {f x} -tail {*}$binglobs]]
#set executables [lsort -unique [glob -nocomplain -directory $p -types {f x} -tail {*}$binglobs]]
set executables [lsort -unique [glob -nocomplain -directory $p -types {f x} -tail *]]
}
}
dict set d_index_executables $path_idx $executables
foreach exe $executables {
#todo - other case-insensitive platforms/filesystems.
if {$::tcl_platform(platform) eq "windows"} {
set exenorm [string tolower $exe]
set exe_key [string tolower $exe]
} else {
set exenorm $exe
#on case
set exe_key $exe
}
if {![dict exists $d_bin_info $exenorm]} {
dict set d_bin_info $exenorm [dict create path_indices [list $path_idx] paths [list $p] executable_names [list $exe]]
if {![dict exists $d_bin_info $exe_key]} {
dict set d_bin_info $exe_key [dict create path_indices [list $path_idx] paths [list $p] executable_names [list $exe]]
} else {
#dict lappend d_bin_info $exenorm path_indices $path_idx paths $p executable_names $exe
set bindata [dict get $d_bin_info $exenorm]
#dict lappend d_bin_info $exe_key path_indices $path_idx paths $p executable_names $exe
set bindata [dict get $d_bin_info $exe_key]
dict lappend bindata path_indices $path_idx
dict lappend bindata paths $p
dict lappend bindata executable_names $exe
dict set d_bin_info $exenorm $bindata
dict set d_bin_info $exe_key $bindata
}
}
} else {
@ -6467,16 +6534,16 @@ namespace eval punk {
set executables [dict get $d_index_executables [lindex [dict get $d_path_info $pnorm indices] 0]] ;#get executables for this path
foreach exe $executables {
if {$::tcl_platform(platform) eq "windows"} {
set exenorm [string tolower $exe]
set exe_key [string tolower $exe]
} else {
set exenorm $exe
set exe_key $exe
}
#dict lappend d_bin_info $exenorm path_indices $path_idx paths $p executable_names $exe
set bindata [dict get $d_bin_info $exenorm]
#dict lappend d_bin_info $exe_key path_indices $path_idx paths $p executable_names $exe
set bindata [dict get $d_bin_info $exe_key]
dict lappend bindata path_indices $path_idx
dict lappend bindata paths $p
dict lappend bindata executable_names $exe
dict set d_bin_info $exenorm $bindata
dict set d_bin_info $exe_key $bindata
}
}
@ -6484,18 +6551,255 @@ namespace eval punk {
}
#temporary debug output to check dicts are being built correctly
set debug ""
append debug "Path info dict:" \n
append debug [showdict $d_path_info] \n
append debug "Binary info dict:" \n
append debug [showdict $d_bin_info] \n
append debug "Index executables dict:" \n
append debug [showdict $d_index_executables] \n
#return $debug
puts stdout $debug
#set debug ""
#append debug "Path info dict:" \n
#append debug [showdict $d_path_info] \n
#append debug "Binary info dict:" \n
#append debug [showdict $d_bin_info {*}$binglobs] \n
##append debug "Index executables dict:" \n
##append debug [showdict $d_index_executables] \n
##return $debug
#puts stdout $debug
#dict for {p pinfo} $d_path_info {
# set original_paths [dict get $pinfo original_paths]
# set indices [dict get $pinfo indices]
# puts stdout "Path: $p"
# puts stdout " Original paths: $original_paths"
# puts stdout " Indices in PATH: $indices"
# if {[dict exists $d_index_executables [lindex $indices 0]]} {
# set executables [dict get $d_index_executables [lindex $indices 0]]
# puts stdout " Executables: [llength $executables]"
# } else {
# puts stdout " Executables: (not a directory or no executables found)"
# }
#}
set nscaller [uplevel 1 {::tcl::namespace::current}]
set context_commands [namespace eval $nscaller {info commands}]
#process paths in order they appear in the original PATH.
set pidx 0
#use a punk::textblock::table for formatting.
set rows [list]
set headers [list "idx" "Path" "exe\nCount" "Shadow\nCount" "Executables" "TCL context\nConflicts"]
set ERR [punk::ansi::a+ red bold]
set RST [punk::ansi::a]
set STR [punk::ansi::a+ strike]
set SDW [punk::ansi::a+ red strike]
set WRN [punk::ansi::a+ yellow bold]
set subcols 2
foreach p $all_paths {
#if {$p ni $matched_paths} {
# incr pidx
# continue
#}
set thisrow [list $pidx]
set pnorm [string tolower $p]
if {[string length $pnorm] > 1} {
set lastchar [string index $pnorm end]
if {$lastchar eq "/" || $lastchar eq "\\"} {
set pnorm [string range $pnorm 0 end-1]
}
}
set pinfo [dict get $d_path_info $pnorm]
set original_paths [dict get $pinfo original_paths]
set indices [dict get $pinfo indices]
if {[lindex $indices 0] == $pidx} {
#this is the first occurrence of this path in the original PATH.
set overshadowed [list]
set conflicts [list]
lappend thisrow $p
if {[dict exists $d_index_executables $pidx]} {
set executables [dict get $d_index_executables $pidx]
lappend thisrow [llength $executables]
set display_executables [list]
foreach exe $executables {
set matched_binglob 0
foreach bg $binglobs {
#review - -nocase only on case-insensitive platforms/filesystems?
#- but it is simpler to just apply it to all platforms here rather than trying to determine case-sensitivity of each path.
if {[string match -nocase $bg $exe]} {
set matched_binglob 1
continue
}
}
set exe_key [string tolower $exe]
if {[dict exists $d_bin_info $exe_key]} {
set bindata [dict get $d_bin_info $exe_key]
set path_indices [dict get $bindata path_indices]
set is_overshadowed 0
foreach pi $path_indices {
if {$pi < $pidx} {
lappend overshadowed $exe
set is_overshadowed 1
break
}
}
if {$matched_binglob} {
if {$is_windows} {
#check for matches in context_commands - which are case-insensitive on windows
#the context_commands are however case sensitive.
#we want to mark conflicts in one of two ways in the conflicts column.
#- if there is a case-insensitive match but not a case-sensitive match
#- then we have a conflict but not an exact match - so we will mark this with orange style.
#If there is an exact match in context_commands - then we will mark this with the red style
#to indicate that this executable is overshadowed by a command in the current context.
#we may have multiple tcl commands that conflict with the same executable.
#e.g DIG and dig.
if {[llength [set ncmatches [lsearch -all -inline -nocase $context_commands [file rootname $exe]]]]} {
if {[set exactmatch [lsearch -exact $context_commands [file rootname $exe]]] ne ""} {
foreach nc $ncmatches {
set nc [namespace eval $nscaller [list namespace origin $nc]]
if {$nc eq $exactmatch} {
lappend conflicts $ERR$nc$RST
} else {
lappend conflicts "$WRN$nc$RST"
}
}
} else {
foreach nc $ncmatches {
set nc [namespace eval $nscaller [namespace origin $nc]]
lappend conflicts "$WRN$nc$RST"
}
}
} else {
if {[llength [set ncmatches [lsearch -all -inline -nocase $context_commands $exe]]]} {
if {[set exactmatch [lsearch -exact $context_commands $exe]] ne ""} {
foreach nc $ncmatches {
set nc [namespace eval $nscaller [namespace origin $nc]]
if {$nc eq $exactmatch} {
lappend conflicts $ERR$nc$RST
} else {
lappend conflicts "$WRN$nc$RST"
}
}
} else {
foreach nc $ncmatches {
set nc [namespace eval $nscaller [namespace origin $nc]]
lappend conflicts "$WRN$nc$RST"
}
}
}
}
} else {
#check for any exact matches in context_commands
if {$exe in $context_commands} {
lappend conflicts $ERR$exe$RST
}
}
if {$is_overshadowed} {
lappend display_executables "$SDW$exe$RST"
} else {
lappend display_executables $exe
}
}
} else {
#executable not found in bin_info dict - this shouldn't happen - but if it does we will just treat it as not overshadowed and include it in the display.
lappend display_executables $WRN$exe$RST
}
}
if {[llength $overshadowed]} {
lappend thisrow "$ERR[llength $overshadowed]$RST"
} else {
lappend thisrow "0"
}
if {[llength $display_executables]} {
lappend thisrow [textblock::list_as_table -columns $subcols -show_edge 0 $display_executables]
} else {
lappend thisrow ""
}
if {[llength $conflicts]} {
#lappend thisrow [textblock::list_as_table -columns $subcols -show_edge 0 $conflicts]
lappend thisrow [join $conflicts \n]
} else {
lappend thisrow ""
}
} else {
lappend thisrow ""
lappend thisrow ""
lappend thisrow ""
lappend thisrow "(not a directory or no executables found)"
lappend thisrow ""
}
} else {
#this is a duplicate path entry - we want to show it as a duplicate of the original path entry.
set original_path_idx [lindex $indices 0]
set original_path [lindex [dict get $d_path_info $pnorm original_paths] 0]
#duplicate paths might be cased differently.
lappend thisrow "$ERR$p (repeated pathentry)\n original at index $original_path_idx as\n$original_path$RST"
set overshadowed [list]
set conflicts [list]
set display_executables [list]
if {[dict exists $d_index_executables $original_path_idx]} {
set executables [dict get $d_index_executables $original_path_idx]
lappend thisrow [llength $executables]
foreach exe $executables {
set exe_key [string tolower $exe]
if {[dict exists $d_bin_info $exe_key]} {
set bindata [dict get $d_bin_info $exe_key]
set path_indices [dict get $bindata path_indices]
set is_overshadowed 0
foreach pi $path_indices {
if {$pi < $pidx} {
lappend overshadowed $exe
set is_overshadowed 1
break
}
}
#dupe will always have all exes as overshadowed by the original.
#don't need to waste time and screen space to display duplicate info - the user should tidy up the PATH.
#if {$is_overshadowed} {
# lappend display_executables "$SDW$exe$RST"
#} else {
# lappend display_executables $exe
#}
}
}
} else {
#this shouldn't happen - but if it does we will just treat it as not overshadowed and include it in the display.
lappend thisrow "(not a directory or no executables found)"
}
if {[llength $overshadowed]} {
lappend thisrow "$ERR[llength $overshadowed]$RST"
} else {
lappend thisrow "0"
}
if {[llength $display_executables]} {
lappend thisrow [textblock::list_as_table -columns $subcols -show_edge 0 $display_executables]
} else {
lappend thisrow ""
}
lappend thisrow "" ;#don't show conflict info for duplicate paths - as the user should tidy up the PATH to remove duplicates, and the conflict info will be the same as the original path entry.
}
if {[llength $matched_paths] < [llength $all_paths]} {
#if there is any filtering of paths - then we want to show all these paths whether or not there are any matches for binglobs
if {$p in $matched_paths} {
lappend rows $thisrow
}
} else {
#no specific filtering of paths - so only show rows where there are matches for binglobs
if {[lsearch -exact $binglobs "*"] >= 0} {
lappend rows $thisrow
} else {
#end-1 is the executables column.
#if there are no matches for binglobs then we'll hide the row.
if {[string length [lindex $thisrow end-1]] > 0} {
lappend rows $thisrow
}
}
}
incr pidx
}
set t [textblock::table -return tableobject -rows $rows -headers $headers]
return [$t print]
}
#-------------------------------------------------------------------
@ -8024,8 +8328,8 @@ namespace eval punk {
set title "[a+ brightgreen] Filesystem navigation: "
set cmdinfo [list]
lappend cmdinfo [list ./ "?${I}glob${NI}?" "view/change dir, list dirs."]
lappend cmdinfo [list ../ "?${I}path${NI}" "go up one dir, then to path if given"]
lappend cmdinfo [list .// "?${I}glob${NI}?" "view/change dir, list dirs and files"]
lappend cmdinfo [list ../ "?${I}path${NI}" "go up one dir, then to path if given"]
lappend cmdinfo [list newdir "${I}subdir${NI}..." "make new dir or dirs and show status"]
lappend cmdinfo [list fcat "${I}file ?file?...${NI}" "cat file(s)"]
set t [textblock::class::table new -minwidth 80 -show_seps 0]
@ -8238,11 +8542,33 @@ namespace eval punk {
lappend chunks [list stdout $text]
}
console - term - terminal {
set term_env_vars {TERM TERM_PROGRAM TERM_PROGRAM_VERSION}
set term_dict [dict create]
foreach e $term_env_vars {
if {[info exists ::env($e)]} {
dict set term_dict $e [set ::env($e)]
} else {
dict set term_dict $e "(NOT SET)"
}
}
set text "Terminal environment variables:\n"
append text [punk::lib::showdict $term_dict] \n
lappend chunks [list stdout $text]
set text ""
if {[catch {package require punk::console} result]} {
set text "Unable to load punk::console package - cannot test\n$result"
lappend chunks [list stdout $text]
} else {
if {![catch {punk::console::class_info} console_class_info]} {
set text "Terminal class info (from device secondary attributes query to terminal):\n"
append text [punk::lib::showdict $console_class_info] \n
} else {
set text "Unable to query terminal class info - err:$console_class_info\n"
}
lappend chunks [list stdout $text]
set indent [string repeat " " [string length "WARNING: "]]
lappend cstring_tests [dict create\
type "PM "\
@ -8339,7 +8665,7 @@ namespace eval punk {
}
}
if {![string length $warningblock]} {
set text "No terminal warnings\n"
set text "[a+ green]No terminal warnings[a]\n"
lappend chunks [list stdout $text]
}
}
@ -8351,6 +8677,7 @@ namespace eval punk {
"tcl" "Tcl version warnings"\
"env|environment" "punkshell environment vars"\
"console|terminal" "Some console behaviour tests and warnings"\
"*" "Try to find help on the topic as a command or external executable"\
]
set t [textblock::class::table new -show_seps 0]

1
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/aliascore-0.1.0.tm

@ -117,6 +117,7 @@ tcl::namespace::eval punk::aliascore {
plist {::punk::lib::pdict -roottype list}\
showlist {::punk::lib::showdict -roottype list}\
rehash ::punk::auto_exec::rehash\
hash ::punk::auto_exec::hash\
showdict ::punk::lib::showdict\
ansistrip ::punk::ansi::ansistrip\
stripansi ::punk::ansi::ansistrip\

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

@ -3920,7 +3920,10 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
}
lappend PUNKARGS [list {
@id -id ::punk::ansi::a+
@cmd -name "punk::ansi::a+" -help\
@cmd -name "punk::ansi::a+"\
-summary\
"ANSI SGR code generator with no reset prefix"\
-help\
"Returns an ANSI sgr escape sequence based on the list of supplied codes.
Unlike punk::ansi::a - it is not prefixed with an ANSI reset.
"
@ -3935,7 +3938,10 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
lappend PUNKARGS [list {
@id -id ::punk::ansi::a
@cmd -name "punk::ansi::a" -help\
@cmd -name "punk::ansi::a"\
-summary\
"ANSI SGR code generator with reset prefix"\
-help\
"Returns an ANSI sgr escape sequence based on the list of supplied codes.
Unlike punk::ansi::a+ - it is prefixed with an ANSI reset.
"
@ -6865,7 +6871,14 @@ tcl::namespace::eval punk::ansi::ta {
#may be same as detect - kept in case detect needs to diverge
#variable re_ansi_split "${re_csi_code}|${re_esc_osc1}|${re_esc_osc2}|${re_esc_osc3}|${re_standalones}|${re_ST}|${re_g0_open}|${re_g0_close}"
set re_ansi_split $re_ansi_detect
#experiment with const for a regex - seems to make no difference to performance - but it does make it clear that the regex is not intended to be modified at runtime
if {[catch {const re_ansi_split $re_ansi_detect}]} {
#tcl 9 has const but tcl 8 doesn't - so we just set it as a normal variable
variable re_ansi_split
set re_ansi_split $re_ansi_detect
}
variable re_ansi_split_multi
if {[string first (?x) $re_ansi_split] == 0} {
set re_ansi_split_multi "(?x)(?:[string range ${re_ansi_split} 4 end])+"
@ -7161,7 +7174,7 @@ tcl::namespace::eval punk::ansi::ta {
#micro optimisations on split_codes to avoid function calls and make re var local tend to yield very little benefit (sub uS diff on calls that commonly take 10s/100s of uSeconds)
#like split_codes - but each ansi-escape is split out separately (with empty string of plaintext between codes so even/odd indices for plain ansi still holds)
#- the slightly simpler regex than split_codes means that it will be slightly faster than keeping the codes grouped.
#- the regex is slighly simpler than for split_codes - but split_codes is faster when there are consecutive codes.
proc split_codes_single {text} {
if {$text eq ""} {
return {}
@ -7177,7 +7190,26 @@ tcl::namespace::eval punk::ansi::ta {
#set next [lindex $cr 1]+1 ;#text index-expression for string range
}
lappend list [tcl::string::range $text $next end]
return $list
}
proc split_codes_single2 {text} {
return [_perlish_split2 $::punk::ansi::ta::re_ansi_split $text]
}
proc split_codes_single3 {text} {
#no faster
if {$text eq ""} {
return {}
}
variable re_ansi_split
set next 0
set coderanges [regexp -indices -all -inline -- $re_ansi_split $text]
set list [lrepeat [expr {[llength $coderanges]*2}] ""]
set r 0
foreach cr $coderanges {
ledit list $r $r+1 [tcl::string::range $text $next [lindex $cr 0]-1] [tcl::string::range $text [lindex $cr 0] [lindex $cr 1]]
set next [expr {[lindex $cr 1]+1}]
incr r
}
return [list {*}$list [tcl::string::range $text $next end]]
}
proc split_codes_single2 {text} {
variable re_ansi_split
@ -7202,7 +7234,6 @@ tcl::namespace::eval punk::ansi::ta {
set next [expr {[lindex $cr 1]+1}]
}
lappend list [tcl::string::range $text $next end]
return $list
}
proc _perlish_split2 {re text} {
if {$text eq ""} {

259
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/args-0.2.1.tm

@ -771,9 +771,9 @@ tcl::namespace::eval punk::args {
literal(<string>)
(exact match for string)
literalprefix(<string>)
(prefix match for string, other literal and literalprefix
(tcl::prefix::match of string, other literal and literalprefix
entries specified as alternates using | are used in the
calculation)
unique prefix calculation)
stringstartswith(<string>)
(value must match glob <string>*)
The value of string must not contain pipe char '|'
@ -785,7 +785,7 @@ tcl::namespace::eval punk::args {
e.g literalprefix(text)|literalprefix(binary)
(when all in the pipe-delimited type-alternates set are
literal or literalprefix - this is similar to the -choices
option)
option with -choiceprefix true)
and more.. (todo - document here)
@ -906,6 +906,8 @@ tcl::namespace::eval punk::args {
is preserved.
-minsize (type dependant)
-maxsize (type dependant)
-mincap {only valid for regex type - min number of captures}
-maxcap {only valid for regex type - max number of captures}
-range (type dependant - only valid if -type is a single item)
-typeranges (list with same number of elements as -type)
-help <string>
@ -2529,6 +2531,15 @@ tcl::namespace::eval punk::args {
#review -solo 1 vs -type none ? conflicting values?
tcl::dict::set spec_merged $spec $specval
}
-mincap - -maxcap {
#todo - allow as default for @leaders, @opts and @values when default -type there is regex or regexp?
#only applies to type regex
set tp [tcl::dict::get $spec_merged -type]
if {![string match *regex* $tp]} {
error "punk::args::resolve - invalid use of '$spec' key for argument '$argname'. '$spec' only applies to arguments with a type of regex or regexp. argument has type '$tp' @id:$DEF_definition_id"
}
tcl::dict::set spec_merged $spec $specval
}
-range {
#allow simple case to be specified without additional list wrapping
#only multi-types require full list specification
@ -2624,7 +2635,9 @@ tcl::namespace::eval punk::args {
-range -typeranges\
-default -defaultdisplaytype -typedefaults\
-minsize -maxsize -choices -choicegroups\
-mincap -maxcap\
-choicemultiple -choicecolumns -choiceprefix -choiceprefixdenylist -choiceprefixreservelist -choicerestricted\
-choicelabels -choiceinfo \
-unindentedfields\
-nocase -optional -multiple -validate_ansistripped -allow_ansi -strip_ansi -help\
-multipleunique -choicemultipleunique -choicemultipleuniqueset\
@ -3816,7 +3829,8 @@ tcl::namespace::eval punk::args {
set arg_error_CLR_info(check) [a+ brightgreen bold]
set arg_error_CLR_info(choiceprefix) [a+ brightgreen bold]
set arg_error_CLR_info(groupname) [a+ cyan bold]
set arg_error_CLR_info(ansiborder) [a+ brightcyan bold]
#set arg_error_CLR_info(ansiborder) [a+ brightcyan bold]
set arg_error_CLR_info(ansiborder) [a+ term-grey23 bold]
set arg_error_CLR_info(ansibase_header) [a+ cyan]
set arg_error_CLR_info(ansibase_body) [a+ white]
variable arg_error_CLR_error
@ -5236,13 +5250,13 @@ tcl::namespace::eval punk::args {
switch -- $tailtype {
withid {
#JJJ
#set id [lindex $opts_and_vals 0]
set deflist [raw_def [lindex $opts_and_vals 0]]
if {[llength $deflist] == 0} {
if {[llength $opts_and_vals] != 1} {
#error "punk::args::parse - invalid call. Expected exactly one argument after 'withid'"
punk::args::parse $args withid ::punk::args::parse
}
set id [lindex $opts_and_vals 0]
error "punk::args::parse - no such id: $id"
}
}
@ -5415,7 +5429,8 @@ tcl::namespace::eval punk::args {
}
#return number of values we can assign to cater for variable length clauses such as {"elseif" expr "?then?" body}
#return number of values we can assign to cater for variable length clauses such as:
# {"elseif" expr "?then?" body}
#review - efficiency? each time we call this - we are looking ahead at the same info
proc _get_dict_can_assign_value {idx values nameidx names namesreceived formdict} {
set ARG_INFO [dict get $formdict ARG_INFO]
@ -5426,12 +5441,23 @@ tcl::namespace::eval punk::args {
#todo - work backwards with any (optional or not) literals at tail that match our values - and remove from assignability.
set ridx 0
#puts "-=============- thisname:'$thisname' thistype:'$thistype' tailnames:'$tailnames' all_remaining:'$all_remaining' [info level -2]"
foreach clausename [lreverse $tailnames] {
#puts "=============== clausename:$clausename all_remaining: $all_remaining"
#puts "=============== thisname:'$thisname' thistype:'$thistype' clausename:'$clausename' all_remaining:'$all_remaining'"
set clause_is_multiple [dict get $ARG_INFO $clausename -multiple]
set clause_is_optional [dict get $ARG_INFO $clausename -optional]
set typelist [dict get $ARG_INFO $clausename -type]
#---------------
#review - not quite right to look for literal* in typelist
#- we should be looking for any type-alternate that starts with literal( or literalprefix(
#- but for now we require the whole type to be literal* if it's a literal match type.
# We should probably also support stringstartswith(*) and stringendswith(*) too.
#also consider that -choices {abc def} is effectively a literal match type too - we should support that here as well.
if {[lsearch $typelist literal*] == -1} {
break
}
#---------------
set max_clause_length [llength $typelist]
if {$max_clause_length == 1} {
#basic case
@ -5452,28 +5478,50 @@ tcl::namespace::eval punk::args {
}
#foreach tp_alternative [split $tp |] {}
foreach tp_alternative [_split_type_expression $tp] {
set tp_alternatives [_split_type_expression $tp]
foreach tp_alternative $tp_alternatives {
switch -exact -- [lindex $tp_alternative 0] {
literal {
set litinfo [string range $tp 7 end] ;#get bracketed part if of form literal(xxx)
set match [lindex $tp_alternative 1]
set match [lindex $tp_alternative 1] ;#was bracketed part if of form literal(xxx)
if {$v eq $match} {
set alloc_ok 1
ledit all_remaining end end
if {![dict get $ARG_INFO $clausename -multiple]} {
if {!$clause_is_multiple} {
ledit tailnames end end
}
#the type (or one of the possible type alternates) matched a literal
break
}
}
literalprefix {
set prefix_of [lindex $tp_alternative 1]
#get list of literal and literalprefix values in the current list of tp_alternatives so we can construct list of alternatives for tcl::prefix::match prefix calculation.
#todo - consider if this clause also has -choices {abc def} - we should support those as well here as literal matches for the purposes of calculating the prefix match.
# (this is somewhat of an edge case but sometimes it's useful to specify a -type when -choices is used with -choicerestricted false, to allow only specific values not in the choices list.)
set comparelist [list]
foreach alt $tp_alternatives {
switch -exact -- [lindex $alt 0] {
literal - literalprefix {
lappend comparelist [lindex $alt 1]
}
}
}
set fullmatch [tcl::prefix::match -error "" $comparelist $v]
if {$fullmatch eq $prefix_of} {
set alloc_ok 1
ledit all_remaining end end
if {!$clause_is_multiple} {
ledit tailnames end end
}
break
}
}
stringstartswith {
set pfx [lindex $tp_alternative 1]
if {[string match "$pfx*" $v]} {
set alloc_ok 1
set alloc_ok 1
ledit all_remaining end end
if {![dict get $ARG_INFO $clausename -multiple]} {
if {!$clause_is_multiple} {
ledit tailnames end end
}
break
@ -5483,10 +5531,9 @@ tcl::namespace::eval punk::args {
stringendswith {
set sfx [lindex $tp_alternative 1]
if {[string match "*$sfx" $v]} {
set alloc_ok 1
set alloc_ok 1
ledit all_remaining end end
if {![dict get $ARG_INFO $clausename -multiple]} {
if {!$clause_is_multiple} {
ledit tailnames end end
}
break
@ -5497,7 +5544,7 @@ tcl::namespace::eval punk::args {
}
}
if {!$alloc_ok} {
if {![dict get $ARG_INFO $clausename -optional]} {
if {!$clause_is_optional} {
break
}
}
@ -5519,6 +5566,7 @@ tcl::namespace::eval punk::args {
set reverse_type_index 0
#todo handle type-alternates
# for example: -type {string literal(x)|literal(y)}
# -type {string literal(max)|literal(min)|int}
foreach tp $rtypelist {
#set rv [lindex $rcvals end-$alloc_count]
set rv [lindex $all_remaining end-$alloc_count]
@ -5528,8 +5576,24 @@ tcl::namespace::eval punk::args {
set clause_member_optional 0
}
set tp [string trim $tp ?]
puts "_get_dict_can_assign_value: checking tp '$tp' against value '$rv'"
switch -glob -- $tp {
literal* {
"literal(*" {
set litmatch [string range $tp 8 end-1]
if {$rv eq $litmatch} {
set alloc_ok 1 ;#we need at least one literal-match to set alloc_ok
incr alloc_count
} else {
if {$clause_member_optional} {
#
} else {
set alloc_ok 0
break
}
}
}
XXXliteral* {
#JJJ
set litinfo [string range $tp 7 end]
set match [string range $litinfo 1 end-1]
#todo -literalprefix
@ -5594,7 +5658,7 @@ tcl::namespace::eval punk::args {
#set all_remaining [lrange $all_remaining end-$n end]
set all_remaining [lrange $all_remaining 0 end-$alloc_count]
#don't lpop if -multiple true
if {![dict get $ARG_INFO $clausename -multiple]} {
if {!$clause_is_multiple} {
#lpop tailnames
ledit tailnames end end
}
@ -6421,13 +6485,48 @@ tcl::namespace::eval punk::args {
break
}
regex - regexp {
#todo - allow -min and -max to specify number of allowed subexpressions(capture groups) present in regex?
if {[catch {regexp -about $e_check} re_about_msg]} {
set msg "$argclass $argname for %caller% requires type regexp. $re_about_msg. Received: '$e_check'"
lset clause_results $c_idx $a_idx [list errorcode [list PUNKARGS VALIDATION [list typemismatch $type] -badarg $argname -argspecs $argspecs] msg $msg]
} else {
lset clause_results $c_idx $a_idx 1
break
#optional -mincap and -maxcap specify number of allowed subexpressions(capture groups) present in regex
set num_caps [lindex $re_about_msg 0]
set mincap 0 ;#default
set maxcap -1 ;#default -1 for unlimited
if {[dict exists $thisarg_checks -mincap]} {
set mincap [dict get $thisarg_checks -mincap]
}
if {[dict exists $thisarg_checks -maxcap]} {
set maxcap [dict get $thisarg_checks -maxcap]
}
if {$maxcap == -1 && $mincap == 0} {
#no cap limits - just accept the regex as valid
lset clause_results $c_idx $a_idx 1
break
} else {
#we have at least one cap limit - we need to count the number of subexpressions in the regex and check it against the limits
if {$maxcap == -1} {
#unlimited maxcap - just check mincap
if {$num_caps < $mincap} {
set msg "$argclass $argname for %caller% requires type regexp with at least $mincap capture groups. Received regex has only $num_caps capture groups. Regex: '$e_check'"
lset clause_results $c_idx $a_idx [list errorcode [list PUNKARGS VALIDATION [list typemismatch $type] -badarg $argname -argspecs $argspecs] msg $msg]
} else {
lset clause_results $c_idx $a_idx 1
break
}
} else {
if {$num_caps < $mincap} {
set msg "$argclass $argname for %caller% requires type regexp with at least $mincap capture groups. Received regex has only $num_caps capture groups. Regex: '$e_check'"
lset clause_results $c_idx $a_idx [list errorcode [list PUNKARGS VALIDATION [list typemismatch $type] -badarg $argname -argspecs $argspecs] msg $msg]
} elseif {$num_caps > $maxcap} {
set msg "$argclass $argname for %caller% requires type regexp with no more than $maxcap capture groups. Received regex has $num_caps capture groups. Regex: '$e_check'"
lset clause_results $c_idx $a_idx [list errorcode [list PUNKARGS VALIDATION [list typemismatch $type] -badarg $argname -argspecs $argspecs] msg $msg]
} else {
lset clause_results $c_idx $a_idx 1
break
}
}
} ;#every leaf of this nested if should have an lset clause_results with 1 for pass or errorcode/msg for fail
}
}
indexexpression {
@ -6776,10 +6875,30 @@ tcl::namespace::eval punk::args {
break
}
}
path -
file -
directory -
directory {
#see comments in existingpath/existingfile/existingdirectory case about the challenges of validating filesystem paths in a general way that works across platforms and use cases.
if {[tcl::string::length $e_check]==0 || [string first \0 $e_check] >= 0} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which doesn't look like it could be a path, file or directory"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
}
lset clause_results $c_idx $a_idx 1
}
existingpath -
existingfile -
existingdirectory {
#do we need types for relative vs absolute paths? readable writable executable owned?
#on windows limit to certain file extensions?
#fileutil::magic::filetype?
#Perhaps these are steps too far for a general validation framework.
#consider - callback validation functions instead?
#ideally we want to define callback validation functions that can work not just on a single argument at a time.
#e.g for testing that 2 file arguments do or don't refer to the same file or are in same directory or same filesystem etc.
#we have to support file and directory names on all platforms - and even characters illegal on a filesystem/platform may need to be passed.
#For example a file/folder may be created with an illegal name on a platform (or mounted on it) and be mapped to another string on the filesystem
#- yet it may remain accessible to commands such as file stat etc via the string with 'illegal' characters as well as its underlying stored (mapped) name.
@ -6790,17 +6909,43 @@ tcl::namespace::eval punk::args {
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
}
if {$type eq "existingfile"} {
if {![file exists $e_check]} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which is not an existing file"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
# -------------------------------------------------------
#review - what do we want to happen with links?
#on unix TCL's file readlink should reliably give us a path to determine the type pointed to.
#on windows we can do so if the link happens to be a junction.
#however on windows we can also have symbolic links which are not junctions and which may point to files or directories
#- but unfortunately tcl's file readlink doesn't seem to be able to read them at all - raises an error.
#(the error seems to be different for a file vs a directory target - but this seems an unreliable mechanism to determine the type of the target)
#At the moment TCL's 'file isfile' and 'file isdirectory' both seem to do the right things for links
#despite the above - treating them as the type of their target
# review whether this is reliable in all cases on windows.
# -------------------------------------------------------
#windows shortcuts (.lnk files) can point to a file or directory - but we can quite reasonably treat them only as files,
#as users *probably* won't have the expectation that a shortcut which points to a directory should be treated as a directory.
switch -exact -- $type {
existingpath {
if {![file exists $e_check]} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which is not an existing path"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
}
}
} elseif {$type eq "existingdirectory"} {
if {![file isdirectory $e_check]} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which is not an existing directory"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
existingfile {
if {![file isfile $e_check]} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which is not an existing file"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
}
}
existingdirectory {
if {![file isdirectory $e_check]} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which is not an existing directory"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
}
}
}
lset clause_results $c_idx $a_idx 1
@ -6809,6 +6954,11 @@ tcl::namespace::eval punk::args {
existingportabledirectory -
portablefile -
portabledirectory {
#review - many absolute paths are not strictly portable when considered as a whole e.g /usr/local/bin c:/test
#- but the idea was more about the directory and file name components being portable excluding the first component.
#this concept may need work as it's unintuitive what it means to be a portable file/directory vs not.
#what about windows specific paths such as //?/ //./ or UNC paths?
if {[tcl::string::length $e_check]==0 || [string first \0 $e_check] >= 0 || [punk::winpath::illegalname_test $e_check]} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which doesn't look like it could be a portable file or directory (must pass punk::winpath::illegalname_test)"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
@ -8699,7 +8849,7 @@ tcl::namespace::eval punk::args {
set leadername [lindex $LEADER_NAMES $nameidx]
set ldr [lindex $leaders $ldridx]
if {$leadername ne ""} {
set leadertypelist [tcl::dict::get $argstate $leadername -type]
set leadertypelist [tcl::dict::get $argstate $leadername -type] ;#often a single type, but can be a list of types (possibly with some optional) for a type that is a clause accepting multiple values.
set leader_clause_size [llength $leadertypelist]
set assign_d [_get_dict_can_assign_value $ldridx $leaders $nameidx $LEADER_NAMES $leadernames_received $formdict]
@ -8738,11 +8888,23 @@ tcl::namespace::eval punk::args {
set clauseval $resultlist
incr ldridx [expr {$consumed - 1}]
#not quite right.. this sets the -type for all clauses - but they should run independently
#e.g if expr {} elseif 2 {script2} elseif 3 then {script3} (where elseif clause defined as "literal(elseif) expr ?literal(then)? script")
#the elseif 2 {script2} will raise an error because the newtypelist from elseif 3 then {script3} overwrote the newtypelist where then was given the type ?omitted-...?
#not quite right.. this modifies the -type for all clauses with this name - but for -multiple true each instance should really be considered separately.
#e.g when a subelement-containing clause is allowed to appear multiple times (-multiple true)
# - we may hava a situation where the supplied arguments do and don't omit optional subelements,
# and the newtypelist from one clause may overwrite the newtypelist from the other clause where the optional subelement was omitted in one arg, but not in the other arg.
# - if expr {} elseif 2 {script2} elseif 3 then {script3}
# - (where elseif clause defined as "literal(elseif) expr ?literal(then)? script")
# The elseif 2 {script2} will reassign the type as "literal(elseif) expr ?omitted-literal(then)? script"
# when the elseif 3 then {script3} is processed, 'then' is now considered against the type ?ommitted-literal(then)?
#which (as a non-recognised type is therefore not validated ) will then
# allow any value instead of 'then' to pass.
tcl::dict::set argstate $leadername -type $newtypelist ;#(possible ?omitted-<type>? and ?defaulted-<type>? entries
#see argument_clause_typestate in value processing loop below for more handling of this issue regarding -multiple true clauses with optional subelements
#todo - synchronize with value processing loop below
#- consider refactor to a common procedure for handling this issue of tracking updated typelist state for optional subelements in -multiple true clauses
#incorrect -don't update default -type info.
#tcl::dict::set argstate $leadername -type $newtypelist ;#(possible ?omitted-<type>? and ?defaulted-<type>? entries
}
if {[tcl::dict::get $argstate $leadername -multiple]} {
@ -8904,7 +9066,7 @@ tcl::namespace::eval punk::args {
}
#incorrect - we shouldn't update the default. see argument_clause_typestate dict of lists of -type
tcl::dict::set argstate $valname -type $newtypelist ;#(possible ?omitted-<type>? and ?defaulted-<type>? entries
#tcl::dict::set argstate $valname -type $newtypelist ;#(possible ?omitted-<type>? and ?defaulted-<type>? and ?validated-<type>? entries
}
if {[tcl::dict::get $argstate $valname -multiple]} {
@ -9206,7 +9368,7 @@ tcl::namespace::eval punk::args {
}
set vlist_typelist [list]
if {[dict exists $argument_clause_typestate $argname]} {
#lookup saved newtypelist (argument_clause_typelist) from can_assign_value result where some optionals were given type ?omitted-<tp>? or ?defaulted-<tp>?
#lookup saved newtypelist (argument_clause_typelist) from can_assign_value result where some optionals were given type ?omitted-<tp>? or ?defaulted-<tp>? or ?validated-<tp>?.
# args.test: parse_withdef_value_clause_missing_optional_multiple
set vlist_typelist [dict get $argument_clause_typestate $argname]
} else {
@ -9315,11 +9477,12 @@ tcl::namespace::eval punk::args {
#fast fail on the wrong number of choices
if {[llength $c_list] < $choicemultiple_min} {
set msg "$argclass $argname for %caller% requires at least $choicemultiple_min choices. Received [llength $c_list] choices."
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list choicecount [llength $c_list] minchoices $choicemultiple_min maxchoices $choicemultiple_max] -badarg $argname]] $msg
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list choicecount [llength $c_list] minchoices $choicemultiple_min maxchoices $choicemultiple_max] -badarg $argname -argspecs $argspecs]] $msg
#return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list optionmissing $full_missing received $flagsreceived] -argspecs $argspecs]] $msg
}
if {$choicemultiple_max != -1 && [llength $c_list] > $choicemultiple_max} {
set msg "$argclass $argname for %caller% requires at most $choicemultiple_max choices. Received [llength $c_list] choices."
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list choicecount [llength $c_list] minchoices $choicemultiple_min maxchoices $choicemultiple_max] -badarg $argname]] $msg
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list choicecount [llength $c_list] minchoices $choicemultiple_min maxchoices $choicemultiple_max] -badarg $argname -argspecs $argspecs]] $msg
}
#-----------------------------------
@ -9435,7 +9598,19 @@ tcl::namespace::eval punk::args {
}
tcl::dict::set $dname $argname_or_ident $existing
} else {
lset existing $element_index $choice_idx $chosen
#test required.
# punk::args::parse {{read write w}} withdef @values {mode -type list -choices {read write} -choicemultiple {1 -1}}
#puts ">>> clause_size $clause_size"
#puts ">>> existing $existing"
#puts ">>> lset existing $element_index $choice_idx $chosen"
if {$clause_size == 1} {
#e.g -type list
#we have multiple choices allowed for a single element clause because that clause type is a list.
lset existing $choice_idx $chosen
} else {
#e.g -type {any any}
lset existing $element_index $choice_idx $chosen
}
tcl::dict::set $dname $argname_or_ident $existing
}
}

438
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/args/moduledoc/tclcore-0.1.0.tm

@ -102,7 +102,8 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
set manbase_tcl "https://tcl.tk/man/tcl/TclCmd"
set manbase_ext .htm
} else {
set manbase_tcl "https://tcl.tk/man/tcl9.0/TclCmd"
set tclv [info tclversion] ;#e.g 9.0 9.1
set manbase_tcl "https://tcl.tk/man/tcl${tclv}/TclCmd"
set manbase_ext .html
}
proc manpage_tcl {cmd} {
@ -1468,7 +1469,10 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
namespace eval argdoc {
lappend PUNKARGS [list {
@id -id ::tcl::chan::blocked
@cmd -name "Built-in: tcl::chan::blocked" -help\
@cmd -name "Built-in: tcl::chan::blocked"\
-summary\
"Test whether the last input operation failed because it would have blocked."\
-help\
"This tests whether the last input operation on the channel called ${$I}channel${$NI}
failed because it would otherwise have caused the process to block, and returns 1
if that was the case. It returns 0 otherwise. Note that this only ever returns 1
@ -1481,15 +1485,19 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
lappend PUNKARGS [list {
@id -id ::tcl::chan::close
@cmd -name "Built-in: tcl::chan::close" -help\
@cmd -name "Built-in: tcl::chan::close"\
-summary\
"Close and destroy a channel."\
-help\
"Close and destroy the channel called channel. Note that this deletes all existing file-events
registered on the channel. If the direction argument (which must be read or write or any
registered on the channel. If the direction argument (which must be ${$B}read${$N} or ${$B}write${$N} or any
unique abbreviation of them) is present, the channel will only be half-closed, so that it can
go from being read-write to write-only or read-only respectively. If a read-only channel is
closed for reading, it is the same as if the channel is fully closed, and respectively similar
for write-only channels. Without the direction argument, the channel is closed for both reading
and writing (but only if those directions are currently open). It is an error to close a
read-only channel for writing, or a write-only channel for reading.
As part of closing the channel, all buffered output is flushed to the channel's output device
(only if the channel is ceasing to be writable), any buffered input is discarded (only if the
channel is ceasing to be readable), the underlying operating system resource is closed and
@ -1540,6 +1548,7 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
{Query/set channel configuration options}\
-help\
{Query or set the configuration options of the channel named ${$I}channel${$NI}
If no ${$I}optionName${$NI} or ${$I}value${$NI} arguments are supplied, the
command returns a list containing alternating option names and values for the
channel. If ${$I}optionName${$NI} is supplied but no ${$I}value${$NI} then the
@ -1809,6 +1818,57 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
}
}]
lappend PUNKARGS [list {
@id -id ::tcl::chan::create
@cmd -name "Built-in: tcl::chan::create"\
-summary\
"Create new script level channel."\
-help\
"This subcommand creates a new script level channel using the command prefix ${$I}cmdPrefix${$NI} as its handler.
Any such channel is called a ${$B}reflected${$N} channel. The specified command prefix, ${$I}cmdPrefix${$NI}, must be a non-empty list,
and should provide the API described in the ${$B}refchan${$N} manual page. The handle of the new channel is returned as the
result of the ${$B}chan create${$N} command, and the channel is open. Use either ${$B}close${$N} or ${$B}chan close${$N} to remove the channel.
The argument mode specifies if the new channel is opened for reading, writing, or both. It has to be a list
containing any of the strings “read” or “write”, The list must have at least one element, as a channel you can
neither write to nor read from makes no sense. The handler command for the new channel must support the chosen mode,
or an error is thrown.
The command prefix is executed in the global namespace, at the top of call stack, following the appending of arguments
as described in the ${$B}refchan${$N} manual page. Command resolution happens at the time of the call. Renaming the command, or
destroying it means that the next call of a handler method may fail, causing the channel command invoking the handler
to fail as well. Depending on the subcommand being invoked, the error message may not be able to explain the reason
for that failure.
Every channel created with this subcommand knows which interpreter it was created in, and only ever executes its
handler command in that interpreter, even if the channel was shared with and/or was moved into a different interpreter.
Each reflected channel also knows the thread it was created in, and executes its handler command only in that thread,
even if the channel was moved into a different thread. To this end all invocations of the handler are forwarded to the
original thread by posting special events to it. This means that the original thread (i.e. the thread that executed the
${$B}chan create${$N} command) must have an active event loop, i.e. it must be able to process such events. Otherwise the thread
sending them will block indefinitely. Deadlock may occur.
Note that this permits the creation of a channel whose two endpoints live in two different threads, providing a
stream-oriented bridge between these threads. In other words, we can provide a way for regular stream communication
between threads instead of having to send commands.
When a thread or interpreter is deleted, all channels created with this subcommand and using this thread/interpreter as
their computing base are deleted as well, in all interpreters they have been shared with or moved into, and in whatever
thread they have been transferred to. While this pulls the rug out under the other thread(s) and/or interpreter(s),
this cannot be avoided. Trying to use such a channel will cause the generation of a regular error about unknown channel
handles.
This subcommand is ${$B}safe${$N} and made accessible to safe interpreters. While it arranges for the execution of arbitrary Tcl
code the system also makes sure that the code is always executed within the safe interpreter."
@values -min 2 -max 2
#man page says must be at least one element in mode list.
#man page doesn't limit list to 2 elements long despite there being only 2 mode values
# - suggests things such as {r write read w ...} without limit on length is allowed
mode -type list -choices {read write} -choicemultiple {1 -1} -help\
"list of at least one of read write or abbreviations of these"
cmdprefix -type string
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::eof
@cmd -name "Built-in: tcl::chan::eof"\
@ -1823,7 +1883,57 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
""
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
#event
lappend PUNKARGS [list {
@id -id ::tcl::chan::event
@cmd -name "Built-in: tcl::chan::event"\
-summary\
"Create, delete or query a file event handler."\
-help\
"Arrange for the Tcl script script to be installed as a file event handler to be called whenever the channel
called channel enters the state described by event (which must be either readable or writable); only one such
handler may be installed per event per channel at a time. If script is the empty string, the current handler
is deleted (this also happens if the channel is closed or the interpreter deleted). If script is omitted, the
currently installed script is returned (or an empty string if no such handler is installed). The callback is
only performed if the event loop is being serviced (e.g. via vwait or update).
A file event handler is a binding between a channel and a script, such that the script is evaluated whenever
the channel becomes readable or writable. File event handlers are most commonly used to allow data to be
received from another process on an event-driven basis, so that the receiver can continue to interact with the
user or with other channels while waiting for the data to arrive. If an application invokes ${$B}chan gets${$N} or
${$B}chan read${$N} on a blocking channel when there is no input data available, the process will block; until the input
data arrives, it will not be able to service other events, so it will appear to the user to “freeze up”.
With ${$B}chan event${$N}, the process can tell when data is present and only invoke ${$B}chan gets${$N} or ${$B}chan read${$N} when they
will not block.
A channel is considered to be readable if there is unread data available on the underlying device. A channel is
also considered to be readable if there is unread data in an input buffer, except in the special case where the
most recent attempt to read from the channel was a ${$B}chan gets${$N} call that could not find a complete line in the
input buffer. This feature allows a file to be read a line at a time in non-blocking mode using events.
A channel is also considered to be readable if an end of file or error condition is present on the underlying
file or device. It is important for script to check for these conditions and handle them appropriately;
for example, if there is no special check for end of file, an infinite loop may occur where script reads no
data, returns, and is immediately invoked again.
A channel is considered to be writable if at least one byte of data can be written to the underlying file or
device without blocking, or if an error condition is present on the underlying file or device. Note that client
sockets opened in asynchronous mode become writable when they become connected or if the connection fails.
Event-driven I/O works best for channels that have been placed into non-blocking mode with the chan configure
command. In blocking mode, a ${$B}chan puts${$N} command may block if you give it more data than the underlying file or
device can accept, and a ${$B}chan gets${$N} or ${$B}chan read${$N} command will block if you attempt to read more data than is
ready; no events will be processed while the commands block. In non-blocking mode ${$B}chan puts${$N}, ${$B}chan read${$N}, and
${$B}chan gets${$N} never block.
The script for a file event is executed at global level (outside the context of any Tcl procedure) in the
interpreter in which the chan event command was invoked. If an error occurs while executing the script then the
command registered with interp bgerror is used to report the error. In addition, the file event handler is
deleted if it ever returns an error; this is done in order to prevent infinite loops due to buggy handlers."
@values -min 2 -max 3
channel
event -choices {readable writable}
script -optional 1
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::flush
@cmd -name "Built-in: tcl::chan::flush"\
@ -1878,9 +1988,58 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
channel
varName -optional 1
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
#isbinary
#names
#pending
lappend PUNKARGS [list {
@id -id ::tcl::chan::isbinary
@cmd -name "Built-in: tcl::chan::isbinary"\
-summary\
"Test if channel is binary (encoding iso8859-1, eofchar {}, translation lf)."\
-help\
"Test whether the channel called ${$I}channel${$NI} is a binary channel, returning 1 if it is and, and 0 otherwise.
A binary channel is a channel with iso8859-1 encoding, -eofchar set to {} and -translation set to lf."
@values -min 1 -max 1
channel
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
#chan names - deviation from online manual to add point about channel names and transformations
lappend PUNKARGS [list {
@id -id ::tcl::chan::names
@cmd -name "Built-in: tcl::chan::names"\
-summary\
"List all channel names. (toplevel)"\
-help\
{Produces a list of all channel names (*).
If pattern is specified, only those channel names that match it (according to the rules of string match)
will be returned.
* Note that the channel names returned are not necessarily the same as the channel names that are visible
in a given interpreter.
For example, if channel transformations are in use on stdin, stdout, or stderr, the channel names returned
will different for those channels.
e.g you may still be able to call ${$B}puts stdout "hello"${$N} even though ${$B}chan names${$N} does not return 'stdout'
It may instead show in the result list as something like 'file17f99e788b0'.
See the documentation for chan push for more details on this.}
@values -min 0 -max 1
pattern -optional 1 -default "*"
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::pending
@cmd -name "Built-in: tcl::chan::pending"\
-summary\
"Number of pending bytes buffered."\
-help\
"Depending on whether mode is input or output, returns the number of bytes of input or output (respectively)
currently buffered internally for channel (especially useful in a readable event callback to impose
application-specific limits on input line lengths to avoid a potential denial-of-service attack where a
hostile user crafts an extremely long line that exceeds the available memory to buffer it). Returns -1 if
the channel was not opened for the mode in question."
@values -min 2 -max 2
mode -choices {input output}
channel -type string
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::pipe
@cmd -name "Built-in: tcl::chan::pipe"\
@ -1921,6 +2080,24 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
channel -type string
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::push
@cmd -name "Built-in: tcl::chan::push"\
-summary\
"Add a new transformation on top of channel."\
-help\
"Adds a new transformation on top of the channel ${$I}channel${$NI}.
The ${$I}cmdPrefix${$NI} argument describes a list of one or more words which represent a handler
that will be used to implement the transformation. The command prefix must provide the
API described in the ${$B}transchan${$N} manual page. The result of this subcommand is a handle to
the transformation. Note that it is important to make sure that the transformation is
capable of supporting the channel mode that it is used with or this can make the channel
neither readable nor writable."
@values -min 2 -max 2
channel -type string
cmdPrefix -type string
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::puts
@cmd -name "Built-in: tcl::chan::puts"\
@ -2262,7 +2439,9 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
is equivalent to a false result. The key/value pairs
are tested in the order in which the keys were inserted
into the dictionary."
vars -type list -minsize 2 -maxsize 2 -typesynopsis {{keyVariable valueVariable}} -optional 0
vars -type list -minsize 2 -maxsize 2 -typesynopsis {{keyVariable valueVariable}} -optional 0 -help\
"Two element list of variable names to be used for the
key and value respectively"
script -type script
@form -form value
@ -2421,7 +2600,10 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
lappend PUNKARGS [list {
@id -id ::tcl::dict::map
@cmd -name "Built-in: tcl::dict::map" -help\
@cmd -name "Built-in: tcl::dict::map"\
-summary\
"Apply a transformation to each value of a dictionary, returning a new dictionary."\
-help\
"This command applies a transformation to each element of a dictionary,
returning a new dictionary. It takes three arguments: the first is a
two-element list of variable names (for the key and value respectively of
@ -2919,6 +3101,7 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
name -type string
} "@doc -name Manpage: -url [manpage_tcl file]" ]
#tcl 9+
lappend PUNKARGS [list {
@id -id ::tcl::file::home
@cmd -name "Built-in: tcl::file::home" -help\
@ -2952,6 +3135,48 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
#join
#link
lappend PUNKARGS [list {
@id -id ::tcl::file::link
@cmd -name "Built-in: tcl::file::link"\
-summary\
"Create a link or return the value of a link."\
-help\
"If only one argument is given, that argument is assumed to be linkName, and this command returns the value
of the link given by linkName (i.e. the name of the file it points to). If linkName is not a link or its
value cannot be read (as, for example, seems to be the case with hard links, which look just like ordinary
files), then an error is returned.
If 2 arguments are given, then these are assumed to be linkName and target. If linkName already exists, or
if target does not exist, an error will be returned. Otherwise, Tcl creates a new link called linkName which
points to the existing filesystem object at target (which is also the returned value), where the type of the
link is platform-specific (on Unix a symbolic link will be the default). This is useful for the case where
the user wishes to create a link in a cross-platform way, and does not care what type of link is created.
If the user wishes to make a link of a specific type only, (and signal an error if for some reason that is
not possible), then the optional -linktype argument should be given. Accepted values for -linktype are
“-symbolic” and “-hard”.
On Unix, symbolic links can be made to relative paths, and those paths must be relative to the actual
linkName's location (not to the cwd), but on all other platforms where relative links are not supported,
target paths will always be converted to absolute, normalized form before the link is created
(and therefore relative paths are interpreted as relative to the cwd). When creating links on filesystems
that either do not support any links, or do not support the specific type requested, an error message will
be returned. Most Unix platforms support both symbolic and hard links (the latter for files only).
Windows supports symbolic directory links and hard file links on NTFS drives.
"
@opts -type none -parsekey "-LINKTYPE" -group "linktype" -grouphelp\
""
-symbolic -typedefaults "-symbolic" -help\
""
-hard -typedefaults "-hard" -help\
"
"
@opts -parsekey "" -group ""
@values -min 1 -max 2
linkName -type string -optional 0
target -type string -optional 1
} "@doc -name Manpage: -url [manpage_tcl file]" ]
#lstat
lappend PUNKARGS [list {
@ -2986,8 +3211,37 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
name -type string
time -type integer -optional 1
} "@doc -name Manpage: -url [manpage_tcl file]"]
#nativename
#normalize
lappend PUNKARGS [list {
@id -id ::tcl::file::nativename
@cmd -name "Built-in: tcl::file::nativename"\
-summary\
{Platform-specific name of the file.}\
-help\
"Returns the platform-specific name of the file. This is useful if the filename is needed to pass
to a platform-specific call, such as to a subprocess via ${$B}exec${$N} under Windows (see EXAMPLES below)."
@values -min 1 -max 1
name -optional 0 -type string
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::normalize
@cmd -name "Built-in: tcl::file::normalize"\
-summary\
{Unique normalized path.}\
-help\
"Returns a unique normalized path representation for the file-system object (file, directory, link, etc),
whose string value can be used as a unique identifier for it. A normalized path is an absolute path which
has all “../” and “./” removed. Also it is one which is in the “standard” format for the native platform.
On Unix, this means the segments leading up to the path must be free of symbolic links/aliases (but the
very last path component may be a symbolic link), and on Windows it also means we want the long form with
that form's case-dependence (which gives us a unique, case-dependent path). The one exception concerning
the last link in the path is necessary, because Tcl or the user may wish to operate on the actual
symbolic link itself (for example ${$B}file delete${$N}, ${$B}file rename${$N}, ${$B}file copy${$N} are defined to operate on symbolic
links, not on the things that they point to)."
@values -min 1 -max 1
name -optional 0 -type string
} "@doc -name Manpage: -url [manpage_tcl file]"]
#owned
#pathtype
lappend PUNKARGS [list {
@ -3015,6 +3269,24 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
} "@doc -name Manpage: -url [manpage_tcl file]"]
#rename (2 forms)
lappend PUNKARGS [list {
@id -id ::tcl::file::rename
@cmd -name "Built-in: tcl::file::rename"\
-summary\
{Rename file or folder.}\
-help\
""
#----------------------------------------------
@form -form "tofile"
@opts
-force -type none -optional 1 -default 0
-- -type none -optional 1
@values -min 2 -max 2
source -optional 0 -type string
#----------------------------------------------
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::rootname
@cmd -name "Built-in: tcl::file::rootname"\
@ -3030,14 +3302,134 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
#separator
#size
#split
#stat
#system
#tail
#tempdir
#tempfile
lappend PUNKARGS [list {
@id -id ::tcl::file::stat
@cmd -name "Built-in: tcl::file::stat"\
-summary\
{Get file metadata - status information.}\
-help\
"Invokes the stat kernel call on name, and returns a dictionary with the information returned from
the kernel call. If varName is given, it uses the variable to hold the information. VarName is
treated as an array variable, and in such case the command returns the empty string. The following
elements are set: ${$B}atime${$N}, ${$B}ctime${$N}, ${$B}dev${$N}, ${$B}gid${$N}, ${$B}ino${$N}, ${$B}mode${$N}, ${$B}mtime${$N}, ${$B}nlink${$N}, ${$B}size${$N}, ${$B}type${$N}, ${$B}uid${$N}.
Each element except ${$B}type${$N} is a decimal string with the value of the corresponding field from the
stat return structure; see the manual entry for stat for details on the meanings of the values.
The type element gives the type of the file in the same form returned by the command ${$B}file type${$N}."
@values -min 1 -max 1
name -optional 0 -type string
varName -type string -optional 1
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::system
@cmd -name "Built-in: tcl::file::system"\
-summary\
{filesystem info for path}\
-help\
"Returns a list of one or two elements, the first of which is the name of the filesystem to use for
the file, and the second, if given, an arbitrary string representing the filesystem-specific nature
or type of the location within that filesystem. If a filesystem only supports one type of file, the
second element may not be supplied. For example the native files have a first element “native”, and
a second element which when given is a platform-specific type name for the file's system
(e.g. “NTFS”, “FAT”, on Windows). A generic virtual file system might return the list “vfs ftp” to
represent a file on a remote ftp site mounted as a virtual filesystem through an extension called
“vfs”. If the file does not belong to any filesystem, an error is generated."
@values -min 1 -max 1
name -optional 0 -type string
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::tail
@cmd -name "Built-in: tcl::file::tail"\
-summary\
{Last filesystem component of path}\
-help\
"Returns all of the characters in the last filesystem component of ${$I}name${$NI}.
Any trailing directory separator in ${$I}name${$NI} is ignored. If ${$I}name${$NI} contains no separators then returns ${$I}name${$NI}.
So, ${$B}file tail a/b${$N}, ${$B}file tail a/b/${$N} and ${$B}file tail b${$N} all return ${$B}b${$N}."
@values -min 1 -max 1
name -optional 0 -type string
} "@doc -name Manpage: -url [manpage_tcl file]"]
#tempdir tcl 9+ only?
lappend PUNKARGS [list {
@id -id ::tcl::file::tempdir
@cmd -name "Built-in: tcl::file::tempdir"\
-summary\
{Create a temporary directory.}\
-help\
"Creates a temporary directory (guaranteed to be newly created and writable by the current script)
and returns its name. If template is given, it specifies one of or both of the existing directory
(on a filesystem controlled by the operating system) to contain the temporary directory, and the
base part of the directory name; it is considered to have the location of the directory if there
is a directory separator in the name, and the base part is everything after the last directory
separator (if non-empty). The default containing directory is determined by system-specific
operations, and the default base name prefix is “tcl”.
The following output is typical and illustrative; the actual output will vary between platforms:
${[punk::args::helpers::example {
% ${$B}file tempdir${$N}
/var/tmp/tcl_u0kuy5
% ${$B}file tempdir /tmp/myapp${$N}
/tmp/myapp_8o7r9L
% ${$B}file tempdir /tmp/${$N}
/tmp/tcl_1m0JHD
% ${$B}file tempdir myapp${$N}
/var/tmp/myapp_0ihS0n
}]}
"
@values -min 0 -max 1
template -type string -optional 1
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::tempfile
@cmd -name "Built-in: tcl::file::tempfile"\
-summary\
{Create temp file and return open channel.}\
-help\
"Creates a temporary file and returns a read-write channel opened on that file.
If the nameVar is given, it specifies a variable that the name of the temporary
file will be written into; if absent, Tcl will attempt to arrange for the
temporary file to be deleted once it is no longer required. If the template is
present, it specifies parts of the template of the filename to use when creating
it (such as the directory, base-name or extension) though some platforms may
ignore some or all of these parts and use a built-in default instead.
Note that temporary files are only ever created on the native filesystem.
As such, they can be relied upon to be used with operating-system native APIs
and external programs that require a filename."
@values -min 0 -max 2
nameVar -type string -optional 1
template -type string -optional 1
} "@doc -name Manpage: -url [manpage_tcl file]"]
#tildeexpand
#type
#volumes
lappend PUNKARGS [list {
@id -id ::tcl::file::type
@cmd -name "Built-in: tcl::file::type"\
-summary\
{Type of file name.}\
-help\
"Returns a string giving the type of file name, which will be one of
${$B}file${$N}, ${$B}directory${$N}, ${$B}characterSpecial${$N}, ${$B}blockSpecial${$N}, ${$B}fifo${$N}, ${$B}link${$N}, or ${$B}socket${$N}."
@values -min 0 -max 0
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::volumes
@cmd -name "Built-in: tcl::file::volumes"\
-summary\
"List volumes mounted on the system."\
-help\
"Returns the absolute paths to the volumes mounted on the system, as a proper Tcl list.
Without any additional virtual filesystems mounted as root volumes, on UNIX, the command
will return “//zipfs:/”/ or “/”, (in case of a --disable-zipfs build), since all
filesystems are locally mounted. On Windows, it will return a list of the available
local drives (e.g. “//zipfs:/ C:/”). If any virtual filesystem has mounted additional
volumes, they will be in the returned list too."
@values -min 0 -max 0
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::writable
@ -6694,21 +7086,21 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
start -type number|expr
..|to -type string -choices {.. to} -optional 1
end -type number|expr
"by step" -type {literal(by) number|expr} -optional 1
"by step" -type {?literalprefix(by)? number|expr} -optional 1
@form -form start_count
@leaders -min 0 -max 0
@values -min 3 -max 5
start -type number|expr
count -type literal
count -type literalprefix(count)
countelements -type number|expr
"by step" -type {literal(by) number|expr} -optional 1
"by step" -type {literalprefix(by) number|expr} -optional 1
@form -form count
@leaders -min 0 -max 0
@values -min 1 -max 3
countelements -type number|expr
"by step" -type {literal(by) number|expr} -optional 1
"by step" -type {literalprefix(by) number|expr} -optional 1
} "@doc -name Manpage: -url [manpage_tcl lseq]"\
{

174
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/auto_exec-0.1.0.tm

@ -56,7 +56,9 @@ tcl::namespace::eval punk::auto_exec {
-help\
{Clear/refresh the autoexec commands in the ::auto_execs array.
This is analogous to the 'rehash' command in shells such as csh, tcsh and zsh.
This is analogous to the 'rehash' command in shells such as csh, tcsh and zsh,
or 'hash -r' in other shells such as bash.
It updates the shell's hash table of executable commands.
This can be useful after installing new software, adjusting the environment PATH directories, or (on windows) making
@ -64,7 +66,9 @@ tcl::namespace::eval punk::auto_exec {
commands are up to date with the current state of the system.
If refresh is false (the default), then all autoexec commands are cleared and will re-register as commands are called.
If refresh is true, then all existing autoexec commands are re-registered by calling auto_execok for each of them again.}
If refresh is true, then all existing autoexec commands are re-registered by calling auto_execok for each of them again.
see also ::punk::auto_exec::hash}
@opts
@values -min 0 -max 1
refresh -type boolean -default 0 -help\
@ -85,6 +89,172 @@ tcl::namespace::eval punk::auto_exec {
}
return
}
namespace eval argdoc {
lappend PUNKARGS [list {
@id -id "::punk::auto_exec::hash"
@cmd -name "punk::auto_exec::hash"\
-summary\
"Manage the hash table of autoexec commands cached in ::auto_execs."\
-help\
{see also ::punk::auto_exec::rehash}
#---------------------
@form -form {show_or_set}
@opts -min 0 -max 0
@values -min 0 -max -1
name -type string -multiple 1 -optional 1 -default {} -help\
"One or more autoexec command names to set.
If no names are provided, then all autoexec commands in the hash table will be shown."
#---------------------
@form -form {rehash}
@opts -min 1 -max 1
-r -type none -optional 0 -help\
"Clear autoexec commands from the hash table"
@values -min 0 -max 0
#---------------------
@form -form {test}
@opts
-t -type none -optional 0 -default "" -help\
"The name of the autoexec command name to display."
@values -min 1 -max -1
name -type string -multiple 1 -help\
"One or more autoexec command names to display information for.
If only a single name is provided, then the output will be the raw command string
associated with that autoexec command in the hash table.
If multiple names are provided, then the output will be a string containing each
name and its associated command string on a separate line."
#---------------------
@form -form {delete}
@opts
-d -type none -optional 0 -help\
"Delete specified autoexec commands from the hash table."
@values -min 1 -max -1
name -type string -multiple 1 -help\
"One or more autoexec command names to delete from the hash table."
#---------------------
#todo?
#-p <path> <name> (manually assign)
#-l (build a list of hash -p <path> <name> entries for all autoexec commands that can be used in a script to pre-populate the hash table without needing to call auto_execok for each command at runtime)
#---------------------
@form -form {help}
@opts -min 1 -max 1 -anyopts 1
--help -type none -optional 0 -help\
"Display usage information for this command."
@values -min 0 -max -1
ignored -type any -multiple 1 -optional 1 -help\
"Additional arguments that are ignored when --help is used"
}]
}
proc hash {args} {
set arg1 [lindex $args 0]
#select parsing form based on first argument
switch -- $arg1 {
-r {
set form rehash
}
-t {
set form test
}
-d {
set form delete
}
--help {
set form help
}
default {
#like bash in this context, we won't allow an option-like entry to be treated as an executable name
if {[string match -* $arg1]} {
puts stderr "hash: ${arg1}: invalid option"
#return [punk::args::usage -scheme error ::punk::auto_exec::hash]
set msg "hash: usage:\n"
append msg [punk::ns::synopsis ::punk::auto_exec::hash]
error $msg
}
set form show_or_set
}
}
set argd [punk::args::parse $args -form $form withid ::punk::auto_exec::hash]
lassign [dict values $argd] _leaders opts values received
global auto_execs
switch -- $form {
rehash {
unset -nocomplain auto_execs
}
test {
#like bash - we'll provide only the path if there is a single name provided, but if there are multiple names we'll provide both the name and path for each.
set names [dict get $values name]
if {[llength $names] == 1} {
set nm [lindex $names 0]
if {[info exists auto_execs($nm)]} {
return [set auto_execs($nm)]
} else {
#review
puts stderr "hash: $nm: not found"
return ""
}
}
set result ""
foreach nm $names {
if {[info exists auto_execs($nm)]} {
append result "$nm [set auto_execs($nm)]\n"
} else {
#review
puts stderr "$hash: nm: not found"
}
}
if {$result ne ""} {
set result [string trimright $result \n]
}
return $result
}
delete {
set names [dict get $values name]
foreach nm $names {
unset -nocomplain auto_execs($nm)
}
}
help {
return [punk::args::usage ::punk::auto_exec::hash]
}
default {
set requested_names [dict get $values name]
if {[llength $requested_names] == 0} {
#show all
set hashed_names [array names auto_execs]
#todo - record and return 'hits' like bash does?
set result ""
foreach nm $hashed_names {
set cached [set auto_execs($nm)]
#unlike some shells - we cache negative results (for absolute paths) that don't exist.
#as we're attempting to be close to behaviour of bash, don't output empty results for negative cache entries.
if {$cached ne ""} {
append result $cached \n
}
}
if {$result ne ""} {
set result [string trimright $result \n]
}
return $result
} else {
#rehash each requested name if it exists, otherwise display an msg on stderr for that name.
foreach nm $requested_names {
set aexec [auto_execok $nm]
if {$aexec ne ""} {
set auto_execs($nm) $aexec
} else {
puts stderr "hash: $nm: not found"
}
}
return
}
}
}
}
variable PUNKARGS
lappend PUNKARGS [list {

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

@ -503,16 +503,33 @@ tcl::namespace::eval punk::config {
key -type string -optional 1
newvalue -optional 1
}]
proc configure {args} {
set argd [punk::args::parse $args withid ::punk::config::configure]
lassign [dict values $argd] leaders opts values received solos
set whichconfig [dict get $argd leaders whichconfig]
proc configure {whichconfig args} {
#set argd [punk::args::parse $args withid ::punk::config::configure]
#lassign [dict values $argd] leaders opts values received solos
#set whichconfig [dict get $argd leaders whichconfig]
set values [dict create]
switch -- [llength $args] {
0 {
}
1 {
dict set values key [lindex $args 0]
}
2 {
dict set values newvalue [lindex $args 1]
}
default {
error "Too many arguments. Expected at most 2 (key [newvalue])"
}
}
variable configdata
if {"running" ni [dict keys $configdata]} {
init
Apply startup
}
switch -- $whichconfig {
set fullwhich [tcl::prefix::match -error "" {defaults startup-configuration running-configuration} $whichconfig]
switch -- $fullwhich {
defaults {
set configrecords [dict get $configdata defaults]
}
@ -522,12 +539,15 @@ tcl::namespace::eval punk::config {
running-configuration {
set configrecords [dict get $configdata running]
}
default {
error "Unknown config name '$whichconfig' - try defaults or startup-configuration or running-configuration"
}
}
if {![dict exists $received key]} {
if {![dict exists $values key]} {
return $configrecords
}
set key [dict get $values key]
if {![dict exists $received newvalue]} {
if {![dict exists $values newvalue]} {
return [dict get $configrecords $key]
}
error "setting value not implemented"

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

File diff suppressed because it is too large Load Diff

34
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/lib-0.1.6.tm

@ -94,19 +94,16 @@ tcl::namespace::eval punk::lib::ensemble {
set routinetail [tcl::namespace::tail $routine]
if {![string match ::* $extension]} {
set extension [uplevel 1 [
list [tcl::namespace::which namespace] current]]::$extension
set extension [uplevel 1 [list [tcl::namespace::which namespace] current]]::$extension
}
if {![tcl::namespace::exists $extension]} {
error [list {no such namespace} $extension]
}
set extension [tcl::namespace::eval $extension [
list [tcl::namespace::which namespace] current]]
set extension [tcl::namespace::eval $extension [list [tcl::namespace::which namespace] current]]
tcl::namespace::eval $extension [
list [tcl::namespace::which namespace] export *]
tcl::namespace::eval $extension [list [tcl::namespace::which namespace] export *]
while 1 {
set renamed ${routinens}::${routinetail}_[clock clicks] ;#clock clicks unlikely to collide when not directly consecutive such as: list [clock clicks] [clock clicks]
@ -140,7 +137,7 @@ tcl::namespace::eval punk::lib::check {
if {"windows" ne $::tcl_platform(platform)} {
set bug 0
} else {
set tmpdir [file tempdir]
set tmpdir [file tempdir] ;#tcl 9+
set testfile [file join $tmpdir "bugtest"]
set fd [open $testfile w]
puts $fd test
@ -4759,14 +4756,21 @@ namespace eval punk::lib {
foreach ln $linelist {
#set is_replay_pure_reset [regexp {\x1b\[0*m$} $replaycodes] ;#only looks at tail code - but if tail is pure reset - any prefix is ignorable
set ansisplits [punk::ansi::ta::split_codes_single $ln] ;#REVIEW - this split accounts for a large portion of the time taken to run this function.
if {[llength $ansisplits]<= 1} {
if {![punk::ansi::ta::detect $ln]} {
#plaintext only - no ansi codes in line
lappend transformed [string cat $replaycodes $ln $RST]
#leave replaycodes as is for next line
set nextreplay $replaycodes
} else {
set replaycodes $nextreplay
continue
}
set ansisplits [punk::ansi::ta::split_codes_single $ln] ;#REVIEW - this split seems to account for a large portion of the time taken to run this function.
#if {[llength $ansisplits]<= 1} {
# #plaintext only - no ansi codes in line
# lappend transformed [string cat $replaycodes $ln $RST]
# #leave replaycodes as is for next line
# set nextreplay $replaycodes
#} else {
set tail $RST
set lastcode [lindex $ansisplits end-1] ;#may or may not be SGR
if {[punk::ansi::codetype::is_sgr_reset $lastcode]} {
@ -4821,7 +4825,7 @@ namespace eval punk::lib {
#set newreplay [join $codestack ""]
set newreplay [punk::ansi::codetype::sgr_merge_list {*}$codestack]
if {$line_has_sgr && $newreplay ne $replaycodes} {
if {$RST ne "" && $line_has_sgr && $newreplay ne $replaycodes} {
#adjust if it doesn't already does a reset at start
if {[punk::ansi::codetype::has_sgr_leadingreset $newreplay]} {
set nextreplay $newreplay
@ -4838,7 +4842,7 @@ namespace eval punk::lib {
} else {
lappend transformed [string cat $replaycodes $ln $tail]
}
}
#}
set replaycodes $nextreplay
}
set linelist $transformed
@ -5505,7 +5509,7 @@ tcl::namespace::eval punk::lib::debug {
namespace eval ::punk::args::register {
#use fully qualified so 8.6 doesn't find existing var in global namespace
lappend ::punk::args::register::NAMESPACES ::punk::lib
lappend ::punk::args::register::NAMESPACES ::punk::lib ::punk::lib::ensemble
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready

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

@ -330,8 +330,11 @@ tcl::namespace::eval punk::nav::fs {
punk::args::define {
@id -id ::punk::nav::fs::d/
@cmd -name punk::nav::fs::d/ -help\
{List directories or directories and files in the current directory or in the
@cmd -name punk::nav::fs::d/\
-summary\
"Navigate and list directories and files"\
-help\
{Navigate/List directories or directories and files in the current directory or in the
targets specified with the fileglob_or_target glob pattern(s).
If a single target is specified without glob characters, and it exists as a directory,

36
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/nav/ns-0.1.0.tm

@ -33,6 +33,40 @@ tcl::namespace::eval punk::nav::ns {
}
namespace path {::punk::ns}
namespace eval argdoc {
lappend PUNKARGS [list {
@id -id ::punk::nav::ns::ns/
@cmd -name punk::nav::ns::ns/\
-summary\
"Navigate and list namespaces and commands"\
-help\
{Navigate/List namespaces or namespaces and commands in the current namespace or in the
targets specified with the nsglob pattern(s).
This function is provided via aliases as n/ n// and n/// with v being inferred from the alias
The n/ n// and n/// forms are more convenient for interactive use.
examples:
n/ - list namespaces below current namespace
n// - list namespaces and commands below current namespace
n/ p* - list namespaces below current matching p*
n// p* - list namespaces below current and commands in current matching p*
}
@values -min 1 -max -1 -type string
v -type string -choices {/ //} -help\
"
/ - list namespaces only
// - list namespaces and commands
/// - list namespaces, commands and commands resolvable via 'namespace path'
"
nsglob -type string -optional true -multiple true -help\
"A glob pattern supporting placeholders * and ?, to filter results.
If multiple patterns are supplied, then a listing for each pattern is returned.
If no patterns are supplied, then all items are listed."
}]
}
proc ns/ {v {ns_or_glob ""} args} {
variable ns_current ;#change active ns of repl by setting ns_current
@ -227,8 +261,6 @@ tcl::namespace::eval punk::nav::ns {
}
}
}

32
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/ns-0.1.0.tm

@ -3711,6 +3711,30 @@ y" {return quirkykeyscript}
}
}
punk::args::define {
@id -id ::punk::ns::nscommands
@cmd -name punk::ns::nscommands\
-summary\
"List current namespace commands one per line."\
-help\
"Display commands in the current namespace, or optionally within specified namespaces.
Namespaces to search can be specified as arguments, with optional glob patterns.
Examples:
'nscommands' - list all commands in the current namespace
'nscommands foo*' - list all commands in the current namespace with names starting with 'foo'
'nscommands foo* bar*' - list all commands in the current namespace with names starting with 'foo' or 'bar'"
@leaders -min 0 -max 0
@opts
-raw -type none -help\
"Output raw command names with no ANSI color codes.
Useful for scripting or when color codes would be undesirable."
@values -min 1 -max -1
glob -multiple 1 -optional 1 -default * -help\
"Namespace patterns to search for commands. If not specified, defaults to '*',
which searches the current namespace. Patterns can include glob characters (* and ?).
Examples: 'foo*' to match namespaces starting with 'foo', '*::bar' to match namespaces
ending with 'bar'."
}
proc nscommands {args} {
set commandns [uplevel 1 [list ::tcl::namespace::current]]
set commandlist [::list]
@ -3803,6 +3827,7 @@ y" {return quirkykeyscript}
}
}
interp alias {} nscommands {} punk::ns::nscommands
proc nscommandlist {{ns *}} {
set nsparts [nsparts_cached $ns]
set tail [lindex $nsparts end]
@ -4051,6 +4076,13 @@ y" {return quirkykeyscript}
#eg because parent interp called something like: interp0 alias ::thread::id ::thread::id
#make sure we don't perform an infinite loop
if {$tgt ne $resolved} {
#--------------
#unqualified alias target - need to resolve to fully qualified for cmdwhich lookup to work correctly
#jmn - todo test/review
if {![string match ::* $tgt]} {
set tgt ::$tgt
}
#--------------
set whichinfo [uplevel 1 [list ::punk::ns::cmdwhich $tgt]]
set origin [dict get $whichinfo origin]
set origintype [dict get $whichinfo origintype]

21
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/repl-0.1.2.tm

@ -2948,8 +2948,11 @@ namespace eval repl {
if {[catch {package require thread} errM]} {
puts stdout ">>repl::init initscript lib load fail on package require thread\n$errM"
puts stdout ">>repl::init auto_path : $::auto_path"
puts stdout ">>repl::init tcl::tm::list: [tcl::tm::list]"
if {[catch {package require Thread} errM2]} {
puts stdout ">>repl::init initscript lib load fail on package require Thread\n$errM2"
puts stdout ">>repl::init auto_path : $::auto_path"
puts stdout ">>repl::init tcl::tm::list: [tcl::tm::list]"
}
}
#-----
@ -3395,9 +3398,11 @@ namespace eval repl {
set v [lindex $versions end]
set path [lindex [package ifneeded $pkg $v] end]
if {[file extension $path] in {.tcl .tm}} {
if {![catch {readFile $path} data]} {
if {![catch {readFile $path} packagedef]} {
code eval [list info script $path]
code eval $data
code eval $packagedef
#jjj
code eval [list package provide $pkg $v] ;#ensure package is marked as provided in interp even if it doesn't call package provide itself
code eval [list info script $prior_infoscript]
} else {
error "safe - failed to read $path"
@ -3705,6 +3710,10 @@ namespace eval repl {
#puts stderr [join $::auto_path \n]
#puts stderr -----
#punk::console is not loaded at this point
#puts "--------------provide punk::console : [package provide punk::console]"
#puts "--------------punk::console commands: [info commands ::punk::console::*]"
if {[catch {
package require punk::args
package require punk::config
@ -3714,6 +3723,10 @@ namespace eval repl {
#Requiring it shouldn't trigger application - but zipfs/vfs interactions confused it in some early versions
package require natsort
#catch {package require packageTrace}
if {[catch {package require punk::console} errM]} {
#review
puts stderr "failed to load punk::console - \n$errM\n$::errorInfo"
}
package require punk
package require shellrun
package require shellfilter

25
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/punk/winlnk-0.1.1.tm

@ -733,18 +733,23 @@ tcl::namespace::eval punk::winlnk {
set r [binary scan $lenfield su count_chars] ;# su is for unsigned short in little endian order
set string_value ""
if {[Header_Has_LinkFlag $contents "IsUnicode"]} {
#string is UTF-16LE encoded
#string is UTF-16LE encoded - we have this encoding available in tcl 9+ - but not in 8.6
set numbytes [expr {2 * $count_chars}]
set string_bytes [string range $contents $start+2 [expr {$start + 2 + $numbytes - 1}]]
#consider using tcl encoding convertfrom utf-16le instead of manually parsing the UTF-16LE bytes - this would be more robust and handle edge cases better (e.g. surrogate pairs, non-BMP characters, etc.)
set string_value [encoding convertfrom utf-16le $string_bytes]
#for {set i 0} {$i < [string length $string_bytes]} {
# set char_bytes [string range $string_bytes $i [expr {$i + 1}]]
# set r [binary scan $char_bytes su char] ;# s for unsigned short
# append string_value [format %c $char]
# incr i 1 ;# skip the next byte since it's part of the UTF-16LE encoding
#}
#use tcl encoding convertfrom utf-16le when we can instead of manually parsing the UTF-16LE bytes
#- this should be more robust and handle edge cases better (e.g. surrogate pairs, non-BMP characters, etc.)
if {[catch {set string_value [encoding convertfrom utf-16le $string_bytes]} err]} {
#puts stderr "Error converting UTF-16LE string: $err"
#set string_value ""
for {set i 0} {$i < [string length $string_bytes]} {incr i} {
set char_bytes [string range $string_bytes $i $i+1]
set r [binary scan $char_bytes su char] ;# su for unsigned short
append string_value [format %c $char]
incr i 1 ;# skip the next byte since it's part of the UTF-16LE encoding
}
}
} else {
set numbytes $count_chars
set string_bytes [string range $contents $start+2 [expr {$start + 2 + $numbytes - 1}]]

45
src/project_layouts/custom/_project/punk.shell-0.1/src/bootsupport/modules/textblock-0.1.3.tm

@ -2107,6 +2107,7 @@ tcl::namespace::eval textblock {
set cidx [lindex [tcl::dict::keys $o_columndefs] $index_expression]
set colwidth [my column_width $cidx]
set fwidth [expr {$colwidth + 2}]
set col_blockalign [tcl::dict::get $o_columndefs $cidx -blockalign]
@ -2509,18 +2510,19 @@ tcl::namespace::eval textblock {
set border_ansi $body_ansibase$body_ansiborder
}
set ansibase $body_ansibase$opt_col_ansibase
set r 0
set ftblock [expr {[tcl::dict::get $o_opts_table -frametype] eq "block"}]
set do_show_edge [tcl::dict::get $o_opts_table -show_edge]
foreach c $cells {
#cells in column - each new c is in a different row
set row_ansibase [tcl::dict::get $o_rowdefs $r -ansibase]
set row_bg ""
set row_ansibase [tcl::dict::get $o_rowdefs $r -ansibase]
if {$row_ansibase ne ""} {
set row_bg [punk::ansi::codetype::sgr_merge_singles [list $row_ansibase] -filter_fg 1]
}
set ansibase $body_ansibase$opt_col_ansibase
#todo - joinleft,joinright,joindown based on opts in args
set cell_ansibase ""
@ -2602,7 +2604,7 @@ tcl::namespace::eval textblock {
} else {
set blims $blims_only_headerless
}
if {![tcl::dict::get $o_opts_table -show_edge]} {
if {!$do_show_edge} {
set blims [struct::set difference $blims [tcl::dict::get $::textblock::class::table_edge_parts only$opt_posn] ]
}
} else {
@ -2612,11 +2614,11 @@ tcl::namespace::eval textblock {
} else {
set blims $blims_top_headerless
}
if {![tcl::dict::get $o_opts_table -show_edge]} {
if {!$do_show_edge} {
set blims [struct::set difference $blims [tcl::dict::get $::textblock::class::table_edge_parts top$opt_posn] ]
}
}
set rowframe [textblock::frame -checkargs 0 -type [tcl::dict::get $ftypes body] -width [expr {$colwidth+2}] -blockalign $col_blockalign -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c]
set rowframe [textblock::frame -checkargs 0 -type $ftype_body -width [expr {$colwidth+2}] -blockalign $col_blockalign -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c]
set return_bodywidth [textblock::widthtopline $rowframe] ;#frame lines always same width - just look at top line
append part_body $rowframe \n
} else {
@ -2624,22 +2626,26 @@ tcl::namespace::eval textblock {
set joins [lremove $joins [lsearch $joins down*]]
set bmap $botmap
set blims $blims_bot
if {![tcl::dict::get $o_opts_table -show_edge]} {
if {!$do_show_edge} {
set blims [struct::set difference $blims [tcl::dict::get $::textblock::class::table_edge_parts bottom$opt_posn] ]
}
} else {
set bmap $midmap
set blims $blims_mid ;#will only be reduced from boxlimits if -show_seps was processed above
if {![tcl::dict::get $o_opts_table -show_edge]} {
if {!$do_show_edge} {
set blims [struct::set difference $blims [tcl::dict::get $::textblock::class::table_edge_parts middle$opt_posn] ]
}
}
append part_body [textblock::frame -checkargs 0 -type [tcl::dict::get $ftypes body] -width [expr {$colwidth+2}] -blockalign $col_blockalign -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c]\n
#append part_body [textblock::frame -checkargs 0 -type [tcl::dict::get $ftypes body] -width [expr {$colwidth+2}] -blockalign $col_blockalign -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c]\n
append part_body [textblock::frame -checkargs 0 -type $ftype_body -width [expr {$colwidth+2}] -blockalign $col_blockalign -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c]\n
}
incr r
}
#return empty (zero content height) row if no rows
if {![llength $cells]} {
set basebg [punk::ansi::codetype::sgr_merge_singles [list $body_ansibase] -filter_fg 1]
set ansiborder_final [punk::ansi::codetype::sgr_merge [list $basebg $body_ansiborder]]
set joins [lremove $joins [lsearch $joins down*]]
#we need to know the width of the column to setup the empty cell properly
#even if no header displayed - we should take account of any defined column widths
@ -2661,7 +2667,9 @@ tcl::namespace::eval textblock {
append part_body [tcl::string::repeat " " $colwidth] \n
set return_bodywidth $colwidth
} else {
set emptyframe [textblock::frame -checkargs 0 -width [expr {$colwidth + 2}] -type [tcl::dict::get $ftypes body] -boxlimits $blims -boxmap $onlymap -joins $joins]
#set emptyframe [textblock::frame -checkargs 0 -width [expr {$colwidth + 2}] -type [tcl::dict::get $ftypes body] -boxlimits $blims -boxmap $onlymap -joins $joins]
# -blockalign probably not relevant for an empty row.
set emptyframe [textblock::frame -checkargs 0 -type $ftype_body -width [expr {$colwidth+2}] -ansibase $body_ansibase -ansiborder $ansiborder_final -boxlimits $blims -boxmap $onlymap -joins $joins]
append part_body $emptyframe \n
set return_bodywidth [textblock::width $emptyframe]
}
@ -5741,7 +5749,10 @@ tcl::namespace::eval textblock {
@id -id ::textblock::join_basic
@cmd -name textblock::join_basic -help\
"Join blocks of text line by line but don't add padding on each line to enforce uniform width.
Already uniform blocks will join faster than textblock::join, and ragged blocks will join in a ragged manner
Already uniform blocks will join faster than textblock::join, and ragged blocks will join in a ragged manner.
see also textblock::join_basic_raw - a thin wrapper around split and join for the common case of joining blocks without any options,
and is intended to avoid the overhead of argument parsing.
"
-ansiresets -type any -default auto
-- -type none -optional 0 -help "end of options marker -- is mandatory because joined blocks may easily conflict with flags"
@ -5787,7 +5798,21 @@ tcl::namespace::eval textblock {
}
return [::join $outlines \n]
}
punk::args::define {
@id -id ::textblock::join_basic_raw
@cmd -name textblock::join_basic_raw -help\
"Join blocks of text line by line but don't add padding on each line to enforce uniform width.
Already uniform blocks will join faster than textblock::join, and ragged blocks will join in a ragged manner.
This version is a thin wrapper around split and join for the common case of joining blocks without any options,
and is intended to avoid the overhead of argument parsing.
"
@values
blocks -type any -multiple 1
}
proc ::textblock::join_basic_raw {args} {
#do not use any argument parsing libs - this is intended as a thin wrapper around split and join for the common case of joining blocks without any options,
#and we want to avoid the overhead of argument parsing.
#no options. -*, -- are legimate blocks
set blocklists [lrepeat [llength $args] ""]
set blocklengths [lrepeat [expr {[llength $args]+1}] 0] ;#add 1 to ensure never empty - used only for rowcount max calc

17
src/vfs/_vfscommon.vfs/modules/overtype-1.7.4.tm

@ -461,8 +461,21 @@ tcl::namespace::eval overtype {
if {$underblock eq ""} {
set underlines [lrepeat $renderheight ""]
} else {
set underblock [textblock::join_basic -- $underblock] ;#ensure properly rendered - ansi per-line resets & replays
set underlines [split $underblock \n]
#----
#this splits into lines - only to rejoin - which is inefficient.
#It also has code to handle joining multiple blocks - but we only have one in this case.
#set underblock [textblock::join_basic_raw $underblock];#ensure properly rendered - ansi per-line resets & replays
#set underlines [split $underblock \n]
#----
if {[punk::ansi::ta::detectcode $underblock]} {
#-ansireplays 1 quite expensive e.g ~15us for only 3 short lines on a 2026 threadripper pro
set underlines [punk::lib::linelist -ansireplays 1 $underblock]
} else {
set underlines [split $underblock \n]
}
}
#if {$underblock eq ""} {
# set blank "\x1b\[0m\x1b\[0m"

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

@ -341,7 +341,7 @@ namespace eval punk {
#}
#safest? could be a link?
foreach match [glob -nocomplain -dir $dir -tail {*}$lookfor] {
foreach match [glob -nocomplain -dir $dir -tail -- {*}$lookfor] {
set file [file join $dir $match]
if {[file exists $file] && ![file isdirectory $file]} {
#set assoc [extension_open_association [file extension $file]]
@ -8542,11 +8542,33 @@ namespace eval punk {
lappend chunks [list stdout $text]
}
console - term - terminal {
set term_env_vars {TERM TERM_PROGRAM TERM_PROGRAM_VERSION}
set term_dict [dict create]
foreach e $term_env_vars {
if {[info exists ::env($e)]} {
dict set term_dict $e [set ::env($e)]
} else {
dict set term_dict $e "(NOT SET)"
}
}
set text "Terminal environment variables:\n"
append text [punk::lib::showdict $term_dict] \n
lappend chunks [list stdout $text]
set text ""
if {[catch {package require punk::console} result]} {
set text "Unable to load punk::console package - cannot test\n$result"
lappend chunks [list stdout $text]
} else {
if {![catch {punk::console::class_info} console_class_info]} {
set text "Terminal class info (from device secondary attributes query to terminal):\n"
append text [punk::lib::showdict $console_class_info] \n
} else {
set text "Unable to query terminal class info - err:$console_class_info\n"
}
lappend chunks [list stdout $text]
set indent [string repeat " " [string length "WARNING: "]]
lappend cstring_tests [dict create\
type "PM "\
@ -8643,7 +8665,7 @@ namespace eval punk {
}
}
if {![string length $warningblock]} {
set text "No terminal warnings\n"
set text "[a+ green]No terminal warnings[a]\n"
lappend chunks [list stdout $text]
}
}
@ -8655,7 +8677,7 @@ namespace eval punk {
"tcl" "Tcl version warnings"\
"env|environment" "punkshell environment vars"\
"console|terminal" "Some console behaviour tests and warnings"\
"*" "Try to find help on the topic as a command or external executable"\
"*" "Try to find help on the topic as a command or external executable"\
]
set t [textblock::class::table new -show_seps 0]

1
src/vfs/_vfscommon.vfs/modules/punk/aliascore-0.1.0.tm

@ -117,6 +117,7 @@ tcl::namespace::eval punk::aliascore {
plist {::punk::lib::pdict -roottype list}\
showlist {::punk::lib::showdict -roottype list}\
rehash ::punk::auto_exec::rehash\
hash ::punk::auto_exec::hash\
showdict ::punk::lib::showdict\
ansistrip ::punk::ansi::ansistrip\
stripansi ::punk::ansi::ansistrip\

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

@ -3920,7 +3920,10 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
}
lappend PUNKARGS [list {
@id -id ::punk::ansi::a+
@cmd -name "punk::ansi::a+" -help\
@cmd -name "punk::ansi::a+"\
-summary\
"ANSI SGR code generator with no reset prefix"\
-help\
"Returns an ANSI sgr escape sequence based on the list of supplied codes.
Unlike punk::ansi::a - it is not prefixed with an ANSI reset.
"
@ -3935,7 +3938,10 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
lappend PUNKARGS [list {
@id -id ::punk::ansi::a
@cmd -name "punk::ansi::a" -help\
@cmd -name "punk::ansi::a"\
-summary\
"ANSI SGR code generator with reset prefix"\
-help\
"Returns an ANSI sgr escape sequence based on the list of supplied codes.
Unlike punk::ansi::a+ - it is prefixed with an ANSI reset.
"
@ -6865,7 +6871,14 @@ tcl::namespace::eval punk::ansi::ta {
#may be same as detect - kept in case detect needs to diverge
#variable re_ansi_split "${re_csi_code}|${re_esc_osc1}|${re_esc_osc2}|${re_esc_osc3}|${re_standalones}|${re_ST}|${re_g0_open}|${re_g0_close}"
set re_ansi_split $re_ansi_detect
#experiment with const for a regex - seems to make no difference to performance - but it does make it clear that the regex is not intended to be modified at runtime
if {[catch {const re_ansi_split $re_ansi_detect}]} {
#tcl 9 has const but tcl 8 doesn't - so we just set it as a normal variable
variable re_ansi_split
set re_ansi_split $re_ansi_detect
}
variable re_ansi_split_multi
if {[string first (?x) $re_ansi_split] == 0} {
set re_ansi_split_multi "(?x)(?:[string range ${re_ansi_split} 4 end])+"
@ -7161,7 +7174,7 @@ tcl::namespace::eval punk::ansi::ta {
#micro optimisations on split_codes to avoid function calls and make re var local tend to yield very little benefit (sub uS diff on calls that commonly take 10s/100s of uSeconds)
#like split_codes - but each ansi-escape is split out separately (with empty string of plaintext between codes so even/odd indices for plain ansi still holds)
#- the slightly simpler regex than split_codes means that it will be slightly faster than keeping the codes grouped.
#- the regex is slighly simpler than for split_codes - but split_codes is faster when there are consecutive codes.
proc split_codes_single {text} {
if {$text eq ""} {
return {}
@ -7177,7 +7190,26 @@ tcl::namespace::eval punk::ansi::ta {
#set next [lindex $cr 1]+1 ;#text index-expression for string range
}
lappend list [tcl::string::range $text $next end]
return $list
}
proc split_codes_single2 {text} {
return [_perlish_split2 $::punk::ansi::ta::re_ansi_split $text]
}
proc split_codes_single3 {text} {
#no faster
if {$text eq ""} {
return {}
}
variable re_ansi_split
set next 0
set coderanges [regexp -indices -all -inline -- $re_ansi_split $text]
set list [lrepeat [expr {[llength $coderanges]*2}] ""]
set r 0
foreach cr $coderanges {
ledit list $r $r+1 [tcl::string::range $text $next [lindex $cr 0]-1] [tcl::string::range $text [lindex $cr 0] [lindex $cr 1]]
set next [expr {[lindex $cr 1]+1}]
incr r
}
return [list {*}$list [tcl::string::range $text $next end]]
}
proc split_codes_single2 {text} {
variable re_ansi_split
@ -7202,7 +7234,6 @@ tcl::namespace::eval punk::ansi::ta {
set next [expr {[lindex $cr 1]+1}]
}
lappend list [tcl::string::range $text $next end]
return $list
}
proc _perlish_split2 {re text} {
if {$text eq ""} {

203
src/vfs/_vfscommon.vfs/modules/punk/args-0.2.1.tm

@ -771,9 +771,9 @@ tcl::namespace::eval punk::args {
literal(<string>)
(exact match for string)
literalprefix(<string>)
(prefix match for string, other literal and literalprefix
(tcl::prefix::match of string, other literal and literalprefix
entries specified as alternates using | are used in the
calculation)
unique prefix calculation)
stringstartswith(<string>)
(value must match glob <string>*)
The value of string must not contain pipe char '|'
@ -785,7 +785,7 @@ tcl::namespace::eval punk::args {
e.g literalprefix(text)|literalprefix(binary)
(when all in the pipe-delimited type-alternates set are
literal or literalprefix - this is similar to the -choices
option)
option with -choiceprefix true)
and more.. (todo - document here)
@ -3829,7 +3829,8 @@ tcl::namespace::eval punk::args {
set arg_error_CLR_info(check) [a+ brightgreen bold]
set arg_error_CLR_info(choiceprefix) [a+ brightgreen bold]
set arg_error_CLR_info(groupname) [a+ cyan bold]
set arg_error_CLR_info(ansiborder) [a+ brightcyan bold]
#set arg_error_CLR_info(ansiborder) [a+ brightcyan bold]
set arg_error_CLR_info(ansiborder) [a+ term-grey23 bold]
set arg_error_CLR_info(ansibase_header) [a+ cyan]
set arg_error_CLR_info(ansibase_body) [a+ white]
variable arg_error_CLR_error
@ -5428,7 +5429,8 @@ tcl::namespace::eval punk::args {
}
#return number of values we can assign to cater for variable length clauses such as {"elseif" expr "?then?" body}
#return number of values we can assign to cater for variable length clauses such as:
# {"elseif" expr "?then?" body}
#review - efficiency? each time we call this - we are looking ahead at the same info
proc _get_dict_can_assign_value {idx values nameidx names namesreceived formdict} {
set ARG_INFO [dict get $formdict ARG_INFO]
@ -5439,12 +5441,23 @@ tcl::namespace::eval punk::args {
#todo - work backwards with any (optional or not) literals at tail that match our values - and remove from assignability.
set ridx 0
#puts "-=============- thisname:'$thisname' thistype:'$thistype' tailnames:'$tailnames' all_remaining:'$all_remaining' [info level -2]"
foreach clausename [lreverse $tailnames] {
#puts "=============== clausename:$clausename all_remaining: $all_remaining"
#puts "=============== thisname:'$thisname' thistype:'$thistype' clausename:'$clausename' all_remaining:'$all_remaining'"
set clause_is_multiple [dict get $ARG_INFO $clausename -multiple]
set clause_is_optional [dict get $ARG_INFO $clausename -optional]
set typelist [dict get $ARG_INFO $clausename -type]
#---------------
#review - not quite right to look for literal* in typelist
#- we should be looking for any type-alternate that starts with literal( or literalprefix(
#- but for now we require the whole type to be literal* if it's a literal match type.
# We should probably also support stringstartswith(*) and stringendswith(*) too.
#also consider that -choices {abc def} is effectively a literal match type too - we should support that here as well.
if {[lsearch $typelist literal*] == -1} {
break
}
#---------------
set max_clause_length [llength $typelist]
if {$max_clause_length == 1} {
#basic case
@ -5465,28 +5478,50 @@ tcl::namespace::eval punk::args {
}
#foreach tp_alternative [split $tp |] {}
foreach tp_alternative [_split_type_expression $tp] {
set tp_alternatives [_split_type_expression $tp]
foreach tp_alternative $tp_alternatives {
switch -exact -- [lindex $tp_alternative 0] {
literal {
set litinfo [string range $tp 7 end] ;#get bracketed part if of form literal(xxx)
set match [lindex $tp_alternative 1]
set match [lindex $tp_alternative 1] ;#was bracketed part if of form literal(xxx)
if {$v eq $match} {
set alloc_ok 1
ledit all_remaining end end
if {![dict get $ARG_INFO $clausename -multiple]} {
if {!$clause_is_multiple} {
ledit tailnames end end
}
#the type (or one of the possible type alternates) matched a literal
break
}
}
literalprefix {
set prefix_of [lindex $tp_alternative 1]
#get list of literal and literalprefix values in the current list of tp_alternatives so we can construct list of alternatives for tcl::prefix::match prefix calculation.
#todo - consider if this clause also has -choices {abc def} - we should support those as well here as literal matches for the purposes of calculating the prefix match.
# (this is somewhat of an edge case but sometimes it's useful to specify a -type when -choices is used with -choicerestricted false, to allow only specific values not in the choices list.)
set comparelist [list]
foreach alt $tp_alternatives {
switch -exact -- [lindex $alt 0] {
literal - literalprefix {
lappend comparelist [lindex $alt 1]
}
}
}
set fullmatch [tcl::prefix::match -error "" $comparelist $v]
if {$fullmatch eq $prefix_of} {
set alloc_ok 1
ledit all_remaining end end
if {!$clause_is_multiple} {
ledit tailnames end end
}
break
}
}
stringstartswith {
set pfx [lindex $tp_alternative 1]
if {[string match "$pfx*" $v]} {
set alloc_ok 1
set alloc_ok 1
ledit all_remaining end end
if {![dict get $ARG_INFO $clausename -multiple]} {
if {!$clause_is_multiple} {
ledit tailnames end end
}
break
@ -5496,10 +5531,9 @@ tcl::namespace::eval punk::args {
stringendswith {
set sfx [lindex $tp_alternative 1]
if {[string match "*$sfx" $v]} {
set alloc_ok 1
set alloc_ok 1
ledit all_remaining end end
if {![dict get $ARG_INFO $clausename -multiple]} {
if {!$clause_is_multiple} {
ledit tailnames end end
}
break
@ -5510,7 +5544,7 @@ tcl::namespace::eval punk::args {
}
}
if {!$alloc_ok} {
if {![dict get $ARG_INFO $clausename -optional]} {
if {!$clause_is_optional} {
break
}
}
@ -5532,6 +5566,7 @@ tcl::namespace::eval punk::args {
set reverse_type_index 0
#todo handle type-alternates
# for example: -type {string literal(x)|literal(y)}
# -type {string literal(max)|literal(min)|int}
foreach tp $rtypelist {
#set rv [lindex $rcvals end-$alloc_count]
set rv [lindex $all_remaining end-$alloc_count]
@ -5541,8 +5576,24 @@ tcl::namespace::eval punk::args {
set clause_member_optional 0
}
set tp [string trim $tp ?]
puts "_get_dict_can_assign_value: checking tp '$tp' against value '$rv'"
switch -glob -- $tp {
literal* {
"literal(*" {
set litmatch [string range $tp 8 end-1]
if {$rv eq $litmatch} {
set alloc_ok 1 ;#we need at least one literal-match to set alloc_ok
incr alloc_count
} else {
if {$clause_member_optional} {
#
} else {
set alloc_ok 0
break
}
}
}
XXXliteral* {
#JJJ
set litinfo [string range $tp 7 end]
set match [string range $litinfo 1 end-1]
#todo -literalprefix
@ -5607,7 +5658,7 @@ tcl::namespace::eval punk::args {
#set all_remaining [lrange $all_remaining end-$n end]
set all_remaining [lrange $all_remaining 0 end-$alloc_count]
#don't lpop if -multiple true
if {![dict get $ARG_INFO $clausename -multiple]} {
if {!$clause_is_multiple} {
#lpop tailnames
ledit tailnames end end
}
@ -6824,10 +6875,30 @@ tcl::namespace::eval punk::args {
break
}
}
path -
file -
directory -
directory {
#see comments in existingpath/existingfile/existingdirectory case about the challenges of validating filesystem paths in a general way that works across platforms and use cases.
if {[tcl::string::length $e_check]==0 || [string first \0 $e_check] >= 0} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which doesn't look like it could be a path, file or directory"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
}
lset clause_results $c_idx $a_idx 1
}
existingpath -
existingfile -
existingdirectory {
#do we need types for relative vs absolute paths? readable writable executable owned?
#on windows limit to certain file extensions?
#fileutil::magic::filetype?
#Perhaps these are steps too far for a general validation framework.
#consider - callback validation functions instead?
#ideally we want to define callback validation functions that can work not just on a single argument at a time.
#e.g for testing that 2 file arguments do or don't refer to the same file or are in same directory or same filesystem etc.
#we have to support file and directory names on all platforms - and even characters illegal on a filesystem/platform may need to be passed.
#For example a file/folder may be created with an illegal name on a platform (or mounted on it) and be mapped to another string on the filesystem
#- yet it may remain accessible to commands such as file stat etc via the string with 'illegal' characters as well as its underlying stored (mapped) name.
@ -6838,17 +6909,43 @@ tcl::namespace::eval punk::args {
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
}
if {$type eq "existingfile"} {
if {![file exists $e_check]} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which is not an existing file"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
# -------------------------------------------------------
#review - what do we want to happen with links?
#on unix TCL's file readlink should reliably give us a path to determine the type pointed to.
#on windows we can do so if the link happens to be a junction.
#however on windows we can also have symbolic links which are not junctions and which may point to files or directories
#- but unfortunately tcl's file readlink doesn't seem to be able to read them at all - raises an error.
#(the error seems to be different for a file vs a directory target - but this seems an unreliable mechanism to determine the type of the target)
#At the moment TCL's 'file isfile' and 'file isdirectory' both seem to do the right things for links
#despite the above - treating them as the type of their target
# review whether this is reliable in all cases on windows.
# -------------------------------------------------------
#windows shortcuts (.lnk files) can point to a file or directory - but we can quite reasonably treat them only as files,
#as users *probably* won't have the expectation that a shortcut which points to a directory should be treated as a directory.
switch -exact -- $type {
existingpath {
if {![file exists $e_check]} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which is not an existing path"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
}
}
} elseif {$type eq "existingdirectory"} {
if {![file isdirectory $e_check]} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which is not an existing directory"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
existingfile {
if {![file isfile $e_check]} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which is not an existing file"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
}
}
existingdirectory {
if {![file isdirectory $e_check]} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which is not an existing directory"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
continue
}
}
}
lset clause_results $c_idx $a_idx 1
@ -6857,6 +6954,11 @@ tcl::namespace::eval punk::args {
existingportabledirectory -
portablefile -
portabledirectory {
#review - many absolute paths are not strictly portable when considered as a whole e.g /usr/local/bin c:/test
#- but the idea was more about the directory and file name components being portable excluding the first component.
#this concept may need work as it's unintuitive what it means to be a portable file/directory vs not.
#what about windows specific paths such as //?/ //./ or UNC paths?
if {[tcl::string::length $e_check]==0 || [string first \0 $e_check] >= 0 || [punk::winpath::illegalname_test $e_check]} {
set msg "$argclass $argname for %caller% requires type '$type'. Received: '$e' which doesn't look like it could be a portable file or directory (must pass punk::winpath::illegalname_test)"
lset clause_results $c_idx $a_idx [list err [list typemismatch $type] msg $msg]
@ -8747,7 +8849,7 @@ tcl::namespace::eval punk::args {
set leadername [lindex $LEADER_NAMES $nameidx]
set ldr [lindex $leaders $ldridx]
if {$leadername ne ""} {
set leadertypelist [tcl::dict::get $argstate $leadername -type]
set leadertypelist [tcl::dict::get $argstate $leadername -type] ;#often a single type, but can be a list of types (possibly with some optional) for a type that is a clause accepting multiple values.
set leader_clause_size [llength $leadertypelist]
set assign_d [_get_dict_can_assign_value $ldridx $leaders $nameidx $LEADER_NAMES $leadernames_received $formdict]
@ -8786,11 +8888,23 @@ tcl::namespace::eval punk::args {
set clauseval $resultlist
incr ldridx [expr {$consumed - 1}]
#not quite right.. this sets the -type for all clauses - but they should run independently
#e.g if expr {} elseif 2 {script2} elseif 3 then {script3} (where elseif clause defined as "literal(elseif) expr ?literal(then)? script")
#the elseif 2 {script2} will raise an error because the newtypelist from elseif 3 then {script3} overwrote the newtypelist where then was given the type ?omitted-...?
#not quite right.. this modifies the -type for all clauses with this name - but for -multiple true each instance should really be considered separately.
#e.g when a subelement-containing clause is allowed to appear multiple times (-multiple true)
# - we may hava a situation where the supplied arguments do and don't omit optional subelements,
# and the newtypelist from one clause may overwrite the newtypelist from the other clause where the optional subelement was omitted in one arg, but not in the other arg.
# - if expr {} elseif 2 {script2} elseif 3 then {script3}
# - (where elseif clause defined as "literal(elseif) expr ?literal(then)? script")
# The elseif 2 {script2} will reassign the type as "literal(elseif) expr ?omitted-literal(then)? script"
# when the elseif 3 then {script3} is processed, 'then' is now considered against the type ?ommitted-literal(then)?
#which (as a non-recognised type is therefore not validated ) will then
# allow any value instead of 'then' to pass.
#see argument_clause_typestate in value processing loop below for more handling of this issue regarding -multiple true clauses with optional subelements
#todo - synchronize with value processing loop below
#- consider refactor to a common procedure for handling this issue of tracking updated typelist state for optional subelements in -multiple true clauses
tcl::dict::set argstate $leadername -type $newtypelist ;#(possible ?omitted-<type>? and ?defaulted-<type>? entries
#incorrect -don't update default -type info.
#tcl::dict::set argstate $leadername -type $newtypelist ;#(possible ?omitted-<type>? and ?defaulted-<type>? entries
}
if {[tcl::dict::get $argstate $leadername -multiple]} {
@ -8952,7 +9066,7 @@ tcl::namespace::eval punk::args {
}
#incorrect - we shouldn't update the default. see argument_clause_typestate dict of lists of -type
tcl::dict::set argstate $valname -type $newtypelist ;#(possible ?omitted-<type>? and ?defaulted-<type>? entries
#tcl::dict::set argstate $valname -type $newtypelist ;#(possible ?omitted-<type>? and ?defaulted-<type>? and ?validated-<type>? entries
}
if {[tcl::dict::get $argstate $valname -multiple]} {
@ -9254,7 +9368,7 @@ tcl::namespace::eval punk::args {
}
set vlist_typelist [list]
if {[dict exists $argument_clause_typestate $argname]} {
#lookup saved newtypelist (argument_clause_typelist) from can_assign_value result where some optionals were given type ?omitted-<tp>? or ?defaulted-<tp>?
#lookup saved newtypelist (argument_clause_typelist) from can_assign_value result where some optionals were given type ?omitted-<tp>? or ?defaulted-<tp>? or ?validated-<tp>?.
# args.test: parse_withdef_value_clause_missing_optional_multiple
set vlist_typelist [dict get $argument_clause_typestate $argname]
} else {
@ -9363,11 +9477,12 @@ tcl::namespace::eval punk::args {
#fast fail on the wrong number of choices
if {[llength $c_list] < $choicemultiple_min} {
set msg "$argclass $argname for %caller% requires at least $choicemultiple_min choices. Received [llength $c_list] choices."
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list choicecount [llength $c_list] minchoices $choicemultiple_min maxchoices $choicemultiple_max] -badarg $argname]] $msg
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list choicecount [llength $c_list] minchoices $choicemultiple_min maxchoices $choicemultiple_max] -badarg $argname -argspecs $argspecs]] $msg
#return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list optionmissing $full_missing received $flagsreceived] -argspecs $argspecs]] $msg
}
if {$choicemultiple_max != -1 && [llength $c_list] > $choicemultiple_max} {
set msg "$argclass $argname for %caller% requires at most $choicemultiple_max choices. Received [llength $c_list] choices."
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list choicecount [llength $c_list] minchoices $choicemultiple_min maxchoices $choicemultiple_max] -badarg $argname]] $msg
return -options [list -code error -errorcode [list PUNKARGS VALIDATION [list choicecount [llength $c_list] minchoices $choicemultiple_min maxchoices $choicemultiple_max] -badarg $argname -argspecs $argspecs]] $msg
}
#-----------------------------------
@ -9483,7 +9598,19 @@ tcl::namespace::eval punk::args {
}
tcl::dict::set $dname $argname_or_ident $existing
} else {
lset existing $element_index $choice_idx $chosen
#test required.
# punk::args::parse {{read write w}} withdef @values {mode -type list -choices {read write} -choicemultiple {1 -1}}
#puts ">>> clause_size $clause_size"
#puts ">>> existing $existing"
#puts ">>> lset existing $element_index $choice_idx $chosen"
if {$clause_size == 1} {
#e.g -type list
#we have multiple choices allowed for a single element clause because that clause type is a list.
lset existing $choice_idx $chosen
} else {
#e.g -type {any any}
lset existing $element_index $choice_idx $chosen
}
tcl::dict::set $dname $argname_or_ident $existing
}
}

438
src/vfs/_vfscommon.vfs/modules/punk/args/moduledoc/tclcore-0.1.0.tm

@ -102,7 +102,8 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
set manbase_tcl "https://tcl.tk/man/tcl/TclCmd"
set manbase_ext .htm
} else {
set manbase_tcl "https://tcl.tk/man/tcl9.0/TclCmd"
set tclv [info tclversion] ;#e.g 9.0 9.1
set manbase_tcl "https://tcl.tk/man/tcl${tclv}/TclCmd"
set manbase_ext .html
}
proc manpage_tcl {cmd} {
@ -1468,7 +1469,10 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
namespace eval argdoc {
lappend PUNKARGS [list {
@id -id ::tcl::chan::blocked
@cmd -name "Built-in: tcl::chan::blocked" -help\
@cmd -name "Built-in: tcl::chan::blocked"\
-summary\
"Test whether the last input operation failed because it would have blocked."\
-help\
"This tests whether the last input operation on the channel called ${$I}channel${$NI}
failed because it would otherwise have caused the process to block, and returns 1
if that was the case. It returns 0 otherwise. Note that this only ever returns 1
@ -1481,15 +1485,19 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
lappend PUNKARGS [list {
@id -id ::tcl::chan::close
@cmd -name "Built-in: tcl::chan::close" -help\
@cmd -name "Built-in: tcl::chan::close"\
-summary\
"Close and destroy a channel."\
-help\
"Close and destroy the channel called channel. Note that this deletes all existing file-events
registered on the channel. If the direction argument (which must be read or write or any
registered on the channel. If the direction argument (which must be ${$B}read${$N} or ${$B}write${$N} or any
unique abbreviation of them) is present, the channel will only be half-closed, so that it can
go from being read-write to write-only or read-only respectively. If a read-only channel is
closed for reading, it is the same as if the channel is fully closed, and respectively similar
for write-only channels. Without the direction argument, the channel is closed for both reading
and writing (but only if those directions are currently open). It is an error to close a
read-only channel for writing, or a write-only channel for reading.
As part of closing the channel, all buffered output is flushed to the channel's output device
(only if the channel is ceasing to be writable), any buffered input is discarded (only if the
channel is ceasing to be readable), the underlying operating system resource is closed and
@ -1540,6 +1548,7 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
{Query/set channel configuration options}\
-help\
{Query or set the configuration options of the channel named ${$I}channel${$NI}
If no ${$I}optionName${$NI} or ${$I}value${$NI} arguments are supplied, the
command returns a list containing alternating option names and values for the
channel. If ${$I}optionName${$NI} is supplied but no ${$I}value${$NI} then the
@ -1809,6 +1818,57 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
}
}]
lappend PUNKARGS [list {
@id -id ::tcl::chan::create
@cmd -name "Built-in: tcl::chan::create"\
-summary\
"Create new script level channel."\
-help\
"This subcommand creates a new script level channel using the command prefix ${$I}cmdPrefix${$NI} as its handler.
Any such channel is called a ${$B}reflected${$N} channel. The specified command prefix, ${$I}cmdPrefix${$NI}, must be a non-empty list,
and should provide the API described in the ${$B}refchan${$N} manual page. The handle of the new channel is returned as the
result of the ${$B}chan create${$N} command, and the channel is open. Use either ${$B}close${$N} or ${$B}chan close${$N} to remove the channel.
The argument mode specifies if the new channel is opened for reading, writing, or both. It has to be a list
containing any of the strings “read” or “write”, The list must have at least one element, as a channel you can
neither write to nor read from makes no sense. The handler command for the new channel must support the chosen mode,
or an error is thrown.
The command prefix is executed in the global namespace, at the top of call stack, following the appending of arguments
as described in the ${$B}refchan${$N} manual page. Command resolution happens at the time of the call. Renaming the command, or
destroying it means that the next call of a handler method may fail, causing the channel command invoking the handler
to fail as well. Depending on the subcommand being invoked, the error message may not be able to explain the reason
for that failure.
Every channel created with this subcommand knows which interpreter it was created in, and only ever executes its
handler command in that interpreter, even if the channel was shared with and/or was moved into a different interpreter.
Each reflected channel also knows the thread it was created in, and executes its handler command only in that thread,
even if the channel was moved into a different thread. To this end all invocations of the handler are forwarded to the
original thread by posting special events to it. This means that the original thread (i.e. the thread that executed the
${$B}chan create${$N} command) must have an active event loop, i.e. it must be able to process such events. Otherwise the thread
sending them will block indefinitely. Deadlock may occur.
Note that this permits the creation of a channel whose two endpoints live in two different threads, providing a
stream-oriented bridge between these threads. In other words, we can provide a way for regular stream communication
between threads instead of having to send commands.
When a thread or interpreter is deleted, all channels created with this subcommand and using this thread/interpreter as
their computing base are deleted as well, in all interpreters they have been shared with or moved into, and in whatever
thread they have been transferred to. While this pulls the rug out under the other thread(s) and/or interpreter(s),
this cannot be avoided. Trying to use such a channel will cause the generation of a regular error about unknown channel
handles.
This subcommand is ${$B}safe${$N} and made accessible to safe interpreters. While it arranges for the execution of arbitrary Tcl
code the system also makes sure that the code is always executed within the safe interpreter."
@values -min 2 -max 2
#man page says must be at least one element in mode list.
#man page doesn't limit list to 2 elements long despite there being only 2 mode values
# - suggests things such as {r write read w ...} without limit on length is allowed
mode -type list -choices {read write} -choicemultiple {1 -1} -help\
"list of at least one of read write or abbreviations of these"
cmdprefix -type string
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::eof
@cmd -name "Built-in: tcl::chan::eof"\
@ -1823,7 +1883,57 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
""
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
#event
lappend PUNKARGS [list {
@id -id ::tcl::chan::event
@cmd -name "Built-in: tcl::chan::event"\
-summary\
"Create, delete or query a file event handler."\
-help\
"Arrange for the Tcl script script to be installed as a file event handler to be called whenever the channel
called channel enters the state described by event (which must be either readable or writable); only one such
handler may be installed per event per channel at a time. If script is the empty string, the current handler
is deleted (this also happens if the channel is closed or the interpreter deleted). If script is omitted, the
currently installed script is returned (or an empty string if no such handler is installed). The callback is
only performed if the event loop is being serviced (e.g. via vwait or update).
A file event handler is a binding between a channel and a script, such that the script is evaluated whenever
the channel becomes readable or writable. File event handlers are most commonly used to allow data to be
received from another process on an event-driven basis, so that the receiver can continue to interact with the
user or with other channels while waiting for the data to arrive. If an application invokes ${$B}chan gets${$N} or
${$B}chan read${$N} on a blocking channel when there is no input data available, the process will block; until the input
data arrives, it will not be able to service other events, so it will appear to the user to “freeze up”.
With ${$B}chan event${$N}, the process can tell when data is present and only invoke ${$B}chan gets${$N} or ${$B}chan read${$N} when they
will not block.
A channel is considered to be readable if there is unread data available on the underlying device. A channel is
also considered to be readable if there is unread data in an input buffer, except in the special case where the
most recent attempt to read from the channel was a ${$B}chan gets${$N} call that could not find a complete line in the
input buffer. This feature allows a file to be read a line at a time in non-blocking mode using events.
A channel is also considered to be readable if an end of file or error condition is present on the underlying
file or device. It is important for script to check for these conditions and handle them appropriately;
for example, if there is no special check for end of file, an infinite loop may occur where script reads no
data, returns, and is immediately invoked again.
A channel is considered to be writable if at least one byte of data can be written to the underlying file or
device without blocking, or if an error condition is present on the underlying file or device. Note that client
sockets opened in asynchronous mode become writable when they become connected or if the connection fails.
Event-driven I/O works best for channels that have been placed into non-blocking mode with the chan configure
command. In blocking mode, a ${$B}chan puts${$N} command may block if you give it more data than the underlying file or
device can accept, and a ${$B}chan gets${$N} or ${$B}chan read${$N} command will block if you attempt to read more data than is
ready; no events will be processed while the commands block. In non-blocking mode ${$B}chan puts${$N}, ${$B}chan read${$N}, and
${$B}chan gets${$N} never block.
The script for a file event is executed at global level (outside the context of any Tcl procedure) in the
interpreter in which the chan event command was invoked. If an error occurs while executing the script then the
command registered with interp bgerror is used to report the error. In addition, the file event handler is
deleted if it ever returns an error; this is done in order to prevent infinite loops due to buggy handlers."
@values -min 2 -max 3
channel
event -choices {readable writable}
script -optional 1
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::flush
@cmd -name "Built-in: tcl::chan::flush"\
@ -1878,9 +1988,58 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
channel
varName -optional 1
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
#isbinary
#names
#pending
lappend PUNKARGS [list {
@id -id ::tcl::chan::isbinary
@cmd -name "Built-in: tcl::chan::isbinary"\
-summary\
"Test if channel is binary (encoding iso8859-1, eofchar {}, translation lf)."\
-help\
"Test whether the channel called ${$I}channel${$NI} is a binary channel, returning 1 if it is and, and 0 otherwise.
A binary channel is a channel with iso8859-1 encoding, -eofchar set to {} and -translation set to lf."
@values -min 1 -max 1
channel
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
#chan names - deviation from online manual to add point about channel names and transformations
lappend PUNKARGS [list {
@id -id ::tcl::chan::names
@cmd -name "Built-in: tcl::chan::names"\
-summary\
"List all channel names. (toplevel)"\
-help\
{Produces a list of all channel names (*).
If pattern is specified, only those channel names that match it (according to the rules of string match)
will be returned.
* Note that the channel names returned are not necessarily the same as the channel names that are visible
in a given interpreter.
For example, if channel transformations are in use on stdin, stdout, or stderr, the channel names returned
will different for those channels.
e.g you may still be able to call ${$B}puts stdout "hello"${$N} even though ${$B}chan names${$N} does not return 'stdout'
It may instead show in the result list as something like 'file17f99e788b0'.
See the documentation for chan push for more details on this.}
@values -min 0 -max 1
pattern -optional 1 -default "*"
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::pending
@cmd -name "Built-in: tcl::chan::pending"\
-summary\
"Number of pending bytes buffered."\
-help\
"Depending on whether mode is input or output, returns the number of bytes of input or output (respectively)
currently buffered internally for channel (especially useful in a readable event callback to impose
application-specific limits on input line lengths to avoid a potential denial-of-service attack where a
hostile user crafts an extremely long line that exceeds the available memory to buffer it). Returns -1 if
the channel was not opened for the mode in question."
@values -min 2 -max 2
mode -choices {input output}
channel -type string
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::pipe
@cmd -name "Built-in: tcl::chan::pipe"\
@ -1921,6 +2080,24 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
channel -type string
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::push
@cmd -name "Built-in: tcl::chan::push"\
-summary\
"Add a new transformation on top of channel."\
-help\
"Adds a new transformation on top of the channel ${$I}channel${$NI}.
The ${$I}cmdPrefix${$NI} argument describes a list of one or more words which represent a handler
that will be used to implement the transformation. The command prefix must provide the
API described in the ${$B}transchan${$N} manual page. The result of this subcommand is a handle to
the transformation. Note that it is important to make sure that the transformation is
capable of supporting the channel mode that it is used with or this can make the channel
neither readable nor writable."
@values -min 2 -max 2
channel -type string
cmdPrefix -type string
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::puts
@cmd -name "Built-in: tcl::chan::puts"\
@ -2262,7 +2439,9 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
is equivalent to a false result. The key/value pairs
are tested in the order in which the keys were inserted
into the dictionary."
vars -type list -minsize 2 -maxsize 2 -typesynopsis {{keyVariable valueVariable}} -optional 0
vars -type list -minsize 2 -maxsize 2 -typesynopsis {{keyVariable valueVariable}} -optional 0 -help\
"Two element list of variable names to be used for the
key and value respectively"
script -type script
@form -form value
@ -2421,7 +2600,10 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
lappend PUNKARGS [list {
@id -id ::tcl::dict::map
@cmd -name "Built-in: tcl::dict::map" -help\
@cmd -name "Built-in: tcl::dict::map"\
-summary\
"Apply a transformation to each value of a dictionary, returning a new dictionary."\
-help\
"This command applies a transformation to each element of a dictionary,
returning a new dictionary. It takes three arguments: the first is a
two-element list of variable names (for the key and value respectively of
@ -2919,6 +3101,7 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
name -type string
} "@doc -name Manpage: -url [manpage_tcl file]" ]
#tcl 9+
lappend PUNKARGS [list {
@id -id ::tcl::file::home
@cmd -name "Built-in: tcl::file::home" -help\
@ -2952,6 +3135,48 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
#join
#link
lappend PUNKARGS [list {
@id -id ::tcl::file::link
@cmd -name "Built-in: tcl::file::link"\
-summary\
"Create a link or return the value of a link."\
-help\
"If only one argument is given, that argument is assumed to be linkName, and this command returns the value
of the link given by linkName (i.e. the name of the file it points to). If linkName is not a link or its
value cannot be read (as, for example, seems to be the case with hard links, which look just like ordinary
files), then an error is returned.
If 2 arguments are given, then these are assumed to be linkName and target. If linkName already exists, or
if target does not exist, an error will be returned. Otherwise, Tcl creates a new link called linkName which
points to the existing filesystem object at target (which is also the returned value), where the type of the
link is platform-specific (on Unix a symbolic link will be the default). This is useful for the case where
the user wishes to create a link in a cross-platform way, and does not care what type of link is created.
If the user wishes to make a link of a specific type only, (and signal an error if for some reason that is
not possible), then the optional -linktype argument should be given. Accepted values for -linktype are
“-symbolic” and “-hard”.
On Unix, symbolic links can be made to relative paths, and those paths must be relative to the actual
linkName's location (not to the cwd), but on all other platforms where relative links are not supported,
target paths will always be converted to absolute, normalized form before the link is created
(and therefore relative paths are interpreted as relative to the cwd). When creating links on filesystems
that either do not support any links, or do not support the specific type requested, an error message will
be returned. Most Unix platforms support both symbolic and hard links (the latter for files only).
Windows supports symbolic directory links and hard file links on NTFS drives.
"
@opts -type none -parsekey "-LINKTYPE" -group "linktype" -grouphelp\
""
-symbolic -typedefaults "-symbolic" -help\
""
-hard -typedefaults "-hard" -help\
"
"
@opts -parsekey "" -group ""
@values -min 1 -max 2
linkName -type string -optional 0
target -type string -optional 1
} "@doc -name Manpage: -url [manpage_tcl file]" ]
#lstat
lappend PUNKARGS [list {
@ -2986,8 +3211,37 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
name -type string
time -type integer -optional 1
} "@doc -name Manpage: -url [manpage_tcl file]"]
#nativename
#normalize
lappend PUNKARGS [list {
@id -id ::tcl::file::nativename
@cmd -name "Built-in: tcl::file::nativename"\
-summary\
{Platform-specific name of the file.}\
-help\
"Returns the platform-specific name of the file. This is useful if the filename is needed to pass
to a platform-specific call, such as to a subprocess via ${$B}exec${$N} under Windows (see EXAMPLES below)."
@values -min 1 -max 1
name -optional 0 -type string
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::normalize
@cmd -name "Built-in: tcl::file::normalize"\
-summary\
{Unique normalized path.}\
-help\
"Returns a unique normalized path representation for the file-system object (file, directory, link, etc),
whose string value can be used as a unique identifier for it. A normalized path is an absolute path which
has all “../” and “./” removed. Also it is one which is in the “standard” format for the native platform.
On Unix, this means the segments leading up to the path must be free of symbolic links/aliases (but the
very last path component may be a symbolic link), and on Windows it also means we want the long form with
that form's case-dependence (which gives us a unique, case-dependent path). The one exception concerning
the last link in the path is necessary, because Tcl or the user may wish to operate on the actual
symbolic link itself (for example ${$B}file delete${$N}, ${$B}file rename${$N}, ${$B}file copy${$N} are defined to operate on symbolic
links, not on the things that they point to)."
@values -min 1 -max 1
name -optional 0 -type string
} "@doc -name Manpage: -url [manpage_tcl file]"]
#owned
#pathtype
lappend PUNKARGS [list {
@ -3015,6 +3269,24 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
} "@doc -name Manpage: -url [manpage_tcl file]"]
#rename (2 forms)
lappend PUNKARGS [list {
@id -id ::tcl::file::rename
@cmd -name "Built-in: tcl::file::rename"\
-summary\
{Rename file or folder.}\
-help\
""
#----------------------------------------------
@form -form "tofile"
@opts
-force -type none -optional 1 -default 0
-- -type none -optional 1
@values -min 2 -max 2
source -optional 0 -type string
#----------------------------------------------
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::rootname
@cmd -name "Built-in: tcl::file::rootname"\
@ -3030,14 +3302,134 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
#separator
#size
#split
#stat
#system
#tail
#tempdir
#tempfile
lappend PUNKARGS [list {
@id -id ::tcl::file::stat
@cmd -name "Built-in: tcl::file::stat"\
-summary\
{Get file metadata - status information.}\
-help\
"Invokes the stat kernel call on name, and returns a dictionary with the information returned from
the kernel call. If varName is given, it uses the variable to hold the information. VarName is
treated as an array variable, and in such case the command returns the empty string. The following
elements are set: ${$B}atime${$N}, ${$B}ctime${$N}, ${$B}dev${$N}, ${$B}gid${$N}, ${$B}ino${$N}, ${$B}mode${$N}, ${$B}mtime${$N}, ${$B}nlink${$N}, ${$B}size${$N}, ${$B}type${$N}, ${$B}uid${$N}.
Each element except ${$B}type${$N} is a decimal string with the value of the corresponding field from the
stat return structure; see the manual entry for stat for details on the meanings of the values.
The type element gives the type of the file in the same form returned by the command ${$B}file type${$N}."
@values -min 1 -max 1
name -optional 0 -type string
varName -type string -optional 1
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::system
@cmd -name "Built-in: tcl::file::system"\
-summary\
{filesystem info for path}\
-help\
"Returns a list of one or two elements, the first of which is the name of the filesystem to use for
the file, and the second, if given, an arbitrary string representing the filesystem-specific nature
or type of the location within that filesystem. If a filesystem only supports one type of file, the
second element may not be supplied. For example the native files have a first element “native”, and
a second element which when given is a platform-specific type name for the file's system
(e.g. “NTFS”, “FAT”, on Windows). A generic virtual file system might return the list “vfs ftp” to
represent a file on a remote ftp site mounted as a virtual filesystem through an extension called
“vfs”. If the file does not belong to any filesystem, an error is generated."
@values -min 1 -max 1
name -optional 0 -type string
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::tail
@cmd -name "Built-in: tcl::file::tail"\
-summary\
{Last filesystem component of path}\
-help\
"Returns all of the characters in the last filesystem component of ${$I}name${$NI}.
Any trailing directory separator in ${$I}name${$NI} is ignored. If ${$I}name${$NI} contains no separators then returns ${$I}name${$NI}.
So, ${$B}file tail a/b${$N}, ${$B}file tail a/b/${$N} and ${$B}file tail b${$N} all return ${$B}b${$N}."
@values -min 1 -max 1
name -optional 0 -type string
} "@doc -name Manpage: -url [manpage_tcl file]"]
#tempdir tcl 9+ only?
lappend PUNKARGS [list {
@id -id ::tcl::file::tempdir
@cmd -name "Built-in: tcl::file::tempdir"\
-summary\
{Create a temporary directory.}\
-help\
"Creates a temporary directory (guaranteed to be newly created and writable by the current script)
and returns its name. If template is given, it specifies one of or both of the existing directory
(on a filesystem controlled by the operating system) to contain the temporary directory, and the
base part of the directory name; it is considered to have the location of the directory if there
is a directory separator in the name, and the base part is everything after the last directory
separator (if non-empty). The default containing directory is determined by system-specific
operations, and the default base name prefix is “tcl”.
The following output is typical and illustrative; the actual output will vary between platforms:
${[punk::args::helpers::example {
% ${$B}file tempdir${$N}
/var/tmp/tcl_u0kuy5
% ${$B}file tempdir /tmp/myapp${$N}
/tmp/myapp_8o7r9L
% ${$B}file tempdir /tmp/${$N}
/tmp/tcl_1m0JHD
% ${$B}file tempdir myapp${$N}
/var/tmp/myapp_0ihS0n
}]}
"
@values -min 0 -max 1
template -type string -optional 1
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::tempfile
@cmd -name "Built-in: tcl::file::tempfile"\
-summary\
{Create temp file and return open channel.}\
-help\
"Creates a temporary file and returns a read-write channel opened on that file.
If the nameVar is given, it specifies a variable that the name of the temporary
file will be written into; if absent, Tcl will attempt to arrange for the
temporary file to be deleted once it is no longer required. If the template is
present, it specifies parts of the template of the filename to use when creating
it (such as the directory, base-name or extension) though some platforms may
ignore some or all of these parts and use a built-in default instead.
Note that temporary files are only ever created on the native filesystem.
As such, they can be relied upon to be used with operating-system native APIs
and external programs that require a filename."
@values -min 0 -max 2
nameVar -type string -optional 1
template -type string -optional 1
} "@doc -name Manpage: -url [manpage_tcl file]"]
#tildeexpand
#type
#volumes
lappend PUNKARGS [list {
@id -id ::tcl::file::type
@cmd -name "Built-in: tcl::file::type"\
-summary\
{Type of file name.}\
-help\
"Returns a string giving the type of file name, which will be one of
${$B}file${$N}, ${$B}directory${$N}, ${$B}characterSpecial${$N}, ${$B}blockSpecial${$N}, ${$B}fifo${$N}, ${$B}link${$N}, or ${$B}socket${$N}."
@values -min 0 -max 0
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::volumes
@cmd -name "Built-in: tcl::file::volumes"\
-summary\
"List volumes mounted on the system."\
-help\
"Returns the absolute paths to the volumes mounted on the system, as a proper Tcl list.
Without any additional virtual filesystems mounted as root volumes, on UNIX, the command
will return “//zipfs:/”/ or “/”, (in case of a --disable-zipfs build), since all
filesystems are locally mounted. On Windows, it will return a list of the available
local drives (e.g. “//zipfs:/ C:/”). If any virtual filesystem has mounted additional
volumes, they will be in the returned list too."
@values -min 0 -max 0
} "@doc -name Manpage: -url [manpage_tcl file]"]
lappend PUNKARGS [list {
@id -id ::tcl::file::writable
@ -6694,21 +7086,21 @@ tcl::namespace::eval punk::args::moduledoc::tclcore {
start -type number|expr
..|to -type string -choices {.. to} -optional 1
end -type number|expr
"by step" -type {literal(by) number|expr} -optional 1
"by step" -type {?literalprefix(by)? number|expr} -optional 1
@form -form start_count
@leaders -min 0 -max 0
@values -min 3 -max 5
start -type number|expr
count -type literal
count -type literalprefix(count)
countelements -type number|expr
"by step" -type {literal(by) number|expr} -optional 1
"by step" -type {literalprefix(by) number|expr} -optional 1
@form -form count
@leaders -min 0 -max 0
@values -min 1 -max 3
countelements -type number|expr
"by step" -type {literal(by) number|expr} -optional 1
"by step" -type {literalprefix(by) number|expr} -optional 1
} "@doc -name Manpage: -url [manpage_tcl lseq]"\
{

174
src/vfs/_vfscommon.vfs/modules/punk/auto_exec-0.1.0.tm

@ -56,7 +56,9 @@ tcl::namespace::eval punk::auto_exec {
-help\
{Clear/refresh the autoexec commands in the ::auto_execs array.
This is analogous to the 'rehash' command in shells such as csh, tcsh and zsh.
This is analogous to the 'rehash' command in shells such as csh, tcsh and zsh,
or 'hash -r' in other shells such as bash.
It updates the shell's hash table of executable commands.
This can be useful after installing new software, adjusting the environment PATH directories, or (on windows) making
@ -64,7 +66,9 @@ tcl::namespace::eval punk::auto_exec {
commands are up to date with the current state of the system.
If refresh is false (the default), then all autoexec commands are cleared and will re-register as commands are called.
If refresh is true, then all existing autoexec commands are re-registered by calling auto_execok for each of them again.}
If refresh is true, then all existing autoexec commands are re-registered by calling auto_execok for each of them again.
see also ::punk::auto_exec::hash}
@opts
@values -min 0 -max 1
refresh -type boolean -default 0 -help\
@ -85,6 +89,172 @@ tcl::namespace::eval punk::auto_exec {
}
return
}
namespace eval argdoc {
lappend PUNKARGS [list {
@id -id "::punk::auto_exec::hash"
@cmd -name "punk::auto_exec::hash"\
-summary\
"Manage the hash table of autoexec commands cached in ::auto_execs."\
-help\
{see also ::punk::auto_exec::rehash}
#---------------------
@form -form {show_or_set}
@opts -min 0 -max 0
@values -min 0 -max -1
name -type string -multiple 1 -optional 1 -default {} -help\
"One or more autoexec command names to set.
If no names are provided, then all autoexec commands in the hash table will be shown."
#---------------------
@form -form {rehash}
@opts -min 1 -max 1
-r -type none -optional 0 -help\
"Clear autoexec commands from the hash table"
@values -min 0 -max 0
#---------------------
@form -form {test}
@opts
-t -type none -optional 0 -default "" -help\
"The name of the autoexec command name to display."
@values -min 1 -max -1
name -type string -multiple 1 -help\
"One or more autoexec command names to display information for.
If only a single name is provided, then the output will be the raw command string
associated with that autoexec command in the hash table.
If multiple names are provided, then the output will be a string containing each
name and its associated command string on a separate line."
#---------------------
@form -form {delete}
@opts
-d -type none -optional 0 -help\
"Delete specified autoexec commands from the hash table."
@values -min 1 -max -1
name -type string -multiple 1 -help\
"One or more autoexec command names to delete from the hash table."
#---------------------
#todo?
#-p <path> <name> (manually assign)
#-l (build a list of hash -p <path> <name> entries for all autoexec commands that can be used in a script to pre-populate the hash table without needing to call auto_execok for each command at runtime)
#---------------------
@form -form {help}
@opts -min 1 -max 1 -anyopts 1
--help -type none -optional 0 -help\
"Display usage information for this command."
@values -min 0 -max -1
ignored -type any -multiple 1 -optional 1 -help\
"Additional arguments that are ignored when --help is used"
}]
}
proc hash {args} {
set arg1 [lindex $args 0]
#select parsing form based on first argument
switch -- $arg1 {
-r {
set form rehash
}
-t {
set form test
}
-d {
set form delete
}
--help {
set form help
}
default {
#like bash in this context, we won't allow an option-like entry to be treated as an executable name
if {[string match -* $arg1]} {
puts stderr "hash: ${arg1}: invalid option"
#return [punk::args::usage -scheme error ::punk::auto_exec::hash]
set msg "hash: usage:\n"
append msg [punk::ns::synopsis ::punk::auto_exec::hash]
error $msg
}
set form show_or_set
}
}
set argd [punk::args::parse $args -form $form withid ::punk::auto_exec::hash]
lassign [dict values $argd] _leaders opts values received
global auto_execs
switch -- $form {
rehash {
unset -nocomplain auto_execs
}
test {
#like bash - we'll provide only the path if there is a single name provided, but if there are multiple names we'll provide both the name and path for each.
set names [dict get $values name]
if {[llength $names] == 1} {
set nm [lindex $names 0]
if {[info exists auto_execs($nm)]} {
return [set auto_execs($nm)]
} else {
#review
puts stderr "hash: $nm: not found"
return ""
}
}
set result ""
foreach nm $names {
if {[info exists auto_execs($nm)]} {
append result "$nm [set auto_execs($nm)]\n"
} else {
#review
puts stderr "$hash: nm: not found"
}
}
if {$result ne ""} {
set result [string trimright $result \n]
}
return $result
}
delete {
set names [dict get $values name]
foreach nm $names {
unset -nocomplain auto_execs($nm)
}
}
help {
return [punk::args::usage ::punk::auto_exec::hash]
}
default {
set requested_names [dict get $values name]
if {[llength $requested_names] == 0} {
#show all
set hashed_names [array names auto_execs]
#todo - record and return 'hits' like bash does?
set result ""
foreach nm $hashed_names {
set cached [set auto_execs($nm)]
#unlike some shells - we cache negative results (for absolute paths) that don't exist.
#as we're attempting to be close to behaviour of bash, don't output empty results for negative cache entries.
if {$cached ne ""} {
append result $cached \n
}
}
if {$result ne ""} {
set result [string trimright $result \n]
}
return $result
} else {
#rehash each requested name if it exists, otherwise display an msg on stderr for that name.
foreach nm $requested_names {
set aexec [auto_execok $nm]
if {$aexec ne ""} {
set auto_execs($nm) $aexec
} else {
puts stderr "hash: $nm: not found"
}
}
return
}
}
}
}
variable PUNKARGS
lappend PUNKARGS [list {

34
src/vfs/_vfscommon.vfs/modules/punk/config-0.1.tm

@ -503,16 +503,33 @@ tcl::namespace::eval punk::config {
key -type string -optional 1
newvalue -optional 1
}]
proc configure {args} {
set argd [punk::args::parse $args withid ::punk::config::configure]
lassign [dict values $argd] leaders opts values received solos
set whichconfig [dict get $argd leaders whichconfig]
proc configure {whichconfig args} {
#set argd [punk::args::parse $args withid ::punk::config::configure]
#lassign [dict values $argd] leaders opts values received solos
#set whichconfig [dict get $argd leaders whichconfig]
set values [dict create]
switch -- [llength $args] {
0 {
}
1 {
dict set values key [lindex $args 0]
}
2 {
dict set values newvalue [lindex $args 1]
}
default {
error "Too many arguments. Expected at most 2 (key [newvalue])"
}
}
variable configdata
if {"running" ni [dict keys $configdata]} {
init
Apply startup
}
switch -- $whichconfig {
set fullwhich [tcl::prefix::match -error "" {defaults startup-configuration running-configuration} $whichconfig]
switch -- $fullwhich {
defaults {
set configrecords [dict get $configdata defaults]
}
@ -522,12 +539,15 @@ tcl::namespace::eval punk::config {
running-configuration {
set configrecords [dict get $configdata running]
}
default {
error "Unknown config name '$whichconfig' - try defaults or startup-configuration or running-configuration"
}
}
if {![dict exists $received key]} {
if {![dict exists $values key]} {
return $configrecords
}
set key [dict get $values key]
if {![dict exists $received newvalue]} {
if {![dict exists $values newvalue]} {
return [dict get $configrecords $key]
}
error "setting value not implemented"

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

@ -3046,7 +3046,7 @@ namespace eval punk::console {
puts -nonewline stdout [punk::ansi::move_row $row]
}
proc move_emit {row col data args} {
upvar ::punk::console::is_v52 is_vt52
upvar ::punk::console::is_vt52 is_vt52
if {!$is_vt52} {
puts -nonewline stdout [punk::ansi::move_emit $row $col $data {*}$args]
} else {
@ -3664,6 +3664,7 @@ namespace eval punk::console::system {
}
return done
}
proc enableRaw_mintty {{channel stdin}} {
#mintty specific enableRaw
upvar ::punk::console::previous_stty_state_$channel previous_stty_state_$channel
@ -3729,6 +3730,53 @@ namespace eval punk::console::system {
return done
}
#note: twapi GetStdHandle & GetConsoleMode & SetConsoleCombo unreliable - fails with invalid handle (somewhat intermittent.. after stdin reopened?)
#could be we were missing a step in reopening stdin and console configuration?
proc enableRaw_twapi {{channel stdin}} {
#twapi version of enableRaw.
upvar ::punk::console::previous_stty_state_$channel previous_stty_state_$channel
if {[catch {twapi::get_console_handle stdin} console_handle]} {
puts stderr "enableRaw error: twapi cannot get console handle for stdin"
#review. If twapi couldn't get a console handle - no point trying other mechanisms(?)
return
}
#returns dictionary
#e.g -processedinput 1 -lineinput 1 -echoinput 1 -windowinput 0 -mouseinput 0 -insertmode 1 -quickeditmode 1 -extendedmode 1 -autoposition 0
set oldmode [twapi::get_console_input_mode]
twapi::modify_console_input_mode $console_handle -lineinput 0 -echoinput 0
# Turn off the echo and line-editing bits
#set newmode [dict merge $oldmode [dict create -lineinput 0 -echoinput 0]]
set newmode [twapi::get_console_input_mode]
tsv::set console is_raw 1
#don't disable handler - it will detect is_raw
### twapi::set_console_control_handler {}
return [list stdin [list from $oldmode to $newmode]]
}
proc disableRaw_twapi {{channel stdin}} {
#disableRaw twapi version
upvar ::punk::console::previous_stty_state_$channel previous_stty_state_$channel
set ch_state [chan conf $channel]
if {[dict exists $ch_state -inputmode]} {
chan conf $channel -inputmode normal
tsv::set console is_raw 0
return [list $channel [list from [dict get $ch_state -inputmode] to normal]]
} else {
if {[catch {twapi::get_console_handle stdin} console_handle]} {
#e.g tkcon/wish
puts stderr "disableRaw error: twapi cannot get console handle for stdin"
return ;# ???
}
set oldmode [twapi::get_console_input_mode]
# Turn on the echo and line-editing bits
twapi::modify_console_input_mode $console_handle -lineinput 1 -echoinput 1
set newmode [twapi::get_console_input_mode]
tsv::set console is_raw 0
return [list stdin [list from $oldmode to $newmode]]
}
}
proc enableRaw_powershell {{channel stdin}} {
#enableRaw_powershell is a fallback for when twapi is not present.
#It uses a persistent powershell process to set the console mode to raw, by writing commands to a named pipe that the powershell process is listening on.
@ -3737,12 +3785,8 @@ namespace eval punk::console::system {
#- but it is really intended for use in environments where twapi is not present and stty doesn't work (e.g standard windows console).
#puts stderr "punk::console::enableRaw"
#variable is_raw
#variable previous_stty_state_$channel
upvar ::punk::console::previous_stty_state_$channel previous_stty_state_$channel
#variable ps_consolemode_contents
upvar ::punk::console::ps_consolemode_contents ps_consolemode_contents
#variable ps_pipename
upvar ::punk::console::ps_pipename ps_pipename
@ -3793,6 +3837,36 @@ namespace eval punk::console::system {
error "punk::console::enableRaw Unable to use twapi or stty to set raw mode - aborting"
}
}
proc disableRaw_powershell {{channel stdin}} {
#disableRaw powershell version
upvar ::punk::console::previous_stty_state_$channel previous_stty_state_$channel
set ch_state [chan conf $channel]
if {[dict exists $ch_state -inputmode]} {
chan conf $channel -inputmode normal
tsv::set console is_raw 0
return [list $channel [list from [dict get $ch_state -inputmode] to normal]]
} else {
#tcl <= 8.6x doesn't support -inputmode
if {[set sttycmd [auto_execok stty]] ne ""} {
#this doesn't work on windows
#It may seem to - only because running *any* external utility can exit raw mode
set sttycmd [auto_execok stty]
if {[set previous_stty_state_$channel] ne ""} {
exec {*}$sttycmd [set previous_stty_state_$channel]
set previous_stty_state_$channel ""
return restored
}
exec {*}$sttycmd -raw echo <@$channel
tsv::set console is_raw 0
#do we really want to exec stty yet again to show final 'to' state?
#probably not. We should work out how to read the stty result flags and set a result.. or just limit from,to to showing echo and lineedit states.
return [list stdin [list from "[set previous_stty_state_$channel]" to "" note "fixme - to state not shown"]]
} else {
error "punk::console::disableRaw Unable to use twapi or stty to unset raw mode - aborting"
}
}
}
}
namespace eval punk::console {
@ -3822,19 +3896,24 @@ namespace eval punk::console {
#ENABLE_PROCESSED_INPUT 0x0001 ;#set to zero will allow ctrl-c to be reported as keyboard input rather than as a signal
#ENABLE_LINE_INPUT 0x0002
#ENABLE_ECHO_INPUT 0x0004
#ENABLE_WINDOW_INPUT 0x0008 (default off when a terminal created)
#ENABLE_WINDOW_INPUT 0x0008 (default off when a terminal created) enables reporting of windows resize events to console input buffer - no direct stty equiv for unix - sigwinch?
#ENABLE_MOUSE_INPUT 0x0010
#ENABLE_INSERT_MODE 0X0020
#ENABLE_QUICK_EDIT_MODE 0x0040
#ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200 (default off when a terminal created) (512)
set h_in [twapi::get_console_handle stdin]
set oldmode_in [twapi::GetConsoleMode $h_in]
set newmode_in [expr {$oldmode_in | 8}]
#set newmode_in [expr {$oldmode_in | 0x208}]
#set h_in [twapi::get_console_handle stdin]
#set oldmode_in [twapi::GetConsoleMode $h_in]
##set newmode_in [expr {$oldmode_in | 8}]
twapi::SetConsoleMode $h_in $newmode_in
return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]]
##test
#set newmode_in [expr {$oldmode_in & ~8}]
#set newmode_in [expr {$newmode_in & ~0x200}]
#twapi::SetConsoleMode $h_in $newmode_in
#return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]]
return [list stdout [list from $oldmode_out to $newmode_out]]
}
proc disableAnsi {} {
set h_out [twapi::get_console_handle stdout]
@ -3843,13 +3922,19 @@ namespace eval punk::console {
twapi::SetConsoleMode $h_out $newmode_out
#??? review
set h_in [twapi::get_console_handle stdin]
set oldmode_in [twapi::GetConsoleMode $h_in]
set newmode_in [expr {$oldmode_in & ~8}]
twapi::SetConsoleMode $h_in $newmode_in
#set h_in [twapi::get_console_handle stdin]
#set oldmode_in [twapi::GetConsoleMode $h_in]
##set newmode_in [expr {$oldmode_in & ~8}]
##test
#set newmode_in [expr {$oldmode_in | 8}]
#set newmode_in [expr {$newmode_in | 0x200}]
#twapi::SetConsoleMode $h_in $newmode_in
return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]]
#return [list stdout [list from $oldmode_out to $newmode_out] stdin [list from $oldmode_in to $newmode_in]]
return [list stdout [list from $oldmode_out to $newmode_out]]
}
proc enableVirtualTerminal {{channels {input output}}} {
set ins [list in input stdin]
@ -3885,7 +3970,9 @@ namespace eval punk::console {
if {"input" in $channels} {
set h_in [twapi::get_console_handle stdin]
set oldmode_in [twapi::GetConsoleMode $h_in]
set newmode_in [expr {$oldmode_in | 0x200}]
set newmode_in [expr {$oldmode_in | 0x208}]
#test
#set newmode_in [expr {$oldmode_in & ~0x200}]
twapi::SetConsoleMode $h_in $newmode_in
dict set result input [list from $oldmode_in to $newmode_in]
}
@ -3926,7 +4013,9 @@ namespace eval punk::console {
if {"input" in $channels} {
set h_in [twapi::get_console_handle stdin]
set oldmode_in [twapi::GetConsoleMode $h_in]
set newmode_in [expr {$oldmode_in & ~0x200}]
set newmode_in [expr {$oldmode_in & ~0x208}]
#test
#set newmode_in [expr {$oldmode_in | 0x200}]
twapi::SetConsoleMode $h_in $newmode_in
dict set result input [list from $oldmode_in to $newmode_in]
}
@ -3948,54 +4037,9 @@ namespace eval punk::console {
twapi::SetConsoleMode $h_in $newmode_in
return [list stdin [list from $oldmode_in to $newmode_in]]
}
proc enableRaw {{channel stdin}} {
#variable is_raw
variable previous_stty_state_$channel
if {[catch {twapi::get_console_handle stdin} console_handle]} {
puts stderr "enableRaw error: twapi cannot get console handle for stdin"
#review. If twapi couldn't get a console handle - no point trying other mechanisms(?)
return
}
#returns dictionary
#e.g -processedinput 1 -lineinput 1 -echoinput 1 -windowinput 0 -mouseinput 0 -insertmode 1 -quickeditmode 1 -extendedmode 1 -autoposition 0
set oldmode [twapi::get_console_input_mode]
twapi::modify_console_input_mode $console_handle -lineinput 0 -echoinput 0
# Turn off the echo and line-editing bits
#set newmode [dict merge $oldmode [dict create -lineinput 0 -echoinput 0]]
set newmode [twapi::get_console_input_mode]
tsv::set console is_raw 1
#don't disable handler - it will detect is_raw
### twapi::set_console_control_handler {}
return [list stdin [list from $oldmode to $newmode]]
}
#note: twapi GetStdHandle & GetConsoleMode & SetConsoleCombo unreliable - fails with invalid handle (somewhat intermittent.. after stdin reopened?)
#could be we were missing a step in reopening stdin and console configuration?
proc disableRaw {{channel stdin}} {
#variable is_raw
variable previous_stty_state_$channel
set ch_state [chan conf $channel]
if {[dict exists $ch_state -inputmode]} {
chan conf $channel -inputmode normal
tsv::set console is_raw 0
return [list $channel [list from [dict get $ch_state -inputmode] to normal]]
} else {
if {[catch {twapi::get_console_handle stdin} console_handle]} {
#e.g tkcon/wish
puts stderr "disableRaw error: twapi cannot get console handle for stdin"
return ;# ???
}
set oldmode [twapi::get_console_input_mode]
# Turn on the echo and line-editing bits
twapi::modify_console_input_mode $console_handle -lineinput 1 -echoinput 1
set newmode [twapi::get_console_input_mode]
tsv::set console is_raw 0
return [list stdin [list from $oldmode to $newmode]]
}
}
proc enableRaw {{channel stdin}} [info body ::punk::console::system::enableRaw_twapi]
proc disableRaw {{channel stdin}} [info body ::punk::console::system::disableRaw_twapi]
} else {
@ -4043,39 +4087,10 @@ namespace eval punk::console {
}
proc enableRaw {{channel stdint}} [info body ::punk::console::system::enableRaw_powershell]
proc enableRaw {{channel stdin}} [info body ::punk::console::system::enableRaw_powershell]
proc disableRaw {{channel stdin}} [info body ::punk::console::system::disableRaw_powershell]
proc disableRaw {{channel stdin}} {
variable previous_stty_state_$channel
set ch_state [chan conf $channel]
if {[dict exists $ch_state -inputmode]} {
chan conf $channel -inputmode normal
tsv::set console is_raw 0
return [list $channel [list from [dict get $ch_state -inputmode] to normal]]
} else {
#tcl <= 8.6x doesn't support -inputmode
if {[set sttycmd [auto_execok stty]] ne ""} {
#this doesn't work on windows
#It may seem to - only because running *any* external utility can exit raw mode
set sttycmd [auto_execok stty]
if {[set previous_stty_state_$channel] ne ""} {
exec {*}$sttycmd [set previous_stty_state_$channel]
set previous_stty_state_$channel ""
return restored
}
exec {*}$sttycmd -raw echo <@$channel
tsv::set console is_raw 0
#do we really want to exec stty yet again to show final 'to' state?
#probably not. We should work out how to read the stty result flags and set a result.. or just limit from,to to showing echo and lineedit states.
return [list stdin [list from "[set previous_stty_state_$channel]" to "" note "fixme - to state not shown"]]
} else {
error "punk::console::disableRaw Unable to use twapi or stty to unset raw mode - aborting"
}
}
}
#enableAnsi
proc enableAnsi {} {
}
@ -4112,58 +4127,8 @@ namespace eval punk::console {
proc disableVirtualTerminal {args} {
}
#NOTE - the is_raw is only being set in current interp - but the channel is shared.
#this is problematic with the repl thread being separate. - must be a tsv? REVIEW
proc enableRaw {{channel stdin}} {
#variable is_raw
variable previous_stty_state_$channel
set sttycmd [auto_execok stty]
if {[set previous_stty_state_$channel] eq ""} {
if {[catch {exec {*}$sttycmd -g <@$channel} previous_stty_state_$channel]} {
set previous_stty_state_$channel ""
}
}
#REVIEW
switch $channel {
stdin {
exec {*}$sttycmd -icanon -echo -isig
}
default {
exec {*}$sttycmd raw -echo <@$channel
}
}
catch {
tsv::set console is_raw 1
}
return [dict create previous [set previous_stty_state_$channel]]
}
proc disableRaw {{channel stdin}} {
#variable is_raw
variable previous_stty_state_$channel
set sttycmd [auto_execok stty]
if {[set previous_stty_state_$channel] ne ""} {
exec {*}$sttycmd [set previous_stty_state_$channel]
set previous_stty_state_$channel ""
tsv::set console is_raw 0
return restored
}
#REVIEW
switch $channel {
stdin {
exec {*}$sttycmd icanon echo isig
}
default {
exec {*}$sttycmd -raw echo <@$channel
}
}
catch {
tsv::set console is_raw 0
}
return done
}
proc enableRaw {{channel stdin}} [info body ::punk::console::system::enableRaw_stty]
proc disableRaw {{channel stdin}} [info body ::punk::console::system::disableRaw_stty]
}
@ -4194,9 +4159,13 @@ namespace eval punk::console {
if {[catch {punk::console::system::enableRaw_stty} errMsg]} {
puts stderr "enableRaw_stty failed: $errMsg"
}
#try also the 'best guess' implementation of enableRaw we installed above - which will be the twapi version if twapi is present, or the powershell version if not.
if {[catch {punk::console::enableRaw} errMsg]} {
puts stderr "enableRaw failed: $errMsg"
}
set failed_classinfo 1
#set failed_classinfo [catch {punk::console::class_info} classinfo]
#set failed_classinfo 1
set failed_classinfo [catch {punk::console::class_info} classinfo]
#puts stderr "got_classinfo: $failed_classinfo classinfo: $classinfo"
if {$failed_classinfo || [dict get $classinfo class] eq "mintty"} {
@ -4208,7 +4177,7 @@ namespace eval punk::console {
proc enableRaw {{channel stdin}} [info body ::punk::console::system::enableRaw_mintty]
proc disableRaw {{channel stdin}} [info body ::punk::console::system::disableRaw_mintty]
}
punk::console::system::disableRaw_stty
punk::console::disableRaw
}
}

192
src/vfs/_vfscommon.vfs/modules/punk/imap4-0.9.1.tm

@ -468,10 +468,15 @@ tcl::namespace::eval punk::imap4::proto {
lappend PUNKARGS [list {
@id -id ::punk::imap4::proto::has_capability
@cmd -name punk::imap4::proto::has_capability -help\
"Return a list of the server capabilities last received,
or a boolean indicating if a particular capability was
present."
@cmd -name punk::imap4::proto::has_capability\
-summary\
"List capabilities or test existence of a specific capability."\
-help\
"Returns a list of the server capabilities last received when called
with no argument.
Returns boolean indicating if a particular capability was
present when called with a capability argument. The capability argument is case-insensitive and should be specified in the same form as it would be expected to be received from"
@leaders -min 1 -max 1
chan -optional 0 -help\
"existing channel for an open IMAP connection"
@ -1803,6 +1808,9 @@ tcl::namespace::eval punk::imap4 {
}
return $result
}
proc lastlog {chan} {
showlog $chan [lastrequesttag $chan]
}
#protocol callbacks to api cache namespace
#msginfo
@ -2158,7 +2166,7 @@ tcl::namespace::eval punk::imap4 {
set chan [dict get $leaders chan]
set mailbox [dict get $values mailbox]
selectmbox $chan SELECT $mailbox
_selectmbox $chan SELECT $mailbox
}
lappend PUNKARGS [list {
@ -2188,10 +2196,10 @@ tcl::namespace::eval punk::imap4 {
set chan [dict get $leaders chan]
set mailbox [dict get $values mailbox]
selectmbox $chan EXAMINE $mailbox
_selectmbox $chan EXAMINE $mailbox
}
# General function for selection.
proc selectmbox {chan cmd mailbox} {
proc _selectmbox {chan cmd mailbox} {
upvar ::punk::imap4::proto::info info
variable mboxinfo
variable msginfo
@ -2319,27 +2327,54 @@ tcl::namespace::eval punk::imap4 {
A mailbox must be SELECTed first and an appropriate
sequence-set supplied for the message(s) of interest."
@leaders -min 1 -max 1
chan
chan -help\
"The channel on which to send the FETCH command.
This should be a channel returned by CONNECT or STARTTLS and that has had SELECT or
EXAMINE issued on it to select a mailbox."
@opts
-inline -type none
-inline -type none -help\
{If specified, the requested data will be returned
in the return value of this command, rather than
being stored in the msginfo cache for retrieval
using msginfo or showlog.
${[punk::args::helpers::example {
showdict [FETCH $chan -inline 1:3 UID FLAGS] */*
}]}
${[punk::args::helpers::example {
showdict [FETCH $chan -inline 1:3 {BODY.PEEK[HEADER.FIELDS (received)]}] {*/*/*/@*}
}]}
}
@values -min 2 -max -1
#todo - use same sequence-set definition across argdefs
sequence-set -help\
"Message sequence set.
1 is the lowest valid sequence number.
* represents the maximum message sequence number
in the mailbox.
e.g
1
2:2
1:3
3,5,9:10
1,10:*
*:5
*
"
1 is the lowest valid sequence number.
* represents the maximum message sequence number
in the mailbox.
e.g
1
2:2
1:3
3,5,9:10
1,10:*
*:5
*
"
queryitems -default {} -help\
"Some common FETCH queries are shown here, but
"
The data items to be fetched for each message in the sequence-set.
A value ending with a colon e.g received: or To: will be interpreted
as a request for all headers with that name.
Such a query could return multiple values for a single message.
(This is likely in particular for the Received: header)
The value(s) will be returned in the msginfo cache (and/or -inline) with the header
name and colon (lower cased) as the key.
Some common FETCH queries are shown here, but
this list isn't exhaustive."\
-multiple 1 -optional 0 -choiceprefix 0 -choicerestricted 0 -choicecolumns 2 -choices {
ALL FAST FULL BODY BODYSTRUCTURE ENVELOPE FLAGS INTERNALDATE
@ -2422,8 +2457,10 @@ tcl::namespace::eval punk::imap4 {
punk::imap4::proto::requirestate $chan SELECT
#parse each seqrange to give it a chance to raise error for bad values
#also store for use in -inline processing
set range_list [list]
foreach seqrange [split $sequenceset ,] {
parse_seq-range $chan $seqrange
lappend range_list [parse_seq-range $chan $seqrange]
}
set items {}
@ -2542,13 +2579,24 @@ tcl::namespace::eval punk::imap4 {
#This is divergent from tcllib::imap4 which returned untagged lists that the client would match
#based on assumed simple value queries such as specific properties and headers that are individually specified.
set fetchresult [dict create]
for {set i $start} {$i <= $end} {incr i} {
set flagdict [dict get $msginfo $chan $i]
#extract the fields that were added for this request_tag only
dict for {f finfo} $flagdict {
if {[dict get $finfo request] eq $request_tag} {
#lappend msgrecord [list $f $finfo]
dict set fetchresult $f $finfo
foreach r $range_list {
lassign $r start end
#puts stderr "fetching range $start:$end"
for {set i $start} {$i <= $end} {incr i} {
set flagdict [dict get $msginfo $chan $i]
dict for {f finfo} $flagdict {
#puts stderr "checking field $f for request $request_tag"
#extract the fields that were added for this request_tag only
if {[dict get $finfo request] eq $request_tag} {
if {[dict exists $fetchresult $f]} {
#merge with existing info for this field
set existing [dict get $fetchresult $f]
dict set existing $i $finfo
dict set fetchresult $f $existing
} else {
dict set fetchresult $f $i $finfo
}
}
}
}
}
@ -2710,8 +2758,9 @@ tcl::namespace::eval punk::imap4 {
@id -id ::punk::imap4::CAPABILITY
@cmd -name punk::imap4::CAPABILITY -help\
"send CAPABILITY command to the server.
The cached results can be checked with
the punk::imap4::has_capability command."
The cached results can be checked with the punk::imap4::has_capability command.
With no arguments has_capability will list all capabilities of the server.
With an argument, it will check for that capability and return a boolean."
@leaders -min 1 -max 1
chan -optional 0
@opts
@ -3148,16 +3197,52 @@ tcl::namespace::eval punk::imap4 {
lappend PUNKARGS [list {
@id -id "::punk::imap4::FOLDERS"
@cmd -name "punk::imap4::FOLDERS" -help\
"List of folders"
@cmd -name "punk::imap4::FOLDERS"\
-summary\
"List folders and flags"\
-help\
{List of folders with their flags.
(Wrapper over IMAP4 protocol's LIST command)
Returns only a 0 (success) or 1 (failure) if -inline is not specified.
The caller can then query the returned information with the folderinfo command.
${[punk::args::helpers::example {
set folders [folderinfo $channelname flags]
}]}
#This will return a list of lists of the form:
{{foldername {{\flag1} {\flag2} ...}} {foldername2 {{\flag1} {\flag2} ...}} ...}
Note the apparent extra bracing around the flags - this is an artifact of how Tcl
represents strings in a list when they have certain characters such as escapes.
${[punk::args::helpers::example {
% lindex $folders 0 1
{\Subscribed} {\HasNoChildren}
% lindex $folder 0 1 0
\Subscribed
}]}
If -inline is specified, this returns a list of 2 element lists of the form:
{foldername {flag1 flag2 ...}}
Note the flags have been converted to lowercase and stripped of any leading backslash
- this is a design choice to make it easier for tcl script users to work with the flags,
If you need the exact IMAP flags, you can query the folderinfo command instead without
using -inline.
}
@leaders -min 1 -max 1
chan
chan -help\
"existing channel for an open IMAP connection"
@opts
-ignorestate -type none
-inline -type none
@values -min 0 -max 2
ref -default ""
mailboxpattern -default "*"
ref -default "" -help\
""
mailboxpattern -default "*" -help\
"The mailbox name pattern with which to query the folders. See IMAP RFC9051 for details on mailbox name patterns and wildcards."
}]
# List of folders
proc FOLDERS {args} {
@ -4263,18 +4348,18 @@ tcl::namespace::eval punk::imap4 {
# get_topic_ functions add more to auto-include in about topics
# -------------------------------------------------------------
proc get_topic_Description {} {
punk::args::lib::tstr [string trim {
punk::args::lib::tstr -indent " " [string trim {
package punk::imap4
A fork from tcllib imap4 module
imap4 - imap client-side tcl implementation of imap protocol
imap4 - imap client-side tcl implementation of IMAP protocol
} \n]
}
proc get_topic_License {} {
return "X11"
return " X11"
}
proc get_topic_Version {} {
return "$::punk::imap4::version"
return " $::punk::imap4::version"
}
proc get_topic_Contributors {} {
set authors {{Salvatore Sanfilippo <antirez@invece.org>} {Nicola Hall <nicci.hall@gmail.com>} {Magnatune <magnatune@users.sourceforge.net>} {Julian Noble <julian@precisium.com.au>}}
@ -4285,10 +4370,31 @@ tcl::namespace::eval punk::imap4 {
if {[string index $contributors end] eq "\n"} {
set contributors [string range $contributors 0 end-1]
}
return $contributors
return [punk::lib::tstr -indent " " $contributors]
}
proc get_topic_API {} {
set B [punk::ansi::a+ bold]
set N [punk::ansi::a+ normal]
punk::args::lib::tstr -indent " " -allowcommands [string trim {
The API is currently in development and subject to change.
${[punk::args::helpers::example {
#For starting point, see output of:
i CONNECT
}]}
Capitalized function names such as FOLDERS and FETCH will generally perform network operations
against the server and require a channel argument.
They will generally return 0 on success and 1 on failure by default.
(many will have a -inline option to return data directly instead of using info)
Lowercase function names such as ${$B}folderinfo${$N} will generally also require a channel
argument but will not perform network operations by default and instead return information
from the most recent successful network operation.
} \n]
}
proc get_topic_notes {} {
punk::args::lib::tstr -return string {
punk::args::lib::tstr -indent " " -return string {
X11 license - is MIT with additional clause regarding use of contributor names.
}
}

34
src/vfs/_vfscommon.vfs/modules/punk/lib-0.1.6.tm

@ -94,19 +94,16 @@ tcl::namespace::eval punk::lib::ensemble {
set routinetail [tcl::namespace::tail $routine]
if {![string match ::* $extension]} {
set extension [uplevel 1 [
list [tcl::namespace::which namespace] current]]::$extension
set extension [uplevel 1 [list [tcl::namespace::which namespace] current]]::$extension
}
if {![tcl::namespace::exists $extension]} {
error [list {no such namespace} $extension]
}
set extension [tcl::namespace::eval $extension [
list [tcl::namespace::which namespace] current]]
set extension [tcl::namespace::eval $extension [list [tcl::namespace::which namespace] current]]
tcl::namespace::eval $extension [
list [tcl::namespace::which namespace] export *]
tcl::namespace::eval $extension [list [tcl::namespace::which namespace] export *]
while 1 {
set renamed ${routinens}::${routinetail}_[clock clicks] ;#clock clicks unlikely to collide when not directly consecutive such as: list [clock clicks] [clock clicks]
@ -140,7 +137,7 @@ tcl::namespace::eval punk::lib::check {
if {"windows" ne $::tcl_platform(platform)} {
set bug 0
} else {
set tmpdir [file tempdir]
set tmpdir [file tempdir] ;#tcl 9+
set testfile [file join $tmpdir "bugtest"]
set fd [open $testfile w]
puts $fd test
@ -4759,14 +4756,21 @@ namespace eval punk::lib {
foreach ln $linelist {
#set is_replay_pure_reset [regexp {\x1b\[0*m$} $replaycodes] ;#only looks at tail code - but if tail is pure reset - any prefix is ignorable
set ansisplits [punk::ansi::ta::split_codes_single $ln] ;#REVIEW - this split accounts for a large portion of the time taken to run this function.
if {[llength $ansisplits]<= 1} {
if {![punk::ansi::ta::detect $ln]} {
#plaintext only - no ansi codes in line
lappend transformed [string cat $replaycodes $ln $RST]
#leave replaycodes as is for next line
set nextreplay $replaycodes
} else {
set replaycodes $nextreplay
continue
}
set ansisplits [punk::ansi::ta::split_codes_single $ln] ;#REVIEW - this split seems to account for a large portion of the time taken to run this function.
#if {[llength $ansisplits]<= 1} {
# #plaintext only - no ansi codes in line
# lappend transformed [string cat $replaycodes $ln $RST]
# #leave replaycodes as is for next line
# set nextreplay $replaycodes
#} else {
set tail $RST
set lastcode [lindex $ansisplits end-1] ;#may or may not be SGR
if {[punk::ansi::codetype::is_sgr_reset $lastcode]} {
@ -4821,7 +4825,7 @@ namespace eval punk::lib {
#set newreplay [join $codestack ""]
set newreplay [punk::ansi::codetype::sgr_merge_list {*}$codestack]
if {$line_has_sgr && $newreplay ne $replaycodes} {
if {$RST ne "" && $line_has_sgr && $newreplay ne $replaycodes} {
#adjust if it doesn't already does a reset at start
if {[punk::ansi::codetype::has_sgr_leadingreset $newreplay]} {
set nextreplay $newreplay
@ -4838,7 +4842,7 @@ namespace eval punk::lib {
} else {
lappend transformed [string cat $replaycodes $ln $tail]
}
}
#}
set replaycodes $nextreplay
}
set linelist $transformed
@ -5505,7 +5509,7 @@ tcl::namespace::eval punk::lib::debug {
namespace eval ::punk::args::register {
#use fully qualified so 8.6 doesn't find existing var in global namespace
lappend ::punk::args::register::NAMESPACES ::punk::lib
lappend ::punk::args::register::NAMESPACES ::punk::lib ::punk::lib::ensemble
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready

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

@ -330,8 +330,11 @@ tcl::namespace::eval punk::nav::fs {
punk::args::define {
@id -id ::punk::nav::fs::d/
@cmd -name punk::nav::fs::d/ -help\
{List directories or directories and files in the current directory or in the
@cmd -name punk::nav::fs::d/\
-summary\
"Navigate and list directories and files"\
-help\
{Navigate/List directories or directories and files in the current directory or in the
targets specified with the fileglob_or_target glob pattern(s).
If a single target is specified without glob characters, and it exists as a directory,

36
src/vfs/_vfscommon.vfs/modules/punk/nav/ns-0.1.0.tm

@ -33,6 +33,40 @@ tcl::namespace::eval punk::nav::ns {
}
namespace path {::punk::ns}
namespace eval argdoc {
lappend PUNKARGS [list {
@id -id ::punk::nav::ns::ns/
@cmd -name punk::nav::ns::ns/\
-summary\
"Navigate and list namespaces and commands"\
-help\
{Navigate/List namespaces or namespaces and commands in the current namespace or in the
targets specified with the nsglob pattern(s).
This function is provided via aliases as n/ n// and n/// with v being inferred from the alias
The n/ n// and n/// forms are more convenient for interactive use.
examples:
n/ - list namespaces below current namespace
n// - list namespaces and commands below current namespace
n/ p* - list namespaces below current matching p*
n// p* - list namespaces below current and commands in current matching p*
}
@values -min 1 -max -1 -type string
v -type string -choices {/ //} -help\
"
/ - list namespaces only
// - list namespaces and commands
/// - list namespaces, commands and commands resolvable via 'namespace path'
"
nsglob -type string -optional true -multiple true -help\
"A glob pattern supporting placeholders * and ?, to filter results.
If multiple patterns are supplied, then a listing for each pattern is returned.
If no patterns are supplied, then all items are listed."
}]
}
proc ns/ {v {ns_or_glob ""} args} {
variable ns_current ;#change active ns of repl by setting ns_current
@ -227,8 +261,6 @@ tcl::namespace::eval punk::nav::ns {
}
}
}

7
src/vfs/_vfscommon.vfs/modules/punk/repl-0.1.2.tm

@ -2948,8 +2948,11 @@ namespace eval repl {
if {[catch {package require thread} errM]} {
puts stdout ">>repl::init initscript lib load fail on package require thread\n$errM"
puts stdout ">>repl::init auto_path : $::auto_path"
puts stdout ">>repl::init tcl::tm::list: [tcl::tm::list]"
if {[catch {package require Thread} errM2]} {
puts stdout ">>repl::init initscript lib load fail on package require Thread\n$errM2"
puts stdout ">>repl::init auto_path : $::auto_path"
puts stdout ">>repl::init tcl::tm::list: [tcl::tm::list]"
}
}
#-----

25
src/vfs/_vfscommon.vfs/modules/punk/winlnk-0.1.1.tm

@ -733,18 +733,23 @@ tcl::namespace::eval punk::winlnk {
set r [binary scan $lenfield su count_chars] ;# su is for unsigned short in little endian order
set string_value ""
if {[Header_Has_LinkFlag $contents "IsUnicode"]} {
#string is UTF-16LE encoded
#string is UTF-16LE encoded - we have this encoding available in tcl 9+ - but not in 8.6
set numbytes [expr {2 * $count_chars}]
set string_bytes [string range $contents $start+2 [expr {$start + 2 + $numbytes - 1}]]
#consider using tcl encoding convertfrom utf-16le instead of manually parsing the UTF-16LE bytes - this would be more robust and handle edge cases better (e.g. surrogate pairs, non-BMP characters, etc.)
set string_value [encoding convertfrom utf-16le $string_bytes]
#for {set i 0} {$i < [string length $string_bytes]} {
# set char_bytes [string range $string_bytes $i [expr {$i + 1}]]
# set r [binary scan $char_bytes su char] ;# s for unsigned short
# append string_value [format %c $char]
# incr i 1 ;# skip the next byte since it's part of the UTF-16LE encoding
#}
#use tcl encoding convertfrom utf-16le when we can instead of manually parsing the UTF-16LE bytes
#- this should be more robust and handle edge cases better (e.g. surrogate pairs, non-BMP characters, etc.)
if {[catch {set string_value [encoding convertfrom utf-16le $string_bytes]} err]} {
#puts stderr "Error converting UTF-16LE string: $err"
#set string_value ""
for {set i 0} {$i < [string length $string_bytes]} {incr i} {
set char_bytes [string range $string_bytes $i $i+1]
set r [binary scan $char_bytes su char] ;# su for unsigned short
append string_value [format %c $char]
incr i 1 ;# skip the next byte since it's part of the UTF-16LE encoding
}
}
} else {
set numbytes $count_chars
set string_bytes [string range $contents $start+2 [expr {$start + 2 + $numbytes - 1}]]

BIN
src/vfs/_vfscommon.vfs/modules/test/punk/args-0.1.5.tm

Binary file not shown.

45
src/vfs/_vfscommon.vfs/modules/textblock-0.1.3.tm

@ -2107,6 +2107,7 @@ tcl::namespace::eval textblock {
set cidx [lindex [tcl::dict::keys $o_columndefs] $index_expression]
set colwidth [my column_width $cidx]
set fwidth [expr {$colwidth + 2}]
set col_blockalign [tcl::dict::get $o_columndefs $cidx -blockalign]
@ -2509,18 +2510,19 @@ tcl::namespace::eval textblock {
set border_ansi $body_ansibase$body_ansiborder
}
set ansibase $body_ansibase$opt_col_ansibase
set r 0
set ftblock [expr {[tcl::dict::get $o_opts_table -frametype] eq "block"}]
set do_show_edge [tcl::dict::get $o_opts_table -show_edge]
foreach c $cells {
#cells in column - each new c is in a different row
set row_ansibase [tcl::dict::get $o_rowdefs $r -ansibase]
set row_bg ""
set row_ansibase [tcl::dict::get $o_rowdefs $r -ansibase]
if {$row_ansibase ne ""} {
set row_bg [punk::ansi::codetype::sgr_merge_singles [list $row_ansibase] -filter_fg 1]
}
set ansibase $body_ansibase$opt_col_ansibase
#todo - joinleft,joinright,joindown based on opts in args
set cell_ansibase ""
@ -2602,7 +2604,7 @@ tcl::namespace::eval textblock {
} else {
set blims $blims_only_headerless
}
if {![tcl::dict::get $o_opts_table -show_edge]} {
if {!$do_show_edge} {
set blims [struct::set difference $blims [tcl::dict::get $::textblock::class::table_edge_parts only$opt_posn] ]
}
} else {
@ -2612,11 +2614,11 @@ tcl::namespace::eval textblock {
} else {
set blims $blims_top_headerless
}
if {![tcl::dict::get $o_opts_table -show_edge]} {
if {!$do_show_edge} {
set blims [struct::set difference $blims [tcl::dict::get $::textblock::class::table_edge_parts top$opt_posn] ]
}
}
set rowframe [textblock::frame -checkargs 0 -type [tcl::dict::get $ftypes body] -width [expr {$colwidth+2}] -blockalign $col_blockalign -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c]
set rowframe [textblock::frame -checkargs 0 -type $ftype_body -width [expr {$colwidth+2}] -blockalign $col_blockalign -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c]
set return_bodywidth [textblock::widthtopline $rowframe] ;#frame lines always same width - just look at top line
append part_body $rowframe \n
} else {
@ -2624,22 +2626,26 @@ tcl::namespace::eval textblock {
set joins [lremove $joins [lsearch $joins down*]]
set bmap $botmap
set blims $blims_bot
if {![tcl::dict::get $o_opts_table -show_edge]} {
if {!$do_show_edge} {
set blims [struct::set difference $blims [tcl::dict::get $::textblock::class::table_edge_parts bottom$opt_posn] ]
}
} else {
set bmap $midmap
set blims $blims_mid ;#will only be reduced from boxlimits if -show_seps was processed above
if {![tcl::dict::get $o_opts_table -show_edge]} {
if {!$do_show_edge} {
set blims [struct::set difference $blims [tcl::dict::get $::textblock::class::table_edge_parts middle$opt_posn] ]
}
}
append part_body [textblock::frame -checkargs 0 -type [tcl::dict::get $ftypes body] -width [expr {$colwidth+2}] -blockalign $col_blockalign -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c]\n
#append part_body [textblock::frame -checkargs 0 -type [tcl::dict::get $ftypes body] -width [expr {$colwidth+2}] -blockalign $col_blockalign -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c]\n
append part_body [textblock::frame -checkargs 0 -type $ftype_body -width [expr {$colwidth+2}] -blockalign $col_blockalign -ansibase $ansibase_final -ansiborder $ansiborder_final -boxlimits $blims -boxmap $bmap -joins $joins $c]\n
}
incr r
}
#return empty (zero content height) row if no rows
if {![llength $cells]} {
set basebg [punk::ansi::codetype::sgr_merge_singles [list $body_ansibase] -filter_fg 1]
set ansiborder_final [punk::ansi::codetype::sgr_merge [list $basebg $body_ansiborder]]
set joins [lremove $joins [lsearch $joins down*]]
#we need to know the width of the column to setup the empty cell properly
#even if no header displayed - we should take account of any defined column widths
@ -2661,7 +2667,9 @@ tcl::namespace::eval textblock {
append part_body [tcl::string::repeat " " $colwidth] \n
set return_bodywidth $colwidth
} else {
set emptyframe [textblock::frame -checkargs 0 -width [expr {$colwidth + 2}] -type [tcl::dict::get $ftypes body] -boxlimits $blims -boxmap $onlymap -joins $joins]
#set emptyframe [textblock::frame -checkargs 0 -width [expr {$colwidth + 2}] -type [tcl::dict::get $ftypes body] -boxlimits $blims -boxmap $onlymap -joins $joins]
# -blockalign probably not relevant for an empty row.
set emptyframe [textblock::frame -checkargs 0 -type $ftype_body -width [expr {$colwidth+2}] -ansibase $body_ansibase -ansiborder $ansiborder_final -boxlimits $blims -boxmap $onlymap -joins $joins]
append part_body $emptyframe \n
set return_bodywidth [textblock::width $emptyframe]
}
@ -5741,7 +5749,10 @@ tcl::namespace::eval textblock {
@id -id ::textblock::join_basic
@cmd -name textblock::join_basic -help\
"Join blocks of text line by line but don't add padding on each line to enforce uniform width.
Already uniform blocks will join faster than textblock::join, and ragged blocks will join in a ragged manner
Already uniform blocks will join faster than textblock::join, and ragged blocks will join in a ragged manner.
see also textblock::join_basic_raw - a thin wrapper around split and join for the common case of joining blocks without any options,
and is intended to avoid the overhead of argument parsing.
"
-ansiresets -type any -default auto
-- -type none -optional 0 -help "end of options marker -- is mandatory because joined blocks may easily conflict with flags"
@ -5787,7 +5798,21 @@ tcl::namespace::eval textblock {
}
return [::join $outlines \n]
}
punk::args::define {
@id -id ::textblock::join_basic_raw
@cmd -name textblock::join_basic_raw -help\
"Join blocks of text line by line but don't add padding on each line to enforce uniform width.
Already uniform blocks will join faster than textblock::join, and ragged blocks will join in a ragged manner.
This version is a thin wrapper around split and join for the common case of joining blocks without any options,
and is intended to avoid the overhead of argument parsing.
"
@values
blocks -type any -multiple 1
}
proc ::textblock::join_basic_raw {args} {
#do not use any argument parsing libs - this is intended as a thin wrapper around split and join for the common case of joining blocks without any options,
#and we want to avoid the overhead of argument parsing.
#no options. -*, -- are legimate blocks
set blocklists [lrepeat [llength $args] ""]
set blocklengths [lrepeat [expr {[llength $args]+1}] 0] ;#add 1 to ensure never empty - used only for rowcount max calc

Loading…
Cancel
Save