Browse Source

punk::args and ansi fixes, another vfs

master
Julian Noble 2 months ago
parent
commit
ae83749222
  1. 568
      src/bootsupport/modules/argparsingtest-0.1.0.tm
  2. 8
      src/bootsupport/modules/commandstack-0.3.tm
  3. 2
      src/bootsupport/modules/funcl-0.1.tm
  4. 2
      src/bootsupport/modules/include_modules.config
  5. 43
      src/bootsupport/modules/overtype-1.6.5.tm
  6. 27
      src/bootsupport/modules/punk-0.1.tm
  7. 5
      src/bootsupport/modules/punk/aliascore-0.1.0.tm
  8. 600
      src/bootsupport/modules/punk/ansi-0.1.1.tm
  9. 1378
      src/bootsupport/modules/punk/args-0.1.0.tm
  10. 36
      src/bootsupport/modules/punk/char-0.1.0.tm
  11. 880
      src/bootsupport/modules/punk/console-0.1.1.tm
  12. 116
      src/bootsupport/modules/punk/lib-0.1.1.tm
  13. 21
      src/bootsupport/modules/punk/mix/cli-0.3.1.tm
  14. 36
      src/bootsupport/modules/punk/mix/commandset/layout-0.1.0.tm
  15. 13
      src/bootsupport/modules/punk/mix/commandset/loadedlib-0.1.0.tm
  16. 21
      src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm
  17. 72
      src/bootsupport/modules/punk/mix/commandset/project-0.1.0.tm
  18. 3
      src/bootsupport/modules/punk/nav/fs-0.1.0.tm
  19. 178
      src/bootsupport/modules/punk/ns-0.1.0.tm
  20. 149
      src/bootsupport/modules/punk/packagepreference-0.1.0.tm
  21. 29
      src/bootsupport/modules/punk/path-0.1.0.tm
  22. 853
      src/bootsupport/modules/punk/pipe-1.0.tm
  23. 22
      src/bootsupport/modules/punk/repl/codethread-0.1.1.tm
  24. 22
      src/bootsupport/modules/punk/repo-0.1.1.tm
  25. 34
      src/bootsupport/modules/punk/zip-0.1.1.tm
  26. 111
      src/bootsupport/modules/punkcheck-0.1.0.tm
  27. 2
      src/bootsupport/modules/punkcheck/cli-0.1.0.tm
  28. 17
      src/bootsupport/modules/shellfilter-0.1.9.tm
  29. 131
      src/bootsupport/modules/textblock-0.1.3.tm
  30. 9
      src/bootsupport/modules/tomlish-1.1.1.tm
  31. 1
      src/bootsupport/modules_tcl8/include_modules.config
  32. BIN
      src/bootsupport/modules_tcl8/win32_x86_64_tcl8-2.8.9.tm
  33. 116
      src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm
  34. 97
      src/make.tcl
  35. 4
      src/modules/argparsingtest-999999.0a1.0.tm
  36. 2
      src/modules/funcl-0.1.tm
  37. 11
      src/modules/patternpunk-1.1.tm
  38. 767
      src/modules/punk-0.1.tm
  39. 5
      src/modules/punk/aliascore-999999.0a1.0.tm
  40. 600
      src/modules/punk/ansi-999999.0a1.0.tm
  41. 1378
      src/modules/punk/args-999999.0a1.0.tm
  42. 516
      src/modules/punk/args/tclcore-999999.0a1.0.tm
  43. 81
      src/modules/punk/basictelnet-999999.0a1.0.tm
  44. 6
      src/modules/punk/blockletter-999999.0a1.0.tm
  45. 36
      src/modules/punk/char-999999.0a1.0.tm
  46. 880
      src/modules/punk/console-999999.0a1.0.tm
  47. 116
      src/modules/punk/lib-999999.0a1.0.tm
  48. 21
      src/modules/punk/mix/cli-999999.0a1.0.tm
  49. 36
      src/modules/punk/mix/commandset/layout-999999.0a1.0.tm
  50. 13
      src/modules/punk/mix/commandset/loadedlib-999999.0a1.0.tm
  51. 21
      src/modules/punk/mix/commandset/module-999999.0a1.0.tm
  52. 55
      src/modules/punk/mix/commandset/project-999999.0a1.0.tm
  53. 3
      src/modules/punk/nav/fs-999999.0a1.0.tm
  54. 178
      src/modules/punk/ns-999999.0a1.0.tm
  55. 149
      src/modules/punk/packagepreference-999999.0a1.0.tm
  56. 29
      src/modules/punk/path-999999.0a1.0.tm
  57. 279
      src/modules/punk/pcon-999999.0a1.0.tm
  58. 3
      src/modules/punk/pcon-buildversion.txt
  59. 853
      src/modules/punk/pipe-999999.0a1.0.tm
  60. 3
      src/modules/punk/pipe-buildversion.txt
  61. 505
      src/modules/punk/repl-0.1.tm
  62. 22
      src/modules/punk/repl/codethread-999999.0a1.0.tm
  63. 22
      src/modules/punk/repo-999999.0a1.0.tm
  64. 41
      src/modules/punk/safe-999999.0a1.0.tm
  65. 6
      src/modules/punk/winrun-999999.0a1.0.tm
  66. 62
      src/modules/punk/winshell-999999.0a1.0.tm
  67. 34
      src/modules/punk/zip-999999.0a1.0.tm
  68. 111
      src/modules/punkcheck-0.1.0.tm
  69. 2
      src/modules/punkcheck/cli-999999.0a1.0.tm
  70. 17
      src/modules/shellfilter-0.1.9.tm
  71. 4
      src/modules/shellthread-1.6.1.tm
  72. 131
      src/modules/textblock-999999.0a1.0.tm
  73. 97
      src/project_layouts/custom/_project/punk.basic/src/make.tcl
  74. 568
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/argparsingtest-0.1.0.tm
  75. 8
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/commandstack-0.3.tm
  76. 2
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/funcl-0.1.tm
  77. 2
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/include_modules.config
  78. 43
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/overtype-1.6.5.tm
  79. 27
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk-0.1.tm
  80. 5
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/aliascore-0.1.0.tm
  81. 600
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/ansi-0.1.1.tm
  82. 1378
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/args-0.1.0.tm
  83. 36
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/char-0.1.0.tm
  84. 880
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/console-0.1.1.tm
  85. 116
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/lib-0.1.1.tm
  86. 21
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/cli-0.3.1.tm
  87. 36
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/layout-0.1.0.tm
  88. 13
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/loadedlib-0.1.0.tm
  89. 21
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/module-0.1.0.tm
  90. 72
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/mix/commandset/project-0.1.0.tm
  91. 3
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/nav/fs-0.1.0.tm
  92. 178
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/ns-0.1.0.tm
  93. 149
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/packagepreference-0.1.0.tm
  94. 29
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/path-0.1.0.tm
  95. 853
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/pipe-1.0.tm
  96. 22
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repl/codethread-0.1.1.tm
  97. 22
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repo-0.1.1.tm
  98. 34
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/zip-0.1.1.tm
  99. 111
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punkcheck-0.1.0.tm
  100. 2
      src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punkcheck/cli-0.1.0.tm
  101. Some files were not shown because too many files have changed in this diff Show More

568
src/bootsupport/modules/argparsingtest-0.1.0.tm

@ -0,0 +1,568 @@
# -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: punkshell/src/decktemplates/vendor/punk/modules/template_module-0.0.2.tm
#
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# (C) Julian Noble 2024
#
# @@ Meta Begin
# Application argparsingtest 0.1.0
# Meta platform tcl
# Meta license MIT
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[manpage_begin punkshell_module_argparsingtest 0 0.1.0]
#[copyright "2024"]
#[titledesc {Module API}] [comment {-- Name section and table of contents description --}]
#[moddesc {-}] [comment {-- Description at end of page heading --}]
#[require argparsingtest]
#[keywords module]
#[description]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Overview]
#[para] overview of argparsingtest
#[subsection Concepts]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection dependencies]
#[para] packages used by argparsingtest
#[list_begin itemized]
package require Tcl 8.6-
package require punk::args
package require struct::set
#*** !doctools
#[item] [package {Tcl 8.6}]
#[item] [package {punk::args}]
# #package require frobz
# #*** !doctools
# #[item] [package {frobz}]
#*** !doctools
#[list_end]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section API]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# oo::class namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval argparsingtest::class {
#*** !doctools
#[subsection {Namespace argparsingtest::class}]
#[para] class definitions
if {[info commands [namespace current]::interface_sample1] eq ""} {
#*** !doctools
#[list_begin enumerated]
# oo::class create interface_sample1 {
# #*** !doctools
# #[enum] CLASS [class interface_sample1]
# #[list_begin definitions]
# method test {arg1} {
# #*** !doctools
# #[call class::interface_sample1 [method test] [arg arg1]]
# #[para] test method
# puts "test: $arg1"
# }
# #*** !doctools
# #[list_end] [comment {-- end definitions interface_sample1}]
# }
#*** !doctools
#[list_end] [comment {--- end class enumeration ---}]
}
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Base namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval argparsingtest {
namespace export {[a-z]*} ;# Convention: export all lowercase
#variable xyz
#*** !doctools
#[subsection {Namespace argparsingtest}]
#[para] Core API functions for argparsingtest
#[list_begin definitions]
proc test1_ni {args} {
set defaults [dict create\
-return string\
-frametype \uFFEF\
-show_edge \uFFEF\
-show_seps \uFFEF\
-x ""\
-y b\
-z c\
-1 1\
-2 2\
-3 3\
]
foreach {k v} $args {
if {$k ni [dict keys $defaults]} {
error "unrecognised option '$k'. Known options [dict keys $defaults]"
}
}
set opts [dict merge $defaults $args]
}
proc test1_switchmerge {args} {
set defaults [dict create\
-return string\
-frametype \uFFEF\
-show_edge \uFFEF\
-show_seps \uFFEF\
-x ""\
-y b\
-z c\
-1 1\
-2 2\
-3 3\
]
foreach {k v} $args {
switch -- $k {
-return - -show_edge - -show_seps - -frametype - -x - -y - -z - -1 - -2 - -3 {}
default {
error "unrecognised option '$k'. Known options [dict keys $defaults]"
}
}
}
set opts [dict merge $defaults $args]
}
#if we need to loop to test arg validity anyway - then dict set as we go is slightly faster than a dict merge at the end
proc test1_switch {args} {
set opts [dict create\
-return string\
-frametype \uFFEF\
-show_edge \uFFEF\
-show_seps \uFFEF\
-x ""\
-y b\
-z c\
-1 1\
-2 2\
-3 3\
]
foreach {k v} $args {
switch -- $k {
-return - -show_edge - -show_seps - -frametype - -x - -y - -z - -1 - -2 - -3 {
dict set opts $k $v
}
default {
error "unrecognised option '$k'. Known options [dict keys $opts]"
}
}
}
return $opts
}
variable switchopts
set switchopts [dict create\
-return string\
-frametype \uFFEF\
-show_edge \uFFEF\
-show_seps \uFFEF\
-x ""\
-y b\
-z c\
-1 1\
-2 2\
-3 3\
]
#slightly slower than just creating the dict within the proc
proc test1_switch_nsvar {args} {
variable switchopts
set opts $switchopts
foreach {k v} $args {
switch -- $k {
-return - -show_edge - -show_seps - -frametype - -x - -y - -z - -1 - -2 - -3 {
dict set opts $k $v
}
default {
error "unrecognised option '$k'. Known options [dict keys $opts]"
}
}
}
return $opts
}
proc test1_switch2 {args} {
set opts [dict create\
-return string\
-frametype \uFFEF\
-show_edge \uFFEF\
-show_seps \uFFEF\
-x ""\
-y b\
-z c\
-1 1\
-2 2\
-3 3\
]
set switches [lmap v [dict keys $opts] {list $v -}]
set switches [concat {*}$switches]
set switches [lrange $switches 0 end-1]
foreach {k v} $args {
switch -- $k\
{*}$switches {
dict set opts $k $v
}\
default {
error "unrecognised option '$k'. Known options [dict keys $opts]"
}
}
return $opts
}
proc test1_prefix {args} {
set opts [dict create\
-return string\
-frametype \uFFEF\
-show_edge \uFFEF\
-show_seps \uFFEF\
-x ""\
-y b\
-z c\
-1 1\
-2 2\
-3 3\
]
foreach {k v} $args {
dict set opts [tcl::prefix::match -message "test1_prefix option $k" {-return -frametype -show_edge -show_seps -x -y -z -1 -2 -3} $k] $v
}
return $opts
}
proc test1_prefix2 {args} {
set opts [dict create\
-return string\
-frametype \uFFEF\
-show_edge \uFFEF\
-show_seps \uFFEF\
-x ""\
-y b\
-z c\
-1 1\
-2 2\
-3 3\
]
if {[llength $args]} {
set knownflags [dict keys $opts]
}
foreach {k v} $args {
dict set opts [tcl::prefix::match -message "test1_prefix2 option $k" $knownflags $k] $v
}
return $opts
}
#punk::args is slower than argp - but comparable, and argp doesn't support solo flags
proc test1_punkargs {args} {
set argd [punk::args::parse $args withdef {
@id -id ::argparsingtest::test1_punkargs
@cmd -name argtest4 -help "test of punk::args::get_dict comparative performance"
@opts -anyopts 0
-return -default string -type string
-frametype -default \uFFEF -type string
-show_edge -default \uFFEF -type string
-show_seps -default \uFFEF -type string
-join -type none -multiple 1
-x -default "" -type string
-y -default b -type string
-z -default c -type string
-1 -default 1 -type boolean
-2 -default 2 -type integer
-3 -default 3 -type integer
@values
}]
return [tcl::dict::get $argd opts]
}
punk::args::define {
@id -id ::test1_punkargs_by_id
@cmd -name argtest4 -help "test of punk::args::get_dict comparative performance"
@opts -anyopts 0
-return -default string -type string
-frametype -default \uFFEF -type string
-show_edge -default \uFFEF -type string
-show_seps -default \uFFEF -type string
-join -type none -multiple 1
-x -default "" -type string
-y -default b -type string
-z -default c -type string
-1 -default 1 -type boolean
-2 -default 2 -type integer
-3 -default 3 -type integer
@values
}
proc test1_punkargs_by_id {args} {
set argd [punk::args::get_by_id ::test1_punkargs_by_id $args]
return [tcl::dict::get $argd opts]
}
punk::args::define {
@id -id ::argparsingtest::test1_punkargs2
@cmd -name argtest4 -help "test of punk::args::get_dict comparative performance"
@opts -anyopts 0
-return -default string -type string
-frametype -default \uFFEF -type string
-show_edge -default \uFFEF -type string
-show_seps -default \uFFEF -type string
-join -type none -multiple 1
-x -default "" -type string
-y -default b -type string
-z -default c -type string
-1 -default 1 -type boolean
-2 -default 2 -type integer
-3 -default 3 -type integer
@values
}
proc test1_punkargs2 {args} {
set argd [punk::args::get_by_id ::argparsingtest::test1_punkargs2 $args]
return [tcl::dict::get $argd opts]
}
proc test1_punkargs_validate_ansistripped {args} {
set argd [punk::args::get_dict {
@id -id ::argparsingtest::test1_punkargs_validate_ansistripped
@cmd -name argtest4 -help "test of punk::args::get_dict comparative performance"
@opts -anyopts 0
-return -default string -type string -choices {string object} -help "return type"
-frametype -default \uFFEF -type string
-show_edge -default \uFFEF -type string
-show_seps -default \uFFEF -type string
-join -type none -multiple 1
-x -default "" -type string
-y -default b -type string
-z -default c -type string
-1 -default 1 -type boolean -validate_ansistripped true
-2 -default 2 -type integer -validate_ansistripped true
-3 -default 3 -type integer -validate_ansistripped true
@values
} $args]
return [tcl::dict::get $argd opts]
}
package require opt
variable optlist
tcl::OptProc test1_opt {
{-return string "return type"}
{-frametype \uFFEF "type of frame"}
{-show_edge \uFFEF "show table outer borders"}
{-show_seps \uFFEF "show separators"}
{-join "solo option"}
{-x "" "x val"}
{-y b "y val"}
{-z c "z val"}
{-1 1 "1val"}
{-2 -int 2 "2val"}
{-3 -int 3 "3val"}
} {
set opts [dict create]
foreach v [info locals] {
dict set opts $v [set $v]
}
return $opts
}
package require cmdline
#cmdline::getoptions is much faster than typedGetoptions
proc test1_cmdline_untyped {args} {
set cmdlineopts_untyped {
{return.arg "string" "return val"}
{frametype.arg \uFFEF "frame type"}
{show_edge.arg \uFFEF "show table borders"}
{show_seps.arg \uFFEF "show table seps"}
{join "join the things"}
{x.arg "" "arg x"}
{y.arg b "arg y"}
{z.arg c "arg z"}
{1.arg 1 "arg 1"}
{2.arg 2 "arg 2"}
{3.arg 3 "arg 3"}
}
set usage "usage etc"
return [::cmdline::getoptions args $cmdlineopts_untyped $usage]
}
proc test1_cmdline_typed {args} {
set cmdlineopts_typed {
{return.arg "string" "return val"}
{frametype.arg \uFFEF "frame type"}
{show_edge.arg \uFFEF "show table borders"}
{show_seps.arg \uFFEF "show table seps"}
{join "join the things"}
{x.arg "" "arg x"}
{y.arg b "arg y"}
{z.arg c "arg z"}
{1.boolean 1 "arg 1"}
{2.integer 2 "arg 2"}
{3.integer 3 "arg 3"}
}
set usage "usage etc"
return [::cmdline::typedGetoptions args $cmdlineopts_typed $usage]
}
catch {
package require argp
argp::registerArgs test1_argp {
{ -return string "string" }
{ -frametype string \uFFEF }
{ -show_edge string \uFFEF }
{ -show_seps string \uFFEF }
{ -x string "" }
{ -y string b }
{ -z string c }
{ -1 boolean 1 }
{ -2 integer 2 }
{ -3 integer 3 }
}
}
proc test1_argp {args} {
argp::parseArgs opts
return [array get opts]
}
package require tepam
tepam::procedure {test1_tepam} {
-args {
{-return -type string -default string}
{-frametype -type string -default \uFFEF}
{-show_edge -type string -default \uFFEF}
{-show_seps -type string -default \uFFEF}
{-join -type none -multiple}
{-x -type string -default ""}
{-y -type string -default b}
{-z -type string -default c}
{-1 -type boolean -default 1}
{-2 -type integer -default 2}
{-3 -type integer -default 3}
}
} {
return [dict create return $return frametype $frametype show_edge $show_edge show_seps $show_seps x $x y $y z $z 1 $1 2 $2 3 $3 join $join]
}
#multiline values use first line of each record to determine amount of indent to trim
proc test_multiline {args} {
set t3 [textblock::frame t3]
set argd [punk::args::get_dict [subst {
-template1 -default {
******
* t1 *
******
}
-template2 -default { ------
******
* t2 *
******}
-template3 -default {$t3}
#substituted or literal values with newlines - no autoindent applied - caller will have to pad appropriately
-template3b -default {
$t3
-----------------
$t3
abc\ndef
}
-template4 -default "******
* t4 *
******"
-template5 -default "
"
-flag -default 0 -type boolean
}] $args]
return $argd
}
#proc sample1 {p1 n args} {
# #*** !doctools
# #[call [fun sample1] [arg p1] [arg n] [opt {option value...}]]
# #[para]Description of sample1
# #[para] Arguments:
# # [list_begin arguments]
# # [arg_def tring p1] A description of string argument p1.
# # [arg_def integer n] A description of integer argument n.
# # [list_end]
# return "ok"
#}
#*** !doctools
#[list_end] [comment {--- end definitions namespace argparsingtest ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Secondary API namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval argparsingtest::lib {
namespace export {[a-z]*} ;# Convention: export all lowercase
namespace path [namespace parent]
#*** !doctools
#[subsection {Namespace argparsingtest::lib}]
#[para] Secondary functions that are part of the API
#[list_begin definitions]
#proc utility1 {p1 args} {
# #*** !doctools
# #[call lib::[fun utility1] [arg p1] [opt {?option value...?}]]
# #[para]Description of utility1
# return 1
#}
#*** !doctools
#[list_end] [comment {--- end definitions namespace argparsingtest::lib ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Internal]
namespace eval argparsingtest::system {
#*** !doctools
#[subsection {Namespace argparsingtest::system}]
#[para] Internal functions that are not part of the API
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide argparsingtest [namespace eval argparsingtest {
variable pkg argparsingtest
variable version
set version 0.1.0
}]
return
#*** !doctools
#[manpage_end]

8
src/bootsupport/modules/commandstack-0.3.tm

@ -211,7 +211,7 @@ namespace eval commandstack {
set new_code [string trim $procbody]
if {$current_code eq $new_code} {
puts stderr "(commandstack::rename_command) WARNING - renamer '$renamer' has already renamed the '$command' command with same procbody - Aborting rename."
puts stderr [show_stack $command]
puts stderr [::commandstack::show_stack $command]
} else {
puts stderr "(commandstack::rename_command) WARNING - renamer '$renamer' has already renamed the '$command' command - but appears to be with new code - proceeding."
puts stdout "----------"
@ -236,8 +236,7 @@ namespace eval commandstack {
set next_target ::commandstack::renamed_commands::${mungedcommand}_${munged_next_implementor}-$mungedrenamer-$tokenid
set do_rename 1
} elseif {$next_implementor in {unspecified undetermined}} {
#review - probably don't need a warning anyway
puts stderr "(commandstack::rename_command) WARNING - Something may have renamed the '$command' command. Attempting to cooperate.(untested)"
#could be a standard tcl proc, or from application or package
set next_target ::commandstack::renamed_commands::${mungedcommand}_${munged_next_implementor}-$mungedrenamer-$tokenid
set do_rename 1
} else {
@ -380,7 +379,8 @@ namespace eval commandstack {
#if caller is attempting exact match - use the calling context to resolve in case they didn't supply namespace
set commandname_glob [uplevel 1 [list namespace which $commandname_glob]]
}
if {[package provide punk::lib] ne ""} {
if {[package provide punk::lib] ne "" && [package provide punk] ne ""} {
#punk pipeline also needed for patterns
return [punk::lib::pdict -channel none all_stacks $commandname_glob/@*/@*.@*]
} else {
set result ""

2
src/bootsupport/modules/funcl-0.1.tm

@ -9,7 +9,7 @@ package provide funcl [namespace eval funcl {
namespace eval funcl {
#from punk
#from punk::pipe
proc arg_is_script_shaped {arg} {
if {[string first " " $arg] >= 0} {
return 1

2
src/bootsupport/modules/include_modules.config

@ -18,7 +18,6 @@ set bootsupport_modules [list\
src/vendormodules md5\
src/vendormodules metaface\
src/vendormodules modpod\
src/vendormodules oolib\
src/vendormodules overtype\
src/vendormodules pattern\
src/vendormodules patterncmd\
@ -40,6 +39,7 @@ set bootsupport_modules [list\
modules funcl\
modules natsort\
modules punk\
modules punk::pipe\
modules punkapp\
modules punkcheck\
modules punkcheck::cli\

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

@ -216,7 +216,9 @@ tcl::namespace::eval overtype {
}
set optargs [lrange $args 0 end-2]
if {[llength $optargs] % 2 == 0} {
lassign [lrange $args end-1 end] underblock overblock
set overblock [lindex $args end]
set underblock [lindex $args end-1]
#lassign [lrange $args end-1 end] underblock overblock
set argsflags [lrange $args 0 end-2]
} else {
set optargs [lrange $args 0 end-1]
@ -1810,8 +1812,10 @@ tcl::namespace::eval overtype {
if {[llength $args] < 2} {
error {usage: ?-info 0|1? ?-startcolumn <int>? ?-cursor_column <int>? ?-cursor_row <int>|""? ?-transparent [0|1|<regexp>]? ?-expand_right [1|0]? undertext overtext}
}
lassign [lrange $args end-1 end] under over
if {[string first \n $under] >= 0} {
set under [lindex $args end-1]
set over [lindex $args end]
#lassign [lrange $args end-1 end] under over
if {[string last \n $under] >= 0} {
error "overtype::renderline not allowed to contain newlines in undertext"
}
#if {[string first \n $over] >=0 || [string first \n $under] >= 0} {
@ -2920,6 +2924,7 @@ tcl::namespace::eval overtype {
set leadernorm [tcl::string::range [tcl::string::map [list\
\x1b\[< 1006\
\x1b\[ 7CSI\
\x1bY 7MAP\
\x1bP 7DCS\
\x90 8DCS\
\x9b 8CSI\
@ -2948,6 +2953,10 @@ tcl::namespace::eval overtype {
#8-bit Device Control String
set codenorm [tcl::string::cat $leadernorm [tcl::string::range $code 1 end]]
}
7MAP {
#map to another type of code to share implementation branch
set codenorm $leadernorm[tcl::string::range $code 1 end]
}
7ESC {
#set codenorm [tcl::string::cat $leadernorm [tcl::string::range $code 1 end]]
set codenorm $leadernorm[tcl::string::range $code 1 end]
@ -2964,6 +2973,30 @@ tcl::namespace::eval overtype {
}
}
switch -- $leadernorm {
7MAP {
switch -- [lindex $codenorm 4] {
Y {
#vt52 movement. we expect 2 chars representing position (limited range)
set params [tcl::string::range $codenorm 5 end]
if {[tcl::string::length $params] != 2} {
#shouldn't really get here or need this branch if ansi splitting was done correctly
puts stderr "overtype::renderline ESC Y recognised as vt52 move, but incorrect parameters length ([string length $params] vs expected 2) [ansistring VIEW -lf 1 -vt 1 -nul 1 $code] not implemented codenorm:[ansistring VIEW -lf 1 -vt 1 -nul 1 $codenorm]"
}
set line [tcl::string::index $params 5]
set column [tcl::string::index $params 1]
set r [expr {[scan $line %c] -31}]
set c [expr {[scan $column %c] -31}]
#MAP to:
#CSI n;m H - CUP - Cursor Position
set leadernorm 7CSI
set codenorm "$leadernorm${r}\;${c}H"
}
}
}
}
#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 {
@ -2983,6 +3016,7 @@ tcl::namespace::eval overtype {
set param [tcl::string::range $codenorm 4 end-1]
#puts stdout "--> CSI [tcl::string::index $leadernorm 0] bit param:$param"
set code_end [tcl::string::index $codenorm end] ;#used for e.g h|l set/unset mode
switch -exact -- $code_end {
A {
#Row move - up
@ -3875,6 +3909,7 @@ tcl::namespace::eval overtype {
7ESC {
#
#re_other_single {\x1b(D|M|E)$}
#also vt52 Y..
#also PM \x1b^...(ST)
switch -- [tcl::string::index $codenorm 4] {
c {
@ -4586,6 +4621,8 @@ tcl::namespace::eval overtype::priv {
set o [lreplace $o $i $i]
set ustacks [lreplace $ustacks $i $i]
set gxstacks [lreplace $gxstacks $i $i]
} elseif {$i == 0 || $i == $nxt} {
#nothing to do
} else {
puts stderr "render_delchar - attempt to delchar at index $i >= number of outcols $nxt - shouldn't happen"
}

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

@ -20,6 +20,21 @@ namespace eval punk {
variable cmdexedir
set cmdexedir ""
proc sync_package_paths_script {} {
#the tcl::tm namespace doesn't exist until one of the tcl::tm commands
#is run. (they are loaded via ::auto_index triggering load of tm.tcl)
#we call tcl::tm::list to trigger the initial set of tm paths before
#we can override it, otherwise our changes will be lost
#REVIEW - won't work on safebase interp where paths are mapped to {$p(:x:)} etc
return "\
apply {{ap tmlist} {
set ::auto_path \$ap
tcl::tm::list
set ::tcl::tm::paths \$tmlist
}} {$::auto_path} {[tcl::tm::list]}
"
}
proc rehash {{refresh 0}} {
global auto_execs
if {!$refresh} {
@ -217,7 +232,7 @@ namespace eval punk {
[file exists [set testapp [file join $::env(LOCALAPPDATA) "Microsoft" "WindowsApps" "winget.exe"]]])} {
#should be unlikely to get here - unless LOCALAPPDATA missing
set windowsappdir [file dirname [lindex [::punk::auto_execok_windows winget.exe] 0]]
puts stderr "(resolved winget by search)"
catch {puts stderr "(resolved winget by search)"}
} else {
set windowsappdir [file dirname $testapp]
}
@ -359,7 +374,7 @@ if {![llength [info commands ::ansistring]]} {
}
#require aliascore after punk::lib & punk::ansi are loaded
package require punk::aliascore ;#mostly punk::lib aliases
punk::aliascore::init
punk::aliascore::init -force 1
package require punk::repl::codethread
package require punk::config
@ -383,9 +398,11 @@ namespace eval punk {
package require punk::assertion
if {[catch {namespace import ::punk::assertion::assert} errM]} {
catch {
puts stderr "punk error importing punk::assertion::assert\n$errM"
puts stderr "punk::a* commands:[info commands ::punk::a*]"
}
}
punk::assertion::active on
# -- --- ---
@ -393,7 +410,7 @@ namespace eval punk {
if {[catch {
package require pattern
} errpkg]} {
puts stderr "Failed to load package pattern error: $errpkg"
catch {puts stderr "Failed to load package pattern error: $errpkg"}
}
package require shellfilter
package require punkapp
@ -524,7 +541,7 @@ namespace eval punk {
set loader [zzzload::pkg_wait twapi]
} errM]} {
if {$loader in [list failed loading]} {
puts stderr "Unexpected problem during thread-load of pkg twapi - zzload::pkg_wait returned $loader"
catch {puts stderr "Unexpected problem during thread-load of pkg twapi - zzload::pkg_wait returned $loader"}
}
} else {
package require twapi
@ -1061,7 +1078,7 @@ namespace eval punk {
proc destructure {selector data} {
# replaced by proc generating destructure_func -
puts stderr "punk::destructure .d. selector:'$selector'"
catch {puts stderr "punk::destructure .d. selector:'$selector'"}
set selector [string trim $selector /]
upvar v_dict_idx v_dict_idx ;#positional tracker for /@@ - dict position

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

@ -105,6 +105,7 @@ tcl::namespace::eval punk::aliascore {
#functions must be in export list of their source namespace
set aliases [tcl::dict::create\
val ::punk::pipe::val\
aliases ::punk::lib::aliases\
alias ::punk::lib::alias\
tstr ::punk::lib::tstr\
@ -123,12 +124,12 @@ tcl::namespace::eval punk::aliascore {
colour ::punk::console::colour\
ansi ::punk::console::ansi\
color ::punk::console::colour\
a? ::punk::console::code_a?\
A? {::punk::console::code_a? forcecolor}\
a+ ::punk::console::code_a+\
A+ {::punk::console::code_a+ forcecolour}\
a ::punk::console::code_a\
A {::punk::console::code_a forcecolour}\
a? ::punk::console::code_a?\
A? {::punk::console::code_a? forcecolor}\
smcup ::punk::console::enable_alt_screen\
rmcup ::punk::console::disable_alt_screen\
]

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

@ -584,25 +584,38 @@ tcl::namespace::eval punk::ansi {
set base $CWD
}
}
if {[info commands file] eq ""} {
#probably a safe interp
return "UNAVAILABLE"
}
return [file join $base src/testansi]
}
lappend PUNKARGS [list -dynamic 1 {
lappend PUNKARGS [list {
@dynamic
@id -id ::punk::ansi::example
@cmd -name punk::ansi::example -help "Display .ans image files in a grid that will fit in console
"
-colwidth -default 82 -help "Width of each column - default of 82 will fit a standard 80wide ansi image (when framed)
-colwidth -default 82 -help\
"Width of each column - default of 82 will fit a standard 80wide ansi image
(when framed)
You can specify a narrower width to truncate images on the right side"
-folder -default "${[punk::ansi::Get_ansifolder]}" -help "Base folder for files if relative paths are used.
Defaults to <projectbase>/src/testansi - where projectbase is determined from current directory.
-folder -default "${[punk::ansi::Get_ansifolder]}" -help\
"Base folder for files if relative paths are used.
Defaults to <projectbase>/src/testansi - where projectbase is determined
from the current directory.
"
@values -min 0 -max -1
files -default {belinda.ans bot.ans flower.ans fish.ans} -multiple true -help "List of filenames - leave empty to display 4 defaults"
files -default {belinda.ans bot.ans flower.ans fish.ans} -multiple true -help\
"List of filenames - leave empty to display 4 defaults"
} ""]
proc example {args} {
set argd [punk::args::get_by_id ::punk::ansi::example $args]
set colwidth [dict get $argd opts -colwidth]
if {[info commands file] eq ""} {
error "file command unavailable - punk::ansi::example cannot be shown"
}
set ansifolder [file normalize [dict get $argd opts -folder]]
set fnames [dict get $argd values files]
@ -617,6 +630,16 @@ tcl::namespace::eval punk::ansi {
puts stderr "Ensure ansi test files exist: $fnames"
#error "punk::ansi::example Cannot find example files"
}
set termsize [punk::console:::get_size]
set termcols [dict get $termsize columns]
set margin 4 ;#review
set freewidth [expr {$termcols-$margin}]
if {$freewidth < $colwidth} {
puts stderr "[a+ red bold]punk::ansi::example freewidth: $freewidth < colwidth: $colwidth TRUNCATING IMAGES[a]"
set colwidth $freewidth
}
set per_row [expr {$freewidth / $colwidth}]
set missingbase [a+ yellow][textblock::block [expr {$colwidth-2}] 23 ?][a] ;#assuming standard frame - subtract 2 for left/right borders
set pics [list]
foreach f $fnames {
@ -636,10 +659,6 @@ tcl::namespace::eval punk::ansi {
}
}
set termsize [punk::console:::get_size]
set margin 4
set freewidth [expr {[tcl::dict::get $termsize columns]-$margin}]
set per_row [expr {$freewidth / $colwidth}]
set rowlist [list] ;# { {<img> <img>} {<img> <img>} }
set heightlist [list] ;# { {<h> <h> } {<h> <h> } }
@ -737,22 +756,23 @@ tcl::namespace::eval punk::ansi {
#review - can terminals handle SGR codes within a PM?
#Wezterm will hide PM,SOS,APC - but not any part following an SGR code - i.e it seems to terminate hiding before the ST (apparently at the )
proc controlstring_PM {text} {
return "\x1b^${text}\033\\"
#dquotes with trailing \\ in string will confuse silly editors
return \x1b^${text}\033\\
}
proc controlstring_PM8 {text} {
return "\x9e${text}\x9c"
return \x9e${text}\x9c
}
proc controlstring_SOS {text} {
return "\x1bX${text}\033\\"
return \x1bX${text}\033\\
}
proc controlstring_SOS8 {text} {
return "\x98${text}\x9c"
return \x98${text}\x9c
}
proc controlstring_APC {text} {
return "\x1b_${text}\033\\"
return \x1b_${text}\033\\
}
proc controlstring_APC8 {text} {
return "\x9f${text}\x9c"
return \x9f${text}\x9c
}
#there is also the SGR hide code (8) which has intermittent terminal support
#This doesn't change the output length - so support is tricky to detec. (terminal checksum report?)
@ -843,10 +863,79 @@ tcl::namespace::eval punk::ansi {
return $out
}
#Wrap text in ansi codes to switch to DEC alternate graphics character set.
#todo vt52 versions
proc g0 {text} {
return \x1b(0$text\x1b(B
}
variable altg_map [dict create\
hl q\
vl x\
tlc l\
trc k\
blc m\
ltj t\
rtj u\
ttj w\
btj v\
rtj u\
fwj n\
]
proc altg_map {names} {
variable altg_map
set result [list]
foreach nm $names {
if {[dict exists $altg_map $nm]} {
lappend result [dict get $altg_map $nm]
} else {
lappend ""
}
}
return $result
}
# --------------------------------
# Taken from term::ansi::code::ctrl
# --------------------------------
#Note that SYN (\016) seems to put terminals in a state
#where alternate graphics are not processed.
#an ETB (\017) needs to be sent to get alt graphics working again.
#It isn't known what software utilises SYN/ETB within altg sequences
# (presumably to alternate between the charsets within a graphics-on/graphics-off section)
#but as modern emulators seem to react to it, we should handle it.
#REVIEW - this mapping not fully understood
#used by groptim
variable grforw
variable grback
variable _
foreach _ {
! \" # $ % & ' ( ) * + , - . /
0 1 2 3 4 5 6 7 8 9 : ; < = >
? @ A B C D E F G H I J K L M
N O P Q R S T U V W X Y Z [ ^
\\ ]
} {
lappend grforw \016$_ $_\016
lappend grback $_\017 \017$_
}
unset _
# ------------------------------
#REVIEW - see forward/backward map in term::ansi::code::macros::cd::groptim - what are they for??
proc groptim {string} {
variable grforw
variable grback
set offon \x1b(B\x1b(0 ;#)) ;#editor highlighting comment
set onoff \x1b(0\x1b(B ;#)) ;#editor highlighting comment
while {![string equal $string [set new [string map [list $offon {} $onoff {}] [string map $grback [string map $grforw $string]]]]]} {
set string $new
}
return $string
}
# --------------------------------
proc ansistrip_gx {text} {
#e.g "\033(0" - select VT100 graphics for character set G0
#e.g "\033(B" - reset
@ -854,10 +943,10 @@ tcl::namespace::eval punk::ansi {
#e.g "\033)X" - where X is any char other than 0 to reset ??
#return [convert_g0 $text]
return [tcl::string::map [list "\x1b(0" "" \x1b(B" "" "\x1b)0" "" "\x1b)X" ""] $text]
return [tcl::string::map [list \x1b(0 "" \x1b(B "" \x1b)0 "" \x1b)X ""] $text]
}
proc stripansi_gx {text} {
return [tcl::string::map [list "\x1b(0" "" \x1b(B" "" "\x1b)0" "" "\x1b)X" ""] $text]
return [tcl::string::map [list \x1b(0 "" \x1b(B "" \x1b)0 "" \x1b)X ""] $text]
}
@ -1459,7 +1548,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
tcl::dict::set opts $k $v
}
default {
error "colourmap1 unrecognised option $k. Known-options: [tcl::dict::keys $opts]
error "colourmap1 unrecognised option $k. Known-options: [tcl::dict::keys $opts]"
}
}
}
@ -2358,8 +2447,8 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
lappend PUNKARGS [list {
@id -id ::punk::ansi::sgr_cache
@cmd -name punk::ansi::sgr_cache -help\
"Convenience function to view and optionally clear the ansi character attribute cache (ansi SGR codes)
"
"Convenience function to view and optionally clear the ansi character attribute cache
(ansi SGR codes)"
-action -default "" -choices "clear" -help\
"-action clear will unset the keys in the punk::ansi::sgr_cache dict
This is called automatically when setting 'colour false' in the console"
@ -2882,6 +2971,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
set SGR_samples [dict create]
foreach k [dict keys $SGR_map] {
#indent of 1 space is important for clarity in i -return string a+ output
dict set SGR_samples $k " [punk::ansi::a+ $k]sample\x1b\[m"
}
lappend PUNKARGS [list {
@ -3264,7 +3354,55 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
return $result
}
proc ansiwrap {codes text} {
lappend PUNKARGS [list {
@id -id ::punk::ansi::ansiwrap
@cmd -name punk::ansi::ansiwrap -help\
"Wrap a string with ANSI codes from
supplied codelist(s) followed by trailing
ANSI reset.
Codes are numbers or strings as indicated
in the output of the colour information
function: punk::ansi::a?
No leading reset is applied - so when
placing resultant text, any existing
SGR codes that aren't overridden may
still take effect.
For finer control use the a+ and a
functions eg
set x \"[a+ red]text [a+ bold]etc[a]\"
"
@leaders -min 0 -max -1
codelist -multiple 1 -default {} -type list -help\
"ANSI names/ints as understood by 'a?'
(Not actual ANSI as output by a+)
These can be supplied individually or
as a list or lists"
@values -min 1 -max 1
text -type string -help\
"String to wrap with ANSI (SGR)"
}]
#proc ansiwrap {codes text} {
# return [a {*}$codes]$text[a]
#}
proc ansiwrap2 {args} {
set argd [punk::args::parse $args withid ::punk::ansi::ansiwrap]
set codelists [dict get $argd leaders codelist]
set text [dict get $argd values text]
set codes [concat {*}$codelists] ;#flatten
return [a {*}$codes]$text[a]
}
proc ansiwrap {args} {
if {[llength $args] < 1} {
#minimal args parsing - unhappy path only
punk::args::parse $args withid ::punk::ansi::ansiwrap
return
}
set text [lindex $args end]
set codelists [lrange $args 0 end-1]
set codes [concat {*}$codelists] ;#flatten
return [a {*}$codes]$text[a]
}
@ -3300,6 +3438,14 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#[call [fun reset_soft]]
return \x1b\[!p
}
proc SYN {} {
#syn seems to disable alternate graphics mode temporarily on modern terminals
return \016
}
proc ETB {} {
#This is a form of soft reset for the state where a SYN was sent - re-enabling altg processing
return \017
}
proc reset_colour {} {
#*** !doctools
#[call [fun reset_colour]]
@ -3341,6 +3487,12 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#[call [fun cursor_off]]
return "\033\[?25l"
}
proc cursor_on_vt52 {} {
return \x1be
}
proc cursor_off_vt52 {} {
return \x1bf
}
# REVIEW - osc8 replays etc for split lines? - textblock
#Hyperlinks (a.k.a. HTML-like anchors) in terminal emulators https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
@ -3387,6 +3539,24 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
}
# -- --- --- --- ---
lappend PUNKARGS [list {
@id -id ::punk::ansi::move
@cmd -name punk::ansi::move -help\
{Return an ANSI sequence to move cursor to row,col
(aka: cursor home)
Sequence is of the form:
\x1b[<row>;<col>H
(CSI row ; col H)
This sequence will not be understood by old vt52
terminals. see also vt52_move.
}
@values -min 2 -max 2
row -type integer -help\
"row number - starting at 1"
col -type integer -help\
"column number - starting at 1"
}]
proc move {row col} {
#*** !doctools
#[call [fun move] [arg row] [arg col]]
@ -3394,6 +3564,44 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#[para]aka cursor home
return \033\[${row}\;${col}H
}
#NOTE vt52 uses ESC Y line column
# where line and column are ascii codes whose values are +31
# vt52 can be entered/exited via escapes
# This means we probably need to to wrap enter/exit vt52 and keep this state - as we don't have a standard way to query for terminal type
# (vt52 supports ESC Z - but vt100 sometimes? doesn't - and querying at each output would be slow anyway, even if there was a common query :/ )
lappend PUNKARGS [list {
@id -id ::punk::ansi::vt52move
@cmd -name punk::ansi::vt52move -help\
{Return a VT52 sequence to move cursor to row,col
(aka: cursor home)
Sequence is of the form:
ESCY<rowchar><colchar>
This sequence will generally not be understood by terminals
that are not in vt52 mode (e.g DECANM unset).
}
@values -min 2 -max 2
row -type integer -help\
"row number - starting at 1"
col -type integer -help\
"column number - starting at 1"
}]
proc vt52move {row col} {
#test
set r [format %c [expr {$row + 31}]]
set c [format %c [expr {$col + 31}]]
return \x1bY${r}${c}
}
proc vt52color {int} {
if {[string is integer -strict $int]} {
if {$int < 0 || $int > 15} {
error "vt52color unsupported - only 0 to 15 available"
}
}
set char [format %c [expr {$int + 31}]]
return \x1bb${char}
}
proc move_emit {row col data args} {
#*** !doctools
#[call [fun move_emit] [arg row] [arg col] [arg data] [opt {row col data...}]]
@ -3424,6 +3632,27 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
}
return $out
}
proc vt52move_emit {row col data args} {
#Todo - G code?
set out ""
if {$row eq "this"} {
#append out \033\[\;${col}G$data
append out [vt52move_column $col]$data
} else {
#append out \033\[${row}\;${col}H$data
append out [vt52move $row $col]$data
}
foreach {row col data} $args {
if {$row eq "this"} {
append out [vt52move_column $col]$data
#append out \033\[\;${col}G$data
} else {
#append out \033\[${row}\;${col}H$data
append out [vt52move $row $col]$data
}
}
return $out
}
proc move_emitblock {row col textblock} {
#*** !doctools
#[call [fun move_emitblock] [arg row] [arg col] [arg textblock]]
@ -3434,31 +3663,63 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
}
return $commands
}
proc vt52move_emitblock {row col textblock} {
#*** !doctools
#[call [fun move_emitblock] [arg row] [arg col] [arg textblock]]
set commands ""
foreach ln [split $textblock \n] {
append commands [punk::ansi::vt52move_emit $row $col $ln]
incr row
}
return $commands
}
proc move_forward {{n 1}} {
#*** !doctools
#[call [fun move_forward] [arg n]]
return \033\[${n}C
}
proc vt52move_forward {{n 1}} {
return [string repeat \x1bC $n]
}
proc move_back {{n 1}} {
#*** !doctools
#[call [fun move_back] [arg n]]
return \033\[${n}D
}
proc vt52move_back {{n 1}} {
return [string repeat \x1bD $n]
}
proc move_up {{n 1}} {
#*** !doctools
#[call [fun move_up] [arg n]]
return \033\[${n}A
}
proc vt52move_up {{n 1}} {
return [string repeat \x1bA $n]
}
proc move_down {{n 1}} {
#*** !doctools
#[call [fun move_down] [arg n]]
return \033\[${n}B
}
proc vt52move_down {{n 1}} {
return [string repeat \x1bB $n]
}
proc move_column {col} {
#*** !doctools
#[call [fun move_column] [arg col]]
return \x1b\[${col}G
}
proc vt52move_column {col} {
#This is a bit of a fudge - as there is no command to move to a specific column.
#without tracking state - we settle for moving back enough times to ensure we're at column 1 - and then move forward.
#inefficient - but will have to do I guess.
#review - max term width vt52? env var LINES and env var COLUMNS ?
# also ESC R <cols,rows> CR - set window size
set back [string repeat \x1bD 132]
set fwd [string repeat \x1bC [expr {$col - 1}]]
return $back$fwd
}
proc move_row {row} {
#*** !doctools
#[call [fun move_row] [arg row]]
@ -3496,6 +3757,13 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#[para] DECRC
return \x1b8
}
proc cursor_save_vt52 {} {
return \x1bj
}
proc cursor_restore_vt52 {} {
return \x1bk
}
# -- --- --- --- ---
#CRM Show Control Character Mode
proc enable_crm {} {
@ -3551,17 +3819,130 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
# \x1b\[?7\;2\$y
#where 1 = set, 2 = unset. (0 = mode not recognised, 3 = permanently set, 4 = permanently unset)
#names for other alt_screen mechanisms: 1047,1048 vs 1049?
variable decmode_names [dict create\
line_wrap 7\
LNM 20\
alt_screen 1049\
grapheme_clusters 2027\
bracketed_paste 2004\
mouse_sgr_extended 1006\
mouse_urxvt 1015\
mouse_sgr 1016\
]
#https://wiki.tau.garden/dec-modes/
#(DEC,xterm,contour,mintty,kitty etc)
#https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
#names for other alt_screen mechanismk: 1047,1048 vs 1049?
#variable decmode_names [dict create\
# DECANM 2\
# origin 6\
# DECCOLM 3\
# line_wrap 7\
# LNM 20\
# alt_screen 1049\
# grapheme_clusters 2027\
# bracketed_paste 2004\
# mouse_sgr 1006\
# mouse_urxvt 1015\
# mouse_sgr_pixel 1016\
#]
variable decmode_data {
1 {
{origin DEC description "DECCKM - Cursor Keys Mode" names {DECCKM cursor_keys}}
}
2 {
{origin DEC description "DECANM - ANSI/VT52 Mode" names {DECANM} note {
Disable to turn on VT52 emulation.
In VT52 mode - use \x1b< to exit.
}
}
}
3 {
{origin DEC description "DECCOLM - Column" names {DECCOLM}}
}
4 {
{origin DEC description "DECSCLM - Scrolling" names {DECSCLM}}
}
5 {
{origin DEC description "DECSCNM - Screen Mode (light or dark screen)" names {DECSNM lightmode}}
}
7 {
{origin DEC description "DECAWM - Auto Wrap Mode" names {DECAWM line_wrap}}
}
9 {
{origin "xterm" description "X10 compatibility mouse" names {SET_X10_MOUSE mouse_tracking} note {
Escape sequence on button press only.
CSI M CbCxCy (6 chars)
Coords limited to 223 (=255 - 32)
}
}
{origin DEC description "DECINLM - Interlace Mode (obsolete?)" names {DECINLM}}
}
20 {
{origin DEC description "LNM - Line Feed/New Line Mode" names {LNM} note {
For terminals that support LNM, the default is off
meaning a lone CR respresents the character emitted
when enter is pushed. Turning LNM on would mean that
CR LF is sent when hitting enter. This feature is
not commonly supported, and the default will normally
be as if this was off - ie lone CR.
}
}
}
25 {
{origin DEC description "DECTCEM - Text Cursor Enable Mode" names {DECTCEM cursor_enable}}
}
47 {
{origin xterm description "xterm alternate buffer" names {xterm_altbuf}}
{origin DEC description "DECGRPM - Graphics Rotated Print Mode (obsolete?)" names {DECGRPM}}
}
66 {
{origin DEC description "DECNKM - Numeric Keypad Mode" names {DECNKM}}
}
1000 {
{origin "xterm" description "VT200 compatibility mouse" names {SET_VT200_MOUSE} note {
Escape sequence on both button press and release.
CSI M CbCxCy
}
}
}
1004 {
{origin "xterm" description "Send FocusIn/FocusOut events" names {mouse_focus_event}}
}
1005 {
{origin "xterm" description "Enable UTF-8 Mouse Mode" names {mouse_utf8 mouse_utf8_extended}}
}
1006 {
{origin "xterm" description "Enable SGR Mouse Mode" names {mouse_sgr mouse_sgr_extended} note{
SET_SGR_EXT_MODE_MOUSE - extended compared to x10 mouse protocol which limits x y coords
to 223 (=255 - 32)
}
}
}
1015 {
{origin "urxvt" description "Enable urxvt Mouse Mode" names {mouse_urxvt}}
}
1016 {
{origin "xterm" description "Enable SGR Pixel Mouse Mode" names {mouse_sgr_pixel}}
}
1047 {
{origin "xterm" description "Alternate Buffer" names {alt_buffer_only}}
}
1049 {
{origin "xterm" description "Alternate Buffer with save cursor" names {alt_buffer alt_screen}}
}
2004 {
{origin "xterm" description "Set bracketed paste mode" names {bracketed_paste}}
}
2027 {
{origin Contour description "Grapheme Cluster Processing" names {grapheme_clusters}}
}
}
set decmode_names [dict create]
dict for {code items} $decmode_data {
foreach itm $items {
set names [dict get $itm names]
foreach nm $names {
dict set decmode_names $nm $code
}
}
}
proc query_mode {num_or_name} {
if {[string is integer -strict $num_or_name]} {
set m $num_or_name
@ -3674,11 +4055,17 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#[para]Erase to start of line, leaving cursor position alone.
return \033\[1K
}
proc vt52erase_sol {} {
return \x1bo
}
proc erase_eol {} {
#*** !doctools
#[call [fun erase_eol]]
return \033\[K
}
proc vt52erase_eol {} {
return \x1bK
}
#see also clear_above clear_below
# -- --- --- --- ---
@ -3732,6 +4119,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
proc cursor_pos_extended {} {
#includes page e.g ^[[47;3;1R
#(but not on all terminals - some (freebsd?) will report as per 6n e.g ^[[74;3R)
return \033\[?6n
}
@ -3789,6 +4177,9 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#[para]This may not work on terminals which have multiple panes/windows
return "\033\]2;$windowtitle\033\\" ;#works for xterm and most derivatives
}
proc vt52titleset {windowtitle} {
return \x1bS$windowtitle\r
}
#titleget - https://invisible-island.net/xterm/xterm.faq.html#how2_title
#no cross-platform ansi-only mechanism ?
@ -4672,8 +5063,14 @@ tcl::namespace::eval punk::ansi::ta {
variable re_osc_open {(?:\x1b\]|\u009d).*}
variable standalone_code_map [list \x1bc "" \x1b7 "" \x1b8 "" \x1bM "" \x1bE "" \x1bD "" \x1bH "" \x1b= "" \x1b> "" \x1b#3 "" \x1b#4 "" \x1b#5 "" \x1b#6 "" \x1b#8 ""]
variable re_standalones {(?:\x1bc|\x1b7|\x1b8|\x1bM|\x1bE|\x1bD|\x1bD|\x1bH|\x1b=|\x1b>|\x1b#3|\x1b#4|\x1b#5|\x1b#6|\x1b#8)}
#variable standalone_code_map [list \x1bc "" \x1b7 "" \x1b8 "" \x1bM "" \x1bE "" \x1bD "" \x1bH "" \x1b= "" \x1b> "" \x1b#3 "" \x1b#4 "" \x1b#5 "" \x1b#6 "" \x1b#8 ""]
variable re_standalones {(?:\x1bc|\x1b7|\x1b8|\x1bM|\x1bD|\x1bE|\x1bH|\x1b=|\x1b>|\x1b#3|\x1b#4|\x1b#5|\x1b#6|\x1b#8)}
variable re_standalones_vt52 {(?:\x1bZ)}
#ESC Y move, ESC b foreground colour
#ESC F - gr-on ESC G - gr-off
variable re_vt52_open {(?:\x1bY|\x1bb|\x1bF)}
#\x1bc vt52 bgcolour conflict ??
#if we don't split on altgraphics too and separate them out - it's easy to get into a horrible mess
variable re_g0_group {(?:\x1b\(0)(?:(?!\x1b\(B).)*\x1b\(B}
@ -4686,7 +5083,7 @@ tcl::namespace::eval punk::ansi::ta {
#regex to capture the start of string/privacy message/application command block including the contents and string terminator (ST)
#non-greedy by exclusion of ST terminators in body
#we need to match \x1b\\ not just \x1b There could be colour codes nested in a privacy msg/string
#we need to match \x1b\\ not just \x1b There could be colour codes nested in a privacy msg/string (not widely supported?)
#even if terminals generally don't support that - it's quite possible for an ansi code to get nested this way - and we'd prefer it not to break our splits
#Just checking for \x1b will terminate the match too early
#we also need to track the start of ST terminated code and not add it for replay (in the ansistring functions)
@ -4705,17 +5102,44 @@ tcl::namespace::eval punk::ansi::ta {
#handrafted TRIE version of above. Somewhat difficult to construct and maintain. TODO - find a regexp TRIE generator that works with Tcl regexes
#This does make things quicker - but it's too early to finalise the detect/split regexes (e.g missing \U0090 ) - will need to be redone.
#variable re_ansi_detect {(?:\x1b(?:\((?:0|B)|\[(?:[\x20-\x2f\x30-\x3f]*[\x40-\x7e])|\](?:(?:[^\007]*)\007|(?:(?!\x1b\\).)*\x1b\\)|(?:P|X|\^|_)(?:(?:(?!\x1b\\|\007).)*(?:\x1b\\|\007))|c|7|8|M|E|D|H|=|>|(?:#(?:3|4|5|6|8))))|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)|(?:\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]|(?:\u009d)(?:[^\u009c]*)?\u009c}
variable re_ansi_detect {(?:\x1b(?:\[(?:[\x20-\x2f\x30-\x3f]*[\x40-\x7e])|\((?:0|B)|\](?:(?:[^\007]*)\007|(?:(?!\x1b\\).)*\x1b\\)|(?:P|X|\^|_)(?:(?:(?!\x1b\\|\007).)*(?:\x1b\\|\007))|c|7|8|M|E|D|H|=|>|(?:#(?:3|4|5|6|8))))|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)|(?:\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]|(?:\u009d)(?:[^\u009c]*)?\u009c}
#NOTE - the literal # char can cause problems in expanded syntax - even though it's within a bracketed section. \# seems to work though.
#vt52 specific |<|A|B|C|I|J|K|L|M|Z|(?:Y(?:..))|(?:b(?:.)|
#https://freemint.github.io/tos.hyp/en/VT_52_terminal.html
#what to with ESC c vs vt52 ESC c <c> (background colour) ???
#we probably need to use a separate re_ansi_detect for vt52
#although it's stated later terminals are backwards compatible with vt52 - that doesn't seem to mean for example a vt100 will process vt52 codes at the same time as ansi codes
#ie - when DECANM is on - VT52 codes are *not* processed
#todo - ansi mode and cursor key mode set ?
# arrow keys -> ESC O A, ESC O B, ESC O C, ESC O D
# plus more for auxiliary keypad codes in keypad application mode (and some in numeric mode)
variable re_ansi_detect {(?x)
(?:\x1b(?:\[(?:[\x20-\x2f\x30-\x3f]*[\x40-\x7e])|c|7|8|M|D|E|H|=|>|<|A|B|C|I|J|K|L|M|Z|(?:Y(?:..))|(?:b(?:.))|\((?:0|B)|\](?:(?:[^\007]*)\007|(?:(?!\x1b\\).)*\x1b\\)|(?:P|X|\^|_)(?:(?:(?!\x1b\\|\007).)*(?:\x1b\\|\007))|(?:\#(?:3|4|5|6|8))))
|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)
|(?:\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]
|(?:\u009d)(?:[^\u009c]*)?\u009c
}
#---
# -- --- --- ---
#variable re_csi_code {(?:\x1b\[|\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]}
variable re_ansi_detect_open "${re_csi_open}|${re_osc_open}|${re_standalones}|${re_ST_open}|${re_g0_open}"
variable re_ansi_detect_open "${re_csi_open}|${re_osc_open}|${re_standalones}|${re_standalones_vt52}|${re_ST_open}|${re_g0_open}|${re_vt52_open}"
#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
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])+"
} else {
set re_ansi_split_multi "(?:${re_ansi_split})+"
}
lappend PUNKARGS [list -dynamic 0 {
@id -id ::punk::ansi::ta::detect
@ -4922,81 +5346,46 @@ tcl::namespace::eval punk::ansi::ta {
# -- --- --- --- --- ---
#Split $text to a list containing alternating ANSI colour codes and text.
#ANSI colour codes are always on the second element, fourth, and so on.
#(ie plaintext on odd list-indices ansi on even indices)
#(ie plaintext on even list-indices ansi on odd indices)
#result of split on non-empty string always has an odd length - with indices 0 and end always being plaintext (possibly empty string)
# Example:
#ta_split_codes "" # => ""
#ta_split_codes "a" # => "a"
#ta_split_codes "a\e[31m" # => {"a" "\e[31m"}
#ta_split_codes "\e[31ma" # => {"" "\e[31m" "a"}
#ta_split_codes "\e[31ma\e[0m" # => {"" "\e[31m" "a" "\e[0m"}
#ta_split_codes "\e[31ma\e[0mb" # => {"" "\e[31m" "a" "\e[0m", "b"}
#ta_split_codes "\e[31m\e[0mb" # => {"" "\e[31m\e[0m" "b"}
#split_codes "" # => ""
#split_codes "a" # => "a"
#split_codes "a\e[31m" # => {"a" "\e[31m" ""}
#split_codes "\e[31ma" # => {"" "\e[31m" "a"}
#split_codes "\e[31ma\e[0m" # => {"" "\e[31m" "a" "\e[0m" ""}
#split_codes "\e[31ma\e[0mb" # => {"" "\e[31m" "a" "\e[0m", "b"}
#split_codes "\e[31m\e[0mb" # => {"" "\e[31m\e[0m" "b"}
#
proc split_codes {text} {
variable re_ansi_split
set re "(?:${re_ansi_split})+"
return [_perlish_split $re $text]
variable re_ansi_split_multi
return [_perlish_split $re_ansi_split_multi $text]
}
#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 odd/even plain ansi still holds)
#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.
proc split_codes_single2 {text} {
variable re_ansi_split
return [_perlish_split $re_ansi_split $text]
}
proc split_codes_single3 {text} {
#copy from re_ansi_split
_perlish_split {(?:\x1b(?:\((?:0|B)|\[(?:[\x20-\x2f\x30-\x3f]*[\x40-\x7e])|\](?:(?:[^\007]*)\007|(?:(?!\x1b\\).)*\x1b\\)|(?:P|X|\^|_)(?:(?:(?!\x1b\\|\007).)*(?:\x1b\\|\007))|c|7|8|M|E|D|H|=|>|(?:#(?:3|4|5|6|8))))|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)|(?:\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]|(?:\u009d)(?:[^\u009c]*)?\u009c} $text
}
proc split_codes_single4 {text} {
if {$text eq ""} {
return {}
}
variable re_ansi_split
set re $re_ansi_split
#variable re_ansi_detect1
#set re $re_ansi_detect1
set list [list]
set start 0
#set re {(?:\x1b(?:\((?:0|B)|\[(?:[\x20-\x2f\x30-\x3f]*[\x40-\x7e])|\](?:(?:[^\007]*)\007|(?:(?!\x1b\\).)*\x1b\\)|(?:P|X|\^|_)(?:(?:(?!\x1b\\|\007).)*(?:\x1b\\|\007))|c|7|8|M|E|D|H|=|>|(?:#(?:3|4|5|6|8))))|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)|(?:\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]|(?:\u009d)(?:[^\u009c]*)?\u009c}
#We can get $matchEnd < $matchStart; we need to ensure there is an exit condition for non-greedy empty results REVIEW
#while {[regexp -start $start -indices -- {(?:\x1b(?:\((?:0|B)|\[(?:[\x20-\x2f\x30-\x3f]*[\x40-\x7e])|\](?:(?:[^\007]*)\007|(?:(?!\x1b\\).)*\x1b\\)|(?:P|X|\^|_)(?:(?:(?!\x1b\\|\007).)*(?:\x1b\\|\007))|c|7|8|M|E|D|H|=|>|(?:#(?:3|4|5|6|8))))|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)|(?:\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]|(?:\u009d)(?:[^\u009c]*)?\u009c} $text match]} {}
while {[regexp -start $start -indices -- $re $text match]} {
lassign $match matchStart matchEnd
#puts "->start $start ->match $matchStart $matchEnd"
lappend list [tcl::string::range $text $start $matchStart-1]
if {$matchEnd < $matchStart} {
set e $matchStart
incr start
} else {
set e $matchEnd
set start [expr {$matchEnd+1}]
}
lappend list [tcl::string::range $text $matchStart $e]
if {$start >= [tcl::string::length $text]} {
break
}
}
return [lappend list [tcl::string::range $text $start end]]
}
proc split_codes_single {text} {
if {$text eq ""} {
return {}
}
variable re_ansi_split
set next 0
set b -1
#set b -1
set list [list]
set coderanges [regexp -indices -all -inline -- $re_ansi_split $text]
foreach cr $coderanges {
lappend list [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}]
#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} {
variable re_ansi_split
return [_perlish_split $re_ansi_split $text]
}
proc get_codes_single {text} {
variable re_ansi_split
regexp -all -inline -- $re_ansi_split $text
@ -5008,7 +5397,7 @@ tcl::namespace::eval punk::ansi::ta {
return {}
}
set next 0
set b -1
#set b -1
set list [list]
set coderanges [regexp -indices -all -inline -- $re $text]
foreach cr $coderanges {
@ -5103,29 +5492,6 @@ tcl::namespace::eval punk::ansi::ta {
#return [lappend list [tcl::string::range $text $start end]]
yield [tcl::string::range $text $start end]
}
proc _perlish_split2 {re text} {
if {[tcl::string::length $text] == 0} {
return {}
}
set list [list]
set start 0
#We can get $matchEnd < $matchStart; we need to ensure there is an exit condition for non-greedy empty results REVIEW
while {[regexp -start $start -indices -- $re $text match]} {
lassign $match matchStart matchEnd
#puts "->start $start ->match $matchStart $matchEnd"
if {$matchEnd < $matchStart} {
lappend list [tcl::string::range $text $start $matchStart-1] [tcl::string::index $text $matchStart]
incr start
} else {
lappend list [tcl::string::range $text $start $matchStart-1] [tcl::string::range $text $matchStart $matchEnd]
set start [expr {$matchEnd+1}]
}
if {$start >= [tcl::string::length $text]} {
break
}
}
return [lappend list [tcl::string::range $text $start end]]
}
proc _ws_split {text} {
regexp -all -inline {(?:\S+)|(?:\s+)} $text
}
@ -7429,12 +7795,10 @@ tcl::namespace::eval punk::ansi::internal {
#todo - document
interp alias {} ansistring {} ::punk::ansi::ansistring
if {![info exists ::punk::args::register::NAMESPACES]} {
namespace eval ::punk::args::register {
set NAMESPACES [list]
}
}
#use fully qualified so 8.6 doesn't find existing var in global namespace
lappend ::punk::args::register::NAMESPACES ::punk::ansi ::punk::ansi::class ::punk::ansi::ta
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready

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

File diff suppressed because it is too large Load Diff

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

@ -2015,7 +2015,7 @@ tcl::namespace::eval punk::char {
# ------------------------------------------------------------------------------------------------------
proc grapheme_split_tk {string} {
if {![regexp "\[\uFF-\U10FFFF\]" $string]} {
#only ascii - no joiners or unicode
#only ascii (7 or 8 bit) - no joiners or unicode
return [split $string {}]
}
package require tk
@ -2068,14 +2068,14 @@ tcl::namespace::eval punk::char {
return $width
}
proc wcswidth_single {char} {
scan $c %c dec
if {$c <= 255 && !($c < 31 || $c == 127)} {
scan $char %c dec
if {$dec <= 255 && !($dec < 31 || $dec == 127)} {
#review - non-printing ascii? why does textutil::wcswidth report 1 ??
#todo - compare with python or other lang wcwidth
return 1
} elseif {$c < 917504 || $c > 917631} {
} elseif {$dec < 917504 || $dec > 917631} {
#TODO - various other joiners and non-printing chars
return [textutil::wcswidth_char $c]
return [textutil::wcswidth_char $dec] ;#note textutil::wcswidth_char takes a decimal codepoint!
#may return -1 - REVIEW
}
return 0
@ -2084,13 +2084,13 @@ tcl::namespace::eval punk::char {
set width 0
foreach c [split $string {}] {
scan $c %c dec
if {$c <= 255 && !($c < 31 || $c == 127)} {
if {$dec <= 255 && !($dec < 31 || $dec == 127)} {
#review - non-printing ascii? why does textutil::wcswidth report 1 ??
#todo - compare with python or other lang wcwidth
incr width
} elseif {$c < 917504 || $c > 917631} {
} elseif {$dec < 917504 || $dec > 917631} {
#TODO - various other joiners and non-printing chars
set w [textutil::wcswidth_char $c]
set w [textutil::wcswidth_char $dec] ;#takes decimal codepoint
if {$w < 0} {
return -1
} else {
@ -2117,14 +2117,14 @@ tcl::namespace::eval punk::char {
set chunk [tcl::string::range $string $startidx $endidx]
set codes [scan $chunk [tcl::string::repeat %c [tcl::string::length $chunk]]]
foreach c $codes {
if {$c <= 255 && !($c < 31 || $c == 127)} {
foreach dec $codes {
if {$dec <= 255 && !($dec < 31 || $dec == 127)} {
#review - non-printing ascii? why does textutil::wcswidth report 1 ??
#todo - compare with python or other lang wcwidth
incr width
} elseif {$c < 917504 || $c > 917631} {
} elseif {$dec < 917504 || $dec > 917631} {
#TODO - various other joiners and non-printing chars
set w [textutil::wcswidth_char $c]
set w [textutil::wcswidth_char $dec]
if {$w < 0} {
return -1
} else {
@ -2145,18 +2145,18 @@ tcl::namespace::eval punk::char {
#TODO
set codes [scan $string [tcl::string::repeat %c [tcl::string::length $string]]]
set width 0
foreach c $codes {
foreach dec $codes {
#unicode Tags block zero width
if {$c < 917504 || $c > 917631} {
if {$c <= 255} {
if {$dec < 917504 || $dec > 917631} {
if {$dec <= 255} {
#review - non-printing ascii? why does textutil::wcswidth report 1 ??
#todo - compare with python or other lang wcwidth
if {!($c < 31 || $c == 127)} {
if {!($dec < 31 || $dec == 127)} {
incr width
}
} else {
#TODO - various other joiners and non-printing chars
set w [textutil::wcswidth_char $c]
set w [textutil::wcswidth_char $dec] ;#takes decimal codepoint
if {$w < 0} {
return -1
} else {
@ -2169,7 +2169,7 @@ tcl::namespace::eval punk::char {
}
proc wcswidth2 {string} {
set codes [scan $string [tcl::string::repeat %c [tcl::string::length $string]]]
set widths [lmap c $codes {textutil::wcswidth_char $c}]
set widths [lmap dec $codes {textutil::wcswidth_char $dec}]
if {-1 in $widths} {
return -1
}

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

File diff suppressed because it is too large Load Diff

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

@ -246,6 +246,58 @@ tcl::namespace::eval punk::lib::compat {
#outside of lmap - don't know of any particularly nice ways to flatten to subindex 1 of each element..
#flattening then lsearch with -stride and * would be nice - but it's not avail in 8.6
if {![info exists ::auto_index(readFile)]} {
if {[info commands ::readFile] eq ""} {
proc ::readFile {filename {mode text}} {
#readFile not seen in auto_index or as command: installed by punk::lib
# Parse the arguments
set MODES {binary text}
set ERR [list -level 1 -errorcode [list TCL LOOKUP MODE $mode]]
set mode [tcl::prefix match -message "mode" -error $ERR $MODES $mode]
# Read the file
set f [open $filename [dict get {text r binary rb} $mode]]
try {
return [read $f]
} finally {
close $f
}
}
}
}
if {![info exists ::auto_index(writeFile)]} {
if {[info commands ::writeFile] eq ""} {
proc ::writeFile {args} {
#writeFile not seen in auto_index or as command: installed by punk::lib
# Parse the arguments
switch [llength $args] {
2 {
lassign $args filename data
set mode text
}
3 {
lassign $args filename mode data
set MODES {binary text}
set ERR [list -level 1 -errorcode [list TCL LOOKUP MODE $mode]]
set mode [tcl::prefix match -message "mode" -error $ERR $MODES $mode]
}
default {
set COMMAND [lindex [info level 0] 0]
return -code error -errorcode {TCL WRONGARGS} "wrong # args: should be \"$COMMAND filename ?mode? data\""
}
}
# Write the File
set f [open $filename [dict get {text w binary wb} $mode]]
try {
puts -nonewline $f $data
} finally {
close $f
}
}
}
}
if {"::lpop" ne [info commands ::lpop]} {
#puts stderr "Warning - no built-in lpop"
interp alias {} lpop {} ::punk::lib::compat::lpop
@ -1021,7 +1073,8 @@ namespace eval punk::lib {
-separator -default "%sep%"
-roottype -default "dict"
-substructure -default {}
-channel -default stdout -help "existing channel - or 'none' to return as string"
-channel -default stdout -help\
"existing channel - or 'none' to return as string"
@values -min 1 -max -1
@ -1049,7 +1102,6 @@ namespace eval punk::lib {
Here we supply 2 separate pattern hierarchies, where @0-end and @* are list operations and are equivalent
The second level segement in each pattern switches to a dict operation to retrieve the value by key.
When a list operation such as @* is used - integer list indexes are displayed on the left side of the = for that hierarchy level.
}
}]
#puts stderr "$argspec"
@ -1091,7 +1143,8 @@ namespace eval punk::lib {
set sep " [punk::ansi::a+ Green]=$RST " ;#stick to basic default colours for wider terminal support
set sep_mismatch " [punk::ansi::a+ Brightred undercurly underline undt-white]mismatch$RST "
}
package require punk ;#we need pipeline pattern matching features
package require punk::pipe
#package require punk ;#we need pipeline pattern matching features
package require textblock
set argd [punk::args::get_dict [string map [list %sep% $sep %sep_mismatch% $sep_mismatch] {
@ -1103,22 +1156,28 @@ namespace eval punk::lib {
-trimright -default 1 -type boolean -help\
"Trim whitespace off rhs of each line.
This can help prevent a single long line that wraps in terminal from making
every line wrap due to long rhs padding.
"
-separator -default {%sep%} -help "Separator column between keys and values"
-separator_mismatch -default {%sep_mismatch%} -help "Separator to use when patterns mismatch"
-roottype -default "dict" -help "list,dict,string"
-ansibase_keys -default "" -help "ansi list for each level in -substructure. e.g \[list \[a+ red\] \[a+ web-green\]\]"
every line wrap due to long rhs padding."
-separator -default {%sep%} -help\
"Separator column between keys and values"
-separator_mismatch -default {%sep_mismatch%} -help\
"Separator to use when patterns mismatch"
-roottype -default "dict" -help\
"list,dict,string"
-ansibase_keys -default "" -help\
"ansi list for each level in -substructure. e.g \[list \[a+ red\] \[a+ web-green\]\]"
-substructure -default {}
-ansibase_values -default ""
-keytemplates -default {\$\{$key\}} -type list -help "list of templates for keys at each level"
-keytemplates -default {\$\{$key\}} -type list -help\
"list of templates for keys at each level"
-keysorttype -default "none" -choices {none dictionary ascii integer real}
-keysortdirection -default increasing -choices {increasing decreasing}
-debug -default 0 -type boolean -help\
"When enabled, produces some rudimentary debug output on stderr"
@values -min 1 -max -1
dictvalue -type list -help "dict or list value"
patterns -default "*" -type string -multiple 1 -help "key or key glob pattern"
dictvalue -type list -help\
"dict or list value"
patterns -default "*" -type string -multiple 1 -help\
"key or key glob pattern"
}] $args]
#for punk::lib - we want to reduce pkg dependencies.
@ -1201,7 +1260,7 @@ namespace eval punk::lib {
set segments [split $pattern_nest /]
set levelpatterns [lindex $segments 0] ;#possibly comma separated patterns
#we need to use _split_patterns to separate (e.g to protect commas that appear within quotes)
set patterninfo [punk::_split_patterns $levelpatterns]
set patterninfo [punk::pipe::lib::_split_patterns $levelpatterns]
#puts stderr "showdict-->_split_patterns: $patterninfo"
foreach v_idx $patterninfo {
lassign $v_idx v idx
@ -1479,7 +1538,7 @@ namespace eval punk::lib {
# -- --- --- ---
set substructure ""
set pnext [lindex $segments 1]
set patterninfo [punk::_split_patterns $levelpatterns]
set patterninfo [punk::pipe::lib::_split_patterns $levelpatterns]
if {[llength $patterninfo] == 0} {
# // ? -review - what does this mean? for xpath this would mean at any level
set substructure [lindex $pattern_this_structure end]
@ -2043,17 +2102,31 @@ namespace eval punk::lib {
concat {*}[uplevel 1 lmap {*}$args]
}
#proc dict_getdef {dictValue args} {
# if {[llength $args] < 1} {
# error {wrong # args: should be "dict_getdef dictValue ?key ...? key default"}
# }
# set keys [lrange $args -1 end-1]
# if {[tcl::dict::exists $dictValue {*}$keys]} {
# return [tcl::dict::get $dictValue {*}$keys]
# } else {
# return [lindex $args end]
# }
#}
if {[info commands ::tcl::dict::getdef] eq ""} {
proc dict_getdef {dictValue args} {
if {[llength $args] < 1} {
error {wrong # args: should be "dict_getdef dictValue ?key ...? key default"}
}
set keys [lrange $args -1 end-1]
set keys [lrange $args 0 end-1]
if {[tcl::dict::exists $dictValue {*}$keys]} {
return [tcl::dict::get $dictValue {*}$keys]
} else {
return [lindex $args end]
}
}
} else {
#we pay a minor perf penalty for the wrap
interp alias "" ::punk::lib::dict_getdef "" ::tcl::dict::getdef
}
#proc sample1 {p1 n args} {
# #*** !doctools
@ -2722,6 +2795,7 @@ namespace eval punk::lib {
}
return [join $result \n]
}
#dedent?
proc undent {text} {
if {$text eq ""} {
return ""
@ -4142,12 +4216,10 @@ tcl::namespace::eval punk::lib::debug {
proc showdict {args} {}
}
if {![info exists ::punk::args::register::NAMESPACES]} {
namespace eval ::punk::args::register {
set ::punk::args::register::NAMESPACES [list] ;#use fully qualified so 8.6 doesn't find existing var in global namespace
}
}
#use fully qualified so 8.6 doesn't find existing var in global namespace
lappend ::punk::args::register::NAMESPACES ::punk::lib
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide punk::lib [tcl::namespace::eval punk::lib {

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

@ -177,7 +177,8 @@ namespace eval punk::mix::cli {
}
}
}
cd $sourcefolder
#cd $sourcefolder
#use run so that stdout visible as it goes
if {![catch {run --timeout=55000 -debug [info nameofexecutable] $sourcefolder/make.tcl {*}$args} exitinfo]} {
#todo - notify if exit because of timeout!
@ -185,11 +186,11 @@ namespace eval punk::mix::cli {
set exitcode [dict get $exitinfo exitcode]
} else {
puts stderr "Error unable to determine exitcode. err: $exitinfo"
cd $startdir
#cd $startdir
return false
}
cd $startdir
#cd $startdir
if {$exitcode != 0} {
puts stderr "FAILED with exitcode $exitcode"
return false
@ -364,10 +365,10 @@ namespace eval punk::mix::cli {
#ignore trailing .tm .TM if present
#if version doesn't pass validation - treat it as part of the modulename and return empty version string without error
#Up to caller to validate.
proc split_modulename_version {modulename} {
set lastpart [namespace tail $modulename]
proc split_modulename_version {fullmodulename} {
set lastpart [namespace tail $fullmodulename]
set lastpart [file tail $lastpart] ;# should be ok to use file tail now that we've ensured no namespace components
if {[string equal -nocase [file extension $modulename] ".tm"]} {
if {[string equal -nocase [file extension $fullmodulename] ".tm"]} {
set fileparts [split [file rootname $lastpart] -]
} else {
set fileparts [split $lastpart -]
@ -380,7 +381,13 @@ namespace eval punk::mix::cli {
set namesegment [join $fileparts -]
set versionsegment ""
}
return [list $namesegment $versionsegment]
set base [namespace qualifiers $fullmodulename]
if {$base ne ""} {
set modulename "${base}::$namesegment"
} else {
set modulename $namesegment
}
return [list $modulename $versionsegment]
}
proc get_status {{workingdir ""} args} {

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

@ -31,16 +31,42 @@ namespace eval punk::mix::commandset::layout {
namespace export *
namespace eval argdoc {
proc layout_names {} {
if {[catch {punk::mix::commandset::layout::lib::layouts_dict *} ldict]} {
#REVIEW
return "punk.project"
} else {
return [dict keys $ldict]
}
}
}
#per layout functions
proc files {{layout ""}} {
set argd [punk::args::get_dict {
punk::args::define {
@dynamic
@id -id ::punk::mix::commandset::layout::files
-datetime -default "%Y-%m-%dT%H:%M:%S" -help\
"Datetime format for mtime. Use empty string for no datetime output"
@values -min 1 -max 1
layout -type string -minsize 1
} [list $layout]]
layout -type string -choices {${[punk::mix::commandset::layout::argdoc::layout_names]}}
}
proc files {args} {
set argd [punk::args::get_by_id ::punk::mix::commandset::layout::files $args]
set layout [dict get $argd values layout]
set dtformat [dict get $argd opts -datetime]
set allfiles [lib::layout_all_files $layout]
if {$dtformat eq ""} {
return [join $allfiles \n]
} else {
set out ""
foreach f $allfiles {
set mtime [dict get [file stat $f] mtime]
append out "$f [clock format $mtime -format $dtformat]" \n
}
set out [string range $out 0 end-1]
return $out
}
}
proc templatefiles {layout} {
set templatefiles_and_tags [lib::layout_scan_for_template_files $layout]
@ -166,7 +192,7 @@ namespace eval punk::mix::commandset::layout {
}
proc as_dict {args} {
tailcall punk::mix::commandset::layout::lib::layouts_dict {*}$args
punk::mix::commandset::layout::lib::layouts_dict {*}$args
}
proc references_as_dict {args} {
package require punk::cap

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

@ -1,5 +1,5 @@
# -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'deck make' or src/make.tcl to update from <pkg>-buildversion.txt
#
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
@ -304,11 +304,12 @@ namespace eval punk::mix::commandset::loadedlib {
}
set versions [package versions [lindex $libfound 0]]
if {$has_natsort} {
set versions [natsort::sort $versions]
} else {
set versions [lsort $versions]
}
set versions [lsort -command {package vcompare} $versions]
#if {$has_natsort} {
# set versions [natsort::sort $versions]
#} else {
# set versions [lsort $versions]
#}
if {![llength $versions]} {
error "No version numbers found for library/module $libfound - sorry, you will need to copy it across manually"
}

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

@ -120,17 +120,20 @@ namespace eval punk::mix::commandset::module {
return $table
}
#return all module templates with repeated ones suffixed with .2 .3 etc
proc templates_dict {args} {
set argspec {
#return all module templates with repeated ones suffixed with #2 #3 etc
punk::args::define {
@id -id ::punk::mix::commandset::module::templates_dict
@cmd -name templates_dict -help "Templates from module and project paths"
-startdir -default "" -help "Project folder used in addition to module paths"
@cmd -name templates_dict -help\
"Templates from module and project paths"
-startdir -default "" -help\
"Project folder used in addition to module paths"
-not -default "" -multiple 1
@values
globsearches -default * -multiple 1
}
set argd [punk::args::get_dict $argspec $args]
proc templates_dict {args} {
set argd [punk::args::get_by_id ::punk::mix::commandset::module::templates_dict $args]
package require punk::cap
if {[punk::cap::capability_has_handler punk.templates]} {
set template_folder_dict [punk::cap::call_handler punk.templates get_itemdict_moduletemplates {*}$args]
@ -154,10 +157,11 @@ namespace eval punk::mix::commandset::module {
the higher version number will be used.
"
-license -default <unspecified>
-author -default <unspecified> -multiple 1
-template -default punk.module
-type -default "[lindex $moduletypes 0]" -choices {$moduletypes}
-force -default 0 -type boolean -help\
"If set true, will overwrite an existing .tm file if there is one.
"If set true, will OVERWRITE an existing .tm file if there is one.
If false (default) an error will be raised if there is a conflict."
-quiet -default 0 -type boolean -help\
"Suppress information messages on stdout"
@ -262,6 +266,7 @@ namespace eval punk::mix::commandset::module {
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
set opt_license [dict get $opts -license]
set opt_authors [dict get $opts -author] ;#-multiple true
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
set opt_template [dict get $opts -template]
if {[regexp {.*[?*].*} $opt_template]} {
@ -403,7 +408,7 @@ namespace eval punk::mix::commandset::module {
#for now the user has the option to override any templates and remove %moduletemplate% if it is a security/privacy concern
#Don't put literal %x% in the code for the commandset::module itself - to stop them being seen by layout scanner as replacable tokens
set tagnames [list moduletemplate $moduletemplate project $projectname pkg $modulename year $year license $opt_license version $infile_version]
set tagnames [list moduletemplate $moduletemplate project $projectname pkg $modulename year $year license $opt_license authors $opt_authors version $infile_version]
set strmap [list]
foreach {tag val} $tagnames {
lappend strmap %$tag% $val

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

@ -109,7 +109,26 @@ namespace eval punk::mix::commandset::project {
}
namespace eval argdoc {
set layout_dict [punk::cap::call_handler punk.templates get_itemdict_projectlayouts]
variable LAYOUTNAMES [dict keys $layout_dict]
}
punk::args::define {
@id -id ::punk::mix::commandset::project::new
@cmd -name "punk::mix::commandset::project::new" -help\
""
@leaders -min 1 -max 1
project -type string -help\
"Project name or path.
If just a name is given ... (todo)"
@opts
-type -default plain
-empty -default 0 -type boolean
-force -default 0 -type boolean
-update -default 0 -type boolean
-confirm -default 1 -type boolean
-layout -default "punk.project" -choices {${$::punk::mix::commandset::project::argdoc::LAYOUTNAMES}}
}
proc new {newprojectpath_or_name args} {
#*** !doctools
@ -300,7 +319,17 @@ namespace eval punk::mix::commandset::project {
}
}
} elseif {$project_dir_exists && $opt_update} {
puts stderr "mix new WARNING: -update 1 was supplied. Will copy layout $layout_path using -update option to add missing items"
set warnmsg "mix new WARNING: -update 1 was supplied. Will copy layout $layout_path using -update option to add missing items"
if {$opt_confirm} {
puts stderr $warnmsg
set msg "Do you want to proceed to possibly overwrite some existing files in $projectdir? Y|N"
set answer [util::askuser $msg]
if {[string tolower $answer] ne "y"} {
puts stderr "mix new aborting due to user response '$answer' (required Y|y to proceed) use -confirm 0 to avoid prompt."
return
}
}
puts stderr $warnmsg
}
set fossil_repo_file ""
@ -366,28 +395,40 @@ namespace eval punk::mix::commandset::project {
#default antiglob_dir_core will stop .fossil* from being updated - which is generally desirable as these are likely to be customized
if {$opt_force} {
puts stdout "copying layout files - with force applied - overwrite all-targets"
set resultdict [punkcheck::install $layout_path $projectdir -installer project.new -overwrite ALL-TARGETS -antiglob_paths $antipaths -antiglob_dir $antiglob_dir]
set resultdict [punkcheck::install $layout_path $projectdir -installer project.new -createempty 1 -overwrite ALL-TARGETS -antiglob_paths $antipaths -antiglob_dir $antiglob_dir]
} else {
puts stdout "copying layout files - (if source file changed)"
set resultdict [punkcheck::install $layout_path $projectdir -installer project.new -overwrite installedsourcechanged-targets -antiglob_paths $antipaths -antiglob_dir $antiglob_dir]
set resultdict [punkcheck::install $layout_path $projectdir -installer project.new -createempty 1 -overwrite installedsourcechanged-targets -antiglob_paths $antipaths -antiglob_dir $antiglob_dir]
}
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
if {[file exists $layout_path/src/doc]} {
puts stdout "copying layout src/doc files (if target missing)"
set resultdict [punkcheck::install $layout_path/src/doc $projectdir/src/doc -punkcheck_folder $projectdir -installer project.new -overwrite SYNCED-TARGETS]
set resultdict [punkcheck::install $layout_path/src/doc $projectdir/src/doc -createdir 1 -punkcheck_folder $projectdir -installer project.new -overwrite SYNCED-TARGETS]
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
} else {
puts stdout "no src/doc in source template - update not required"
}
#target folders .fossil-custom and .fossil-settings may not exist. use -createdir 1 to ensure existence.
#In this case we need to override the default dir antiglob - as .fossil-xxx folders need to be installed from template if missing, or if target is uncustomized.
## default_antiglob_dir_core [list "#*" "_aside" ".git" ".fossil*"]
set override_antiglob_dir_core [list #* _aside .git]
if {[file exists $layout_path/.fossil-custom]} {
puts stdout "copying layout src/.fossil-custom files (if target missing or uncustomised)"
set resultdict [punkcheck::install $layout_path/.fossil-custom $projectdir/.fossil-custom -createdir 1 -punkcheck_folder $projectdir -installer project.new -antiglob_dir_core $override_antiglob_dir_core -overwrite SYNCED-TARGETS]
set resultdict [punkcheck::install $layout_path/.fossil-custom $projectdir/.fossil-custom -createdir 1 -createempty 1 -punkcheck_folder $projectdir -installer project.new -antiglob_dir_core $override_antiglob_dir_core -overwrite SYNCED-TARGETS]
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
} else {
puts stdout "no .fossil-custom in source template - update not required"
}
if {[file exists $layout_path/.fossil-settings]} {
puts stdout "copying layout src/.fossil-settings files (if target missing or uncustomised)"
set resultdict [punkcheck::install $layout_path/.fossil-settings $projectdir/.fossil-settings -createdir 1 -punkcheck_folder $projectdir -installer project.new -antiglob_dir_core $override_antiglob_dir_core -overwrite SYNCED-TARGETS]
set resultdict [punkcheck::install $layout_path/.fossil-settings $projectdir/.fossil-settings -createdir 1 -createempty 1 -punkcheck_folder $projectdir -installer project.new -antiglob_dir_core $override_antiglob_dir_core -overwrite SYNCED-TARGETS]
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
} else {
puts stdout "no .fossil-settings in source template - update not required"
}
#scan all files in template
#
@ -395,30 +436,19 @@ namespace eval punk::mix::commandset::project {
set templatefiles [punk::mix::commandset::layout::lib::layout_scan_for_template_files $opt_layout]
set stripprefix [file normalize $layout_path]
#set tagmap [list [lib::template_tag project] $projectname]
#todo - get from somewhere
set alltag_substitutions [list project $projectname]
set tagmap [list [lib::template_tag project] $projectname]
if {[llength $templatefiles]} {
puts stdout "Filling template file placeholders with the following tag map:"
foreach {placeholder value} $alltag_substitutions {
foreach {placeholder value} $tagmap {
puts stdout " $placeholder -> $value"
}
}
foreach templatefullpath_and_tags $templatefiles {
lassign $templatefullpath_and_tags templatefullpath tags_present
foreach templatefullpath $templatefiles {
set templatetail [punk::repo::path_strip_alreadynormalized_prefixdepth $templatefullpath $stripprefix]
set fpath [file join $projectdir $templatetail]
if {[file exists $fpath]} {
set fd [open $fpath r]; fconfigure $fd -translation binary; set data [read $fd]; close $fd
set tagmap [list]
dict for {t v} $alltag_substitutions {
if {$t in $tags_present} {
lappend tagmap [lib::template_tag $t] $v
}
}
set data2 [string map $tagmap $data]
if {$data2 ne $data} {
puts stdout "updated template file: $fpath"

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

@ -281,7 +281,8 @@ tcl::namespace::eval punk::nav::fs {
}
}
if {[file pathtype $a1] ne "relative"} {
if {![regexp {[*?]} $a1] && [file pathtype $a1] ne "relative"} {
#non-relative non-glob
if { ![string match //zipfs:/* $a1]} {
if {[file type $a1] eq "directory"} {
cd $a1

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

@ -26,9 +26,16 @@ tcl::namespace::eval ::punk::ns::evaluator {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::ns {
variable ns_current "::"
variable ns_current
#allow presetting
if {![info exists ::punk::ns::ns_current]} {
set ns_current ::
}
variable ns_re_cache [dict create] ;#cache regular expressions used in globmatchns
namespace export nsjoin nsprefix nstail nsparts nseval nschildren nsimport_noclobber corp
namespace path {::punk::lib ::punk::ansi} ;#list_as_lines etc
catch {
package require debug
debug define punk.ns.compile
@ -1259,7 +1266,7 @@ tcl::namespace::eval punk::ns {
} else {
set report_namespaces $matched_namespaces
}
punk::args::update_definitions
punk::args::update_definitions $report_namespaces
set nsdict_list [list]
foreach ch $report_namespaces {
@ -1371,9 +1378,9 @@ tcl::namespace::eval punk::ns {
#use aliases glob - because aliases can be present with or without leading ::
#NOTE: alias may not have matching command in the relevant namespce (renamed alias) so we can't just start with commands and check if it's an alias if we want to show all aliases
if {$weird_ns} {
set raw_aliases [nseval_ifexists $location [list ::aliases $glob]] ;#'aliases $glob' must be passed as list, not separate args to namespace eval.
set raw_aliases [nseval_ifexists $location [list ::punk::lib::aliases $glob]] ;#'aliases $glob' must be passed as list, not separate args to namespace eval.
} else {
set raw_aliases [tcl::namespace::eval $location [list ::aliases $glob]] ;#'aliases $glob' must be passed as list, not separate args to namespace eval.
set raw_aliases [tcl::namespace::eval $location [list ::punk::lib::aliases $glob]] ;#'aliases $glob' must be passed as list, not separate args to namespace eval.
}
#set raw_aliases [nseval $location [list ::aliases $glob]] ;#'aliases $glob' must be passed as list, not separate args to namespace eval.
set aliases [list]
@ -1620,6 +1627,7 @@ tcl::namespace::eval punk::ns {
if {$has_punkargs} {
#set id [string trimleft $fq :]
set id $fq
punk::args::update_definitions [list [namespace qualifiers $id]]
if {[::punk::args::id_exists $id]} {
lappend usageinfo $c
} else {
@ -1969,7 +1977,8 @@ tcl::namespace::eval punk::ns {
#todo - -cache or -refresh to configure whether we introspect ensembles/objects each time?
# - as this is interactive generally introspection should be ok at the top level
# but if we need to go down multiple levels of subcommands generating/testing prefixes - could be an issue ??
punk::args::define -dynamic 0 {
punk::args::define {
@dynamic
@id -id ::punk::ns::arginfo
@cmd -name punk::ns::arginfo -help\
"Show usage info for a command.
@ -1995,7 +2004,7 @@ tcl::namespace::eval punk::ns {
-return -type string -default table -choices {string table tableobject}
} {${[punk::args::resolved_def -type @opts ::punk::args::arg_error -scheme]}} {
} {${[punk::args::resolved_def -types opts ::punk::args::arg_error -scheme]}} {
-- -type none -help\
"End of options marker
@ -2008,7 +2017,7 @@ tcl::namespace::eval punk::ns {
Multiple subcommands can be supplied if ensembles are further nested"
}
proc arginfo {args} {
lassign [dict values [punk::args::get_by_id ::punk::ns::arginfo $args]] leaders opts values received
lassign [dict values [punk::args::parse $args withid ::punk::ns::arginfo]] leaders opts values received
#review - setting this afterwards is an architecture smell - we should be able to override the default in the dynamic part
#todo - enable retrieving by id just the record_opts part - so we can treat as a dict directly, as well as easily apply it as a different flag name.
@ -2018,7 +2027,6 @@ tcl::namespace::eval punk::ns {
set querycommand [dict get $values commandpath]
set queryargs [dict get $values subcommand]
punk::args::update_definitions ;#ensure any packages that register PUNKARGS have been loaded
#todo - similar to corp? review corp resolution process
@ -2087,6 +2095,16 @@ tcl::namespace::eval punk::ns {
}
}
#check for a direct match first
if {[info commands ::punk::args::id_exists] ne ""} {
if {![llength $queryargs]} {
punk::args::update_definitions [list [namespace qualifiers $origin]]
if {[punk::args::id_exists $origin]} {
return [uplevel 1 [list punk::args::usage {*}$opts $origin]]
}
}
}
#ns::cmdtype only detects alias type on 8.7+?
set initial_cmdtype [punk::ns::cmdtype $origin]
switch -- $initial_cmdtype {
@ -2137,31 +2155,40 @@ tcl::namespace::eval punk::ns {
set id $origin
if {[info commands ::punk::args::id_exists] ne ""} {
#cycle through longest first checking for id matching ::cmd ?subcmd..?
#REVIEW - this doesn't cater for prefix callable subcommands!
#check longest first checking for id matching ::cmd ?subcmd..?
#REVIEW - this doesn't cater for prefix callable subcommands
set argcopy $queryargs
while {[llength $argcopy]} {
if {[punk::args::id_exists [list $id {*}$argcopy]]} {
return [uplevel 1 [list punk::args::usage {*}$opts [list $id {*}$argcopy]]]
if {[llength $queryargs]} {
punk::args::update_definitions [list [namespace qualifiers $id]]
if {[punk::args::id_exists [list $id {*}$queryargs]]} {
return [uplevel 1 [list punk::args::usage {*}$opts [list $id {*}$queryargs]]]
}
lpop argcopy
}
#while {[llength $argcopy]} {
# if {[punk::args::id_exists [list $id {*}$argcopy]]} {
# return [uplevel 1 [list punk::args::usage {*}$opts [list $id {*}$argcopy]]]
# }
# lpop argcopy
#}
#didn't find any exact matches
#traverse from other direction taking prefixes into account
punk::args::update_definitions [list [namespace qualifiers $id]]
if {[punk::args::id_exists $id]} {
#cycle forward through leading values
set def [punk::args::get_def $id]
set spec [punk::args::get_spec $id]
if {[llength $queryargs]} {
set nextqueryargs [list] ;#build a list of prefix-resolved queryargs
set queryargs_untested $queryargs
foreach q $queryargs {
if {[llength [dict get $def LEADER_NAMES]]} {
set subitems [dict get $def LEADER_NAMES]
if {[llength [dict get $spec LEADER_NAMES]]} {
set subitems [dict get $spec LEADER_NAMES]
if {[llength $subitems]} {
set next [lindex $subitems 0]
set arginfo [dict get $def ARG_INFO $next]
set arginfo [dict get $spec ARG_INFO $next]
set allchoices [list]
set choices [punk::args::system::Dict_getdef $arginfo -choices {}]
@ -2184,16 +2211,22 @@ tcl::namespace::eval punk::ns {
#we have our first difference - recurse with new query args
#set numvals [expr {[llength $queryargs]+1}]
#return [ punk::ns::arginfo {*}[lrange $args 0 end-$numvals] $querycommand {*}$nextqueryargs {*}$queryargs_untested]
#puts "===> testing arginfo {*}$opts $querycommand {*}$nextqueryargs {*}$queryargs_untested"
return [ punk::ns::arginfo {*}$opts $querycommand {*}$nextqueryargs {*}$queryargs_untested]
}
#check if subcommands so far have a custom args def
set currentid [list $querycommand {*}$nextqueryargs]
#set currentid [list $querycommand {*}$nextqueryargs]
set currentid [list $id {*}$nextqueryargs]
if {[punk::args::id_exists $currentid]} {
set def [punk::args::get_def $currentid
set spec [punk::args::get_spec $currentid]
} else {
#We can get no further with custom defs
#It is possible we have a documented lower level subcommand but missing the intermediate
#e.g if ::trace remove command was specified and is documented - it will be found above
#but if ::trace remove is not documented and the query is "::trace remove com"
#There is no way to determine com is a prefix as we don't have the intermediate documented -choice info available.
#that's probably ok.
break
}
}
@ -2307,7 +2340,7 @@ tcl::namespace::eval punk::ns {
set implementations [::info object call $origin $c1]
#result documented as list of 4 element lists
#set callinfo [lindex $implementations 0]
set def ""
set oodef ""
foreach impl $implementations {
lassign $impl generaltype mname location methodtype
switch -- $generaltype {
@ -2323,7 +2356,7 @@ tcl::namespace::eval punk::ns {
return [uplevel 1 [list punk::args::usage {*}$opts $idcustom]]
}
}
set def [::info object definition $origin $c1]
set oodef [::info object definition $origin $c1]
} else {
#set id "[string trimleft $location :] $c1" ;# "<class> <method>"
set idcustom "$location $c1"
@ -2332,7 +2365,7 @@ tcl::namespace::eval punk::ns {
return [uplevel 1 [list punk::args::usage {*}$opts $idcustom]]
}
}
set def [::info class definition $location $c1]
set oodef [::info class definition $location $c1]
}
break
}
@ -2342,10 +2375,9 @@ tcl::namespace::eval punk::ns {
}
}
}
if {$def ne ""} {
#assert - if we pre
if {$oodef ne ""} {
set autoid "(autodef)$location $c1"
set arglist [lindex $def 0]
set arglist [lindex $oodef 0]
set argdef [punk::lib::tstr -return string {
@id -id "${$autoid}"
@cmd -name "${$location} ${$c1}" -help\
@ -2368,7 +2400,7 @@ tcl::namespace::eval punk::ns {
append argdef \n "[lindex $a 0] -default {[lindex $a 1]} -optional 1"
}
default {
error "punk::ns::arginfo unexpected argument signature '$arglist'\ndef:$def\nimplementaions:$implementations"
error "punk::ns::arginfo unexpected oo argument signature '$arglist'\noodef:$oodef\nimplementations:$implementations"
}
}
incr i
@ -2427,7 +2459,7 @@ tcl::namespace::eval punk::ns {
@id -id ${$idauto}
@cmd -name "Object: ${$origin}" -help\
"Instance of class: ${$class} (info autogenerated)"
@values -min 1
@leaders -min 1
}]
append argdef \n $vline
punk::args::define $argdef
@ -2542,7 +2574,7 @@ tcl::namespace::eval punk::ns {
@cmd -help\
"(autogenerated)
ensemble: ${$origin}"
@values -min 1
@leaders -min 1
}]
append argdef \n $vline
punk::args::define $argdef
@ -2977,44 +3009,58 @@ tcl::namespace::eval punk::ns {
If not supplied, caller's namespace is used."
-prefix -optional 1 -help\
"string prefix for command names in target namespace"
@values -min 1 -max 1
sourcepattern -type string -optional 0 -help\
"Glob pattern for source namespace.
@values -min 1 -max -1
sourcepattern -type string -optional 0 -multiple 1 -help\
"Glob pattern(s) for exported commands in source namespace(s).
Globbing only active in the tail segment.
e.g ::mynamespace::*"
e.g ::mynamespace::a* ::mynamespace::j*"
}
proc nsimport_noclobber {args} {
lassign [dict values [punk::args::get_by_id ::punk::ns::nsimport_noclobber $args]] leaders opts values received
set sourcepattern [dict get $values sourcepattern]
set sourcepatterns [dict get $values sourcepattern]
set source_ns [tcl::namespace::qualifiers $sourcepattern]
if {![tcl::namespace::exists $source_ns]} {
error "nsimport_noclobber error namespace $source_ns not found"
}
set nscaller [uplevel 1 {namespace current}]
if {![dict exists $received -targetnamespace]} {
set target_ns $nscaller
} else {
set target_ns [dict get $opts -targetnamespace]
if {![string match ::* $target_ns]} {
set target_ns [punk::nsjoin $nscaller $target_ns]
set target_ns [punk::ns::nsjoin $nscaller $target_ns]
}
}
set all_imported [list]
set nstemp ::punk::ns::temp_import
foreach pat $sourcepatterns {
set source_ns [tcl::namespace::qualifiers $pat]
if {![tcl::namespace::exists $source_ns]} {
error "nsimport_noclobber error namespace $source_ns not found"
}
set a_export_patterns [tcl::namespace::eval $source_ns {namespace export}]
set a_commands [info commands $sourcepattern]
set a_commands [info commands $pat]
#puts "-->commands:'$a_commands'"
set a_tails [lmap v $a_commands {tcl::namespace::tail $v}]
set a_exported_tails [list]
foreach epattern $a_export_patterns {
set matches [lsearch -all -inline $a_tails $epattern]
foreach m $matches {
#we will be using namespace import <pattern> one by one on commands.
#we must protect glob chars that may exist in the actual command names.
#e.g nsimport_noclobber ::punk::ansi::a?
# will import a+ and a?
#but nsimport_noclobber {::punk::ansi::a\?}
# must import only a?
set m [string map {\\ \\\\ ? \\? * \\* \[ \\[ \] \\]} $m]
if {$m ni $a_exported_tails} {
lappend a_exported_tails $m
}
}
}
set nstemp ::punk::ns::temp_import
if {[tcl::dict:::exists $received -prefix]} {
#import via temporary/intermediate namespace
set pfx [dict get $opts -prefix]
set imported_commands [list]
if {[namespace exists $nstemp]} {
@ -3022,39 +3068,41 @@ tcl::namespace::eval punk::ns {
}
namespace eval $nstemp {}
foreach e $a_exported_tails {
set imported [tcl::namespace::eval $nstemp [string map [list <func> $e <a> $source_ns <pfx> $pfx <tgtns> $target_ns] {
set imported [apply {{tgtns func srcns pfx tmpns} {
set cmd ""
if {![catch {namespace import <a>::<func>}]} {
if {![catch {::tcl::namespace::eval $tmpns [list ::namespace import ${srcns}::$func]}]} {
#renaming will fail if target already exists
#renaming a command into another namespace still results in a command with 'info cmdtype' = 'import'
if {![catch {rename <func> [punk::ns::nsjoin <tgtns> <pfx><func>]}]} {
set cmd <pfx><func>
if {![catch {::tcl::namespace::eval $tmpns [list ::rename $func [::punk::ns::nsjoin $tgtns $pfx$func]]}]} {
set cmd $pfx$func
}
}
set cmd
}]]
} } $target_ns $e $source_ns $pfx $nstemp]
if {$imported ne ""} {
lappend imported_commands $imported
}
}
namespace delete $nstemp
return $imported_commands
}
} else {
#no prefix - direct import
set imported_commands [list]
foreach e $a_exported_tails {
set imported [tcl::namespace::eval $target_ns [string map [list <func> $e <a> $source_ns] {
set imported [apply {{tgtns func srcns} {
set cmd ""
if {![catch {namespace import <a>::<func>}]} {
set cmd <func>
if {![catch {tcl::namespace::eval $tgtns [list namespace import ${srcns}::$func]}]} {
set cmd $func
}
set cmd
}]]
if {[string length $imported]} {
} } $target_ns $e $source_ns]
if {$imported ne ""} {
lappend imported_commands $imported
}
}
return $imported_commands
}
lappend all_imported {*}$imported_commands
}
return $all_imported
}
#todo - use ns::nsimport_noclobber instead ?
@ -3092,7 +3140,23 @@ tcl::namespace::eval punk::ns {
interp alias {} corp {} punk::ns::corp
interp alias {} i {} punk::ns::arginfo
#An example of using punk::args in a pipeline
punk::args::define {
@id -id ::i+
@cmd -name "i+" -help\
"Display command help side by side"
@values
cmds -multiple 1 -help\
"Command names for which to show help info"
}
interp alias {} i+ {}\
.=args> punk::args::get_by_id ::i+ |argd>\
.=>2 dict get values cmds |cmds>\
.=cmds>2 lmap c {i -return tableobject {*}$c} |objs>\
.=objs>2 lmap t {$t configure -show_vseps 0 -show_edge 1} |>\
.=objs>2 lmap t {$t print} |tables>\
.=objs>2 lmap t {$t destroy} |>\
.=tables>* textblock::join -- <args|
}

149
src/bootsupport/modules/punk/packagepreference-0.1.0.tm

@ -101,13 +101,20 @@ package require commandstack
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::packagepreference {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
#variable xyz
variable PUNKARGS
#*** !doctools
#[subsection {Namespace punk::packagepreference}]
#[para] Core API functions for punk::packagepreference
#[list_begin definitions]
lappend PUNKARGS [list {
@id -id ::punk::packagepreference::install
@cmd -name ::punk::packagepreference::install -help\
"Install override for ::package builtin - for 'require' subcommand only."
@values -min 0 -max 0
}]
proc uninstall {} {
#*** !doctools
#[call [fun uninstall]]
@ -115,6 +122,13 @@ tcl::namespace::eval punk::packagepreference {
commandstack::remove_rename {::package punk::packagepreference}
}
lappend PUNKARGS [list {
@id -id ::punk::packagepreference::install
@cmd -name ::punk::packagepreference::install -help\
"Install override for ::package builtin - for 'require' subcommand only."
@values -min 0 -max 0
}]
proc install {} {
#*** !doctools
#[call [fun install]]
@ -179,32 +193,37 @@ tcl::namespace::eval punk::packagepreference {
if {!$is_exact && [llength $vwant] <= 1 } {
#required version unspecified - or specified singularly
set available_versions [$COMMANDSTACKNEXT_ORIGINAL versions $pkg]
if {[llength $available_versions] > 1} {
# ---------------------------------------------------------------
#An attempt to detect dll/so loaded and try to load same version
set pkgloadedinfo [lsearch -inline -index 1 [info loaded] $pkg]
if {![llength $pkgloadedinfo]} {
if {[regexp {[A-Z]} $pkg]} {
set pkgloadedinfo [lsearch -inline -index 1 [info loaded] [string tolower $pkg]]
if {![llength $pkgloadedinfo]} {
set pkgloadedinfo [lsearch -inline -index 1 [info loaded] [string totitle $pkg]]
}
}
}
#dll/so files are often named with version numbers that don't contain dots or a version number at all
#e.g sqlite3400.dll Thread288.dll
set pkgloadedinfo [lsearch -nocase -inline -index 1 [info loaded] $pkg]
if {[llength $pkgloadedinfo]} {
puts stderr "--> pkg not present but shared object seems to be loaded: $pkgloadedinfo"
puts stderr "--> pkg not already 'provided' but shared object seems to be loaded: $pkgloadedinfo - and multiple versions available"
lassign $pkgloadedinfo path name
set lcpath [string tolower $path]
set obj [file tail $lcpath]
if {[string match tcl9* $obj]} {
set obj [string range $obj 4 end]
} elseif {[string match lib* $obj]} {
set obj [string range $obj 3 end]
}
set pkginfo [file rootname $obj]
#e.g Thread2.8.8
if {[regexp {^([a-zA-Z\-]+)(.*)} $pkginfo _match lname lversion]} {
if {[string tolower $lname] eq [string tolower $pkg]} {
#first attempt to find a match for our loaded sharedlib path in a *simple* package ifneeded statement.
set lcpath_to_version [dict create]
foreach av $available_versions {
set scr [package ifneeded $pkg $av]
#ifneeded script not always a valid tcl list
if {![catch {llength $scr} scrlen]} {
if {$scrlen == 3 && [lindex $scr 0] eq "load" && [string match -nocase [lindex $scr 2] $pkg]} {
dict set lcpath_to_version [string tolower [lindex $scr 1]] $av
}
}
}
if {[dict exists $lcpath_to_version $lcpath]} {
set lversion [dict get $lcpath_to_version $lcpath]
} else {
#fallback to a best effort guess based on the path
set lversion [::punk::packagepreference::system::slibpath_guess_pkgversion $path $pkg]
}
if {$lversion ne ""} {
#name matches pkg
#hack for known dll version mismatch
if {[string tolower $pkg] eq "thread" && $lversion eq "30b3"} {
@ -240,9 +259,11 @@ tcl::namespace::eval punk::packagepreference {
}]
if {[dict get $stackrecord implementation] ne ""} {
set impl [dict get $stackrecord implementation] ;#use hardcoded name rather than slower (but more flexible) commandstack::get_next_command
puts stdout "punk::packagepreference renamed ::package to $impl"
#puts stdout "punk::packagepreference renamed ::package to $impl"
return 1
} else {
puts stderr "punk::packagepreference failed to rename ::package"
return 0
}
#puts stdout [info body ::package]
}
@ -297,14 +318,94 @@ tcl::namespace::eval punk::packagepreference::lib {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Internal]
#tcl::namespace::eval punk::packagepreference::system {
tcl::namespace::eval punk::packagepreference::system {
#*** !doctools
#[subsection {Namespace punk::packagepreference::system}]
#[para] Internal functions that are not part of the API
variable PUNKARGS
lappend PUNKARGS [list {
@id -id ::punk::packagepreference::system::slibpath_guess_pkgversion
@cmd -name punk::packagepreference::system::slibpath_guess_pkgversion -help\
"Assistance function to determine pkg version from the information
obtained from [info loaded]. This is used to try to avoid loading a different
version of a binary package in another thread/interp when the package isn't
present in the interp, but [info loaded] indicates the binary is already loaded.
The more general/robust way to avoid this is to ensure ::auto_path and
tcl::tm::list are the same in each interp/thread.
This call should only be used as a fallback in case a binary package has a more
complex ifneeded script. If the ifneeded script for a binary package is a
straightforward 'load <path_to_binary> <pkgname>' - then that information
should be used to determine the version by matching <path_to_binary>
rather than this one.
Takes a path to a shared lib (.so/.dll), and the name of its providing
package, and return the version of the package if possible to determine
from the path.
The filename portion of the lib is often missing a version number or has
a version number that has been shortened (e.g dots removed).
The filename itself is first checked for a version number - but the number
is ignored if it doesn't contain any dots.
(prefix is checked to match with $pkgname, with a possible additional prefix
of lib or tcl<int>)
Often (even usually) the parent or grandparent folder will be named as
per the package name with a proper version. If so we can return it,
otherwise return empty string.
The parent/grandparent matching will be done by looking for a case
insensitive match of the prefix to $pkgname.
"
@values -min 1
libpath -help "Full path to shared library (.so,.dll etc)"
pkgname -help ""
}]
proc slibpath_guess_pkgversion {libpath pkgname} {
set root [file rootname [file tail $libpath]]
set namelen [string length $pkgname]
regexp {^(tcl(?:[0-9])+){0,1}(.*)} $root _match tclxx root ;#regexp will match anything - but only truncate leading tclXX..
set testv ""
if {[string match -nocase $pkgname* $root]} {
set testv [string range $root $namelen end]
} elseif {[string match -nocase lib$pkgname* $root]} {
set testv [string range $root $namelen+3 end]
}
if {[string first . $testv] > 0} {
if {![catch [list package vcompare $testv $testv]]} {
#testv has an inner dot and is understood by tcl as a valid version number
return $testv
}
}
#no valid dotted version found directly on dll or so filename
set parent [file dirname $libpath] ;#parent folder is often some differentiator for platform or featureset (e.g win-x64)
set grandparent [file dirname $parent]
foreach path [list $parent $grandparent] {
set segment [file tail $path]
if {$segment eq "bin"} {
continue
}
set testv ""
if {[string match -nocase $pkgname* $segment]} {
set testv [string range $segment $namelen end]
} elseif {[string match -nocase critcl_$pkgname* $segment]} {
set testv [string range $segment $namelen+7 end]
}
#we don't look for dot in parent/grandparent version - a bare integer here after the <pkgname> will be taken to be the version
if {![catch [list package vcompare $testv $testv]]} {
return $testv
}
}
#review - sometimes path and lib are named only for major.minor but package provides major.minor.subversion
#using returned val to attempt to package require -exact major.minor will fail to load major.minor.subversion
return ""
}
}
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::packagepreference ::punk::packagepreference::system
}
#}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide punk::packagepreference [tcl::namespace::eval punk::packagepreference {

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

@ -651,9 +651,14 @@ namespace eval punk::path {
-call-depth-internal -default 0 -type integer
-antiglob_paths -default {} -help\
"list of path patterns to exclude
may include * and ** path segments e.g /usr/**"
may include * and ** path segments e.g
/usr/** (exlude subfolders based at /usr but not
files within /usr itself)
**/_aside (exlude files where _aside is last segment)
**/_aside/* (exclude folders one below an _aside folder)
**/_aside/** (exclude all folders with _aside as a segment)"
@values -min 0 -max -1 -optional 1 -type string
tailglobs -multiple 1 -help\
tailglobs -default * -multiple 1 -help\
"Patterns to match against filename portion (last segment) of each file path
within the directory tree being searched."
}
@ -671,29 +676,29 @@ namespace eval punk::path {
#[para] list of path patterns to exclude - may include * and ** path segments e.g /usr/**
#[para]no natsorting - so order is dependent on filesystem
set argd [punk::args::get_by_id ::punk::path::treefilenames $args]
set argd [punk::args::parse $args withid ::punk::path::treefilenames]
lassign [dict values $argd] leaders opts values received
set tailglobs [dict values $values]
set tailglobs [dict get $values tailglobs]
# -- --- --- --- --- --- ---
set opt_antiglob_paths [dict get $opts -antiglob_paths]
set CALLDEPTH [dict get $opts -call-depth-internal]
# -- --- --- --- --- --- ---
# -- --- --- --- --- --- ---
set files [list]
if {$CALLDEPTH == 0} {
#set opts [dict merge $opts [list -directory $opt_dir]]
if {![dict exists $received -directory]} {
set opt_dir [pwd]
} else {
set opt_dir [dict get $opts -directory]
}
# -- --- --- --- --- --- ---
set files [list]
if {$CALLDEPTH == 0} {
if {![file isdirectory $opt_dir]} {
return [list]
}
set opts [dict merge $opts [list -directory $opt_dir]]
if {![llength $tailglobs]} {
lappend tailglobs *
}
} else {
#assume/require to exist in any recursive call
set opt_dir [dict get $opts -directory]
}
set skip 0

853
src/bootsupport/modules/punk/pipe-1.0.tm

@ -0,0 +1,853 @@
# -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm
#
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# (C) 2025
#
# @@ Meta Begin
# Application punk::pipe 1.0
# Meta platform tcl
# Meta license MIT
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[manpage_begin shellspy_module_punk::pipe 0 1.0]
#[copyright "2025"]
#[titledesc {Module API}] [comment {-- Name section and table of contents description --}]
#[moddesc {-}] [comment {-- Description at end of page heading --}]
#[require punk::pipe]
#[keywords module]
#[description]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Overview]
#[para] overview of punk::pipe
#[subsection Concepts]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection dependencies]
#[para] packages used by punk::pipe
#[list_begin itemized]
package require Tcl 8.6-
#*** !doctools
#[item] [package {Tcl 8.6}]
# #package require frobz
# #*** !doctools
# #[item] [package {frobz}]
#*** !doctools
#[list_end]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section API]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# oo::class namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#tcl::namespace::eval punk::pipe::class {
#*** !doctools
#[subsection {Namespace punk::pipe::class}]
#[para] class definitions
#if {[tcl::info::commands [tcl::namespace::current]::interface_sample1] eq ""} {
#*** !doctools
#[list_begin enumerated]
# oo::class create interface_sample1 {
# #*** !doctools
# #[enum] CLASS [class interface_sample1]
# #[list_begin definitions]
# method test {arg1} {
# #*** !doctools
# #[call class::interface_sample1 [method test] [arg arg1]]
# #[para] test method
# puts "test: $arg1"
# }
# #*** !doctools
# #[list_end] [comment {-- end definitions interface_sample1}]
# }
#*** !doctools
#[list_end] [comment {--- end class enumeration ---}]
#}
#}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::pipe {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Base namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection {Namespace punk::pipe}]
#[para] Core API functions for punk::pipe
#[list_begin definitions]
#proc sample1 {p1 n args} {
# #*** !doctools
# #[call [fun sample1] [arg p1] [arg n] [opt {option value...}]]
# #[para]Description of sample1
# #[para] Arguments:
# # [list_begin arguments]
# # [arg_def tring p1] A description of string argument p1.
# # [arg_def integer n] A description of integer argument n.
# # [list_end]
# return "ok"
#}
#https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
#
#we can't provide a float comparison suitable for every situation,
#but we should pick something reasonable, keep it stable, and document it.
proc float_almost_equal {a b} {
package require math::constants
set diff [expr {abs($a - $b)}]
if {$diff <= $::math::constants::eps} {
return 1
}
set A [expr {abs($a)}]
set B [expr {abs($b)}]
set largest [expr {($B > $A) ? $B : $A}]
return [expr {$diff <= $largest * $::math::constants::eps}]
}
#debatable whether boolean_almost_equal is more surprising than helpful.
#values from a calculation that are extremely close to zero but aren't false could also be surprising - especially if they compare equal numerically
#perhaps a fuzzy-boolean is a step too far for a default - but it's inline with float-comparison for pattern-matching.
#alternatively - use an even more complex classifier? (^&~) ?
proc boolean_almost_equal {a b} {
if {[string is double -strict $a]} {
if {[float_almost_equal $a 0]} {
set a 0
}
}
if {[string is double -strict $b]} {
if {[float_almost_equal $b 0]} {
set b 0
}
}
#must handle true,no etc.
expr {($a && 1) == ($b && 1)}
}
#boolean could be tr, true, y, ye, yes,Yes, 1 , 0 etc.
proc boolean_equal {a b} {
#equivalenttly xnor: expr {!(($a && 1) ^ ($b && 1))} ;# less clear and no discernable timing benefit.
expr {($a && 1) == ($b && 1)}
}
proc val [list [list v [lreplace x 0 0]]] {return $v}
#*** !doctools
#[list_end] [comment {--- end definitions namespace punk::pipe ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Secondary API namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::pipe::lib {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
tcl::namespace::path [tcl::namespace::parent]
#*** !doctools
#[subsection {Namespace punk::pipe::lib}]
#[para] Secondary functions that are part of the API
#[list_begin definitions]
#proc utility1 {p1 args} {
# #*** !doctools
# #[call lib::[fun utility1] [arg p1] [opt {?option value...?}]]
# #[para]Description of utility1
# return 1
#}
#map rhs to names suitable to use in pipemcd proc name (whitespace mapping)
# (for .= and = pipecmds)
proc pipecmd_namemapping {rhs} {
#used to build a command from a pattern which could contain :: - we need to map it to keep it a single command in the desired namespace.
#glob chars will prevent us using simple test {[info commands $cmd] eq ""} to test existence
#we could use the test {$cmd in [info commands]} - but mapping away globchars is more robust, allowing the simpler test
#set rhs [string trim $rhs];#ignore all leading & trailing whitespace
set rhs [string trimleft $rhs]
#---
#REVIEW!
#set rhs [regsub -all {\s{1,}} $rhs {<sp>}] ;#collapse all internal whitespace to a single <sp> token
#This stops us matching {/@**@x x} vs {/@**@x x}
#---
set rhs [tcl::string::map {: <c> ? <q> * <star> [ <lb> ] <rb> \\ <bsl> {"} <dq> " " <sp>} $rhs]
#review - we don't expect other command-incompatible chars such as colon?
return $rhs
}
# relatively slow on even small sized scripts
#proc arg_is_script_shaped2 {arg} {
# set re {^(\s|;|\n)$}
# set chars [split $arg ""]
# if {[lsearch -regex $chars $re] >=0} {
# return 1
# } else {
# return 0
# }
#}
#exclude quoted whitespace
proc arg_is_script_shaped {arg} {
if {[tcl::string::first \n $arg] >= 0} {
return 1
} elseif {[tcl::string::first ";" $arg] >= 0} {
return 1
} elseif {[tcl::string::first " " $arg] >= 0 || [tcl::string::first \t $arg] >= 0} {
lassign [_rhs_tail_split $arg] _ part2 ;#will have part2 if unquoted whitespace found
return [expr {$part2 ne ""}]
} else {
return 0
}
}
#split top level of patterns only.
proc _split_patterns_memoized {varspecs} {
set name_mapped [pipecmd_namemapping $varspecs]
set cmdname ::punk::pipecmds::split_patterns::_$name_mapped
if {[info commands $cmdname] ne ""} {
return [$cmdname]
}
set result [_split_patterns $varspecs]
proc $cmdname {} [list return $result]
#debug.punk.pipe.compile {proc $cmdname} 4
return $result
}
#note - empty data after trailing , is ignored. (comma as very last character)
# - fix by documentation only. double up trailing comma e.g <pattern>,, if desired to return pattern match plus all at end!
#todo - move to punk::pipe
proc _split_patterns {varspecs} {
set varlist [list]
# @ @@ - list and dict functions
# / level separator
# # list count, ## dict size
# % string functions
# ! not
set var_terminals [list "@" "/" "#" "%" "!" ">" "<"] ;# (> required for insertionspecs at rhs of = & .= )
#right bracket ) also ends a var - but is different depending on whether var is array or basic. For array - it forms part of the varname
#except when prefixed directly by pin classifier ^
set protect_terminals [list "^"] ;# e.g sequence ^#
#also - an atom usually doesn't need the / as a terminal - because it can't match a missing element unless it's empty string
#ie the one usecase is '/n to match either empty string or missing item at position n. For this one usecase - we miss the capability to atom match paths/urls .. '/usr/local/et'
set in_brackets 0 ;#count depth
set in_atom 0
set token ""
set end_var_posn -1 ;#first var_terminal encountered within each comma delimited section
set token_index 0 ;#index of terminal char within each token
set indq 0
set inbraces 0
set inesc 0 ;#whether last char was backslash (see also punk::escv)
set prevc ""
set char_index 0
#if {[string index $varspecs end] eq ","} {
# set varspecs [string range $varspecs 0 end-1]
#}
set charcount 0
foreach c [split $varspecs ""] {
incr charcount
if {$indq} {
if {$inesc} {
#puts stderr "inesc adding '$c'"
append token \\$c
} else {
if {$c eq {"}} {
set indq 0
} else {
append token $c
}
}
} elseif {$inbraces} {
if {$inesc} {
append token \\$c
} else {
if {$c eq "\}"} {
incr inbraces -1
if {$inbraces} {
append token $c
}
} elseif {$c eq "\{"} {
incr inbraces
if {$inbraces} {
append token $c
}
} else {
append token $c
}
}
} elseif {$in_atom} {
#ignore dquotes/brackets in atoms - pass through
append token $c
#set nextc [lindex $chars $char_index+1]
if {$c eq "'"} {
set in_atom 0
}
} elseif {$in_brackets > 0} {
append token $c
if {$c eq ")"} {
incr in_brackets -1
}
} else {
if {$c eq {"}} {
if {!$inesc} {
set indq 1
} else {
append token $c
}
} elseif {$c eq "\{"} {
if {!$inesc} {
set inbraces 1
} else {
append token $c
}
} elseif {$c eq ","} {
#set var $token
#set spec ""
#if {$end_var_posn > 0} {
# #tcl scan with %s will not handle whitespace as desired. Be explicit using string range instead.
# #lassign [scan $token %${end_var_posn}s%s] var spec
# set var [string range $token 0 $end_var_posn-1]
# set spec [string range $token $end_var_posn end] ;#key section includes the terminal char which ended the var and starts the spec
#} else {
# if {$end_var_posn == 0} {
# set var ""
# set spec $token
# }
#}
#lappend varlist [list [string trim $var] [string trim $spec]]
#set token ""
#set token_index -1 ;#reduce by 1 because , not included in next token
#set end_var_posn -1
} else {
append token $c
switch -exact -- $c {
' {
set in_atom 1
}
( {
incr in_brackets
}
default {
if {$end_var_posn == -1 && (($c in $var_terminals) && ($prevc ni $protect_terminals))} {
set end_var_posn $token_index
}
}
}
}
if {$c eq ","} {
set var $token
set spec ""
if {$end_var_posn > 0} {
#tcl scan with %s will not handle whitespace as desired. Be explicit using string range instead.
#lassign [scan $token %${end_var_posn}s%s] var spec
set var [string range $token 0 $end_var_posn-1]
set spec [string range $token $end_var_posn end] ;#key section includes the terminal char which ended the var and starts the spec
} else {
if {$end_var_posn == 0} {
set var ""
set spec $token
}
}
lappend varlist [list [string trim $var] $spec]
set token ""
set token_index -1
set end_var_posn -1
}
}
if {$charcount == [string length $varspecs]} {
if {!($indq || $inbraces || $in_atom || $in_brackets)} {
if {$c ne ","} {
set var $token
set spec ""
if {$end_var_posn > 0} {
#tcl scan with %s will not handle whitespace as desired. Be explicit using string range instead.
#lassign [scan $token %${end_var_posn}s%s] var spec
set var [string range $token 0 $end_var_posn-1]
set spec [string range $token $end_var_posn end] ;#key section includes the terminal char which ended the var and starts the spec
} else {
if {$end_var_posn == 0} {
set var ""
set spec $token
}
}
lappend varlist [list [string trim $var] $spec]
set token ""
set token_index -1
set end_var_posn -1
}
}
}
set prevc $c
if {$c eq "\\"} {
#review
if {$inesc} {
set inesc 0
} else {
set token [string range $token 0 end-1]
set inesc 1
}
} else {
set inesc 0
}
incr token_index
incr char_index
}
#if {[string length $token]} {
# #lappend varlist [splitstrposn $token $end_var_posn]
# set var $token
# set spec ""
# if {$end_var_posn > 0} {
# #lassign [scan $token %${end_var_posn}s%s] var spec
# set var [string range $token 0 $end_var_posn-1]
# set spec [string range $token $end_var_posn end] ;#key section includes the terminal char which ended the var and starts the spec
# } else {
# if {$end_var_posn == 0} {
# set var ""
# set spec $token
# }
# }
# #lappend varlist [list [string trim $var] [string trim $spec]]
# #spec needs to be able to match whitespace too
# lappend varlist [list [string trim $var] $spec]
#}
return $varlist
}
#todo - consider whether we can use < for insertion/iteration combinations
# =a<,b< iterate once through
# =a><,b>< cartesian product
# =a<>,b<> ??? zip ?
#
# ie = {a b c} |> .=< inspect
# would call inspect 3 times, once for each argument
# .= list {a b c} {x y z} |a/0,b/1> .=a><,b>< list
# would produce list of cartesian pairs?
#
proc _split_equalsrhs {insertionpattern} {
#map the insertionpattern so we can use faster globless info command search
set name_mapped [pipecmd_namemapping $insertionpattern]
set cmdname ::punk::pipecmds::split_rhs::_$name_mapped
if {[info commands $cmdname] ne ""} {
return [$cmdname]
}
set lst_var_indexposition [_split_patterns_memoized $insertionpattern]
set i 0
set return_triples [list]
foreach v_pos $lst_var_indexposition {
lassign $v_pos v index_and_position
#e.g varname@@data/ok>0 varname/1/0>end
#ensure only one ">" is detected
if {![string length $index_and_position]} {
set indexspec ""
set positionspec ""
} else {
set chars [split $index_and_position ""]
set posns [lsearch -all $chars ">"]
if {[llength $posns] > 1} {
error "pipesyntax error in segment insertion pattern '$insertionpattern' -v '$v' multiple '>' characters. Pattern not understood." "_split_equalsrhs $insertionpattern" [list pipesyntax insertionpattern_invalid]
}
if {![llength $posns]} {
set indexspec $index_and_position
set positionspec ""
} else {
set splitposn [lindex $posns 0]
set indexspec [string range $index_and_position 0 $splitposn-1]
set positionspec [string range $index_and_position $splitposn+1 end]
}
}
#review -
if {($positionspec in [list "*" "/*" "@*" "/" "@"]) || ($v eq "*" && $positionspec eq "")} {
set star ""
if {$v eq "*"} {
set v ""
set star "*"
}
if {[string index $positionspec end] eq "*"} {
set star "*"
}
#it is always possible to insert at end of list regardless of current length - so /end* and @end* are equivalent
#as are /end and @end
#lset lst_var_indexposition $i [list $v "/end$star"]
set triple [list $v $indexspec "/end$star"]
} else {
if {$positionspec eq ""} {
#e.g just =varname
#lset lst_var_indexposition $i [list $v "/end"]
set triple [list $v $indexspec "/end"]
#error "pipesyntax error in segment insertionpattern '$insertionpattern' - v '$v' missing position spec e.g /0"
} else {
if {[string index $indexspec 0] ni [list "" "/" "@"]} {
error "pipesyntax error in segment insertionpattern '$insertionpattern' - v '$v' bad index spec '$indexspec'" "_split_equalsrhs $insertionpattern" [list pipesyntax insertionpattern_invalid]
}
set triple [list $v $indexspec $positionspec]
}
}
lappend return_triples $triple
incr i
}
proc $cmdname {} [list return $return_triples]
return $return_triples
}
proc _rhs_tail_split {fullrhs} {
set inq 0; set indq 0
set equalsrhs ""
set i 0
foreach ch [split $fullrhs ""] {
if {$inq} {
append equalsrhs $ch
if {$ch eq {'}} {
set inq 0
}
} elseif {$indq} {
append equalsrhs $ch
if {$ch eq {"}} {
set indq 0
}
} else {
switch -- $ch {
{'} {
set inq 1
}
{"} {
set indq 1
}
" " {
#whitespace outside of quoting
break
}
0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - a - b - c - d - e - f - g - h - i - j - k - l - m - n - o - p - q - r - s - t - u - v - w - x - y - z {}
default {
#\t not a literal for switch jumpTable bytecompile - review - can we do it without for example string mapping to <t> (and without a literal binary tab in source file)?
#we can't (reliably?) put \t as one of our switch keys
#
if {$ch eq "\t"} {
break
}
}
}
append equalsrhs $ch
}
incr i
}
set tail [tcl::string::range $fullrhs $i end]
return [list $equalsrhs $tail]
}
#todo - recurse into bracketed sub parts
#JMN3
#e.g @*/(x@0,y@2)
proc _var_classify {multivar} {
set cmdname ::punk::pipecmds::var_classify::_[pipecmd_namemapping $multivar]
if {[info commands $cmdname] ne ""} {
return [$cmdname]
}
#comma seems a natural choice to split varspecs,
#but also for list and dict subelement access
#/ normally indicates some sort of hierarchical separation - (e.g in filesytems)
#so / will indicate subelements e.g @0/1 for lindex $list 0 1
#set valsource_key_list [_split_var_key_at_unbracketed_comma $multivar]
set valsource_key_list [_split_patterns_memoized $multivar]
#mutually exclusive - atom/pin
#set map [list "" ' ^ &] ;#0 = default/var/not-yet-determined 1 = atom 2 = pin
#set var_class [lmap var $valsource_key_list {expr {([set m [lsearch $map [string index [lindex $var 0] 0]]] >= 0) ? [list $var $m] : [list $var 0]}}]
#0 - novar
#1 - atom '
#2 - pin ^
#3 - boolean &
#4 - integer
#5 - double
#6 - var
#7 - glob (no classifier and contains * or ?)
#8 - numeric
#9 - > (+)
#10 - < (-)
set var_names [list]
set var_class [list]
set varspecs_trimmed [list] ;#raw varspecs without pin/atom modifiers - or empty string for glob
set leading_classifiers [list "'" "&" "^" ]
set trailing_classifiers [list + -]
set possible_number_start [list - + . 0 1 2 3 4 5 6 7 8 9 > <]
foreach v_key $valsource_key_list {
lassign $v_key v key
set vname $v ;#default
set classes [list]
if {$v eq ""} {
lappend var_class [list $v_key 0]
lappend varspecs_trimmed $v_key
} else {
set lastchar [string index $v end]
switch -- $lastchar {
+ {
lappend classes 9
set vname [string range $v 0 end-1]
}
- {
lappend classes 10
set vname [string range $v 0 end-1]
}
}
set firstchar [string index $v 0]
switch -- $firstchar {
' {
lappend var_class [list $v_key 1]
#set vname [string range $v 1 end]
lappend varspecs_trimmed [list $vname $key]
}
^ {
lappend classes [list 2]
#use vname - may already have trailing +/- stripped
set vname [string range $vname 1 end]
set secondclassifier [string index $v 1]
switch -- $secondclassifier {
"&" {
#pinned boolean
lappend classes 3
set vname [string range $v 2 end]
}
"#" {
#pinned numeric comparison instead of string comparison
#e.g set x 2
# this should match: ^#x.= list 2.0
lappend classes 8
set vname [string range $vname 1 end]
}
"*" {
#pinned glob
lappend classes 7
set vname [string range $v 2 end]
}
}
#todo - check for second tag - & for pinned boolean?
#consider requiring ^# for numeric comparisons. currently no way to do a strictly string comparison on pinned variables.... default ^var really shouldn't be doing any magic.
#while we're at it.. pinned glob would be nice. ^*
#maybe even pinned scan ^% ? regex? ^/ or ^? these would be hard to have corresponding literals in the pattern mini-lang.
#These all limit the range of varnames permissible - which is no big deal.
lappend var_class [list $v_key $classes]
lappend varspecs_trimmed [list $vname $key]
}
& {
#we require boolean literals to be single-quoted so we can use cross-binding on boolean vars.
#ie &true is the variable true whereas &'true' or &'1' &'t' etc are literal booleans
#allow exception of &1 &0 to be literal booleans - because we disallow 0 & 1 as varnames in other contexts anyway - so it would be more consistent not to treat as varnames here.
lappend var_class [list $v_key 3]
set vname [string range $v 1 end]
lappend varspecs_trimmed [list $vname $key]
}
default {
if {([string first ? $v]) >=0 || ([string first * $v] >=0)} {
lappend var_class [list $v_key 7] ;#glob
#leave vname as the full glob
lappend varspecs_trimmed [list "" $key]
} else {
#scan vname not v - will either be same as v - or possibly stripped of trailing +/-
set numtestv [join [scan $vname %lld%s] ""] ;# handles octals (leading zeros), ok for use with bignums, decimal points and sci notation - fails to handle leading dot e.g .5
#leading . still need to test directly for double
if {[string is double -strict $vname] || [string is double -strict $numtestv]} {
if {[string is integer -strict $numtestv]} {
#this will pick up boolean 1 or 0 - but that's ok - they require "&" marker if boolean comparison desired
#integer test before double..
#note there is also string is wide (string is wideinteger) for larger ints..
lappend classes 4
lappend var_class [list $v_key $classes]
lappend varspecs_trimmed $v_key
} else {
#double
#sci notation 1e123 etc
#also large numbers like 1000000000 - even without decimal point - (tcl bignum)
lappend classes 5
lappend var_class [list $v_key $classes]
lappend varspecs_trimmed $v_key
}
} else {
lappend var_class [list $v_key 6] ;#var
lappend varspecs_trimmed $v_key
}
}
}
}
}
lappend var_names $vname
}
set result [list var_names $var_names var_class $var_class varspecs_trimmed $varspecs_trimmed]
proc $cmdname {} [list return $result]
#JMN
#debug.punk.pipe.compile {proc $cmdname}
return $result
}
#*** !doctools
#[list_end] [comment {--- end definitions namespace punk::pipe::lib ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Internal]
#tcl::namespace::eval punk::pipe::system {
#*** !doctools
#[subsection {Namespace punk::pipe::system}]
#[para] Internal functions that are not part of the API
#}
# == === === === === === === === === === === === === === ===
# Sample 'about' function with punk::args documentation
# == === === === === === === === === === === === === === ===
tcl::namespace::eval punk::pipe {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
variable PUNKARGS
variable PUNKARGS_aliases
lappend PUNKARGS [list {
@id -id "(package)punk::pipe"
@package -name "punk::pipe" -help\
"Package
Description"
}]
namespace eval argdoc {
#namespace for custom argument documentation
proc package_name {} {
return punk::pipe
}
proc about_topics {} {
#info commands results are returned in an arbitrary order (like array keys)
set topic_funs [info commands [namespace current]::get_topic_*]
set about_topics [list]
foreach f $topic_funs {
set tail [namespace tail $f]
lappend about_topics [string range $tail [string length get_topic_] end]
}
return $about_topics
}
proc default_topics {} {return [list Description outline *]}
# -------------------------------------------------------------
# get_topic_ functions add more to auto-include in about topics
# -------------------------------------------------------------
proc get_topic_Description {} {
punk::args::lib::tstr [string trim {
punk pipeline features
} \n]
}
proc get_topic_License {} {
return "MIT"
}
proc get_topic_Version {} {
return $::punk::pipe::version
}
proc get_topic_Contributors {} {
set authors {{Julian Noble <julian@precisium.com.au>}}
set contributors ""
foreach a $authors {
append contributors $a \n
}
if {[string index $contributors end] eq "\n"} {
set contributors [string range $contributors 0 end-1]
}
return $contributors
}
proc get_topic_outline {} {
punk::args::lib::tstr -return string {
todo..
}
}
# -------------------------------------------------------------
}
# we re-use the argument definition from punk::args::standard_about and override some items
set overrides [dict create]
dict set overrides @id -id "::punk::pipe::about"
dict set overrides @cmd -name "punk::pipe::about"
dict set overrides @cmd -help [string trim [punk::args::lib::tstr {
About punk::pipe
}] \n]
dict set overrides topic -choices [list {*}[punk::pipe::argdoc::about_topics] *]
dict set overrides topic -choicerestricted 1
dict set overrides topic -default [punk::pipe::argdoc::default_topics] ;#if -default is present 'topic' will always appear in parsed 'values' dict
set newdef [punk::args::resolved_def -antiglobs -package_about_namespace -override $overrides ::punk::args::package::standard_about *]
lappend PUNKARGS [list $newdef]
proc about {args} {
package require punk::args
#standard_about accepts additional choices for topic - but we need to normalize any abbreviations to full topic name before passing on
set argd [punk::args::parse $args withid ::punk::pipe::about]
lassign [dict values $argd] _leaders opts values _received
punk::args::package::standard_about -package_about_namespace ::punk::pipe::argdoc {*}$opts {*}[dict get $values topic]
}
}
# end of sample 'about' function
# == === === === === === === === === === === === === === ===
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::pipe
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide punk::pipe [tcl::namespace::eval punk::pipe {
variable pkg punk::pipe
variable version
set version 1.0
}]
return
#*** !doctools
#[manpage_end]

22
src/bootsupport/modules/punk/repl/codethread-0.1.1.tm

@ -114,6 +114,10 @@ tcl::namespace::eval punk::repl::codethread {
variable output_stdout ""
variable output_stderr ""
#review/test
catch {package require punk::ns}
catch {package rquire punk::repl}
#variable xyz
#*** !doctools
@ -191,9 +195,14 @@ tcl::namespace::eval punk::repl::codethread {
#shennanigans to keep compiled script around after call.
#otherwise when $script goes out of scope - internal rep of vars set in script changes.
#The shimmering may be no big deal(?) - but debug/analysis using tcl::unsupported::representation becomes impossible.
interp eval code [list ::punk::lib::set_clone ::codeinterp::clonescript $script] ;#like objclone
#interp eval code [list ::punk::lib::set_clone ::codeinterp::clonescript $script] ;#like objclone
interp eval code [list ::codeinterp::set_clone ::codeinterp::clonescript $script] ;#like objclone
interp eval code {lappend ::codeinterp::run_command_cache $::codeinterp::clonescript}
interp eval code {
lappend ::codeinterp::run_command_cache $::codeinterp::clonescript
#lappend ::codeinterp::run_command_cache $::codeinterp::clonescript
if {[llength $::codeinterp::run_command_cache] > 2000} {
set ::codeinterp::run_command_cache [lrange $::codeinterp::run_command_cache 1750 end][unset ::codeinterp::run_command_cache]
}
@ -205,10 +214,19 @@ tcl::namespace::eval punk::repl::codethread {
package require punk::ns
punk::ns::nseval_ifexists $::punk::ns::ns_current $::codeinterp::clonescript
} else {
if {![namespace exists $::punk::ns::ns_current]} {
namespace eval $::punk::ns::ns_current {
puts stderr "Created namespace: $::punk::ns::ns_current"
}
}
tcl::namespace::inscope $::punk::ns::ns_current $::codeinterp::clonescript
}
}
} result]
#temp test for subshell experimentation
#if {$status == 1} {
# puts stderr "--codethread::runscript error--------\n$::errorInfo"
#}
flush stdout

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

@ -107,14 +107,16 @@ namespace eval punk::repo {
}
#lappend PUNKARGS [list -dynamic 1 {
#lappend PUNKARGS [list {
# @dynamic
# @id -id ::punk::repo::fossil_proxy
# @cmd -name fossil -help "fossil executable
# "
# @argdisplay -header "fossil help" -body {${[runout -n fossil help]}}
# } ""]
lappend PUNKARGS [list -dynamic 1 {
lappend PUNKARGS [list {
@dynamic
@id -id ::punk::repo::fossil_proxy
@cmd -name fossil -help "fossil executable"
${[punk::repo::get_fossil_usage]}
@ -123,20 +125,24 @@ namespace eval punk::repo {
#experiment
lappend PUNKARGS [list -dynamic 1 {
lappend PUNKARGS [list {
@dynamic
@id -id "::punk::repo::fossil_proxy diff"
@cmd -name "fossil diff" -help "fossil diff
"
@argdisplay -header "fossil help diff" -body {${[runout -n fossil help diff]}}
} ""]
lappend PUNKARGS [list -dynamic 1 {
lappend PUNKARGS [list {
#todo - remove this comment - testing dynamic directive
@dynamic
@id -id "::punk::repo::fossil_proxy add"
@cmd -name "fossil add" -help "fossil add
"
@argdisplay -header "fossil help add" -body {${[runout -n fossil help add]}}
} ""]
#TODO
#lappend PUNKARGS [list -dynamic 1 {
#lappend PUNKARGS [list {
# @dynamic
# @id -glob 1 -id "::punk::repo::fossil_proxy *" -aliases {fs}
# @cmd -name "fossil add" -help "fossil add
# "
@ -1699,12 +1705,10 @@ namespace eval punk::repo::lib {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
if {![info exists ::punk::args::register::NAMESPACES]} {
namespace eval ::punk::args::register {
set ::punk::args::register::NAMESPACES [list] ;#use fully qualified so 8.6 doesn't find existing var in global namespace
}
}
#use fully qualified so 8.6 doesn't find existing var in global namespace
lappend ::punk::args::register::NAMESPACES ::punk::repo
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++

34
src/bootsupport/modules/punk/zip-0.1.1.tm

@ -194,6 +194,12 @@ tcl::namespace::eval punk::zip {
Defaults to the value of base. If empty string
is given to -resultrelative the paths returned
are effectively absolute paths."
-emptydirs -default 0 -type boolean -help\
"Whether to include directory trees in the result which had no
matches for the given fileglobs.
Intermediate dirs are always returned if there is a match with
fileglobs further down even if -emptdirs is 0.
"
-excludes -default "" -help "list of glob expressions to match against files and exclude"
-subpath -default "" -help\
"May contain glob chars for folder elements"
@ -205,6 +211,7 @@ tcl::namespace::eval punk::zip {
set fileglobs [dict get $argd values fileglobs]
set subpath [dict get $argd opts -subpath]
set excludes [dict get $argd opts -excludes]
set emptydirs [dict get $argd opts -emptydirs]
set received [dict get $argd received]
@ -242,13 +249,32 @@ tcl::namespace::eval punk::zip {
if {!$excluded} {lappend result [file join $prefix $file]}
}
foreach dir [glob -nocomplain -tails -types d -directory $base -- [file join $subpath *]] {
set subdir_entries [walk -subpath $dir -excludes $excludes $base {*}$fileglobs]
if {[llength $subdir_entries]>0} {
set submatches [walk -subpath $dir -emptydirs $emptydirs -excludes $excludes $base {*}$fileglobs]
set subdir_entries [list]
set thisdir_match [list]
set has_file 0
foreach sd $submatches {
set fullpath [file join $prefix $sd] ;#file join destroys trailing slash
if {[string index $sd end] eq "/"} {
lappend subdir_entries $fullpath/
} else {
set has_file 1
lappend subdir_entries $fullpath
}
}
if {$emptydirs} {
set thisdir_match [list "[file join $prefix $dir]/"]
} else {
if {$has_file} {
set thisdir_match [list "[file join $prefix $dir]/"]
} else {
set subdir_entries [list]
}
}
#NOTE: trailing slash required for entries to be recognised as 'file type' = "directory"
#This is true for 2024 Tcl9 mounted zipfs at least. zip utilities such as 7zip seem(icon correct) to recognize dirs with or without trailing slash
#Although there are attributes on some systems to specify if entry is a directory - it appears trailing slash should always be used for folder names.
set result [list {*}$result "[file join $prefix $dir]/" {*}$subdir_entries]
}
set result [list {*}$result {*}$thisdir_match {*}$subdir_entries]
}
return $result
}

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

@ -1170,6 +1170,7 @@ namespace eval punkcheck {
}
#skip writing punkcheck during checksum/timestamp checks
#todo - punk::args - fetch from punkcheck::install (with overrides)
proc install_tm_files {srcdir basedir args} {
set defaults [list\
-glob *.tm\
@ -1209,13 +1210,71 @@ namespace eval punkcheck {
return [lindex $args end]
}
}
lappend PUNKARGS [list {
@id -id ::punkcheck::install
@cmd -name ::punkcheck::install -help\
"Unidirectional file transfer to possibly non-empty target folder."
@leaders -min 2 -max 2
srcdir -type directory
tgtdir -type directory
-call-depth-internal -type integer -default 0 -help "(internal recursion tracker)"
-subdirlist -type list -default "" -help "(primarily internal - length generally matching -call-depth-internal)"
-max_depth -type integer -default 1000 -help\
"Deepest subdirectory - use -1 for no limit."
-createdir -type boolean -default 0 -help\
"Whether to create the folder at tgtdir.
Any required subdirectories are created regardless of this setting."
-createempty -type boolean -default 0 -help\
"Whether to create folders at target that had no matches for our glob"
-glob -type string -default "*" -help\
"Pattern matching for source file(s) to copy. Can be glob based or exact match."
-antiglob_file_core -default {${[::punkcheck::default_antiglob_file_core]}}
-antiglob_file -default ""
-antiglob_dir_core -default {${[::punkcheck::default_antiglob_dir_core]}}
-antiglob_dir -default ""
-antiglob_paths -default {}
-overwrite -default no-targets\
-choices {no-targets newer-targets older-targets all-targets installedsourcechanged-targets synced-targets}\
-choicecolumns 1\
-choicelabels {
no-targets "only copy files that are missing at the target"
newer-targets "copy files with older source timestamp over newer
target timestamp and those missing at the target
(a form of 'restore' operation)"
older-targets "copy files with newer source timestamp over older
target timestamp and those missing at the target"
all-targets "copy regardless of timestamp at target"
installedsourcechanged-targets "copy if the target doesn't exist or the source changed"
synced-targets "copy if the target doesn't exist or the source changed
and the target cksum is the same as the last INSTALL-RECORD -targets_cksums entry"
}
-source_checksum -default comparestore -choicecolumns 3 -choices {compare store comparestore false true}\
-choicelabels {
true "same as comparestore"
}
-punkcheck_folder -default target -choices {target source project} -choicerestricted 0 -help\
"The location of the .punkcheck file to track installations and checksums.
The default value 'target' is generally recommended.
Can also be an absolute path to a folder."
-punkcheck_records -default "" -help\
"Empty string or a parsed TDL records structure.
e.g
{tag FILEINFO -<opt> <val>... body {
{tag INSTALL-RECORD -<opt> <val>... body {<sublist>}}
...
}...
}"
-installer -default "punkcheck::install" -help\
"A user nominated string that is stored in the .punkcheck file
This might be the name of a script or installation process."
}]
## unidirectional file transfer to possibly non empty folder
#default of -overwrite no-targets will only copy files that are missing at the target
# -overwrite newer-targets will copy files with older source timestamp over newer target timestamp and those missing at the target (a form of 'restore' operation)
# -overwrite older-targets will copy files with newer source timestamp over older target timestamp and those missing at the target
# -overwrite all-targets will copy regardless of timestamp at target
# -overwrite installedsourcechanged-targets will copy if the target doesn't exist or the source changed
# -overwrite synced-targets will copy if the target doesn't exist or the source changed and the target cksum is the same as the last INSTALL-RECORD targets_cksums entry
# -overwrite synced-targets will copy if the target doesn't exist or the source changed and the target cksum is the same as the last INSTALL-RECORD -targets_cksums entry
# review - timestamps unreliable
# - what about slightly mismatched system clocks and mounted filesystems? caller responsibility to verify first?
# if timestamp exactly equal - should we check content-hash? This is presumably only likely to occur deliberately(maliciously?)
@ -1243,6 +1302,7 @@ namespace eval punkcheck {
-max_depth 1000\
-subdirlist {}\
-createdir 0\
-createempty 0\
-glob *\
-antiglob_file_core "\uFFFF"\
-antiglob_file "" \
@ -1271,13 +1331,14 @@ namespace eval punkcheck {
#(as opposed to a more 'standard' mechanism of adjusting srcdir & tgtdir as we move down the tree)
#It comes from build_modules_from_source_to_base where we need to keep track of position relative to our targetdir starting point to handle submodules e.g pkg::something::mypkg-0.1.tm
#It could have been handled with some other parameter such as -depth, but this -subdirlist mechanism, whilst perhaps not beautiful, is straightforward enough
#and may be less error prone than doing slightly more opaue path manipulations at each recursion level to determine where we started
#and may be less error prone than doing slightly more opaque path manipulations at each recursion level to determine where we started
#For consistency - we'll use the same mechanism in various recursive directory walking procedures such as this one.
set CALLDEPTH [dict get $opts -call-depth-internal] ;#added for extra debug/sanity checking - clearer test for initial function call ie CALLDPEPTH = 0
set CALLDEPTH [dict get $opts -call-depth-internal] ;#added for extra debug/sanity checking - clearer test for initial function call ie CALLDEPTH = 0
set max_depth [dict get $opts -max_depth] ;# -1 for no limit
set subdirlist [dict get $opts -subdirlist] ;# generally should be same length as CALLDEPTH - but user could prefill
set fileglob [dict get $opts -glob]
set createdir [dict get $opts -createdir] ;#defaults to zero to help avoid mistakes with initial target dir - required target subdirs are created regardless of this setting
set opt_createempty [dict get $opts -createempty]
if {$CALLDEPTH == 0} {
#expensive to normalize but we need to do it at least once
@ -1285,6 +1346,13 @@ namespace eval punkcheck {
set tgtdir [file normalize $tgtdir]
if {$createdir} {
file mkdir $tgtdir
} else {
if {![file exists $tgtdir]} {
error "punkcheck::install base target dir:'$tgtdir' doesn't exist (srcdir:$srcdir tgtdir:$tgtdir args:'$args')"
}
}
if {([file type $srcdir] ni [list directory]) || ([file type $tgtdir] ni [list directory])} {
error "punkcheck::install requires source and target dirs to be of type 'directory' type current source: [file type $srcdir] type current target: [file type $tgtdir]"
}
#now the values we build from these will be properly cased
}
@ -1450,13 +1518,7 @@ namespace eval punkcheck {
if {![file exists $current_source_dir]} {
error "copy_files_from_source_to_target current source dir:'$current_source_dir' doesn't exist (srcdir:$srcdir tgtdir:$tgtdir args:'$args')"
}
if {![file exists $current_target_dir]} {
error "copy_files_from_source_to_target current target dir:'$current_target_dir' doesn't exist (srcdir:$srcdir tgtdir:$tgtdir args:'$args')"
}
if {([file type $current_source_dir] ni [list directory]) || ([file type $current_target_dir] ni [list directory])} {
error "copy_files_from_source_to_target requires source and target dirs to be of type 'directory' type current source: [file type $current_source_dir] type current target: [file type $current_target_dir]"
error "punkcheck::install current source dir:'$current_source_dir' doesn't exist (srcdir:$srcdir tgtdir:$tgtdir args:'$args')"
}
set files_copied [list]
@ -1501,6 +1563,12 @@ namespace eval punkcheck {
# }
#}
if {[llength $match_list]} {
#example - target dir has a file where there is a directory at the source
if {[file exists $current_target_dir] && ([file type $current_target_dir] ni [list directory])} {
error "punkcheck::install target subfolder $current_target_dir exists but is not of type 'directory'. Type current target folder: [file type $current_target_dir]"
}
}
#proc get_relativecksum_from_base_and_fullpath {base fullpath args}
@ -1579,10 +1647,12 @@ namespace eval punkcheck {
set is_skip 0
if {$overwrite_what eq "all-targets"} {
file mkdir $current_target_dir
file copy -force $current_source_dir/$m $current_target_dir
lappend files_copied $current_source_dir/$m
} else {
if {![file exists $current_target_dir/$m]} {
file mkdir $current_target_dir
file copy $current_source_dir/$m $current_target_dir
set new_tgt_cksum_info [punk::mix::base::lib::cksum_path $current_target_dir/$m]
lappend files_copied $current_source_dir/$m
@ -1592,6 +1662,7 @@ namespace eval punkcheck {
installedsourcechanged-targets {
if {[llength $changed]} {
#An unrecorded installation is considered a source change (from unknown/unrecorded source to recorded)
file mkdir $current_target_dir
file copy -force $current_source_dir/$m $current_target_dir
set new_tgt_cksum_info [punk::mix::base::lib::cksum_path $current_target_dir/$m]
lappend files_copied $current_source_dir/$m
@ -1619,6 +1690,7 @@ namespace eval punkcheck {
set target_cksum_compare "norecord"
}
if {$is_target_unmodified_since_install} {
file mkdir $current_target_dir
file copy -force $current_source_dir/$m $current_target_dir
set new_tgt_cksum_info [punk::mix::base::lib::cksum_path $current_target_dir/$m]
lappend files_copied $current_source_dir/$m
@ -1642,6 +1714,12 @@ namespace eval punkcheck {
}
}
}
#target dir was created as necessary if files matched above
#now ensure target dir exists if -createempty true
if {$opt_createempty && ![file exists $current_target_dir]} {
file mkdir $current_target_dir
}
set ts_now [clock microseconds]
@ -1724,10 +1802,9 @@ namespace eval punkcheck {
continue
}
if {![file exists $current_target_dir/$d]} {
file mkdir $current_target_dir/$d
}
#if {![file exists $current_target_dir/$d]} {
# file mkdir $current_target_dir/$d
#}
set sub_opts_1 [list\
@ -2096,8 +2173,10 @@ namespace eval punkcheck {
namespace eval ::punk::args::register {
#use fully qualified so 8.6 doesn't find existing var in global namespace
lappend ::punk::args::register::NAMESPACES ::punkcheck
}

2
src/bootsupport/modules/punkcheck/cli-0.1.0.tm

@ -64,6 +64,8 @@ namespace eval punkcheck::cli {
#vfs can mask mounted files - so we can't just use 'file type' or glob with -type f
##set files [glob -nocomplain -dir $fullpath -type f *]
package require punk::nav::fs
#TODO - get all files in tree!!!
set folderinfo [punk::nav::fs::dirfiles_dict $fullpath]
set files [concat [dict get $folderinfo files] [dict get $folderinfo underlayfiles]]
}

17
src/bootsupport/modules/shellfilter-0.1.9.tm

@ -751,6 +751,12 @@ namespace eval shellfilter::chan {
} else {
#REVIEW - this holding a buffer without emitting as we go is ugly.
# - we may do better to detect and retain the opener, then use that opener to avoid false splits within the sequence.
# - we'd then need to detect the appropriate close to restart splitting and codestacking
# - we may still need to retain and append the data to the opener (in some cases?) - which is a slight memory issue - but at least we would emit everything immediately.
#puts "-->esc but no detect"
#no complete ansi codes - but at least one esc is present
if {[string last \x1b $buf] == [llength $buf]-1} {
@ -1118,8 +1124,8 @@ namespace eval shellfilter::stack {
proc status {{pipename *} args} {
variable pipelines
set pipecount [dict size $pipelines]
set tableprefix "$pipecount pipelines active\n"
set t [textblock::class::table new $tableprefix]
set tabletitle "$pipecount pipelines active"
set t [textblock::class::table new $tabletitle]
$t add_column -headers [list channel-ident]
$t add_column -headers [list device-info localchan]
$t configure_column 1 -header_colspans {3}
@ -1402,7 +1408,8 @@ namespace eval shellfilter::stack {
}
dict set pipelines $pipename stack $stack
}
show_pipeline $pipename -note "after_remove $remove_id"
#JMNJMN 2025 review!
#show_pipeline $pipename -note "after_remove $remove_id"
return 1
}
@ -1607,7 +1614,9 @@ namespace eval shellfilter::stack {
#puts stdout "=="
#puts stdout "==>stack: $stack"
#puts stdout "=="
show_pipeline $pipename -note "after_add $transformname $args"
#JMNJMN
#show_pipeline $pipename -note "after_add $transformname $args"
return $id
}
proc show_pipeline {pipename args} {

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

@ -62,14 +62,16 @@ catch {package require patternpunk}
package require overtype
#safebase interps as at 2024-08 can't access deeper paths - even though they are below the supposed safe list.
if {[catch {
package require term::ansi::code::macros ;#required for frame if old ansi g0 used - review - make package optional?
} errM]} {
#catch this too in case stderr not available
catch {
puts stderr "textblock package failed to load term::ansi::code::macros with error: $errM"
}
}
#2025 - required term::ansi features for altg now built in to textblock
#the deeper paths issue is still a potential issue for some packages - review
#if {[catch {
# package require term::ansi::code::macros ;#required for frame if old ansi g0 used - review - make package optional?
#} errM]} {
# #catch this too in case stderr not available
# catch {
# puts stderr "textblock package failed to load term::ansi::code::macros with error: $errM"
# }
#}
package require textutil
@ -139,7 +141,8 @@ tcl::namespace::eval textblock {
# hash_algorithm -optional 1 -choices {${[::textblock::argdoc::hash_algorithm_choices]}} -help\
# "algorithm choice"
punk::args::define -dynamic 1 {
punk::args::define {
@dynamic
@id -id ::textblock::use_hash
@cmd -name "textblock::use_hash" -help\
"Hashing algorithm to use for framecache lookup.
@ -4068,12 +4071,12 @@ tcl::namespace::eval textblock {
return $frametypes
}
tcl::namespace::eval cd {
#todo - save and restore existing tcl::namespace::export in case macros::cd has default exports in future
tcl::namespace::eval ::term::ansi::code::macros::cd {tcl::namespace::export *}
tcl::namespace::import ::term::ansi::code::macros::cd::*
tcl::namespace::eval ::term::ansi::code::macros::cd {tcl::namespace::export -clear}
}
#tcl::namespace::eval cd {
# #todo - save and restore existing tcl::namespace::export in case macros::cd has default exports in future
# tcl::namespace::eval ::term::ansi::code::macros::cd {tcl::namespace::export *}
# tcl::namespace::import ::term::ansi::code::macros::cd::*
# tcl::namespace::eval ::term::ansi::code::macros::cd {tcl::namespace::export -clear}
#}
proc spantest {} {
set t [list_as_table -columns 5 -return tableobject {a b c d e aa bb cc dd ee X Y}]
$t configure_column 0 -headers [list span3 "1-span4\n2-span4 second line" span5/5 "span-all etc blah 123 hmmmmm" span2]
@ -5071,7 +5074,7 @@ tcl::namespace::eval textblock {
#only call width functions if known_samewidth - otherwise let the pad algorithm below determine it as we go
if {$known_samewidth ne "" && $known_samewidth} {
if {$known_blockwidth eq ""} {
set datawidth [textblock::widthtopline $block
set datawidth [textblock::widthtopline $block]
} else {
set datawidth $known_blockwidth
}
@ -6214,16 +6217,22 @@ tcl::namespace::eval textblock {
switch -- $f {
"altg" {
#old style ansi escape sequences with alternate graphics page G0
set hl [cd::hl]
#set hl [cd::hl]
set hl [punk::ansi::g0 q]
set hlt $hl
set hlb $hl
set vl [cd::vl]
#set vl [cd::vl]
set vl [punk::ansi::g0 x]
set vll $vl
set vlr $vl
set tlc [cd::tlc]
set trc [cd::trc]
set blc [cd::blc]
set brc [cd::brc]
#set tlc [cd::tlc]
set tlc [punk::ansi::g0 l]
#set trc [cd::trc]
set trc [punk::ansi::g0 k]
#set blc [cd::blc]
set blc [punk::ansi::g0 m]
#set brc [cd::brc]
set brc [punk::ansi::g0 j]
#horizontal and vertical bar joins
set hltj $hlt
@ -7417,7 +7426,8 @@ tcl::namespace::eval textblock {
set blc \U1fb7c ;#legacy block
set brc \U1fb7f ;#legacy block
if {[punk::console::check::has_bug_legacysymbolwidth]} {
if {(![interp issafe])} {
if {![catch {punk::console::check::has_bug_legacysymbolwidth} symbug] && $symbug} {
#rather than totally fail on some mixed layout that happens to use block2 - just degrade it - but prevent alignment problems
set sp \u00a0 ;#non breaking space (plain space may act transparent in some use cases)
set tlc $sp
@ -7425,6 +7435,7 @@ tcl::namespace::eval textblock {
set blc $sp
set brc $sp
}
}
#horizontal and vertical bar joins
set hltj $hlt
@ -7560,7 +7571,7 @@ tcl::namespace::eval textblock {
still wrap in an ugly manner. Try 'textblock::use_cache md5'
to shorten the argument display and reduce wrapping.
"
@values -min 0 -max 1
@values -min 0 -max -1
action -default {display} -choices {clear size info display} -choicelabels {
clear "Clear the textblock::frame_cache dictionary."
} -help "Perform an action on the frame cache."
@ -7569,6 +7580,8 @@ tcl::namespace::eval textblock {
set argd [punk::args::get_by_id ::textblock::frame_cache $args]
set action [dict get $argd values action]
variable frame_cache
set all_values_dict [dict get $argd values]
set action_values [lrange [dict values $all_values_dict] 1 end]
switch -- $action {
clear {
set size [dict size $frame_cache]
@ -7589,8 +7602,65 @@ tcl::namespace::eval textblock {
error "frame_cache -action '$action' not understood. Valid actions: clear size info display"
}
}
if {[llength $action_values]} {
return [frame_cache_display -pretty [dict get $argd opts -pretty] {*}$action_values]
} else {
return [frame_cache_display -pretty [dict get $argd opts -pretty]]
}
}
punk::args::define {
@dynamic
@id -id ::textblock::frame_cache_display
@opts
${[::punk::args::resolved_def -types opts ::textblock::frame_cache -pretty]}
@values -min 0 -max 2
startindex -default "" -type indexexpression -help\
"If both startindex and endindex are missing/empty, it is treated as
startindex 0 endindex end. (ie displays all records)
If only startindex has a value - the frame_cache record at that
index will be displayed"
endindex -default "" -type indexexpression
}
proc frame_cache_display {args} {
set argd [punk::args::get_by_id ::textblock::frame_cache_display $args]
variable frame_cache
lassign [dict values [dict get $argd values]] startidx endidx
set limit ""
if {$startidx ne ""} {
if {$endidx ne ""} {
if {$startidx eq $endidx} {
set limit "index"
} else {
set limit "range"
}
} else {
set limit "index"
}
} else {
set limit "all"
}
set display_dict {}
switch -- $limit {
all {
set display_dict $frame_cache
}
index {
set k [lindex [dict keys $frame_cache] $startidx]
if {$k ne ""} {
set display_dict [dict create $k [dict get $frame_cache $k]]
}
}
range {
set keys [lrange [dict keys $frame_cache] $startidx $endidx]
foreach k $keys {
dict set display_dict $k [dict get $frame_cache $k]
}
}
}
if {[dict get $argd opts -pretty]} {
set out [pdict -chan none frame_cache */*]
set out [pdict -chan none display_dict */*]
} else {
set out ""
if {[catch {
@ -7599,7 +7669,7 @@ tcl::namespace::eval textblock {
set termwidth 80
}
tcl::dict::for {k v} $frame_cache {
tcl::dict::for {k v} $display_dict {
lassign $v _f frame _used used
set fwidth [textblock::widthtopline $frame]
#review - are cached frames uniform width lines?
@ -7651,7 +7721,8 @@ tcl::namespace::eval textblock {
# ${[textblock::frame_samples]}
#todo punk::args alias for centre center etc?
punk::args::define -dynamic 1 {
punk::args::define {
@dynamic
@id -id ::textblock::frame
@cmd -name "textblock::frame"\
-help "Frame a block of text with a border."
@ -8208,9 +8279,11 @@ tcl::namespace::eval textblock {
}
altg {
set tbar [tcl::string::repeat $hlt $frame_inner_width]
set tbar [cd::groptim $tbar]
#set tbar [cd::groptim $tbar]
set tbar [punk::ansi::groptim $tbar]
set bbar [tcl::string::repeat $hlb $frame_inner_width]
set bbar [cd::groptim $bbar]
#set bbar [cd::groptim $bbar]
set bbar [punk::ansi::groptim $bbar]
}
default {
set tbar [tcl::string::repeat $hlt $frame_inner_width]

9
src/bootsupport/modules/tomlish-1.1.1.tm

@ -716,6 +716,7 @@ namespace eval tomlish {
set toml [::tomlish::to_toml $tomlish]
}
#TODO use huddle?
proc from_json {json} {
set jstruct [::tomlish::json_struct $json]
return [::tomlish::from_json_struct $jstruct]
@ -1080,11 +1081,13 @@ namespace eval tomlish::decode {
# For this reason, we also do absolutely no line-ending transformations based on platform.
# All line-endings are maintained as is, and even a file with mixed cr crlf line-endings will be correctly interpreted and can be 'roundtripped'
proc toml {s} {
proc toml {args} {
#*** !doctools
#[call [fun toml] [arg s]]
#[call [fun toml] [arg arg...]]
#[para] return a Tcl list of tomlish tokens
set s [join $args \n]
namespace upvar ::tomlish::parse is_parsing is_parsing
set is_parsing 1
@ -2380,7 +2383,7 @@ namespace eval tomlish::parse {
squotedkey {PUSHSPACE "itable-keyval-space" state "itable-keyval-syntax"}\
endinlinetable "POPSPACE"\
startquote "quoted-key"\
startsquote {TOSTATE "squoted-key" comment "jn-ok"}\
startsquote {TOSTATE "squoted-key" comment "jn-testing"}\
comma "itable-space"\
comment "err-state"\
eof "err-state"\

1
src/bootsupport/modules_tcl8/include_modules.config

@ -5,5 +5,6 @@
#each entry - base module
set bootsupport_modules [list\
modules_tcl8 thread\
modules_tcl8/thread/platform *\
]

BIN
src/bootsupport/modules_tcl8/win32_x86_64_tcl8-2.8.9.tm

Binary file not shown.

116
src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm vendored

@ -94,18 +94,20 @@ package require Tcl 8.6-
#}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval %pkg% {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Base namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval %pkg% {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
#variable xyz
#*** !doctools
#[subsection {Namespace %pkg%}]
#[para] Core API functions for %pkg%
#[list_begin definitions]
variable PUNKARGS
#proc sample1 {p1 n args} {
@ -167,6 +169,112 @@ tcl::namespace::eval %pkg%::lib {
#}
# == === === === === === === === === === === === === === ===
# Sample 'about' function with punk::args documentation
# == === === === === === === === === === === === === === ===
tcl::namespace::eval %pkg% {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
variable PUNKARGS
variable PUNKARGS_aliases
lappend PUNKARGS [list {
@id -id "(package)%pkg%"
@package -name "%pkg%" -help\
"Package
Description"
}]
namespace eval argdoc {
#namespace for custom argument documentation
proc package_name {} {
return %pkg%
}
proc about_topics {} {
#info commands results are returned in an arbitrary order (like array keys)
set topic_funs [info commands [namespace current]::get_topic_*]
set about_topics [list]
foreach f $topic_funs {
set tail [namespace tail $f]
lappend about_topics [string range $tail [string length get_topic_] end]
}
#Adjust this function or 'default_topics' if a different order is required
return [lsort $about_topics]
}
proc default_topics {} {return [list Description *]}
# -------------------------------------------------------------
# get_topic_ functions add more to auto-include in about topics
# -------------------------------------------------------------
proc get_topic_Description {} {
punk::args::lib::tstr [string trim {
package %pkg%
description to come..
} \n]
}
proc get_topic_License {} {
return "%license%"
}
proc get_topic_Version {} {
return "$::%pkg%::version"
}
proc get_topic_Contributors {} {
set authors {%authors%}
set contributors ""
foreach a $authors {
append contributors $a \n
}
if {[string index $contributors end] eq "\n"} {
set contributors [string range $contributors 0 end-1]
}
return $contributors
}
proc get_topic_custom-topic {} {
punk::args::lib::tstr -return string {
A custom
topic
etc
}
}
# -------------------------------------------------------------
}
# we re-use the argument definition from punk::args::standard_about and override some items
set overrides [dict create]
dict set overrides @id -id "::%pkg%::about"
dict set overrides @cmd -name "%pkg%::about"
dict set overrides @cmd -help [string trim [punk::args::lib::tstr {
About %pkg%
}] \n]
dict set overrides topic -choices [list {*}[%pkg%::argdoc::about_topics] *]
dict set overrides topic -choicerestricted 1
dict set overrides topic -default [%pkg%::argdoc::default_topics] ;#if -default is present 'topic' will always appear in parsed 'values' dict
set newdef [punk::args::resolved_def -antiglobs -package_about_namespace -override $overrides ::punk::args::package::standard_about *]
lappend PUNKARGS [list $newdef]
proc about {args} {
package require punk::args
#standard_about accepts additional choices for topic - but we need to normalize any abbreviations to full topic name before passing on
set argd [punk::args::parse $args withid ::%pkg%::about]
lassign [dict values $argd] _leaders opts values _received
punk::args::package::standard_about -package_about_namespace ::%pkg%::argdoc {*}$opts {*}[dict get $values topic]
}
}
# end of sample 'about' function
# == === === === === === === === === === === === === === ===
# -----------------------------------------------------------------------------
# register namespace(s) to have PUNKARGS,PUNKARGS_aliases variables checked
# -----------------------------------------------------------------------------
# variable PUNKARGS
# variable PUNKARGS_aliases
namespace eval ::punk::args::register {
#use fully qualified so 8.6 doesn't find existing var in global namespace
lappend ::punk::args::register::NAMESPACES ::%pkg%
}
# -----------------------------------------------------------------------------
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide %pkg% [tcl::namespace::eval %pkg% {

97
src/make.tcl

@ -18,7 +18,7 @@ namespace eval ::punkboot {
variable foldername [file tail $scriptfolder]
variable pkg_requirements [list]; variable pkg_missing [list];variable pkg_loaded [list]
variable non_help_flags [list -k]
variable help_flags [list -help --help /?]
variable help_flags [list -help --help /? -h]
variable known_commands [list project modules vfs info check shell vendorupdate bootsupport vfscommonupdate]
}
@ -180,10 +180,14 @@ set bootsupport_module_paths [list]
set bootsupport_library_paths [list]
if {[file exists [file join $startdir src bootsupport]]} {
lappend bootsupport_module_paths [file join $startdir src bootsupport modules]
lappend bootsupport_module_paths [file join $startdir src bootsupport modules_tcl$::tclmajorv]
lappend bootsupport_library_paths [file join $startdir src bootsupport lib]
lappend bootsupport_library_paths [file join $startdir src bootsupport lib_tcl$::tclmajorv]
} else {
lappend bootsupport_module_paths [file join $startdir bootsupport modules]
lappend bootsupport_module_paths [file join $startdir bootsupport modules_tcl$::tclmajorv]
lappend bootsupport_library_paths [file join $startdir bootsupport lib]
lappend bootsupport_library_paths [file join $startdir bootsupport lib_tcl$::tclmajorv]
}
set bootsupport_paths_exist 0
foreach p [list {*}$bootsupport_module_paths {*}$bootsupport_library_paths] {
@ -203,13 +207,13 @@ set sourcesupport_paths_exist 0
#(most?) Modules in src/modules etc should still be runnable directly in certain cases like this where we point to them.
if {[file tail $startdir] eq "src"} {
#todo - other src 'module' dirs..
foreach p [list $startdir/modules $startdir/modules_tcl$::tclmajorv] {
foreach p [list $startdir/modules $startdir/modules_tcl$::tclmajorv $startdir/vendormodules $startdir/vendormodules_tcl$::tclmajorv] {
if {[file exists $p]} {
lappend sourcesupport_module_paths $p
}
}
# -- -- --
foreach p [list $startdir/vendorlib $startdir/vendorlib_tcl${::tclmajorv}] {
foreach p [list $startdir/lib $startdir/lib_tcl$::tclmajorv $startdir/vendorlib $startdir/vendorlib_tcl$::tclmajorv] {
if {[file exists $p]} {
lappend sourcesupport_library_paths $p
}
@ -266,9 +270,12 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} {
package forget $pkg
}
}
#tcl::tm::add {*}$original_tm_list {*}$bootsupport_module_paths {*}$sourcesupport_module_paths
#set ::auto_path [list {*}$original_auto_path {*}$bootsupport_library_paths {*}$sourcesupport_library_paths]
tcl::tm::add {*}$bootsupport_module_paths {*}$sourcesupport_module_paths
set ::auto_path [list {*}$bootsupport_library_paths {*}$sourcesupport_library_paths]
}
puts "----> auto_path $::auto_path"
@ -1046,7 +1053,9 @@ proc ::punkboot::punkboot_gethelp {args} {
append h " - before calling this (followed by make project) - you can test using '<builtexe>(.exe) dev'" \n
append h " this will load modules from your <projectdir>/module <projectdir>/lib paths instead of from the kit/zipkit" \n \n
append h " $scriptname info" \n
append h " - show the name and base folder of the project to be built" \n
append h " - show the name and base folder of the project to be built" \n \n
append h " $scriptname check" \n
append h " - show module/library paths and any potentially problematic packages for running this script" \n
append h "" \n
if {[llength [dict get $pkg_availability missing]] || [llength [dict get $pkg_availability broken]]} {
set has_recommended 0
@ -1116,6 +1125,7 @@ if {[llength $commands_found] != 1 } {
set do_help 1
}
if {$do_help} {
puts stdout "Checking package availability..."
set ::punkboot::pkg_availability [::punkboot::check_package_availability -quiet 1 $::punkboot::bootsupport_requirements]
foreach pkg_request [dict get $::punkboot::pkg_availability loaded] {
#puts stderr "---> $pkg_request"
@ -1407,6 +1417,7 @@ if {$::punkboot::command eq "vendorupdate"} {
puts stderr "Unable to use punkcheck for vendormodules$which update. Error: $errM"
set installation_event ""
}
#todo - sync alg with bootsupport_localupdate!
foreach {relpath requested_module} $local_modules {
set requested_module [string trim $requested_module :]
set module_subpath [string map {:: /} [namespace qualifiers $requested_module]]
@ -1488,6 +1499,11 @@ if {$::punkboot::command eq "bootsupport"} {
#puts "-- [tcl::tm::list] --"
puts stdout "Updating bootsupport from local files"
proc modfile_sort {p1 p2} {
lassign [split [file rootname $p1] -] _ v1
lassign [split [file rootname $p1] -] _ v2
package vcompare $v1 $v2
}
proc bootsupport_localupdate {projectroot} {
set bootsupport_modules [list] ;#variable populated by include_modules.config file - review
set sourcefolder $projectroot/src
@ -1521,29 +1537,37 @@ if {$::punkboot::command eq "bootsupport"} {
set boot_event ""
}
foreach {relpath module} $bootsupport_modules {
set module [string trim $module :]
set module_subpath [string map [list :: /] [namespace qualifiers $module]]
foreach {relpath modulematch} $bootsupport_modules {
set modulematch [string trim $modulematch :]
set module_subpath [string map [list :: /] [namespace qualifiers $modulematch]]
set srclocation [file join $projectroot $relpath $module_subpath]
#puts stdout "$relpath $module $module_subpath $srclocation"
set pkgmatches [glob -nocomplain -dir $srclocation -tail [namespace tail $module]-*]
#lsort won't sort version numbers properly e.g with -dictionary 0.1.1 comes before 0.1
#puts stdout "$relpath $modulematch $module_subpath $srclocation"
if {[string first - $modulematch]} {
set pkgmatches [glob -nocomplain -dir $srclocation -tail -type f [namespace tail $modulematch]*.tm]
} else {
set pkgmatches [glob -nocomplain -dir $srclocation -tail -type f [namespace tail $modulematch]-*.tm]
}
if {![llength $pkgmatches]} {
puts stderr "Missing source for bootsupport module $module - not found in $srclocation"
puts stderr "Missing source for bootsupport module $modulematch - no matches in $srclocation"
continue
}
set latestfile [lindex $pkgmatches 0]
set latestver [lindex [split [file rootname $latestfile] -] 1]
foreach m $pkgmatches {
lassign [split [file rootname $m] -] _pkg ver
#puts "comparing $ver vs $latestver"
if {[package vcompare $ver $latestver] == 1} {
set latestver $ver
set latestfile $m
}
set modulematch_is_glob [regexp {[*?\[\]]} $modulematch]
if {!$modulematch_is_glob} {
#if modulematch was specified without globs - only copy latest
#lsort won't sort version numbers properly e.g with -dictionary 0.1.1 comes before 0.1b3 - use helper func
set pkgmatches [lsort -command modfile_sort $pkgmatches]
set latestfile [lindex $pkgmatches end]
#set latestver [lindex [split [file rootname $latestfile] -] 1]
set copy_files $latestfile
} else {
#globs in modulematch - may be different packages matched by glob - copy all versions of matches
#review
set copy_files $pkgmatches
}
set srcfile [file join $srclocation $latestfile]
set tgtfile [file join $targetroot $module_subpath $latestfile]
foreach cfile $copy_files {
set srcfile [file join $srclocation $cfile]
set tgtfile [file join $targetroot $module_subpath $cfile]
if {$boot_event ne ""} {
#----------
$boot_event targetset_init INSTALL $tgtfile
@ -1574,6 +1598,7 @@ if {$::punkboot::command eq "bootsupport"} {
file copy -force $srcfile $tgtfile
}
}
}
if {$boot_event ne ""} {
puts \n
$boot_event destroy
@ -1597,13 +1622,14 @@ if {$::punkboot::command eq "bootsupport"} {
if {[file exists $project_layout_base]} {
set project_layouts [glob -nocomplain -dir $project_layout_base -type d -tail *]
foreach layoutname $project_layouts {
puts stdout "Processing layout $project_layout_base/$layoutname"
#don't auto-create src/bootsupport - just update it if it exists
if {[file exists [file join $project_layout_base $layoutname/src/bootsupport]]} {
set antipaths [list\
README.md\
]
set boot_module_folders [glob -nocomplain -dir $projectroot/src/bootsupport/modules_tcl*]
lappend bootsupport_module_folders "modules"
#set boot_module_folders [list modules {*}[glob -nocomplain -dir $projectroot/src/bootsupport/modules_tcl*]]
set bootsupport_module_folders "modules"
foreach bm $bootsupport_module_folders {
if {[file exists $projectroot/src/bootsupport/$bm]} {
lassign [split $bm _] _bm tclx
@ -1617,12 +1643,33 @@ if {$::punkboot::command eq "bootsupport"} {
file mkdir $targetroot
puts stdout "BOOTSUPPORT$which layouts/$layoutname: copying from $sourcemodules to $targetroot (if source file changed)"
set resultdict [punkcheck::install $sourcemodules $targetroot -overwrite installedsourcechanged-targets -antiglob_paths $antipaths]
set resultdict [punkcheck::install $sourcemodules $targetroot\
-overwrite installedsourcechanged-targets\
-antiglob_paths $antipaths\
-installer "punkboot-bootsupport"
]
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
flush stdout
}
}
}
#make.tcl (to be boot.tcl?) is part of bootsupport
set source_bootscript [file join $projectroot src/make.tcl]
set targetroot_bootscript $project_layout_base/$layoutname/src
if {[file exists $source_bootscript]} {
puts stdout "BOOTSUPPORT layouts/$layoutname: copying from $source_bootscript to $targetroot_bootscript (if source file changed)"
set resultdict [punkcheck::install [file dirname $source_bootscript] $targetroot_bootscript\
-glob make.tcl\
-max_depth 1\
-createempty 0\
-overwrite installedsourcechanged-targets\
-installer "punkboot-bootsupport"
]
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
flush stdout
}
}
} else {
puts stderr "No layout base at $project_layout_base"

4
src/modules/argparsingtest-999999.0a1.0.tm

@ -276,7 +276,7 @@ namespace eval argparsingtest {
#punk::args is slower than argp - but comparable, and argp doesn't support solo flags
proc test1_punkargs {args} {
set argd [punk::args::get_dict {
set argd [punk::args::parse $args withdef {
@id -id ::argparsingtest::test1_punkargs
@cmd -name argtest4 -help "test of punk::args::get_dict comparative performance"
@opts -anyopts 0
@ -292,7 +292,7 @@ namespace eval argparsingtest {
-2 -default 2 -type integer
-3 -default 3 -type integer
@values
} $args]
}]
return [tcl::dict::get $argd opts]
}

2
src/modules/funcl-0.1.tm

@ -9,7 +9,7 @@ package provide funcl [namespace eval funcl {
namespace eval funcl {
#from punk
#from punk::pipe
proc arg_is_script_shaped {arg} {
if {[string first " " $arg] >= 0} {
return 1

11
src/modules/patternpunk-1.1.tm

@ -113,7 +113,7 @@ proc TCL {args} {
punk::args::define {
#Review
@id -id ">punk . poses"
@id -id "::>punk . poses"
@cmd -name ">punk . poses" -help "Show or list the poses for the Punk mascot"
-censored -default 1 -type boolean -help "Set true to include mild toilet humour poses"
-return -default table -choices {list table}
@ -323,7 +323,7 @@ _+ +_
+_+_
} \n]
>punk .. Property fossil [string trim {
>punk .. Property fossil [punk::args::lib::tstr [string trim {
..
> <
\ / v
@ -331,7 +331,8 @@ v \\_/
\/\\ v .
v_ /|\/ /
\__/
} \n]
} \n]]
>punk .. Method deck {args} {
#todo - themes?
set this @this@
@ -344,7 +345,7 @@ v_ /|\/ /
set punk $punk_colour[$this . lhs_air]$RST
package require punk::args
set standard_frame_types [textblock::frametypes]
set argd [punk::args::get_dict [tstr -return string {
set argd [punk::args::parse $args withdef [tstr -return string {
@id -id ">punk . deck"
@cmd -name "deck" -help "Punk Deck mascot"
-frame -default arc -choices "${$standard_frame_types}" -choicerestricted 0 -choiceprefix 1
@ -356,7 +357,7 @@ v_ /|\/ /
-title -default "PATTERN" -type string
-subtitle -default "PUNK" -type string
@values -max 0
}] $args]
}]]
set frame_type [dict get $argd opts -frame]
set box_map [dict get $argd opts -boxmap]
set box_limits [dict get $argd opts -boxlimits]

767
src/modules/punk-0.1.tm

File diff suppressed because it is too large Load Diff

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

@ -105,6 +105,7 @@ tcl::namespace::eval punk::aliascore {
#functions must be in export list of their source namespace
set aliases [tcl::dict::create\
val ::punk::pipe::val\
aliases ::punk::lib::aliases\
alias ::punk::lib::alias\
tstr ::punk::lib::tstr\
@ -123,12 +124,12 @@ tcl::namespace::eval punk::aliascore {
colour ::punk::console::colour\
ansi ::punk::console::ansi\
color ::punk::console::colour\
a? ::punk::console::code_a?\
A? {::punk::console::code_a? forcecolor}\
a+ ::punk::console::code_a+\
A+ {::punk::console::code_a+ forcecolour}\
a ::punk::console::code_a\
A {::punk::console::code_a forcecolour}\
a? ::punk::console::code_a?\
A? {::punk::console::code_a? forcecolor}\
smcup ::punk::console::enable_alt_screen\
rmcup ::punk::console::disable_alt_screen\
]

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

@ -584,25 +584,38 @@ tcl::namespace::eval punk::ansi {
set base $CWD
}
}
if {[info commands file] eq ""} {
#probably a safe interp
return "UNAVAILABLE"
}
return [file join $base src/testansi]
}
lappend PUNKARGS [list -dynamic 1 {
lappend PUNKARGS [list {
@dynamic
@id -id ::punk::ansi::example
@cmd -name punk::ansi::example -help "Display .ans image files in a grid that will fit in console
"
-colwidth -default 82 -help "Width of each column - default of 82 will fit a standard 80wide ansi image (when framed)
-colwidth -default 82 -help\
"Width of each column - default of 82 will fit a standard 80wide ansi image
(when framed)
You can specify a narrower width to truncate images on the right side"
-folder -default "${[punk::ansi::Get_ansifolder]}" -help "Base folder for files if relative paths are used.
Defaults to <projectbase>/src/testansi - where projectbase is determined from current directory.
-folder -default "${[punk::ansi::Get_ansifolder]}" -help\
"Base folder for files if relative paths are used.
Defaults to <projectbase>/src/testansi - where projectbase is determined
from the current directory.
"
@values -min 0 -max -1
files -default {belinda.ans bot.ans flower.ans fish.ans} -multiple true -help "List of filenames - leave empty to display 4 defaults"
files -default {belinda.ans bot.ans flower.ans fish.ans} -multiple true -help\
"List of filenames - leave empty to display 4 defaults"
} ""]
proc example {args} {
set argd [punk::args::get_by_id ::punk::ansi::example $args]
set colwidth [dict get $argd opts -colwidth]
if {[info commands file] eq ""} {
error "file command unavailable - punk::ansi::example cannot be shown"
}
set ansifolder [file normalize [dict get $argd opts -folder]]
set fnames [dict get $argd values files]
@ -617,6 +630,16 @@ tcl::namespace::eval punk::ansi {
puts stderr "Ensure ansi test files exist: $fnames"
#error "punk::ansi::example Cannot find example files"
}
set termsize [punk::console:::get_size]
set termcols [dict get $termsize columns]
set margin 4 ;#review
set freewidth [expr {$termcols-$margin}]
if {$freewidth < $colwidth} {
puts stderr "[a+ red bold]punk::ansi::example freewidth: $freewidth < colwidth: $colwidth TRUNCATING IMAGES[a]"
set colwidth $freewidth
}
set per_row [expr {$freewidth / $colwidth}]
set missingbase [a+ yellow][textblock::block [expr {$colwidth-2}] 23 ?][a] ;#assuming standard frame - subtract 2 for left/right borders
set pics [list]
foreach f $fnames {
@ -636,10 +659,6 @@ tcl::namespace::eval punk::ansi {
}
}
set termsize [punk::console:::get_size]
set margin 4
set freewidth [expr {[tcl::dict::get $termsize columns]-$margin}]
set per_row [expr {$freewidth / $colwidth}]
set rowlist [list] ;# { {<img> <img>} {<img> <img>} }
set heightlist [list] ;# { {<h> <h> } {<h> <h> } }
@ -737,22 +756,23 @@ tcl::namespace::eval punk::ansi {
#review - can terminals handle SGR codes within a PM?
#Wezterm will hide PM,SOS,APC - but not any part following an SGR code - i.e it seems to terminate hiding before the ST (apparently at the )
proc controlstring_PM {text} {
return "\x1b^${text}\033\\"
#dquotes with trailing \\ in string will confuse silly editors
return \x1b^${text}\033\\
}
proc controlstring_PM8 {text} {
return "\x9e${text}\x9c"
return \x9e${text}\x9c
}
proc controlstring_SOS {text} {
return "\x1bX${text}\033\\"
return \x1bX${text}\033\\
}
proc controlstring_SOS8 {text} {
return "\x98${text}\x9c"
return \x98${text}\x9c
}
proc controlstring_APC {text} {
return "\x1b_${text}\033\\"
return \x1b_${text}\033\\
}
proc controlstring_APC8 {text} {
return "\x9f${text}\x9c"
return \x9f${text}\x9c
}
#there is also the SGR hide code (8) which has intermittent terminal support
#This doesn't change the output length - so support is tricky to detec. (terminal checksum report?)
@ -843,10 +863,79 @@ tcl::namespace::eval punk::ansi {
return $out
}
#Wrap text in ansi codes to switch to DEC alternate graphics character set.
#todo vt52 versions
proc g0 {text} {
return \x1b(0$text\x1b(B
}
variable altg_map [dict create\
hl q\
vl x\
tlc l\
trc k\
blc m\
ltj t\
rtj u\
ttj w\
btj v\
rtj u\
fwj n\
]
proc altg_map {names} {
variable altg_map
set result [list]
foreach nm $names {
if {[dict exists $altg_map $nm]} {
lappend result [dict get $altg_map $nm]
} else {
lappend ""
}
}
return $result
}
# --------------------------------
# Taken from term::ansi::code::ctrl
# --------------------------------
#Note that SYN (\016) seems to put terminals in a state
#where alternate graphics are not processed.
#an ETB (\017) needs to be sent to get alt graphics working again.
#It isn't known what software utilises SYN/ETB within altg sequences
# (presumably to alternate between the charsets within a graphics-on/graphics-off section)
#but as modern emulators seem to react to it, we should handle it.
#REVIEW - this mapping not fully understood
#used by groptim
variable grforw
variable grback
variable _
foreach _ {
! \" # $ % & ' ( ) * + , - . /
0 1 2 3 4 5 6 7 8 9 : ; < = >
? @ A B C D E F G H I J K L M
N O P Q R S T U V W X Y Z [ ^
\\ ]
} {
lappend grforw \016$_ $_\016
lappend grback $_\017 \017$_
}
unset _
# ------------------------------
#REVIEW - see forward/backward map in term::ansi::code::macros::cd::groptim - what are they for??
proc groptim {string} {
variable grforw
variable grback
set offon \x1b(B\x1b(0 ;#)) ;#editor highlighting comment
set onoff \x1b(0\x1b(B ;#)) ;#editor highlighting comment
while {![string equal $string [set new [string map [list $offon {} $onoff {}] [string map $grback [string map $grforw $string]]]]]} {
set string $new
}
return $string
}
# --------------------------------
proc ansistrip_gx {text} {
#e.g "\033(0" - select VT100 graphics for character set G0
#e.g "\033(B" - reset
@ -854,10 +943,10 @@ tcl::namespace::eval punk::ansi {
#e.g "\033)X" - where X is any char other than 0 to reset ??
#return [convert_g0 $text]
return [tcl::string::map [list "\x1b(0" "" \x1b(B" "" "\x1b)0" "" "\x1b)X" ""] $text]
return [tcl::string::map [list \x1b(0 "" \x1b(B "" \x1b)0 "" \x1b)X ""] $text]
}
proc stripansi_gx {text} {
return [tcl::string::map [list "\x1b(0" "" \x1b(B" "" "\x1b)0" "" "\x1b)X" ""] $text]
return [tcl::string::map [list \x1b(0 "" \x1b(B "" \x1b)0 "" \x1b)X ""] $text]
}
@ -1459,7 +1548,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
tcl::dict::set opts $k $v
}
default {
error "colourmap1 unrecognised option $k. Known-options: [tcl::dict::keys $opts]
error "colourmap1 unrecognised option $k. Known-options: [tcl::dict::keys $opts]"
}
}
}
@ -2358,8 +2447,8 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
lappend PUNKARGS [list {
@id -id ::punk::ansi::sgr_cache
@cmd -name punk::ansi::sgr_cache -help\
"Convenience function to view and optionally clear the ansi character attribute cache (ansi SGR codes)
"
"Convenience function to view and optionally clear the ansi character attribute cache
(ansi SGR codes)"
-action -default "" -choices "clear" -help\
"-action clear will unset the keys in the punk::ansi::sgr_cache dict
This is called automatically when setting 'colour false' in the console"
@ -2882,6 +2971,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
set SGR_samples [dict create]
foreach k [dict keys $SGR_map] {
#indent of 1 space is important for clarity in i -return string a+ output
dict set SGR_samples $k " [punk::ansi::a+ $k]sample\x1b\[m"
}
lappend PUNKARGS [list {
@ -3264,7 +3354,55 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
return $result
}
proc ansiwrap {codes text} {
lappend PUNKARGS [list {
@id -id ::punk::ansi::ansiwrap
@cmd -name punk::ansi::ansiwrap -help\
"Wrap a string with ANSI codes from
supplied codelist(s) followed by trailing
ANSI reset.
Codes are numbers or strings as indicated
in the output of the colour information
function: punk::ansi::a?
No leading reset is applied - so when
placing resultant text, any existing
SGR codes that aren't overridden may
still take effect.
For finer control use the a+ and a
functions eg
set x \"[a+ red]text [a+ bold]etc[a]\"
"
@leaders -min 0 -max -1
codelist -multiple 1 -default {} -type list -help\
"ANSI names/ints as understood by 'a?'
(Not actual ANSI as output by a+)
These can be supplied individually or
as a list or lists"
@values -min 1 -max 1
text -type string -help\
"String to wrap with ANSI (SGR)"
}]
#proc ansiwrap {codes text} {
# return [a {*}$codes]$text[a]
#}
proc ansiwrap2 {args} {
set argd [punk::args::parse $args withid ::punk::ansi::ansiwrap]
set codelists [dict get $argd leaders codelist]
set text [dict get $argd values text]
set codes [concat {*}$codelists] ;#flatten
return [a {*}$codes]$text[a]
}
proc ansiwrap {args} {
if {[llength $args] < 1} {
#minimal args parsing - unhappy path only
punk::args::parse $args withid ::punk::ansi::ansiwrap
return
}
set text [lindex $args end]
set codelists [lrange $args 0 end-1]
set codes [concat {*}$codelists] ;#flatten
return [a {*}$codes]$text[a]
}
@ -3300,6 +3438,14 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#[call [fun reset_soft]]
return \x1b\[!p
}
proc SYN {} {
#syn seems to disable alternate graphics mode temporarily on modern terminals
return \016
}
proc ETB {} {
#This is a form of soft reset for the state where a SYN was sent - re-enabling altg processing
return \017
}
proc reset_colour {} {
#*** !doctools
#[call [fun reset_colour]]
@ -3341,6 +3487,12 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#[call [fun cursor_off]]
return "\033\[?25l"
}
proc cursor_on_vt52 {} {
return \x1be
}
proc cursor_off_vt52 {} {
return \x1bf
}
# REVIEW - osc8 replays etc for split lines? - textblock
#Hyperlinks (a.k.a. HTML-like anchors) in terminal emulators https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
@ -3387,6 +3539,24 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
}
# -- --- --- --- ---
lappend PUNKARGS [list {
@id -id ::punk::ansi::move
@cmd -name punk::ansi::move -help\
{Return an ANSI sequence to move cursor to row,col
(aka: cursor home)
Sequence is of the form:
\x1b[<row>;<col>H
(CSI row ; col H)
This sequence will not be understood by old vt52
terminals. see also vt52_move.
}
@values -min 2 -max 2
row -type integer -help\
"row number - starting at 1"
col -type integer -help\
"column number - starting at 1"
}]
proc move {row col} {
#*** !doctools
#[call [fun move] [arg row] [arg col]]
@ -3394,6 +3564,44 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#[para]aka cursor home
return \033\[${row}\;${col}H
}
#NOTE vt52 uses ESC Y line column
# where line and column are ascii codes whose values are +31
# vt52 can be entered/exited via escapes
# This means we probably need to to wrap enter/exit vt52 and keep this state - as we don't have a standard way to query for terminal type
# (vt52 supports ESC Z - but vt100 sometimes? doesn't - and querying at each output would be slow anyway, even if there was a common query :/ )
lappend PUNKARGS [list {
@id -id ::punk::ansi::vt52move
@cmd -name punk::ansi::vt52move -help\
{Return a VT52 sequence to move cursor to row,col
(aka: cursor home)
Sequence is of the form:
ESCY<rowchar><colchar>
This sequence will generally not be understood by terminals
that are not in vt52 mode (e.g DECANM unset).
}
@values -min 2 -max 2
row -type integer -help\
"row number - starting at 1"
col -type integer -help\
"column number - starting at 1"
}]
proc vt52move {row col} {
#test
set r [format %c [expr {$row + 31}]]
set c [format %c [expr {$col + 31}]]
return \x1bY${r}${c}
}
proc vt52color {int} {
if {[string is integer -strict $int]} {
if {$int < 0 || $int > 15} {
error "vt52color unsupported - only 0 to 15 available"
}
}
set char [format %c [expr {$int + 31}]]
return \x1bb${char}
}
proc move_emit {row col data args} {
#*** !doctools
#[call [fun move_emit] [arg row] [arg col] [arg data] [opt {row col data...}]]
@ -3424,6 +3632,27 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
}
return $out
}
proc vt52move_emit {row col data args} {
#Todo - G code?
set out ""
if {$row eq "this"} {
#append out \033\[\;${col}G$data
append out [vt52move_column $col]$data
} else {
#append out \033\[${row}\;${col}H$data
append out [vt52move $row $col]$data
}
foreach {row col data} $args {
if {$row eq "this"} {
append out [vt52move_column $col]$data
#append out \033\[\;${col}G$data
} else {
#append out \033\[${row}\;${col}H$data
append out [vt52move $row $col]$data
}
}
return $out
}
proc move_emitblock {row col textblock} {
#*** !doctools
#[call [fun move_emitblock] [arg row] [arg col] [arg textblock]]
@ -3434,31 +3663,63 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
}
return $commands
}
proc vt52move_emitblock {row col textblock} {
#*** !doctools
#[call [fun move_emitblock] [arg row] [arg col] [arg textblock]]
set commands ""
foreach ln [split $textblock \n] {
append commands [punk::ansi::vt52move_emit $row $col $ln]
incr row
}
return $commands
}
proc move_forward {{n 1}} {
#*** !doctools
#[call [fun move_forward] [arg n]]
return \033\[${n}C
}
proc vt52move_forward {{n 1}} {
return [string repeat \x1bC $n]
}
proc move_back {{n 1}} {
#*** !doctools
#[call [fun move_back] [arg n]]
return \033\[${n}D
}
proc vt52move_back {{n 1}} {
return [string repeat \x1bD $n]
}
proc move_up {{n 1}} {
#*** !doctools
#[call [fun move_up] [arg n]]
return \033\[${n}A
}
proc vt52move_up {{n 1}} {
return [string repeat \x1bA $n]
}
proc move_down {{n 1}} {
#*** !doctools
#[call [fun move_down] [arg n]]
return \033\[${n}B
}
proc vt52move_down {{n 1}} {
return [string repeat \x1bB $n]
}
proc move_column {col} {
#*** !doctools
#[call [fun move_column] [arg col]]
return \x1b\[${col}G
}
proc vt52move_column {col} {
#This is a bit of a fudge - as there is no command to move to a specific column.
#without tracking state - we settle for moving back enough times to ensure we're at column 1 - and then move forward.
#inefficient - but will have to do I guess.
#review - max term width vt52? env var LINES and env var COLUMNS ?
# also ESC R <cols,rows> CR - set window size
set back [string repeat \x1bD 132]
set fwd [string repeat \x1bC [expr {$col - 1}]]
return $back$fwd
}
proc move_row {row} {
#*** !doctools
#[call [fun move_row] [arg row]]
@ -3496,6 +3757,13 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#[para] DECRC
return \x1b8
}
proc cursor_save_vt52 {} {
return \x1bj
}
proc cursor_restore_vt52 {} {
return \x1bk
}
# -- --- --- --- ---
#CRM Show Control Character Mode
proc enable_crm {} {
@ -3551,17 +3819,130 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
# \x1b\[?7\;2\$y
#where 1 = set, 2 = unset. (0 = mode not recognised, 3 = permanently set, 4 = permanently unset)
#names for other alt_screen mechanisms: 1047,1048 vs 1049?
variable decmode_names [dict create\
line_wrap 7\
LNM 20\
alt_screen 1049\
grapheme_clusters 2027\
bracketed_paste 2004\
mouse_sgr_extended 1006\
mouse_urxvt 1015\
mouse_sgr 1016\
]
#https://wiki.tau.garden/dec-modes/
#(DEC,xterm,contour,mintty,kitty etc)
#https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
#names for other alt_screen mechanismk: 1047,1048 vs 1049?
#variable decmode_names [dict create\
# DECANM 2\
# origin 6\
# DECCOLM 3\
# line_wrap 7\
# LNM 20\
# alt_screen 1049\
# grapheme_clusters 2027\
# bracketed_paste 2004\
# mouse_sgr 1006\
# mouse_urxvt 1015\
# mouse_sgr_pixel 1016\
#]
variable decmode_data {
1 {
{origin DEC description "DECCKM - Cursor Keys Mode" names {DECCKM cursor_keys}}
}
2 {
{origin DEC description "DECANM - ANSI/VT52 Mode" names {DECANM} note {
Disable to turn on VT52 emulation.
In VT52 mode - use \x1b< to exit.
}
}
}
3 {
{origin DEC description "DECCOLM - Column" names {DECCOLM}}
}
4 {
{origin DEC description "DECSCLM - Scrolling" names {DECSCLM}}
}
5 {
{origin DEC description "DECSCNM - Screen Mode (light or dark screen)" names {DECSNM lightmode}}
}
7 {
{origin DEC description "DECAWM - Auto Wrap Mode" names {DECAWM line_wrap}}
}
9 {
{origin "xterm" description "X10 compatibility mouse" names {SET_X10_MOUSE mouse_tracking} note {
Escape sequence on button press only.
CSI M CbCxCy (6 chars)
Coords limited to 223 (=255 - 32)
}
}
{origin DEC description "DECINLM - Interlace Mode (obsolete?)" names {DECINLM}}
}
20 {
{origin DEC description "LNM - Line Feed/New Line Mode" names {LNM} note {
For terminals that support LNM, the default is off
meaning a lone CR respresents the character emitted
when enter is pushed. Turning LNM on would mean that
CR LF is sent when hitting enter. This feature is
not commonly supported, and the default will normally
be as if this was off - ie lone CR.
}
}
}
25 {
{origin DEC description "DECTCEM - Text Cursor Enable Mode" names {DECTCEM cursor_enable}}
}
47 {
{origin xterm description "xterm alternate buffer" names {xterm_altbuf}}
{origin DEC description "DECGRPM - Graphics Rotated Print Mode (obsolete?)" names {DECGRPM}}
}
66 {
{origin DEC description "DECNKM - Numeric Keypad Mode" names {DECNKM}}
}
1000 {
{origin "xterm" description "VT200 compatibility mouse" names {SET_VT200_MOUSE} note {
Escape sequence on both button press and release.
CSI M CbCxCy
}
}
}
1004 {
{origin "xterm" description "Send FocusIn/FocusOut events" names {mouse_focus_event}}
}
1005 {
{origin "xterm" description "Enable UTF-8 Mouse Mode" names {mouse_utf8 mouse_utf8_extended}}
}
1006 {
{origin "xterm" description "Enable SGR Mouse Mode" names {mouse_sgr mouse_sgr_extended} note{
SET_SGR_EXT_MODE_MOUSE - extended compared to x10 mouse protocol which limits x y coords
to 223 (=255 - 32)
}
}
}
1015 {
{origin "urxvt" description "Enable urxvt Mouse Mode" names {mouse_urxvt}}
}
1016 {
{origin "xterm" description "Enable SGR Pixel Mouse Mode" names {mouse_sgr_pixel}}
}
1047 {
{origin "xterm" description "Alternate Buffer" names {alt_buffer_only}}
}
1049 {
{origin "xterm" description "Alternate Buffer with save cursor" names {alt_buffer alt_screen}}
}
2004 {
{origin "xterm" description "Set bracketed paste mode" names {bracketed_paste}}
}
2027 {
{origin Contour description "Grapheme Cluster Processing" names {grapheme_clusters}}
}
}
set decmode_names [dict create]
dict for {code items} $decmode_data {
foreach itm $items {
set names [dict get $itm names]
foreach nm $names {
dict set decmode_names $nm $code
}
}
}
proc query_mode {num_or_name} {
if {[string is integer -strict $num_or_name]} {
set m $num_or_name
@ -3674,11 +4055,17 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#[para]Erase to start of line, leaving cursor position alone.
return \033\[1K
}
proc vt52erase_sol {} {
return \x1bo
}
proc erase_eol {} {
#*** !doctools
#[call [fun erase_eol]]
return \033\[K
}
proc vt52erase_eol {} {
return \x1bK
}
#see also clear_above clear_below
# -- --- --- --- ---
@ -3732,6 +4119,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
proc cursor_pos_extended {} {
#includes page e.g ^[[47;3;1R
#(but not on all terminals - some (freebsd?) will report as per 6n e.g ^[[74;3R)
return \033\[?6n
}
@ -3789,6 +4177,9 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#[para]This may not work on terminals which have multiple panes/windows
return "\033\]2;$windowtitle\033\\" ;#works for xterm and most derivatives
}
proc vt52titleset {windowtitle} {
return \x1bS$windowtitle\r
}
#titleget - https://invisible-island.net/xterm/xterm.faq.html#how2_title
#no cross-platform ansi-only mechanism ?
@ -4672,8 +5063,14 @@ tcl::namespace::eval punk::ansi::ta {
variable re_osc_open {(?:\x1b\]|\u009d).*}
variable standalone_code_map [list \x1bc "" \x1b7 "" \x1b8 "" \x1bM "" \x1bE "" \x1bD "" \x1bH "" \x1b= "" \x1b> "" \x1b#3 "" \x1b#4 "" \x1b#5 "" \x1b#6 "" \x1b#8 ""]
variable re_standalones {(?:\x1bc|\x1b7|\x1b8|\x1bM|\x1bE|\x1bD|\x1bD|\x1bH|\x1b=|\x1b>|\x1b#3|\x1b#4|\x1b#5|\x1b#6|\x1b#8)}
#variable standalone_code_map [list \x1bc "" \x1b7 "" \x1b8 "" \x1bM "" \x1bE "" \x1bD "" \x1bH "" \x1b= "" \x1b> "" \x1b#3 "" \x1b#4 "" \x1b#5 "" \x1b#6 "" \x1b#8 ""]
variable re_standalones {(?:\x1bc|\x1b7|\x1b8|\x1bM|\x1bD|\x1bE|\x1bH|\x1b=|\x1b>|\x1b#3|\x1b#4|\x1b#5|\x1b#6|\x1b#8)}
variable re_standalones_vt52 {(?:\x1bZ)}
#ESC Y move, ESC b foreground colour
#ESC F - gr-on ESC G - gr-off
variable re_vt52_open {(?:\x1bY|\x1bb|\x1bF)}
#\x1bc vt52 bgcolour conflict ??
#if we don't split on altgraphics too and separate them out - it's easy to get into a horrible mess
variable re_g0_group {(?:\x1b\(0)(?:(?!\x1b\(B).)*\x1b\(B}
@ -4686,7 +5083,7 @@ tcl::namespace::eval punk::ansi::ta {
#regex to capture the start of string/privacy message/application command block including the contents and string terminator (ST)
#non-greedy by exclusion of ST terminators in body
#we need to match \x1b\\ not just \x1b There could be colour codes nested in a privacy msg/string
#we need to match \x1b\\ not just \x1b There could be colour codes nested in a privacy msg/string (not widely supported?)
#even if terminals generally don't support that - it's quite possible for an ansi code to get nested this way - and we'd prefer it not to break our splits
#Just checking for \x1b will terminate the match too early
#we also need to track the start of ST terminated code and not add it for replay (in the ansistring functions)
@ -4705,17 +5102,44 @@ tcl::namespace::eval punk::ansi::ta {
#handrafted TRIE version of above. Somewhat difficult to construct and maintain. TODO - find a regexp TRIE generator that works with Tcl regexes
#This does make things quicker - but it's too early to finalise the detect/split regexes (e.g missing \U0090 ) - will need to be redone.
#variable re_ansi_detect {(?:\x1b(?:\((?:0|B)|\[(?:[\x20-\x2f\x30-\x3f]*[\x40-\x7e])|\](?:(?:[^\007]*)\007|(?:(?!\x1b\\).)*\x1b\\)|(?:P|X|\^|_)(?:(?:(?!\x1b\\|\007).)*(?:\x1b\\|\007))|c|7|8|M|E|D|H|=|>|(?:#(?:3|4|5|6|8))))|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)|(?:\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]|(?:\u009d)(?:[^\u009c]*)?\u009c}
variable re_ansi_detect {(?:\x1b(?:\[(?:[\x20-\x2f\x30-\x3f]*[\x40-\x7e])|\((?:0|B)|\](?:(?:[^\007]*)\007|(?:(?!\x1b\\).)*\x1b\\)|(?:P|X|\^|_)(?:(?:(?!\x1b\\|\007).)*(?:\x1b\\|\007))|c|7|8|M|E|D|H|=|>|(?:#(?:3|4|5|6|8))))|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)|(?:\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]|(?:\u009d)(?:[^\u009c]*)?\u009c}
#NOTE - the literal # char can cause problems in expanded syntax - even though it's within a bracketed section. \# seems to work though.
#vt52 specific |<|A|B|C|I|J|K|L|M|Z|(?:Y(?:..))|(?:b(?:.)|
#https://freemint.github.io/tos.hyp/en/VT_52_terminal.html
#what to with ESC c vs vt52 ESC c <c> (background colour) ???
#we probably need to use a separate re_ansi_detect for vt52
#although it's stated later terminals are backwards compatible with vt52 - that doesn't seem to mean for example a vt100 will process vt52 codes at the same time as ansi codes
#ie - when DECANM is on - VT52 codes are *not* processed
#todo - ansi mode and cursor key mode set ?
# arrow keys -> ESC O A, ESC O B, ESC O C, ESC O D
# plus more for auxiliary keypad codes in keypad application mode (and some in numeric mode)
variable re_ansi_detect {(?x)
(?:\x1b(?:\[(?:[\x20-\x2f\x30-\x3f]*[\x40-\x7e])|c|7|8|M|D|E|H|=|>|<|A|B|C|I|J|K|L|M|Z|(?:Y(?:..))|(?:b(?:.))|\((?:0|B)|\](?:(?:[^\007]*)\007|(?:(?!\x1b\\).)*\x1b\\)|(?:P|X|\^|_)(?:(?:(?!\x1b\\|\007).)*(?:\x1b\\|\007))|(?:\#(?:3|4|5|6|8))))
|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)
|(?:\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]
|(?:\u009d)(?:[^\u009c]*)?\u009c
}
#---
# -- --- --- ---
#variable re_csi_code {(?:\x1b\[|\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]}
variable re_ansi_detect_open "${re_csi_open}|${re_osc_open}|${re_standalones}|${re_ST_open}|${re_g0_open}"
variable re_ansi_detect_open "${re_csi_open}|${re_osc_open}|${re_standalones}|${re_standalones_vt52}|${re_ST_open}|${re_g0_open}|${re_vt52_open}"
#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
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])+"
} else {
set re_ansi_split_multi "(?:${re_ansi_split})+"
}
lappend PUNKARGS [list -dynamic 0 {
@id -id ::punk::ansi::ta::detect
@ -4922,81 +5346,46 @@ tcl::namespace::eval punk::ansi::ta {
# -- --- --- --- --- ---
#Split $text to a list containing alternating ANSI colour codes and text.
#ANSI colour codes are always on the second element, fourth, and so on.
#(ie plaintext on odd list-indices ansi on even indices)
#(ie plaintext on even list-indices ansi on odd indices)
#result of split on non-empty string always has an odd length - with indices 0 and end always being plaintext (possibly empty string)
# Example:
#ta_split_codes "" # => ""
#ta_split_codes "a" # => "a"
#ta_split_codes "a\e[31m" # => {"a" "\e[31m"}
#ta_split_codes "\e[31ma" # => {"" "\e[31m" "a"}
#ta_split_codes "\e[31ma\e[0m" # => {"" "\e[31m" "a" "\e[0m"}
#ta_split_codes "\e[31ma\e[0mb" # => {"" "\e[31m" "a" "\e[0m", "b"}
#ta_split_codes "\e[31m\e[0mb" # => {"" "\e[31m\e[0m" "b"}
#split_codes "" # => ""
#split_codes "a" # => "a"
#split_codes "a\e[31m" # => {"a" "\e[31m" ""}
#split_codes "\e[31ma" # => {"" "\e[31m" "a"}
#split_codes "\e[31ma\e[0m" # => {"" "\e[31m" "a" "\e[0m" ""}
#split_codes "\e[31ma\e[0mb" # => {"" "\e[31m" "a" "\e[0m", "b"}
#split_codes "\e[31m\e[0mb" # => {"" "\e[31m\e[0m" "b"}
#
proc split_codes {text} {
variable re_ansi_split
set re "(?:${re_ansi_split})+"
return [_perlish_split $re $text]
variable re_ansi_split_multi
return [_perlish_split $re_ansi_split_multi $text]
}
#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 odd/even plain ansi still holds)
#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.
proc split_codes_single2 {text} {
variable re_ansi_split
return [_perlish_split $re_ansi_split $text]
}
proc split_codes_single3 {text} {
#copy from re_ansi_split
_perlish_split {(?:\x1b(?:\((?:0|B)|\[(?:[\x20-\x2f\x30-\x3f]*[\x40-\x7e])|\](?:(?:[^\007]*)\007|(?:(?!\x1b\\).)*\x1b\\)|(?:P|X|\^|_)(?:(?:(?!\x1b\\|\007).)*(?:\x1b\\|\007))|c|7|8|M|E|D|H|=|>|(?:#(?:3|4|5|6|8))))|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)|(?:\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]|(?:\u009d)(?:[^\u009c]*)?\u009c} $text
}
proc split_codes_single4 {text} {
if {$text eq ""} {
return {}
}
variable re_ansi_split
set re $re_ansi_split
#variable re_ansi_detect1
#set re $re_ansi_detect1
set list [list]
set start 0
#set re {(?:\x1b(?:\((?:0|B)|\[(?:[\x20-\x2f\x30-\x3f]*[\x40-\x7e])|\](?:(?:[^\007]*)\007|(?:(?!\x1b\\).)*\x1b\\)|(?:P|X|\^|_)(?:(?:(?!\x1b\\|\007).)*(?:\x1b\\|\007))|c|7|8|M|E|D|H|=|>|(?:#(?:3|4|5|6|8))))|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)|(?:\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]|(?:\u009d)(?:[^\u009c]*)?\u009c}
#We can get $matchEnd < $matchStart; we need to ensure there is an exit condition for non-greedy empty results REVIEW
#while {[regexp -start $start -indices -- {(?:\x1b(?:\((?:0|B)|\[(?:[\x20-\x2f\x30-\x3f]*[\x40-\x7e])|\](?:(?:[^\007]*)\007|(?:(?!\x1b\\).)*\x1b\\)|(?:P|X|\^|_)(?:(?:(?!\x1b\\|\007).)*(?:\x1b\\|\007))|c|7|8|M|E|D|H|=|>|(?:#(?:3|4|5|6|8))))|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)|(?:\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]|(?:\u009d)(?:[^\u009c]*)?\u009c} $text match]} {}
while {[regexp -start $start -indices -- $re $text match]} {
lassign $match matchStart matchEnd
#puts "->start $start ->match $matchStart $matchEnd"
lappend list [tcl::string::range $text $start $matchStart-1]
if {$matchEnd < $matchStart} {
set e $matchStart
incr start
} else {
set e $matchEnd
set start [expr {$matchEnd+1}]
}
lappend list [tcl::string::range $text $matchStart $e]
if {$start >= [tcl::string::length $text]} {
break
}
}
return [lappend list [tcl::string::range $text $start end]]
}
proc split_codes_single {text} {
if {$text eq ""} {
return {}
}
variable re_ansi_split
set next 0
set b -1
#set b -1
set list [list]
set coderanges [regexp -indices -all -inline -- $re_ansi_split $text]
foreach cr $coderanges {
lappend list [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}]
#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} {
variable re_ansi_split
return [_perlish_split $re_ansi_split $text]
}
proc get_codes_single {text} {
variable re_ansi_split
regexp -all -inline -- $re_ansi_split $text
@ -5008,7 +5397,7 @@ tcl::namespace::eval punk::ansi::ta {
return {}
}
set next 0
set b -1
#set b -1
set list [list]
set coderanges [regexp -indices -all -inline -- $re $text]
foreach cr $coderanges {
@ -5103,29 +5492,6 @@ tcl::namespace::eval punk::ansi::ta {
#return [lappend list [tcl::string::range $text $start end]]
yield [tcl::string::range $text $start end]
}
proc _perlish_split2 {re text} {
if {[tcl::string::length $text] == 0} {
return {}
}
set list [list]
set start 0
#We can get $matchEnd < $matchStart; we need to ensure there is an exit condition for non-greedy empty results REVIEW
while {[regexp -start $start -indices -- $re $text match]} {
lassign $match matchStart matchEnd
#puts "->start $start ->match $matchStart $matchEnd"
if {$matchEnd < $matchStart} {
lappend list [tcl::string::range $text $start $matchStart-1] [tcl::string::index $text $matchStart]
incr start
} else {
lappend list [tcl::string::range $text $start $matchStart-1] [tcl::string::range $text $matchStart $matchEnd]
set start [expr {$matchEnd+1}]
}
if {$start >= [tcl::string::length $text]} {
break
}
}
return [lappend list [tcl::string::range $text $start end]]
}
proc _ws_split {text} {
regexp -all -inline {(?:\S+)|(?:\s+)} $text
}
@ -7429,12 +7795,10 @@ tcl::namespace::eval punk::ansi::internal {
#todo - document
interp alias {} ansistring {} ::punk::ansi::ansistring
if {![info exists ::punk::args::register::NAMESPACES]} {
namespace eval ::punk::args::register {
set NAMESPACES [list]
}
}
#use fully qualified so 8.6 doesn't find existing var in global namespace
lappend ::punk::args::register::NAMESPACES ::punk::ansi ::punk::ansi::class ::punk::ansi::ta
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready

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

File diff suppressed because it is too large Load Diff

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

@ -141,9 +141,11 @@ tcl::namespace::eval punk::args::tclcore {
variable PUNKARGS
package require punk::ansi
tcl::namespace::import ::punk::ansi::a+
# -- --- --- --- ---
#non colour SGR codes
# we can use these directly via ${$I} etc without marking a definition with -dynamic
# we can use these directly via ${$I} etc without marking a definition with @dynamic
#This is because they don't need to change when colour switched on and off.
set I [a+ italic]
set NI [a+ noitalic]
@ -151,54 +153,123 @@ tcl::namespace::eval punk::args::tclcore {
set N [a+ normal]
# -- --- --- --- ---
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---
# library commands loaded via auto_index
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---
namespace eval argdoc {
variable PUNKARGS
lappend PUNKARGS [list {
@id -id ::parray
@cmd -name "Builtin: parray" -help\
"Prints on standard output the names and values of all the elements in the
array arrayName, or just the names that match pattern (using the matching
rules of string_match) and their values if pattern is given.
ArrayName must be an array accessible to the caller of parray. It may either
be local or global. The result of this command is the empty string.
(loaded via auto_index)"
@values -min 1 -max 2
arrayName -type string -help\
"variable name of an array"
pattern -type string -optional 1 -help\
"Match pattern possibly containing glob characters"
} "@doc -name Manpage: -url [manpage_tcl library]" ]
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---
@id -id ::punk::args::tclcore::argdoc::ensemble_subcommands_definition
@cmd -name ::punk::args::tclcore::argdoc::ensemble_subcommands_definition -help\
"Helper function to return a punk::args definition snippet for subcommands"
@leaders -max 0 -min 0
-groupdict -default {} -type dict -help\
"Dictionary keyed on arbitrary groupname, where value
is a list of known subcommands that should be displayed
by groupname. Each groupname forms the title of a subtable
in the choices list.
Subcommands not assigned to a groupname will appear first
in an untitled subtable."
-columns -default 4 -type integer -help\
"Max number of columns for all subtables in the choices
display area"
@values -min 1 -max 1
ensemble -optional 0 -help\
"Name of ensemble command"
}]
proc ensemble_subcommands_definition {args} {
#args manually parsed - with use of argdef for unhappy-path only
if {![llength $args]} {
punk::args::get_by_id ::punk::args::tclcore::argdoc::ensemble_subcommands_definition $args
return
}
set ensemble [lindex $args end]
set optlist [lrange $args 0 end-1]
if {[llength $optlist] % 2} {
punk::args::get_by_id ::punk::args::tclcore::argdoc::ensemble_subcommands_definition $args
return
}
set defaults [dict create\
-groupdict {}\
-columns 4\
]
set optlist [dict merge $defaults $optlist]
dict for {k v} $optlist {
switch -- $k {
-groupdict - -columns {}
default {
punk::args::get_by_id ::punk::args::tclcore::argdoc::ensemble_subcommands_definition $args
return
}
}
}
set opt_groupdict [dict get $optlist -groupdict]
set opt_columns [dict get $optlist -columns]
#todo - make generic - take command and known_groups_dict
proc info_subcommands {} {
package require punk::ns
set subdict [punk::ns::ensemble_subcommands -return dict info]
set subdict [punk::ns::ensemble_subcommands -return dict $ensemble]
set allsubs [dict keys $subdict]
dict set groups "system" {hostname library nameofexecutable patchlevel script sharedlibextension tclversion}
dict set groups "{proc introspection}" {args body default}
dict set groups "variables" {constant consts exists globals locals vars}
dict set groups "{oo object introspection}" {class object}
# ----------------------------------------------
# manually defined group members may have subcommands that are obsoleted/missing
# we choose to make the situation obvious by re-classifying into a corresponding group with the " - MISSING" suffix
set checked_groupdict [dict create]
dict for {g members} $opt_groupdict {
set validmembers {}
set invalidmembers {}
foreach m $members {
if {$m in $allsubs} {
lappend validmembers $m
} else {
lappend invalidmembers $m
}
}
dict set checked_groupdict $g $validmembers
if {[llength $invalidmembers]} {
dict set checked_groupdict "${g}_MISSING" $invalidmembers
}
}
if {[dict exists $checked_groupdict ""]} {
set others [dict get $checked_groupdict ""]
dict unset checked_groupdict ""
} else {
set others [list]
}
#REVIEW
set debug 0
if {$debug} {
puts "punk::args::tclcore::argdoc::ensemble_subcommands_definition"
if {[catch {
::punk::lib::pdict checked_groupdict
} msg]} {
puts stderr "punk::args::tclcore::ensemble_subcommands_definition Cannot call pdict\n$msg"
}
puts --------------------
puts "$checked_groupdict"
puts --------------------
}
set opt_groupdict $checked_groupdict
# ----------------------------------------------
set allgrouped [list]
dict for {g members} $groups {
dict for {g members} $opt_groupdict {
lappend allgrouped {*}$members
}
set others [list]
foreach sc $allsubs {
if {$sc ni $allgrouped} {
if {$sc ni $others} {
lappend others $sc
}
}
}
set argdef ""
append argdef "subcommand -choicegroups \{" \n
append argdef " \"\" \{$others\}" \n
dict for {g members} $groups {
append argdef " $g \{$members\}" \n
dict for {g members} $opt_groupdict {
append argdef " \"$g\" \{$members\}" \n
}
append argdef " \}" \n
append argdef " \} -choicecolumns $opt_columns" \n
#todo -choicelabels
#detect subcommand further info available e.g if oo or ensemble or punk::args id exists..
@ -206,9 +277,29 @@ tcl::namespace::eval punk::args::tclcore {
return $argdef
}
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---
# library commands loaded via auto_index
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---
lappend PUNKARGS [list {
@id -id ::parray
@cmd -name "Builtin: parray" -help\
"Prints on standard output the names and values of all the elements in the
array arrayName, or just the names that match pattern (using the matching
rules of string_match) and their values if pattern is given.
ArrayName must be an array accessible to the caller of parray. It may either
be local or global. The result of this command is the empty string.
(loaded via auto_index)"
@values -min 1 -max 2
arrayName -type string -help\
"variable name of an array"
pattern -type string -optional 1 -help\
"Match pattern possibly containing glob characters"
} "@doc -name Manpage: -url [manpage_tcl library]" ]
# -- --- --- --- --- --- --- --- --- --- --- --- --- ---
lappend PUNKARGS [list -dynamic 1 {
lappend PUNKARGS [list {
#test of @form
@id -id ::AFTER
@cmd -name "Builtin: after" -help\
@ -223,7 +314,8 @@ tcl::namespace::eval punk::args::tclcore {
@form -form {schedule_ms} -synopsis "after ms ?script...?"
#@values -form {*} #note "classify next argument as a value not a leader"
ms -form {*} -type int
ms -form {*} -type int -help\
"milliseconds"
@values -form {delay} -min 1 -max 1
@values -form {schedule_ms} -min 2
script -form {schedule_ms} -multiple 1 -optional 1 ref-help common_script_help
@ -252,12 +344,28 @@ tcl::namespace::eval punk::args::tclcore {
} "@doc -name Manpage: -url [manpage_tcl after]" ]
lappend PUNKARGS [list -dynamic 1 {
namespace eval argdoc {
#todo - make generic - take command and known_groups_dict
proc info_subcommands {} {
#package require punk::ns
#set subdict [punk::ns::ensemble_subcommands -return dict info]
#set allsubs [dict keys $subdict]
dict set groups "system" {hostname library nameofexecutable patchlevel script sharedlibextension tclversion}
dict set groups "proc introspection" {args body default}
dict set groups "variables" {constant consts exists globals locals vars}
dict set groups "oo object introspection" {class object}
return [ensemble_subcommands_definition -groupdict $groups -columns 4 info]
}
}
lappend PUNKARGS [list {
@dynamic
@id -id ::info
@cmd -name "Builtin: info" -help\
"Information about the state of the Tcl interpreter"
@values
${[punk::args::tclcore::info_subcommands]}
@leaders -min 1 -max 1
${[punk::args::tclcore::argdoc::info_subcommands]}
@values -min 0
} "@doc -name Manpage: -url [manpage_tcl array]" ]
@ -389,7 +497,7 @@ tcl::namespace::eval punk::args::tclcore {
@cmd -name "Builtin: tcl::chan::tell" -help\
"Returns a number giving the current access position within the underlying
data stream for the channel named channel. This value returned is a byte
offset that can be passed to ${[a+ bold]}chan seek${[a normal]} in order
offset that can be passed to ${[a+ bold]}chan seek${[a+ normal]} in order
to set the channel to a particular position. Note that this value is in
terms of bytes, not characters like ${[a+ bold]}chan read${[a+ normal]}. The
value returned is -1 for channels that do not support seeking."
@ -398,7 +506,25 @@ tcl::namespace::eval punk::args::tclcore {
""
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
lappend PUNKARGS [list {
@id -id ::tcl::chan::truncate
@cmd -name "Builtin: tcl::chan::truncate" -help\
"Sets the byte length of the underlying data stream for the channel to be
length (or to the current byte offset within the underlying data stream if
length is omitted). The channel is flushed before truncation."
#todo - auto synopsis?
@form -synopsis\
"chan truncate channel ?length?"
@values
channel -help \
""
length -optional 1 -type integer
} "@doc -name Manpage: -url [manpage_tcl chan]" ]
#TODO - autocreate argdef namespace and import B N etc
# ${[B]import[N]}
lappend PUNKARGS [list {
@id -id ::tcl::info::cmdtype
@cmd -name "Builtin: tcl::info::cmdtype" -help\
@ -498,13 +624,13 @@ tcl::namespace::eval punk::args::tclcore {
name
} "@doc -name Manpage: -url [manpage_tcl namespace]" ]
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
lappend PUNKARGS [list {
@id -id ::tcl::process::status
@cmd -name "Builtin: tcl::process::status" -help\
"Returns a dictionary mapping subprocess PIDs to their respective status.
if ${$I}pids${$NI} is specified as a list of PIDs then the command
only returns the status of the matching subprocesses if they exist, and
raises an error otherwise.
If ${$I}pids${$NI} is specified as a list of PIDs then the command
only returns the status of the matching subprocesses if they exist.
For active processes, the status is an empty value. For terminated
processes, the status is a list with the following format:
{code ?msg errorCode?}
@ -537,6 +663,20 @@ tcl::namespace::eval punk::args::tclcore {
"A list of PIDs"
} "@doc -name Manpage: -url [manpage_tcl namespace]" ]
lappend PUNKARGS [list {
@id -id ::tcl::process::purge
@cmd -name "Builtin: tcl::process::purge" -help\
"Cleans up all data associated with terminated subprocesses. If pids is
specified as a list of PIDs then the command only cleans up data for
the matching subprocesses if they exist. If a process listed is still
active, this command does nothing to that process.
Any PID that does not correspond to a subprocess is ignored."
@values -min 0 -max 1
pids -type list -optional 1 -help\
"A list of PIDs"
} "@doc -name Manpage: -url [manpage_tcl namespace]" ]
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
############################################################################################################################################################
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
@ -549,30 +689,19 @@ tcl::namespace::eval punk::args::tclcore {
#categorise array subcommands based on currently known groupings.
#we do this dynamically because Tcl ensembles (even some core ones) can have subcommands added at runtime.
proc array_subcommands {} {
package require punk::ns
set subdict [punk::ns::ensemble_subcommands array]
set expected_searchcmds {startsearch anymore nextelement donesearch}
set searchcmds [list]
foreach sc $expected_searchcmds {
if {$sc in [dict keys $subdict]} {
lappend searchcmds $sc
}
}
set argdef ""
append argdef "subcommand -choicegroups \{" \n
append argdef " \"\" \{" \n
append argdef " [dict keys [dict remove $subdict {*}$searchcmds]]" \n
append argdef " \}" \n
append argdef " \"search\" \{" \n
append argdef " $searchcmds" \n
append argdef " \}" \n
append argdef " \} -choicecolumns 4 " \n
#puts "--array_subcommands frames:"
#for {set i 0} {$i <= [info frame]} {incr i} {
# puts "$i [info frame $i]"
#}
return $argdef
#dict set groups "" {bogus names} ;#test adding both existant and nonexistant to the default group
dict set groups "search" {startsearch anymore nextelement donesearch}
return [ensemble_subcommands_definition -groupdict $groups -columns 4 array]
}
}
lappend PUNKARGS [list -dynamic 1 {
lappend PUNKARGS [list {
@dynamic
@id -id ::array
@cmd -name "Builtin: array" -help\
"Manipulate array variables"
@ -584,7 +713,7 @@ tcl::namespace::eval punk::args::tclcore {
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
lappend PUNKARGS [list -dynamic 1 {
lappend PUNKARGS [list {
@id -id ::const
@cmd -name "Builtin: const" -help\
"Create and initialise a constant.
@ -671,6 +800,28 @@ tcl::namespace::eval punk::args::tclcore {
} "@doc -name Manpage: -url [manpage_tcl ledit]"
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
punk::args::define {
@id -id ::lremove
@cmd -name "builtin: lremove" -help\
"Remove elements from a list by index
lremove returns a new list formed by simultaneously removing zero or
more elements of list at each of the indices given by an arbitrary
number of index arguments. The indices may be in any order and may be
repeated; the element at index will only be removed once. The index
values are interpreted the same as index values for the command
'string index', supporting simple index arithmetic and indices relative
to the end of the list. 0 refers to the first element of the list, and
end refers to the last element of the list."
@values -min 1 -max -1
list -type list -help\
"tcl list as a value"
index -type indexexpression -multiple 1 -optional 1
@seealso -commands {list lappend lassign ledit lindex linsert llength lmap lpop lrange lrepeat lreplace lreverse lsearch lseq lset lsort}
} "@doc -name Manpage: -url [manpage_tcl lremove]"
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
punk::args::define {
@id -id ::lpop
@ -700,8 +851,7 @@ tcl::namespace::eval punk::args::tclcore {
The index values first and last are interpreted the same as index values
for the command 'string index', supporting simple index arithmetic and
indices relative to the end of the list.
e.g lrange {a b c} 0 end-1
"
e.g lrange {a b c} 0 end-1"
@values -min 3 -max 3
list -type list -help\
"tcl list as a value"
@ -759,8 +909,7 @@ tcl::namespace::eval punk::args::tclcore {
e.g set myarray(config,0) \"val1\"
set myarray(config,1) \"etc\"
set myarray(data,0) {a b c}
see the dict command for an alternative datastructure.
"
see the dict command for an alternative datastructure."
value -type any -optional 1
} "@doc -name Manpage: -url [manpage_tcl set]"
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
@ -912,7 +1061,7 @@ tcl::namespace::eval punk::args::tclcore {
@id -id ::tcl::string::totitle
@cmd -name "builtin: tcl::string::totitle" -help\
"Returns a value equal to string except that the first character in string is converted to
it's Unicode title case variant (or upper case if there is no title case variant) and the
its Unicode title case variant (or upper case if there is no title case variant) and the
rest of the string is converted to lower case."
@values -min 1 -max 1
@ -956,7 +1105,7 @@ tcl::namespace::eval punk::args::tclcore {
e.g M+N"
} "@doc -name Manpage: -url [manpage_tcl string]"
punk::args::define [punk::lib::tstr -return string {
punk::args::define [punk::args::lib::tstr -return string {
@id -id ::tcl::string::is
@cmd -name "builtin: tcl::string::is" -help\
"Returns 1 if string is a valid member of the specified character class, otherwise returns 0.
@ -1094,10 +1243,7 @@ tcl::namespace::eval punk::args::tclcore {
(e.g. underscore)"
xdigit\
" Any hexadecimal digit
character, and any Unicode
connector punctuation
characters (e.g. underscore)"
character ([0-9A-Fa-f])."
}\
-help\
"character class
@ -1115,6 +1261,221 @@ tcl::namespace::eval punk::args::tclcore {
string -type string -optional 0
}] "@doc -name Manpage: -url [manpage_tcl string]"
#a test of going deeper - we should be able to define these by reference to above text
#e.g dict get [lrange [punk::args::resolved_def -types leaders ::tcl::string::is class] 1 end] -choicelabels xdigit
#set string_class_choices [dict get [lrange [punk::args::resolved_def -types leaders ::tcl::string::is class] 1 end] -choices]
set string_class_choicelabels [dict get [lrange [punk::args::resolved_def -types leaders ::tcl::string::is class] 1 end] -choicelabels]
dict for {sclass slabel} $string_class_choicelabels {
punk::args::define [string map [list %sc% $sclass %slabel% $slabel] {
@id -id "::tcl::string::is %sc%"
@cmd -name "builtin: string is %sc%" -help\
{%slabel%}
${[punk::args::resolved_def -types opts ::tcl::string::is -*]}
@values -min 1 -max 1
string -type string -optional 0
}] "@doc -name Manpage: -url [manpage_tcl string]"
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
punk::args::define {
@id -id ::trace
@cmd -name "builtin: trace" -help\
"Monitor variable accesses, command usages and command executions
"
@form -synopsis "trace option ?arg arg...?"
option -choicegroups {
"" {add remove info}
obsolete {variable vdelete vinfo}
}\
-choiceinfo {
add {subhelp "::trace add"}
}
} "@doc -name Manpage: -url [manpage_tcl trace]"
punk::args::define {
@id -id "::trace add"
@cmd -name "builtin: trace add" -help\
""
@form -synopsis "trace add type name ops ?args?"
@leaders
type -choicegroups {
"" {command execution variable}
}\
-choiceinfo {
command {subhelp "::trace add command"}
}
} "@doc -name Manpage: -url [manpage_tcl trace]"
punk::args::define {
@id -id "::trace add command"
@cmd -name "builtin: trace add command" -help\
"Arrange for commandPrefix to be executed (with additional arguments)
whenever command name is modified in one of the ways given by the list
ops. Name will be resolved using the usual namespace resolution rules
used by commands. If the command does not exist, an error will be thrown."
name -type string -help\
"Name of command"
ops -type list -choices {rename delete} -choiceprefix 0 -choicemultiple {1 2}\
-choicelabels {
rename\
" Invoke commandPrefix whenever the traced command
is renamed. Note that renaming to the empty string
is considered deletion, and will not be traced with
'rename'"
delete\
" Invoke commandPrefix when the traced command is deleted.
Commands can be deleted explicitly using the rename command to
rename the command to an empty string. Commands are also deleted
when the interpreter is deleted, but traces will not be invoked
because there is no interpreter in which to execute them."
}\
-help\
"Indicates which operations are of interest."
commandPrefix -type string -help\
"When the trace triggers, depending on the operations being traced, a
number of arguments are appended to commandPrefix so that the actual
command is as follows:
--------------------------------
commandPrefix oldName newName op
--------------------------------
OldName and newName give the traced command's current (old) name,
and the name to which it is being renamed (the empty string if this
is a \"delete\" operation). Op indicates what operation is being
performed on the command, and is one of rename or delete as defined
above. The trace operation cannot be used to stop a command from being
deleted. Tcl will always remove the command once the trace is complete.
Recursive renaming or deleting will not cause further traces of the
same type to be evaluated, so a delete trace which itself deletes a
command, or a rename trace which itself renames the command will not
cause further trace evaluations to occur. Both oldName and newName are
fully qualified with any namespace(s) in which they appear.
"
} "@doc -name Manpage: -url [manpage_tcl trace]"
punk::args::define {
@id -id "::trace add execution"
@cmd -name "builtin: trace add execution" -help\
"Arrange for commandPrefix to be executed (with additional arguments)
whenever command name is executed, with traces occurring at the points
indicated by the list ops. Name will be resolved using the usual namespace
resolution ruls used by commands. If the command does not exist, and error
will be thrown"
name -type string -help\
"Name of command"
# ---------------------------------------------------------------
ops -type list -choices {enter leave enterstep leavestep} -choiceprefix 0\
-choicemultiple {1 4}\
-choicecolumns 2\
-choicelabels {
enter\
" Invoke commandPrefix whenever the command name is executed,
just before the actual execution takes place."
leave\
" Invoke commandPrefix whenever the command name is executed,
just after the actual execution takes place."
enterstep\
" Invoke commandPrefix for every Tcl command which is executed
from the start of the execution of the procedure name until
that procedure finishes. CommandPrefix is invoked just before
the actual execution of the Tcl command being reported takes
place. For example if we have
\"proc foo {} { puts \"hello\" }\", then an enterstep trace
would be invoked just before \"puts \"hello\"\" is executed.
Setting an enterstep trace on a command name that does not
refer to a procedure will not result in an error and is
simply ignored."
leavestep\
" Invoke commandPrefix for every Tcl command which is executed
from the start of the execution of the procedure name until
that procedure finishes. CommandPrefix is invoked just after
the actual execution of the Tcl command being reported takes
place. Setting a leavestep trace on a command name that does
not refer to a procedure will not result in an error and is
simply ignored."
}\
-help\
"Indicates which operations are of interest."
commandPrefix -type string -help\
"When the trace triggers, depending on the operation being traced, a
number of arguments are appended to commandPrefix so that the actual
command is as follows:
For enter and enterstep operations:
-------------------------------
commandPrefix command-string op
-------------------------------
Command-string give the complete current command being executed
(the traced command for a enter operation, an arbitrary command
for an enterstep operation), including all arguments in their
fully expanded form. Op indicates what operation is being performed
on the command execution, and is on of enter or enterstep as
defined above. The trace operation can be used to stop the command
from executing, by deleting the command in question. Of course when
the command is subsequently executed, an \"invalid command\" error
will occur.
For leave and leavestep operations:
-------------------------------------------
commandPrefix command-string code result op
-------------------------------------------
Command-string gives the complete current command being executed
(the traced command for a leave operation, an arbitrary command
for a leavestep operation), including all arguments in their
fully expanded form. Code give the result code of that execution,
and result the result string. Op indicates what operation is being
performed on the command execution and is one of leave or leavestep
as defined above.
Note that the creation of many enterstep or leavestep traces can
lead to unintuitive results, since the invoked commands from one
trace can themselves lead to further command invocations for other
traces.
CommandPrefix executes in the same context as the code that invoked
the traced operation: thus the commandPrefix, if invoked from a
procedure, will have access to the same local variables as code in the
procedure. This context may be different thatn the context in which
the trace was created. If commandPrefix invokes a procedure (which
it normally does) then the procedure will have to use upvar or uplevel
commands if it wishes to access the local variables of the code which
invoked the trace operation.
While commandPrefix is executing during an execution trace, traces on
name are temporarily disabled. This allows the commandPrefix to execute
name in its body without invoking any other traces again. If an error
occurs while executing the commandPrefix, then the command name as a
whole will return that same error.
When multiple traces are set on name, then for enter and enterstep
operations, the traced commands are invoked in the reverse order of how
the traces were originally created; and for leave and leavestep operations,
the traced commands are invoked in the original order of creation.
The behaviour of execution traces is currently undefined for a command name
imported into another namespace.
"
} "@doc -name Manpage: -url [manpage_tcl trace]"
punk::args::define {
@id -id "::trace remove command"
@cmd -name "builtin: trace remove command" -help\
"If there is a trace set on command name with the operations and command
given by opList and commandPrefix, then the trace is removed, so that
commandPrefix will never again be invoked. Returns an empty string. If
name does not exist, the command will throw an error"
@values
name -type string -help\
"Name of command"
opList -type list -help\
"A list of one or more of the following items:
rename
delete"
commandPrefix
} "@doc -name Manpage: -url [manpage_tcl trace]"
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
punk::args::define {
@id -id ::variable
@ -1147,11 +1508,16 @@ tcl::namespace::eval punk::args::tclcore {
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
namespace eval argdoc {
if {[catch {zlib::pkgconfig get zlibVersion} ZLIBVERSION]} {
set ZLIBVERSION "(unknown)"
}
}
punk::args::define {
@id -id ::zlib
@cmd -name "builtin: ::zlib" -help\
"zlib - compression and decompression operations
"
zlib version: ${$::punk::args::tclcore::argdoc::ZLIBVERSION}"
@leaders -min 1 -max 1
subcommand -type string\
-choicecolumns 2\
@ -1261,12 +1627,10 @@ tcl::namespace::eval punk::args::tclcore::lib {
#}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
if {![info exists ::punk::args::register::NAMESPACES]} {
namespace eval ::punk::args::register {
set ::punk::args::register::NAMESPACES [list] ;#use fully qualified so 8.6 doesn't find existing var in global namespace
}
#use fully qualified so 8.6 doesn't find existing var in global namespace
lappend ::punk::args::register::NAMESPACES ::punk::args::tclcore ::punk::args::tclcore::argdoc
}
lappend ::punk::args::register::NAMESPACES ::punk::args::tclcore
## Ready
package provide punk::args::tclcore [tcl::namespace::eval punk::args::tclcore {

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

@ -46,12 +46,11 @@
#[list_begin itemized]
package require Tcl 8.6-
package require punk::args
#*** !doctools
#[item] [package {Tcl 8.6}]
#[item] [package {punk::args}]
# #package require frobz
# #*** !doctools
# #[item] [package {frobz}]
#*** !doctools
#[list_end]
@ -457,16 +456,72 @@ namespace eval punk::basictelnet {
}
}
proc telnet {{server localhost} {port telnet}} {
punk::args::define {
@id -id ::punk::basictelnet::telnet
@cmd -name punk::basictelnet::telnet -help\
"Connect to a telnet server or other TCP based service.
The terminal can then be used to interact with the service.
"
-mode -choices {line raw} -default line
-mouse -type boolean -default 0 -help\
"Whether to enable mouse events"
@values -min 1 -max 2
server -type string -help\
"Hostname or IP address"
port -type integer -range {1 65535} -default 23 -help\
"TCP port"
}
proc telnet {args} {
set argd [punk::args::get_by_id ::punk::basictelnet::telnet $args]
set server [dict get $argd values server]
set port [dict get $argd values port]
set tmode [dict get $argd opts -mode]
set mouse [dict get $argd opts -mouse]
#todo - check for vt52 and don't try DEC queries
if {[info commands ::mode] eq ""} {
puts stderr "::mode command for terminal is unavailable - please set line/raw mode manually on the terminal"
} else {
set priormode [mode]
if {$tmode ne $priormode} {
::mode $tmode
}
}
if {[catch {set priormouse [punk::console::get_mode mouse_sgr]}]} {
set priormouse -1
if {$mouse} {
puts stderr "Cannot determine mouse_sgr mode - assuming terminal doesn't support mouse"
}
}
#decmode 1006 (SET_SGR_EXT_MODE_MOUSE)
#decmode 1016 (SET_PIXEL_POSITION_MOUSE)
#mouse_sgr 1 - mouse on
#mouse_sgr 2 - mouse off
if {$mouse} {
if {$priormouse eq "2"} {
punk::console::enable_mouse
}
} else {
if {$priormouse eq "1"} {
punk::console::disable_mouse
}
}
variable debug
variable consolewidth ;#note - this is not terminal width for the telnet output - which needs to be about 80cols narrower if debug is to be displayed on same screen
set consolewidth [dict get [punk::console::get_size] columns]
variable consolewidth 80 ;#note - this is not terminal width for the telnet output - which needs to be about 80cols narrower if debug is to be displayed on same screen
catch {set consolewidth [dict get [punk::console::get_size] columns]}
if {$consolewidth eq ""} {
#vt52?
set consolewidth 80
}
if {$debug && $consolewidth-$::punk::basictelnet::window_cols < 80} {
puts stderr "Terminal width not wide enough for debug_window width: 80 + telnet window_cols:$::punk::basictelnet::window_cols"
puts stderr "Terminal width '$consolewidth' not wide enough for debug_window width: 80 + telnet window_cols:$::punk::basictelnet::window_cols"
puts stderr "Turn off debug, or make terminal window wider"
return
} elseif {$consolewidth < $::punk::basictelnet::window_cols} {
puts stderr "Terminal width is less than telnet window_cols:$::punk::basictelnet::window_cols"
puts stderr "Terminal width '$consolewidth' is less than telnet window_cols:$::punk::basictelnet::window_cols"
puts stderr "Ensure terminal is greater than or equal to punk::basictelnet::window_cols"
return
}
@ -485,6 +540,16 @@ namespace eval punk::basictelnet {
vwait ::punk::basictelnet::closed($sock)
unset closed($sock)
chan conf stdin -blocking 1
if {[info commands ::mode] ne ""} {
::mode $priormode
}
if {$priormouse eq "2"} {
#mouse was off
punk::console::disable_mouse
} elseif {$priormouse eq "1"} {
punk::console::enable_mouse
}
}

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

@ -281,9 +281,9 @@ tcl::namespace::eval punk::blockletter::lib {
#use tstr when resolving params as a one-off at definition time
#versus slower -dynamic 1 if defaults/choices etc need to reflect the current state of the system.
#versus slower @dynamic if defaults/choices etc need to reflect the current state of the system.
punk::args::define [tstr -return string {
@id -id ::punk::blockletter::block
@id -id ::punk::blockletter::lib::block
-height -default 2
-width -default 4
-frametype -default {${$::punk::blockletter::default_frametype}}
@ -293,7 +293,7 @@ tcl::namespace::eval punk::blockletter::lib {
}]
proc block {args} {
upvar ::punk::blockletter::default_frametype ft
set argd [punk::args::get_by_id ::punk::blockletter::block $args]
set argd [punk::args::get_by_id ::punk::blockletter::lib::block $args]
set bg [dict get $argd opts -bgcolour]
set bd [dict get $argd opts -bordercolour]
set h [dict get $argd opts -height]

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

@ -2015,7 +2015,7 @@ tcl::namespace::eval punk::char {
# ------------------------------------------------------------------------------------------------------
proc grapheme_split_tk {string} {
if {![regexp "\[\uFF-\U10FFFF\]" $string]} {
#only ascii - no joiners or unicode
#only ascii (7 or 8 bit) - no joiners or unicode
return [split $string {}]
}
package require tk
@ -2068,14 +2068,14 @@ tcl::namespace::eval punk::char {
return $width
}
proc wcswidth_single {char} {
scan $c %c dec
if {$c <= 255 && !($c < 31 || $c == 127)} {
scan $char %c dec
if {$dec <= 255 && !($dec < 31 || $dec == 127)} {
#review - non-printing ascii? why does textutil::wcswidth report 1 ??
#todo - compare with python or other lang wcwidth
return 1
} elseif {$c < 917504 || $c > 917631} {
} elseif {$dec < 917504 || $dec > 917631} {
#TODO - various other joiners and non-printing chars
return [textutil::wcswidth_char $c]
return [textutil::wcswidth_char $dec] ;#note textutil::wcswidth_char takes a decimal codepoint!
#may return -1 - REVIEW
}
return 0
@ -2084,13 +2084,13 @@ tcl::namespace::eval punk::char {
set width 0
foreach c [split $string {}] {
scan $c %c dec
if {$c <= 255 && !($c < 31 || $c == 127)} {
if {$dec <= 255 && !($dec < 31 || $dec == 127)} {
#review - non-printing ascii? why does textutil::wcswidth report 1 ??
#todo - compare with python or other lang wcwidth
incr width
} elseif {$c < 917504 || $c > 917631} {
} elseif {$dec < 917504 || $dec > 917631} {
#TODO - various other joiners and non-printing chars
set w [textutil::wcswidth_char $c]
set w [textutil::wcswidth_char $dec] ;#takes decimal codepoint
if {$w < 0} {
return -1
} else {
@ -2117,14 +2117,14 @@ tcl::namespace::eval punk::char {
set chunk [tcl::string::range $string $startidx $endidx]
set codes [scan $chunk [tcl::string::repeat %c [tcl::string::length $chunk]]]
foreach c $codes {
if {$c <= 255 && !($c < 31 || $c == 127)} {
foreach dec $codes {
if {$dec <= 255 && !($dec < 31 || $dec == 127)} {
#review - non-printing ascii? why does textutil::wcswidth report 1 ??
#todo - compare with python or other lang wcwidth
incr width
} elseif {$c < 917504 || $c > 917631} {
} elseif {$dec < 917504 || $dec > 917631} {
#TODO - various other joiners and non-printing chars
set w [textutil::wcswidth_char $c]
set w [textutil::wcswidth_char $dec]
if {$w < 0} {
return -1
} else {
@ -2145,18 +2145,18 @@ tcl::namespace::eval punk::char {
#TODO
set codes [scan $string [tcl::string::repeat %c [tcl::string::length $string]]]
set width 0
foreach c $codes {
foreach dec $codes {
#unicode Tags block zero width
if {$c < 917504 || $c > 917631} {
if {$c <= 255} {
if {$dec < 917504 || $dec > 917631} {
if {$dec <= 255} {
#review - non-printing ascii? why does textutil::wcswidth report 1 ??
#todo - compare with python or other lang wcwidth
if {!($c < 31 || $c == 127)} {
if {!($dec < 31 || $dec == 127)} {
incr width
}
} else {
#TODO - various other joiners and non-printing chars
set w [textutil::wcswidth_char $c]
set w [textutil::wcswidth_char $dec] ;#takes decimal codepoint
if {$w < 0} {
return -1
} else {
@ -2169,7 +2169,7 @@ tcl::namespace::eval punk::char {
}
proc wcswidth2 {string} {
set codes [scan $string [tcl::string::repeat %c [tcl::string::length $string]]]
set widths [lmap c $codes {textutil::wcswidth_char $c}]
set widths [lmap dec $codes {textutil::wcswidth_char $dec}]
if {-1 in $widths} {
return -1
}

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

File diff suppressed because it is too large Load Diff

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

@ -246,6 +246,58 @@ tcl::namespace::eval punk::lib::compat {
#outside of lmap - don't know of any particularly nice ways to flatten to subindex 1 of each element..
#flattening then lsearch with -stride and * would be nice - but it's not avail in 8.6
if {![info exists ::auto_index(readFile)]} {
if {[info commands ::readFile] eq ""} {
proc ::readFile {filename {mode text}} {
#readFile not seen in auto_index or as command: installed by punk::lib
# Parse the arguments
set MODES {binary text}
set ERR [list -level 1 -errorcode [list TCL LOOKUP MODE $mode]]
set mode [tcl::prefix match -message "mode" -error $ERR $MODES $mode]
# Read the file
set f [open $filename [dict get {text r binary rb} $mode]]
try {
return [read $f]
} finally {
close $f
}
}
}
}
if {![info exists ::auto_index(writeFile)]} {
if {[info commands ::writeFile] eq ""} {
proc ::writeFile {args} {
#writeFile not seen in auto_index or as command: installed by punk::lib
# Parse the arguments
switch [llength $args] {
2 {
lassign $args filename data
set mode text
}
3 {
lassign $args filename mode data
set MODES {binary text}
set ERR [list -level 1 -errorcode [list TCL LOOKUP MODE $mode]]
set mode [tcl::prefix match -message "mode" -error $ERR $MODES $mode]
}
default {
set COMMAND [lindex [info level 0] 0]
return -code error -errorcode {TCL WRONGARGS} "wrong # args: should be \"$COMMAND filename ?mode? data\""
}
}
# Write the File
set f [open $filename [dict get {text w binary wb} $mode]]
try {
puts -nonewline $f $data
} finally {
close $f
}
}
}
}
if {"::lpop" ne [info commands ::lpop]} {
#puts stderr "Warning - no built-in lpop"
interp alias {} lpop {} ::punk::lib::compat::lpop
@ -1021,7 +1073,8 @@ namespace eval punk::lib {
-separator -default "%sep%"
-roottype -default "dict"
-substructure -default {}
-channel -default stdout -help "existing channel - or 'none' to return as string"
-channel -default stdout -help\
"existing channel - or 'none' to return as string"
@values -min 1 -max -1
@ -1049,7 +1102,6 @@ namespace eval punk::lib {
Here we supply 2 separate pattern hierarchies, where @0-end and @* are list operations and are equivalent
The second level segement in each pattern switches to a dict operation to retrieve the value by key.
When a list operation such as @* is used - integer list indexes are displayed on the left side of the = for that hierarchy level.
}
}]
#puts stderr "$argspec"
@ -1091,7 +1143,8 @@ namespace eval punk::lib {
set sep " [punk::ansi::a+ Green]=$RST " ;#stick to basic default colours for wider terminal support
set sep_mismatch " [punk::ansi::a+ Brightred undercurly underline undt-white]mismatch$RST "
}
package require punk ;#we need pipeline pattern matching features
package require punk::pipe
#package require punk ;#we need pipeline pattern matching features
package require textblock
set argd [punk::args::get_dict [string map [list %sep% $sep %sep_mismatch% $sep_mismatch] {
@ -1103,22 +1156,28 @@ namespace eval punk::lib {
-trimright -default 1 -type boolean -help\
"Trim whitespace off rhs of each line.
This can help prevent a single long line that wraps in terminal from making
every line wrap due to long rhs padding.
"
-separator -default {%sep%} -help "Separator column between keys and values"
-separator_mismatch -default {%sep_mismatch%} -help "Separator to use when patterns mismatch"
-roottype -default "dict" -help "list,dict,string"
-ansibase_keys -default "" -help "ansi list for each level in -substructure. e.g \[list \[a+ red\] \[a+ web-green\]\]"
every line wrap due to long rhs padding."
-separator -default {%sep%} -help\
"Separator column between keys and values"
-separator_mismatch -default {%sep_mismatch%} -help\
"Separator to use when patterns mismatch"
-roottype -default "dict" -help\
"list,dict,string"
-ansibase_keys -default "" -help\
"ansi list for each level in -substructure. e.g \[list \[a+ red\] \[a+ web-green\]\]"
-substructure -default {}
-ansibase_values -default ""
-keytemplates -default {\$\{$key\}} -type list -help "list of templates for keys at each level"
-keytemplates -default {\$\{$key\}} -type list -help\
"list of templates for keys at each level"
-keysorttype -default "none" -choices {none dictionary ascii integer real}
-keysortdirection -default increasing -choices {increasing decreasing}
-debug -default 0 -type boolean -help\
"When enabled, produces some rudimentary debug output on stderr"
@values -min 1 -max -1
dictvalue -type list -help "dict or list value"
patterns -default "*" -type string -multiple 1 -help "key or key glob pattern"
dictvalue -type list -help\
"dict or list value"
patterns -default "*" -type string -multiple 1 -help\
"key or key glob pattern"
}] $args]
#for punk::lib - we want to reduce pkg dependencies.
@ -1201,7 +1260,7 @@ namespace eval punk::lib {
set segments [split $pattern_nest /]
set levelpatterns [lindex $segments 0] ;#possibly comma separated patterns
#we need to use _split_patterns to separate (e.g to protect commas that appear within quotes)
set patterninfo [punk::_split_patterns $levelpatterns]
set patterninfo [punk::pipe::lib::_split_patterns $levelpatterns]
#puts stderr "showdict-->_split_patterns: $patterninfo"
foreach v_idx $patterninfo {
lassign $v_idx v idx
@ -1479,7 +1538,7 @@ namespace eval punk::lib {
# -- --- --- ---
set substructure ""
set pnext [lindex $segments 1]
set patterninfo [punk::_split_patterns $levelpatterns]
set patterninfo [punk::pipe::lib::_split_patterns $levelpatterns]
if {[llength $patterninfo] == 0} {
# // ? -review - what does this mean? for xpath this would mean at any level
set substructure [lindex $pattern_this_structure end]
@ -2043,17 +2102,31 @@ namespace eval punk::lib {
concat {*}[uplevel 1 lmap {*}$args]
}
#proc dict_getdef {dictValue args} {
# if {[llength $args] < 1} {
# error {wrong # args: should be "dict_getdef dictValue ?key ...? key default"}
# }
# set keys [lrange $args -1 end-1]
# if {[tcl::dict::exists $dictValue {*}$keys]} {
# return [tcl::dict::get $dictValue {*}$keys]
# } else {
# return [lindex $args end]
# }
#}
if {[info commands ::tcl::dict::getdef] eq ""} {
proc dict_getdef {dictValue args} {
if {[llength $args] < 1} {
error {wrong # args: should be "dict_getdef dictValue ?key ...? key default"}
}
set keys [lrange $args -1 end-1]
set keys [lrange $args 0 end-1]
if {[tcl::dict::exists $dictValue {*}$keys]} {
return [tcl::dict::get $dictValue {*}$keys]
} else {
return [lindex $args end]
}
}
} else {
#we pay a minor perf penalty for the wrap
interp alias "" ::punk::lib::dict_getdef "" ::tcl::dict::getdef
}
#proc sample1 {p1 n args} {
# #*** !doctools
@ -2722,6 +2795,7 @@ namespace eval punk::lib {
}
return [join $result \n]
}
#dedent?
proc undent {text} {
if {$text eq ""} {
return ""
@ -4142,12 +4216,10 @@ tcl::namespace::eval punk::lib::debug {
proc showdict {args} {}
}
if {![info exists ::punk::args::register::NAMESPACES]} {
namespace eval ::punk::args::register {
set ::punk::args::register::NAMESPACES [list] ;#use fully qualified so 8.6 doesn't find existing var in global namespace
}
}
#use fully qualified so 8.6 doesn't find existing var in global namespace
lappend ::punk::args::register::NAMESPACES ::punk::lib
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide punk::lib [tcl::namespace::eval punk::lib {

21
src/modules/punk/mix/cli-999999.0a1.0.tm

@ -177,7 +177,8 @@ namespace eval punk::mix::cli {
}
}
}
cd $sourcefolder
#cd $sourcefolder
#use run so that stdout visible as it goes
if {![catch {run --timeout=55000 -debug [info nameofexecutable] $sourcefolder/make.tcl {*}$args} exitinfo]} {
#todo - notify if exit because of timeout!
@ -185,11 +186,11 @@ namespace eval punk::mix::cli {
set exitcode [dict get $exitinfo exitcode]
} else {
puts stderr "Error unable to determine exitcode. err: $exitinfo"
cd $startdir
#cd $startdir
return false
}
cd $startdir
#cd $startdir
if {$exitcode != 0} {
puts stderr "FAILED with exitcode $exitcode"
return false
@ -364,10 +365,10 @@ namespace eval punk::mix::cli {
#ignore trailing .tm .TM if present
#if version doesn't pass validation - treat it as part of the modulename and return empty version string without error
#Up to caller to validate.
proc split_modulename_version {modulename} {
set lastpart [namespace tail $modulename]
proc split_modulename_version {fullmodulename} {
set lastpart [namespace tail $fullmodulename]
set lastpart [file tail $lastpart] ;# should be ok to use file tail now that we've ensured no namespace components
if {[string equal -nocase [file extension $modulename] ".tm"]} {
if {[string equal -nocase [file extension $fullmodulename] ".tm"]} {
set fileparts [split [file rootname $lastpart] -]
} else {
set fileparts [split $lastpart -]
@ -380,7 +381,13 @@ namespace eval punk::mix::cli {
set namesegment [join $fileparts -]
set versionsegment ""
}
return [list $namesegment $versionsegment]
set base [namespace qualifiers $fullmodulename]
if {$base ne ""} {
set modulename "${base}::$namesegment"
} else {
set modulename $namesegment
}
return [list $modulename $versionsegment]
}
proc get_status {{workingdir ""} args} {

36
src/modules/punk/mix/commandset/layout-999999.0a1.0.tm

@ -31,16 +31,42 @@ namespace eval punk::mix::commandset::layout {
namespace export *
namespace eval argdoc {
proc layout_names {} {
if {[catch {punk::mix::commandset::layout::lib::layouts_dict *} ldict]} {
#REVIEW
return "punk.project"
} else {
return [dict keys $ldict]
}
}
}
#per layout functions
proc files {{layout ""}} {
set argd [punk::args::get_dict {
punk::args::define {
@dynamic
@id -id ::punk::mix::commandset::layout::files
-datetime -default "%Y-%m-%dT%H:%M:%S" -help\
"Datetime format for mtime. Use empty string for no datetime output"
@values -min 1 -max 1
layout -type string -minsize 1
} [list $layout]]
layout -type string -choices {${[punk::mix::commandset::layout::argdoc::layout_names]}}
}
proc files {args} {
set argd [punk::args::get_by_id ::punk::mix::commandset::layout::files $args]
set layout [dict get $argd values layout]
set dtformat [dict get $argd opts -datetime]
set allfiles [lib::layout_all_files $layout]
if {$dtformat eq ""} {
return [join $allfiles \n]
} else {
set out ""
foreach f $allfiles {
set mtime [dict get [file stat $f] mtime]
append out "$f [clock format $mtime -format $dtformat]" \n
}
set out [string range $out 0 end-1]
return $out
}
}
proc templatefiles {layout} {
set templatefiles_and_tags [lib::layout_scan_for_template_files $layout]
@ -166,7 +192,7 @@ namespace eval punk::mix::commandset::layout {
}
proc as_dict {args} {
tailcall punk::mix::commandset::layout::lib::layouts_dict {*}$args
punk::mix::commandset::layout::lib::layouts_dict {*}$args
}
proc references_as_dict {args} {
package require punk::cap

13
src/modules/punk/mix/commandset/loadedlib-999999.0a1.0.tm

@ -1,5 +1,5 @@
# -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'deck make' or src/make.tcl to update from <pkg>-buildversion.txt
#
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
@ -304,11 +304,12 @@ namespace eval punk::mix::commandset::loadedlib {
}
set versions [package versions [lindex $libfound 0]]
if {$has_natsort} {
set versions [natsort::sort $versions]
} else {
set versions [lsort $versions]
}
set versions [lsort -command {package vcompare} $versions]
#if {$has_natsort} {
# set versions [natsort::sort $versions]
#} else {
# set versions [lsort $versions]
#}
if {![llength $versions]} {
error "No version numbers found for library/module $libfound - sorry, you will need to copy it across manually"
}

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

@ -120,17 +120,20 @@ namespace eval punk::mix::commandset::module {
return $table
}
#return all module templates with repeated ones suffixed with .2 .3 etc
proc templates_dict {args} {
set argspec {
#return all module templates with repeated ones suffixed with #2 #3 etc
punk::args::define {
@id -id ::punk::mix::commandset::module::templates_dict
@cmd -name templates_dict -help "Templates from module and project paths"
-startdir -default "" -help "Project folder used in addition to module paths"
@cmd -name templates_dict -help\
"Templates from module and project paths"
-startdir -default "" -help\
"Project folder used in addition to module paths"
-not -default "" -multiple 1
@values
globsearches -default * -multiple 1
}
set argd [punk::args::get_dict $argspec $args]
proc templates_dict {args} {
set argd [punk::args::get_by_id ::punk::mix::commandset::module::templates_dict $args]
package require punk::cap
if {[punk::cap::capability_has_handler punk.templates]} {
set template_folder_dict [punk::cap::call_handler punk.templates get_itemdict_moduletemplates {*}$args]
@ -154,10 +157,11 @@ namespace eval punk::mix::commandset::module {
the higher version number will be used.
"
-license -default <unspecified>
-author -default <unspecified> -multiple 1
-template -default punk.module
-type -default "[lindex $moduletypes 0]" -choices {$moduletypes}
-force -default 0 -type boolean -help\
"If set true, will overwrite an existing .tm file if there is one.
"If set true, will OVERWRITE an existing .tm file if there is one.
If false (default) an error will be raised if there is a conflict."
-quiet -default 0 -type boolean -help\
"Suppress information messages on stdout"
@ -262,6 +266,7 @@ namespace eval punk::mix::commandset::module {
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
set opt_license [dict get $opts -license]
set opt_authors [dict get $opts -author] ;#-multiple true
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
set opt_template [dict get $opts -template]
if {[regexp {.*[?*].*} $opt_template]} {
@ -403,7 +408,7 @@ namespace eval punk::mix::commandset::module {
#for now the user has the option to override any templates and remove %moduletemplate% if it is a security/privacy concern
#Don't put literal %x% in the code for the commandset::module itself - to stop them being seen by layout scanner as replacable tokens
set tagnames [list moduletemplate $moduletemplate project $projectname pkg $modulename year $year license $opt_license version $infile_version]
set tagnames [list moduletemplate $moduletemplate project $projectname pkg $modulename year $year license $opt_license authors $opt_authors version $infile_version]
set strmap [list]
foreach {tag val} $tagnames {
lappend strmap %$tag% $val

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

@ -109,7 +109,26 @@ namespace eval punk::mix::commandset::project {
}
namespace eval argdoc {
set layout_dict [punk::cap::call_handler punk.templates get_itemdict_projectlayouts]
variable LAYOUTNAMES [dict keys $layout_dict]
}
punk::args::define {
@id -id ::punk::mix::commandset::project::new
@cmd -name "punk::mix::commandset::project::new" -help\
""
@leaders -min 1 -max 1
project -type string -help\
"Project name or path.
If just a name is given ... (todo)"
@opts
-type -default plain
-empty -default 0 -type boolean
-force -default 0 -type boolean
-update -default 0 -type boolean
-confirm -default 1 -type boolean
-layout -default "punk.project" -choices {${$::punk::mix::commandset::project::argdoc::LAYOUTNAMES}}
}
proc new {newprojectpath_or_name args} {
#*** !doctools
@ -300,7 +319,17 @@ namespace eval punk::mix::commandset::project {
}
}
} elseif {$project_dir_exists && $opt_update} {
puts stderr "mix new WARNING: -update 1 was supplied. Will copy layout $layout_path using -update option to add missing items"
set warnmsg "mix new WARNING: -update 1 was supplied. Will copy layout $layout_path using -update option to add missing items"
if {$opt_confirm} {
puts stderr $warnmsg
set msg "Do you want to proceed to possibly overwrite some existing files in $projectdir? Y|N"
set answer [util::askuser $msg]
if {[string tolower $answer] ne "y"} {
puts stderr "mix new aborting due to user response '$answer' (required Y|y to proceed) use -confirm 0 to avoid prompt."
return
}
}
puts stderr $warnmsg
}
set fossil_repo_file ""
@ -366,28 +395,40 @@ namespace eval punk::mix::commandset::project {
#default antiglob_dir_core will stop .fossil* from being updated - which is generally desirable as these are likely to be customized
if {$opt_force} {
puts stdout "copying layout files - with force applied - overwrite all-targets"
set resultdict [punkcheck::install $layout_path $projectdir -installer project.new -overwrite ALL-TARGETS -antiglob_paths $antipaths -antiglob_dir $antiglob_dir]
set resultdict [punkcheck::install $layout_path $projectdir -installer project.new -createempty 1 -overwrite ALL-TARGETS -antiglob_paths $antipaths -antiglob_dir $antiglob_dir]
} else {
puts stdout "copying layout files - (if source file changed)"
set resultdict [punkcheck::install $layout_path $projectdir -installer project.new -overwrite installedsourcechanged-targets -antiglob_paths $antipaths -antiglob_dir $antiglob_dir]
set resultdict [punkcheck::install $layout_path $projectdir -installer project.new -createempty 1 -overwrite installedsourcechanged-targets -antiglob_paths $antipaths -antiglob_dir $antiglob_dir]
}
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
if {[file exists $layout_path/src/doc]} {
puts stdout "copying layout src/doc files (if target missing)"
set resultdict [punkcheck::install $layout_path/src/doc $projectdir/src/doc -punkcheck_folder $projectdir -installer project.new -overwrite SYNCED-TARGETS]
set resultdict [punkcheck::install $layout_path/src/doc $projectdir/src/doc -createdir 1 -punkcheck_folder $projectdir -installer project.new -overwrite SYNCED-TARGETS]
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
} else {
puts stdout "no src/doc in source template - update not required"
}
#target folders .fossil-custom and .fossil-settings may not exist. use -createdir 1 to ensure existence.
#In this case we need to override the default dir antiglob - as .fossil-xxx folders need to be installed from template if missing, or if target is uncustomized.
## default_antiglob_dir_core [list "#*" "_aside" ".git" ".fossil*"]
set override_antiglob_dir_core [list #* _aside .git]
if {[file exists $layout_path/.fossil-custom]} {
puts stdout "copying layout src/.fossil-custom files (if target missing or uncustomised)"
set resultdict [punkcheck::install $layout_path/.fossil-custom $projectdir/.fossil-custom -createdir 1 -punkcheck_folder $projectdir -installer project.new -antiglob_dir_core $override_antiglob_dir_core -overwrite SYNCED-TARGETS]
set resultdict [punkcheck::install $layout_path/.fossil-custom $projectdir/.fossil-custom -createdir 1 -createempty 1 -punkcheck_folder $projectdir -installer project.new -antiglob_dir_core $override_antiglob_dir_core -overwrite SYNCED-TARGETS]
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
} else {
puts stdout "no .fossil-custom in source template - update not required"
}
if {[file exists $layout_path/.fossil-settings]} {
puts stdout "copying layout src/.fossil-settings files (if target missing or uncustomised)"
set resultdict [punkcheck::install $layout_path/.fossil-settings $projectdir/.fossil-settings -createdir 1 -punkcheck_folder $projectdir -installer project.new -antiglob_dir_core $override_antiglob_dir_core -overwrite SYNCED-TARGETS]
set resultdict [punkcheck::install $layout_path/.fossil-settings $projectdir/.fossil-settings -createdir 1 -createempty 1 -punkcheck_folder $projectdir -installer project.new -antiglob_dir_core $override_antiglob_dir_core -overwrite SYNCED-TARGETS]
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
} else {
puts stdout "no .fossil-settings in source template - update not required"
}
#scan all files in template
#

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

@ -281,7 +281,8 @@ tcl::namespace::eval punk::nav::fs {
}
}
if {[file pathtype $a1] ne "relative"} {
if {![regexp {[*?]} $a1] && [file pathtype $a1] ne "relative"} {
#non-relative non-glob
if { ![string match //zipfs:/* $a1]} {
if {[file type $a1] eq "directory"} {
cd $a1

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

@ -26,9 +26,16 @@ tcl::namespace::eval ::punk::ns::evaluator {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::ns {
variable ns_current "::"
variable ns_current
#allow presetting
if {![info exists ::punk::ns::ns_current]} {
set ns_current ::
}
variable ns_re_cache [dict create] ;#cache regular expressions used in globmatchns
namespace export nsjoin nsprefix nstail nsparts nseval nschildren nsimport_noclobber corp
namespace path {::punk::lib ::punk::ansi} ;#list_as_lines etc
catch {
package require debug
debug define punk.ns.compile
@ -1259,7 +1266,7 @@ tcl::namespace::eval punk::ns {
} else {
set report_namespaces $matched_namespaces
}
punk::args::update_definitions
punk::args::update_definitions $report_namespaces
set nsdict_list [list]
foreach ch $report_namespaces {
@ -1371,9 +1378,9 @@ tcl::namespace::eval punk::ns {
#use aliases glob - because aliases can be present with or without leading ::
#NOTE: alias may not have matching command in the relevant namespce (renamed alias) so we can't just start with commands and check if it's an alias if we want to show all aliases
if {$weird_ns} {
set raw_aliases [nseval_ifexists $location [list ::aliases $glob]] ;#'aliases $glob' must be passed as list, not separate args to namespace eval.
set raw_aliases [nseval_ifexists $location [list ::punk::lib::aliases $glob]] ;#'aliases $glob' must be passed as list, not separate args to namespace eval.
} else {
set raw_aliases [tcl::namespace::eval $location [list ::aliases $glob]] ;#'aliases $glob' must be passed as list, not separate args to namespace eval.
set raw_aliases [tcl::namespace::eval $location [list ::punk::lib::aliases $glob]] ;#'aliases $glob' must be passed as list, not separate args to namespace eval.
}
#set raw_aliases [nseval $location [list ::aliases $glob]] ;#'aliases $glob' must be passed as list, not separate args to namespace eval.
set aliases [list]
@ -1620,6 +1627,7 @@ tcl::namespace::eval punk::ns {
if {$has_punkargs} {
#set id [string trimleft $fq :]
set id $fq
punk::args::update_definitions [list [namespace qualifiers $id]]
if {[::punk::args::id_exists $id]} {
lappend usageinfo $c
} else {
@ -1969,7 +1977,8 @@ tcl::namespace::eval punk::ns {
#todo - -cache or -refresh to configure whether we introspect ensembles/objects each time?
# - as this is interactive generally introspection should be ok at the top level
# but if we need to go down multiple levels of subcommands generating/testing prefixes - could be an issue ??
punk::args::define -dynamic 0 {
punk::args::define {
@dynamic
@id -id ::punk::ns::arginfo
@cmd -name punk::ns::arginfo -help\
"Show usage info for a command.
@ -1995,7 +2004,7 @@ tcl::namespace::eval punk::ns {
-return -type string -default table -choices {string table tableobject}
} {${[punk::args::resolved_def -type @opts ::punk::args::arg_error -scheme]}} {
} {${[punk::args::resolved_def -types opts ::punk::args::arg_error -scheme]}} {
-- -type none -help\
"End of options marker
@ -2008,7 +2017,7 @@ tcl::namespace::eval punk::ns {
Multiple subcommands can be supplied if ensembles are further nested"
}
proc arginfo {args} {
lassign [dict values [punk::args::get_by_id ::punk::ns::arginfo $args]] leaders opts values received
lassign [dict values [punk::args::parse $args withid ::punk::ns::arginfo]] leaders opts values received
#review - setting this afterwards is an architecture smell - we should be able to override the default in the dynamic part
#todo - enable retrieving by id just the record_opts part - so we can treat as a dict directly, as well as easily apply it as a different flag name.
@ -2018,7 +2027,6 @@ tcl::namespace::eval punk::ns {
set querycommand [dict get $values commandpath]
set queryargs [dict get $values subcommand]
punk::args::update_definitions ;#ensure any packages that register PUNKARGS have been loaded
#todo - similar to corp? review corp resolution process
@ -2087,6 +2095,16 @@ tcl::namespace::eval punk::ns {
}
}
#check for a direct match first
if {[info commands ::punk::args::id_exists] ne ""} {
if {![llength $queryargs]} {
punk::args::update_definitions [list [namespace qualifiers $origin]]
if {[punk::args::id_exists $origin]} {
return [uplevel 1 [list punk::args::usage {*}$opts $origin]]
}
}
}
#ns::cmdtype only detects alias type on 8.7+?
set initial_cmdtype [punk::ns::cmdtype $origin]
switch -- $initial_cmdtype {
@ -2137,31 +2155,40 @@ tcl::namespace::eval punk::ns {
set id $origin
if {[info commands ::punk::args::id_exists] ne ""} {
#cycle through longest first checking for id matching ::cmd ?subcmd..?
#REVIEW - this doesn't cater for prefix callable subcommands!
#check longest first checking for id matching ::cmd ?subcmd..?
#REVIEW - this doesn't cater for prefix callable subcommands
set argcopy $queryargs
while {[llength $argcopy]} {
if {[punk::args::id_exists [list $id {*}$argcopy]]} {
return [uplevel 1 [list punk::args::usage {*}$opts [list $id {*}$argcopy]]]
if {[llength $queryargs]} {
punk::args::update_definitions [list [namespace qualifiers $id]]
if {[punk::args::id_exists [list $id {*}$queryargs]]} {
return [uplevel 1 [list punk::args::usage {*}$opts [list $id {*}$queryargs]]]
}
lpop argcopy
}
#while {[llength $argcopy]} {
# if {[punk::args::id_exists [list $id {*}$argcopy]]} {
# return [uplevel 1 [list punk::args::usage {*}$opts [list $id {*}$argcopy]]]
# }
# lpop argcopy
#}
#didn't find any exact matches
#traverse from other direction taking prefixes into account
punk::args::update_definitions [list [namespace qualifiers $id]]
if {[punk::args::id_exists $id]} {
#cycle forward through leading values
set def [punk::args::get_def $id]
set spec [punk::args::get_spec $id]
if {[llength $queryargs]} {
set nextqueryargs [list] ;#build a list of prefix-resolved queryargs
set queryargs_untested $queryargs
foreach q $queryargs {
if {[llength [dict get $def LEADER_NAMES]]} {
set subitems [dict get $def LEADER_NAMES]
if {[llength [dict get $spec LEADER_NAMES]]} {
set subitems [dict get $spec LEADER_NAMES]
if {[llength $subitems]} {
set next [lindex $subitems 0]
set arginfo [dict get $def ARG_INFO $next]
set arginfo [dict get $spec ARG_INFO $next]
set allchoices [list]
set choices [punk::args::system::Dict_getdef $arginfo -choices {}]
@ -2184,16 +2211,22 @@ tcl::namespace::eval punk::ns {
#we have our first difference - recurse with new query args
#set numvals [expr {[llength $queryargs]+1}]
#return [ punk::ns::arginfo {*}[lrange $args 0 end-$numvals] $querycommand {*}$nextqueryargs {*}$queryargs_untested]
#puts "===> testing arginfo {*}$opts $querycommand {*}$nextqueryargs {*}$queryargs_untested"
return [ punk::ns::arginfo {*}$opts $querycommand {*}$nextqueryargs {*}$queryargs_untested]
}
#check if subcommands so far have a custom args def
set currentid [list $querycommand {*}$nextqueryargs]
#set currentid [list $querycommand {*}$nextqueryargs]
set currentid [list $id {*}$nextqueryargs]
if {[punk::args::id_exists $currentid]} {
set def [punk::args::get_def $currentid
set spec [punk::args::get_spec $currentid]
} else {
#We can get no further with custom defs
#It is possible we have a documented lower level subcommand but missing the intermediate
#e.g if ::trace remove command was specified and is documented - it will be found above
#but if ::trace remove is not documented and the query is "::trace remove com"
#There is no way to determine com is a prefix as we don't have the intermediate documented -choice info available.
#that's probably ok.
break
}
}
@ -2307,7 +2340,7 @@ tcl::namespace::eval punk::ns {
set implementations [::info object call $origin $c1]
#result documented as list of 4 element lists
#set callinfo [lindex $implementations 0]
set def ""
set oodef ""
foreach impl $implementations {
lassign $impl generaltype mname location methodtype
switch -- $generaltype {
@ -2323,7 +2356,7 @@ tcl::namespace::eval punk::ns {
return [uplevel 1 [list punk::args::usage {*}$opts $idcustom]]
}
}
set def [::info object definition $origin $c1]
set oodef [::info object definition $origin $c1]
} else {
#set id "[string trimleft $location :] $c1" ;# "<class> <method>"
set idcustom "$location $c1"
@ -2332,7 +2365,7 @@ tcl::namespace::eval punk::ns {
return [uplevel 1 [list punk::args::usage {*}$opts $idcustom]]
}
}
set def [::info class definition $location $c1]
set oodef [::info class definition $location $c1]
}
break
}
@ -2342,10 +2375,9 @@ tcl::namespace::eval punk::ns {
}
}
}
if {$def ne ""} {
#assert - if we pre
if {$oodef ne ""} {
set autoid "(autodef)$location $c1"
set arglist [lindex $def 0]
set arglist [lindex $oodef 0]
set argdef [punk::lib::tstr -return string {
@id -id "${$autoid}"
@cmd -name "${$location} ${$c1}" -help\
@ -2368,7 +2400,7 @@ tcl::namespace::eval punk::ns {
append argdef \n "[lindex $a 0] -default {[lindex $a 1]} -optional 1"
}
default {
error "punk::ns::arginfo unexpected argument signature '$arglist'\ndef:$def\nimplementaions:$implementations"
error "punk::ns::arginfo unexpected oo argument signature '$arglist'\noodef:$oodef\nimplementations:$implementations"
}
}
incr i
@ -2427,7 +2459,7 @@ tcl::namespace::eval punk::ns {
@id -id ${$idauto}
@cmd -name "Object: ${$origin}" -help\
"Instance of class: ${$class} (info autogenerated)"
@values -min 1
@leaders -min 1
}]
append argdef \n $vline
punk::args::define $argdef
@ -2542,7 +2574,7 @@ tcl::namespace::eval punk::ns {
@cmd -help\
"(autogenerated)
ensemble: ${$origin}"
@values -min 1
@leaders -min 1
}]
append argdef \n $vline
punk::args::define $argdef
@ -2977,44 +3009,58 @@ tcl::namespace::eval punk::ns {
If not supplied, caller's namespace is used."
-prefix -optional 1 -help\
"string prefix for command names in target namespace"
@values -min 1 -max 1
sourcepattern -type string -optional 0 -help\
"Glob pattern for source namespace.
@values -min 1 -max -1
sourcepattern -type string -optional 0 -multiple 1 -help\
"Glob pattern(s) for exported commands in source namespace(s).
Globbing only active in the tail segment.
e.g ::mynamespace::*"
e.g ::mynamespace::a* ::mynamespace::j*"
}
proc nsimport_noclobber {args} {
lassign [dict values [punk::args::get_by_id ::punk::ns::nsimport_noclobber $args]] leaders opts values received
set sourcepattern [dict get $values sourcepattern]
set sourcepatterns [dict get $values sourcepattern]
set source_ns [tcl::namespace::qualifiers $sourcepattern]
if {![tcl::namespace::exists $source_ns]} {
error "nsimport_noclobber error namespace $source_ns not found"
}
set nscaller [uplevel 1 {namespace current}]
if {![dict exists $received -targetnamespace]} {
set target_ns $nscaller
} else {
set target_ns [dict get $opts -targetnamespace]
if {![string match ::* $target_ns]} {
set target_ns [punk::nsjoin $nscaller $target_ns]
set target_ns [punk::ns::nsjoin $nscaller $target_ns]
}
}
set all_imported [list]
set nstemp ::punk::ns::temp_import
foreach pat $sourcepatterns {
set source_ns [tcl::namespace::qualifiers $pat]
if {![tcl::namespace::exists $source_ns]} {
error "nsimport_noclobber error namespace $source_ns not found"
}
set a_export_patterns [tcl::namespace::eval $source_ns {namespace export}]
set a_commands [info commands $sourcepattern]
set a_commands [info commands $pat]
#puts "-->commands:'$a_commands'"
set a_tails [lmap v $a_commands {tcl::namespace::tail $v}]
set a_exported_tails [list]
foreach epattern $a_export_patterns {
set matches [lsearch -all -inline $a_tails $epattern]
foreach m $matches {
#we will be using namespace import <pattern> one by one on commands.
#we must protect glob chars that may exist in the actual command names.
#e.g nsimport_noclobber ::punk::ansi::a?
# will import a+ and a?
#but nsimport_noclobber {::punk::ansi::a\?}
# must import only a?
set m [string map {\\ \\\\ ? \\? * \\* \[ \\[ \] \\]} $m]
if {$m ni $a_exported_tails} {
lappend a_exported_tails $m
}
}
}
set nstemp ::punk::ns::temp_import
if {[tcl::dict:::exists $received -prefix]} {
#import via temporary/intermediate namespace
set pfx [dict get $opts -prefix]
set imported_commands [list]
if {[namespace exists $nstemp]} {
@ -3022,39 +3068,41 @@ tcl::namespace::eval punk::ns {
}
namespace eval $nstemp {}
foreach e $a_exported_tails {
set imported [tcl::namespace::eval $nstemp [string map [list <func> $e <a> $source_ns <pfx> $pfx <tgtns> $target_ns] {
set imported [apply {{tgtns func srcns pfx tmpns} {
set cmd ""
if {![catch {namespace import <a>::<func>}]} {
if {![catch {::tcl::namespace::eval $tmpns [list ::namespace import ${srcns}::$func]}]} {
#renaming will fail if target already exists
#renaming a command into another namespace still results in a command with 'info cmdtype' = 'import'
if {![catch {rename <func> [punk::ns::nsjoin <tgtns> <pfx><func>]}]} {
set cmd <pfx><func>
if {![catch {::tcl::namespace::eval $tmpns [list ::rename $func [::punk::ns::nsjoin $tgtns $pfx$func]]}]} {
set cmd $pfx$func
}
}
set cmd
}]]
} } $target_ns $e $source_ns $pfx $nstemp]
if {$imported ne ""} {
lappend imported_commands $imported
}
}
namespace delete $nstemp
return $imported_commands
}
} else {
#no prefix - direct import
set imported_commands [list]
foreach e $a_exported_tails {
set imported [tcl::namespace::eval $target_ns [string map [list <func> $e <a> $source_ns] {
set imported [apply {{tgtns func srcns} {
set cmd ""
if {![catch {namespace import <a>::<func>}]} {
set cmd <func>
if {![catch {tcl::namespace::eval $tgtns [list namespace import ${srcns}::$func]}]} {
set cmd $func
}
set cmd
}]]
if {[string length $imported]} {
} } $target_ns $e $source_ns]
if {$imported ne ""} {
lappend imported_commands $imported
}
}
return $imported_commands
}
lappend all_imported {*}$imported_commands
}
return $all_imported
}
#todo - use ns::nsimport_noclobber instead ?
@ -3092,7 +3140,23 @@ tcl::namespace::eval punk::ns {
interp alias {} corp {} punk::ns::corp
interp alias {} i {} punk::ns::arginfo
#An example of using punk::args in a pipeline
punk::args::define {
@id -id ::i+
@cmd -name "i+" -help\
"Display command help side by side"
@values
cmds -multiple 1 -help\
"Command names for which to show help info"
}
interp alias {} i+ {}\
.=args> punk::args::get_by_id ::i+ |argd>\
.=>2 dict get values cmds |cmds>\
.=cmds>2 lmap c {i -return tableobject {*}$c} |objs>\
.=objs>2 lmap t {$t configure -show_vseps 0 -show_edge 1} |>\
.=objs>2 lmap t {$t print} |tables>\
.=objs>2 lmap t {$t destroy} |>\
.=tables>* textblock::join -- <args|
}

149
src/modules/punk/packagepreference-999999.0a1.0.tm

@ -101,13 +101,20 @@ package require commandstack
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::packagepreference {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
#variable xyz
variable PUNKARGS
#*** !doctools
#[subsection {Namespace punk::packagepreference}]
#[para] Core API functions for punk::packagepreference
#[list_begin definitions]
lappend PUNKARGS [list {
@id -id ::punk::packagepreference::install
@cmd -name ::punk::packagepreference::install -help\
"Install override for ::package builtin - for 'require' subcommand only."
@values -min 0 -max 0
}]
proc uninstall {} {
#*** !doctools
#[call [fun uninstall]]
@ -115,6 +122,13 @@ tcl::namespace::eval punk::packagepreference {
commandstack::remove_rename {::package punk::packagepreference}
}
lappend PUNKARGS [list {
@id -id ::punk::packagepreference::install
@cmd -name ::punk::packagepreference::install -help\
"Install override for ::package builtin - for 'require' subcommand only."
@values -min 0 -max 0
}]
proc install {} {
#*** !doctools
#[call [fun install]]
@ -179,32 +193,37 @@ tcl::namespace::eval punk::packagepreference {
if {!$is_exact && [llength $vwant] <= 1 } {
#required version unspecified - or specified singularly
set available_versions [$COMMANDSTACKNEXT_ORIGINAL versions $pkg]
if {[llength $available_versions] > 1} {
# ---------------------------------------------------------------
#An attempt to detect dll/so loaded and try to load same version
set pkgloadedinfo [lsearch -inline -index 1 [info loaded] $pkg]
if {![llength $pkgloadedinfo]} {
if {[regexp {[A-Z]} $pkg]} {
set pkgloadedinfo [lsearch -inline -index 1 [info loaded] [string tolower $pkg]]
if {![llength $pkgloadedinfo]} {
set pkgloadedinfo [lsearch -inline -index 1 [info loaded] [string totitle $pkg]]
}
}
}
#dll/so files are often named with version numbers that don't contain dots or a version number at all
#e.g sqlite3400.dll Thread288.dll
set pkgloadedinfo [lsearch -nocase -inline -index 1 [info loaded] $pkg]
if {[llength $pkgloadedinfo]} {
puts stderr "--> pkg not present but shared object seems to be loaded: $pkgloadedinfo"
puts stderr "--> pkg not already 'provided' but shared object seems to be loaded: $pkgloadedinfo - and multiple versions available"
lassign $pkgloadedinfo path name
set lcpath [string tolower $path]
set obj [file tail $lcpath]
if {[string match tcl9* $obj]} {
set obj [string range $obj 4 end]
} elseif {[string match lib* $obj]} {
set obj [string range $obj 3 end]
}
set pkginfo [file rootname $obj]
#e.g Thread2.8.8
if {[regexp {^([a-zA-Z\-]+)(.*)} $pkginfo _match lname lversion]} {
if {[string tolower $lname] eq [string tolower $pkg]} {
#first attempt to find a match for our loaded sharedlib path in a *simple* package ifneeded statement.
set lcpath_to_version [dict create]
foreach av $available_versions {
set scr [package ifneeded $pkg $av]
#ifneeded script not always a valid tcl list
if {![catch {llength $scr} scrlen]} {
if {$scrlen == 3 && [lindex $scr 0] eq "load" && [string match -nocase [lindex $scr 2] $pkg]} {
dict set lcpath_to_version [string tolower [lindex $scr 1]] $av
}
}
}
if {[dict exists $lcpath_to_version $lcpath]} {
set lversion [dict get $lcpath_to_version $lcpath]
} else {
#fallback to a best effort guess based on the path
set lversion [::punk::packagepreference::system::slibpath_guess_pkgversion $path $pkg]
}
if {$lversion ne ""} {
#name matches pkg
#hack for known dll version mismatch
if {[string tolower $pkg] eq "thread" && $lversion eq "30b3"} {
@ -240,9 +259,11 @@ tcl::namespace::eval punk::packagepreference {
}]
if {[dict get $stackrecord implementation] ne ""} {
set impl [dict get $stackrecord implementation] ;#use hardcoded name rather than slower (but more flexible) commandstack::get_next_command
puts stdout "punk::packagepreference renamed ::package to $impl"
#puts stdout "punk::packagepreference renamed ::package to $impl"
return 1
} else {
puts stderr "punk::packagepreference failed to rename ::package"
return 0
}
#puts stdout [info body ::package]
}
@ -297,14 +318,94 @@ tcl::namespace::eval punk::packagepreference::lib {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Internal]
#tcl::namespace::eval punk::packagepreference::system {
tcl::namespace::eval punk::packagepreference::system {
#*** !doctools
#[subsection {Namespace punk::packagepreference::system}]
#[para] Internal functions that are not part of the API
variable PUNKARGS
lappend PUNKARGS [list {
@id -id ::punk::packagepreference::system::slibpath_guess_pkgversion
@cmd -name punk::packagepreference::system::slibpath_guess_pkgversion -help\
"Assistance function to determine pkg version from the information
obtained from [info loaded]. This is used to try to avoid loading a different
version of a binary package in another thread/interp when the package isn't
present in the interp, but [info loaded] indicates the binary is already loaded.
The more general/robust way to avoid this is to ensure ::auto_path and
tcl::tm::list are the same in each interp/thread.
This call should only be used as a fallback in case a binary package has a more
complex ifneeded script. If the ifneeded script for a binary package is a
straightforward 'load <path_to_binary> <pkgname>' - then that information
should be used to determine the version by matching <path_to_binary>
rather than this one.
Takes a path to a shared lib (.so/.dll), and the name of its providing
package, and return the version of the package if possible to determine
from the path.
The filename portion of the lib is often missing a version number or has
a version number that has been shortened (e.g dots removed).
The filename itself is first checked for a version number - but the number
is ignored if it doesn't contain any dots.
(prefix is checked to match with $pkgname, with a possible additional prefix
of lib or tcl<int>)
Often (even usually) the parent or grandparent folder will be named as
per the package name with a proper version. If so we can return it,
otherwise return empty string.
The parent/grandparent matching will be done by looking for a case
insensitive match of the prefix to $pkgname.
"
@values -min 1
libpath -help "Full path to shared library (.so,.dll etc)"
pkgname -help ""
}]
proc slibpath_guess_pkgversion {libpath pkgname} {
set root [file rootname [file tail $libpath]]
set namelen [string length $pkgname]
regexp {^(tcl(?:[0-9])+){0,1}(.*)} $root _match tclxx root ;#regexp will match anything - but only truncate leading tclXX..
set testv ""
if {[string match -nocase $pkgname* $root]} {
set testv [string range $root $namelen end]
} elseif {[string match -nocase lib$pkgname* $root]} {
set testv [string range $root $namelen+3 end]
}
if {[string first . $testv] > 0} {
if {![catch [list package vcompare $testv $testv]]} {
#testv has an inner dot and is understood by tcl as a valid version number
return $testv
}
}
#no valid dotted version found directly on dll or so filename
set parent [file dirname $libpath] ;#parent folder is often some differentiator for platform or featureset (e.g win-x64)
set grandparent [file dirname $parent]
foreach path [list $parent $grandparent] {
set segment [file tail $path]
if {$segment eq "bin"} {
continue
}
set testv ""
if {[string match -nocase $pkgname* $segment]} {
set testv [string range $segment $namelen end]
} elseif {[string match -nocase critcl_$pkgname* $segment]} {
set testv [string range $segment $namelen+7 end]
}
#we don't look for dot in parent/grandparent version - a bare integer here after the <pkgname> will be taken to be the version
if {![catch [list package vcompare $testv $testv]]} {
return $testv
}
}
#review - sometimes path and lib are named only for major.minor but package provides major.minor.subversion
#using returned val to attempt to package require -exact major.minor will fail to load major.minor.subversion
return ""
}
}
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::packagepreference ::punk::packagepreference::system
}
#}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide punk::packagepreference [tcl::namespace::eval punk::packagepreference {

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

@ -651,9 +651,14 @@ namespace eval punk::path {
-call-depth-internal -default 0 -type integer
-antiglob_paths -default {} -help\
"list of path patterns to exclude
may include * and ** path segments e.g /usr/**"
may include * and ** path segments e.g
/usr/** (exlude subfolders based at /usr but not
files within /usr itself)
**/_aside (exlude files where _aside is last segment)
**/_aside/* (exclude folders one below an _aside folder)
**/_aside/** (exclude all folders with _aside as a segment)"
@values -min 0 -max -1 -optional 1 -type string
tailglobs -multiple 1 -help\
tailglobs -default * -multiple 1 -help\
"Patterns to match against filename portion (last segment) of each file path
within the directory tree being searched."
}
@ -671,29 +676,29 @@ namespace eval punk::path {
#[para] list of path patterns to exclude - may include * and ** path segments e.g /usr/**
#[para]no natsorting - so order is dependent on filesystem
set argd [punk::args::get_by_id ::punk::path::treefilenames $args]
set argd [punk::args::parse $args withid ::punk::path::treefilenames]
lassign [dict values $argd] leaders opts values received
set tailglobs [dict values $values]
set tailglobs [dict get $values tailglobs]
# -- --- --- --- --- --- ---
set opt_antiglob_paths [dict get $opts -antiglob_paths]
set CALLDEPTH [dict get $opts -call-depth-internal]
# -- --- --- --- --- --- ---
# -- --- --- --- --- --- ---
set files [list]
if {$CALLDEPTH == 0} {
#set opts [dict merge $opts [list -directory $opt_dir]]
if {![dict exists $received -directory]} {
set opt_dir [pwd]
} else {
set opt_dir [dict get $opts -directory]
}
# -- --- --- --- --- --- ---
set files [list]
if {$CALLDEPTH == 0} {
if {![file isdirectory $opt_dir]} {
return [list]
}
set opts [dict merge $opts [list -directory $opt_dir]]
if {![llength $tailglobs]} {
lappend tailglobs *
}
} else {
#assume/require to exist in any recursive call
set opt_dir [dict get $opts -directory]
}
set skip 0

279
src/modules/punk/pcon-999999.0a1.0.tm

@ -0,0 +1,279 @@
# -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm
#
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# (C) 2025
#
# @@ Meta Begin
# Application punk::pcon 999999.0a1.0
# Meta platform tcl
# Meta license MIT
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[manpage_begin shellspy_module_punk::pcon 0 999999.0a1.0]
#[copyright "2025"]
#[titledesc {Module API}] [comment {-- Name section and table of contents description --}]
#[moddesc {-}] [comment {-- Description at end of page heading --}]
#[require punk::pcon]
#[keywords module]
#[description]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Overview]
#[para] overview of punk::pcon
#[subsection Concepts]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection dependencies]
#[para] packages used by punk::pcon
#[list_begin itemized]
package require Tcl 8.6-
#*** !doctools
#[item] [package {Tcl 8.6}]
# #package require frobz
# #*** !doctools
# #[item] [package {frobz}]
#*** !doctools
#[list_end]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section API]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# oo::class namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#tcl::namespace::eval punk::pcon::class {
#*** !doctools
#[subsection {Namespace punk::pcon::class}]
#[para] class definitions
#if {[tcl::info::commands [tcl::namespace::current]::interface_sample1] eq ""} {
#*** !doctools
#[list_begin enumerated]
# oo::class create interface_sample1 {
# #*** !doctools
# #[enum] CLASS [class interface_sample1]
# #[list_begin definitions]
# method test {arg1} {
# #*** !doctools
# #[call class::interface_sample1 [method test] [arg arg1]]
# #[para] test method
# puts "test: $arg1"
# }
# #*** !doctools
# #[list_end] [comment {-- end definitions interface_sample1}]
# }
#*** !doctools
#[list_end] [comment {--- end class enumeration ---}]
#}
#}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::pcon {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Base namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection {Namespace punk::pcon}]
#[para] Core API functions for punk::pcon
#[list_begin definitions]
#proc sample1 {p1 n args} {
# #*** !doctools
# #[call [fun sample1] [arg p1] [arg n] [opt {option value...}]]
# #[para]Description of sample1
# #[para] Arguments:
# # [list_begin arguments]
# # [arg_def tring p1] A description of string argument p1.
# # [arg_def integer n] A description of integer argument n.
# # [list_end]
# return "ok"
#}
#*** !doctools
#[list_end] [comment {--- end definitions namespace punk::pcon ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Secondary API namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::pcon::lib {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
tcl::namespace::path [tcl::namespace::parent]
#*** !doctools
#[subsection {Namespace punk::pcon::lib}]
#[para] Secondary functions that are part of the API
#[list_begin definitions]
#proc utility1 {p1 args} {
# #*** !doctools
# #[call lib::[fun utility1] [arg p1] [opt {?option value...?}]]
# #[para]Description of utility1
# return 1
#}
#*** !doctools
#[list_end] [comment {--- end definitions namespace punk::pcon::lib ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Internal]
#tcl::namespace::eval punk::pcon::system {
#*** !doctools
#[subsection {Namespace punk::pcon::system}]
#[para] Internal functions that are not part of the API
#}
# == === === === === === === === === === === === === === ===
# Sample 'about' function with punk::args documentation
# == === === === === === === === === === === === === === ===
tcl::namespace::eval punk::pcon {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
variable PUNKARGS
variable PUNKARGS_aliases
lappend PUNKARGS [list {
@id -id "(package)punk::pcon"
@package -name "punk::pcon" -help\
"Package
Description"
}]
namespace eval argdoc {
#namespace for custom argument documentation
proc package_name {} {
return punk::pcon
}
proc about_topics {} {
#info commands results are returned in an arbitrary order (like array keys)
set topic_funs [info commands [namespace current]::get_topic_*]
set about_topics [list]
foreach f $topic_funs {
set tail [namespace tail $f]
lappend about_topics [string range $tail [string length get_topic_] end]
}
#Adjust this function or 'default_topics' if a different order is required
return [lsort $about_topics]
}
proc default_topics {} {return [list Description *]}
# -------------------------------------------------------------
# get_topic_ functions add more to auto-include in about topics
# -------------------------------------------------------------
proc get_topic_Description {} {
punk::args::lib::tstr [string trim {
package punk::pcon
description to come..
} \n]
}
proc get_topic_License {} {
return "MIT"
}
proc get_topic_Version {} {
return "$::punk::pcon::version"
}
proc get_topic_Contributors {} {
set authors {{Julian Noble <julian@precisium.com.au>}}
set contributors ""
foreach a $authors {
append contributors $a \n
}
if {[string index $contributors end] eq "\n"} {
set contributors [string range $contributors 0 end-1]
}
return $contributors
}
proc get_topic_custom-topic {} {
punk::args::lib::tstr -return string {
A custom
topic
etc
}
}
# -------------------------------------------------------------
}
# we re-use the argument definition from punk::args::standard_about and override some items
set overrides [dict create]
dict set overrides @id -id "::punk::pcon::about"
dict set overrides @cmd -name "punk::pcon::about"
dict set overrides @cmd -help [string trim [punk::args::lib::tstr {
About punk::pcon
}] \n]
dict set overrides topic -choices [list {*}[punk::pcon::argdoc::about_topics] *]
dict set overrides topic -choicerestricted 1
dict set overrides topic -default [punk::pcon::argdoc::default_topics] ;#if -default is present 'topic' will always appear in parsed 'values' dict
set newdef [punk::args::resolved_def -antiglobs -package_about_namespace -override $overrides ::punk::args::package::standard_about *]
lappend PUNKARGS [list $newdef]
proc about {args} {
package require punk::args
#standard_about accepts additional choices for topic - but we need to normalize any abbreviations to full topic name before passing on
set argd [punk::args::parse $args withid ::punk::pcon::about]
lassign [dict values $argd] _leaders opts values _received
punk::args::package::standard_about -package_about_namespace ::punk::pcon::argdoc {*}$opts {*}[dict get $values topic]
}
}
# end of sample 'about' function
# == === === === === === === === === === === === === === ===
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::pcon
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide punk::pcon [tcl::namespace::eval punk::pcon {
variable pkg punk::pcon
variable version
set version 999999.0a1.0
}]
return
#*** !doctools
#[manpage_end]

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

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

853
src/modules/punk/pipe-999999.0a1.0.tm

@ -0,0 +1,853 @@
# -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm
#
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# (C) 2025
#
# @@ Meta Begin
# Application punk::pipe 999999.0a1.0
# Meta platform tcl
# Meta license MIT
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[manpage_begin shellspy_module_punk::pipe 0 999999.0a1.0]
#[copyright "2025"]
#[titledesc {Module API}] [comment {-- Name section and table of contents description --}]
#[moddesc {-}] [comment {-- Description at end of page heading --}]
#[require punk::pipe]
#[keywords module]
#[description]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Overview]
#[para] overview of punk::pipe
#[subsection Concepts]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection dependencies]
#[para] packages used by punk::pipe
#[list_begin itemized]
package require Tcl 8.6-
#*** !doctools
#[item] [package {Tcl 8.6}]
# #package require frobz
# #*** !doctools
# #[item] [package {frobz}]
#*** !doctools
#[list_end]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section API]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# oo::class namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#tcl::namespace::eval punk::pipe::class {
#*** !doctools
#[subsection {Namespace punk::pipe::class}]
#[para] class definitions
#if {[tcl::info::commands [tcl::namespace::current]::interface_sample1] eq ""} {
#*** !doctools
#[list_begin enumerated]
# oo::class create interface_sample1 {
# #*** !doctools
# #[enum] CLASS [class interface_sample1]
# #[list_begin definitions]
# method test {arg1} {
# #*** !doctools
# #[call class::interface_sample1 [method test] [arg arg1]]
# #[para] test method
# puts "test: $arg1"
# }
# #*** !doctools
# #[list_end] [comment {-- end definitions interface_sample1}]
# }
#*** !doctools
#[list_end] [comment {--- end class enumeration ---}]
#}
#}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::pipe {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Base namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection {Namespace punk::pipe}]
#[para] Core API functions for punk::pipe
#[list_begin definitions]
#proc sample1 {p1 n args} {
# #*** !doctools
# #[call [fun sample1] [arg p1] [arg n] [opt {option value...}]]
# #[para]Description of sample1
# #[para] Arguments:
# # [list_begin arguments]
# # [arg_def tring p1] A description of string argument p1.
# # [arg_def integer n] A description of integer argument n.
# # [list_end]
# return "ok"
#}
#https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
#
#we can't provide a float comparison suitable for every situation,
#but we should pick something reasonable, keep it stable, and document it.
proc float_almost_equal {a b} {
package require math::constants
set diff [expr {abs($a - $b)}]
if {$diff <= $::math::constants::eps} {
return 1
}
set A [expr {abs($a)}]
set B [expr {abs($b)}]
set largest [expr {($B > $A) ? $B : $A}]
return [expr {$diff <= $largest * $::math::constants::eps}]
}
#debatable whether boolean_almost_equal is more surprising than helpful.
#values from a calculation that are extremely close to zero but aren't false could also be surprising - especially if they compare equal numerically
#perhaps a fuzzy-boolean is a step too far for a default - but it's inline with float-comparison for pattern-matching.
#alternatively - use an even more complex classifier? (^&~) ?
proc boolean_almost_equal {a b} {
if {[string is double -strict $a]} {
if {[float_almost_equal $a 0]} {
set a 0
}
}
if {[string is double -strict $b]} {
if {[float_almost_equal $b 0]} {
set b 0
}
}
#must handle true,no etc.
expr {($a && 1) == ($b && 1)}
}
#boolean could be tr, true, y, ye, yes,Yes, 1 , 0 etc.
proc boolean_equal {a b} {
#equivalenttly xnor: expr {!(($a && 1) ^ ($b && 1))} ;# less clear and no discernable timing benefit.
expr {($a && 1) == ($b && 1)}
}
proc val [list [list v [lreplace x 0 0]]] {return $v}
#*** !doctools
#[list_end] [comment {--- end definitions namespace punk::pipe ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Secondary API namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::pipe::lib {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
tcl::namespace::path [tcl::namespace::parent]
#*** !doctools
#[subsection {Namespace punk::pipe::lib}]
#[para] Secondary functions that are part of the API
#[list_begin definitions]
#proc utility1 {p1 args} {
# #*** !doctools
# #[call lib::[fun utility1] [arg p1] [opt {?option value...?}]]
# #[para]Description of utility1
# return 1
#}
#map rhs to names suitable to use in pipemcd proc name (whitespace mapping)
# (for .= and = pipecmds)
proc pipecmd_namemapping {rhs} {
#used to build a command from a pattern which could contain :: - we need to map it to keep it a single command in the desired namespace.
#glob chars will prevent us using simple test {[info commands $cmd] eq ""} to test existence
#we could use the test {$cmd in [info commands]} - but mapping away globchars is more robust, allowing the simpler test
#set rhs [string trim $rhs];#ignore all leading & trailing whitespace
set rhs [string trimleft $rhs]
#---
#REVIEW!
#set rhs [regsub -all {\s{1,}} $rhs {<sp>}] ;#collapse all internal whitespace to a single <sp> token
#This stops us matching {/@**@x x} vs {/@**@x x}
#---
set rhs [tcl::string::map {: <c> ? <q> * <star> [ <lb> ] <rb> \\ <bsl> {"} <dq> " " <sp>} $rhs]
#review - we don't expect other command-incompatible chars such as colon?
return $rhs
}
# relatively slow on even small sized scripts
#proc arg_is_script_shaped2 {arg} {
# set re {^(\s|;|\n)$}
# set chars [split $arg ""]
# if {[lsearch -regex $chars $re] >=0} {
# return 1
# } else {
# return 0
# }
#}
#exclude quoted whitespace
proc arg_is_script_shaped {arg} {
if {[tcl::string::first \n $arg] >= 0} {
return 1
} elseif {[tcl::string::first ";" $arg] >= 0} {
return 1
} elseif {[tcl::string::first " " $arg] >= 0 || [tcl::string::first \t $arg] >= 0} {
lassign [_rhs_tail_split $arg] _ part2 ;#will have part2 if unquoted whitespace found
return [expr {$part2 ne ""}]
} else {
return 0
}
}
#split top level of patterns only.
proc _split_patterns_memoized {varspecs} {
set name_mapped [pipecmd_namemapping $varspecs]
set cmdname ::punk::pipecmds::split_patterns::_$name_mapped
if {[info commands $cmdname] ne ""} {
return [$cmdname]
}
set result [_split_patterns $varspecs]
proc $cmdname {} [list return $result]
#debug.punk.pipe.compile {proc $cmdname} 4
return $result
}
#note - empty data after trailing , is ignored. (comma as very last character)
# - fix by documentation only. double up trailing comma e.g <pattern>,, if desired to return pattern match plus all at end!
#todo - move to punk::pipe
proc _split_patterns {varspecs} {
set varlist [list]
# @ @@ - list and dict functions
# / level separator
# # list count, ## dict size
# % string functions
# ! not
set var_terminals [list "@" "/" "#" "%" "!" ">" "<"] ;# (> required for insertionspecs at rhs of = & .= )
#right bracket ) also ends a var - but is different depending on whether var is array or basic. For array - it forms part of the varname
#except when prefixed directly by pin classifier ^
set protect_terminals [list "^"] ;# e.g sequence ^#
#also - an atom usually doesn't need the / as a terminal - because it can't match a missing element unless it's empty string
#ie the one usecase is '/n to match either empty string or missing item at position n. For this one usecase - we miss the capability to atom match paths/urls .. '/usr/local/et'
set in_brackets 0 ;#count depth
set in_atom 0
set token ""
set end_var_posn -1 ;#first var_terminal encountered within each comma delimited section
set token_index 0 ;#index of terminal char within each token
set indq 0
set inbraces 0
set inesc 0 ;#whether last char was backslash (see also punk::escv)
set prevc ""
set char_index 0
#if {[string index $varspecs end] eq ","} {
# set varspecs [string range $varspecs 0 end-1]
#}
set charcount 0
foreach c [split $varspecs ""] {
incr charcount
if {$indq} {
if {$inesc} {
#puts stderr "inesc adding '$c'"
append token \\$c
} else {
if {$c eq {"}} {
set indq 0
} else {
append token $c
}
}
} elseif {$inbraces} {
if {$inesc} {
append token \\$c
} else {
if {$c eq "\}"} {
incr inbraces -1
if {$inbraces} {
append token $c
}
} elseif {$c eq "\{"} {
incr inbraces
if {$inbraces} {
append token $c
}
} else {
append token $c
}
}
} elseif {$in_atom} {
#ignore dquotes/brackets in atoms - pass through
append token $c
#set nextc [lindex $chars $char_index+1]
if {$c eq "'"} {
set in_atom 0
}
} elseif {$in_brackets > 0} {
append token $c
if {$c eq ")"} {
incr in_brackets -1
}
} else {
if {$c eq {"}} {
if {!$inesc} {
set indq 1
} else {
append token $c
}
} elseif {$c eq "\{"} {
if {!$inesc} {
set inbraces 1
} else {
append token $c
}
} elseif {$c eq ","} {
#set var $token
#set spec ""
#if {$end_var_posn > 0} {
# #tcl scan with %s will not handle whitespace as desired. Be explicit using string range instead.
# #lassign [scan $token %${end_var_posn}s%s] var spec
# set var [string range $token 0 $end_var_posn-1]
# set spec [string range $token $end_var_posn end] ;#key section includes the terminal char which ended the var and starts the spec
#} else {
# if {$end_var_posn == 0} {
# set var ""
# set spec $token
# }
#}
#lappend varlist [list [string trim $var] [string trim $spec]]
#set token ""
#set token_index -1 ;#reduce by 1 because , not included in next token
#set end_var_posn -1
} else {
append token $c
switch -exact -- $c {
' {
set in_atom 1
}
( {
incr in_brackets
}
default {
if {$end_var_posn == -1 && (($c in $var_terminals) && ($prevc ni $protect_terminals))} {
set end_var_posn $token_index
}
}
}
}
if {$c eq ","} {
set var $token
set spec ""
if {$end_var_posn > 0} {
#tcl scan with %s will not handle whitespace as desired. Be explicit using string range instead.
#lassign [scan $token %${end_var_posn}s%s] var spec
set var [string range $token 0 $end_var_posn-1]
set spec [string range $token $end_var_posn end] ;#key section includes the terminal char which ended the var and starts the spec
} else {
if {$end_var_posn == 0} {
set var ""
set spec $token
}
}
lappend varlist [list [string trim $var] $spec]
set token ""
set token_index -1
set end_var_posn -1
}
}
if {$charcount == [string length $varspecs]} {
if {!($indq || $inbraces || $in_atom || $in_brackets)} {
if {$c ne ","} {
set var $token
set spec ""
if {$end_var_posn > 0} {
#tcl scan with %s will not handle whitespace as desired. Be explicit using string range instead.
#lassign [scan $token %${end_var_posn}s%s] var spec
set var [string range $token 0 $end_var_posn-1]
set spec [string range $token $end_var_posn end] ;#key section includes the terminal char which ended the var and starts the spec
} else {
if {$end_var_posn == 0} {
set var ""
set spec $token
}
}
lappend varlist [list [string trim $var] $spec]
set token ""
set token_index -1
set end_var_posn -1
}
}
}
set prevc $c
if {$c eq "\\"} {
#review
if {$inesc} {
set inesc 0
} else {
set token [string range $token 0 end-1]
set inesc 1
}
} else {
set inesc 0
}
incr token_index
incr char_index
}
#if {[string length $token]} {
# #lappend varlist [splitstrposn $token $end_var_posn]
# set var $token
# set spec ""
# if {$end_var_posn > 0} {
# #lassign [scan $token %${end_var_posn}s%s] var spec
# set var [string range $token 0 $end_var_posn-1]
# set spec [string range $token $end_var_posn end] ;#key section includes the terminal char which ended the var and starts the spec
# } else {
# if {$end_var_posn == 0} {
# set var ""
# set spec $token
# }
# }
# #lappend varlist [list [string trim $var] [string trim $spec]]
# #spec needs to be able to match whitespace too
# lappend varlist [list [string trim $var] $spec]
#}
return $varlist
}
#todo - consider whether we can use < for insertion/iteration combinations
# =a<,b< iterate once through
# =a><,b>< cartesian product
# =a<>,b<> ??? zip ?
#
# ie = {a b c} |> .=< inspect
# would call inspect 3 times, once for each argument
# .= list {a b c} {x y z} |a/0,b/1> .=a><,b>< list
# would produce list of cartesian pairs?
#
proc _split_equalsrhs {insertionpattern} {
#map the insertionpattern so we can use faster globless info command search
set name_mapped [pipecmd_namemapping $insertionpattern]
set cmdname ::punk::pipecmds::split_rhs::_$name_mapped
if {[info commands $cmdname] ne ""} {
return [$cmdname]
}
set lst_var_indexposition [_split_patterns_memoized $insertionpattern]
set i 0
set return_triples [list]
foreach v_pos $lst_var_indexposition {
lassign $v_pos v index_and_position
#e.g varname@@data/ok>0 varname/1/0>end
#ensure only one ">" is detected
if {![string length $index_and_position]} {
set indexspec ""
set positionspec ""
} else {
set chars [split $index_and_position ""]
set posns [lsearch -all $chars ">"]
if {[llength $posns] > 1} {
error "pipesyntax error in segment insertion pattern '$insertionpattern' -v '$v' multiple '>' characters. Pattern not understood." "_split_equalsrhs $insertionpattern" [list pipesyntax insertionpattern_invalid]
}
if {![llength $posns]} {
set indexspec $index_and_position
set positionspec ""
} else {
set splitposn [lindex $posns 0]
set indexspec [string range $index_and_position 0 $splitposn-1]
set positionspec [string range $index_and_position $splitposn+1 end]
}
}
#review -
if {($positionspec in [list "*" "/*" "@*" "/" "@"]) || ($v eq "*" && $positionspec eq "")} {
set star ""
if {$v eq "*"} {
set v ""
set star "*"
}
if {[string index $positionspec end] eq "*"} {
set star "*"
}
#it is always possible to insert at end of list regardless of current length - so /end* and @end* are equivalent
#as are /end and @end
#lset lst_var_indexposition $i [list $v "/end$star"]
set triple [list $v $indexspec "/end$star"]
} else {
if {$positionspec eq ""} {
#e.g just =varname
#lset lst_var_indexposition $i [list $v "/end"]
set triple [list $v $indexspec "/end"]
#error "pipesyntax error in segment insertionpattern '$insertionpattern' - v '$v' missing position spec e.g /0"
} else {
if {[string index $indexspec 0] ni [list "" "/" "@"]} {
error "pipesyntax error in segment insertionpattern '$insertionpattern' - v '$v' bad index spec '$indexspec'" "_split_equalsrhs $insertionpattern" [list pipesyntax insertionpattern_invalid]
}
set triple [list $v $indexspec $positionspec]
}
}
lappend return_triples $triple
incr i
}
proc $cmdname {} [list return $return_triples]
return $return_triples
}
proc _rhs_tail_split {fullrhs} {
set inq 0; set indq 0
set equalsrhs ""
set i 0
foreach ch [split $fullrhs ""] {
if {$inq} {
append equalsrhs $ch
if {$ch eq {'}} {
set inq 0
}
} elseif {$indq} {
append equalsrhs $ch
if {$ch eq {"}} {
set indq 0
}
} else {
switch -- $ch {
{'} {
set inq 1
}
{"} {
set indq 1
}
" " {
#whitespace outside of quoting
break
}
0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - a - b - c - d - e - f - g - h - i - j - k - l - m - n - o - p - q - r - s - t - u - v - w - x - y - z {}
default {
#\t not a literal for switch jumpTable bytecompile - review - can we do it without for example string mapping to <t> (and without a literal binary tab in source file)?
#we can't (reliably?) put \t as one of our switch keys
#
if {$ch eq "\t"} {
break
}
}
}
append equalsrhs $ch
}
incr i
}
set tail [tcl::string::range $fullrhs $i end]
return [list $equalsrhs $tail]
}
#todo - recurse into bracketed sub parts
#JMN3
#e.g @*/(x@0,y@2)
proc _var_classify {multivar} {
set cmdname ::punk::pipecmds::var_classify::_[pipecmd_namemapping $multivar]
if {[info commands $cmdname] ne ""} {
return [$cmdname]
}
#comma seems a natural choice to split varspecs,
#but also for list and dict subelement access
#/ normally indicates some sort of hierarchical separation - (e.g in filesytems)
#so / will indicate subelements e.g @0/1 for lindex $list 0 1
#set valsource_key_list [_split_var_key_at_unbracketed_comma $multivar]
set valsource_key_list [_split_patterns_memoized $multivar]
#mutually exclusive - atom/pin
#set map [list "" ' ^ &] ;#0 = default/var/not-yet-determined 1 = atom 2 = pin
#set var_class [lmap var $valsource_key_list {expr {([set m [lsearch $map [string index [lindex $var 0] 0]]] >= 0) ? [list $var $m] : [list $var 0]}}]
#0 - novar
#1 - atom '
#2 - pin ^
#3 - boolean &
#4 - integer
#5 - double
#6 - var
#7 - glob (no classifier and contains * or ?)
#8 - numeric
#9 - > (+)
#10 - < (-)
set var_names [list]
set var_class [list]
set varspecs_trimmed [list] ;#raw varspecs without pin/atom modifiers - or empty string for glob
set leading_classifiers [list "'" "&" "^" ]
set trailing_classifiers [list + -]
set possible_number_start [list - + . 0 1 2 3 4 5 6 7 8 9 > <]
foreach v_key $valsource_key_list {
lassign $v_key v key
set vname $v ;#default
set classes [list]
if {$v eq ""} {
lappend var_class [list $v_key 0]
lappend varspecs_trimmed $v_key
} else {
set lastchar [string index $v end]
switch -- $lastchar {
+ {
lappend classes 9
set vname [string range $v 0 end-1]
}
- {
lappend classes 10
set vname [string range $v 0 end-1]
}
}
set firstchar [string index $v 0]
switch -- $firstchar {
' {
lappend var_class [list $v_key 1]
#set vname [string range $v 1 end]
lappend varspecs_trimmed [list $vname $key]
}
^ {
lappend classes [list 2]
#use vname - may already have trailing +/- stripped
set vname [string range $vname 1 end]
set secondclassifier [string index $v 1]
switch -- $secondclassifier {
"&" {
#pinned boolean
lappend classes 3
set vname [string range $v 2 end]
}
"#" {
#pinned numeric comparison instead of string comparison
#e.g set x 2
# this should match: ^#x.= list 2.0
lappend classes 8
set vname [string range $vname 1 end]
}
"*" {
#pinned glob
lappend classes 7
set vname [string range $v 2 end]
}
}
#todo - check for second tag - & for pinned boolean?
#consider requiring ^# for numeric comparisons. currently no way to do a strictly string comparison on pinned variables.... default ^var really shouldn't be doing any magic.
#while we're at it.. pinned glob would be nice. ^*
#maybe even pinned scan ^% ? regex? ^/ or ^? these would be hard to have corresponding literals in the pattern mini-lang.
#These all limit the range of varnames permissible - which is no big deal.
lappend var_class [list $v_key $classes]
lappend varspecs_trimmed [list $vname $key]
}
& {
#we require boolean literals to be single-quoted so we can use cross-binding on boolean vars.
#ie &true is the variable true whereas &'true' or &'1' &'t' etc are literal booleans
#allow exception of &1 &0 to be literal booleans - because we disallow 0 & 1 as varnames in other contexts anyway - so it would be more consistent not to treat as varnames here.
lappend var_class [list $v_key 3]
set vname [string range $v 1 end]
lappend varspecs_trimmed [list $vname $key]
}
default {
if {([string first ? $v]) >=0 || ([string first * $v] >=0)} {
lappend var_class [list $v_key 7] ;#glob
#leave vname as the full glob
lappend varspecs_trimmed [list "" $key]
} else {
#scan vname not v - will either be same as v - or possibly stripped of trailing +/-
set numtestv [join [scan $vname %lld%s] ""] ;# handles octals (leading zeros), ok for use with bignums, decimal points and sci notation - fails to handle leading dot e.g .5
#leading . still need to test directly for double
if {[string is double -strict $vname] || [string is double -strict $numtestv]} {
if {[string is integer -strict $numtestv]} {
#this will pick up boolean 1 or 0 - but that's ok - they require "&" marker if boolean comparison desired
#integer test before double..
#note there is also string is wide (string is wideinteger) for larger ints..
lappend classes 4
lappend var_class [list $v_key $classes]
lappend varspecs_trimmed $v_key
} else {
#double
#sci notation 1e123 etc
#also large numbers like 1000000000 - even without decimal point - (tcl bignum)
lappend classes 5
lappend var_class [list $v_key $classes]
lappend varspecs_trimmed $v_key
}
} else {
lappend var_class [list $v_key 6] ;#var
lappend varspecs_trimmed $v_key
}
}
}
}
}
lappend var_names $vname
}
set result [list var_names $var_names var_class $var_class varspecs_trimmed $varspecs_trimmed]
proc $cmdname {} [list return $result]
#JMN
#debug.punk.pipe.compile {proc $cmdname}
return $result
}
#*** !doctools
#[list_end] [comment {--- end definitions namespace punk::pipe::lib ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Internal]
#tcl::namespace::eval punk::pipe::system {
#*** !doctools
#[subsection {Namespace punk::pipe::system}]
#[para] Internal functions that are not part of the API
#}
# == === === === === === === === === === === === === === ===
# Sample 'about' function with punk::args documentation
# == === === === === === === === === === === === === === ===
tcl::namespace::eval punk::pipe {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
variable PUNKARGS
variable PUNKARGS_aliases
lappend PUNKARGS [list {
@id -id "(package)punk::pipe"
@package -name "punk::pipe" -help\
"Package
Description"
}]
namespace eval argdoc {
#namespace for custom argument documentation
proc package_name {} {
return punk::pipe
}
proc about_topics {} {
#info commands results are returned in an arbitrary order (like array keys)
set topic_funs [info commands [namespace current]::get_topic_*]
set about_topics [list]
foreach f $topic_funs {
set tail [namespace tail $f]
lappend about_topics [string range $tail [string length get_topic_] end]
}
return $about_topics
}
proc default_topics {} {return [list Description outline *]}
# -------------------------------------------------------------
# get_topic_ functions add more to auto-include in about topics
# -------------------------------------------------------------
proc get_topic_Description {} {
punk::args::lib::tstr [string trim {
punk pipeline features
} \n]
}
proc get_topic_License {} {
return "MIT"
}
proc get_topic_Version {} {
return $::punk::pipe::version
}
proc get_topic_Contributors {} {
set authors {{Julian Noble <julian@precisium.com.au>}}
set contributors ""
foreach a $authors {
append contributors $a \n
}
if {[string index $contributors end] eq "\n"} {
set contributors [string range $contributors 0 end-1]
}
return $contributors
}
proc get_topic_outline {} {
punk::args::lib::tstr -return string {
todo..
}
}
# -------------------------------------------------------------
}
# we re-use the argument definition from punk::args::standard_about and override some items
set overrides [dict create]
dict set overrides @id -id "::punk::pipe::about"
dict set overrides @cmd -name "punk::pipe::about"
dict set overrides @cmd -help [string trim [punk::args::lib::tstr {
About punk::pipe
}] \n]
dict set overrides topic -choices [list {*}[punk::pipe::argdoc::about_topics] *]
dict set overrides topic -choicerestricted 1
dict set overrides topic -default [punk::pipe::argdoc::default_topics] ;#if -default is present 'topic' will always appear in parsed 'values' dict
set newdef [punk::args::resolved_def -antiglobs -package_about_namespace -override $overrides ::punk::args::package::standard_about *]
lappend PUNKARGS [list $newdef]
proc about {args} {
package require punk::args
#standard_about accepts additional choices for topic - but we need to normalize any abbreviations to full topic name before passing on
set argd [punk::args::parse $args withid ::punk::pipe::about]
lassign [dict values $argd] _leaders opts values _received
punk::args::package::standard_about -package_about_namespace ::punk::pipe::argdoc {*}$opts {*}[dict get $values topic]
}
}
# end of sample 'about' function
# == === === === === === === === === === === === === === ===
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::pipe
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide punk::pipe [tcl::namespace::eval punk::pipe {
variable pkg punk::pipe
variable version
set version 999999.0a1.0
}]
return
#*** !doctools
#[manpage_end]

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

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

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

@ -83,6 +83,8 @@ namespace eval repl {
namespace eval punk::repl {
tsv::set repl runid 0
#todo - key on shell/subshell
tsv::set repl runchunks-0 [list] ;#last_run_display
@ -312,6 +314,15 @@ proc punk::repl::reset_terminal {} {
}
proc punk::repl::get_prompt_config {} {
if {[catch {punk::console::vt52} is_vt52]} {
set is_vt52 0
}
if {$is_vt52} {
set resultprompt "52-"
set nlprompt "52."
set infoprompt "52*"
set debugprompt "52~"
} else {
if {$::tcl_interactive} {
set RST [a]
set resultprompt "[a green bold]-$RST "
@ -324,11 +335,17 @@ proc punk::repl::get_prompt_config {} {
set infoprompt ""
set debugprompt ""
}
}
return [list resultprompt $resultprompt nlprompt $nlprompt infoprompt $infoprompt debugprompt $debugprompt]
}
proc repl::start {inchan args} {
puts stderr "-->repl::start $inchan $args"
upvar ::punk::console::input_chunks_waiting input_chunks_waiting
if {![info exists input_chunks_waiting($inchan)]} {
set input_chunks_waiting($inchan) [list]
}
variable codethread
#review
if {$codethread eq ""} {
@ -356,7 +373,12 @@ proc repl::start {inchan args} {
}
incr startinstance
set loopinstance 0
thread::send $codethread {
if {[info exists ::punk::ns::ns_current]} {
set start_in_ns $::punk::ns::ns_current
} else {
set start_in_ns ::
}
thread::send $codethread [string map [list %ns1% $start_in_ns] {
#set ::punk::repl::codethread::running 1
#the interp in which commands such as d/ run
@ -366,9 +388,9 @@ proc repl::start {inchan args} {
namespace eval ::punk::repl::codethread {}
set ::punk::repl::codethread::running 1
namespace eval ::punk::ns::ns_current {}
set ::punk::ns::ns_current ::
}
set ::punk::ns::ns_current %ns1%
}
}]
set commandstr ""
# ---
@ -385,14 +407,15 @@ proc repl::start {inchan args} {
set ::punk::console::ansi_wanted -1
}
}
puts stderr "-->repl::start active on $inchan $args replthread:[thread::id] codethread:$codethread"
set prompt_config [punk::repl::get_prompt_config]
doprompt "P% "
fileevent $inchan readable [list [namespace current]::repl_handler $inchan $prompt_config]
set reading 1
catch {
#catch {
# set punk::console::tabwidth [punk::console::get_tabstop_apparent_width]
}
#}
vwait [namespace current]::done
fileevent $inchan readable {}
@ -900,7 +923,11 @@ namespace eval punk::repl::class {
append debug \n "input:[ansistring VIEW -lf 1 -vt 1 $new0] before row:$o_cursor_row after row: $result_row before col:$o_cursor_col after col:$result_col"
package require textblock
set debug [textblock::frame -checkargs 0 -buildcache 0 $debug]
if {![punk::console::vt52]} {
catch {punk::console::move_emitblock_return $debug_first_row 1 $debug}
} else {
#??
}
# -- --- --- --- --- ---
set o_cursor_col $result_col
@ -1363,8 +1390,9 @@ proc repl::repl_handler {inputchan prompt_config} {
lappend input_chunks_waiting($inputchan) $chunk
}
} else {
if {[fblocked $inputchan]} {
#REVIEW - need to und
#'chan blocked' docs state: 'Note that this only ever returns 1 when the channel has been configured to be non-blocking..'
if {[chan blocked $inputchan]} {
#REVIEW -
#todo - figure out why we're here.
#can we even put a spinner so we don't keep emitting lines? We probably can't use any ansi functions that need to get a response on stdin..(like get_cursor_pos)
#punk::console::get_size is problematic if -winsize not available on the stdout channel - which is the case for certain 8.6 versions at least.. platform variances?
@ -1372,9 +1400,9 @@ proc repl::repl_handler {inputchan prompt_config} {
set outconf [chan configure stdout]
set RED [punk::ansi::a+ red bold]; set RST [punk::ansi::a]
if {"windows" eq $::tcl_platform(platform)} {
set msg "${RED}$inputchan fblocked is true. (line-length Tcl windows channel bug?)$RST \{$allwaiting\}"
set msg "${RED}$inputchan chan blocked is true. (line-length Tcl windows channel bug?)$RST \{$allwaiting\}"
} else {
set msg "${RED}$inputchan fblocked is true.$RST \{$allwaiting\}"
set msg "${RED}$inputchan chan blocked is true.$RST \{$allwaiting\}"
}
set cols ""
set rows ""
@ -1483,6 +1511,11 @@ proc repl::repl_handler {inputchan prompt_config} {
chan configure $inputchan -translation lf
}
set chunk [read $inputchan]
#we expect a chan configured with -blocking 0 to be blocked immediately after reads
#test - just bug console for now - try to understand when/how/if a non blocking read occurs.
if {![chan blocked $inputchan]} {
puts stderr "repl_handler->$inputchan not blocked after read"
}
punk::repl::repl_handler_restorechannel_if_not_eof $inputchan $original_input_conf
uplevel #0 [list repl::repl_process_data $inputchan raw-read $chunk [list] $prompt_config]
@ -1532,6 +1565,10 @@ interp alias {} editbuf {} ::punk::repl::editbuf
proc punk::repl::console_debugview {editbuf consolewidth args} {
if {[punk::console::vt52]} {
#topleft?
return [dict create width 0 height 0 topleft 0]
}
package require textblock
variable debug_repl
if {$debug_repl <= 0} {
@ -1578,19 +1615,24 @@ proc punk::repl::console_debugview {editbuf consolewidth args} {
set debug_width [textblock::widthtopline $info]
set patch_height [expr {2 + $debug_height + 2}]
set spacepatch [textblock::block $debug_width $patch_height " "]
puts -nonewline [punk::ansi::cursor_off]
#puts -nonewline [punk::ansi::cursor_off]
punk::console::cursor_off
#use non cursorsave versions - cursor save/restore will interfere with any concurrent ansi rendering that uses save/restore - because save/restore is a single item, not a stack.
set debug_offset [expr {$consolewidth - $debug_width - $opt_rightmargin}]
set row_clear [expr {$opt_row -2}]
punk::console::move_emitblock_return $row_clear $debug_offset $spacepatch
punk::console::move_emitblock_return $opt_row $debug_offset $info
set topleft [list $debug_offset $opt_row] ;#col,row REVIEW
puts -nonewline [punk::ansi::cursor_on]
#puts -nonewline [punk::ansi::cursor_on]
punk::console::cursor_on
flush stdout
return [dict create width $debug_width height $debug_height topleft $topleft]
}
proc punk::repl::console_editbufview {editbuf consolewidth args} {
if {[punk::console::vt52]} {
return [dict create width 0]
}
package require textblock
upvar ::repl::editbuf_list editbuf_list
@ -1647,6 +1689,12 @@ proc punk::repl::console_controlnotification {message consolewidth consoleheight
}
proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config} {
if {[info exists ::punk::console::is_vt52]} {
set is_vt52 $::punk::console::is_vt52
} else {
set is_vt52 0
}
variable loopinstance
incr loopinstance
upvar ::punk::console::input_chunks_waiting input_chunks_waiting
@ -1765,25 +1813,28 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config
error "character 03 -> ctrl-c"
}
if {$chunk eq "\x7f"} {
#review - configurable?
#translate raw del to backspace del for those terminals that send plain del
if {$chunk eq "\x7f"} {
set chunk "\b\x7f"
}
} elseif {$chunk eq "\x7f\x7f"} {
#commonly if key held down we will get 2 dels in a row
#review - could get more in a row depending on hardware/os
set chunk "\b\x7f\b\x7f"
} elseif {$chunk eq "\x1c"} {
#ctrl-bslash
if {$chunk eq "\x1c"} {
#try to brutally terminate process
#attempt to leave terminal in a reasonable state
punk::mode line
mode line ;#may be aliased to ::repl::interphelpers::mode
after 250 {exit 42}
return
}
} elseif {$chunk eq "\x1a"} {
#for now - exit with small delay for tidyup
#ctrl-z
if {$chunk eq "\x1a"} {
#::punk::repl::handler_console_control "ctrl-z_via_rawloop"
punk::mode line
if {[catch {mode line}]} {
interp eval code {mode line}
}
after 1000 {exit 43}
return
}
@ -1802,7 +1853,8 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config
#--------------------------
# editbuf and debugview rhs frames
if {[set ::punk::console::ansi_available]} {
#for now disable entirely on vt52 - we can only do cursor save restore - nothing that requires responses on stdin (?)
if {!$is_vt52 && [set ::punk::console::ansi_available]} {
#experimental - use punk::console::get_size to determine current visible width.
#This should ideally be using sigwinch or some equivalent to set a value somewhere.
#testing each time is very inefficient (1+ms)
@ -1811,9 +1863,16 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config
set consolewidth 132
if {$do_checkwidth} {
if {[catch {set consolewidth [dict get [punk::console::get_size] columns]} errM]} {
#review
if {!$is_vt52} {
puts stderr "repl_process_data failed on call to punk::console::get_size :$errM"
}
}
#if chan conf stdout doesn't give dimensions and console doesn't respond to queries - we can get empty results in get_size dict
if {$consolewidth eq ""} {
set consolewidth 132
}
}
set debug_width 0
set rightmargin 0
set space_occupied [punk::repl::console_debugview $editbuf $consolewidth -row 10 -chunktype $chunktype -rightmargin $rightmargin] ;#contains cursor movements
@ -1850,14 +1909,25 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config
set leftmargin 3
if {!$is_vt52} {
puts -nonewline stdout [a+ cyan][punk::ansi::move_column [expr {$leftmargin +1}]][punk::ansi::erase_eol][$editbuf line $cursor_row][a][punk::ansi::move_column [expr {$leftmargin + [$editbuf cursor_column]}]]
} else {
puts -nonewline stdout [a+ cyan][punk::ansi::vt52move_column [expr {$leftmargin +1}]][punk::ansi::vt52erase_eol][$editbuf line $cursor_row][punk::ansi::vt52move_column [expr {$leftmargin + [$editbuf cursor_column]}]]
}
#puts -nonewline stdout $chunk
flush stdout
if {[$editbuf last_char] eq "\n"} {
set linelen [punk::ansi::printing_length [$editbuf line $nextsubmit_line_num]]
if {!$is_vt52} {
puts -nonewline stdout [a+ cyan bold][punk::ansi::move_column [expr {$leftmargin +1}]][$editbuf line $nextsubmit_line_num][a][punk::ansi::move_column [expr {$leftmargin + $linelen +1}]]
#screen_last_char_add "\n" input inputline
puts -nonewline stdout [punk::ansi::erase_eol]\n
} else {
puts -nonewline stdout [a+ cyan bold][punk::ansi::vt52move_column [expr {$leftmargin +1}]][$editbuf line $nextsubmit_line_num][punk::ansi::vt52move_column [expr {$leftmargin + $linelen +1}]]
puts -nonewline stdout [punk::ansi::vt52erase_eol]\n
}
#puts -nonewline stdout \n
screen_last_char_add "\n" input inputline
set waiting [$editbuf line end]
@ -2077,6 +2147,9 @@ proc repl::repl_process_data {inputchan chunktype chunk stdinlines prompt_config
set repl_runid [tsv::incr repl runid]
tsv::set repl runchunks-$repl_runid [list] ;#last_run_display
catch {
#REVIEW - when we launch a subshell and run more than 10 commands,
#we delete runchunks from the outer shell that we'll return to!
#we should use a toplevel key pertaining to the shell/subshell instead of just 'repl'
tsv::unset repl runchunks-[expr {$repl_runid - 10}]
}
@ -2530,6 +2603,8 @@ proc repl::completion {context ebuf} {
}
namespace eval repl {
proc init {args} {
if {![info exists ::argv0]} {
#error out before we create a thread - punk requires this - review
@ -2579,7 +2654,6 @@ namespace eval repl {
error "repl:init codethread: $codethread already exists. use -force 1 to override"
}
set codethread [thread::create -preserved]
#review - naming of the possibly 2 cond variables parent and child thread
set codethread_cond [thread::cond create] ;#repl::codethread_cond held by parent(repl) vs punk::repl::codethread::replthread_cond held by child(codethread)
set codethread_mutex [thread::mutex create]
@ -2711,6 +2785,9 @@ namespace eval repl {
}
#todo - add/remove shellfilter stacked ansiwrap
}
proc vt52 {args} {
return [thread::send %replthread% [list punk::console::vt52 {*}$args]]
}
proc mode args {
#with tsv::set console is_raw we don't need to call mode in both the replthread and the codethread
# REVIEW - call in local interp? how about if codethread is safe interp?
@ -2735,6 +2812,15 @@ namespace eval repl {
proc md5 args {
::md5::md5 {*}$args
}
proc fconfigure {args} {
code invokehidden fconfigure {*}$args
}
proc fnormalize name {
code invokehidden tcl:file:normalize $name
}
proc fdirname name {
code invokehidden tcl:file:dirname $name
}
}
namespace eval ::repl::interpextras {
#install using safe::setLogCmd
@ -2775,32 +2861,44 @@ namespace eval repl {
namespace export {[a-z]*}
namespace ensemble create
proc punk {} {
interp eval code {
set ts_start [clock seconds]
set replresult [interp eval code {
package require punk::repl
repl::init -safe punk
repl::start stdin
}
}]
return [list replresult $replresult elapsed [expr {[clock seconds]-$ts_start}]]
}
proc safe {args} {
set ts_start [clock seconds]
interp eval code {
package require punk::repl
}
interp eval code [list repl::init -safe safe {*}$args]
interp eval code [list repl::start stdin]
set replresult [interp eval code [list repl::start stdin]]
return [list replresult $replresult elapsed [expr {[clock seconds]-$ts_start}]]
}
proc safebase {args} {
set ts_start [clock seconds]
interp eval code {
package require punk::repl
}
interp eval code [list repl::init -safe safebase {*}$args]
interp eval code [list repl::start stdin]
set codethread [interp eval code [list repl::init -safe safebase {*}$args]]
puts stdout "safebase codethread:$codethread"
set replresult [interp eval code [list repl::start stdin]]
return [list replresult $replresult elapsed [expr {[clock seconds]-$ts_start}]]
}
proc punksafe {args} {
set ts_start [clock seconds]
interp eval code {
package require punk::repl
}
interp eval code [list repl::init -safe punksafe {*}$args]
interp eval code [list repl::start stdin]
set replresult [interp eval code [list repl::start stdin]]
return [list replresult $replresult elapsed [expr {[clock seconds]-$ts_start}]]
}
}
# -- --- --- --- ---
@ -2819,24 +2917,173 @@ namespace eval repl {
switch -- $safe {
safe {
interp create -safe -- code
package require punk::args
}
safebase {
safe::interpCreate code -nested 1 -autoPath %autopath%
code alias "file normalize" "file normalize"
code alias "file dirname" "file dirname"
code alias "file exists" "file exists"
code alias ::tcl::file::normalize ::tcl::file::normalize
code alias ::tcl::file::dirname ::tcl::file::dirname
code alias ::tcl::file::exists ::tcl::file::exists
#code alias ::punk::console::colour ::punk::console::colour
}
punksafe {
#less safe than safebase - we need file normalize and info script to handle modpod?
package require punk::safe
punk::safe::interpCreate code -autoPath %autopath%
code alias "file normalize" "file normalize"
code alias "file dirname" "file dirname"
code alias "file exists" "file exists"
code alias ::tcl::file::normalize ::tcl::file::normalize
code alias ::tcl::file::dirname ::tcl::file::dirname
code alias ::tcl::file::exists ::tcl::file::exists
code alias ::punk::console::colour ::punk::console::colour
}
punk - 0 {
interp create code
}
punkisland {
#todo
#when no island paths specified - should be like safebase, but without folder hiding and with expanded read to ::auto_path folders
}
}
interp eval code {
namespace eval codeinterp {
variable errstack {}
variable outstack {}
variable run_command_cache
proc set_clone {varname obj} {
append obj2 $obj {}
uplevel 1 [list set $varname $obj2]
}
}
}
switch -- $safe {
safe {
if {[llength $paths]} {
package require punk::island
foreach p $paths {
punk::island::add code $p
}
}
interp share "" stdout code
interp share "" stderr code
interp share "" stdin code ;#needed for ANSI queries
set codehidden [code hidden]
code alias file file
if {"source" in $codehidden} {
code expose source
}
if {"encoding" in $codehidden} {
code expose encoding ;#leave enabled
}
if {"tcl:encoding:system" in $codehidden} {
code expose tcl:encoding:system
code eval {rename ::tcl::encoding::system ""}
code eval {rename tcl:encoding:system ::tcl::encoding::system}
}
#interp alias is available in safe - so it seems unreasonable to disallow 'info cmdtype'
if {"tcl:info:cmdtype" in $codehidden} {
code eval {rename ::tcl::info::cmdtype ""}
code expose tcl:info:cmdtype
code eval {rename tcl:info:cmdtype ::tcl::info::cmdtype}
}
set pkgs [list\
punk::args\
punk::pipe\
cmdline\
struct::list\
struct::set\
textutil::wcswidth\
textutil::trim\
textutil::repeat\
textutil::tabify\
textutil::split\
textutil::string\
textutil::adjust\
textutil\
punk::encmime\
punk::char\
punk::assertion\
punk::ansi\
punk::lib\
overtype\
dictutils\
debug\
punk::ns\
textblock\
punk::args::tclcore\
punk::aliascore\
]
#review argv0,argv,argc
interp eval code {
namespace eval ::codeinterp {
variable errstack {}
variable outstack {}
#pattern looks up versions available of patternlib before loading (but we don't have an index for tm files) todo fix pattern.
# patterncmd\
# metaface\
# patternpredator2\
# patternlib\
# pattern
# - no longer required by textblock
# term::ansi::code\
# term::ansi::code::attr\
# term::ansi::code::ctrl\
# term::ansi::code::macros
#----------
#all this scanning and loading core packages - we should possibly cache the file data for other interps?
#make sure codethread has scanned for packages - must do for each namespace level
#catch {package require flubber_nonexistent}
set ns_scanned [dict create]
#----------
set prior_infoscript [code eval {info script}] ;#probably empty that's ok
foreach pkg $pkgs {
if {[catch {
set nsquals [namespace qualifiers $pkg]
if {$nsquals ne ""} {
if {![dict exists $ns_scanned $nsquals]} {
catch {package require ${nsquals}::flubber_nonexistant} ;#force scan
dict set ns_scanned $nsquals 1
}
set ::argv0 %argv0%
set ::auto_path %autopath%
#puts stdout "safe interp"
#flush stdout
}
set versions [lsort -command {package vcompare} [package versions $pkg]]
if {[llength $versions]} {
set v [lindex $versions end]
set path [lindex [package ifneeded $pkg $v] end]
if {[file extension $path] in {.tcl .tm}} {
if {[file exists $path]} {
set data [readFile $path]
code eval [list info script $path]
code eval $data
code eval [list info script $prior_infoscript]
} else {
error "safe - failed to find $path"
}
} else {
error "safe - refusing to attempt load of $pkg from $path - (allowed extensions .tcl .tm)"
}
} else {
error "safe - no versions of $pkg found"
}
} errMsg]} {
puts stderr "safe - failed to load package $pkg\n$errMsg\n$::errorInfo"
} else {
#puts stdout "safe - loaded $pkg from $path"
}
}
code alias file ""
code hide source
#review argv0,argv,argc
#interp eval code {
# set ::argv0 %argv0%
# set ::auto_path %autopath%
#}
interp eval code [list set ::tcl_platform(os) $::tcl_platform(os)]
interp eval code [list set ::tcl_platform(osVersion) $::tcl_platform(osVersion)]
interp eval code [list set ::tcl_platform(machine) $::tcl_platform(machine)]
@ -2851,12 +3098,18 @@ namespace eval repl {
interp share {} [shellfilter::stack::item_tophandle stderr] code
}
#review
code alias ::shellfilter::stack ::shellfilter::stack
#code alias ::punk::lib::set_clone ::punk::lib::set_clone
#code alias ::aliases ::punk::lib::aliases
code alias ::punk::lib::aliases ::punk::lib::aliases
namespace eval ::codeinterp {}
code alias ::md5::md5 ::repl::interphelpers::md5
code alias exit ::repl::interphelpers::quit
}
safebase {
#safebase
safe::interpCreate code -nested 1 -autopath %autopath%
#safe::setLogCmd ::repl::interpextras::safe_msg ;#using setLogcmd early will show the auto_path notice - which is *verbose*
#while it may conceivably be useful in debugging safe itself - auto_path and tcl::tm::list can be inspected to show these values in the safe interp itself anyway - so early logging is of limited utility here.
if {[llength $paths]} {
@ -2871,15 +3124,13 @@ namespace eval repl {
set ::argv {}
#puts stdout "safebase interp"
#flush stdout
namespace eval ::codeinterp {
variable errstack {}
variable outstack {}
}
}
interp eval code [list set ::tcl_platform(os) $::tcl_platform(os)]
interp eval code [list set ::tcl_platform(osVersion) $::tcl_platform(osVersion)]
interp eval code [list set ::tcl_platform(machine) $::tcl_platform(machine)]
code alias ::fconfigure ::repl::interphelpers::fconfigure ;#needed for shellfilter
#code invokehidden package require punk::lib
if {"stdout" in [chan names]} {
interp share {} stdout code
@ -2893,7 +3144,7 @@ namespace eval repl {
}
interp eval code {
package require punk::lib
package require textblock ;#may fail to load term::ansi::code::macros - (only required for altg)
package require textblock
}
#JMN
@ -2926,60 +3177,65 @@ namespace eval repl {
code alias exit ::repl::interphelpers::quit
code alias ::md5::md5 ::repl::interphelpers::md5
code alias ::fconfigure ::fconfigure ;#needed for shellfilter
code alias ::file ::file
interp eval code [list package provide md5 $md5version]
}
punk - 0 {
interp create code
punksafe {
interp eval code {
#safe !=1 and safe !=2, tmlist: %tmlist%
set ::argv0 %argv0%
set ::argv %argv%
set ::argc %argc%
set ::auto_path %autopath%
tcl::tm::remove {*}[tcl::tm::list]
tcl::tm::add {*}[lreverse %tmlist%]
#puts "code interp chan names-->[chan names]"
namespace eval ::codeinterp {
variable errstack {}
variable outstack {}
variable run_command_cache
set ::argc 0
set ::argv {}
#set ::auto_path %autopath% ;#jmn
#tcl::tm::remove {*}[tcl::tm::list]
#tcl::tm::add {*}[lreverse %tmlist%]
}
interp eval code [list set ::tcl_platform(os) $::tcl_platform(os)]
interp eval code [list set ::tcl_platform(osVersion) $::tcl_platform(osVersion)]
interp eval code [list set ::tcl_platform(machine) $::tcl_platform(machine)]
# -- ---
#review
#we have to blow some time on a rescan to provide more deterministic ordering (match behaviour of initial thread regarding pkg precedence)
#review - can we speed that scan up?
##catch {package require flobrudder-nonexistant}
# -- ---
code alias ::fconfigure ::repl::interphelpers::fconfigure ;#needed for shellfilter
if {"stdout" in [chan names]} {
interp share {} stdout code
} else {
interp share {} [shellfilter::stack::item_tophandle stdout] code
}
if {"stderr" in [chan names]} {
interp share {} stderr code
} else {
interp share {} [shellfilter::stack::item_tophandle stderr] code
}
interp eval code {
package require punk::lib
package require punk::args
package require punk::args::tclcore
package require textblock
}
interp eval code {
if {[catch {
package require vfs
package require vfs::zip
#package require packagetrace
#packagetrace::init
} errM]} {
puts stderr "repl code interp can't load vfs,vfs::zip"
puts stderr "========================"
puts stderr "code interp error 1:"
puts stderr $errM
puts stderr $::errorInfo
puts stderr "========================"
#error "$errM"
}
}
#puts stderr -----
#puts stderr [join $::auto_path \n]
#puts stderr -----
interp eval code {
if {[catch {
package require punk::config
package require punk::ns
#puts stderr "loading natsort"
#natsort has 'application mode' which can exit.
#Requiring it shouldn't trigger application - but zipfs/vfs interactions confused it in some early versions
package require natsort
#catch {package require packageTrace}
package require punk
package require punk::args
package require punk::args::tclcore
package require shellrun
package require shellfilter
package require punk::config ;#requires: none
#package require punk::console ;#requires: Thread,punk::ansi,punk::args
#set running_config $::punk::config::running
package require shellfilter ;#requires: shellthread,Thread
apply {running_config {
if {[string length [dict get $running_config color_stderr]] && [punk::console::colour]} {
lappend ::codeinterp::errstack [shellfilter::stack::add stderr ansiwrap -settings [list -colour [dict get $running_config color_stderr]]]
@ -2989,63 +3245,85 @@ namespace eval repl {
}
}} $::punk::config::running
package require textblock
} errM]} {
puts stderr "========================"
puts stderr "code interp error:"
puts stderr "code interp error 2:"
puts stderr $errM
puts stderr $::errorInfo
puts stderr "========================"
error "$errM"
}
}
}
punksafe {
package require punk::safe
punk::safe::interpCreate code -autoPath %auto_path%
interp eval code {
set ::argv0 %argv0%
set ::argc 0
set ::argv {}
tcl::tm::remove {*}[tcl::tm::list]
tcl::tm::add {*}[lreverse %tmlist%]
namespace eval ::codeinterp {
variable errstack {}
variable outstack {}
variable run_command_cache
}
}
if {[catch {
#puts stderr "loading natsort"
#natsort has 'application mode' which can exit.
#Requiring it shouldn't trigger application - but zipfs/vfs interactions confused it in some early versions
package require natsort
#package require punk ;# Thread
#package require shellrun ;#subcommand exists of file
if {"stdout" in [chan names]} {
interp share {} stdout code
} else {
interp share {} [shellfilter::stack::item_tophandle stdout] code
#-----------------------------------------------------------------------------------------------------------------------------------------
package require punk::ns ;#requires:punk::lib,punk::args,struct::list,cmdline+(tcllibc),struct::set,punk::ansi,punk::char,
#textutil,textutil::string,textutil::adjust,textutil::repeat,textutil::string,textutil::split,textutil::tabify,textutil::wcswidth
#punk::encmime,punk::assertion
#twapi,platform,registry,debug,overtype,patternpunk,pattern,patterncmd,metaface,patternpredator2,patternlib,dictutils
#-----------------------------------------------------------------------------------------------------------------------------------------
#package require textblock
} errM]} {
puts stderr "========================"
puts stderr "code interp error 3:"
puts stderr $errM
puts stderr $::errorInfo
puts stderr "========================"
error "$errM"
}
if {"stderr" in [chan names]} {
interp share {} stderr code
} else {
interp share {} [shellfilter::stack::item_tophandle stderr] code
}
interp eval code {
package require punk::lib
package require textblock ;#may fail to load term::ansi::code::macros - (only required for altg)
}
punk - 0 {
interp eval code {
#safe !=1 and safe !=2, tmlist: %tmlist%
set ::argv0 %argv0%
set ::argv %argv%
set ::argc %argc%
set ::auto_path %autopath%
tcl::tm::remove {*}[tcl::tm::list]
tcl::tm::add {*}[lreverse %tmlist%]
#puts "code interp chan names-->[chan names]"
# -- ---
#review
#we have to blow some time on a rescan to provide more deterministic ordering (match behaviour of initial thread regarding pkg precedence)
#review - can we speed that scan up?
##catch {package require flobrudder-nonexistant}
# -- ---
interp eval code {
if {[catch {
catch {
package require packagetrace
packagetrace::init
package require vfs
package require vfs::zip
} errM]} {
puts stderr "repl code interp can't load vfs,vfs::zip"
}
#puts stderr -----
#puts stderr [join $::auto_path \n]
#puts stderr -----
if {[catch {
package require punk::config
package require punk::ns
#puts stderr "loading natsort"
#natsort has 'application mode' which can exit.
#Requiring it shouldn't trigger application - but zipfs/vfs interactions confused it in some early versions
package require natsort
#catch {package require packageTrace}
package require punk
package require punk::args
package require punk::args::tclcore
@ -3070,9 +3348,7 @@ namespace eval repl {
puts stderr "========================"
error "$errM"
}
}
}
default {
}
@ -3083,6 +3359,7 @@ namespace eval repl {
code alias editbuf ::repl::interphelpers::editbuf
code alias colour ::repl::interphelpers::colour
code alias mode ::repl::interphelpers::mode
code alias vt52 ::repl::interphelpers::vt52
#code alias after ::repl::interphelpers::do_after
code alias ::punk::set_repl_last_unknown ::repl::interphelpers::set_repl_last_unknown

22
src/modules/punk/repl/codethread-999999.0a1.0.tm

@ -114,6 +114,10 @@ tcl::namespace::eval punk::repl::codethread {
variable output_stdout ""
variable output_stderr ""
#review/test
catch {package require punk::ns}
catch {package rquire punk::repl}
#variable xyz
#*** !doctools
@ -191,9 +195,14 @@ tcl::namespace::eval punk::repl::codethread {
#shennanigans to keep compiled script around after call.
#otherwise when $script goes out of scope - internal rep of vars set in script changes.
#The shimmering may be no big deal(?) - but debug/analysis using tcl::unsupported::representation becomes impossible.
interp eval code [list ::punk::lib::set_clone ::codeinterp::clonescript $script] ;#like objclone
#interp eval code [list ::punk::lib::set_clone ::codeinterp::clonescript $script] ;#like objclone
interp eval code [list ::codeinterp::set_clone ::codeinterp::clonescript $script] ;#like objclone
interp eval code {lappend ::codeinterp::run_command_cache $::codeinterp::clonescript}
interp eval code {
lappend ::codeinterp::run_command_cache $::codeinterp::clonescript
#lappend ::codeinterp::run_command_cache $::codeinterp::clonescript
if {[llength $::codeinterp::run_command_cache] > 2000} {
set ::codeinterp::run_command_cache [lrange $::codeinterp::run_command_cache 1750 end][unset ::codeinterp::run_command_cache]
}
@ -205,10 +214,19 @@ tcl::namespace::eval punk::repl::codethread {
package require punk::ns
punk::ns::nseval_ifexists $::punk::ns::ns_current $::codeinterp::clonescript
} else {
if {![namespace exists $::punk::ns::ns_current]} {
namespace eval $::punk::ns::ns_current {
puts stderr "Created namespace: $::punk::ns::ns_current"
}
}
tcl::namespace::inscope $::punk::ns::ns_current $::codeinterp::clonescript
}
}
} result]
#temp test for subshell experimentation
#if {$status == 1} {
# puts stderr "--codethread::runscript error--------\n$::errorInfo"
#}
flush stdout

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

@ -107,14 +107,16 @@ namespace eval punk::repo {
}
#lappend PUNKARGS [list -dynamic 1 {
#lappend PUNKARGS [list {
# @dynamic
# @id -id ::punk::repo::fossil_proxy
# @cmd -name fossil -help "fossil executable
# "
# @argdisplay -header "fossil help" -body {${[runout -n fossil help]}}
# } ""]
lappend PUNKARGS [list -dynamic 1 {
lappend PUNKARGS [list {
@dynamic
@id -id ::punk::repo::fossil_proxy
@cmd -name fossil -help "fossil executable"
${[punk::repo::get_fossil_usage]}
@ -123,20 +125,24 @@ namespace eval punk::repo {
#experiment
lappend PUNKARGS [list -dynamic 1 {
lappend PUNKARGS [list {
@dynamic
@id -id "::punk::repo::fossil_proxy diff"
@cmd -name "fossil diff" -help "fossil diff
"
@argdisplay -header "fossil help diff" -body {${[runout -n fossil help diff]}}
} ""]
lappend PUNKARGS [list -dynamic 1 {
lappend PUNKARGS [list {
#todo - remove this comment - testing dynamic directive
@dynamic
@id -id "::punk::repo::fossil_proxy add"
@cmd -name "fossil add" -help "fossil add
"
@argdisplay -header "fossil help add" -body {${[runout -n fossil help add]}}
} ""]
#TODO
#lappend PUNKARGS [list -dynamic 1 {
#lappend PUNKARGS [list {
# @dynamic
# @id -glob 1 -id "::punk::repo::fossil_proxy *" -aliases {fs}
# @cmd -name "fossil add" -help "fossil add
# "
@ -1699,12 +1705,10 @@ namespace eval punk::repo::lib {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
if {![info exists ::punk::args::register::NAMESPACES]} {
namespace eval ::punk::args::register {
set ::punk::args::register::NAMESPACES [list] ;#use fully qualified so 8.6 doesn't find existing var in global namespace
}
}
#use fully qualified so 8.6 doesn't find existing var in global namespace
lappend ::punk::args::register::NAMESPACES ::punk::repo
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++

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

@ -458,7 +458,7 @@ tcl::namespace::eval punk::safe {
# If we have exactly 2 arguments the semantic is a "configure get"
lassign $args child arg
set spec_dict [punk::args::define [punk::args::rawdef punk::safe::interpIC]]
set spec_dict [punk::args::define [punk::args::raw_def punk::safe::interpIC]]
set opt_names [dict get $spec_dict opt_names]
CheckInterp $child
@ -773,7 +773,7 @@ tcl::namespace::eval punk::safe::system {
"::auto_path for the child"}
}
punk::args::define $OPTS
set optlines [punk::args::resolved_def -type @opts punk::safe::OPTS -*]
set optlines [punk::args::resolved_def -types opts ::punk::safe::OPTS -*]
set INTERPCREATE {
@id -id ::punk::safe::interpCreate
@ -783,6 +783,7 @@ tcl::namespace::eval punk::safe::system {
@leaders
child -type string -default "" -regexprefail "^-" -regexprefailmsg "" -optional 1 -help\
"name of the child (optional)"
#opts added separately
}
append INTERPCREATE \n $optlines
append INTERPCREATE \n {@values -max 0}
@ -1020,6 +1021,7 @@ tcl::namespace::eval punk::safe::system {
# Add the tcl::tm directories to the access path.
set morepaths [::tcl::tm::list]
set morepaths [lreverse $morepaths] ;#JMN - maintains same order when re-adding.
set firstpass 1
while {[llength $morepaths]} {
set addpaths $morepaths
@ -1059,6 +1061,13 @@ tcl::namespace::eval punk::safe::system {
# 'platform/shell-X.tm', i.e arbitrarily deep
# subdirectories.
lappend morepaths {*}[glob -nocomplain -directory $dir -type d *]
#experiment
#if {$firstpass} {
# package require punk::zip
# set subs [punk::zip::walk -resultrelative "" $dir *.tm] ;#walk finds files and dirs - dirs have trailing slash
# lappend morepaths {*}[lsearch -all -inline $subs */]
#}
}
set firstpass 0
}
@ -1142,7 +1151,8 @@ tcl::namespace::eval punk::safe::system {
fconfigure $f -encoding $encoding -eofchar \x1A
set contents [read $f]
close $f
::interp eval $child [list info script $file]
#::interp eval $child [list info script $file]
::interp eval $child [list info script $realfile]
} msg opt
]
@ -1513,7 +1523,7 @@ tcl::namespace::eval punk::safe::system {
# Add (only if needed, avoid duplicates) 1 level of sub directories to an
# existing path list. Also removes non directories from the returned
# list.
proc AddSubDirs {pathList} {
proc AddSubDirs1 {pathList} {
set res {}
foreach dir $pathList {
if {[file isdirectory $dir]} {
@ -1532,6 +1542,29 @@ tcl::namespace::eval punk::safe::system {
}
return $res
}
proc AddSubDirs {pathList} {
set res {}
foreach dir $pathList {
if {[file isdirectory $dir]} {
# check that we don't have it yet as a children of a previous
# dir
if {$dir ni $res} {
lappend res $dir
}
package require punk::zip
set subs [punk::zip::walk -resultrelative "" $dir *] ;#walk finds files and dirs - dirs have trailing slash
set dirs [lsearch -all -inline $subs */]
foreach sub $dirs {
if {[file isdirectory $sub] && ($sub ni $res)} {
# new sub dir, add it !
lappend res $sub
}
}
}
}
return $res
}
#
# Sets the child auto_path to its recorded access path. Also sets

6
src/modules/punk/winrun-999999.0a1.0.tm

@ -39,13 +39,13 @@ namespace eval punk::winrun {
proc readchild_handler {chan hpid} {
#fileevent $chan readable {}
set data [read $chan 4096]
while {![fblocked $chan] && ![eof $chan]} {
while {![chan blocked $chan] && ![eof $chan]} {
append data [read $chan 4096]
}
puts stdout "-->$data eof:[eof $chan] fblocked [fblocked $chan]"
puts stdout "-->$data eof:[eof $chan] chan blocked [chan blocked $chan]"
flush stdout
if {![eof $chan]} {
puts stdout "not eof $chan [fconfigure $chan] fblocked:[fblocked $chan]"
puts stdout "not eof $chan [fconfigure $chan] chan blocked:[chan blocked $chan]"
#fileevent $chan readable [list punk::winrun::readchild_handler $chan $hpid]
} else {
#puts "eof: waiting exit process"

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

@ -129,6 +129,7 @@ tcl::namespace::eval punk::winshell {
set pipename_stdin $pipebase$shellid-stdin
set pipename_stdout $pipebase$shellid-stdout
set pipename_stderr $pipebase$shellid-stderr
#swapped thisend/child - labels now wrong - todo - relabel or swap back?
set h_stdin [twapi::namedpipe_server $pipename_stdin] ;#handle for child process redirection
set p_stdin [twapi::namedpipe_client $pipename_stdin] ;#this end
set h_stdout [twapi::namedpipe_server $pipename_stdout] ;#handle for child process redirection
@ -138,15 +139,53 @@ tcl::namespace::eval punk::winshell {
set p_stderr [twapi::namedpipe_client $pipename_stderr] ;#this end
chan configure $p_stderr -blocking 0
set pid [exec cmd.exe /k >@$h_stdout 2>@$h_stderr <@$h_stdin &]
set pid [exec cmd.exe /k >@$p_stdout 2>@$p_stderr <@$p_stdin &]
dict set shellinfo $shellid id $shellid
dict set shellinfo $shellid pid $pid
dict set shellinfo $shellid stdin $p_stdin
dict set shellinfo $shellid stdout $p_stdout
dict set shellinfo $shellid stderr $p_stderr
dict set shellinfo $shellid stdin $h_stdin
dict set shellinfo $shellid stdout $h_stdout
dict set shellinfo $shellid stderr $h_stderr
return [dict get $shellinfo $shellid]
}
variable ack 0
proc handle_out {chan args} {
variable ack
#if {[catch {
# if {$ack} {
# punk::console::move_emit_return 3 79 "\\"
# set ack 0
# } else {
# punk::console::move_emit_return 3 79 /
# set ack 1
# }
#} errM]} {
# puts "err on move_emit_return"
#}
puts -nonewline stdout [punk::ansi::ansistring VIEW [read $chan]]
}
proc handle_err {chan args} {
variable ack
#if {$ack} {
# punk::console::move_emit_return 3 79 -
# set ack 0
#} else {
# punk::console::move_emit_return 3 79 |
# set ack 1
#}
puts -nonewline stderr [read $chan]
}
proc cmdtest {{id ""}} {
set cinfo [cmdexec $id]
set o [dict get $cinfo stdout]
chan conf $o -buffering none -blocking 0
set e [dict get $cinfo stderr]
chan conf $e -buffering none -blocking 0
chan event $o readable [list ::punk::winshell::handle_out $o]
chan event $e readable [list ::punk::winshell::handle_err $e]
return $cinfo
}
#test with twapi create_process
proc cmdcreate {{id ""}} {
@ -255,10 +294,10 @@ tcl::namespace::eval punk::winshell {
#review - *no running instance* works with windows taskkill - "*No such process*" works with kill -9 on FreeBSD and linux - other platforms?
if {![string match "*no running instance*" $errMsg] && ![string match "*No such process*" $errMsg]} {
lappend failed_kits [list kit $targetkit reason "could not kill running process for $targetkit (using '$killcmd')"]
lappend failed_kits [list reason "could not kill running process for shellid $shellid (using '$killcmd')"]
continue
} else {
puts stderr "
puts stderr ""
}
} else {
puts stderr "$killcmd ran without error"
@ -267,6 +306,10 @@ tcl::namespace::eval punk::winshell {
}
proc shellinfo {} {
variable shellinfo
return $shellinfo
}
proc cmdinfo {{id ""}} {
variable autoshellid
variable shellinfo
@ -279,8 +322,11 @@ tcl::namespace::eval punk::winshell {
set info [dict get $shellinfo $shellid]
set pid [dict get $info pid]
catch {
set statusresult [tcl::process status $pid]
dict set info status $statusresult
}
set cmdline [twapi::get_process_commandline $pid]
dict set info cmdline $cmdline
return [showdict $info]
@ -297,7 +343,11 @@ tcl::namespace::eval punk::winshell {
set shellid $id
}
set pid [dict get $shellinfo $shellid pid]
set statusresult ""
catch {
#not in 8.6?
set statusresult [tcl::process status $pid]
}
return [dict create id $shellid status $statusresult]
}

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

@ -194,6 +194,12 @@ tcl::namespace::eval punk::zip {
Defaults to the value of base. If empty string
is given to -resultrelative the paths returned
are effectively absolute paths."
-emptydirs -default 0 -type boolean -help\
"Whether to include directory trees in the result which had no
matches for the given fileglobs.
Intermediate dirs are always returned if there is a match with
fileglobs further down even if -emptdirs is 0.
"
-excludes -default "" -help "list of glob expressions to match against files and exclude"
-subpath -default "" -help\
"May contain glob chars for folder elements"
@ -205,6 +211,7 @@ tcl::namespace::eval punk::zip {
set fileglobs [dict get $argd values fileglobs]
set subpath [dict get $argd opts -subpath]
set excludes [dict get $argd opts -excludes]
set emptydirs [dict get $argd opts -emptydirs]
set received [dict get $argd received]
@ -242,13 +249,32 @@ tcl::namespace::eval punk::zip {
if {!$excluded} {lappend result [file join $prefix $file]}
}
foreach dir [glob -nocomplain -tails -types d -directory $base -- [file join $subpath *]] {
set subdir_entries [walk -subpath $dir -excludes $excludes $base {*}$fileglobs]
if {[llength $subdir_entries]>0} {
set submatches [walk -subpath $dir -emptydirs $emptydirs -excludes $excludes $base {*}$fileglobs]
set subdir_entries [list]
set thisdir_match [list]
set has_file 0
foreach sd $submatches {
set fullpath [file join $prefix $sd] ;#file join destroys trailing slash
if {[string index $sd end] eq "/"} {
lappend subdir_entries $fullpath/
} else {
set has_file 1
lappend subdir_entries $fullpath
}
}
if {$emptydirs} {
set thisdir_match [list "[file join $prefix $dir]/"]
} else {
if {$has_file} {
set thisdir_match [list "[file join $prefix $dir]/"]
} else {
set subdir_entries [list]
}
}
#NOTE: trailing slash required for entries to be recognised as 'file type' = "directory"
#This is true for 2024 Tcl9 mounted zipfs at least. zip utilities such as 7zip seem(icon correct) to recognize dirs with or without trailing slash
#Although there are attributes on some systems to specify if entry is a directory - it appears trailing slash should always be used for folder names.
set result [list {*}$result "[file join $prefix $dir]/" {*}$subdir_entries]
}
set result [list {*}$result {*}$thisdir_match {*}$subdir_entries]
}
return $result
}

111
src/modules/punkcheck-0.1.0.tm

@ -1170,6 +1170,7 @@ namespace eval punkcheck {
}
#skip writing punkcheck during checksum/timestamp checks
#todo - punk::args - fetch from punkcheck::install (with overrides)
proc install_tm_files {srcdir basedir args} {
set defaults [list\
-glob *.tm\
@ -1209,13 +1210,71 @@ namespace eval punkcheck {
return [lindex $args end]
}
}
lappend PUNKARGS [list {
@id -id ::punkcheck::install
@cmd -name ::punkcheck::install -help\
"Unidirectional file transfer to possibly non-empty target folder."
@leaders -min 2 -max 2
srcdir -type directory
tgtdir -type directory
-call-depth-internal -type integer -default 0 -help "(internal recursion tracker)"
-subdirlist -type list -default "" -help "(primarily internal - length generally matching -call-depth-internal)"
-max_depth -type integer -default 1000 -help\
"Deepest subdirectory - use -1 for no limit."
-createdir -type boolean -default 0 -help\
"Whether to create the folder at tgtdir.
Any required subdirectories are created regardless of this setting."
-createempty -type boolean -default 0 -help\
"Whether to create folders at target that had no matches for our glob"
-glob -type string -default "*" -help\
"Pattern matching for source file(s) to copy. Can be glob based or exact match."
-antiglob_file_core -default {${[::punkcheck::default_antiglob_file_core]}}
-antiglob_file -default ""
-antiglob_dir_core -default {${[::punkcheck::default_antiglob_dir_core]}}
-antiglob_dir -default ""
-antiglob_paths -default {}
-overwrite -default no-targets\
-choices {no-targets newer-targets older-targets all-targets installedsourcechanged-targets synced-targets}\
-choicecolumns 1\
-choicelabels {
no-targets "only copy files that are missing at the target"
newer-targets "copy files with older source timestamp over newer
target timestamp and those missing at the target
(a form of 'restore' operation)"
older-targets "copy files with newer source timestamp over older
target timestamp and those missing at the target"
all-targets "copy regardless of timestamp at target"
installedsourcechanged-targets "copy if the target doesn't exist or the source changed"
synced-targets "copy if the target doesn't exist or the source changed
and the target cksum is the same as the last INSTALL-RECORD -targets_cksums entry"
}
-source_checksum -default comparestore -choicecolumns 3 -choices {compare store comparestore false true}\
-choicelabels {
true "same as comparestore"
}
-punkcheck_folder -default target -choices {target source project} -choicerestricted 0 -help\
"The location of the .punkcheck file to track installations and checksums.
The default value 'target' is generally recommended.
Can also be an absolute path to a folder."
-punkcheck_records -default "" -help\
"Empty string or a parsed TDL records structure.
e.g
{tag FILEINFO -<opt> <val>... body {
{tag INSTALL-RECORD -<opt> <val>... body {<sublist>}}
...
}...
}"
-installer -default "punkcheck::install" -help\
"A user nominated string that is stored in the .punkcheck file
This might be the name of a script or installation process."
}]
## unidirectional file transfer to possibly non empty folder
#default of -overwrite no-targets will only copy files that are missing at the target
# -overwrite newer-targets will copy files with older source timestamp over newer target timestamp and those missing at the target (a form of 'restore' operation)
# -overwrite older-targets will copy files with newer source timestamp over older target timestamp and those missing at the target
# -overwrite all-targets will copy regardless of timestamp at target
# -overwrite installedsourcechanged-targets will copy if the target doesn't exist or the source changed
# -overwrite synced-targets will copy if the target doesn't exist or the source changed and the target cksum is the same as the last INSTALL-RECORD targets_cksums entry
# -overwrite synced-targets will copy if the target doesn't exist or the source changed and the target cksum is the same as the last INSTALL-RECORD -targets_cksums entry
# review - timestamps unreliable
# - what about slightly mismatched system clocks and mounted filesystems? caller responsibility to verify first?
# if timestamp exactly equal - should we check content-hash? This is presumably only likely to occur deliberately(maliciously?)
@ -1243,6 +1302,7 @@ namespace eval punkcheck {
-max_depth 1000\
-subdirlist {}\
-createdir 0\
-createempty 0\
-glob *\
-antiglob_file_core "\uFFFF"\
-antiglob_file "" \
@ -1271,13 +1331,14 @@ namespace eval punkcheck {
#(as opposed to a more 'standard' mechanism of adjusting srcdir & tgtdir as we move down the tree)
#It comes from build_modules_from_source_to_base where we need to keep track of position relative to our targetdir starting point to handle submodules e.g pkg::something::mypkg-0.1.tm
#It could have been handled with some other parameter such as -depth, but this -subdirlist mechanism, whilst perhaps not beautiful, is straightforward enough
#and may be less error prone than doing slightly more opaue path manipulations at each recursion level to determine where we started
#and may be less error prone than doing slightly more opaque path manipulations at each recursion level to determine where we started
#For consistency - we'll use the same mechanism in various recursive directory walking procedures such as this one.
set CALLDEPTH [dict get $opts -call-depth-internal] ;#added for extra debug/sanity checking - clearer test for initial function call ie CALLDPEPTH = 0
set CALLDEPTH [dict get $opts -call-depth-internal] ;#added for extra debug/sanity checking - clearer test for initial function call ie CALLDEPTH = 0
set max_depth [dict get $opts -max_depth] ;# -1 for no limit
set subdirlist [dict get $opts -subdirlist] ;# generally should be same length as CALLDEPTH - but user could prefill
set fileglob [dict get $opts -glob]
set createdir [dict get $opts -createdir] ;#defaults to zero to help avoid mistakes with initial target dir - required target subdirs are created regardless of this setting
set opt_createempty [dict get $opts -createempty]
if {$CALLDEPTH == 0} {
#expensive to normalize but we need to do it at least once
@ -1285,6 +1346,13 @@ namespace eval punkcheck {
set tgtdir [file normalize $tgtdir]
if {$createdir} {
file mkdir $tgtdir
} else {
if {![file exists $tgtdir]} {
error "punkcheck::install base target dir:'$tgtdir' doesn't exist (srcdir:$srcdir tgtdir:$tgtdir args:'$args')"
}
}
if {([file type $srcdir] ni [list directory]) || ([file type $tgtdir] ni [list directory])} {
error "punkcheck::install requires source and target dirs to be of type 'directory' type current source: [file type $srcdir] type current target: [file type $tgtdir]"
}
#now the values we build from these will be properly cased
}
@ -1450,13 +1518,7 @@ namespace eval punkcheck {
if {![file exists $current_source_dir]} {
error "copy_files_from_source_to_target current source dir:'$current_source_dir' doesn't exist (srcdir:$srcdir tgtdir:$tgtdir args:'$args')"
}
if {![file exists $current_target_dir]} {
error "copy_files_from_source_to_target current target dir:'$current_target_dir' doesn't exist (srcdir:$srcdir tgtdir:$tgtdir args:'$args')"
}
if {([file type $current_source_dir] ni [list directory]) || ([file type $current_target_dir] ni [list directory])} {
error "copy_files_from_source_to_target requires source and target dirs to be of type 'directory' type current source: [file type $current_source_dir] type current target: [file type $current_target_dir]"
error "punkcheck::install current source dir:'$current_source_dir' doesn't exist (srcdir:$srcdir tgtdir:$tgtdir args:'$args')"
}
set files_copied [list]
@ -1501,6 +1563,12 @@ namespace eval punkcheck {
# }
#}
if {[llength $match_list]} {
#example - target dir has a file where there is a directory at the source
if {[file exists $current_target_dir] && ([file type $current_target_dir] ni [list directory])} {
error "punkcheck::install target subfolder $current_target_dir exists but is not of type 'directory'. Type current target folder: [file type $current_target_dir]"
}
}
#proc get_relativecksum_from_base_and_fullpath {base fullpath args}
@ -1579,10 +1647,12 @@ namespace eval punkcheck {
set is_skip 0
if {$overwrite_what eq "all-targets"} {
file mkdir $current_target_dir
file copy -force $current_source_dir/$m $current_target_dir
lappend files_copied $current_source_dir/$m
} else {
if {![file exists $current_target_dir/$m]} {
file mkdir $current_target_dir
file copy $current_source_dir/$m $current_target_dir
set new_tgt_cksum_info [punk::mix::base::lib::cksum_path $current_target_dir/$m]
lappend files_copied $current_source_dir/$m
@ -1592,6 +1662,7 @@ namespace eval punkcheck {
installedsourcechanged-targets {
if {[llength $changed]} {
#An unrecorded installation is considered a source change (from unknown/unrecorded source to recorded)
file mkdir $current_target_dir
file copy -force $current_source_dir/$m $current_target_dir
set new_tgt_cksum_info [punk::mix::base::lib::cksum_path $current_target_dir/$m]
lappend files_copied $current_source_dir/$m
@ -1619,6 +1690,7 @@ namespace eval punkcheck {
set target_cksum_compare "norecord"
}
if {$is_target_unmodified_since_install} {
file mkdir $current_target_dir
file copy -force $current_source_dir/$m $current_target_dir
set new_tgt_cksum_info [punk::mix::base::lib::cksum_path $current_target_dir/$m]
lappend files_copied $current_source_dir/$m
@ -1642,6 +1714,12 @@ namespace eval punkcheck {
}
}
}
#target dir was created as necessary if files matched above
#now ensure target dir exists if -createempty true
if {$opt_createempty && ![file exists $current_target_dir]} {
file mkdir $current_target_dir
}
set ts_now [clock microseconds]
@ -1724,10 +1802,9 @@ namespace eval punkcheck {
continue
}
if {![file exists $current_target_dir/$d]} {
file mkdir $current_target_dir/$d
}
#if {![file exists $current_target_dir/$d]} {
# file mkdir $current_target_dir/$d
#}
set sub_opts_1 [list\
@ -2096,8 +2173,10 @@ namespace eval punkcheck {
namespace eval ::punk::args::register {
#use fully qualified so 8.6 doesn't find existing var in global namespace
lappend ::punk::args::register::NAMESPACES ::punkcheck
}

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

@ -64,6 +64,8 @@ namespace eval punkcheck::cli {
#vfs can mask mounted files - so we can't just use 'file type' or glob with -type f
##set files [glob -nocomplain -dir $fullpath -type f *]
package require punk::nav::fs
#TODO - get all files in tree!!!
set folderinfo [punk::nav::fs::dirfiles_dict $fullpath]
set files [concat [dict get $folderinfo files] [dict get $folderinfo underlayfiles]]
}

17
src/modules/shellfilter-0.1.9.tm

@ -751,6 +751,12 @@ namespace eval shellfilter::chan {
} else {
#REVIEW - this holding a buffer without emitting as we go is ugly.
# - we may do better to detect and retain the opener, then use that opener to avoid false splits within the sequence.
# - we'd then need to detect the appropriate close to restart splitting and codestacking
# - we may still need to retain and append the data to the opener (in some cases?) - which is a slight memory issue - but at least we would emit everything immediately.
#puts "-->esc but no detect"
#no complete ansi codes - but at least one esc is present
if {[string last \x1b $buf] == [llength $buf]-1} {
@ -1118,8 +1124,8 @@ namespace eval shellfilter::stack {
proc status {{pipename *} args} {
variable pipelines
set pipecount [dict size $pipelines]
set tableprefix "$pipecount pipelines active\n"
set t [textblock::class::table new $tableprefix]
set tabletitle "$pipecount pipelines active"
set t [textblock::class::table new $tabletitle]
$t add_column -headers [list channel-ident]
$t add_column -headers [list device-info localchan]
$t configure_column 1 -header_colspans {3}
@ -1402,7 +1408,8 @@ namespace eval shellfilter::stack {
}
dict set pipelines $pipename stack $stack
}
show_pipeline $pipename -note "after_remove $remove_id"
#JMNJMN 2025 review!
#show_pipeline $pipename -note "after_remove $remove_id"
return 1
}
@ -1607,7 +1614,9 @@ namespace eval shellfilter::stack {
#puts stdout "=="
#puts stdout "==>stack: $stack"
#puts stdout "=="
show_pipeline $pipename -note "after_add $transformname $args"
#JMNJMN
#show_pipeline $pipename -note "after_add $transformname $args"
return $id
}
proc show_pipeline {pipename args} {

4
src/modules/shellthread-1.6.1.tm

@ -398,8 +398,8 @@ namespace eval shellthread::manager {
error {wrong # args: should be "dict_getdef dictValue ?key ...? key default"}
}
set keys [lrange $args 0 end-1]
if {[dict exists $dictValue {*}$keys]} {
return [dict get $dictValue {*}$keys]
if {[tcl::dict::exists $dictValue {*}$keys]} {
return [tcl::dict::get $dictValue {*}$keys]
} else {
return [lindex $args end]
}

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

@ -62,14 +62,16 @@ catch {package require patternpunk}
package require overtype
#safebase interps as at 2024-08 can't access deeper paths - even though they are below the supposed safe list.
if {[catch {
package require term::ansi::code::macros ;#required for frame if old ansi g0 used - review - make package optional?
} errM]} {
#catch this too in case stderr not available
catch {
puts stderr "textblock package failed to load term::ansi::code::macros with error: $errM"
}
}
#2025 - required term::ansi features for altg now built in to textblock
#the deeper paths issue is still a potential issue for some packages - review
#if {[catch {
# package require term::ansi::code::macros ;#required for frame if old ansi g0 used - review - make package optional?
#} errM]} {
# #catch this too in case stderr not available
# catch {
# puts stderr "textblock package failed to load term::ansi::code::macros with error: $errM"
# }
#}
package require textutil
@ -139,7 +141,8 @@ tcl::namespace::eval textblock {
# hash_algorithm -optional 1 -choices {${[::textblock::argdoc::hash_algorithm_choices]}} -help\
# "algorithm choice"
punk::args::define -dynamic 1 {
punk::args::define {
@dynamic
@id -id ::textblock::use_hash
@cmd -name "textblock::use_hash" -help\
"Hashing algorithm to use for framecache lookup.
@ -4068,12 +4071,12 @@ tcl::namespace::eval textblock {
return $frametypes
}
tcl::namespace::eval cd {
#todo - save and restore existing tcl::namespace::export in case macros::cd has default exports in future
tcl::namespace::eval ::term::ansi::code::macros::cd {tcl::namespace::export *}
tcl::namespace::import ::term::ansi::code::macros::cd::*
tcl::namespace::eval ::term::ansi::code::macros::cd {tcl::namespace::export -clear}
}
#tcl::namespace::eval cd {
# #todo - save and restore existing tcl::namespace::export in case macros::cd has default exports in future
# tcl::namespace::eval ::term::ansi::code::macros::cd {tcl::namespace::export *}
# tcl::namespace::import ::term::ansi::code::macros::cd::*
# tcl::namespace::eval ::term::ansi::code::macros::cd {tcl::namespace::export -clear}
#}
proc spantest {} {
set t [list_as_table -columns 5 -return tableobject {a b c d e aa bb cc dd ee X Y}]
$t configure_column 0 -headers [list span3 "1-span4\n2-span4 second line" span5/5 "span-all etc blah 123 hmmmmm" span2]
@ -5071,7 +5074,7 @@ tcl::namespace::eval textblock {
#only call width functions if known_samewidth - otherwise let the pad algorithm below determine it as we go
if {$known_samewidth ne "" && $known_samewidth} {
if {$known_blockwidth eq ""} {
set datawidth [textblock::widthtopline $block
set datawidth [textblock::widthtopline $block]
} else {
set datawidth $known_blockwidth
}
@ -6214,16 +6217,22 @@ tcl::namespace::eval textblock {
switch -- $f {
"altg" {
#old style ansi escape sequences with alternate graphics page G0
set hl [cd::hl]
#set hl [cd::hl]
set hl [punk::ansi::g0 q]
set hlt $hl
set hlb $hl
set vl [cd::vl]
#set vl [cd::vl]
set vl [punk::ansi::g0 x]
set vll $vl
set vlr $vl
set tlc [cd::tlc]
set trc [cd::trc]
set blc [cd::blc]
set brc [cd::brc]
#set tlc [cd::tlc]
set tlc [punk::ansi::g0 l]
#set trc [cd::trc]
set trc [punk::ansi::g0 k]
#set blc [cd::blc]
set blc [punk::ansi::g0 m]
#set brc [cd::brc]
set brc [punk::ansi::g0 j]
#horizontal and vertical bar joins
set hltj $hlt
@ -7417,7 +7426,8 @@ tcl::namespace::eval textblock {
set blc \U1fb7c ;#legacy block
set brc \U1fb7f ;#legacy block
if {[punk::console::check::has_bug_legacysymbolwidth]} {
if {(![interp issafe])} {
if {![catch {punk::console::check::has_bug_legacysymbolwidth} symbug] && $symbug} {
#rather than totally fail on some mixed layout that happens to use block2 - just degrade it - but prevent alignment problems
set sp \u00a0 ;#non breaking space (plain space may act transparent in some use cases)
set tlc $sp
@ -7425,6 +7435,7 @@ tcl::namespace::eval textblock {
set blc $sp
set brc $sp
}
}
#horizontal and vertical bar joins
set hltj $hlt
@ -7560,7 +7571,7 @@ tcl::namespace::eval textblock {
still wrap in an ugly manner. Try 'textblock::use_cache md5'
to shorten the argument display and reduce wrapping.
"
@values -min 0 -max 1
@values -min 0 -max -1
action -default {display} -choices {clear size info display} -choicelabels {
clear "Clear the textblock::frame_cache dictionary."
} -help "Perform an action on the frame cache."
@ -7569,6 +7580,8 @@ tcl::namespace::eval textblock {
set argd [punk::args::get_by_id ::textblock::frame_cache $args]
set action [dict get $argd values action]
variable frame_cache
set all_values_dict [dict get $argd values]
set action_values [lrange [dict values $all_values_dict] 1 end]
switch -- $action {
clear {
set size [dict size $frame_cache]
@ -7589,8 +7602,65 @@ tcl::namespace::eval textblock {
error "frame_cache -action '$action' not understood. Valid actions: clear size info display"
}
}
if {[llength $action_values]} {
return [frame_cache_display -pretty [dict get $argd opts -pretty] {*}$action_values]
} else {
return [frame_cache_display -pretty [dict get $argd opts -pretty]]
}
}
punk::args::define {
@dynamic
@id -id ::textblock::frame_cache_display
@opts
${[::punk::args::resolved_def -types opts ::textblock::frame_cache -pretty]}
@values -min 0 -max 2
startindex -default "" -type indexexpression -help\
"If both startindex and endindex are missing/empty, it is treated as
startindex 0 endindex end. (ie displays all records)
If only startindex has a value - the frame_cache record at that
index will be displayed"
endindex -default "" -type indexexpression
}
proc frame_cache_display {args} {
set argd [punk::args::get_by_id ::textblock::frame_cache_display $args]
variable frame_cache
lassign [dict values [dict get $argd values]] startidx endidx
set limit ""
if {$startidx ne ""} {
if {$endidx ne ""} {
if {$startidx eq $endidx} {
set limit "index"
} else {
set limit "range"
}
} else {
set limit "index"
}
} else {
set limit "all"
}
set display_dict {}
switch -- $limit {
all {
set display_dict $frame_cache
}
index {
set k [lindex [dict keys $frame_cache] $startidx]
if {$k ne ""} {
set display_dict [dict create $k [dict get $frame_cache $k]]
}
}
range {
set keys [lrange [dict keys $frame_cache] $startidx $endidx]
foreach k $keys {
dict set display_dict $k [dict get $frame_cache $k]
}
}
}
if {[dict get $argd opts -pretty]} {
set out [pdict -chan none frame_cache */*]
set out [pdict -chan none display_dict */*]
} else {
set out ""
if {[catch {
@ -7599,7 +7669,7 @@ tcl::namespace::eval textblock {
set termwidth 80
}
tcl::dict::for {k v} $frame_cache {
tcl::dict::for {k v} $display_dict {
lassign $v _f frame _used used
set fwidth [textblock::widthtopline $frame]
#review - are cached frames uniform width lines?
@ -7651,7 +7721,8 @@ tcl::namespace::eval textblock {
# ${[textblock::frame_samples]}
#todo punk::args alias for centre center etc?
punk::args::define -dynamic 1 {
punk::args::define {
@dynamic
@id -id ::textblock::frame
@cmd -name "textblock::frame"\
-help "Frame a block of text with a border."
@ -8208,9 +8279,11 @@ tcl::namespace::eval textblock {
}
altg {
set tbar [tcl::string::repeat $hlt $frame_inner_width]
set tbar [cd::groptim $tbar]
#set tbar [cd::groptim $tbar]
set tbar [punk::ansi::groptim $tbar]
set bbar [tcl::string::repeat $hlb $frame_inner_width]
set bbar [cd::groptim $bbar]
#set bbar [cd::groptim $bbar]
set bbar [punk::ansi::groptim $bbar]
}
default {
set tbar [tcl::string::repeat $hlt $frame_inner_width]

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

@ -18,7 +18,7 @@ namespace eval ::punkboot {
variable foldername [file tail $scriptfolder]
variable pkg_requirements [list]; variable pkg_missing [list];variable pkg_loaded [list]
variable non_help_flags [list -k]
variable help_flags [list -help --help /?]
variable help_flags [list -help --help /? -h]
variable known_commands [list project modules vfs info check shell vendorupdate bootsupport vfscommonupdate]
}
@ -180,10 +180,14 @@ set bootsupport_module_paths [list]
set bootsupport_library_paths [list]
if {[file exists [file join $startdir src bootsupport]]} {
lappend bootsupport_module_paths [file join $startdir src bootsupport modules]
lappend bootsupport_module_paths [file join $startdir src bootsupport modules_tcl$::tclmajorv]
lappend bootsupport_library_paths [file join $startdir src bootsupport lib]
lappend bootsupport_library_paths [file join $startdir src bootsupport lib_tcl$::tclmajorv]
} else {
lappend bootsupport_module_paths [file join $startdir bootsupport modules]
lappend bootsupport_module_paths [file join $startdir bootsupport modules_tcl$::tclmajorv]
lappend bootsupport_library_paths [file join $startdir bootsupport lib]
lappend bootsupport_library_paths [file join $startdir bootsupport lib_tcl$::tclmajorv]
}
set bootsupport_paths_exist 0
foreach p [list {*}$bootsupport_module_paths {*}$bootsupport_library_paths] {
@ -203,13 +207,13 @@ set sourcesupport_paths_exist 0
#(most?) Modules in src/modules etc should still be runnable directly in certain cases like this where we point to them.
if {[file tail $startdir] eq "src"} {
#todo - other src 'module' dirs..
foreach p [list $startdir/modules $startdir/modules_tcl$::tclmajorv] {
foreach p [list $startdir/modules $startdir/modules_tcl$::tclmajorv $startdir/vendormodules $startdir/vendormodules_tcl$::tclmajorv] {
if {[file exists $p]} {
lappend sourcesupport_module_paths $p
}
}
# -- -- --
foreach p [list $startdir/vendorlib $startdir/vendorlib_tcl${::tclmajorv}] {
foreach p [list $startdir/lib $startdir/lib_tcl$::tclmajorv $startdir/vendorlib $startdir/vendorlib_tcl$::tclmajorv] {
if {[file exists $p]} {
lappend sourcesupport_library_paths $p
}
@ -266,9 +270,12 @@ if {$bootsupport_paths_exist || $sourcesupport_paths_exist} {
package forget $pkg
}
}
#tcl::tm::add {*}$original_tm_list {*}$bootsupport_module_paths {*}$sourcesupport_module_paths
#set ::auto_path [list {*}$original_auto_path {*}$bootsupport_library_paths {*}$sourcesupport_library_paths]
tcl::tm::add {*}$bootsupport_module_paths {*}$sourcesupport_module_paths
set ::auto_path [list {*}$bootsupport_library_paths {*}$sourcesupport_library_paths]
}
puts "----> auto_path $::auto_path"
@ -1046,7 +1053,9 @@ proc ::punkboot::punkboot_gethelp {args} {
append h " - before calling this (followed by make project) - you can test using '<builtexe>(.exe) dev'" \n
append h " this will load modules from your <projectdir>/module <projectdir>/lib paths instead of from the kit/zipkit" \n \n
append h " $scriptname info" \n
append h " - show the name and base folder of the project to be built" \n
append h " - show the name and base folder of the project to be built" \n \n
append h " $scriptname check" \n
append h " - show module/library paths and any potentially problematic packages for running this script" \n
append h "" \n
if {[llength [dict get $pkg_availability missing]] || [llength [dict get $pkg_availability broken]]} {
set has_recommended 0
@ -1116,6 +1125,7 @@ if {[llength $commands_found] != 1 } {
set do_help 1
}
if {$do_help} {
puts stdout "Checking package availability..."
set ::punkboot::pkg_availability [::punkboot::check_package_availability -quiet 1 $::punkboot::bootsupport_requirements]
foreach pkg_request [dict get $::punkboot::pkg_availability loaded] {
#puts stderr "---> $pkg_request"
@ -1407,6 +1417,7 @@ if {$::punkboot::command eq "vendorupdate"} {
puts stderr "Unable to use punkcheck for vendormodules$which update. Error: $errM"
set installation_event ""
}
#todo - sync alg with bootsupport_localupdate!
foreach {relpath requested_module} $local_modules {
set requested_module [string trim $requested_module :]
set module_subpath [string map {:: /} [namespace qualifiers $requested_module]]
@ -1488,6 +1499,11 @@ if {$::punkboot::command eq "bootsupport"} {
#puts "-- [tcl::tm::list] --"
puts stdout "Updating bootsupport from local files"
proc modfile_sort {p1 p2} {
lassign [split [file rootname $p1] -] _ v1
lassign [split [file rootname $p1] -] _ v2
package vcompare $v1 $v2
}
proc bootsupport_localupdate {projectroot} {
set bootsupport_modules [list] ;#variable populated by include_modules.config file - review
set sourcefolder $projectroot/src
@ -1521,29 +1537,37 @@ if {$::punkboot::command eq "bootsupport"} {
set boot_event ""
}
foreach {relpath module} $bootsupport_modules {
set module [string trim $module :]
set module_subpath [string map [list :: /] [namespace qualifiers $module]]
foreach {relpath modulematch} $bootsupport_modules {
set modulematch [string trim $modulematch :]
set module_subpath [string map [list :: /] [namespace qualifiers $modulematch]]
set srclocation [file join $projectroot $relpath $module_subpath]
#puts stdout "$relpath $module $module_subpath $srclocation"
set pkgmatches [glob -nocomplain -dir $srclocation -tail [namespace tail $module]-*]
#lsort won't sort version numbers properly e.g with -dictionary 0.1.1 comes before 0.1
#puts stdout "$relpath $modulematch $module_subpath $srclocation"
if {[string first - $modulematch]} {
set pkgmatches [glob -nocomplain -dir $srclocation -tail -type f [namespace tail $modulematch]*.tm]
} else {
set pkgmatches [glob -nocomplain -dir $srclocation -tail -type f [namespace tail $modulematch]-*.tm]
}
if {![llength $pkgmatches]} {
puts stderr "Missing source for bootsupport module $module - not found in $srclocation"
puts stderr "Missing source for bootsupport module $modulematch - no matches in $srclocation"
continue
}
set latestfile [lindex $pkgmatches 0]
set latestver [lindex [split [file rootname $latestfile] -] 1]
foreach m $pkgmatches {
lassign [split [file rootname $m] -] _pkg ver
#puts "comparing $ver vs $latestver"
if {[package vcompare $ver $latestver] == 1} {
set latestver $ver
set latestfile $m
}
set modulematch_is_glob [regexp {[*?\[\]]} $modulematch]
if {!$modulematch_is_glob} {
#if modulematch was specified without globs - only copy latest
#lsort won't sort version numbers properly e.g with -dictionary 0.1.1 comes before 0.1b3 - use helper func
set pkgmatches [lsort -command modfile_sort $pkgmatches]
set latestfile [lindex $pkgmatches end]
#set latestver [lindex [split [file rootname $latestfile] -] 1]
set copy_files $latestfile
} else {
#globs in modulematch - may be different packages matched by glob - copy all versions of matches
#review
set copy_files $pkgmatches
}
set srcfile [file join $srclocation $latestfile]
set tgtfile [file join $targetroot $module_subpath $latestfile]
foreach cfile $copy_files {
set srcfile [file join $srclocation $cfile]
set tgtfile [file join $targetroot $module_subpath $cfile]
if {$boot_event ne ""} {
#----------
$boot_event targetset_init INSTALL $tgtfile
@ -1574,6 +1598,7 @@ if {$::punkboot::command eq "bootsupport"} {
file copy -force $srcfile $tgtfile
}
}
}
if {$boot_event ne ""} {
puts \n
$boot_event destroy
@ -1597,13 +1622,14 @@ if {$::punkboot::command eq "bootsupport"} {
if {[file exists $project_layout_base]} {
set project_layouts [glob -nocomplain -dir $project_layout_base -type d -tail *]
foreach layoutname $project_layouts {
puts stdout "Processing layout $project_layout_base/$layoutname"
#don't auto-create src/bootsupport - just update it if it exists
if {[file exists [file join $project_layout_base $layoutname/src/bootsupport]]} {
set antipaths [list\
README.md\
]
set boot_module_folders [glob -nocomplain -dir $projectroot/src/bootsupport/modules_tcl*]
lappend bootsupport_module_folders "modules"
#set boot_module_folders [list modules {*}[glob -nocomplain -dir $projectroot/src/bootsupport/modules_tcl*]]
set bootsupport_module_folders "modules"
foreach bm $bootsupport_module_folders {
if {[file exists $projectroot/src/bootsupport/$bm]} {
lassign [split $bm _] _bm tclx
@ -1617,12 +1643,33 @@ if {$::punkboot::command eq "bootsupport"} {
file mkdir $targetroot
puts stdout "BOOTSUPPORT$which layouts/$layoutname: copying from $sourcemodules to $targetroot (if source file changed)"
set resultdict [punkcheck::install $sourcemodules $targetroot -overwrite installedsourcechanged-targets -antiglob_paths $antipaths]
set resultdict [punkcheck::install $sourcemodules $targetroot\
-overwrite installedsourcechanged-targets\
-antiglob_paths $antipaths\
-installer "punkboot-bootsupport"
]
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
flush stdout
}
}
}
#make.tcl (to be boot.tcl?) is part of bootsupport
set source_bootscript [file join $projectroot src/make.tcl]
set targetroot_bootscript $project_layout_base/$layoutname/src
if {[file exists $source_bootscript]} {
puts stdout "BOOTSUPPORT layouts/$layoutname: copying from $source_bootscript to $targetroot_bootscript (if source file changed)"
set resultdict [punkcheck::install [file dirname $source_bootscript] $targetroot_bootscript\
-glob make.tcl\
-max_depth 1\
-createempty 0\
-overwrite installedsourcechanged-targets\
-installer "punkboot-bootsupport"
]
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
flush stdout
}
}
} else {
puts stderr "No layout base at $project_layout_base"

568
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/argparsingtest-0.1.0.tm

@ -0,0 +1,568 @@
# -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: punkshell/src/decktemplates/vendor/punk/modules/template_module-0.0.2.tm
#
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# (C) Julian Noble 2024
#
# @@ Meta Begin
# Application argparsingtest 0.1.0
# Meta platform tcl
# Meta license MIT
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[manpage_begin punkshell_module_argparsingtest 0 0.1.0]
#[copyright "2024"]
#[titledesc {Module API}] [comment {-- Name section and table of contents description --}]
#[moddesc {-}] [comment {-- Description at end of page heading --}]
#[require argparsingtest]
#[keywords module]
#[description]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Overview]
#[para] overview of argparsingtest
#[subsection Concepts]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection dependencies]
#[para] packages used by argparsingtest
#[list_begin itemized]
package require Tcl 8.6-
package require punk::args
package require struct::set
#*** !doctools
#[item] [package {Tcl 8.6}]
#[item] [package {punk::args}]
# #package require frobz
# #*** !doctools
# #[item] [package {frobz}]
#*** !doctools
#[list_end]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section API]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# oo::class namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval argparsingtest::class {
#*** !doctools
#[subsection {Namespace argparsingtest::class}]
#[para] class definitions
if {[info commands [namespace current]::interface_sample1] eq ""} {
#*** !doctools
#[list_begin enumerated]
# oo::class create interface_sample1 {
# #*** !doctools
# #[enum] CLASS [class interface_sample1]
# #[list_begin definitions]
# method test {arg1} {
# #*** !doctools
# #[call class::interface_sample1 [method test] [arg arg1]]
# #[para] test method
# puts "test: $arg1"
# }
# #*** !doctools
# #[list_end] [comment {-- end definitions interface_sample1}]
# }
#*** !doctools
#[list_end] [comment {--- end class enumeration ---}]
}
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Base namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval argparsingtest {
namespace export {[a-z]*} ;# Convention: export all lowercase
#variable xyz
#*** !doctools
#[subsection {Namespace argparsingtest}]
#[para] Core API functions for argparsingtest
#[list_begin definitions]
proc test1_ni {args} {
set defaults [dict create\
-return string\
-frametype \uFFEF\
-show_edge \uFFEF\
-show_seps \uFFEF\
-x ""\
-y b\
-z c\
-1 1\
-2 2\
-3 3\
]
foreach {k v} $args {
if {$k ni [dict keys $defaults]} {
error "unrecognised option '$k'. Known options [dict keys $defaults]"
}
}
set opts [dict merge $defaults $args]
}
proc test1_switchmerge {args} {
set defaults [dict create\
-return string\
-frametype \uFFEF\
-show_edge \uFFEF\
-show_seps \uFFEF\
-x ""\
-y b\
-z c\
-1 1\
-2 2\
-3 3\
]
foreach {k v} $args {
switch -- $k {
-return - -show_edge - -show_seps - -frametype - -x - -y - -z - -1 - -2 - -3 {}
default {
error "unrecognised option '$k'. Known options [dict keys $defaults]"
}
}
}
set opts [dict merge $defaults $args]
}
#if we need to loop to test arg validity anyway - then dict set as we go is slightly faster than a dict merge at the end
proc test1_switch {args} {
set opts [dict create\
-return string\
-frametype \uFFEF\
-show_edge \uFFEF\
-show_seps \uFFEF\
-x ""\
-y b\
-z c\
-1 1\
-2 2\
-3 3\
]
foreach {k v} $args {
switch -- $k {
-return - -show_edge - -show_seps - -frametype - -x - -y - -z - -1 - -2 - -3 {
dict set opts $k $v
}
default {
error "unrecognised option '$k'. Known options [dict keys $opts]"
}
}
}
return $opts
}
variable switchopts
set switchopts [dict create\
-return string\
-frametype \uFFEF\
-show_edge \uFFEF\
-show_seps \uFFEF\
-x ""\
-y b\
-z c\
-1 1\
-2 2\
-3 3\
]
#slightly slower than just creating the dict within the proc
proc test1_switch_nsvar {args} {
variable switchopts
set opts $switchopts
foreach {k v} $args {
switch -- $k {
-return - -show_edge - -show_seps - -frametype - -x - -y - -z - -1 - -2 - -3 {
dict set opts $k $v
}
default {
error "unrecognised option '$k'. Known options [dict keys $opts]"
}
}
}
return $opts
}
proc test1_switch2 {args} {
set opts [dict create\
-return string\
-frametype \uFFEF\
-show_edge \uFFEF\
-show_seps \uFFEF\
-x ""\
-y b\
-z c\
-1 1\
-2 2\
-3 3\
]
set switches [lmap v [dict keys $opts] {list $v -}]
set switches [concat {*}$switches]
set switches [lrange $switches 0 end-1]
foreach {k v} $args {
switch -- $k\
{*}$switches {
dict set opts $k $v
}\
default {
error "unrecognised option '$k'. Known options [dict keys $opts]"
}
}
return $opts
}
proc test1_prefix {args} {
set opts [dict create\
-return string\
-frametype \uFFEF\
-show_edge \uFFEF\
-show_seps \uFFEF\
-x ""\
-y b\
-z c\
-1 1\
-2 2\
-3 3\
]
foreach {k v} $args {
dict set opts [tcl::prefix::match -message "test1_prefix option $k" {-return -frametype -show_edge -show_seps -x -y -z -1 -2 -3} $k] $v
}
return $opts
}
proc test1_prefix2 {args} {
set opts [dict create\
-return string\
-frametype \uFFEF\
-show_edge \uFFEF\
-show_seps \uFFEF\
-x ""\
-y b\
-z c\
-1 1\
-2 2\
-3 3\
]
if {[llength $args]} {
set knownflags [dict keys $opts]
}
foreach {k v} $args {
dict set opts [tcl::prefix::match -message "test1_prefix2 option $k" $knownflags $k] $v
}
return $opts
}
#punk::args is slower than argp - but comparable, and argp doesn't support solo flags
proc test1_punkargs {args} {
set argd [punk::args::parse $args withdef {
@id -id ::argparsingtest::test1_punkargs
@cmd -name argtest4 -help "test of punk::args::get_dict comparative performance"
@opts -anyopts 0
-return -default string -type string
-frametype -default \uFFEF -type string
-show_edge -default \uFFEF -type string
-show_seps -default \uFFEF -type string
-join -type none -multiple 1
-x -default "" -type string
-y -default b -type string
-z -default c -type string
-1 -default 1 -type boolean
-2 -default 2 -type integer
-3 -default 3 -type integer
@values
}]
return [tcl::dict::get $argd opts]
}
punk::args::define {
@id -id ::test1_punkargs_by_id
@cmd -name argtest4 -help "test of punk::args::get_dict comparative performance"
@opts -anyopts 0
-return -default string -type string
-frametype -default \uFFEF -type string
-show_edge -default \uFFEF -type string
-show_seps -default \uFFEF -type string
-join -type none -multiple 1
-x -default "" -type string
-y -default b -type string
-z -default c -type string
-1 -default 1 -type boolean
-2 -default 2 -type integer
-3 -default 3 -type integer
@values
}
proc test1_punkargs_by_id {args} {
set argd [punk::args::get_by_id ::test1_punkargs_by_id $args]
return [tcl::dict::get $argd opts]
}
punk::args::define {
@id -id ::argparsingtest::test1_punkargs2
@cmd -name argtest4 -help "test of punk::args::get_dict comparative performance"
@opts -anyopts 0
-return -default string -type string
-frametype -default \uFFEF -type string
-show_edge -default \uFFEF -type string
-show_seps -default \uFFEF -type string
-join -type none -multiple 1
-x -default "" -type string
-y -default b -type string
-z -default c -type string
-1 -default 1 -type boolean
-2 -default 2 -type integer
-3 -default 3 -type integer
@values
}
proc test1_punkargs2 {args} {
set argd [punk::args::get_by_id ::argparsingtest::test1_punkargs2 $args]
return [tcl::dict::get $argd opts]
}
proc test1_punkargs_validate_ansistripped {args} {
set argd [punk::args::get_dict {
@id -id ::argparsingtest::test1_punkargs_validate_ansistripped
@cmd -name argtest4 -help "test of punk::args::get_dict comparative performance"
@opts -anyopts 0
-return -default string -type string -choices {string object} -help "return type"
-frametype -default \uFFEF -type string
-show_edge -default \uFFEF -type string
-show_seps -default \uFFEF -type string
-join -type none -multiple 1
-x -default "" -type string
-y -default b -type string
-z -default c -type string
-1 -default 1 -type boolean -validate_ansistripped true
-2 -default 2 -type integer -validate_ansistripped true
-3 -default 3 -type integer -validate_ansistripped true
@values
} $args]
return [tcl::dict::get $argd opts]
}
package require opt
variable optlist
tcl::OptProc test1_opt {
{-return string "return type"}
{-frametype \uFFEF "type of frame"}
{-show_edge \uFFEF "show table outer borders"}
{-show_seps \uFFEF "show separators"}
{-join "solo option"}
{-x "" "x val"}
{-y b "y val"}
{-z c "z val"}
{-1 1 "1val"}
{-2 -int 2 "2val"}
{-3 -int 3 "3val"}
} {
set opts [dict create]
foreach v [info locals] {
dict set opts $v [set $v]
}
return $opts
}
package require cmdline
#cmdline::getoptions is much faster than typedGetoptions
proc test1_cmdline_untyped {args} {
set cmdlineopts_untyped {
{return.arg "string" "return val"}
{frametype.arg \uFFEF "frame type"}
{show_edge.arg \uFFEF "show table borders"}
{show_seps.arg \uFFEF "show table seps"}
{join "join the things"}
{x.arg "" "arg x"}
{y.arg b "arg y"}
{z.arg c "arg z"}
{1.arg 1 "arg 1"}
{2.arg 2 "arg 2"}
{3.arg 3 "arg 3"}
}
set usage "usage etc"
return [::cmdline::getoptions args $cmdlineopts_untyped $usage]
}
proc test1_cmdline_typed {args} {
set cmdlineopts_typed {
{return.arg "string" "return val"}
{frametype.arg \uFFEF "frame type"}
{show_edge.arg \uFFEF "show table borders"}
{show_seps.arg \uFFEF "show table seps"}
{join "join the things"}
{x.arg "" "arg x"}
{y.arg b "arg y"}
{z.arg c "arg z"}
{1.boolean 1 "arg 1"}
{2.integer 2 "arg 2"}
{3.integer 3 "arg 3"}
}
set usage "usage etc"
return [::cmdline::typedGetoptions args $cmdlineopts_typed $usage]
}
catch {
package require argp
argp::registerArgs test1_argp {
{ -return string "string" }
{ -frametype string \uFFEF }
{ -show_edge string \uFFEF }
{ -show_seps string \uFFEF }
{ -x string "" }
{ -y string b }
{ -z string c }
{ -1 boolean 1 }
{ -2 integer 2 }
{ -3 integer 3 }
}
}
proc test1_argp {args} {
argp::parseArgs opts
return [array get opts]
}
package require tepam
tepam::procedure {test1_tepam} {
-args {
{-return -type string -default string}
{-frametype -type string -default \uFFEF}
{-show_edge -type string -default \uFFEF}
{-show_seps -type string -default \uFFEF}
{-join -type none -multiple}
{-x -type string -default ""}
{-y -type string -default b}
{-z -type string -default c}
{-1 -type boolean -default 1}
{-2 -type integer -default 2}
{-3 -type integer -default 3}
}
} {
return [dict create return $return frametype $frametype show_edge $show_edge show_seps $show_seps x $x y $y z $z 1 $1 2 $2 3 $3 join $join]
}
#multiline values use first line of each record to determine amount of indent to trim
proc test_multiline {args} {
set t3 [textblock::frame t3]
set argd [punk::args::get_dict [subst {
-template1 -default {
******
* t1 *
******
}
-template2 -default { ------
******
* t2 *
******}
-template3 -default {$t3}
#substituted or literal values with newlines - no autoindent applied - caller will have to pad appropriately
-template3b -default {
$t3
-----------------
$t3
abc\ndef
}
-template4 -default "******
* t4 *
******"
-template5 -default "
"
-flag -default 0 -type boolean
}] $args]
return $argd
}
#proc sample1 {p1 n args} {
# #*** !doctools
# #[call [fun sample1] [arg p1] [arg n] [opt {option value...}]]
# #[para]Description of sample1
# #[para] Arguments:
# # [list_begin arguments]
# # [arg_def tring p1] A description of string argument p1.
# # [arg_def integer n] A description of integer argument n.
# # [list_end]
# return "ok"
#}
#*** !doctools
#[list_end] [comment {--- end definitions namespace argparsingtest ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Secondary API namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
namespace eval argparsingtest::lib {
namespace export {[a-z]*} ;# Convention: export all lowercase
namespace path [namespace parent]
#*** !doctools
#[subsection {Namespace argparsingtest::lib}]
#[para] Secondary functions that are part of the API
#[list_begin definitions]
#proc utility1 {p1 args} {
# #*** !doctools
# #[call lib::[fun utility1] [arg p1] [opt {?option value...?}]]
# #[para]Description of utility1
# return 1
#}
#*** !doctools
#[list_end] [comment {--- end definitions namespace argparsingtest::lib ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Internal]
namespace eval argparsingtest::system {
#*** !doctools
#[subsection {Namespace argparsingtest::system}]
#[para] Internal functions that are not part of the API
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide argparsingtest [namespace eval argparsingtest {
variable pkg argparsingtest
variable version
set version 0.1.0
}]
return
#*** !doctools
#[manpage_end]

8
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/commandstack-0.3.tm

@ -211,7 +211,7 @@ namespace eval commandstack {
set new_code [string trim $procbody]
if {$current_code eq $new_code} {
puts stderr "(commandstack::rename_command) WARNING - renamer '$renamer' has already renamed the '$command' command with same procbody - Aborting rename."
puts stderr [show_stack $command]
puts stderr [::commandstack::show_stack $command]
} else {
puts stderr "(commandstack::rename_command) WARNING - renamer '$renamer' has already renamed the '$command' command - but appears to be with new code - proceeding."
puts stdout "----------"
@ -236,8 +236,7 @@ namespace eval commandstack {
set next_target ::commandstack::renamed_commands::${mungedcommand}_${munged_next_implementor}-$mungedrenamer-$tokenid
set do_rename 1
} elseif {$next_implementor in {unspecified undetermined}} {
#review - probably don't need a warning anyway
puts stderr "(commandstack::rename_command) WARNING - Something may have renamed the '$command' command. Attempting to cooperate.(untested)"
#could be a standard tcl proc, or from application or package
set next_target ::commandstack::renamed_commands::${mungedcommand}_${munged_next_implementor}-$mungedrenamer-$tokenid
set do_rename 1
} else {
@ -380,7 +379,8 @@ namespace eval commandstack {
#if caller is attempting exact match - use the calling context to resolve in case they didn't supply namespace
set commandname_glob [uplevel 1 [list namespace which $commandname_glob]]
}
if {[package provide punk::lib] ne ""} {
if {[package provide punk::lib] ne "" && [package provide punk] ne ""} {
#punk pipeline also needed for patterns
return [punk::lib::pdict -channel none all_stacks $commandname_glob/@*/@*.@*]
} else {
set result ""

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

@ -9,7 +9,7 @@ package provide funcl [namespace eval funcl {
namespace eval funcl {
#from punk
#from punk::pipe
proc arg_is_script_shaped {arg} {
if {[string first " " $arg] >= 0} {
return 1

2
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/include_modules.config

@ -18,7 +18,6 @@ set bootsupport_modules [list\
src/vendormodules md5\
src/vendormodules metaface\
src/vendormodules modpod\
src/vendormodules oolib\
src/vendormodules overtype\
src/vendormodules pattern\
src/vendormodules patterncmd\
@ -40,6 +39,7 @@ set bootsupport_modules [list\
modules funcl\
modules natsort\
modules punk\
modules punk::pipe\
modules punkapp\
modules punkcheck\
modules punkcheck::cli\

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

@ -216,7 +216,9 @@ tcl::namespace::eval overtype {
}
set optargs [lrange $args 0 end-2]
if {[llength $optargs] % 2 == 0} {
lassign [lrange $args end-1 end] underblock overblock
set overblock [lindex $args end]
set underblock [lindex $args end-1]
#lassign [lrange $args end-1 end] underblock overblock
set argsflags [lrange $args 0 end-2]
} else {
set optargs [lrange $args 0 end-1]
@ -1810,8 +1812,10 @@ tcl::namespace::eval overtype {
if {[llength $args] < 2} {
error {usage: ?-info 0|1? ?-startcolumn <int>? ?-cursor_column <int>? ?-cursor_row <int>|""? ?-transparent [0|1|<regexp>]? ?-expand_right [1|0]? undertext overtext}
}
lassign [lrange $args end-1 end] under over
if {[string first \n $under] >= 0} {
set under [lindex $args end-1]
set over [lindex $args end]
#lassign [lrange $args end-1 end] under over
if {[string last \n $under] >= 0} {
error "overtype::renderline not allowed to contain newlines in undertext"
}
#if {[string first \n $over] >=0 || [string first \n $under] >= 0} {
@ -2920,6 +2924,7 @@ tcl::namespace::eval overtype {
set leadernorm [tcl::string::range [tcl::string::map [list\
\x1b\[< 1006\
\x1b\[ 7CSI\
\x1bY 7MAP\
\x1bP 7DCS\
\x90 8DCS\
\x9b 8CSI\
@ -2948,6 +2953,10 @@ tcl::namespace::eval overtype {
#8-bit Device Control String
set codenorm [tcl::string::cat $leadernorm [tcl::string::range $code 1 end]]
}
7MAP {
#map to another type of code to share implementation branch
set codenorm $leadernorm[tcl::string::range $code 1 end]
}
7ESC {
#set codenorm [tcl::string::cat $leadernorm [tcl::string::range $code 1 end]]
set codenorm $leadernorm[tcl::string::range $code 1 end]
@ -2964,6 +2973,30 @@ tcl::namespace::eval overtype {
}
}
switch -- $leadernorm {
7MAP {
switch -- [lindex $codenorm 4] {
Y {
#vt52 movement. we expect 2 chars representing position (limited range)
set params [tcl::string::range $codenorm 5 end]
if {[tcl::string::length $params] != 2} {
#shouldn't really get here or need this branch if ansi splitting was done correctly
puts stderr "overtype::renderline ESC Y recognised as vt52 move, but incorrect parameters length ([string length $params] vs expected 2) [ansistring VIEW -lf 1 -vt 1 -nul 1 $code] not implemented codenorm:[ansistring VIEW -lf 1 -vt 1 -nul 1 $codenorm]"
}
set line [tcl::string::index $params 5]
set column [tcl::string::index $params 1]
set r [expr {[scan $line %c] -31}]
set c [expr {[scan $column %c] -31}]
#MAP to:
#CSI n;m H - CUP - Cursor Position
set leadernorm 7CSI
set codenorm "$leadernorm${r}\;${c}H"
}
}
}
}
#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 {
@ -2983,6 +3016,7 @@ tcl::namespace::eval overtype {
set param [tcl::string::range $codenorm 4 end-1]
#puts stdout "--> CSI [tcl::string::index $leadernorm 0] bit param:$param"
set code_end [tcl::string::index $codenorm end] ;#used for e.g h|l set/unset mode
switch -exact -- $code_end {
A {
#Row move - up
@ -3875,6 +3909,7 @@ tcl::namespace::eval overtype {
7ESC {
#
#re_other_single {\x1b(D|M|E)$}
#also vt52 Y..
#also PM \x1b^...(ST)
switch -- [tcl::string::index $codenorm 4] {
c {
@ -4586,6 +4621,8 @@ tcl::namespace::eval overtype::priv {
set o [lreplace $o $i $i]
set ustacks [lreplace $ustacks $i $i]
set gxstacks [lreplace $gxstacks $i $i]
} elseif {$i == 0 || $i == $nxt} {
#nothing to do
} else {
puts stderr "render_delchar - attempt to delchar at index $i >= number of outcols $nxt - shouldn't happen"
}

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

@ -20,6 +20,21 @@ namespace eval punk {
variable cmdexedir
set cmdexedir ""
proc sync_package_paths_script {} {
#the tcl::tm namespace doesn't exist until one of the tcl::tm commands
#is run. (they are loaded via ::auto_index triggering load of tm.tcl)
#we call tcl::tm::list to trigger the initial set of tm paths before
#we can override it, otherwise our changes will be lost
#REVIEW - won't work on safebase interp where paths are mapped to {$p(:x:)} etc
return "\
apply {{ap tmlist} {
set ::auto_path \$ap
tcl::tm::list
set ::tcl::tm::paths \$tmlist
}} {$::auto_path} {[tcl::tm::list]}
"
}
proc rehash {{refresh 0}} {
global auto_execs
if {!$refresh} {
@ -217,7 +232,7 @@ namespace eval punk {
[file exists [set testapp [file join $::env(LOCALAPPDATA) "Microsoft" "WindowsApps" "winget.exe"]]])} {
#should be unlikely to get here - unless LOCALAPPDATA missing
set windowsappdir [file dirname [lindex [::punk::auto_execok_windows winget.exe] 0]]
puts stderr "(resolved winget by search)"
catch {puts stderr "(resolved winget by search)"}
} else {
set windowsappdir [file dirname $testapp]
}
@ -359,7 +374,7 @@ if {![llength [info commands ::ansistring]]} {
}
#require aliascore after punk::lib & punk::ansi are loaded
package require punk::aliascore ;#mostly punk::lib aliases
punk::aliascore::init
punk::aliascore::init -force 1
package require punk::repl::codethread
package require punk::config
@ -383,9 +398,11 @@ namespace eval punk {
package require punk::assertion
if {[catch {namespace import ::punk::assertion::assert} errM]} {
catch {
puts stderr "punk error importing punk::assertion::assert\n$errM"
puts stderr "punk::a* commands:[info commands ::punk::a*]"
}
}
punk::assertion::active on
# -- --- ---
@ -393,7 +410,7 @@ namespace eval punk {
if {[catch {
package require pattern
} errpkg]} {
puts stderr "Failed to load package pattern error: $errpkg"
catch {puts stderr "Failed to load package pattern error: $errpkg"}
}
package require shellfilter
package require punkapp
@ -524,7 +541,7 @@ namespace eval punk {
set loader [zzzload::pkg_wait twapi]
} errM]} {
if {$loader in [list failed loading]} {
puts stderr "Unexpected problem during thread-load of pkg twapi - zzload::pkg_wait returned $loader"
catch {puts stderr "Unexpected problem during thread-load of pkg twapi - zzload::pkg_wait returned $loader"}
}
} else {
package require twapi
@ -1061,7 +1078,7 @@ namespace eval punk {
proc destructure {selector data} {
# replaced by proc generating destructure_func -
puts stderr "punk::destructure .d. selector:'$selector'"
catch {puts stderr "punk::destructure .d. selector:'$selector'"}
set selector [string trim $selector /]
upvar v_dict_idx v_dict_idx ;#positional tracker for /@@ - dict position

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

@ -105,6 +105,7 @@ tcl::namespace::eval punk::aliascore {
#functions must be in export list of their source namespace
set aliases [tcl::dict::create\
val ::punk::pipe::val\
aliases ::punk::lib::aliases\
alias ::punk::lib::alias\
tstr ::punk::lib::tstr\
@ -123,12 +124,12 @@ tcl::namespace::eval punk::aliascore {
colour ::punk::console::colour\
ansi ::punk::console::ansi\
color ::punk::console::colour\
a? ::punk::console::code_a?\
A? {::punk::console::code_a? forcecolor}\
a+ ::punk::console::code_a+\
A+ {::punk::console::code_a+ forcecolour}\
a ::punk::console::code_a\
A {::punk::console::code_a forcecolour}\
a? ::punk::console::code_a?\
A? {::punk::console::code_a? forcecolor}\
smcup ::punk::console::enable_alt_screen\
rmcup ::punk::console::disable_alt_screen\
]

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

@ -584,25 +584,38 @@ tcl::namespace::eval punk::ansi {
set base $CWD
}
}
if {[info commands file] eq ""} {
#probably a safe interp
return "UNAVAILABLE"
}
return [file join $base src/testansi]
}
lappend PUNKARGS [list -dynamic 1 {
lappend PUNKARGS [list {
@dynamic
@id -id ::punk::ansi::example
@cmd -name punk::ansi::example -help "Display .ans image files in a grid that will fit in console
"
-colwidth -default 82 -help "Width of each column - default of 82 will fit a standard 80wide ansi image (when framed)
-colwidth -default 82 -help\
"Width of each column - default of 82 will fit a standard 80wide ansi image
(when framed)
You can specify a narrower width to truncate images on the right side"
-folder -default "${[punk::ansi::Get_ansifolder]}" -help "Base folder for files if relative paths are used.
Defaults to <projectbase>/src/testansi - where projectbase is determined from current directory.
-folder -default "${[punk::ansi::Get_ansifolder]}" -help\
"Base folder for files if relative paths are used.
Defaults to <projectbase>/src/testansi - where projectbase is determined
from the current directory.
"
@values -min 0 -max -1
files -default {belinda.ans bot.ans flower.ans fish.ans} -multiple true -help "List of filenames - leave empty to display 4 defaults"
files -default {belinda.ans bot.ans flower.ans fish.ans} -multiple true -help\
"List of filenames - leave empty to display 4 defaults"
} ""]
proc example {args} {
set argd [punk::args::get_by_id ::punk::ansi::example $args]
set colwidth [dict get $argd opts -colwidth]
if {[info commands file] eq ""} {
error "file command unavailable - punk::ansi::example cannot be shown"
}
set ansifolder [file normalize [dict get $argd opts -folder]]
set fnames [dict get $argd values files]
@ -617,6 +630,16 @@ tcl::namespace::eval punk::ansi {
puts stderr "Ensure ansi test files exist: $fnames"
#error "punk::ansi::example Cannot find example files"
}
set termsize [punk::console:::get_size]
set termcols [dict get $termsize columns]
set margin 4 ;#review
set freewidth [expr {$termcols-$margin}]
if {$freewidth < $colwidth} {
puts stderr "[a+ red bold]punk::ansi::example freewidth: $freewidth < colwidth: $colwidth TRUNCATING IMAGES[a]"
set colwidth $freewidth
}
set per_row [expr {$freewidth / $colwidth}]
set missingbase [a+ yellow][textblock::block [expr {$colwidth-2}] 23 ?][a] ;#assuming standard frame - subtract 2 for left/right borders
set pics [list]
foreach f $fnames {
@ -636,10 +659,6 @@ tcl::namespace::eval punk::ansi {
}
}
set termsize [punk::console:::get_size]
set margin 4
set freewidth [expr {[tcl::dict::get $termsize columns]-$margin}]
set per_row [expr {$freewidth / $colwidth}]
set rowlist [list] ;# { {<img> <img>} {<img> <img>} }
set heightlist [list] ;# { {<h> <h> } {<h> <h> } }
@ -737,22 +756,23 @@ tcl::namespace::eval punk::ansi {
#review - can terminals handle SGR codes within a PM?
#Wezterm will hide PM,SOS,APC - but not any part following an SGR code - i.e it seems to terminate hiding before the ST (apparently at the )
proc controlstring_PM {text} {
return "\x1b^${text}\033\\"
#dquotes with trailing \\ in string will confuse silly editors
return \x1b^${text}\033\\
}
proc controlstring_PM8 {text} {
return "\x9e${text}\x9c"
return \x9e${text}\x9c
}
proc controlstring_SOS {text} {
return "\x1bX${text}\033\\"
return \x1bX${text}\033\\
}
proc controlstring_SOS8 {text} {
return "\x98${text}\x9c"
return \x98${text}\x9c
}
proc controlstring_APC {text} {
return "\x1b_${text}\033\\"
return \x1b_${text}\033\\
}
proc controlstring_APC8 {text} {
return "\x9f${text}\x9c"
return \x9f${text}\x9c
}
#there is also the SGR hide code (8) which has intermittent terminal support
#This doesn't change the output length - so support is tricky to detec. (terminal checksum report?)
@ -843,10 +863,79 @@ tcl::namespace::eval punk::ansi {
return $out
}
#Wrap text in ansi codes to switch to DEC alternate graphics character set.
#todo vt52 versions
proc g0 {text} {
return \x1b(0$text\x1b(B
}
variable altg_map [dict create\
hl q\
vl x\
tlc l\
trc k\
blc m\
ltj t\
rtj u\
ttj w\
btj v\
rtj u\
fwj n\
]
proc altg_map {names} {
variable altg_map
set result [list]
foreach nm $names {
if {[dict exists $altg_map $nm]} {
lappend result [dict get $altg_map $nm]
} else {
lappend ""
}
}
return $result
}
# --------------------------------
# Taken from term::ansi::code::ctrl
# --------------------------------
#Note that SYN (\016) seems to put terminals in a state
#where alternate graphics are not processed.
#an ETB (\017) needs to be sent to get alt graphics working again.
#It isn't known what software utilises SYN/ETB within altg sequences
# (presumably to alternate between the charsets within a graphics-on/graphics-off section)
#but as modern emulators seem to react to it, we should handle it.
#REVIEW - this mapping not fully understood
#used by groptim
variable grforw
variable grback
variable _
foreach _ {
! \" # $ % & ' ( ) * + , - . /
0 1 2 3 4 5 6 7 8 9 : ; < = >
? @ A B C D E F G H I J K L M
N O P Q R S T U V W X Y Z [ ^
\\ ]
} {
lappend grforw \016$_ $_\016
lappend grback $_\017 \017$_
}
unset _
# ------------------------------
#REVIEW - see forward/backward map in term::ansi::code::macros::cd::groptim - what are they for??
proc groptim {string} {
variable grforw
variable grback
set offon \x1b(B\x1b(0 ;#)) ;#editor highlighting comment
set onoff \x1b(0\x1b(B ;#)) ;#editor highlighting comment
while {![string equal $string [set new [string map [list $offon {} $onoff {}] [string map $grback [string map $grforw $string]]]]]} {
set string $new
}
return $string
}
# --------------------------------
proc ansistrip_gx {text} {
#e.g "\033(0" - select VT100 graphics for character set G0
#e.g "\033(B" - reset
@ -854,10 +943,10 @@ tcl::namespace::eval punk::ansi {
#e.g "\033)X" - where X is any char other than 0 to reset ??
#return [convert_g0 $text]
return [tcl::string::map [list "\x1b(0" "" \x1b(B" "" "\x1b)0" "" "\x1b)X" ""] $text]
return [tcl::string::map [list \x1b(0 "" \x1b(B "" \x1b)0 "" \x1b)X ""] $text]
}
proc stripansi_gx {text} {
return [tcl::string::map [list "\x1b(0" "" \x1b(B" "" "\x1b)0" "" "\x1b)X" ""] $text]
return [tcl::string::map [list \x1b(0 "" \x1b(B "" \x1b)0 "" \x1b)X ""] $text]
}
@ -1459,7 +1548,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
tcl::dict::set opts $k $v
}
default {
error "colourmap1 unrecognised option $k. Known-options: [tcl::dict::keys $opts]
error "colourmap1 unrecognised option $k. Known-options: [tcl::dict::keys $opts]"
}
}
}
@ -2358,8 +2447,8 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
lappend PUNKARGS [list {
@id -id ::punk::ansi::sgr_cache
@cmd -name punk::ansi::sgr_cache -help\
"Convenience function to view and optionally clear the ansi character attribute cache (ansi SGR codes)
"
"Convenience function to view and optionally clear the ansi character attribute cache
(ansi SGR codes)"
-action -default "" -choices "clear" -help\
"-action clear will unset the keys in the punk::ansi::sgr_cache dict
This is called automatically when setting 'colour false' in the console"
@ -2882,6 +2971,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
set SGR_samples [dict create]
foreach k [dict keys $SGR_map] {
#indent of 1 space is important for clarity in i -return string a+ output
dict set SGR_samples $k " [punk::ansi::a+ $k]sample\x1b\[m"
}
lappend PUNKARGS [list {
@ -3264,7 +3354,55 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
return $result
}
proc ansiwrap {codes text} {
lappend PUNKARGS [list {
@id -id ::punk::ansi::ansiwrap
@cmd -name punk::ansi::ansiwrap -help\
"Wrap a string with ANSI codes from
supplied codelist(s) followed by trailing
ANSI reset.
Codes are numbers or strings as indicated
in the output of the colour information
function: punk::ansi::a?
No leading reset is applied - so when
placing resultant text, any existing
SGR codes that aren't overridden may
still take effect.
For finer control use the a+ and a
functions eg
set x \"[a+ red]text [a+ bold]etc[a]\"
"
@leaders -min 0 -max -1
codelist -multiple 1 -default {} -type list -help\
"ANSI names/ints as understood by 'a?'
(Not actual ANSI as output by a+)
These can be supplied individually or
as a list or lists"
@values -min 1 -max 1
text -type string -help\
"String to wrap with ANSI (SGR)"
}]
#proc ansiwrap {codes text} {
# return [a {*}$codes]$text[a]
#}
proc ansiwrap2 {args} {
set argd [punk::args::parse $args withid ::punk::ansi::ansiwrap]
set codelists [dict get $argd leaders codelist]
set text [dict get $argd values text]
set codes [concat {*}$codelists] ;#flatten
return [a {*}$codes]$text[a]
}
proc ansiwrap {args} {
if {[llength $args] < 1} {
#minimal args parsing - unhappy path only
punk::args::parse $args withid ::punk::ansi::ansiwrap
return
}
set text [lindex $args end]
set codelists [lrange $args 0 end-1]
set codes [concat {*}$codelists] ;#flatten
return [a {*}$codes]$text[a]
}
@ -3300,6 +3438,14 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#[call [fun reset_soft]]
return \x1b\[!p
}
proc SYN {} {
#syn seems to disable alternate graphics mode temporarily on modern terminals
return \016
}
proc ETB {} {
#This is a form of soft reset for the state where a SYN was sent - re-enabling altg processing
return \017
}
proc reset_colour {} {
#*** !doctools
#[call [fun reset_colour]]
@ -3341,6 +3487,12 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#[call [fun cursor_off]]
return "\033\[?25l"
}
proc cursor_on_vt52 {} {
return \x1be
}
proc cursor_off_vt52 {} {
return \x1bf
}
# REVIEW - osc8 replays etc for split lines? - textblock
#Hyperlinks (a.k.a. HTML-like anchors) in terminal emulators https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda
@ -3387,6 +3539,24 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
}
# -- --- --- --- ---
lappend PUNKARGS [list {
@id -id ::punk::ansi::move
@cmd -name punk::ansi::move -help\
{Return an ANSI sequence to move cursor to row,col
(aka: cursor home)
Sequence is of the form:
\x1b[<row>;<col>H
(CSI row ; col H)
This sequence will not be understood by old vt52
terminals. see also vt52_move.
}
@values -min 2 -max 2
row -type integer -help\
"row number - starting at 1"
col -type integer -help\
"column number - starting at 1"
}]
proc move {row col} {
#*** !doctools
#[call [fun move] [arg row] [arg col]]
@ -3394,6 +3564,44 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#[para]aka cursor home
return \033\[${row}\;${col}H
}
#NOTE vt52 uses ESC Y line column
# where line and column are ascii codes whose values are +31
# vt52 can be entered/exited via escapes
# This means we probably need to to wrap enter/exit vt52 and keep this state - as we don't have a standard way to query for terminal type
# (vt52 supports ESC Z - but vt100 sometimes? doesn't - and querying at each output would be slow anyway, even if there was a common query :/ )
lappend PUNKARGS [list {
@id -id ::punk::ansi::vt52move
@cmd -name punk::ansi::vt52move -help\
{Return a VT52 sequence to move cursor to row,col
(aka: cursor home)
Sequence is of the form:
ESCY<rowchar><colchar>
This sequence will generally not be understood by terminals
that are not in vt52 mode (e.g DECANM unset).
}
@values -min 2 -max 2
row -type integer -help\
"row number - starting at 1"
col -type integer -help\
"column number - starting at 1"
}]
proc vt52move {row col} {
#test
set r [format %c [expr {$row + 31}]]
set c [format %c [expr {$col + 31}]]
return \x1bY${r}${c}
}
proc vt52color {int} {
if {[string is integer -strict $int]} {
if {$int < 0 || $int > 15} {
error "vt52color unsupported - only 0 to 15 available"
}
}
set char [format %c [expr {$int + 31}]]
return \x1bb${char}
}
proc move_emit {row col data args} {
#*** !doctools
#[call [fun move_emit] [arg row] [arg col] [arg data] [opt {row col data...}]]
@ -3424,6 +3632,27 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
}
return $out
}
proc vt52move_emit {row col data args} {
#Todo - G code?
set out ""
if {$row eq "this"} {
#append out \033\[\;${col}G$data
append out [vt52move_column $col]$data
} else {
#append out \033\[${row}\;${col}H$data
append out [vt52move $row $col]$data
}
foreach {row col data} $args {
if {$row eq "this"} {
append out [vt52move_column $col]$data
#append out \033\[\;${col}G$data
} else {
#append out \033\[${row}\;${col}H$data
append out [vt52move $row $col]$data
}
}
return $out
}
proc move_emitblock {row col textblock} {
#*** !doctools
#[call [fun move_emitblock] [arg row] [arg col] [arg textblock]]
@ -3434,31 +3663,63 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
}
return $commands
}
proc vt52move_emitblock {row col textblock} {
#*** !doctools
#[call [fun move_emitblock] [arg row] [arg col] [arg textblock]]
set commands ""
foreach ln [split $textblock \n] {
append commands [punk::ansi::vt52move_emit $row $col $ln]
incr row
}
return $commands
}
proc move_forward {{n 1}} {
#*** !doctools
#[call [fun move_forward] [arg n]]
return \033\[${n}C
}
proc vt52move_forward {{n 1}} {
return [string repeat \x1bC $n]
}
proc move_back {{n 1}} {
#*** !doctools
#[call [fun move_back] [arg n]]
return \033\[${n}D
}
proc vt52move_back {{n 1}} {
return [string repeat \x1bD $n]
}
proc move_up {{n 1}} {
#*** !doctools
#[call [fun move_up] [arg n]]
return \033\[${n}A
}
proc vt52move_up {{n 1}} {
return [string repeat \x1bA $n]
}
proc move_down {{n 1}} {
#*** !doctools
#[call [fun move_down] [arg n]]
return \033\[${n}B
}
proc vt52move_down {{n 1}} {
return [string repeat \x1bB $n]
}
proc move_column {col} {
#*** !doctools
#[call [fun move_column] [arg col]]
return \x1b\[${col}G
}
proc vt52move_column {col} {
#This is a bit of a fudge - as there is no command to move to a specific column.
#without tracking state - we settle for moving back enough times to ensure we're at column 1 - and then move forward.
#inefficient - but will have to do I guess.
#review - max term width vt52? env var LINES and env var COLUMNS ?
# also ESC R <cols,rows> CR - set window size
set back [string repeat \x1bD 132]
set fwd [string repeat \x1bC [expr {$col - 1}]]
return $back$fwd
}
proc move_row {row} {
#*** !doctools
#[call [fun move_row] [arg row]]
@ -3496,6 +3757,13 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#[para] DECRC
return \x1b8
}
proc cursor_save_vt52 {} {
return \x1bj
}
proc cursor_restore_vt52 {} {
return \x1bk
}
# -- --- --- --- ---
#CRM Show Control Character Mode
proc enable_crm {} {
@ -3551,17 +3819,130 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
# \x1b\[?7\;2\$y
#where 1 = set, 2 = unset. (0 = mode not recognised, 3 = permanently set, 4 = permanently unset)
#names for other alt_screen mechanisms: 1047,1048 vs 1049?
variable decmode_names [dict create\
line_wrap 7\
LNM 20\
alt_screen 1049\
grapheme_clusters 2027\
bracketed_paste 2004\
mouse_sgr_extended 1006\
mouse_urxvt 1015\
mouse_sgr 1016\
]
#https://wiki.tau.garden/dec-modes/
#(DEC,xterm,contour,mintty,kitty etc)
#https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-Mouse-Tracking
#names for other alt_screen mechanismk: 1047,1048 vs 1049?
#variable decmode_names [dict create\
# DECANM 2\
# origin 6\
# DECCOLM 3\
# line_wrap 7\
# LNM 20\
# alt_screen 1049\
# grapheme_clusters 2027\
# bracketed_paste 2004\
# mouse_sgr 1006\
# mouse_urxvt 1015\
# mouse_sgr_pixel 1016\
#]
variable decmode_data {
1 {
{origin DEC description "DECCKM - Cursor Keys Mode" names {DECCKM cursor_keys}}
}
2 {
{origin DEC description "DECANM - ANSI/VT52 Mode" names {DECANM} note {
Disable to turn on VT52 emulation.
In VT52 mode - use \x1b< to exit.
}
}
}
3 {
{origin DEC description "DECCOLM - Column" names {DECCOLM}}
}
4 {
{origin DEC description "DECSCLM - Scrolling" names {DECSCLM}}
}
5 {
{origin DEC description "DECSCNM - Screen Mode (light or dark screen)" names {DECSNM lightmode}}
}
7 {
{origin DEC description "DECAWM - Auto Wrap Mode" names {DECAWM line_wrap}}
}
9 {
{origin "xterm" description "X10 compatibility mouse" names {SET_X10_MOUSE mouse_tracking} note {
Escape sequence on button press only.
CSI M CbCxCy (6 chars)
Coords limited to 223 (=255 - 32)
}
}
{origin DEC description "DECINLM - Interlace Mode (obsolete?)" names {DECINLM}}
}
20 {
{origin DEC description "LNM - Line Feed/New Line Mode" names {LNM} note {
For terminals that support LNM, the default is off
meaning a lone CR respresents the character emitted
when enter is pushed. Turning LNM on would mean that
CR LF is sent when hitting enter. This feature is
not commonly supported, and the default will normally
be as if this was off - ie lone CR.
}
}
}
25 {
{origin DEC description "DECTCEM - Text Cursor Enable Mode" names {DECTCEM cursor_enable}}
}
47 {
{origin xterm description "xterm alternate buffer" names {xterm_altbuf}}
{origin DEC description "DECGRPM - Graphics Rotated Print Mode (obsolete?)" names {DECGRPM}}
}
66 {
{origin DEC description "DECNKM - Numeric Keypad Mode" names {DECNKM}}
}
1000 {
{origin "xterm" description "VT200 compatibility mouse" names {SET_VT200_MOUSE} note {
Escape sequence on both button press and release.
CSI M CbCxCy
}
}
}
1004 {
{origin "xterm" description "Send FocusIn/FocusOut events" names {mouse_focus_event}}
}
1005 {
{origin "xterm" description "Enable UTF-8 Mouse Mode" names {mouse_utf8 mouse_utf8_extended}}
}
1006 {
{origin "xterm" description "Enable SGR Mouse Mode" names {mouse_sgr mouse_sgr_extended} note{
SET_SGR_EXT_MODE_MOUSE - extended compared to x10 mouse protocol which limits x y coords
to 223 (=255 - 32)
}
}
}
1015 {
{origin "urxvt" description "Enable urxvt Mouse Mode" names {mouse_urxvt}}
}
1016 {
{origin "xterm" description "Enable SGR Pixel Mouse Mode" names {mouse_sgr_pixel}}
}
1047 {
{origin "xterm" description "Alternate Buffer" names {alt_buffer_only}}
}
1049 {
{origin "xterm" description "Alternate Buffer with save cursor" names {alt_buffer alt_screen}}
}
2004 {
{origin "xterm" description "Set bracketed paste mode" names {bracketed_paste}}
}
2027 {
{origin Contour description "Grapheme Cluster Processing" names {grapheme_clusters}}
}
}
set decmode_names [dict create]
dict for {code items} $decmode_data {
foreach itm $items {
set names [dict get $itm names]
foreach nm $names {
dict set decmode_names $nm $code
}
}
}
proc query_mode {num_or_name} {
if {[string is integer -strict $num_or_name]} {
set m $num_or_name
@ -3674,11 +4055,17 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#[para]Erase to start of line, leaving cursor position alone.
return \033\[1K
}
proc vt52erase_sol {} {
return \x1bo
}
proc erase_eol {} {
#*** !doctools
#[call [fun erase_eol]]
return \033\[K
}
proc vt52erase_eol {} {
return \x1bK
}
#see also clear_above clear_below
# -- --- --- --- ---
@ -3732,6 +4119,7 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
proc cursor_pos_extended {} {
#includes page e.g ^[[47;3;1R
#(but not on all terminals - some (freebsd?) will report as per 6n e.g ^[[74;3R)
return \033\[?6n
}
@ -3789,6 +4177,9 @@ Brightblack 100 Brightred 101 Brightgreen 102 Brightyellow 103 Brightblu
#[para]This may not work on terminals which have multiple panes/windows
return "\033\]2;$windowtitle\033\\" ;#works for xterm and most derivatives
}
proc vt52titleset {windowtitle} {
return \x1bS$windowtitle\r
}
#titleget - https://invisible-island.net/xterm/xterm.faq.html#how2_title
#no cross-platform ansi-only mechanism ?
@ -4672,8 +5063,14 @@ tcl::namespace::eval punk::ansi::ta {
variable re_osc_open {(?:\x1b\]|\u009d).*}
variable standalone_code_map [list \x1bc "" \x1b7 "" \x1b8 "" \x1bM "" \x1bE "" \x1bD "" \x1bH "" \x1b= "" \x1b> "" \x1b#3 "" \x1b#4 "" \x1b#5 "" \x1b#6 "" \x1b#8 ""]
variable re_standalones {(?:\x1bc|\x1b7|\x1b8|\x1bM|\x1bE|\x1bD|\x1bD|\x1bH|\x1b=|\x1b>|\x1b#3|\x1b#4|\x1b#5|\x1b#6|\x1b#8)}
#variable standalone_code_map [list \x1bc "" \x1b7 "" \x1b8 "" \x1bM "" \x1bE "" \x1bD "" \x1bH "" \x1b= "" \x1b> "" \x1b#3 "" \x1b#4 "" \x1b#5 "" \x1b#6 "" \x1b#8 ""]
variable re_standalones {(?:\x1bc|\x1b7|\x1b8|\x1bM|\x1bD|\x1bE|\x1bH|\x1b=|\x1b>|\x1b#3|\x1b#4|\x1b#5|\x1b#6|\x1b#8)}
variable re_standalones_vt52 {(?:\x1bZ)}
#ESC Y move, ESC b foreground colour
#ESC F - gr-on ESC G - gr-off
variable re_vt52_open {(?:\x1bY|\x1bb|\x1bF)}
#\x1bc vt52 bgcolour conflict ??
#if we don't split on altgraphics too and separate them out - it's easy to get into a horrible mess
variable re_g0_group {(?:\x1b\(0)(?:(?!\x1b\(B).)*\x1b\(B}
@ -4686,7 +5083,7 @@ tcl::namespace::eval punk::ansi::ta {
#regex to capture the start of string/privacy message/application command block including the contents and string terminator (ST)
#non-greedy by exclusion of ST terminators in body
#we need to match \x1b\\ not just \x1b There could be colour codes nested in a privacy msg/string
#we need to match \x1b\\ not just \x1b There could be colour codes nested in a privacy msg/string (not widely supported?)
#even if terminals generally don't support that - it's quite possible for an ansi code to get nested this way - and we'd prefer it not to break our splits
#Just checking for \x1b will terminate the match too early
#we also need to track the start of ST terminated code and not add it for replay (in the ansistring functions)
@ -4705,17 +5102,44 @@ tcl::namespace::eval punk::ansi::ta {
#handrafted TRIE version of above. Somewhat difficult to construct and maintain. TODO - find a regexp TRIE generator that works with Tcl regexes
#This does make things quicker - but it's too early to finalise the detect/split regexes (e.g missing \U0090 ) - will need to be redone.
#variable re_ansi_detect {(?:\x1b(?:\((?:0|B)|\[(?:[\x20-\x2f\x30-\x3f]*[\x40-\x7e])|\](?:(?:[^\007]*)\007|(?:(?!\x1b\\).)*\x1b\\)|(?:P|X|\^|_)(?:(?:(?!\x1b\\|\007).)*(?:\x1b\\|\007))|c|7|8|M|E|D|H|=|>|(?:#(?:3|4|5|6|8))))|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)|(?:\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]|(?:\u009d)(?:[^\u009c]*)?\u009c}
variable re_ansi_detect {(?:\x1b(?:\[(?:[\x20-\x2f\x30-\x3f]*[\x40-\x7e])|\((?:0|B)|\](?:(?:[^\007]*)\007|(?:(?!\x1b\\).)*\x1b\\)|(?:P|X|\^|_)(?:(?:(?!\x1b\\|\007).)*(?:\x1b\\|\007))|c|7|8|M|E|D|H|=|>|(?:#(?:3|4|5|6|8))))|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)|(?:\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]|(?:\u009d)(?:[^\u009c]*)?\u009c}
#NOTE - the literal # char can cause problems in expanded syntax - even though it's within a bracketed section. \# seems to work though.
#vt52 specific |<|A|B|C|I|J|K|L|M|Z|(?:Y(?:..))|(?:b(?:.)|
#https://freemint.github.io/tos.hyp/en/VT_52_terminal.html
#what to with ESC c vs vt52 ESC c <c> (background colour) ???
#we probably need to use a separate re_ansi_detect for vt52
#although it's stated later terminals are backwards compatible with vt52 - that doesn't seem to mean for example a vt100 will process vt52 codes at the same time as ansi codes
#ie - when DECANM is on - VT52 codes are *not* processed
#todo - ansi mode and cursor key mode set ?
# arrow keys -> ESC O A, ESC O B, ESC O C, ESC O D
# plus more for auxiliary keypad codes in keypad application mode (and some in numeric mode)
variable re_ansi_detect {(?x)
(?:\x1b(?:\[(?:[\x20-\x2f\x30-\x3f]*[\x40-\x7e])|c|7|8|M|D|E|H|=|>|<|A|B|C|I|J|K|L|M|Z|(?:Y(?:..))|(?:b(?:.))|\((?:0|B)|\](?:(?:[^\007]*)\007|(?:(?!\x1b\\).)*\x1b\\)|(?:P|X|\^|_)(?:(?:(?!\x1b\\|\007).)*(?:\x1b\\|\007))|(?:\#(?:3|4|5|6|8))))
|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)
|(?:\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]
|(?:\u009d)(?:[^\u009c]*)?\u009c
}
#---
# -- --- --- ---
#variable re_csi_code {(?:\x1b\[|\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]}
variable re_ansi_detect_open "${re_csi_open}|${re_osc_open}|${re_standalones}|${re_ST_open}|${re_g0_open}"
variable re_ansi_detect_open "${re_csi_open}|${re_osc_open}|${re_standalones}|${re_standalones_vt52}|${re_ST_open}|${re_g0_open}|${re_vt52_open}"
#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
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])+"
} else {
set re_ansi_split_multi "(?:${re_ansi_split})+"
}
lappend PUNKARGS [list -dynamic 0 {
@id -id ::punk::ansi::ta::detect
@ -4922,81 +5346,46 @@ tcl::namespace::eval punk::ansi::ta {
# -- --- --- --- --- ---
#Split $text to a list containing alternating ANSI colour codes and text.
#ANSI colour codes are always on the second element, fourth, and so on.
#(ie plaintext on odd list-indices ansi on even indices)
#(ie plaintext on even list-indices ansi on odd indices)
#result of split on non-empty string always has an odd length - with indices 0 and end always being plaintext (possibly empty string)
# Example:
#ta_split_codes "" # => ""
#ta_split_codes "a" # => "a"
#ta_split_codes "a\e[31m" # => {"a" "\e[31m"}
#ta_split_codes "\e[31ma" # => {"" "\e[31m" "a"}
#ta_split_codes "\e[31ma\e[0m" # => {"" "\e[31m" "a" "\e[0m"}
#ta_split_codes "\e[31ma\e[0mb" # => {"" "\e[31m" "a" "\e[0m", "b"}
#ta_split_codes "\e[31m\e[0mb" # => {"" "\e[31m\e[0m" "b"}
#split_codes "" # => ""
#split_codes "a" # => "a"
#split_codes "a\e[31m" # => {"a" "\e[31m" ""}
#split_codes "\e[31ma" # => {"" "\e[31m" "a"}
#split_codes "\e[31ma\e[0m" # => {"" "\e[31m" "a" "\e[0m" ""}
#split_codes "\e[31ma\e[0mb" # => {"" "\e[31m" "a" "\e[0m", "b"}
#split_codes "\e[31m\e[0mb" # => {"" "\e[31m\e[0m" "b"}
#
proc split_codes {text} {
variable re_ansi_split
set re "(?:${re_ansi_split})+"
return [_perlish_split $re $text]
variable re_ansi_split_multi
return [_perlish_split $re_ansi_split_multi $text]
}
#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 odd/even plain ansi still holds)
#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.
proc split_codes_single2 {text} {
variable re_ansi_split
return [_perlish_split $re_ansi_split $text]
}
proc split_codes_single3 {text} {
#copy from re_ansi_split
_perlish_split {(?:\x1b(?:\((?:0|B)|\[(?:[\x20-\x2f\x30-\x3f]*[\x40-\x7e])|\](?:(?:[^\007]*)\007|(?:(?!\x1b\\).)*\x1b\\)|(?:P|X|\^|_)(?:(?:(?!\x1b\\|\007).)*(?:\x1b\\|\007))|c|7|8|M|E|D|H|=|>|(?:#(?:3|4|5|6|8))))|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)|(?:\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]|(?:\u009d)(?:[^\u009c]*)?\u009c} $text
}
proc split_codes_single4 {text} {
if {$text eq ""} {
return {}
}
variable re_ansi_split
set re $re_ansi_split
#variable re_ansi_detect1
#set re $re_ansi_detect1
set list [list]
set start 0
#set re {(?:\x1b(?:\((?:0|B)|\[(?:[\x20-\x2f\x30-\x3f]*[\x40-\x7e])|\](?:(?:[^\007]*)\007|(?:(?!\x1b\\).)*\x1b\\)|(?:P|X|\^|_)(?:(?:(?!\x1b\\|\007).)*(?:\x1b\\|\007))|c|7|8|M|E|D|H|=|>|(?:#(?:3|4|5|6|8))))|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)|(?:\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]|(?:\u009d)(?:[^\u009c]*)?\u009c}
#We can get $matchEnd < $matchStart; we need to ensure there is an exit condition for non-greedy empty results REVIEW
#while {[regexp -start $start -indices -- {(?:\x1b(?:\((?:0|B)|\[(?:[\x20-\x2f\x30-\x3f]*[\x40-\x7e])|\](?:(?:[^\007]*)\007|(?:(?!\x1b\\).)*\x1b\\)|(?:P|X|\^|_)(?:(?:(?!\x1b\\|\007).)*(?:\x1b\\|\007))|c|7|8|M|E|D|H|=|>|(?:#(?:3|4|5|6|8))))|(?:\u0090|\u0098|\u009E|\u009F)(?:(?!\u009c).)*(?:\u009c)|(?:\u009b)[\x20-\x2f\x30-\x3f]*[\x40-\x7e]|(?:\u009d)(?:[^\u009c]*)?\u009c} $text match]} {}
while {[regexp -start $start -indices -- $re $text match]} {
lassign $match matchStart matchEnd
#puts "->start $start ->match $matchStart $matchEnd"
lappend list [tcl::string::range $text $start $matchStart-1]
if {$matchEnd < $matchStart} {
set e $matchStart
incr start
} else {
set e $matchEnd
set start [expr {$matchEnd+1}]
}
lappend list [tcl::string::range $text $matchStart $e]
if {$start >= [tcl::string::length $text]} {
break
}
}
return [lappend list [tcl::string::range $text $start end]]
}
proc split_codes_single {text} {
if {$text eq ""} {
return {}
}
variable re_ansi_split
set next 0
set b -1
#set b -1
set list [list]
set coderanges [regexp -indices -all -inline -- $re_ansi_split $text]
foreach cr $coderanges {
lappend list [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}]
#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} {
variable re_ansi_split
return [_perlish_split $re_ansi_split $text]
}
proc get_codes_single {text} {
variable re_ansi_split
regexp -all -inline -- $re_ansi_split $text
@ -5008,7 +5397,7 @@ tcl::namespace::eval punk::ansi::ta {
return {}
}
set next 0
set b -1
#set b -1
set list [list]
set coderanges [regexp -indices -all -inline -- $re $text]
foreach cr $coderanges {
@ -5103,29 +5492,6 @@ tcl::namespace::eval punk::ansi::ta {
#return [lappend list [tcl::string::range $text $start end]]
yield [tcl::string::range $text $start end]
}
proc _perlish_split2 {re text} {
if {[tcl::string::length $text] == 0} {
return {}
}
set list [list]
set start 0
#We can get $matchEnd < $matchStart; we need to ensure there is an exit condition for non-greedy empty results REVIEW
while {[regexp -start $start -indices -- $re $text match]} {
lassign $match matchStart matchEnd
#puts "->start $start ->match $matchStart $matchEnd"
if {$matchEnd < $matchStart} {
lappend list [tcl::string::range $text $start $matchStart-1] [tcl::string::index $text $matchStart]
incr start
} else {
lappend list [tcl::string::range $text $start $matchStart-1] [tcl::string::range $text $matchStart $matchEnd]
set start [expr {$matchEnd+1}]
}
if {$start >= [tcl::string::length $text]} {
break
}
}
return [lappend list [tcl::string::range $text $start end]]
}
proc _ws_split {text} {
regexp -all -inline {(?:\S+)|(?:\s+)} $text
}
@ -7429,12 +7795,10 @@ tcl::namespace::eval punk::ansi::internal {
#todo - document
interp alias {} ansistring {} ::punk::ansi::ansistring
if {![info exists ::punk::args::register::NAMESPACES]} {
namespace eval ::punk::args::register {
set NAMESPACES [list]
}
}
#use fully qualified so 8.6 doesn't find existing var in global namespace
lappend ::punk::args::register::NAMESPACES ::punk::ansi ::punk::ansi::class ::punk::ansi::ta
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready

1378
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/args-0.1.0.tm

File diff suppressed because it is too large Load Diff

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

@ -2015,7 +2015,7 @@ tcl::namespace::eval punk::char {
# ------------------------------------------------------------------------------------------------------
proc grapheme_split_tk {string} {
if {![regexp "\[\uFF-\U10FFFF\]" $string]} {
#only ascii - no joiners or unicode
#only ascii (7 or 8 bit) - no joiners or unicode
return [split $string {}]
}
package require tk
@ -2068,14 +2068,14 @@ tcl::namespace::eval punk::char {
return $width
}
proc wcswidth_single {char} {
scan $c %c dec
if {$c <= 255 && !($c < 31 || $c == 127)} {
scan $char %c dec
if {$dec <= 255 && !($dec < 31 || $dec == 127)} {
#review - non-printing ascii? why does textutil::wcswidth report 1 ??
#todo - compare with python or other lang wcwidth
return 1
} elseif {$c < 917504 || $c > 917631} {
} elseif {$dec < 917504 || $dec > 917631} {
#TODO - various other joiners and non-printing chars
return [textutil::wcswidth_char $c]
return [textutil::wcswidth_char $dec] ;#note textutil::wcswidth_char takes a decimal codepoint!
#may return -1 - REVIEW
}
return 0
@ -2084,13 +2084,13 @@ tcl::namespace::eval punk::char {
set width 0
foreach c [split $string {}] {
scan $c %c dec
if {$c <= 255 && !($c < 31 || $c == 127)} {
if {$dec <= 255 && !($dec < 31 || $dec == 127)} {
#review - non-printing ascii? why does textutil::wcswidth report 1 ??
#todo - compare with python or other lang wcwidth
incr width
} elseif {$c < 917504 || $c > 917631} {
} elseif {$dec < 917504 || $dec > 917631} {
#TODO - various other joiners and non-printing chars
set w [textutil::wcswidth_char $c]
set w [textutil::wcswidth_char $dec] ;#takes decimal codepoint
if {$w < 0} {
return -1
} else {
@ -2117,14 +2117,14 @@ tcl::namespace::eval punk::char {
set chunk [tcl::string::range $string $startidx $endidx]
set codes [scan $chunk [tcl::string::repeat %c [tcl::string::length $chunk]]]
foreach c $codes {
if {$c <= 255 && !($c < 31 || $c == 127)} {
foreach dec $codes {
if {$dec <= 255 && !($dec < 31 || $dec == 127)} {
#review - non-printing ascii? why does textutil::wcswidth report 1 ??
#todo - compare with python or other lang wcwidth
incr width
} elseif {$c < 917504 || $c > 917631} {
} elseif {$dec < 917504 || $dec > 917631} {
#TODO - various other joiners and non-printing chars
set w [textutil::wcswidth_char $c]
set w [textutil::wcswidth_char $dec]
if {$w < 0} {
return -1
} else {
@ -2145,18 +2145,18 @@ tcl::namespace::eval punk::char {
#TODO
set codes [scan $string [tcl::string::repeat %c [tcl::string::length $string]]]
set width 0
foreach c $codes {
foreach dec $codes {
#unicode Tags block zero width
if {$c < 917504 || $c > 917631} {
if {$c <= 255} {
if {$dec < 917504 || $dec > 917631} {
if {$dec <= 255} {
#review - non-printing ascii? why does textutil::wcswidth report 1 ??
#todo - compare with python or other lang wcwidth
if {!($c < 31 || $c == 127)} {
if {!($dec < 31 || $dec == 127)} {
incr width
}
} else {
#TODO - various other joiners and non-printing chars
set w [textutil::wcswidth_char $c]
set w [textutil::wcswidth_char $dec] ;#takes decimal codepoint
if {$w < 0} {
return -1
} else {
@ -2169,7 +2169,7 @@ tcl::namespace::eval punk::char {
}
proc wcswidth2 {string} {
set codes [scan $string [tcl::string::repeat %c [tcl::string::length $string]]]
set widths [lmap c $codes {textutil::wcswidth_char $c}]
set widths [lmap dec $codes {textutil::wcswidth_char $dec}]
if {-1 in $widths} {
return -1
}

880
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

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

@ -246,6 +246,58 @@ tcl::namespace::eval punk::lib::compat {
#outside of lmap - don't know of any particularly nice ways to flatten to subindex 1 of each element..
#flattening then lsearch with -stride and * would be nice - but it's not avail in 8.6
if {![info exists ::auto_index(readFile)]} {
if {[info commands ::readFile] eq ""} {
proc ::readFile {filename {mode text}} {
#readFile not seen in auto_index or as command: installed by punk::lib
# Parse the arguments
set MODES {binary text}
set ERR [list -level 1 -errorcode [list TCL LOOKUP MODE $mode]]
set mode [tcl::prefix match -message "mode" -error $ERR $MODES $mode]
# Read the file
set f [open $filename [dict get {text r binary rb} $mode]]
try {
return [read $f]
} finally {
close $f
}
}
}
}
if {![info exists ::auto_index(writeFile)]} {
if {[info commands ::writeFile] eq ""} {
proc ::writeFile {args} {
#writeFile not seen in auto_index or as command: installed by punk::lib
# Parse the arguments
switch [llength $args] {
2 {
lassign $args filename data
set mode text
}
3 {
lassign $args filename mode data
set MODES {binary text}
set ERR [list -level 1 -errorcode [list TCL LOOKUP MODE $mode]]
set mode [tcl::prefix match -message "mode" -error $ERR $MODES $mode]
}
default {
set COMMAND [lindex [info level 0] 0]
return -code error -errorcode {TCL WRONGARGS} "wrong # args: should be \"$COMMAND filename ?mode? data\""
}
}
# Write the File
set f [open $filename [dict get {text w binary wb} $mode]]
try {
puts -nonewline $f $data
} finally {
close $f
}
}
}
}
if {"::lpop" ne [info commands ::lpop]} {
#puts stderr "Warning - no built-in lpop"
interp alias {} lpop {} ::punk::lib::compat::lpop
@ -1021,7 +1073,8 @@ namespace eval punk::lib {
-separator -default "%sep%"
-roottype -default "dict"
-substructure -default {}
-channel -default stdout -help "existing channel - or 'none' to return as string"
-channel -default stdout -help\
"existing channel - or 'none' to return as string"
@values -min 1 -max -1
@ -1049,7 +1102,6 @@ namespace eval punk::lib {
Here we supply 2 separate pattern hierarchies, where @0-end and @* are list operations and are equivalent
The second level segement in each pattern switches to a dict operation to retrieve the value by key.
When a list operation such as @* is used - integer list indexes are displayed on the left side of the = for that hierarchy level.
}
}]
#puts stderr "$argspec"
@ -1091,7 +1143,8 @@ namespace eval punk::lib {
set sep " [punk::ansi::a+ Green]=$RST " ;#stick to basic default colours for wider terminal support
set sep_mismatch " [punk::ansi::a+ Brightred undercurly underline undt-white]mismatch$RST "
}
package require punk ;#we need pipeline pattern matching features
package require punk::pipe
#package require punk ;#we need pipeline pattern matching features
package require textblock
set argd [punk::args::get_dict [string map [list %sep% $sep %sep_mismatch% $sep_mismatch] {
@ -1103,22 +1156,28 @@ namespace eval punk::lib {
-trimright -default 1 -type boolean -help\
"Trim whitespace off rhs of each line.
This can help prevent a single long line that wraps in terminal from making
every line wrap due to long rhs padding.
"
-separator -default {%sep%} -help "Separator column between keys and values"
-separator_mismatch -default {%sep_mismatch%} -help "Separator to use when patterns mismatch"
-roottype -default "dict" -help "list,dict,string"
-ansibase_keys -default "" -help "ansi list for each level in -substructure. e.g \[list \[a+ red\] \[a+ web-green\]\]"
every line wrap due to long rhs padding."
-separator -default {%sep%} -help\
"Separator column between keys and values"
-separator_mismatch -default {%sep_mismatch%} -help\
"Separator to use when patterns mismatch"
-roottype -default "dict" -help\
"list,dict,string"
-ansibase_keys -default "" -help\
"ansi list for each level in -substructure. e.g \[list \[a+ red\] \[a+ web-green\]\]"
-substructure -default {}
-ansibase_values -default ""
-keytemplates -default {\$\{$key\}} -type list -help "list of templates for keys at each level"
-keytemplates -default {\$\{$key\}} -type list -help\
"list of templates for keys at each level"
-keysorttype -default "none" -choices {none dictionary ascii integer real}
-keysortdirection -default increasing -choices {increasing decreasing}
-debug -default 0 -type boolean -help\
"When enabled, produces some rudimentary debug output on stderr"
@values -min 1 -max -1
dictvalue -type list -help "dict or list value"
patterns -default "*" -type string -multiple 1 -help "key or key glob pattern"
dictvalue -type list -help\
"dict or list value"
patterns -default "*" -type string -multiple 1 -help\
"key or key glob pattern"
}] $args]
#for punk::lib - we want to reduce pkg dependencies.
@ -1201,7 +1260,7 @@ namespace eval punk::lib {
set segments [split $pattern_nest /]
set levelpatterns [lindex $segments 0] ;#possibly comma separated patterns
#we need to use _split_patterns to separate (e.g to protect commas that appear within quotes)
set patterninfo [punk::_split_patterns $levelpatterns]
set patterninfo [punk::pipe::lib::_split_patterns $levelpatterns]
#puts stderr "showdict-->_split_patterns: $patterninfo"
foreach v_idx $patterninfo {
lassign $v_idx v idx
@ -1479,7 +1538,7 @@ namespace eval punk::lib {
# -- --- --- ---
set substructure ""
set pnext [lindex $segments 1]
set patterninfo [punk::_split_patterns $levelpatterns]
set patterninfo [punk::pipe::lib::_split_patterns $levelpatterns]
if {[llength $patterninfo] == 0} {
# // ? -review - what does this mean? for xpath this would mean at any level
set substructure [lindex $pattern_this_structure end]
@ -2043,17 +2102,31 @@ namespace eval punk::lib {
concat {*}[uplevel 1 lmap {*}$args]
}
#proc dict_getdef {dictValue args} {
# if {[llength $args] < 1} {
# error {wrong # args: should be "dict_getdef dictValue ?key ...? key default"}
# }
# set keys [lrange $args -1 end-1]
# if {[tcl::dict::exists $dictValue {*}$keys]} {
# return [tcl::dict::get $dictValue {*}$keys]
# } else {
# return [lindex $args end]
# }
#}
if {[info commands ::tcl::dict::getdef] eq ""} {
proc dict_getdef {dictValue args} {
if {[llength $args] < 1} {
error {wrong # args: should be "dict_getdef dictValue ?key ...? key default"}
}
set keys [lrange $args -1 end-1]
set keys [lrange $args 0 end-1]
if {[tcl::dict::exists $dictValue {*}$keys]} {
return [tcl::dict::get $dictValue {*}$keys]
} else {
return [lindex $args end]
}
}
} else {
#we pay a minor perf penalty for the wrap
interp alias "" ::punk::lib::dict_getdef "" ::tcl::dict::getdef
}
#proc sample1 {p1 n args} {
# #*** !doctools
@ -2722,6 +2795,7 @@ namespace eval punk::lib {
}
return [join $result \n]
}
#dedent?
proc undent {text} {
if {$text eq ""} {
return ""
@ -4142,12 +4216,10 @@ tcl::namespace::eval punk::lib::debug {
proc showdict {args} {}
}
if {![info exists ::punk::args::register::NAMESPACES]} {
namespace eval ::punk::args::register {
set ::punk::args::register::NAMESPACES [list] ;#use fully qualified so 8.6 doesn't find existing var in global namespace
}
}
#use fully qualified so 8.6 doesn't find existing var in global namespace
lappend ::punk::args::register::NAMESPACES ::punk::lib
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide punk::lib [tcl::namespace::eval punk::lib {

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

@ -177,7 +177,8 @@ namespace eval punk::mix::cli {
}
}
}
cd $sourcefolder
#cd $sourcefolder
#use run so that stdout visible as it goes
if {![catch {run --timeout=55000 -debug [info nameofexecutable] $sourcefolder/make.tcl {*}$args} exitinfo]} {
#todo - notify if exit because of timeout!
@ -185,11 +186,11 @@ namespace eval punk::mix::cli {
set exitcode [dict get $exitinfo exitcode]
} else {
puts stderr "Error unable to determine exitcode. err: $exitinfo"
cd $startdir
#cd $startdir
return false
}
cd $startdir
#cd $startdir
if {$exitcode != 0} {
puts stderr "FAILED with exitcode $exitcode"
return false
@ -364,10 +365,10 @@ namespace eval punk::mix::cli {
#ignore trailing .tm .TM if present
#if version doesn't pass validation - treat it as part of the modulename and return empty version string without error
#Up to caller to validate.
proc split_modulename_version {modulename} {
set lastpart [namespace tail $modulename]
proc split_modulename_version {fullmodulename} {
set lastpart [namespace tail $fullmodulename]
set lastpart [file tail $lastpart] ;# should be ok to use file tail now that we've ensured no namespace components
if {[string equal -nocase [file extension $modulename] ".tm"]} {
if {[string equal -nocase [file extension $fullmodulename] ".tm"]} {
set fileparts [split [file rootname $lastpart] -]
} else {
set fileparts [split $lastpart -]
@ -380,7 +381,13 @@ namespace eval punk::mix::cli {
set namesegment [join $fileparts -]
set versionsegment ""
}
return [list $namesegment $versionsegment]
set base [namespace qualifiers $fullmodulename]
if {$base ne ""} {
set modulename "${base}::$namesegment"
} else {
set modulename $namesegment
}
return [list $modulename $versionsegment]
}
proc get_status {{workingdir ""} args} {

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

@ -31,16 +31,42 @@ namespace eval punk::mix::commandset::layout {
namespace export *
namespace eval argdoc {
proc layout_names {} {
if {[catch {punk::mix::commandset::layout::lib::layouts_dict *} ldict]} {
#REVIEW
return "punk.project"
} else {
return [dict keys $ldict]
}
}
}
#per layout functions
proc files {{layout ""}} {
set argd [punk::args::get_dict {
punk::args::define {
@dynamic
@id -id ::punk::mix::commandset::layout::files
-datetime -default "%Y-%m-%dT%H:%M:%S" -help\
"Datetime format for mtime. Use empty string for no datetime output"
@values -min 1 -max 1
layout -type string -minsize 1
} [list $layout]]
layout -type string -choices {${[punk::mix::commandset::layout::argdoc::layout_names]}}
}
proc files {args} {
set argd [punk::args::get_by_id ::punk::mix::commandset::layout::files $args]
set layout [dict get $argd values layout]
set dtformat [dict get $argd opts -datetime]
set allfiles [lib::layout_all_files $layout]
if {$dtformat eq ""} {
return [join $allfiles \n]
} else {
set out ""
foreach f $allfiles {
set mtime [dict get [file stat $f] mtime]
append out "$f [clock format $mtime -format $dtformat]" \n
}
set out [string range $out 0 end-1]
return $out
}
}
proc templatefiles {layout} {
set templatefiles_and_tags [lib::layout_scan_for_template_files $layout]
@ -166,7 +192,7 @@ namespace eval punk::mix::commandset::layout {
}
proc as_dict {args} {
tailcall punk::mix::commandset::layout::lib::layouts_dict {*}$args
punk::mix::commandset::layout::lib::layouts_dict {*}$args
}
proc references_as_dict {args} {
package require punk::cap

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

@ -1,5 +1,5 @@
# -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'pmix make' or src/make.tcl to update from <pkg>-buildversion.txt
# Maintenance Instruction: leave the 999999.xxx.x as is and use 'deck make' or src/make.tcl to update from <pkg>-buildversion.txt
#
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
@ -304,11 +304,12 @@ namespace eval punk::mix::commandset::loadedlib {
}
set versions [package versions [lindex $libfound 0]]
if {$has_natsort} {
set versions [natsort::sort $versions]
} else {
set versions [lsort $versions]
}
set versions [lsort -command {package vcompare} $versions]
#if {$has_natsort} {
# set versions [natsort::sort $versions]
#} else {
# set versions [lsort $versions]
#}
if {![llength $versions]} {
error "No version numbers found for library/module $libfound - sorry, you will need to copy it across manually"
}

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

@ -120,17 +120,20 @@ namespace eval punk::mix::commandset::module {
return $table
}
#return all module templates with repeated ones suffixed with .2 .3 etc
proc templates_dict {args} {
set argspec {
#return all module templates with repeated ones suffixed with #2 #3 etc
punk::args::define {
@id -id ::punk::mix::commandset::module::templates_dict
@cmd -name templates_dict -help "Templates from module and project paths"
-startdir -default "" -help "Project folder used in addition to module paths"
@cmd -name templates_dict -help\
"Templates from module and project paths"
-startdir -default "" -help\
"Project folder used in addition to module paths"
-not -default "" -multiple 1
@values
globsearches -default * -multiple 1
}
set argd [punk::args::get_dict $argspec $args]
proc templates_dict {args} {
set argd [punk::args::get_by_id ::punk::mix::commandset::module::templates_dict $args]
package require punk::cap
if {[punk::cap::capability_has_handler punk.templates]} {
set template_folder_dict [punk::cap::call_handler punk.templates get_itemdict_moduletemplates {*}$args]
@ -154,10 +157,11 @@ namespace eval punk::mix::commandset::module {
the higher version number will be used.
"
-license -default <unspecified>
-author -default <unspecified> -multiple 1
-template -default punk.module
-type -default "[lindex $moduletypes 0]" -choices {$moduletypes}
-force -default 0 -type boolean -help\
"If set true, will overwrite an existing .tm file if there is one.
"If set true, will OVERWRITE an existing .tm file if there is one.
If false (default) an error will be raised if there is a conflict."
-quiet -default 0 -type boolean -help\
"Suppress information messages on stdout"
@ -262,6 +266,7 @@ namespace eval punk::mix::commandset::module {
}
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
set opt_license [dict get $opts -license]
set opt_authors [dict get $opts -author] ;#-multiple true
# -- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- --- ---
set opt_template [dict get $opts -template]
if {[regexp {.*[?*].*} $opt_template]} {
@ -403,7 +408,7 @@ namespace eval punk::mix::commandset::module {
#for now the user has the option to override any templates and remove %moduletemplate% if it is a security/privacy concern
#Don't put literal %x% in the code for the commandset::module itself - to stop them being seen by layout scanner as replacable tokens
set tagnames [list moduletemplate $moduletemplate project $projectname pkg $modulename year $year license $opt_license version $infile_version]
set tagnames [list moduletemplate $moduletemplate project $projectname pkg $modulename year $year license $opt_license authors $opt_authors version $infile_version]
set strmap [list]
foreach {tag val} $tagnames {
lappend strmap %$tag% $val

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

@ -109,7 +109,26 @@ namespace eval punk::mix::commandset::project {
}
namespace eval argdoc {
set layout_dict [punk::cap::call_handler punk.templates get_itemdict_projectlayouts]
variable LAYOUTNAMES [dict keys $layout_dict]
}
punk::args::define {
@id -id ::punk::mix::commandset::project::new
@cmd -name "punk::mix::commandset::project::new" -help\
""
@leaders -min 1 -max 1
project -type string -help\
"Project name or path.
If just a name is given ... (todo)"
@opts
-type -default plain
-empty -default 0 -type boolean
-force -default 0 -type boolean
-update -default 0 -type boolean
-confirm -default 1 -type boolean
-layout -default "punk.project" -choices {${$::punk::mix::commandset::project::argdoc::LAYOUTNAMES}}
}
proc new {newprojectpath_or_name args} {
#*** !doctools
@ -300,7 +319,17 @@ namespace eval punk::mix::commandset::project {
}
}
} elseif {$project_dir_exists && $opt_update} {
puts stderr "mix new WARNING: -update 1 was supplied. Will copy layout $layout_path using -update option to add missing items"
set warnmsg "mix new WARNING: -update 1 was supplied. Will copy layout $layout_path using -update option to add missing items"
if {$opt_confirm} {
puts stderr $warnmsg
set msg "Do you want to proceed to possibly overwrite some existing files in $projectdir? Y|N"
set answer [util::askuser $msg]
if {[string tolower $answer] ne "y"} {
puts stderr "mix new aborting due to user response '$answer' (required Y|y to proceed) use -confirm 0 to avoid prompt."
return
}
}
puts stderr $warnmsg
}
set fossil_repo_file ""
@ -366,28 +395,40 @@ namespace eval punk::mix::commandset::project {
#default antiglob_dir_core will stop .fossil* from being updated - which is generally desirable as these are likely to be customized
if {$opt_force} {
puts stdout "copying layout files - with force applied - overwrite all-targets"
set resultdict [punkcheck::install $layout_path $projectdir -installer project.new -overwrite ALL-TARGETS -antiglob_paths $antipaths -antiglob_dir $antiglob_dir]
set resultdict [punkcheck::install $layout_path $projectdir -installer project.new -createempty 1 -overwrite ALL-TARGETS -antiglob_paths $antipaths -antiglob_dir $antiglob_dir]
} else {
puts stdout "copying layout files - (if source file changed)"
set resultdict [punkcheck::install $layout_path $projectdir -installer project.new -overwrite installedsourcechanged-targets -antiglob_paths $antipaths -antiglob_dir $antiglob_dir]
set resultdict [punkcheck::install $layout_path $projectdir -installer project.new -createempty 1 -overwrite installedsourcechanged-targets -antiglob_paths $antipaths -antiglob_dir $antiglob_dir]
}
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
if {[file exists $layout_path/src/doc]} {
puts stdout "copying layout src/doc files (if target missing)"
set resultdict [punkcheck::install $layout_path/src/doc $projectdir/src/doc -punkcheck_folder $projectdir -installer project.new -overwrite SYNCED-TARGETS]
set resultdict [punkcheck::install $layout_path/src/doc $projectdir/src/doc -createdir 1 -punkcheck_folder $projectdir -installer project.new -overwrite SYNCED-TARGETS]
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
} else {
puts stdout "no src/doc in source template - update not required"
}
#target folders .fossil-custom and .fossil-settings may not exist. use -createdir 1 to ensure existence.
#In this case we need to override the default dir antiglob - as .fossil-xxx folders need to be installed from template if missing, or if target is uncustomized.
## default_antiglob_dir_core [list "#*" "_aside" ".git" ".fossil*"]
set override_antiglob_dir_core [list #* _aside .git]
if {[file exists $layout_path/.fossil-custom]} {
puts stdout "copying layout src/.fossil-custom files (if target missing or uncustomised)"
set resultdict [punkcheck::install $layout_path/.fossil-custom $projectdir/.fossil-custom -createdir 1 -punkcheck_folder $projectdir -installer project.new -antiglob_dir_core $override_antiglob_dir_core -overwrite SYNCED-TARGETS]
set resultdict [punkcheck::install $layout_path/.fossil-custom $projectdir/.fossil-custom -createdir 1 -createempty 1 -punkcheck_folder $projectdir -installer project.new -antiglob_dir_core $override_antiglob_dir_core -overwrite SYNCED-TARGETS]
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
} else {
puts stdout "no .fossil-custom in source template - update not required"
}
if {[file exists $layout_path/.fossil-settings]} {
puts stdout "copying layout src/.fossil-settings files (if target missing or uncustomised)"
set resultdict [punkcheck::install $layout_path/.fossil-settings $projectdir/.fossil-settings -createdir 1 -punkcheck_folder $projectdir -installer project.new -antiglob_dir_core $override_antiglob_dir_core -overwrite SYNCED-TARGETS]
set resultdict [punkcheck::install $layout_path/.fossil-settings $projectdir/.fossil-settings -createdir 1 -createempty 1 -punkcheck_folder $projectdir -installer project.new -antiglob_dir_core $override_antiglob_dir_core -overwrite SYNCED-TARGETS]
puts stdout [punkcheck::summarize_install_resultdict $resultdict]
} else {
puts stdout "no .fossil-settings in source template - update not required"
}
#scan all files in template
#
@ -395,30 +436,19 @@ namespace eval punk::mix::commandset::project {
set templatefiles [punk::mix::commandset::layout::lib::layout_scan_for_template_files $opt_layout]
set stripprefix [file normalize $layout_path]
#set tagmap [list [lib::template_tag project] $projectname]
#todo - get from somewhere
set alltag_substitutions [list project $projectname]
set tagmap [list [lib::template_tag project] $projectname]
if {[llength $templatefiles]} {
puts stdout "Filling template file placeholders with the following tag map:"
foreach {placeholder value} $alltag_substitutions {
foreach {placeholder value} $tagmap {
puts stdout " $placeholder -> $value"
}
}
foreach templatefullpath_and_tags $templatefiles {
lassign $templatefullpath_and_tags templatefullpath tags_present
foreach templatefullpath $templatefiles {
set templatetail [punk::repo::path_strip_alreadynormalized_prefixdepth $templatefullpath $stripprefix]
set fpath [file join $projectdir $templatetail]
if {[file exists $fpath]} {
set fd [open $fpath r]; fconfigure $fd -translation binary; set data [read $fd]; close $fd
set tagmap [list]
dict for {t v} $alltag_substitutions {
if {$t in $tags_present} {
lappend tagmap [lib::template_tag $t] $v
}
}
set data2 [string map $tagmap $data]
if {$data2 ne $data} {
puts stdout "updated template file: $fpath"

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

@ -281,7 +281,8 @@ tcl::namespace::eval punk::nav::fs {
}
}
if {[file pathtype $a1] ne "relative"} {
if {![regexp {[*?]} $a1] && [file pathtype $a1] ne "relative"} {
#non-relative non-glob
if { ![string match //zipfs:/* $a1]} {
if {[file type $a1] eq "directory"} {
cd $a1

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

@ -26,9 +26,16 @@ tcl::namespace::eval ::punk::ns::evaluator {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::ns {
variable ns_current "::"
variable ns_current
#allow presetting
if {![info exists ::punk::ns::ns_current]} {
set ns_current ::
}
variable ns_re_cache [dict create] ;#cache regular expressions used in globmatchns
namespace export nsjoin nsprefix nstail nsparts nseval nschildren nsimport_noclobber corp
namespace path {::punk::lib ::punk::ansi} ;#list_as_lines etc
catch {
package require debug
debug define punk.ns.compile
@ -1259,7 +1266,7 @@ tcl::namespace::eval punk::ns {
} else {
set report_namespaces $matched_namespaces
}
punk::args::update_definitions
punk::args::update_definitions $report_namespaces
set nsdict_list [list]
foreach ch $report_namespaces {
@ -1371,9 +1378,9 @@ tcl::namespace::eval punk::ns {
#use aliases glob - because aliases can be present with or without leading ::
#NOTE: alias may not have matching command in the relevant namespce (renamed alias) so we can't just start with commands and check if it's an alias if we want to show all aliases
if {$weird_ns} {
set raw_aliases [nseval_ifexists $location [list ::aliases $glob]] ;#'aliases $glob' must be passed as list, not separate args to namespace eval.
set raw_aliases [nseval_ifexists $location [list ::punk::lib::aliases $glob]] ;#'aliases $glob' must be passed as list, not separate args to namespace eval.
} else {
set raw_aliases [tcl::namespace::eval $location [list ::aliases $glob]] ;#'aliases $glob' must be passed as list, not separate args to namespace eval.
set raw_aliases [tcl::namespace::eval $location [list ::punk::lib::aliases $glob]] ;#'aliases $glob' must be passed as list, not separate args to namespace eval.
}
#set raw_aliases [nseval $location [list ::aliases $glob]] ;#'aliases $glob' must be passed as list, not separate args to namespace eval.
set aliases [list]
@ -1620,6 +1627,7 @@ tcl::namespace::eval punk::ns {
if {$has_punkargs} {
#set id [string trimleft $fq :]
set id $fq
punk::args::update_definitions [list [namespace qualifiers $id]]
if {[::punk::args::id_exists $id]} {
lappend usageinfo $c
} else {
@ -1969,7 +1977,8 @@ tcl::namespace::eval punk::ns {
#todo - -cache or -refresh to configure whether we introspect ensembles/objects each time?
# - as this is interactive generally introspection should be ok at the top level
# but if we need to go down multiple levels of subcommands generating/testing prefixes - could be an issue ??
punk::args::define -dynamic 0 {
punk::args::define {
@dynamic
@id -id ::punk::ns::arginfo
@cmd -name punk::ns::arginfo -help\
"Show usage info for a command.
@ -1995,7 +2004,7 @@ tcl::namespace::eval punk::ns {
-return -type string -default table -choices {string table tableobject}
} {${[punk::args::resolved_def -type @opts ::punk::args::arg_error -scheme]}} {
} {${[punk::args::resolved_def -types opts ::punk::args::arg_error -scheme]}} {
-- -type none -help\
"End of options marker
@ -2008,7 +2017,7 @@ tcl::namespace::eval punk::ns {
Multiple subcommands can be supplied if ensembles are further nested"
}
proc arginfo {args} {
lassign [dict values [punk::args::get_by_id ::punk::ns::arginfo $args]] leaders opts values received
lassign [dict values [punk::args::parse $args withid ::punk::ns::arginfo]] leaders opts values received
#review - setting this afterwards is an architecture smell - we should be able to override the default in the dynamic part
#todo - enable retrieving by id just the record_opts part - so we can treat as a dict directly, as well as easily apply it as a different flag name.
@ -2018,7 +2027,6 @@ tcl::namespace::eval punk::ns {
set querycommand [dict get $values commandpath]
set queryargs [dict get $values subcommand]
punk::args::update_definitions ;#ensure any packages that register PUNKARGS have been loaded
#todo - similar to corp? review corp resolution process
@ -2087,6 +2095,16 @@ tcl::namespace::eval punk::ns {
}
}
#check for a direct match first
if {[info commands ::punk::args::id_exists] ne ""} {
if {![llength $queryargs]} {
punk::args::update_definitions [list [namespace qualifiers $origin]]
if {[punk::args::id_exists $origin]} {
return [uplevel 1 [list punk::args::usage {*}$opts $origin]]
}
}
}
#ns::cmdtype only detects alias type on 8.7+?
set initial_cmdtype [punk::ns::cmdtype $origin]
switch -- $initial_cmdtype {
@ -2137,31 +2155,40 @@ tcl::namespace::eval punk::ns {
set id $origin
if {[info commands ::punk::args::id_exists] ne ""} {
#cycle through longest first checking for id matching ::cmd ?subcmd..?
#REVIEW - this doesn't cater for prefix callable subcommands!
#check longest first checking for id matching ::cmd ?subcmd..?
#REVIEW - this doesn't cater for prefix callable subcommands
set argcopy $queryargs
while {[llength $argcopy]} {
if {[punk::args::id_exists [list $id {*}$argcopy]]} {
return [uplevel 1 [list punk::args::usage {*}$opts [list $id {*}$argcopy]]]
if {[llength $queryargs]} {
punk::args::update_definitions [list [namespace qualifiers $id]]
if {[punk::args::id_exists [list $id {*}$queryargs]]} {
return [uplevel 1 [list punk::args::usage {*}$opts [list $id {*}$queryargs]]]
}
lpop argcopy
}
#while {[llength $argcopy]} {
# if {[punk::args::id_exists [list $id {*}$argcopy]]} {
# return [uplevel 1 [list punk::args::usage {*}$opts [list $id {*}$argcopy]]]
# }
# lpop argcopy
#}
#didn't find any exact matches
#traverse from other direction taking prefixes into account
punk::args::update_definitions [list [namespace qualifiers $id]]
if {[punk::args::id_exists $id]} {
#cycle forward through leading values
set def [punk::args::get_def $id]
set spec [punk::args::get_spec $id]
if {[llength $queryargs]} {
set nextqueryargs [list] ;#build a list of prefix-resolved queryargs
set queryargs_untested $queryargs
foreach q $queryargs {
if {[llength [dict get $def LEADER_NAMES]]} {
set subitems [dict get $def LEADER_NAMES]
if {[llength [dict get $spec LEADER_NAMES]]} {
set subitems [dict get $spec LEADER_NAMES]
if {[llength $subitems]} {
set next [lindex $subitems 0]
set arginfo [dict get $def ARG_INFO $next]
set arginfo [dict get $spec ARG_INFO $next]
set allchoices [list]
set choices [punk::args::system::Dict_getdef $arginfo -choices {}]
@ -2184,16 +2211,22 @@ tcl::namespace::eval punk::ns {
#we have our first difference - recurse with new query args
#set numvals [expr {[llength $queryargs]+1}]
#return [ punk::ns::arginfo {*}[lrange $args 0 end-$numvals] $querycommand {*}$nextqueryargs {*}$queryargs_untested]
#puts "===> testing arginfo {*}$opts $querycommand {*}$nextqueryargs {*}$queryargs_untested"
return [ punk::ns::arginfo {*}$opts $querycommand {*}$nextqueryargs {*}$queryargs_untested]
}
#check if subcommands so far have a custom args def
set currentid [list $querycommand {*}$nextqueryargs]
#set currentid [list $querycommand {*}$nextqueryargs]
set currentid [list $id {*}$nextqueryargs]
if {[punk::args::id_exists $currentid]} {
set def [punk::args::get_def $currentid
set spec [punk::args::get_spec $currentid]
} else {
#We can get no further with custom defs
#It is possible we have a documented lower level subcommand but missing the intermediate
#e.g if ::trace remove command was specified and is documented - it will be found above
#but if ::trace remove is not documented and the query is "::trace remove com"
#There is no way to determine com is a prefix as we don't have the intermediate documented -choice info available.
#that's probably ok.
break
}
}
@ -2307,7 +2340,7 @@ tcl::namespace::eval punk::ns {
set implementations [::info object call $origin $c1]
#result documented as list of 4 element lists
#set callinfo [lindex $implementations 0]
set def ""
set oodef ""
foreach impl $implementations {
lassign $impl generaltype mname location methodtype
switch -- $generaltype {
@ -2323,7 +2356,7 @@ tcl::namespace::eval punk::ns {
return [uplevel 1 [list punk::args::usage {*}$opts $idcustom]]
}
}
set def [::info object definition $origin $c1]
set oodef [::info object definition $origin $c1]
} else {
#set id "[string trimleft $location :] $c1" ;# "<class> <method>"
set idcustom "$location $c1"
@ -2332,7 +2365,7 @@ tcl::namespace::eval punk::ns {
return [uplevel 1 [list punk::args::usage {*}$opts $idcustom]]
}
}
set def [::info class definition $location $c1]
set oodef [::info class definition $location $c1]
}
break
}
@ -2342,10 +2375,9 @@ tcl::namespace::eval punk::ns {
}
}
}
if {$def ne ""} {
#assert - if we pre
if {$oodef ne ""} {
set autoid "(autodef)$location $c1"
set arglist [lindex $def 0]
set arglist [lindex $oodef 0]
set argdef [punk::lib::tstr -return string {
@id -id "${$autoid}"
@cmd -name "${$location} ${$c1}" -help\
@ -2368,7 +2400,7 @@ tcl::namespace::eval punk::ns {
append argdef \n "[lindex $a 0] -default {[lindex $a 1]} -optional 1"
}
default {
error "punk::ns::arginfo unexpected argument signature '$arglist'\ndef:$def\nimplementaions:$implementations"
error "punk::ns::arginfo unexpected oo argument signature '$arglist'\noodef:$oodef\nimplementations:$implementations"
}
}
incr i
@ -2427,7 +2459,7 @@ tcl::namespace::eval punk::ns {
@id -id ${$idauto}
@cmd -name "Object: ${$origin}" -help\
"Instance of class: ${$class} (info autogenerated)"
@values -min 1
@leaders -min 1
}]
append argdef \n $vline
punk::args::define $argdef
@ -2542,7 +2574,7 @@ tcl::namespace::eval punk::ns {
@cmd -help\
"(autogenerated)
ensemble: ${$origin}"
@values -min 1
@leaders -min 1
}]
append argdef \n $vline
punk::args::define $argdef
@ -2977,44 +3009,58 @@ tcl::namespace::eval punk::ns {
If not supplied, caller's namespace is used."
-prefix -optional 1 -help\
"string prefix for command names in target namespace"
@values -min 1 -max 1
sourcepattern -type string -optional 0 -help\
"Glob pattern for source namespace.
@values -min 1 -max -1
sourcepattern -type string -optional 0 -multiple 1 -help\
"Glob pattern(s) for exported commands in source namespace(s).
Globbing only active in the tail segment.
e.g ::mynamespace::*"
e.g ::mynamespace::a* ::mynamespace::j*"
}
proc nsimport_noclobber {args} {
lassign [dict values [punk::args::get_by_id ::punk::ns::nsimport_noclobber $args]] leaders opts values received
set sourcepattern [dict get $values sourcepattern]
set sourcepatterns [dict get $values sourcepattern]
set source_ns [tcl::namespace::qualifiers $sourcepattern]
if {![tcl::namespace::exists $source_ns]} {
error "nsimport_noclobber error namespace $source_ns not found"
}
set nscaller [uplevel 1 {namespace current}]
if {![dict exists $received -targetnamespace]} {
set target_ns $nscaller
} else {
set target_ns [dict get $opts -targetnamespace]
if {![string match ::* $target_ns]} {
set target_ns [punk::nsjoin $nscaller $target_ns]
set target_ns [punk::ns::nsjoin $nscaller $target_ns]
}
}
set all_imported [list]
set nstemp ::punk::ns::temp_import
foreach pat $sourcepatterns {
set source_ns [tcl::namespace::qualifiers $pat]
if {![tcl::namespace::exists $source_ns]} {
error "nsimport_noclobber error namespace $source_ns not found"
}
set a_export_patterns [tcl::namespace::eval $source_ns {namespace export}]
set a_commands [info commands $sourcepattern]
set a_commands [info commands $pat]
#puts "-->commands:'$a_commands'"
set a_tails [lmap v $a_commands {tcl::namespace::tail $v}]
set a_exported_tails [list]
foreach epattern $a_export_patterns {
set matches [lsearch -all -inline $a_tails $epattern]
foreach m $matches {
#we will be using namespace import <pattern> one by one on commands.
#we must protect glob chars that may exist in the actual command names.
#e.g nsimport_noclobber ::punk::ansi::a?
# will import a+ and a?
#but nsimport_noclobber {::punk::ansi::a\?}
# must import only a?
set m [string map {\\ \\\\ ? \\? * \\* \[ \\[ \] \\]} $m]
if {$m ni $a_exported_tails} {
lappend a_exported_tails $m
}
}
}
set nstemp ::punk::ns::temp_import
if {[tcl::dict:::exists $received -prefix]} {
#import via temporary/intermediate namespace
set pfx [dict get $opts -prefix]
set imported_commands [list]
if {[namespace exists $nstemp]} {
@ -3022,39 +3068,41 @@ tcl::namespace::eval punk::ns {
}
namespace eval $nstemp {}
foreach e $a_exported_tails {
set imported [tcl::namespace::eval $nstemp [string map [list <func> $e <a> $source_ns <pfx> $pfx <tgtns> $target_ns] {
set imported [apply {{tgtns func srcns pfx tmpns} {
set cmd ""
if {![catch {namespace import <a>::<func>}]} {
if {![catch {::tcl::namespace::eval $tmpns [list ::namespace import ${srcns}::$func]}]} {
#renaming will fail if target already exists
#renaming a command into another namespace still results in a command with 'info cmdtype' = 'import'
if {![catch {rename <func> [punk::ns::nsjoin <tgtns> <pfx><func>]}]} {
set cmd <pfx><func>
if {![catch {::tcl::namespace::eval $tmpns [list ::rename $func [::punk::ns::nsjoin $tgtns $pfx$func]]}]} {
set cmd $pfx$func
}
}
set cmd
}]]
} } $target_ns $e $source_ns $pfx $nstemp]
if {$imported ne ""} {
lappend imported_commands $imported
}
}
namespace delete $nstemp
return $imported_commands
}
} else {
#no prefix - direct import
set imported_commands [list]
foreach e $a_exported_tails {
set imported [tcl::namespace::eval $target_ns [string map [list <func> $e <a> $source_ns] {
set imported [apply {{tgtns func srcns} {
set cmd ""
if {![catch {namespace import <a>::<func>}]} {
set cmd <func>
if {![catch {tcl::namespace::eval $tgtns [list namespace import ${srcns}::$func]}]} {
set cmd $func
}
set cmd
}]]
if {[string length $imported]} {
} } $target_ns $e $source_ns]
if {$imported ne ""} {
lappend imported_commands $imported
}
}
return $imported_commands
}
lappend all_imported {*}$imported_commands
}
return $all_imported
}
#todo - use ns::nsimport_noclobber instead ?
@ -3092,7 +3140,23 @@ tcl::namespace::eval punk::ns {
interp alias {} corp {} punk::ns::corp
interp alias {} i {} punk::ns::arginfo
#An example of using punk::args in a pipeline
punk::args::define {
@id -id ::i+
@cmd -name "i+" -help\
"Display command help side by side"
@values
cmds -multiple 1 -help\
"Command names for which to show help info"
}
interp alias {} i+ {}\
.=args> punk::args::get_by_id ::i+ |argd>\
.=>2 dict get values cmds |cmds>\
.=cmds>2 lmap c {i -return tableobject {*}$c} |objs>\
.=objs>2 lmap t {$t configure -show_vseps 0 -show_edge 1} |>\
.=objs>2 lmap t {$t print} |tables>\
.=objs>2 lmap t {$t destroy} |>\
.=tables>* textblock::join -- <args|
}

149
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/packagepreference-0.1.0.tm

@ -101,13 +101,20 @@ package require commandstack
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::packagepreference {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
#variable xyz
variable PUNKARGS
#*** !doctools
#[subsection {Namespace punk::packagepreference}]
#[para] Core API functions for punk::packagepreference
#[list_begin definitions]
lappend PUNKARGS [list {
@id -id ::punk::packagepreference::install
@cmd -name ::punk::packagepreference::install -help\
"Install override for ::package builtin - for 'require' subcommand only."
@values -min 0 -max 0
}]
proc uninstall {} {
#*** !doctools
#[call [fun uninstall]]
@ -115,6 +122,13 @@ tcl::namespace::eval punk::packagepreference {
commandstack::remove_rename {::package punk::packagepreference}
}
lappend PUNKARGS [list {
@id -id ::punk::packagepreference::install
@cmd -name ::punk::packagepreference::install -help\
"Install override for ::package builtin - for 'require' subcommand only."
@values -min 0 -max 0
}]
proc install {} {
#*** !doctools
#[call [fun install]]
@ -179,32 +193,37 @@ tcl::namespace::eval punk::packagepreference {
if {!$is_exact && [llength $vwant] <= 1 } {
#required version unspecified - or specified singularly
set available_versions [$COMMANDSTACKNEXT_ORIGINAL versions $pkg]
if {[llength $available_versions] > 1} {
# ---------------------------------------------------------------
#An attempt to detect dll/so loaded and try to load same version
set pkgloadedinfo [lsearch -inline -index 1 [info loaded] $pkg]
if {![llength $pkgloadedinfo]} {
if {[regexp {[A-Z]} $pkg]} {
set pkgloadedinfo [lsearch -inline -index 1 [info loaded] [string tolower $pkg]]
if {![llength $pkgloadedinfo]} {
set pkgloadedinfo [lsearch -inline -index 1 [info loaded] [string totitle $pkg]]
}
}
}
#dll/so files are often named with version numbers that don't contain dots or a version number at all
#e.g sqlite3400.dll Thread288.dll
set pkgloadedinfo [lsearch -nocase -inline -index 1 [info loaded] $pkg]
if {[llength $pkgloadedinfo]} {
puts stderr "--> pkg not present but shared object seems to be loaded: $pkgloadedinfo"
puts stderr "--> pkg not already 'provided' but shared object seems to be loaded: $pkgloadedinfo - and multiple versions available"
lassign $pkgloadedinfo path name
set lcpath [string tolower $path]
set obj [file tail $lcpath]
if {[string match tcl9* $obj]} {
set obj [string range $obj 4 end]
} elseif {[string match lib* $obj]} {
set obj [string range $obj 3 end]
}
set pkginfo [file rootname $obj]
#e.g Thread2.8.8
if {[regexp {^([a-zA-Z\-]+)(.*)} $pkginfo _match lname lversion]} {
if {[string tolower $lname] eq [string tolower $pkg]} {
#first attempt to find a match for our loaded sharedlib path in a *simple* package ifneeded statement.
set lcpath_to_version [dict create]
foreach av $available_versions {
set scr [package ifneeded $pkg $av]
#ifneeded script not always a valid tcl list
if {![catch {llength $scr} scrlen]} {
if {$scrlen == 3 && [lindex $scr 0] eq "load" && [string match -nocase [lindex $scr 2] $pkg]} {
dict set lcpath_to_version [string tolower [lindex $scr 1]] $av
}
}
}
if {[dict exists $lcpath_to_version $lcpath]} {
set lversion [dict get $lcpath_to_version $lcpath]
} else {
#fallback to a best effort guess based on the path
set lversion [::punk::packagepreference::system::slibpath_guess_pkgversion $path $pkg]
}
if {$lversion ne ""} {
#name matches pkg
#hack for known dll version mismatch
if {[string tolower $pkg] eq "thread" && $lversion eq "30b3"} {
@ -240,9 +259,11 @@ tcl::namespace::eval punk::packagepreference {
}]
if {[dict get $stackrecord implementation] ne ""} {
set impl [dict get $stackrecord implementation] ;#use hardcoded name rather than slower (but more flexible) commandstack::get_next_command
puts stdout "punk::packagepreference renamed ::package to $impl"
#puts stdout "punk::packagepreference renamed ::package to $impl"
return 1
} else {
puts stderr "punk::packagepreference failed to rename ::package"
return 0
}
#puts stdout [info body ::package]
}
@ -297,14 +318,94 @@ tcl::namespace::eval punk::packagepreference::lib {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Internal]
#tcl::namespace::eval punk::packagepreference::system {
tcl::namespace::eval punk::packagepreference::system {
#*** !doctools
#[subsection {Namespace punk::packagepreference::system}]
#[para] Internal functions that are not part of the API
variable PUNKARGS
lappend PUNKARGS [list {
@id -id ::punk::packagepreference::system::slibpath_guess_pkgversion
@cmd -name punk::packagepreference::system::slibpath_guess_pkgversion -help\
"Assistance function to determine pkg version from the information
obtained from [info loaded]. This is used to try to avoid loading a different
version of a binary package in another thread/interp when the package isn't
present in the interp, but [info loaded] indicates the binary is already loaded.
The more general/robust way to avoid this is to ensure ::auto_path and
tcl::tm::list are the same in each interp/thread.
This call should only be used as a fallback in case a binary package has a more
complex ifneeded script. If the ifneeded script for a binary package is a
straightforward 'load <path_to_binary> <pkgname>' - then that information
should be used to determine the version by matching <path_to_binary>
rather than this one.
Takes a path to a shared lib (.so/.dll), and the name of its providing
package, and return the version of the package if possible to determine
from the path.
The filename portion of the lib is often missing a version number or has
a version number that has been shortened (e.g dots removed).
The filename itself is first checked for a version number - but the number
is ignored if it doesn't contain any dots.
(prefix is checked to match with $pkgname, with a possible additional prefix
of lib or tcl<int>)
Often (even usually) the parent or grandparent folder will be named as
per the package name with a proper version. If so we can return it,
otherwise return empty string.
The parent/grandparent matching will be done by looking for a case
insensitive match of the prefix to $pkgname.
"
@values -min 1
libpath -help "Full path to shared library (.so,.dll etc)"
pkgname -help ""
}]
proc slibpath_guess_pkgversion {libpath pkgname} {
set root [file rootname [file tail $libpath]]
set namelen [string length $pkgname]
regexp {^(tcl(?:[0-9])+){0,1}(.*)} $root _match tclxx root ;#regexp will match anything - but only truncate leading tclXX..
set testv ""
if {[string match -nocase $pkgname* $root]} {
set testv [string range $root $namelen end]
} elseif {[string match -nocase lib$pkgname* $root]} {
set testv [string range $root $namelen+3 end]
}
if {[string first . $testv] > 0} {
if {![catch [list package vcompare $testv $testv]]} {
#testv has an inner dot and is understood by tcl as a valid version number
return $testv
}
}
#no valid dotted version found directly on dll or so filename
set parent [file dirname $libpath] ;#parent folder is often some differentiator for platform or featureset (e.g win-x64)
set grandparent [file dirname $parent]
foreach path [list $parent $grandparent] {
set segment [file tail $path]
if {$segment eq "bin"} {
continue
}
set testv ""
if {[string match -nocase $pkgname* $segment]} {
set testv [string range $segment $namelen end]
} elseif {[string match -nocase critcl_$pkgname* $segment]} {
set testv [string range $segment $namelen+7 end]
}
#we don't look for dot in parent/grandparent version - a bare integer here after the <pkgname> will be taken to be the version
if {![catch [list package vcompare $testv $testv]]} {
return $testv
}
}
#review - sometimes path and lib are named only for major.minor but package provides major.minor.subversion
#using returned val to attempt to package require -exact major.minor will fail to load major.minor.subversion
return ""
}
}
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::packagepreference ::punk::packagepreference::system
}
#}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide punk::packagepreference [tcl::namespace::eval punk::packagepreference {

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

@ -651,9 +651,14 @@ namespace eval punk::path {
-call-depth-internal -default 0 -type integer
-antiglob_paths -default {} -help\
"list of path patterns to exclude
may include * and ** path segments e.g /usr/**"
may include * and ** path segments e.g
/usr/** (exlude subfolders based at /usr but not
files within /usr itself)
**/_aside (exlude files where _aside is last segment)
**/_aside/* (exclude folders one below an _aside folder)
**/_aside/** (exclude all folders with _aside as a segment)"
@values -min 0 -max -1 -optional 1 -type string
tailglobs -multiple 1 -help\
tailglobs -default * -multiple 1 -help\
"Patterns to match against filename portion (last segment) of each file path
within the directory tree being searched."
}
@ -671,29 +676,29 @@ namespace eval punk::path {
#[para] list of path patterns to exclude - may include * and ** path segments e.g /usr/**
#[para]no natsorting - so order is dependent on filesystem
set argd [punk::args::get_by_id ::punk::path::treefilenames $args]
set argd [punk::args::parse $args withid ::punk::path::treefilenames]
lassign [dict values $argd] leaders opts values received
set tailglobs [dict values $values]
set tailglobs [dict get $values tailglobs]
# -- --- --- --- --- --- ---
set opt_antiglob_paths [dict get $opts -antiglob_paths]
set CALLDEPTH [dict get $opts -call-depth-internal]
# -- --- --- --- --- --- ---
# -- --- --- --- --- --- ---
set files [list]
if {$CALLDEPTH == 0} {
#set opts [dict merge $opts [list -directory $opt_dir]]
if {![dict exists $received -directory]} {
set opt_dir [pwd]
} else {
set opt_dir [dict get $opts -directory]
}
# -- --- --- --- --- --- ---
set files [list]
if {$CALLDEPTH == 0} {
if {![file isdirectory $opt_dir]} {
return [list]
}
set opts [dict merge $opts [list -directory $opt_dir]]
if {![llength $tailglobs]} {
lappend tailglobs *
}
} else {
#assume/require to exist in any recursive call
set opt_dir [dict get $opts -directory]
}
set skip 0

853
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/pipe-1.0.tm

@ -0,0 +1,853 @@
# -*- tcl -*-
# Maintenance Instruction: leave the 999999.xxx.x as is and use punkshell 'dev make' or bin/punkmake to update from <pkg>-buildversion.txt
# module template: shellspy/src/decktemplates/vendor/punk/modules/template_module-0.0.3.tm
#
# Please consider using a BSD or MIT style license for greatest compatibility with the Tcl ecosystem.
# Code using preferred Tcl licenses can be eligible for inclusion in Tcllib, Tklib and the punk package repository.
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# (C) 2025
#
# @@ Meta Begin
# Application punk::pipe 1.0
# Meta platform tcl
# Meta license MIT
# @@ Meta End
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# doctools header
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[manpage_begin shellspy_module_punk::pipe 0 1.0]
#[copyright "2025"]
#[titledesc {Module API}] [comment {-- Name section and table of contents description --}]
#[moddesc {-}] [comment {-- Description at end of page heading --}]
#[require punk::pipe]
#[keywords module]
#[description]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Overview]
#[para] overview of punk::pipe
#[subsection Concepts]
#[para] -
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Requirements
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection dependencies]
#[para] packages used by punk::pipe
#[list_begin itemized]
package require Tcl 8.6-
#*** !doctools
#[item] [package {Tcl 8.6}]
# #package require frobz
# #*** !doctools
# #[item] [package {frobz}]
#*** !doctools
#[list_end]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section API]
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# oo::class namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#tcl::namespace::eval punk::pipe::class {
#*** !doctools
#[subsection {Namespace punk::pipe::class}]
#[para] class definitions
#if {[tcl::info::commands [tcl::namespace::current]::interface_sample1] eq ""} {
#*** !doctools
#[list_begin enumerated]
# oo::class create interface_sample1 {
# #*** !doctools
# #[enum] CLASS [class interface_sample1]
# #[list_begin definitions]
# method test {arg1} {
# #*** !doctools
# #[call class::interface_sample1 [method test] [arg arg1]]
# #[para] test method
# puts "test: $arg1"
# }
# #*** !doctools
# #[list_end] [comment {-- end definitions interface_sample1}]
# }
#*** !doctools
#[list_end] [comment {--- end class enumeration ---}]
#}
#}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::pipe {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Base namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[subsection {Namespace punk::pipe}]
#[para] Core API functions for punk::pipe
#[list_begin definitions]
#proc sample1 {p1 n args} {
# #*** !doctools
# #[call [fun sample1] [arg p1] [arg n] [opt {option value...}]]
# #[para]Description of sample1
# #[para] Arguments:
# # [list_begin arguments]
# # [arg_def tring p1] A description of string argument p1.
# # [arg_def integer n] A description of integer argument n.
# # [list_end]
# return "ok"
#}
#https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
#
#we can't provide a float comparison suitable for every situation,
#but we should pick something reasonable, keep it stable, and document it.
proc float_almost_equal {a b} {
package require math::constants
set diff [expr {abs($a - $b)}]
if {$diff <= $::math::constants::eps} {
return 1
}
set A [expr {abs($a)}]
set B [expr {abs($b)}]
set largest [expr {($B > $A) ? $B : $A}]
return [expr {$diff <= $largest * $::math::constants::eps}]
}
#debatable whether boolean_almost_equal is more surprising than helpful.
#values from a calculation that are extremely close to zero but aren't false could also be surprising - especially if they compare equal numerically
#perhaps a fuzzy-boolean is a step too far for a default - but it's inline with float-comparison for pattern-matching.
#alternatively - use an even more complex classifier? (^&~) ?
proc boolean_almost_equal {a b} {
if {[string is double -strict $a]} {
if {[float_almost_equal $a 0]} {
set a 0
}
}
if {[string is double -strict $b]} {
if {[float_almost_equal $b 0]} {
set b 0
}
}
#must handle true,no etc.
expr {($a && 1) == ($b && 1)}
}
#boolean could be tr, true, y, ye, yes,Yes, 1 , 0 etc.
proc boolean_equal {a b} {
#equivalenttly xnor: expr {!(($a && 1) ^ ($b && 1))} ;# less clear and no discernable timing benefit.
expr {($a && 1) == ($b && 1)}
}
proc val [list [list v [lreplace x 0 0]]] {return $v}
#*** !doctools
#[list_end] [comment {--- end definitions namespace punk::pipe ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# Secondary API namespace
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
tcl::namespace::eval punk::pipe::lib {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
tcl::namespace::path [tcl::namespace::parent]
#*** !doctools
#[subsection {Namespace punk::pipe::lib}]
#[para] Secondary functions that are part of the API
#[list_begin definitions]
#proc utility1 {p1 args} {
# #*** !doctools
# #[call lib::[fun utility1] [arg p1] [opt {?option value...?}]]
# #[para]Description of utility1
# return 1
#}
#map rhs to names suitable to use in pipemcd proc name (whitespace mapping)
# (for .= and = pipecmds)
proc pipecmd_namemapping {rhs} {
#used to build a command from a pattern which could contain :: - we need to map it to keep it a single command in the desired namespace.
#glob chars will prevent us using simple test {[info commands $cmd] eq ""} to test existence
#we could use the test {$cmd in [info commands]} - but mapping away globchars is more robust, allowing the simpler test
#set rhs [string trim $rhs];#ignore all leading & trailing whitespace
set rhs [string trimleft $rhs]
#---
#REVIEW!
#set rhs [regsub -all {\s{1,}} $rhs {<sp>}] ;#collapse all internal whitespace to a single <sp> token
#This stops us matching {/@**@x x} vs {/@**@x x}
#---
set rhs [tcl::string::map {: <c> ? <q> * <star> [ <lb> ] <rb> \\ <bsl> {"} <dq> " " <sp>} $rhs]
#review - we don't expect other command-incompatible chars such as colon?
return $rhs
}
# relatively slow on even small sized scripts
#proc arg_is_script_shaped2 {arg} {
# set re {^(\s|;|\n)$}
# set chars [split $arg ""]
# if {[lsearch -regex $chars $re] >=0} {
# return 1
# } else {
# return 0
# }
#}
#exclude quoted whitespace
proc arg_is_script_shaped {arg} {
if {[tcl::string::first \n $arg] >= 0} {
return 1
} elseif {[tcl::string::first ";" $arg] >= 0} {
return 1
} elseif {[tcl::string::first " " $arg] >= 0 || [tcl::string::first \t $arg] >= 0} {
lassign [_rhs_tail_split $arg] _ part2 ;#will have part2 if unquoted whitespace found
return [expr {$part2 ne ""}]
} else {
return 0
}
}
#split top level of patterns only.
proc _split_patterns_memoized {varspecs} {
set name_mapped [pipecmd_namemapping $varspecs]
set cmdname ::punk::pipecmds::split_patterns::_$name_mapped
if {[info commands $cmdname] ne ""} {
return [$cmdname]
}
set result [_split_patterns $varspecs]
proc $cmdname {} [list return $result]
#debug.punk.pipe.compile {proc $cmdname} 4
return $result
}
#note - empty data after trailing , is ignored. (comma as very last character)
# - fix by documentation only. double up trailing comma e.g <pattern>,, if desired to return pattern match plus all at end!
#todo - move to punk::pipe
proc _split_patterns {varspecs} {
set varlist [list]
# @ @@ - list and dict functions
# / level separator
# # list count, ## dict size
# % string functions
# ! not
set var_terminals [list "@" "/" "#" "%" "!" ">" "<"] ;# (> required for insertionspecs at rhs of = & .= )
#right bracket ) also ends a var - but is different depending on whether var is array or basic. For array - it forms part of the varname
#except when prefixed directly by pin classifier ^
set protect_terminals [list "^"] ;# e.g sequence ^#
#also - an atom usually doesn't need the / as a terminal - because it can't match a missing element unless it's empty string
#ie the one usecase is '/n to match either empty string or missing item at position n. For this one usecase - we miss the capability to atom match paths/urls .. '/usr/local/et'
set in_brackets 0 ;#count depth
set in_atom 0
set token ""
set end_var_posn -1 ;#first var_terminal encountered within each comma delimited section
set token_index 0 ;#index of terminal char within each token
set indq 0
set inbraces 0
set inesc 0 ;#whether last char was backslash (see also punk::escv)
set prevc ""
set char_index 0
#if {[string index $varspecs end] eq ","} {
# set varspecs [string range $varspecs 0 end-1]
#}
set charcount 0
foreach c [split $varspecs ""] {
incr charcount
if {$indq} {
if {$inesc} {
#puts stderr "inesc adding '$c'"
append token \\$c
} else {
if {$c eq {"}} {
set indq 0
} else {
append token $c
}
}
} elseif {$inbraces} {
if {$inesc} {
append token \\$c
} else {
if {$c eq "\}"} {
incr inbraces -1
if {$inbraces} {
append token $c
}
} elseif {$c eq "\{"} {
incr inbraces
if {$inbraces} {
append token $c
}
} else {
append token $c
}
}
} elseif {$in_atom} {
#ignore dquotes/brackets in atoms - pass through
append token $c
#set nextc [lindex $chars $char_index+1]
if {$c eq "'"} {
set in_atom 0
}
} elseif {$in_brackets > 0} {
append token $c
if {$c eq ")"} {
incr in_brackets -1
}
} else {
if {$c eq {"}} {
if {!$inesc} {
set indq 1
} else {
append token $c
}
} elseif {$c eq "\{"} {
if {!$inesc} {
set inbraces 1
} else {
append token $c
}
} elseif {$c eq ","} {
#set var $token
#set spec ""
#if {$end_var_posn > 0} {
# #tcl scan with %s will not handle whitespace as desired. Be explicit using string range instead.
# #lassign [scan $token %${end_var_posn}s%s] var spec
# set var [string range $token 0 $end_var_posn-1]
# set spec [string range $token $end_var_posn end] ;#key section includes the terminal char which ended the var and starts the spec
#} else {
# if {$end_var_posn == 0} {
# set var ""
# set spec $token
# }
#}
#lappend varlist [list [string trim $var] [string trim $spec]]
#set token ""
#set token_index -1 ;#reduce by 1 because , not included in next token
#set end_var_posn -1
} else {
append token $c
switch -exact -- $c {
' {
set in_atom 1
}
( {
incr in_brackets
}
default {
if {$end_var_posn == -1 && (($c in $var_terminals) && ($prevc ni $protect_terminals))} {
set end_var_posn $token_index
}
}
}
}
if {$c eq ","} {
set var $token
set spec ""
if {$end_var_posn > 0} {
#tcl scan with %s will not handle whitespace as desired. Be explicit using string range instead.
#lassign [scan $token %${end_var_posn}s%s] var spec
set var [string range $token 0 $end_var_posn-1]
set spec [string range $token $end_var_posn end] ;#key section includes the terminal char which ended the var and starts the spec
} else {
if {$end_var_posn == 0} {
set var ""
set spec $token
}
}
lappend varlist [list [string trim $var] $spec]
set token ""
set token_index -1
set end_var_posn -1
}
}
if {$charcount == [string length $varspecs]} {
if {!($indq || $inbraces || $in_atom || $in_brackets)} {
if {$c ne ","} {
set var $token
set spec ""
if {$end_var_posn > 0} {
#tcl scan with %s will not handle whitespace as desired. Be explicit using string range instead.
#lassign [scan $token %${end_var_posn}s%s] var spec
set var [string range $token 0 $end_var_posn-1]
set spec [string range $token $end_var_posn end] ;#key section includes the terminal char which ended the var and starts the spec
} else {
if {$end_var_posn == 0} {
set var ""
set spec $token
}
}
lappend varlist [list [string trim $var] $spec]
set token ""
set token_index -1
set end_var_posn -1
}
}
}
set prevc $c
if {$c eq "\\"} {
#review
if {$inesc} {
set inesc 0
} else {
set token [string range $token 0 end-1]
set inesc 1
}
} else {
set inesc 0
}
incr token_index
incr char_index
}
#if {[string length $token]} {
# #lappend varlist [splitstrposn $token $end_var_posn]
# set var $token
# set spec ""
# if {$end_var_posn > 0} {
# #lassign [scan $token %${end_var_posn}s%s] var spec
# set var [string range $token 0 $end_var_posn-1]
# set spec [string range $token $end_var_posn end] ;#key section includes the terminal char which ended the var and starts the spec
# } else {
# if {$end_var_posn == 0} {
# set var ""
# set spec $token
# }
# }
# #lappend varlist [list [string trim $var] [string trim $spec]]
# #spec needs to be able to match whitespace too
# lappend varlist [list [string trim $var] $spec]
#}
return $varlist
}
#todo - consider whether we can use < for insertion/iteration combinations
# =a<,b< iterate once through
# =a><,b>< cartesian product
# =a<>,b<> ??? zip ?
#
# ie = {a b c} |> .=< inspect
# would call inspect 3 times, once for each argument
# .= list {a b c} {x y z} |a/0,b/1> .=a><,b>< list
# would produce list of cartesian pairs?
#
proc _split_equalsrhs {insertionpattern} {
#map the insertionpattern so we can use faster globless info command search
set name_mapped [pipecmd_namemapping $insertionpattern]
set cmdname ::punk::pipecmds::split_rhs::_$name_mapped
if {[info commands $cmdname] ne ""} {
return [$cmdname]
}
set lst_var_indexposition [_split_patterns_memoized $insertionpattern]
set i 0
set return_triples [list]
foreach v_pos $lst_var_indexposition {
lassign $v_pos v index_and_position
#e.g varname@@data/ok>0 varname/1/0>end
#ensure only one ">" is detected
if {![string length $index_and_position]} {
set indexspec ""
set positionspec ""
} else {
set chars [split $index_and_position ""]
set posns [lsearch -all $chars ">"]
if {[llength $posns] > 1} {
error "pipesyntax error in segment insertion pattern '$insertionpattern' -v '$v' multiple '>' characters. Pattern not understood." "_split_equalsrhs $insertionpattern" [list pipesyntax insertionpattern_invalid]
}
if {![llength $posns]} {
set indexspec $index_and_position
set positionspec ""
} else {
set splitposn [lindex $posns 0]
set indexspec [string range $index_and_position 0 $splitposn-1]
set positionspec [string range $index_and_position $splitposn+1 end]
}
}
#review -
if {($positionspec in [list "*" "/*" "@*" "/" "@"]) || ($v eq "*" && $positionspec eq "")} {
set star ""
if {$v eq "*"} {
set v ""
set star "*"
}
if {[string index $positionspec end] eq "*"} {
set star "*"
}
#it is always possible to insert at end of list regardless of current length - so /end* and @end* are equivalent
#as are /end and @end
#lset lst_var_indexposition $i [list $v "/end$star"]
set triple [list $v $indexspec "/end$star"]
} else {
if {$positionspec eq ""} {
#e.g just =varname
#lset lst_var_indexposition $i [list $v "/end"]
set triple [list $v $indexspec "/end"]
#error "pipesyntax error in segment insertionpattern '$insertionpattern' - v '$v' missing position spec e.g /0"
} else {
if {[string index $indexspec 0] ni [list "" "/" "@"]} {
error "pipesyntax error in segment insertionpattern '$insertionpattern' - v '$v' bad index spec '$indexspec'" "_split_equalsrhs $insertionpattern" [list pipesyntax insertionpattern_invalid]
}
set triple [list $v $indexspec $positionspec]
}
}
lappend return_triples $triple
incr i
}
proc $cmdname {} [list return $return_triples]
return $return_triples
}
proc _rhs_tail_split {fullrhs} {
set inq 0; set indq 0
set equalsrhs ""
set i 0
foreach ch [split $fullrhs ""] {
if {$inq} {
append equalsrhs $ch
if {$ch eq {'}} {
set inq 0
}
} elseif {$indq} {
append equalsrhs $ch
if {$ch eq {"}} {
set indq 0
}
} else {
switch -- $ch {
{'} {
set inq 1
}
{"} {
set indq 1
}
" " {
#whitespace outside of quoting
break
}
0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - a - b - c - d - e - f - g - h - i - j - k - l - m - n - o - p - q - r - s - t - u - v - w - x - y - z {}
default {
#\t not a literal for switch jumpTable bytecompile - review - can we do it without for example string mapping to <t> (and without a literal binary tab in source file)?
#we can't (reliably?) put \t as one of our switch keys
#
if {$ch eq "\t"} {
break
}
}
}
append equalsrhs $ch
}
incr i
}
set tail [tcl::string::range $fullrhs $i end]
return [list $equalsrhs $tail]
}
#todo - recurse into bracketed sub parts
#JMN3
#e.g @*/(x@0,y@2)
proc _var_classify {multivar} {
set cmdname ::punk::pipecmds::var_classify::_[pipecmd_namemapping $multivar]
if {[info commands $cmdname] ne ""} {
return [$cmdname]
}
#comma seems a natural choice to split varspecs,
#but also for list and dict subelement access
#/ normally indicates some sort of hierarchical separation - (e.g in filesytems)
#so / will indicate subelements e.g @0/1 for lindex $list 0 1
#set valsource_key_list [_split_var_key_at_unbracketed_comma $multivar]
set valsource_key_list [_split_patterns_memoized $multivar]
#mutually exclusive - atom/pin
#set map [list "" ' ^ &] ;#0 = default/var/not-yet-determined 1 = atom 2 = pin
#set var_class [lmap var $valsource_key_list {expr {([set m [lsearch $map [string index [lindex $var 0] 0]]] >= 0) ? [list $var $m] : [list $var 0]}}]
#0 - novar
#1 - atom '
#2 - pin ^
#3 - boolean &
#4 - integer
#5 - double
#6 - var
#7 - glob (no classifier and contains * or ?)
#8 - numeric
#9 - > (+)
#10 - < (-)
set var_names [list]
set var_class [list]
set varspecs_trimmed [list] ;#raw varspecs without pin/atom modifiers - or empty string for glob
set leading_classifiers [list "'" "&" "^" ]
set trailing_classifiers [list + -]
set possible_number_start [list - + . 0 1 2 3 4 5 6 7 8 9 > <]
foreach v_key $valsource_key_list {
lassign $v_key v key
set vname $v ;#default
set classes [list]
if {$v eq ""} {
lappend var_class [list $v_key 0]
lappend varspecs_trimmed $v_key
} else {
set lastchar [string index $v end]
switch -- $lastchar {
+ {
lappend classes 9
set vname [string range $v 0 end-1]
}
- {
lappend classes 10
set vname [string range $v 0 end-1]
}
}
set firstchar [string index $v 0]
switch -- $firstchar {
' {
lappend var_class [list $v_key 1]
#set vname [string range $v 1 end]
lappend varspecs_trimmed [list $vname $key]
}
^ {
lappend classes [list 2]
#use vname - may already have trailing +/- stripped
set vname [string range $vname 1 end]
set secondclassifier [string index $v 1]
switch -- $secondclassifier {
"&" {
#pinned boolean
lappend classes 3
set vname [string range $v 2 end]
}
"#" {
#pinned numeric comparison instead of string comparison
#e.g set x 2
# this should match: ^#x.= list 2.0
lappend classes 8
set vname [string range $vname 1 end]
}
"*" {
#pinned glob
lappend classes 7
set vname [string range $v 2 end]
}
}
#todo - check for second tag - & for pinned boolean?
#consider requiring ^# for numeric comparisons. currently no way to do a strictly string comparison on pinned variables.... default ^var really shouldn't be doing any magic.
#while we're at it.. pinned glob would be nice. ^*
#maybe even pinned scan ^% ? regex? ^/ or ^? these would be hard to have corresponding literals in the pattern mini-lang.
#These all limit the range of varnames permissible - which is no big deal.
lappend var_class [list $v_key $classes]
lappend varspecs_trimmed [list $vname $key]
}
& {
#we require boolean literals to be single-quoted so we can use cross-binding on boolean vars.
#ie &true is the variable true whereas &'true' or &'1' &'t' etc are literal booleans
#allow exception of &1 &0 to be literal booleans - because we disallow 0 & 1 as varnames in other contexts anyway - so it would be more consistent not to treat as varnames here.
lappend var_class [list $v_key 3]
set vname [string range $v 1 end]
lappend varspecs_trimmed [list $vname $key]
}
default {
if {([string first ? $v]) >=0 || ([string first * $v] >=0)} {
lappend var_class [list $v_key 7] ;#glob
#leave vname as the full glob
lappend varspecs_trimmed [list "" $key]
} else {
#scan vname not v - will either be same as v - or possibly stripped of trailing +/-
set numtestv [join [scan $vname %lld%s] ""] ;# handles octals (leading zeros), ok for use with bignums, decimal points and sci notation - fails to handle leading dot e.g .5
#leading . still need to test directly for double
if {[string is double -strict $vname] || [string is double -strict $numtestv]} {
if {[string is integer -strict $numtestv]} {
#this will pick up boolean 1 or 0 - but that's ok - they require "&" marker if boolean comparison desired
#integer test before double..
#note there is also string is wide (string is wideinteger) for larger ints..
lappend classes 4
lappend var_class [list $v_key $classes]
lappend varspecs_trimmed $v_key
} else {
#double
#sci notation 1e123 etc
#also large numbers like 1000000000 - even without decimal point - (tcl bignum)
lappend classes 5
lappend var_class [list $v_key $classes]
lappend varspecs_trimmed $v_key
}
} else {
lappend var_class [list $v_key 6] ;#var
lappend varspecs_trimmed $v_key
}
}
}
}
}
lappend var_names $vname
}
set result [list var_names $var_names var_class $var_class varspecs_trimmed $varspecs_trimmed]
proc $cmdname {} [list return $result]
#JMN
#debug.punk.pipe.compile {proc $cmdname}
return $result
}
#*** !doctools
#[list_end] [comment {--- end definitions namespace punk::pipe::lib ---}]
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
#*** !doctools
#[section Internal]
#tcl::namespace::eval punk::pipe::system {
#*** !doctools
#[subsection {Namespace punk::pipe::system}]
#[para] Internal functions that are not part of the API
#}
# == === === === === === === === === === === === === === ===
# Sample 'about' function with punk::args documentation
# == === === === === === === === === === === === === === ===
tcl::namespace::eval punk::pipe {
tcl::namespace::export {[a-z]*} ;# Convention: export all lowercase
variable PUNKARGS
variable PUNKARGS_aliases
lappend PUNKARGS [list {
@id -id "(package)punk::pipe"
@package -name "punk::pipe" -help\
"Package
Description"
}]
namespace eval argdoc {
#namespace for custom argument documentation
proc package_name {} {
return punk::pipe
}
proc about_topics {} {
#info commands results are returned in an arbitrary order (like array keys)
set topic_funs [info commands [namespace current]::get_topic_*]
set about_topics [list]
foreach f $topic_funs {
set tail [namespace tail $f]
lappend about_topics [string range $tail [string length get_topic_] end]
}
return $about_topics
}
proc default_topics {} {return [list Description outline *]}
# -------------------------------------------------------------
# get_topic_ functions add more to auto-include in about topics
# -------------------------------------------------------------
proc get_topic_Description {} {
punk::args::lib::tstr [string trim {
punk pipeline features
} \n]
}
proc get_topic_License {} {
return "MIT"
}
proc get_topic_Version {} {
return $::punk::pipe::version
}
proc get_topic_Contributors {} {
set authors {{Julian Noble <julian@precisium.com.au>}}
set contributors ""
foreach a $authors {
append contributors $a \n
}
if {[string index $contributors end] eq "\n"} {
set contributors [string range $contributors 0 end-1]
}
return $contributors
}
proc get_topic_outline {} {
punk::args::lib::tstr -return string {
todo..
}
}
# -------------------------------------------------------------
}
# we re-use the argument definition from punk::args::standard_about and override some items
set overrides [dict create]
dict set overrides @id -id "::punk::pipe::about"
dict set overrides @cmd -name "punk::pipe::about"
dict set overrides @cmd -help [string trim [punk::args::lib::tstr {
About punk::pipe
}] \n]
dict set overrides topic -choices [list {*}[punk::pipe::argdoc::about_topics] *]
dict set overrides topic -choicerestricted 1
dict set overrides topic -default [punk::pipe::argdoc::default_topics] ;#if -default is present 'topic' will always appear in parsed 'values' dict
set newdef [punk::args::resolved_def -antiglobs -package_about_namespace -override $overrides ::punk::args::package::standard_about *]
lappend PUNKARGS [list $newdef]
proc about {args} {
package require punk::args
#standard_about accepts additional choices for topic - but we need to normalize any abbreviations to full topic name before passing on
set argd [punk::args::parse $args withid ::punk::pipe::about]
lassign [dict values $argd] _leaders opts values _received
punk::args::package::standard_about -package_about_namespace ::punk::pipe::argdoc {*}$opts {*}[dict get $values topic]
}
}
# end of sample 'about' function
# == === === === === === === === === === === === === === ===
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::pipe
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
## Ready
package provide punk::pipe [tcl::namespace::eval punk::pipe {
variable pkg punk::pipe
variable version
set version 1.0
}]
return
#*** !doctools
#[manpage_end]

22
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punk/repl/codethread-0.1.1.tm

@ -114,6 +114,10 @@ tcl::namespace::eval punk::repl::codethread {
variable output_stdout ""
variable output_stderr ""
#review/test
catch {package require punk::ns}
catch {package rquire punk::repl}
#variable xyz
#*** !doctools
@ -191,9 +195,14 @@ tcl::namespace::eval punk::repl::codethread {
#shennanigans to keep compiled script around after call.
#otherwise when $script goes out of scope - internal rep of vars set in script changes.
#The shimmering may be no big deal(?) - but debug/analysis using tcl::unsupported::representation becomes impossible.
interp eval code [list ::punk::lib::set_clone ::codeinterp::clonescript $script] ;#like objclone
#interp eval code [list ::punk::lib::set_clone ::codeinterp::clonescript $script] ;#like objclone
interp eval code [list ::codeinterp::set_clone ::codeinterp::clonescript $script] ;#like objclone
interp eval code {lappend ::codeinterp::run_command_cache $::codeinterp::clonescript}
interp eval code {
lappend ::codeinterp::run_command_cache $::codeinterp::clonescript
#lappend ::codeinterp::run_command_cache $::codeinterp::clonescript
if {[llength $::codeinterp::run_command_cache] > 2000} {
set ::codeinterp::run_command_cache [lrange $::codeinterp::run_command_cache 1750 end][unset ::codeinterp::run_command_cache]
}
@ -205,10 +214,19 @@ tcl::namespace::eval punk::repl::codethread {
package require punk::ns
punk::ns::nseval_ifexists $::punk::ns::ns_current $::codeinterp::clonescript
} else {
if {![namespace exists $::punk::ns::ns_current]} {
namespace eval $::punk::ns::ns_current {
puts stderr "Created namespace: $::punk::ns::ns_current"
}
}
tcl::namespace::inscope $::punk::ns::ns_current $::codeinterp::clonescript
}
}
} result]
#temp test for subshell experimentation
#if {$status == 1} {
# puts stderr "--codethread::runscript error--------\n$::errorInfo"
#}
flush stdout

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

@ -107,14 +107,16 @@ namespace eval punk::repo {
}
#lappend PUNKARGS [list -dynamic 1 {
#lappend PUNKARGS [list {
# @dynamic
# @id -id ::punk::repo::fossil_proxy
# @cmd -name fossil -help "fossil executable
# "
# @argdisplay -header "fossil help" -body {${[runout -n fossil help]}}
# } ""]
lappend PUNKARGS [list -dynamic 1 {
lappend PUNKARGS [list {
@dynamic
@id -id ::punk::repo::fossil_proxy
@cmd -name fossil -help "fossil executable"
${[punk::repo::get_fossil_usage]}
@ -123,20 +125,24 @@ namespace eval punk::repo {
#experiment
lappend PUNKARGS [list -dynamic 1 {
lappend PUNKARGS [list {
@dynamic
@id -id "::punk::repo::fossil_proxy diff"
@cmd -name "fossil diff" -help "fossil diff
"
@argdisplay -header "fossil help diff" -body {${[runout -n fossil help diff]}}
} ""]
lappend PUNKARGS [list -dynamic 1 {
lappend PUNKARGS [list {
#todo - remove this comment - testing dynamic directive
@dynamic
@id -id "::punk::repo::fossil_proxy add"
@cmd -name "fossil add" -help "fossil add
"
@argdisplay -header "fossil help add" -body {${[runout -n fossil help add]}}
} ""]
#TODO
#lappend PUNKARGS [list -dynamic 1 {
#lappend PUNKARGS [list {
# @dynamic
# @id -glob 1 -id "::punk::repo::fossil_proxy *" -aliases {fs}
# @cmd -name "fossil add" -help "fossil add
# "
@ -1699,12 +1705,10 @@ namespace eval punk::repo::lib {
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++
if {![info exists ::punk::args::register::NAMESPACES]} {
namespace eval ::punk::args::register {
set ::punk::args::register::NAMESPACES [list] ;#use fully qualified so 8.6 doesn't find existing var in global namespace
}
}
#use fully qualified so 8.6 doesn't find existing var in global namespace
lappend ::punk::args::register::NAMESPACES ::punk::repo
}
# ++ +++ +++ +++ +++ +++ +++ +++ +++ +++ +++

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

@ -194,6 +194,12 @@ tcl::namespace::eval punk::zip {
Defaults to the value of base. If empty string
is given to -resultrelative the paths returned
are effectively absolute paths."
-emptydirs -default 0 -type boolean -help\
"Whether to include directory trees in the result which had no
matches for the given fileglobs.
Intermediate dirs are always returned if there is a match with
fileglobs further down even if -emptdirs is 0.
"
-excludes -default "" -help "list of glob expressions to match against files and exclude"
-subpath -default "" -help\
"May contain glob chars for folder elements"
@ -205,6 +211,7 @@ tcl::namespace::eval punk::zip {
set fileglobs [dict get $argd values fileglobs]
set subpath [dict get $argd opts -subpath]
set excludes [dict get $argd opts -excludes]
set emptydirs [dict get $argd opts -emptydirs]
set received [dict get $argd received]
@ -242,13 +249,32 @@ tcl::namespace::eval punk::zip {
if {!$excluded} {lappend result [file join $prefix $file]}
}
foreach dir [glob -nocomplain -tails -types d -directory $base -- [file join $subpath *]] {
set subdir_entries [walk -subpath $dir -excludes $excludes $base {*}$fileglobs]
if {[llength $subdir_entries]>0} {
set submatches [walk -subpath $dir -emptydirs $emptydirs -excludes $excludes $base {*}$fileglobs]
set subdir_entries [list]
set thisdir_match [list]
set has_file 0
foreach sd $submatches {
set fullpath [file join $prefix $sd] ;#file join destroys trailing slash
if {[string index $sd end] eq "/"} {
lappend subdir_entries $fullpath/
} else {
set has_file 1
lappend subdir_entries $fullpath
}
}
if {$emptydirs} {
set thisdir_match [list "[file join $prefix $dir]/"]
} else {
if {$has_file} {
set thisdir_match [list "[file join $prefix $dir]/"]
} else {
set subdir_entries [list]
}
}
#NOTE: trailing slash required for entries to be recognised as 'file type' = "directory"
#This is true for 2024 Tcl9 mounted zipfs at least. zip utilities such as 7zip seem(icon correct) to recognize dirs with or without trailing slash
#Although there are attributes on some systems to specify if entry is a directory - it appears trailing slash should always be used for folder names.
set result [list {*}$result "[file join $prefix $dir]/" {*}$subdir_entries]
}
set result [list {*}$result {*}$thisdir_match {*}$subdir_entries]
}
return $result
}

111
src/project_layouts/custom/_project/punk.project-0.1/src/bootsupport/modules/punkcheck-0.1.0.tm

@ -1170,6 +1170,7 @@ namespace eval punkcheck {
}
#skip writing punkcheck during checksum/timestamp checks
#todo - punk::args - fetch from punkcheck::install (with overrides)
proc install_tm_files {srcdir basedir args} {
set defaults [list\
-glob *.tm\
@ -1209,13 +1210,71 @@ namespace eval punkcheck {
return [lindex $args end]
}
}
lappend PUNKARGS [list {
@id -id ::punkcheck::install
@cmd -name ::punkcheck::install -help\
"Unidirectional file transfer to possibly non-empty target folder."
@leaders -min 2 -max 2
srcdir -type directory
tgtdir -type directory
-call-depth-internal -type integer -default 0 -help "(internal recursion tracker)"
-subdirlist -type list -default "" -help "(primarily internal - length generally matching -call-depth-internal)"
-max_depth -type integer -default 1000 -help\
"Deepest subdirectory - use -1 for no limit."
-createdir -type boolean -default 0 -help\
"Whether to create the folder at tgtdir.
Any required subdirectories are created regardless of this setting."
-createempty -type boolean -default 0 -help\
"Whether to create folders at target that had no matches for our glob"
-glob -type string -default "*" -help\
"Pattern matching for source file(s) to copy. Can be glob based or exact match."
-antiglob_file_core -default {${[::punkcheck::default_antiglob_file_core]}}
-antiglob_file -default ""
-antiglob_dir_core -default {${[::punkcheck::default_antiglob_dir_core]}}
-antiglob_dir -default ""
-antiglob_paths -default {}
-overwrite -default no-targets\
-choices {no-targets newer-targets older-targets all-targets installedsourcechanged-targets synced-targets}\
-choicecolumns 1\
-choicelabels {
no-targets "only copy files that are missing at the target"
newer-targets "copy files with older source timestamp over newer
target timestamp and those missing at the target
(a form of 'restore' operation)"
older-targets "copy files with newer source timestamp over older
target timestamp and those missing at the target"
all-targets "copy regardless of timestamp at target"
installedsourcechanged-targets "copy if the target doesn't exist or the source changed"
synced-targets "copy if the target doesn't exist or the source changed
and the target cksum is the same as the last INSTALL-RECORD -targets_cksums entry"
}
-source_checksum -default comparestore -choicecolumns 3 -choices {compare store comparestore false true}\
-choicelabels {
true "same as comparestore"
}
-punkcheck_folder -default target -choices {target source project} -choicerestricted 0 -help\
"The location of the .punkcheck file to track installations and checksums.
The default value 'target' is generally recommended.
Can also be an absolute path to a folder."
-punkcheck_records -default "" -help\
"Empty string or a parsed TDL records structure.
e.g
{tag FILEINFO -<opt> <val>... body {
{tag INSTALL-RECORD -<opt> <val>... body {<sublist>}}
...
}...
}"
-installer -default "punkcheck::install" -help\
"A user nominated string that is stored in the .punkcheck file
This might be the name of a script or installation process."
}]
## unidirectional file transfer to possibly non empty folder
#default of -overwrite no-targets will only copy files that are missing at the target
# -overwrite newer-targets will copy files with older source timestamp over newer target timestamp and those missing at the target (a form of 'restore' operation)
# -overwrite older-targets will copy files with newer source timestamp over older target timestamp and those missing at the target
# -overwrite all-targets will copy regardless of timestamp at target
# -overwrite installedsourcechanged-targets will copy if the target doesn't exist or the source changed
# -overwrite synced-targets will copy if the target doesn't exist or the source changed and the target cksum is the same as the last INSTALL-RECORD targets_cksums entry
# -overwrite synced-targets will copy if the target doesn't exist or the source changed and the target cksum is the same as the last INSTALL-RECORD -targets_cksums entry
# review - timestamps unreliable
# - what about slightly mismatched system clocks and mounted filesystems? caller responsibility to verify first?
# if timestamp exactly equal - should we check content-hash? This is presumably only likely to occur deliberately(maliciously?)
@ -1243,6 +1302,7 @@ namespace eval punkcheck {
-max_depth 1000\
-subdirlist {}\
-createdir 0\
-createempty 0\
-glob *\
-antiglob_file_core "\uFFFF"\
-antiglob_file "" \
@ -1271,13 +1331,14 @@ namespace eval punkcheck {
#(as opposed to a more 'standard' mechanism of adjusting srcdir & tgtdir as we move down the tree)
#It comes from build_modules_from_source_to_base where we need to keep track of position relative to our targetdir starting point to handle submodules e.g pkg::something::mypkg-0.1.tm
#It could have been handled with some other parameter such as -depth, but this -subdirlist mechanism, whilst perhaps not beautiful, is straightforward enough
#and may be less error prone than doing slightly more opaue path manipulations at each recursion level to determine where we started
#and may be less error prone than doing slightly more opaque path manipulations at each recursion level to determine where we started
#For consistency - we'll use the same mechanism in various recursive directory walking procedures such as this one.
set CALLDEPTH [dict get $opts -call-depth-internal] ;#added for extra debug/sanity checking - clearer test for initial function call ie CALLDPEPTH = 0
set CALLDEPTH [dict get $opts -call-depth-internal] ;#added for extra debug/sanity checking - clearer test for initial function call ie CALLDEPTH = 0
set max_depth [dict get $opts -max_depth] ;# -1 for no limit
set subdirlist [dict get $opts -subdirlist] ;# generally should be same length as CALLDEPTH - but user could prefill
set fileglob [dict get $opts -glob]
set createdir [dict get $opts -createdir] ;#defaults to zero to help avoid mistakes with initial target dir - required target subdirs are created regardless of this setting
set opt_createempty [dict get $opts -createempty]
if {$CALLDEPTH == 0} {
#expensive to normalize but we need to do it at least once
@ -1285,6 +1346,13 @@ namespace eval punkcheck {
set tgtdir [file normalize $tgtdir]
if {$createdir} {
file mkdir $tgtdir
} else {
if {![file exists $tgtdir]} {
error "punkcheck::install base target dir:'$tgtdir' doesn't exist (srcdir:$srcdir tgtdir:$tgtdir args:'$args')"
}
}
if {([file type $srcdir] ni [list directory]) || ([file type $tgtdir] ni [list directory])} {
error "punkcheck::install requires source and target dirs to be of type 'directory' type current source: [file type $srcdir] type current target: [file type $tgtdir]"
}
#now the values we build from these will be properly cased
}
@ -1450,13 +1518,7 @@ namespace eval punkcheck {
if {![file exists $current_source_dir]} {
error "copy_files_from_source_to_target current source dir:'$current_source_dir' doesn't exist (srcdir:$srcdir tgtdir:$tgtdir args:'$args')"
}
if {![file exists $current_target_dir]} {
error "copy_files_from_source_to_target current target dir:'$current_target_dir' doesn't exist (srcdir:$srcdir tgtdir:$tgtdir args:'$args')"
}
if {([file type $current_source_dir] ni [list directory]) || ([file type $current_target_dir] ni [list directory])} {
error "copy_files_from_source_to_target requires source and target dirs to be of type 'directory' type current source: [file type $current_source_dir] type current target: [file type $current_target_dir]"
error "punkcheck::install current source dir:'$current_source_dir' doesn't exist (srcdir:$srcdir tgtdir:$tgtdir args:'$args')"
}
set files_copied [list]
@ -1501,6 +1563,12 @@ namespace eval punkcheck {
# }
#}
if {[llength $match_list]} {
#example - target dir has a file where there is a directory at the source
if {[file exists $current_target_dir] && ([file type $current_target_dir] ni [list directory])} {
error "punkcheck::install target subfolder $current_target_dir exists but is not of type 'directory'. Type current target folder: [file type $current_target_dir]"
}
}
#proc get_relativecksum_from_base_and_fullpath {base fullpath args}
@ -1579,10 +1647,12 @@ namespace eval punkcheck {
set is_skip 0
if {$overwrite_what eq "all-targets"} {
file mkdir $current_target_dir
file copy -force $current_source_dir/$m $current_target_dir
lappend files_copied $current_source_dir/$m
} else {
if {![file exists $current_target_dir/$m]} {
file mkdir $current_target_dir
file copy $current_source_dir/$m $current_target_dir
set new_tgt_cksum_info [punk::mix::base::lib::cksum_path $current_target_dir/$m]
lappend files_copied $current_source_dir/$m
@ -1592,6 +1662,7 @@ namespace eval punkcheck {
installedsourcechanged-targets {
if {[llength $changed]} {
#An unrecorded installation is considered a source change (from unknown/unrecorded source to recorded)
file mkdir $current_target_dir
file copy -force $current_source_dir/$m $current_target_dir
set new_tgt_cksum_info [punk::mix::base::lib::cksum_path $current_target_dir/$m]
lappend files_copied $current_source_dir/$m
@ -1619,6 +1690,7 @@ namespace eval punkcheck {
set target_cksum_compare "norecord"
}
if {$is_target_unmodified_since_install} {
file mkdir $current_target_dir
file copy -force $current_source_dir/$m $current_target_dir
set new_tgt_cksum_info [punk::mix::base::lib::cksum_path $current_target_dir/$m]
lappend files_copied $current_source_dir/$m
@ -1642,6 +1714,12 @@ namespace eval punkcheck {
}
}
}
#target dir was created as necessary if files matched above
#now ensure target dir exists if -createempty true
if {$opt_createempty && ![file exists $current_target_dir]} {
file mkdir $current_target_dir
}
set ts_now [clock microseconds]
@ -1724,10 +1802,9 @@ namespace eval punkcheck {
continue
}
if {![file exists $current_target_dir/$d]} {
file mkdir $current_target_dir/$d
}
#if {![file exists $current_target_dir/$d]} {
# file mkdir $current_target_dir/$d
#}
set sub_opts_1 [list\
@ -2096,8 +2173,10 @@ namespace eval punkcheck {
namespace eval ::punk::args::register {
#use fully qualified so 8.6 doesn't find existing var in global namespace
lappend ::punk::args::register::NAMESPACES ::punkcheck
}

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

@ -64,6 +64,8 @@ namespace eval punkcheck::cli {
#vfs can mask mounted files - so we can't just use 'file type' or glob with -type f
##set files [glob -nocomplain -dir $fullpath -type f *]
package require punk::nav::fs
#TODO - get all files in tree!!!
set folderinfo [punk::nav::fs::dirfiles_dict $fullpath]
set files [concat [dict get $folderinfo files] [dict get $folderinfo underlayfiles]]
}

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

Loading…
Cancel
Save